internal/graphicsdriver/metal: Reuse RenderCommandEncoder when possible

This commit is contained in:
Hajime Hoshi 2021-07-04 18:13:18 +09:00
parent 7668052a6b
commit 7f86761dde

View File

@ -310,8 +310,10 @@ type Graphics struct {
rpss map[rpsKey]mtl.RenderPipelineState
cq mtl.CommandQueue
cb mtl.CommandBuffer
rce mtl.RenderCommandEncoder
screenDrawable ca.MetalDrawable
lastDstTexture mtl.Texture
vb mtl.Buffer
ib mtl.Buffer
@ -609,39 +611,46 @@ func (g *Graphics) Reset() error {
return nil
}
func (g *Graphics) flushRenderCommandEncoderIfNeeded() {
if g.rce == (mtl.RenderCommandEncoder{}) {
return
}
g.rce.EndEncoding()
g.rce = mtl.RenderCommandEncoder{}
g.lastDstTexture = mtl.Texture{}
}
func (g *Graphics) draw(rps mtl.RenderPipelineState, dst *Image, dstRegion driver.Region, srcs [graphics.ShaderImageNum]*Image, indexLen int, indexOffset int, uniforms []interface{}) error {
if g.lastDstTexture != dst.mtlTexture() {
g.flushRenderCommandEncoderIfNeeded()
}
if g.rce == (mtl.RenderCommandEncoder{}) {
rpd := mtl.RenderPassDescriptor{}
// Even though the destination pixels are not used, mtl.LoadActionDontCare might cause glitches
// (#1019). Always using mtl.LoadActionLoad is safe.
rpd.ColorAttachments[0].LoadAction = mtl.LoadActionLoad
rpd.ColorAttachments[0].StoreAction = mtl.StoreActionStore
var t mtl.Texture
if dst.screen {
if g.screenDrawable == (ca.MetalDrawable{}) {
drawable := g.view.drawable()
if drawable == (ca.MetalDrawable{}) {
t := dst.mtlTexture()
g.lastDstTexture = t
if t == (mtl.Texture{}) {
return nil
}
g.screenDrawable = drawable
}
t = g.screenDrawable.Texture()
} else {
t = dst.texture
}
rpd.ColorAttachments[0].Texture = t
rpd.ColorAttachments[0].ClearColor = mtl.ClearColor{}
if g.cb == (mtl.CommandBuffer{}) {
g.cb = g.cq.MakeCommandBuffer()
}
rce := g.cb.MakeRenderCommandEncoder(rpd)
rce.SetRenderPipelineState(rps)
g.rce = g.cb.MakeRenderCommandEncoder(rpd)
}
g.rce.SetRenderPipelineState(rps)
// In Metal, the NDC's Y direction (upward) and the framebuffer's Y direction (downward) don't
// match. Then, the Y direction must be inverted.
w, h := dst.internalSize()
rce.SetViewport(mtl.Viewport{
g.rce.SetViewport(mtl.Viewport{
OriginX: 0,
OriginY: float64(h),
Width: float64(w),
@ -649,22 +658,22 @@ func (g *Graphics) draw(rps mtl.RenderPipelineState, dst *Image, dstRegion drive
ZNear: -1,
ZFar: 1,
})
rce.SetScissorRect(mtl.ScissorRect{
g.rce.SetScissorRect(mtl.ScissorRect{
X: int(dstRegion.X),
Y: int(dstRegion.Y),
Width: int(dstRegion.Width),
Height: int(dstRegion.Height),
})
rce.SetVertexBuffer(g.vb, 0, 0)
g.rce.SetVertexBuffer(g.vb, 0, 0)
for i, u := range uniforms {
switch u := u.(type) {
case float32:
rce.SetVertexBytes(unsafe.Pointer(&u), unsafe.Sizeof(u), i+1)
rce.SetFragmentBytes(unsafe.Pointer(&u), unsafe.Sizeof(u), i+1)
g.rce.SetVertexBytes(unsafe.Pointer(&u), unsafe.Sizeof(u), i+1)
g.rce.SetFragmentBytes(unsafe.Pointer(&u), unsafe.Sizeof(u), i+1)
case []float32:
rce.SetVertexBytes(unsafe.Pointer(&u[0]), unsafe.Sizeof(u[0])*uintptr(len(u)), i+1)
rce.SetFragmentBytes(unsafe.Pointer(&u[0]), unsafe.Sizeof(u[0])*uintptr(len(u)), i+1)
g.rce.SetVertexBytes(unsafe.Pointer(&u[0]), unsafe.Sizeof(u[0])*uintptr(len(u)), i+1)
g.rce.SetFragmentBytes(unsafe.Pointer(&u[0]), unsafe.Sizeof(u[0])*uintptr(len(u)), i+1)
default:
return fmt.Errorf("metal: unexpected uniform value: %[1]v (type: %[1]T)", u)
}
@ -672,13 +681,12 @@ func (g *Graphics) draw(rps mtl.RenderPipelineState, dst *Image, dstRegion drive
for i, src := range srcs {
if src != nil {
rce.SetFragmentTexture(src.texture, i)
g.rce.SetFragmentTexture(src.texture, i)
} else {
rce.SetFragmentTexture(mtl.Texture{}, i)
g.rce.SetFragmentTexture(mtl.Texture{}, i)
}
}
rce.DrawIndexedPrimitives(mtl.PrimitiveTypeTriangle, indexLen, mtl.IndexTypeUInt16, g.ib, indexOffset*2)
rce.EndEncoding()
g.rce.DrawIndexedPrimitives(mtl.PrimitiveTypeTriangle, indexLen, mtl.IndexTypeUInt16, g.ib, indexOffset*2)
return nil
}
@ -792,6 +800,11 @@ func (g *Graphics) DrawTriangles(dstID driver.ImageID, srcIDs [graphics.ShaderIm
if err := g.draw(rps, dst, dstRegion, srcs, indexLen, indexOffset, uniformVars); err != nil {
return err
}
if dst.screen {
g.flushRenderCommandEncoderIfNeeded()
}
return nil
}
@ -906,6 +919,8 @@ func (i *Image) IsInvalidated() bool {
}
func (i *Image) syncTexture() {
i.graphics.flushRenderCommandEncoderIfNeeded()
// Calling SynchronizeTexture is ignored on iOS (see mtl.m), but it looks like committing BlitCommandEncoder
// is necessary (#1337).
if i.graphics.cb != (mtl.CommandBuffer{}) {
@ -935,6 +950,8 @@ func (i *Image) Pixels() ([]byte, error) {
func (i *Image) ReplacePixels(args []*driver.ReplacePixelsArgs) {
g := i.graphics
g.flushRenderCommandEncoderIfNeeded()
// Calculate the smallest texture size to include all the values in args.
minX := math.MaxInt32
minY := math.MaxInt32
@ -991,3 +1008,18 @@ func (i *Image) ReplacePixels(args []*driver.ReplacePixelsArgs) {
}
bce.EndEncoding()
}
func (i *Image) mtlTexture() mtl.Texture {
if i.screen {
g := i.graphics
if g.screenDrawable == (ca.MetalDrawable{}) {
drawable := g.view.drawable()
if drawable == (ca.MetalDrawable{}) {
return mtl.Texture{}
}
g.screenDrawable = drawable
}
return g.screenDrawable.Texture()
}
return i.texture
}