internal/graphicsdriver/metal: Do not retain MTLCommandBuffer for MTLBuffer

Before this change, a command buffer is retained indirectly by
a buffer, and this might extend the life of drawable unexpectedly.

This change stops using command buffers as a key of the buffers pool,
and use a counter increated by nextDrawable calls.

Updates #1196
This commit is contained in:
Hajime Hoshi 2021-07-07 02:52:01 +09:00
parent 80ac0646d5
commit e0fbfc2bb0
2 changed files with 30 additions and 43 deletions

View File

@ -317,8 +317,7 @@ type Graphics struct {
screenDrawable ca.MetalDrawable screenDrawable ca.MetalDrawable
buffers map[mtl.CommandBuffer][]mtl.Buffer buffers map[mtl.Buffer]int
unusedBuffers map[mtl.Buffer]struct{}
lastDstTexture mtl.Texture lastDstTexture mtl.Texture
lastStencilMode stencilMode lastStencilMode stencilMode
@ -388,54 +387,40 @@ func pow2(x uintptr) uintptr {
return p2 return p2
} }
func (g *Graphics) availableBuffer(length uintptr) mtl.Buffer { func (g *Graphics) ageBuffers() {
if g.cb == (mtl.CommandBuffer{}) { for b, age := range g.buffers {
g.cb = g.cq.MakeCommandBuffer() if age <= maximumDrawableCount {
g.buffers[b]++
}
}
} }
func (g *Graphics) availableBuffer(length uintptr) mtl.Buffer {
var newBuf mtl.Buffer var newBuf mtl.Buffer
for cb, bs := range g.buffers { var availBufs []mtl.Buffer
// If the command buffer still lives, the buffer must not be updated. for b, age := range g.buffers {
// TODO: Handle an error? // If the buffer is too young, its command buffer might still live.
if cb.Status() != mtl.CommandBufferStatusCompleted { // The buffer must not be updated in this case.
if age <= maximumDrawableCount {
continue continue
} }
for _, b := range bs {
if newBuf == (mtl.Buffer{}) && b.Length() >= length { if newBuf == (mtl.Buffer{}) && b.Length() >= length {
newBuf = b newBuf = b
continue continue
} }
if g.unusedBuffers == nil { availBufs = append(availBufs, b)
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 {
newBuf = b
delete(g.unusedBuffers, b)
break
}
} }
// GC unused buffers. // GC unused buffers.
const maxUnusedBuffers = 10 const maxUnusedBuffers = 10
if len(g.unusedBuffers) > maxUnusedBuffers { if len(availBufs) > maxUnusedBuffers {
bufs := make([]mtl.Buffer, 0, len(g.unusedBuffers)) sort.Slice(availBufs, func(a, b int) bool {
for b := range g.unusedBuffers { return availBufs[a].Length() > availBufs[b].Length()
bufs = append(bufs, b)
}
sort.Slice(bufs, func(a, b int) bool {
return bufs[a].Length() > bufs[b].Length()
}) })
for _, b := range bufs[maxUnusedBuffers:] { for _, b := range availBufs[maxUnusedBuffers:] {
delete(g.unusedBuffers, b) delete(g.buffers, b)
b.Release() b.Release()
} }
} }
@ -443,14 +428,10 @@ func (g *Graphics) availableBuffer(length uintptr) mtl.Buffer {
if newBuf == (mtl.Buffer{}) { if newBuf == (mtl.Buffer{}) {
newBuf = g.view.getMTLDevice().MakeBufferWithLength(pow2(length), resourceStorageMode) newBuf = g.view.getMTLDevice().MakeBufferWithLength(pow2(length), resourceStorageMode)
} }
if g.buffers == nil { 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.buffers[newBuf] = 0
g.cb.Retain()
}
g.buffers[g.cb] = append(g.buffers[g.cb], newBuf)
return newBuf return newBuf
} }
@ -1229,6 +1210,10 @@ func (i *Image) mtlTexture() mtl.Texture {
return mtl.Texture{} return mtl.Texture{}
} }
g.screenDrawable = drawable 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() return g.screenDrawable.Texture()
} }

View File

@ -24,6 +24,8 @@ import (
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal/mtl" "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal/mtl"
) )
const maximumDrawableCount = 3
type view struct { type view struct {
window uintptr window uintptr
uiview uintptr uiview uintptr
@ -69,7 +71,7 @@ func (v *view) reset() error {
// MTLPixelFormatBGRA8Unorm_sRGB, MTLPixelFormatRGBA16Float, MTLPixelFormatBGRA10_XR, or // MTLPixelFormatBGRA8Unorm_sRGB, MTLPixelFormatRGBA16Float, MTLPixelFormatBGRA10_XR, or
// MTLPixelFormatBGRA10_XR_sRGB. // MTLPixelFormatBGRA10_XR_sRGB.
v.ml.SetPixelFormat(mtl.PixelFormatBGRA8UNorm) v.ml.SetPixelFormat(mtl.PixelFormatBGRA8UNorm)
v.ml.SetMaximumDrawableCount(3) v.ml.SetMaximumDrawableCount(maximumDrawableCount)
// The vsync state might be reset. Set the state again (#1364). // The vsync state might be reset. Set the state again (#1364).
v.ml.SetDisplaySyncEnabled(v.vsync) v.ml.SetDisplaySyncEnabled(v.vsync)