internal/graphicsdriver/metal: Reuse MTLBuffer objects

In Metal, MTLBuffer objects are not 'transient' and are expensive
to create. Reuse them whenever possible.

See also: https://developer.apple.com/library/archive/documentation/Miscellaneous/Conceptual/MetalProgrammingGuide/Cmd-Submiss/Cmd-Submiss.html

Updates #1196
This commit is contained in:
Hajime Hoshi 2021-07-06 17:25:11 +09:00
parent be1a0e90e7
commit ee2f891fcc
4 changed files with 139 additions and 8 deletions

View File

@ -20,6 +20,7 @@ package metal
import (
"fmt"
"math"
"sort"
"strings"
"unsafe"
@ -316,6 +317,9 @@ type Graphics struct {
screenDrawable ca.MetalDrawable
buffers map[mtl.CommandBuffer][]mtl.Buffer
unusedBuffers map[mtl.Buffer]struct{}
lastDstTexture mtl.Texture
lastStencilMode stencilMode
@ -376,15 +380,90 @@ func (g *Graphics) SetUIView(uiview uintptr) {
g.view.setUIView(uiview)
}
func pow2(x uintptr) uintptr {
var p2 uintptr = 1
for p2 < x {
p2 *= 2
}
return p2
}
func (g *Graphics) availableBuffer(length uintptr) mtl.Buffer {
if g.cb == (mtl.CommandBuffer{}) {
g.cb = g.cq.MakeCommandBuffer()
}
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 {
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 {
newBuf = b
delete(g.unusedBuffers, b)
break
}
}
// GC unused buffers.
const maxUnusedBuffers = 20
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()
})
for _, b := range bufs[maxUnusedBuffers:] {
println("delete!", b.Length())
delete(g.unusedBuffers, b)
b.Release()
}
}
if newBuf == (mtl.Buffer{}) {
newBuf = g.view.getMTLDevice().MakeBufferWithLength(pow2(length), resourceStorageMode)
}
if g.buffers == nil {
g.buffers = map[mtl.CommandBuffer][]mtl.Buffer{}
}
if _, ok := g.buffers[g.cb]; !ok {
g.cb.Retain()
}
g.buffers[g.cb] = append(g.buffers[g.cb], newBuf)
return newBuf
}
func (g *Graphics) SetVertices(vertices []float32, indices []uint16) {
if g.vb != (mtl.Buffer{}) {
g.vb.Release()
}
if g.ib != (mtl.Buffer{}) {
g.ib.Release()
}
g.vb = g.view.getMTLDevice().MakeBufferWithBytes(unsafe.Pointer(&vertices[0]), unsafe.Sizeof(vertices[0])*uintptr(len(vertices)), resourceStorageMode)
g.ib = g.view.getMTLDevice().MakeBufferWithBytes(unsafe.Pointer(&indices[0]), unsafe.Sizeof(indices[0])*uintptr(len(indices)), resourceStorageMode)
vbSize := unsafe.Sizeof(vertices[0]) * uintptr(len(vertices))
ibSize := unsafe.Sizeof(indices[0]) * uintptr(len(indices))
g.vb = g.availableBuffer(vbSize)
g.vb.CopyToContents(unsafe.Pointer(&vertices[0]), vbSize)
g.ib = g.availableBuffer(ibSize)
g.ib.CopyToContents(unsafe.Pointer(&indices[0]), ibSize)
}
func (g *Graphics) flushIfNeeded(present bool) {

View File

@ -330,6 +330,17 @@ const (
CompareFunctionAlways CompareFunction = 7
)
type CommandBufferStatus uint8
const (
CommandBufferStatusNotEnqueued CommandBufferStatus = 0 //The command buffer is not enqueued yet.
CommandBufferStatusEnqueued CommandBufferStatus = 1 // The command buffer is enqueued.
CommandBufferStatusCommitted CommandBufferStatus = 2 // The command buffer is committed for execution.
CommandBufferStatusScheduled CommandBufferStatus = 3 // The command buffer is scheduled.
CommandBufferStatusCompleted CommandBufferStatus = 4 // The command buffer completed execution successfully.
CommandBufferStatusError CommandBufferStatus = 5 // Execution of the command buffer was aborted due to an error during execution.
)
// Resource represents a memory allocation for storing specialized data
// that is accessible to the GPU.
//
@ -623,6 +634,21 @@ type CommandBuffer struct {
commandBuffer unsafe.Pointer
}
func (cb CommandBuffer) Retain() {
C.CommandBuffer_Retain(cb.commandBuffer)
}
func (cb CommandBuffer) Release() {
C.CommandBuffer_Release(cb.commandBuffer)
}
// Status returns the current stage in the lifetime of the command buffer.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443048-status
func (cb CommandBuffer) Status() CommandBufferStatus {
return CommandBufferStatus(C.CommandBuffer_Status(cb.commandBuffer))
}
// PresentDrawable registers a drawable presentation to occur as soon as possible.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443029-presentdrawable.
@ -873,6 +899,12 @@ type Buffer struct {
buffer unsafe.Pointer
}
func (b Buffer) resource() unsafe.Pointer { return b.buffer }
func (b Buffer) Length() uintptr {
return uintptr(C.Buffer_Length(b.buffer))
}
func (b Buffer) CopyToContents(data unsafe.Pointer, lengthInBytes uintptr) {
C.Buffer_CopyToContents(b.buffer, data, C.size_t(lengthInBytes))
}

View File

@ -147,7 +147,9 @@ void *Device_MakeDepthStencilState(void *device,
void CommandQueue_Release(void *commandQueue);
void *CommandQueue_MakeCommandBuffer(void *commandQueue);
void CommandBuffer_Retain(void *commandBuffer);
void CommandBuffer_Release(void *commandBuffer);
uint8_t CommandBuffer_Status(void *commandBuffer);
void CommandBuffer_PresentDrawable(void *commandBuffer, void *drawable);
void CommandBuffer_Commit(void *commandBuffer);
void CommandBuffer_WaitUntilCompleted(void *commandBuffer);
@ -208,6 +210,7 @@ void Texture_ReplaceRegion(void *texture, struct Region region, uint_t level,
int Texture_Width(void *texture);
int Texture_Height(void *texture);
size_t Buffer_Length(void *buffer);
void Buffer_CopyToContents(void *buffer, void *data, size_t lengthInBytes);
void Buffer_Retain(void *buffer);
void Buffer_Release(void *buffer);

View File

@ -163,6 +163,18 @@ void *CommandQueue_MakeCommandBuffer(void *commandQueue) {
return [(id<MTLCommandQueue>)commandQueue commandBuffer];
}
void CommandBuffer_Retain(void *commandBuffer) {
[(id<MTLCommandBuffer>)commandBuffer retain];
}
void CommandBuffer_Release(void *commandBuffer) {
[(id<MTLCommandBuffer>)commandBuffer release];
}
uint8_t CommandBuffer_Status(void *commandBuffer) {
return [(id<MTLCommandBuffer>)commandBuffer status];
}
void CommandBuffer_PresentDrawable(void *commandBuffer, void *drawable) {
[(id<MTLCommandBuffer>)commandBuffer
presentDrawable:(id<MTLDrawable>)drawable];
@ -401,8 +413,13 @@ int Texture_Width(void *texture) { return [(id<MTLTexture>)texture width]; }
int Texture_Height(void *texture) { return [(id<MTLTexture>)texture height]; }
size_t Buffer_Length(void *buffer) { return [(id<MTLBuffer>)buffer length]; }
void Buffer_CopyToContents(void *buffer, void *data, size_t lengthInBytes) {
memcpy(((id<MTLBuffer>)buffer).contents, data, lengthInBytes);
#if !TARGET_OS_IPHONE
[(id<MTLBuffer>)buffer didModifyRange:NSMakeRange(0, lengthInBytes)];
#endif
}
void Buffer_Retain(void *buffer) { [(id<MTLBuffer>)buffer retain]; }