MTKView刷新问题

时间:2019-07-11 02:25:59

标签: swift gpu metal visual-artifacts mtkview

我正在通过MTKView合成UIImages数组,并且看到刷新问题仅在复合阶段才表现出来,但在与应用程序交互时就消失了。换句话说,复合材料可以正常工作,但是它们在屏幕上的外观会出现毛刺,直到我通过放大/平移等方式强制刷新为止。

我发布了两个视频,展示了实际的问题:Glitch1Glitch2

我选择的复合方法是将每个UIImage转换为MTLTexture,然后将其提交到设置为“ .load”的渲染缓冲区,该渲染缓冲区将在其上渲染具有此纹理的多边形,然后为每个图像重复该过程。在UIImage数组中。

合成器可以正常工作,但是从视频中可以看到屏幕反馈很不正常。

关于可能发生什么的任何想法?任何建议将不胜感激

一些相关代码:

for strokeDataCurrent in strokeDataArray {

        let strokeImage = UIImage(data: strokeDataCurrent.image)
        let strokeBbox = strokeDataCurrent.bbox
        let strokeType = strokeDataCurrent.strokeType
        self.brushStrokeMetal.drawStrokeImage(paintingViewMetal: self.canvasMetalViewPainting, strokeImage: strokeImage!, strokeBbox: strokeBbox, strokeType: strokeType)

} // end of for strokeDataCurrent in strokeDataArray

...

func drawStrokeUIImage (strokeUIImage: UIImage, strokeBbox: CGRect, strokeType: brushTypeMode) {

    // set up proper compositing mode fragmentFunction
    self.updateRenderPipeline(stampCompStyle: drawStampCompMode)

    let stampTexture = UIImageToMTLTexture(strokeUIImage: strokeUIImage)
    let stampColor = UIColor.white
    let stampCorners = self.stampSetVerticesFromBbox(bbox: strokeBbox)
    self.stampAppendToVertexBuffer(stampUse: stampUseMode.strokeBezier, stampCorners: stampCorners, stampColor: stampColor)
    self.renderStampSingle(stampTexture: stampTexture)


  } // end of func drawStrokeUIImage (strokeUIImage: UIImage, strokeBbox: CGRect)

func renderStampSingle(stampTexture: MTLTexture) {

    // this routine is designed to update metalDrawableTextureComposite one stroke at a time, taking into account
    // whatever compMode the stroke requires. Note that we copy the contents of metalDrawableTextureComposite to
    // self.currentDrawable!.texture because the goal will be to eventually display a resulting composite

    let renderPassDescriptorSingleStamp: MTLRenderPassDescriptor? = self.currentRenderPassDescriptor

    renderPassDescriptorSingleStamp?.colorAttachments[0].loadAction = .load
    renderPassDescriptorSingleStamp?.colorAttachments[0].clearColor = MTLClearColorMake(0, 0, 0, 0)
    renderPassDescriptorSingleStamp?.colorAttachments[0].texture = metalDrawableTextureComposite 

    // Create a new command buffer for each tessellation pass
    let commandBuffer: MTLCommandBuffer? = commandQueue.makeCommandBuffer()

    let renderCommandEncoder: MTLRenderCommandEncoder? = commandBuffer?.makeRenderCommandEncoder(descriptor: renderPassDescriptorSingleStamp!)

    renderCommandEncoder?.label = "Render Command Encoder"
    renderCommandEncoder?.setTriangleFillMode(.fill)
    defineCommandEncoder(
      renderCommandEncoder: renderCommandEncoder,
      vertexArrayStamps: vertexArrayStrokeStamps,
      metalTexture: stampTexture) // foreground sub-curve chunk

    renderCommandEncoder?.endEncoding() // finalize renderEncoder set up

    //begin presentsWithTransaction approach  (needed to better synchronize with Core Image scheduling
    copyTexture(buffer: commandBuffer!, from: metalDrawableTextureComposite, to: self.currentDrawable!.texture)
    commandBuffer?.commit() // commit and send task to gpu

    commandBuffer?.waitUntilScheduled()
    self.currentDrawable!.present()
    // end presentsWithTransaction approach
    self.initializeStampArray(stampUse: stampUseMode.strokeBezier) // clears out the stamp array in preparation of next draw call

  } // end of func renderStampSingle(stampTexture: MTLTexture)

1 个答案:

答案 0 :(得分:0)

首先,Metal领域非常深入,并且在MTKView构造中很少使用它,特别是对于超出传统游戏范式的任何应用程序。在@ warrenm,@ ken-thomases和@modj之类的人的帮助下,我在Metal积累的有限经验中正是在这里,他们的贡献对我以及Swift / Metal社区都非常宝贵大体上。非常感谢你们。

第二,对于任何排除金属故障的人,请注意以下几点:如果您收到此消息:

[CAMetalLayerDrawable present] should not be called after already presenting this drawable. Get a nextDrawable instead

请不要忽略它。它似乎无害,尤其是仅报告一次时。但是请注意,这表明您的实现的一部分存在缺陷,必须先解决该问题,然后才能对应用程序的任何其他与金属相关的方面进行故障排除。至少我是这种情况。从视频帖子中可以看到,出现此问题的症状非常严重,并导致无法预测的行为,我很难确定其来源。对于我来说,特别难过的是,我只在应用程序周期的早期就获得了此消息,但是单个实例足以以我认为可归因于CoreImage和/的方式以图形方式抛弃所有其他内容或我所做的其他完全不相关的设计选择。

那么,我如何摆脱这个警告?好吧,以我为例,我假设具有以下设置:

self.enableSetNeedsDisplay = true // needed so we can call setNeedsDisplay()  to force a display update as soon as metal deems possible
self.isPaused = true // needed so the draw() loop does not get called once/fps
self.presentsWithTransaction = true // for better synchronization with CoreImage (such as simultaneously turning on a layer while also clearing MTKView)

意味着我想刷新屏幕时几乎可以直接调用currentDrawable!.present()commandBuffer.presentDrawable(view.currentDrawable)。好吧,根本不是这种情况。事实证明,这些调用仅应在draw()循环内进行,并且只能通过setNeedsDisplay()调用进行访问。做出更改后,我就可以很好地解决自己的困惑。

此外,我发现MTKView设置self.isPaused = true(这样我就可以直接进行setNeedsDisplay()调用)仍然导致某些意外行为。因此,我选择了:

self.enableSetNeedsDisplay = false // needed so we can call setNeedsDisplay()  to force a display update as soon as metal deems possible
self.isPaused = false // draw() loop gets called once/fps
self.presentsWithTransaction = true // for better synchronization with CoreImage

以及修改我的draw()循环以驱动一旦设置metalDrawableDriver标志并调用setNeedsDisplay()后执行哪种更新:

override func draw(_ rect: CGRect) {

autoreleasepool(invoking: { () -> () in

  switch metalDrawableDriver {

  case stampRenderMode.canvasRenderNoVisualUpdates:

    return

  case stampRenderMode.canvasRenderClearAll:

    renderClearCanvas()

  case stampRenderMode.canvasRenderPreComputedComposite:

    renderPreComputedComposite()

  case stampRenderMode.canvasRenderStampArraySubCurve:
      renderSubCurveArray()

  } // end of switch metalDrawableDriver

}) // end of autoreleasepool

} // end of draw()

这看起来似乎很简单,但这是我发现的唯一获得一致的用户驱动显示更新的机制。

我希望本文能描述一个无错误且可行的解决方案,Metal开发人员将来可能会发现有用的解决方案。