internal/graphicsdriver/metal: add in-flight semaphore

Closes #3278
This commit is contained in:
Hajime Hoshi
2025-08-04 22:25:01 +09:00
parent 20872c1bd0
commit db9ca28598
2 changed files with 31 additions and 1 deletions

View File

@@ -15,6 +15,7 @@
package metal
import (
"context"
"fmt"
"image"
"math"
@@ -23,6 +24,7 @@ import (
"unsafe"
"github.com/ebitengine/purego/objc"
"golang.org/x/sync/semaphore"
"github.com/hajimehoshi/ebiten/v2/internal/cocoa"
"github.com/hajimehoshi/ebiten/v2/internal/graphics"
@@ -44,6 +46,9 @@ type Graphics struct {
rce mtl.RenderCommandEncoder
dsss map[stencilMode]mtl.DepthStencilState
cbCompletedBlock objc.Block
inFlightSemaphore *semaphore.Weighted
screenDrawable ca.MetalDrawable
buffers map[mtl.CommandBuffer][]mtl.Buffer
@@ -103,7 +108,8 @@ func NewGraphics(colorSpace graphicsdriver.ColorSpace) (graphicsdriver.Graphics,
}
g := &Graphics{
colorSpace: colorSpace,
colorSpace: colorSpace,
inFlightSemaphore: semaphore.NewWeighted(maximumDrawableCount),
}
if runtime.GOOS != "ios" {
@@ -113,6 +119,11 @@ func NewGraphics(colorSpace graphicsdriver.ColorSpace) (graphicsdriver.Graphics,
return nil, err
}
}
g.cbCompletedBlock = objc.NewBlock(func(self objc.Block, _ objc.ID) {
g.inFlightSemaphore.Release(1)
})
return g, nil
}
@@ -828,14 +839,25 @@ func (i *Image) mtlTexture() mtl.Texture {
if i.screen {
g := i.graphics
if g.screenDrawable == (ca.MetalDrawable{}) {
// Wait for the inflight semaphore to be available.
_ = g.inFlightSemaphore.Acquire(context.Background(), 1)
i.graphics.view.waitForDisplayLinkOutputCallback()
drawable := g.view.nextDrawable()
if drawable == (ca.MetalDrawable{}) {
g.inFlightSemaphore.Release(1)
return mtl.Texture{}
}
g.screenDrawable = drawable
// After nextDrawable, it is expected some command buffers are completed.
g.gcBuffers()
// Set the completed handler to release the semaphore when the command buffer is completed.
if g.cb == (mtl.CommandBuffer{}) {
g.cb = g.cq.CommandBuffer()
}
g.cb.AddCompletedHandler(g.cbCompletedBlock)
}
return g.screenDrawable.Texture()
}

View File

@@ -563,6 +563,7 @@ var (
sel_replaceRegion_mipmapLevel_withBytes_bytesPerRow = objc.RegisterName("replaceRegion:mipmapLevel:withBytes:bytesPerRow:")
sel_getBytes_bytesPerRow_fromRegion_mipmapLevel = objc.RegisterName("getBytes:bytesPerRow:fromRegion:mipmapLevel:")
sel_respondsToSelector = objc.RegisterName("respondsToSelector:")
sel_addCompletedHandler = objc.RegisterName("addCompletedHandler:")
)
// CreateSystemDefaultDevice returns the preferred system default Metal device.
@@ -843,6 +844,13 @@ func (cb CommandBuffer) WaitUntilScheduled() {
cb.commandBuffer.Send(sel_waitUntilScheduled)
}
// AddCompletedHandler registers a completion handler the GPU device calls immediately after the GPU finishes running the commands in the command buffer.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/addcompletedhandler(_:)
func (cb CommandBuffer) AddCompletedHandler(block objc.Block) {
cb.commandBuffer.Send(sel_addCompletedHandler, block)
}
// RenderCommandEncoderWithDescriptor creates a render command encoder from a descriptor.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1442999-rendercommandencoderwithdescript?language=objc.