mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2025-02-03 06:24:27 +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"
|
"image"
|
||||||
"math"
|
"math"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/debug"
|
"github.com/hajimehoshi/ebiten/v2/internal/debug"
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/graphics"
|
"github.com/hajimehoshi/ebiten/v2/internal/graphics"
|
||||||
@ -36,6 +37,7 @@ type command interface {
|
|||||||
fmt.Stringer
|
fmt.Stringer
|
||||||
|
|
||||||
Exec(graphicsDriver graphicsdriver.Graphics, indexOffset int) error
|
Exec(graphicsDriver graphicsdriver.Graphics, indexOffset int) error
|
||||||
|
NeedsSync() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type drawTrianglesCommandPool struct {
|
type drawTrianglesCommandPool struct {
|
||||||
@ -72,6 +74,8 @@ type commandQueue struct {
|
|||||||
drawTrianglesCommandPool drawTrianglesCommandPool
|
drawTrianglesCommandPool drawTrianglesCommandPool
|
||||||
|
|
||||||
uint32sBuffer uint32sBuffer
|
uint32sBuffer uint32sBuffer
|
||||||
|
|
||||||
|
err atomic.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
// theCommandQueues is the set of command queues for the current process.
|
// 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.
|
// 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() {
|
runOnRenderThread(func() {
|
||||||
err = q.flush(graphicsDriver, endFrame)
|
if err := q.flush(graphicsDriver, endFrame); err != nil {
|
||||||
if err != nil {
|
if sync {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
q.err.Store(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if endFrame && swapBuffersForGL != nil {
|
if endFrame && swapBuffersForGL != nil {
|
||||||
swapBuffersForGL()
|
swapBuffersForGL()
|
||||||
}
|
}
|
||||||
})
|
}, sync)
|
||||||
return
|
|
||||||
|
if sync && flushErr != nil {
|
||||||
|
return flushErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// flush must be called the main thread.
|
// 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)
|
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 {
|
func (c *drawTrianglesCommand) numVertices() int {
|
||||||
return len(c.vertices)
|
return len(c.vertices)
|
||||||
}
|
}
|
||||||
@ -452,6 +481,10 @@ func (c *writePixelsCommand) Exec(graphicsDriver graphicsdriver.Graphics, indexO
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *writePixelsCommand) NeedsSync() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
type readPixelsCommand struct {
|
type readPixelsCommand struct {
|
||||||
result []byte
|
result []byte
|
||||||
img *Image
|
img *Image
|
||||||
@ -466,6 +499,10 @@ func (c *readPixelsCommand) Exec(graphicsDriver graphicsdriver.Graphics, indexOf
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *readPixelsCommand) NeedsSync() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (c *readPixelsCommand) String() string {
|
func (c *readPixelsCommand) String() string {
|
||||||
return fmt.Sprintf("read-pixels: image: %d", c.img.id)
|
return fmt.Sprintf("read-pixels: image: %d", c.img.id)
|
||||||
}
|
}
|
||||||
@ -485,6 +522,10 @@ func (c *disposeImageCommand) Exec(graphicsDriver graphicsdriver.Graphics, index
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *disposeImageCommand) NeedsSync() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// disposeShaderCommand represents a command to dispose a shader.
|
// disposeShaderCommand represents a command to dispose a shader.
|
||||||
type disposeShaderCommand struct {
|
type disposeShaderCommand struct {
|
||||||
target *Shader
|
target *Shader
|
||||||
@ -500,6 +541,10 @@ func (c *disposeShaderCommand) Exec(graphicsDriver graphicsdriver.Graphics, inde
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *disposeShaderCommand) NeedsSync() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// newImageCommand represents a command to create an empty image with given width and height.
|
// newImageCommand represents a command to create an empty image with given width and height.
|
||||||
type newImageCommand struct {
|
type newImageCommand struct {
|
||||||
result *Image
|
result *Image
|
||||||
@ -523,6 +568,10 @@ func (c *newImageCommand) Exec(graphicsDriver graphicsdriver.Graphics, indexOffs
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *newImageCommand) NeedsSync() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// newShaderCommand is a command to create a shader.
|
// newShaderCommand is a command to create a shader.
|
||||||
type newShaderCommand struct {
|
type newShaderCommand struct {
|
||||||
result *Shader
|
result *Shader
|
||||||
@ -543,6 +592,10 @@ func (c *newShaderCommand) Exec(graphicsDriver graphicsdriver.Graphics, indexOff
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *newShaderCommand) NeedsSync() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
type isInvalidatedCommand struct {
|
type isInvalidatedCommand struct {
|
||||||
result bool
|
result bool
|
||||||
image *Image
|
image *Image
|
||||||
@ -557,11 +610,15 @@ func (c *isInvalidatedCommand) Exec(graphicsDriver graphicsdriver.Graphics, inde
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *isInvalidatedCommand) NeedsSync() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// InitializeGraphicsDriverState initialize the current graphics driver state.
|
// InitializeGraphicsDriverState initialize the current graphics driver state.
|
||||||
func InitializeGraphicsDriverState(graphicsDriver graphicsdriver.Graphics) (err error) {
|
func InitializeGraphicsDriverState(graphicsDriver graphicsdriver.Graphics) (err error) {
|
||||||
runOnRenderThread(func() {
|
runOnRenderThread(func() {
|
||||||
err = graphicsDriver.Initialize()
|
err = graphicsDriver.Initialize()
|
||||||
})
|
}, true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -571,7 +628,7 @@ func ResetGraphicsDriverState(graphicsDriver graphicsdriver.Graphics) (err error
|
|||||||
if r, ok := graphicsDriver.(graphicsdriver.Resetter); ok {
|
if r, ok := graphicsDriver.(graphicsdriver.Resetter); ok {
|
||||||
runOnRenderThread(func() {
|
runOnRenderThread(func() {
|
||||||
err = r.Reset()
|
err = r.Reset()
|
||||||
})
|
}, true)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -581,7 +638,7 @@ func MaxImageSize(graphicsDriver graphicsdriver.Graphics) int {
|
|||||||
var size int
|
var size int
|
||||||
runOnRenderThread(func() {
|
runOnRenderThread(func() {
|
||||||
size = graphicsDriver.MaxImageSize()
|
size = graphicsDriver.MaxImageSize()
|
||||||
})
|
}, true)
|
||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,6 +28,14 @@ func SetRenderThread(thread thread.Thread) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// runOnRenderThread calls f on the rendering thread.
|
// runOnRenderThread calls f on the rendering thread.
|
||||||
func runOnRenderThread(f func()) {
|
func runOnRenderThread(f func(), sync bool) {
|
||||||
|
if sync {
|
||||||
theRenderThread.Call(f)
|
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 {
|
type Thread interface {
|
||||||
Loop(ctx context.Context) error
|
Loop(ctx context.Context) error
|
||||||
Call(f func())
|
Call(f func())
|
||||||
|
CallAsync(f func())
|
||||||
|
|
||||||
private()
|
private()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type queueItem struct {
|
||||||
|
f func()
|
||||||
|
sync bool
|
||||||
|
}
|
||||||
|
|
||||||
// OSThread represents an OS thread.
|
// OSThread represents an OS thread.
|
||||||
type OSThread struct {
|
type OSThread struct {
|
||||||
funcs chan func()
|
funcs chan queueItem
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewOSThread creates a new thread.
|
// NewOSThread creates a new thread.
|
||||||
|
//
|
||||||
|
// queueSize indicates the function queue size. This matters when you use CallAsync.
|
||||||
func NewOSThread() *OSThread {
|
func NewOSThread() *OSThread {
|
||||||
return &OSThread{
|
return &OSThread{
|
||||||
funcs: make(chan func()),
|
funcs: make(chan queueItem),
|
||||||
done: make(chan struct{}),
|
done: make(chan struct{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loop starts the thread loop until Stop is called on the current OS thread.
|
// 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 {
|
func (t *OSThread) Loop(ctx context.Context) error {
|
||||||
runtime.LockOSThread()
|
runtime.LockOSThread()
|
||||||
defer runtime.UnlockOSThread()
|
defer runtime.UnlockOSThread()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case fn := <-t.funcs:
|
case item := <-t.funcs:
|
||||||
func() {
|
func() {
|
||||||
|
if item.sync {
|
||||||
defer func() {
|
defer func() {
|
||||||
t.done <- struct{}{}
|
t.done <- struct{}{}
|
||||||
}()
|
}()
|
||||||
|
}
|
||||||
fn()
|
item.f()
|
||||||
}()
|
}()
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
@ -65,17 +74,26 @@ func (t *OSThread) Loop(ctx context.Context) error {
|
|||||||
|
|
||||||
// Call calls f on the thread.
|
// 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.
|
// Call blocks if Loop is not called.
|
||||||
func (t *OSThread) Call(f func()) {
|
func (t *OSThread) Call(f func()) {
|
||||||
t.funcs <- f
|
t.funcs <- queueItem{f: f, sync: true}
|
||||||
<-t.done
|
<-t.done
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *OSThread) private() {
|
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.
|
// NoopThread is used to disable threading.
|
||||||
type NoopThread struct{}
|
type NoopThread struct{}
|
||||||
|
|
||||||
@ -94,5 +112,10 @@ func (t *NoopThread) Call(f func()) {
|
|||||||
f()
|
f()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CallAsync executes the func immediately.
|
||||||
|
func (t *NoopThread) CallAsync(f func()) {
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
|
||||||
func (t *NoopThread) private() {
|
func (t *NoopThread) private() {
|
||||||
}
|
}
|
||||||
|
@ -1037,9 +1037,14 @@ func (u *userInterfaceImpl) update() (float64, float64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *userInterfaceImpl) loopGame() error {
|
func (u *userInterfaceImpl) loopGame() error {
|
||||||
defer u.mainThread.Call(func() {
|
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()
|
glfw.Terminate()
|
||||||
})
|
})
|
||||||
|
}()
|
||||||
|
|
||||||
u.renderThread.Call(func() {
|
u.renderThread.Call(func() {
|
||||||
if u.graphicsDriver.IsGL() {
|
if u.graphicsDriver.IsGL() {
|
||||||
|
Loading…
Reference in New Issue
Block a user