mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-11-10 04:57:26 +01:00
internal/graphicsdriver: flush commands asynchronously whenever possible
Closes #2664
This commit is contained in:
parent
a7e4665f71
commit
f81dbd9288
@ -19,6 +19,7 @@ import (
|
||||
"image"
|
||||
"math"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/debug"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphics"
|
||||
@ -36,6 +37,7 @@ type command interface {
|
||||
fmt.Stringer
|
||||
|
||||
Exec(graphicsDriver graphicsdriver.Graphics, indexOffset int) error
|
||||
NeedsSync() bool
|
||||
}
|
||||
|
||||
type drawTrianglesCommandPool struct {
|
||||
@ -72,6 +74,8 @@ type commandQueue struct {
|
||||
drawTrianglesCommandPool drawTrianglesCommandPool
|
||||
|
||||
uint32sBuffer uint32sBuffer
|
||||
|
||||
err atomic.Value
|
||||
}
|
||||
|
||||
// theCommandQueues is the set of command queues for the current process.
|
||||
@ -179,18 +183,39 @@ func (q *commandQueue) Enqueue(command command) {
|
||||
}
|
||||
|
||||
// Flush flushes the command queue.
|
||||
func (q *commandQueue) Flush(graphicsDriver graphicsdriver.Graphics, endFrame bool, swapBuffersForGL func()) (err error) {
|
||||
func (q *commandQueue) Flush(graphicsDriver graphicsdriver.Graphics, endFrame bool, swapBuffersForGL func()) error {
|
||||
if err := q.err.Load(); err != nil {
|
||||
return err.(error)
|
||||
}
|
||||
|
||||
var sync bool
|
||||
for _, c := range q.commands {
|
||||
if c.NeedsSync() {
|
||||
sync = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var flushErr error
|
||||
runOnRenderThread(func() {
|
||||
err = q.flush(graphicsDriver, endFrame)
|
||||
if err != nil {
|
||||
if err := q.flush(graphicsDriver, endFrame); err != nil {
|
||||
if sync {
|
||||
return
|
||||
}
|
||||
q.err.Store(err)
|
||||
return
|
||||
}
|
||||
|
||||
if endFrame && swapBuffersForGL != nil {
|
||||
swapBuffersForGL()
|
||||
}
|
||||
})
|
||||
return
|
||||
}, sync)
|
||||
|
||||
if sync && flushErr != nil {
|
||||
return flushErr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// flush must be called the main thread.
|
||||
@ -346,6 +371,10 @@ func (c *drawTrianglesCommand) Exec(graphicsDriver graphicsdriver.Graphics, inde
|
||||
return graphicsDriver.DrawTriangles(c.dst.image.ID(), imgs, c.shader.shader.ID(), c.dstRegions, indexOffset, c.blend, c.uniforms, c.evenOdd)
|
||||
}
|
||||
|
||||
func (c *drawTrianglesCommand) NeedsSync() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *drawTrianglesCommand) numVertices() int {
|
||||
return len(c.vertices)
|
||||
}
|
||||
@ -452,6 +481,10 @@ func (c *writePixelsCommand) Exec(graphicsDriver graphicsdriver.Graphics, indexO
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *writePixelsCommand) NeedsSync() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type readPixelsCommand struct {
|
||||
result []byte
|
||||
img *Image
|
||||
@ -466,6 +499,10 @@ func (c *readPixelsCommand) Exec(graphicsDriver graphicsdriver.Graphics, indexOf
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *readPixelsCommand) NeedsSync() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *readPixelsCommand) String() string {
|
||||
return fmt.Sprintf("read-pixels: image: %d", c.img.id)
|
||||
}
|
||||
@ -485,6 +522,10 @@ func (c *disposeImageCommand) Exec(graphicsDriver graphicsdriver.Graphics, index
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *disposeImageCommand) NeedsSync() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// disposeShaderCommand represents a command to dispose a shader.
|
||||
type disposeShaderCommand struct {
|
||||
target *Shader
|
||||
@ -500,6 +541,10 @@ func (c *disposeShaderCommand) Exec(graphicsDriver graphicsdriver.Graphics, inde
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *disposeShaderCommand) NeedsSync() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// newImageCommand represents a command to create an empty image with given width and height.
|
||||
type newImageCommand struct {
|
||||
result *Image
|
||||
@ -523,6 +568,10 @@ func (c *newImageCommand) Exec(graphicsDriver graphicsdriver.Graphics, indexOffs
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *newImageCommand) NeedsSync() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// newShaderCommand is a command to create a shader.
|
||||
type newShaderCommand struct {
|
||||
result *Shader
|
||||
@ -543,6 +592,10 @@ func (c *newShaderCommand) Exec(graphicsDriver graphicsdriver.Graphics, indexOff
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *newShaderCommand) NeedsSync() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type isInvalidatedCommand struct {
|
||||
result bool
|
||||
image *Image
|
||||
@ -557,11 +610,15 @@ func (c *isInvalidatedCommand) Exec(graphicsDriver graphicsdriver.Graphics, inde
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *isInvalidatedCommand) NeedsSync() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// InitializeGraphicsDriverState initialize the current graphics driver state.
|
||||
func InitializeGraphicsDriverState(graphicsDriver graphicsdriver.Graphics) (err error) {
|
||||
runOnRenderThread(func() {
|
||||
err = graphicsDriver.Initialize()
|
||||
})
|
||||
}, true)
|
||||
return
|
||||
}
|
||||
|
||||
@ -571,7 +628,7 @@ func ResetGraphicsDriverState(graphicsDriver graphicsdriver.Graphics) (err error
|
||||
if r, ok := graphicsDriver.(graphicsdriver.Resetter); ok {
|
||||
runOnRenderThread(func() {
|
||||
err = r.Reset()
|
||||
})
|
||||
}, true)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -581,7 +638,7 @@ func MaxImageSize(graphicsDriver graphicsdriver.Graphics) int {
|
||||
var size int
|
||||
runOnRenderThread(func() {
|
||||
size = graphicsDriver.MaxImageSize()
|
||||
})
|
||||
}, true)
|
||||
return size
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,14 @@ func SetRenderThread(thread thread.Thread) {
|
||||
}
|
||||
|
||||
// runOnRenderThread calls f on the rendering thread.
|
||||
func runOnRenderThread(f func()) {
|
||||
theRenderThread.Call(f)
|
||||
func runOnRenderThread(f func(), sync bool) {
|
||||
if sync {
|
||||
theRenderThread.Call(f)
|
||||
return
|
||||
}
|
||||
|
||||
// As the current thread doesn't have a capacity in a channel,
|
||||
// CallAsync should block when the previously-queued task is not executed yet.
|
||||
// This blocking is expected as double-buffering is used.
|
||||
theRenderThread.CallAsync(f)
|
||||
}
|
||||
|
@ -22,40 +22,49 @@ import (
|
||||
type Thread interface {
|
||||
Loop(ctx context.Context) error
|
||||
Call(f func())
|
||||
CallAsync(f func())
|
||||
|
||||
private()
|
||||
}
|
||||
|
||||
type queueItem struct {
|
||||
f func()
|
||||
sync bool
|
||||
}
|
||||
|
||||
// OSThread represents an OS thread.
|
||||
type OSThread struct {
|
||||
funcs chan func()
|
||||
funcs chan queueItem
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
// NewOSThread creates a new thread.
|
||||
//
|
||||
// queueSize indicates the function queue size. This matters when you use CallAsync.
|
||||
func NewOSThread() *OSThread {
|
||||
return &OSThread{
|
||||
funcs: make(chan func()),
|
||||
funcs: make(chan queueItem),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Loop starts the thread loop until Stop is called on the current OS thread.
|
||||
//
|
||||
// Loop must be called on the thread.
|
||||
// Loop must be called on the OS thread.
|
||||
func (t *OSThread) Loop(ctx context.Context) error {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
for {
|
||||
select {
|
||||
case fn := <-t.funcs:
|
||||
case item := <-t.funcs:
|
||||
func() {
|
||||
defer func() {
|
||||
t.done <- struct{}{}
|
||||
}()
|
||||
|
||||
fn()
|
||||
if item.sync {
|
||||
defer func() {
|
||||
t.done <- struct{}{}
|
||||
}()
|
||||
}
|
||||
item.f()
|
||||
}()
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
@ -65,17 +74,26 @@ func (t *OSThread) Loop(ctx context.Context) error {
|
||||
|
||||
// Call calls f on the thread.
|
||||
//
|
||||
// Do not call this from the same thread. This would block forever.
|
||||
// Do not call Call from the same thread. Call would block forever.
|
||||
//
|
||||
// Call blocks if Loop is not called.
|
||||
func (t *OSThread) Call(f func()) {
|
||||
t.funcs <- f
|
||||
t.funcs <- queueItem{f: f, sync: true}
|
||||
<-t.done
|
||||
}
|
||||
|
||||
func (t *OSThread) private() {
|
||||
}
|
||||
|
||||
// CallAsync tries to queue f.
|
||||
// CallAsync returns immediately if f can be queued.
|
||||
// CallAsync blocks if f cannot be queued.
|
||||
//
|
||||
// Do not call CallAsync from the same thread. CallAsync would block forever.
|
||||
func (t *OSThread) CallAsync(f func()) {
|
||||
t.funcs <- queueItem{f: f, sync: false}
|
||||
}
|
||||
|
||||
// NoopThread is used to disable threading.
|
||||
type NoopThread struct{}
|
||||
|
||||
@ -94,5 +112,10 @@ func (t *NoopThread) Call(f func()) {
|
||||
f()
|
||||
}
|
||||
|
||||
// CallAsync executes the func immediately.
|
||||
func (t *NoopThread) CallAsync(f func()) {
|
||||
f()
|
||||
}
|
||||
|
||||
func (t *NoopThread) private() {
|
||||
}
|
||||
|
@ -1037,9 +1037,14 @@ func (u *userInterfaceImpl) update() (float64, float64, error) {
|
||||
}
|
||||
|
||||
func (u *userInterfaceImpl) loopGame() error {
|
||||
defer u.mainThread.Call(func() {
|
||||
glfw.Terminate()
|
||||
})
|
||||
defer func() {
|
||||
// Post a task to the render thread to ensure all the queued functions are executed.
|
||||
// glfw.Terminate will remove the context and any graphics calls after that will be invalidated.
|
||||
u.renderThread.Call(func() {})
|
||||
u.mainThread.Call(func() {
|
||||
glfw.Terminate()
|
||||
})
|
||||
}()
|
||||
|
||||
u.renderThread.Call(func() {
|
||||
if u.graphicsDriver.IsGL() {
|
||||
|
Loading…
Reference in New Issue
Block a user