mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-25 03:08:54 +01:00
all: separate the rendering thread from the main thread
Updates #1704 Closes #2512
This commit is contained in:
parent
0fc21470e2
commit
34941ca083
@ -17,6 +17,7 @@ package metal
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
@ -86,7 +87,15 @@ func NewGraphics() (graphicsdriver.Graphics, error) {
|
|||||||
return nil, fmt.Errorf("metal: mtl.CreateSystemDefaultDevice failed")
|
return nil, fmt.Errorf("metal: mtl.CreateSystemDefaultDevice failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Graphics{}, nil
|
g := &Graphics{}
|
||||||
|
|
||||||
|
if runtime.GOOS != "ios" {
|
||||||
|
// Initializing a Metal device and a layer must be done in the main thread on macOS.
|
||||||
|
if err := g.view.initialize(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return g, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Graphics) Begin() error {
|
func (g *Graphics) Begin() error {
|
||||||
@ -356,9 +365,12 @@ func (g *Graphics) Initialize() error {
|
|||||||
g.dsss = map[stencilMode]mtl.DepthStencilState{}
|
g.dsss = map[stencilMode]mtl.DepthStencilState{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if runtime.GOOS == "ios" {
|
||||||
|
// Initializing a Metal device and a layer must be done in the render thread on iOS.
|
||||||
if err := g.view.initialize(); err != nil {
|
if err := g.view.initialize(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if g.transparent {
|
if g.transparent {
|
||||||
g.view.ml.SetOpaque(false)
|
g.view.ml.SetOpaque(false)
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,7 @@ func (v *view) update() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Should this be called on the main thread?
|
||||||
cocoaWindow := ns.NewWindow(v.window)
|
cocoaWindow := ns.NewWindow(v.window)
|
||||||
cocoaWindow.ContentView().SetLayer(v.ml)
|
cocoaWindow.ContentView().SetLayer(v.ml)
|
||||||
cocoaWindow.ContentView().SetWantsLayer(true)
|
cocoaWindow.ContentView().SetWantsLayer(true)
|
||||||
|
@ -16,6 +16,7 @@ package thread
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
// OSThread represents an OS thread.
|
// OSThread represents an OS thread.
|
||||||
@ -32,12 +33,13 @@ func NewOSThread() *OSThread {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loop starts the thread loop until Stop is called.
|
// Loop starts the thread loop until Stop is called on the current OS thread.
|
||||||
//
|
|
||||||
// It is assumed that an OS thread is fixed by runtime.LockOSThread when Loop is called.
|
|
||||||
//
|
//
|
||||||
// Loop must be called on the thread.
|
// Loop must be called on the thread.
|
||||||
func (t *OSThread) Loop(ctx context.Context) error {
|
func (t *OSThread) Loop(ctx context.Context) error {
|
||||||
|
runtime.LockOSThread()
|
||||||
|
defer runtime.UnlockOSThread()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case fn := <-t.funcs:
|
case fn := <-t.funcs:
|
||||||
|
@ -16,6 +16,7 @@ package ui
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/atlas"
|
"github.com/hajimehoshi/ebiten/v2/internal/atlas"
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/buffered"
|
"github.com/hajimehoshi/ebiten/v2/internal/buffered"
|
||||||
@ -57,6 +58,8 @@ type context struct {
|
|||||||
isOffscreenModified bool
|
isOffscreenModified bool
|
||||||
|
|
||||||
skipCount int
|
skipCount int
|
||||||
|
|
||||||
|
setContextOnce sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
func newContext(game Game) *context {
|
func newContext(game Game) *context {
|
||||||
|
@ -27,21 +27,24 @@ import (
|
|||||||
func (u *userInterfaceImpl) Run(game Game, options *RunOptions) error {
|
func (u *userInterfaceImpl) Run(game Game, options *RunOptions) error {
|
||||||
u.context = newContext(game)
|
u.context = newContext(game)
|
||||||
|
|
||||||
// Initialize the main thread first so the thread is available at u.run (#809).
|
|
||||||
u.mainThread = thread.NewOSThread()
|
|
||||||
|
|
||||||
graphicscommand.SetRenderThread(u.mainThread)
|
|
||||||
|
|
||||||
u.setRunning(true)
|
u.setRunning(true)
|
||||||
defer u.setRunning(false)
|
defer u.setRunning(false)
|
||||||
|
|
||||||
if err := u.init(options); err != nil {
|
if err := u.initOnMainThread(options); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u.mainThread = thread.NewOSThread()
|
||||||
|
u.renderThread = thread.NewOSThread()
|
||||||
|
graphicscommand.SetRenderThread(u.renderThread)
|
||||||
|
|
||||||
ctx, cancel := stdcontext.WithCancel(stdcontext.Background())
|
ctx, cancel := stdcontext.WithCancel(stdcontext.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
_ = u.renderThread.Loop(ctx)
|
||||||
|
}()
|
||||||
|
|
||||||
ch := make(chan error, 1)
|
ch := make(chan error, 1)
|
||||||
go func() {
|
go func() {
|
||||||
defer close(ch)
|
defer close(ch)
|
@ -26,12 +26,13 @@ func (u *userInterfaceImpl) Run(game Game, options *RunOptions) error {
|
|||||||
|
|
||||||
// Initialize the main thread first so the thread is available at u.run (#809).
|
// Initialize the main thread first so the thread is available at u.run (#809).
|
||||||
u.mainThread = thread.NewNoopThread()
|
u.mainThread = thread.NewNoopThread()
|
||||||
graphicscommand.SetRenderThread(u.mainThread)
|
u.renderThread = thread.NewNoopThread()
|
||||||
|
graphicscommand.SetRenderThread(u.renderThread)
|
||||||
|
|
||||||
u.setRunning(true)
|
u.setRunning(true)
|
||||||
defer u.setRunning(false)
|
defer u.setRunning(false)
|
||||||
|
|
||||||
if err := u.init(options); err != nil {
|
if err := u.initOnMainThread(options); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -111,6 +111,7 @@ type userInterfaceImpl struct {
|
|||||||
glContextSetOnce sync.Once
|
glContextSetOnce sync.Once
|
||||||
|
|
||||||
mainThread threadInterface
|
mainThread threadInterface
|
||||||
|
renderThread threadInterface
|
||||||
m sync.RWMutex
|
m sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -796,8 +797,7 @@ event:
|
|||||||
u.framebufferSizeCallbackCh = nil
|
u.framebufferSizeCallbackCh = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// init must be called from the main thread.
|
func (u *userInterfaceImpl) initOnMainThread(options *RunOptions) error {
|
||||||
func (u *userInterfaceImpl) init(options *RunOptions) error {
|
|
||||||
glfw.WindowHint(glfw.AutoIconify, glfw.False)
|
glfw.WindowHint(glfw.AutoIconify, glfw.False)
|
||||||
|
|
||||||
decorated := glfw.False
|
decorated := glfw.False
|
||||||
@ -1004,7 +1004,8 @@ func (u *userInterfaceImpl) update() (float64, float64, error) {
|
|||||||
|
|
||||||
func (u *userInterfaceImpl) loopGame() error {
|
func (u *userInterfaceImpl) loopGame() error {
|
||||||
defer u.mainThread.Call(func() {
|
defer u.mainThread.Call(func() {
|
||||||
u.window.Destroy()
|
// An explicit destorying a window tries to delete a GL context on the main thread on Windows (wglDeleteContext),
|
||||||
|
// but this causes an error unfortunately.
|
||||||
glfw.Terminate()
|
glfw.Terminate()
|
||||||
})
|
})
|
||||||
for {
|
for {
|
||||||
@ -1026,7 +1027,7 @@ func (u *userInterfaceImpl) updateGame() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
u.glContextSetOnce.Do(func() {
|
u.glContextSetOnce.Do(func() {
|
||||||
u.mainThread.Call(func() {
|
u.renderThread.Call(func() {
|
||||||
if u.graphicsDriver.IsGL() {
|
if u.graphicsDriver.IsGL() {
|
||||||
u.window.MakeContextCurrent()
|
u.window.MakeContextCurrent()
|
||||||
}
|
}
|
||||||
@ -1037,13 +1038,13 @@ func (u *userInterfaceImpl) updateGame() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
u.mainThread.Call(func() {
|
u.renderThread.Call(func() {
|
||||||
// Call updateVsync even though fpsMode is not updated.
|
// Call updateVsync even though fpsMode is not updated.
|
||||||
// When toggling to fullscreen, vsync state might be reset unexpectedly (#1787).
|
// When toggling to fullscreen, vsync state might be reset unexpectedly (#1787).
|
||||||
u.updateVsync()
|
u.updateVsyncOnRenderThread()
|
||||||
|
|
||||||
// This works only for OpenGL.
|
// This works only for OpenGL.
|
||||||
u.swapBuffers()
|
u.swapBuffersOnRenderThread()
|
||||||
})
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -1083,8 +1084,7 @@ func (u *userInterfaceImpl) updateIconIfNeeded() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// swapBuffers must be called from the main thread.
|
func (u *userInterfaceImpl) swapBuffersOnRenderThread() {
|
||||||
func (u *userInterfaceImpl) swapBuffers() {
|
|
||||||
if u.graphicsDriver.IsGL() {
|
if u.graphicsDriver.IsGL() {
|
||||||
u.window.SwapBuffers()
|
u.window.SwapBuffers()
|
||||||
}
|
}
|
||||||
@ -1260,8 +1260,7 @@ func (u *userInterfaceImpl) minimumWindowWidth() int {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateVsync must be called on the main thread.
|
func (u *userInterfaceImpl) updateVsyncOnRenderThread() {
|
||||||
func (u *userInterfaceImpl) updateVsync() {
|
|
||||||
if u.graphicsDriver.IsGL() {
|
if u.graphicsDriver.IsGL() {
|
||||||
// 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.
|
||||||
|
@ -67,6 +67,7 @@ func (u *userInterfaceImpl) Run(game Game, options *RunOptions) error {
|
|||||||
u.graphicsDriver = g
|
u.graphicsDriver = g
|
||||||
nintendosdk.InitializeGame()
|
nintendosdk.InitializeGame()
|
||||||
for {
|
for {
|
||||||
|
// TODO: Make a separate thread for rendering (#2512).
|
||||||
nintendosdk.BeginFrame()
|
nintendosdk.BeginFrame()
|
||||||
gamepad.Update()
|
gamepad.Update()
|
||||||
u.updateInputState()
|
u.updateInputState()
|
||||||
|
Loading…
Reference in New Issue
Block a user