diff --git a/internal/graphicscommand/bytes.go b/internal/graphicscommand/bytes.go index f008b2a01..06893148e 100644 --- a/internal/graphicscommand/bytes.go +++ b/internal/graphicscommand/bytes.go @@ -71,5 +71,5 @@ func (t *temporaryBytes) reset() { // // Be careful that the returned pixels might not be zero-cleared. func AllocBytes(size int) []byte { - return currentCommandQueue().temporaryBytes.alloc(size) + return theCommandQueueManager.allocBytes(size) } diff --git a/internal/graphicscommand/command.go b/internal/graphicscommand/command.go index b3c070b39..da0e2b6cf 100644 --- a/internal/graphicscommand/command.go +++ b/internal/graphicscommand/command.go @@ -19,6 +19,7 @@ import ( "image" "math" "strings" + "sync" "sync/atomic" "github.com/hajimehoshi/ebiten/v2/internal/debug" @@ -81,24 +82,6 @@ type commandQueue struct { err atomic.Value } -// theCommandQueues is the set of command queues for the current process. -var ( - theCommandQueues = [...]*commandQueue{ - {}, - {}, - } - commandQueueIndex int -) - -func currentCommandQueue() *commandQueue { - return theCommandQueues[commandQueueIndex] -} - -func switchCommandQueue() { - commandQueueIndex++ - commandQueueIndex = commandQueueIndex % len(theCommandQueues) -} - func (q *commandQueue) appendIndices(indices []uint16, offset uint16) { n := len(q.indices) q.indices = append(q.indices, indices...) @@ -217,6 +200,8 @@ func (q *commandQueue) Flush(graphicsDriver graphicsdriver.Graphics, endFrame bo if endFrame && swapBuffersForGL != nil { swapBuffersForGL() } + + theCommandQueueManager.putCommandQueue(q) }, sync) if sync && flushErr != nil { @@ -312,9 +297,7 @@ func (q *commandQueue) flush(graphicsDriver graphicsdriver.Graphics, endFrame bo // If endFrame is true, the current screen might be used to present. func FlushCommands(graphicsDriver graphicsdriver.Graphics, endFrame bool, swapBuffersForGL func()) error { flushImageBuffers() - q := currentCommandQueue() - switchCommandQueue() - if err := q.Flush(graphicsDriver, endFrame, swapBuffersForGL); err != nil { + if err := theCommandQueueManager.flush(graphicsDriver, endFrame, swapBuffersForGL); err != nil { return err } return nil @@ -651,6 +634,89 @@ func MaxImageSize(graphicsDriver graphicsdriver.Graphics) int { return size } +type commandQueuePool struct { + cache []*commandQueue + m sync.Mutex +} + +func (c *commandQueuePool) get() (*commandQueue, error) { + c.m.Lock() + defer c.m.Unlock() + + if len(c.cache) == 0 { + return &commandQueue{}, nil + } + + for _, q := range c.cache { + if err := q.err.Load(); err != nil { + return nil, err.(error) + } + } + + q := c.cache[len(c.cache)-1] + c.cache[len(c.cache)-1] = nil + c.cache = c.cache[:len(c.cache)-1] + return q, nil +} + +func (c *commandQueuePool) put(queue *commandQueue) { + c.m.Lock() + defer c.m.Unlock() + + c.cache = append(c.cache, queue) +} + +type commandQueueManager struct { + pool commandQueuePool + current *commandQueue +} + +var theCommandQueueManager commandQueueManager + +func (c *commandQueueManager) allocBytes(size int) []byte { + if c.current == nil { + c.current, _ = c.pool.get() + } + return c.current.temporaryBytes.alloc(size) +} + +func (c *commandQueueManager) enqueueCommand(command command) { + if c.current == nil { + c.current, _ = c.pool.get() + } + c.current.Enqueue(command) +} + +// put can be called from any goroutines. +func (c *commandQueueManager) putCommandQueue(commandQueue *commandQueue) { + c.pool.put(commandQueue) +} + +func (c *commandQueueManager) enqueueDrawTrianglesCommand(dst *Image, srcs [graphics.ShaderImageCount]*Image, vertices []float32, indices []uint16, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderImageCount]image.Rectangle, shader *Shader, uniforms []uint32, evenOdd bool) { + if c.current == nil { + c.current, _ = c.pool.get() + } + c.current.EnqueueDrawTrianglesCommand(dst, srcs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, evenOdd) +} + +func (c *commandQueueManager) flush(graphicsDriver graphicsdriver.Graphics, endFrame bool, swapBuffersForGL func()) error { + // Switch the command queue. + prev := c.current + q, err := c.pool.get() + if err != nil { + return err + } + c.current = q + + if prev == nil { + return nil + } + if err := prev.Flush(graphicsDriver, endFrame, swapBuffersForGL); err != nil { + return err + } + return nil +} + func max(a, b int) int { if a < b { return b diff --git a/internal/graphicscommand/image.go b/internal/graphicscommand/image.go index 662ec41d2..4ef3f330f 100644 --- a/internal/graphicscommand/image.go +++ b/internal/graphicscommand/image.go @@ -88,7 +88,7 @@ func NewImage(width, height int, screenFramebuffer bool) *Image { height: height, screen: screenFramebuffer, } - currentCommandQueue().Enqueue(c) + theCommandQueueManager.enqueueCommand(c) return i } @@ -100,7 +100,7 @@ func (i *Image) flushBufferedWritePixels() { dst: i, args: i.bufferedWritePixelsArgs, } - currentCommandQueue().Enqueue(c) + theCommandQueueManager.enqueueCommand(c) i.bufferedWritePixelsArgs = nil } @@ -110,7 +110,7 @@ func (i *Image) Dispose() { c := &disposeImageCommand{ target: i, } - currentCommandQueue().Enqueue(c) + theCommandQueueManager.enqueueCommand(c) } func (i *Image) InternalSize() (int, int) { @@ -159,7 +159,7 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderImageCount]*Image, vertices [ } i.flushBufferedWritePixels() - currentCommandQueue().EnqueueDrawTrianglesCommand(i, srcs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, evenOdd) + theCommandQueueManager.enqueueDrawTrianglesCommand(i, srcs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, evenOdd) } // ReadPixels reads the image's pixels. @@ -170,8 +170,8 @@ func (i *Image) ReadPixels(graphicsDriver graphicsdriver.Graphics, args []graphi img: i, args: args, } - currentCommandQueue().Enqueue(c) - if err := currentCommandQueue().Flush(graphicsDriver, false, nil); err != nil { + theCommandQueueManager.enqueueCommand(c) + if err := theCommandQueueManager.flush(graphicsDriver, false, nil); err != nil { return err } return nil @@ -200,8 +200,8 @@ func (i *Image) IsInvalidated(graphicsDriver graphicsdriver.Graphics) (bool, err c := &isInvalidatedCommand{ image: i, } - currentCommandQueue().Enqueue(c) - if err := currentCommandQueue().Flush(graphicsDriver, false, nil); err != nil { + theCommandQueueManager.enqueueCommand(c) + if err := theCommandQueueManager.flush(graphicsDriver, false, nil); err != nil { return false, err } return c.result, nil diff --git a/internal/graphicscommand/shader.go b/internal/graphicscommand/shader.go index 071dbf50e..2feb6ea68 100644 --- a/internal/graphicscommand/shader.go +++ b/internal/graphicscommand/shader.go @@ -32,7 +32,7 @@ func NewShader(ir *shaderir.Program) *Shader { result: s, ir: ir, } - currentCommandQueue().Enqueue(c) + theCommandQueueManager.enqueueCommand(c) return s } @@ -40,7 +40,7 @@ func (s *Shader) Dispose() { c := &disposeShaderCommand{ target: s, } - currentCommandQueue().Enqueue(c) + theCommandQueueManager.enqueueCommand(c) } func (s *Shader) unit() shaderir.Unit {