diff --git a/internal/graphicsdriver/metal/graphics.go b/internal/graphicsdriver/metal/graphics.go index 0325f514d..f493f27c5 100644 --- a/internal/graphicsdriver/metal/graphics.go +++ b/internal/graphicsdriver/metal/graphics.go @@ -317,8 +317,7 @@ type Graphics struct { screenDrawable ca.MetalDrawable - buffers map[mtl.CommandBuffer][]mtl.Buffer - unusedBuffers map[mtl.Buffer]struct{} + buffers map[mtl.Buffer]int lastDstTexture mtl.Texture lastStencilMode stencilMode @@ -388,54 +387,40 @@ func pow2(x uintptr) uintptr { return p2 } -func (g *Graphics) availableBuffer(length uintptr) mtl.Buffer { - if g.cb == (mtl.CommandBuffer{}) { - g.cb = g.cq.MakeCommandBuffer() +func (g *Graphics) ageBuffers() { + for b, age := range g.buffers { + if age <= maximumDrawableCount { + g.buffers[b]++ + } } +} +func (g *Graphics) availableBuffer(length uintptr) mtl.Buffer { var newBuf mtl.Buffer - for cb, bs := range g.buffers { - // If the command buffer still lives, the buffer must not be updated. - // TODO: Handle an error? - if cb.Status() != mtl.CommandBufferStatusCompleted { + var availBufs []mtl.Buffer + for b, age := range g.buffers { + // If the buffer is too young, its command buffer might still live. + // The buffer must not be updated in this case. + if age <= maximumDrawableCount { continue } - for _, b := range bs { - if newBuf == (mtl.Buffer{}) && b.Length() >= length { - newBuf = b - continue - } - if g.unusedBuffers == nil { - g.unusedBuffers = map[mtl.Buffer]struct{}{} - } - g.unusedBuffers[b] = struct{}{} - } - delete(g.buffers, cb) - cb.Release() - } - - for b := range g.unusedBuffers { - if b.Length() >= length { + if newBuf == (mtl.Buffer{}) && b.Length() >= length { newBuf = b - delete(g.unusedBuffers, b) - break + continue } + availBufs = append(availBufs, b) } // GC unused buffers. const maxUnusedBuffers = 10 - if len(g.unusedBuffers) > maxUnusedBuffers { - bufs := make([]mtl.Buffer, 0, len(g.unusedBuffers)) - for b := range g.unusedBuffers { - bufs = append(bufs, b) - } - sort.Slice(bufs, func(a, b int) bool { - return bufs[a].Length() > bufs[b].Length() + if len(availBufs) > maxUnusedBuffers { + sort.Slice(availBufs, func(a, b int) bool { + return availBufs[a].Length() > availBufs[b].Length() }) - for _, b := range bufs[maxUnusedBuffers:] { - delete(g.unusedBuffers, b) + for _, b := range availBufs[maxUnusedBuffers:] { + delete(g.buffers, b) b.Release() } } @@ -443,14 +428,10 @@ func (g *Graphics) availableBuffer(length uintptr) mtl.Buffer { if newBuf == (mtl.Buffer{}) { newBuf = g.view.getMTLDevice().MakeBufferWithLength(pow2(length), resourceStorageMode) } - if g.buffers == nil { - g.buffers = map[mtl.CommandBuffer][]mtl.Buffer{} + g.buffers = map[mtl.Buffer]int{} } - if _, ok := g.buffers[g.cb]; !ok { - g.cb.Retain() - } - g.buffers[g.cb] = append(g.buffers[g.cb], newBuf) + g.buffers[newBuf] = 0 return newBuf } @@ -1229,6 +1210,10 @@ func (i *Image) mtlTexture() mtl.Texture { return mtl.Texture{} } g.screenDrawable = drawable + + // nextDrawable blocks until the new drawable is available. + // If nextDrawable returns a valid drawable, this means that at least one drawable is already processed. + g.ageBuffers() } return g.screenDrawable.Texture() } diff --git a/internal/graphicsdriver/metal/view.go b/internal/graphicsdriver/metal/view.go index c221d12a2..7eda74af0 100644 --- a/internal/graphicsdriver/metal/view.go +++ b/internal/graphicsdriver/metal/view.go @@ -24,6 +24,8 @@ import ( "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal/mtl" ) +const maximumDrawableCount = 3 + type view struct { window uintptr uiview uintptr @@ -69,7 +71,7 @@ func (v *view) reset() error { // MTLPixelFormatBGRA8Unorm_sRGB, MTLPixelFormatRGBA16Float, MTLPixelFormatBGRA10_XR, or // MTLPixelFormatBGRA10_XR_sRGB. v.ml.SetPixelFormat(mtl.PixelFormatBGRA8UNorm) - v.ml.SetMaximumDrawableCount(3) + v.ml.SetMaximumDrawableCount(maximumDrawableCount) // The vsync state might be reset. Set the state again (#1364). v.ml.SetDisplaySyncEnabled(v.vsync)