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