Plu*_*man 2 gpu visual-artifacts swift metal mtkview
我正在通过 MTKView 合成 UIImage 数组,并且我看到刷新问题仅在合成阶段显现出来,但一旦我与应用程序交互,这些问题就会消失。换句话说,复合材料按预期工作,但它们在屏幕上的外观看起来有点问题,直到我通过放大/平移等强制刷新。
我发布了两个视频来展示实际问题:Glitch1、Glitch2
我选择的复合方法是将每个 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)
Run Code Online (Sandbox Code Playgroud)
首先,Metal 领域非常深入,并且它在 MTKView 构造中的使用记录很少,特别是对于任何不属于更传统的游戏范例的应用程序。在 @warrenm、@ken-thomases 和 @modj 等人的帮助下,我发现自己在 Metal 方面积累了有限的经验,他们的贡献对我和 Swift/Metal 社区都非常有价值在逃。非常感谢大家。
其次,对于任何对金属进行故障排除的人,请注意以下事项: 如果您收到消息:
[CAMetalLayerDrawable present] should not be called after already presenting this drawable. Get a nextDrawable instead
Run Code Online (Sandbox Code Playgroud)
请不要忽视它。它可能看起来无害,特别是如果它只被报告一次。但请注意,这表明您的实现的一部分存在缺陷,必须先解决该问题,然后才能对应用程序的任何其他与 Metal 相关的方面进行故障排除。至少对我来说是这样。正如您从视频帖子中看到的那样,这个问题的症状非常严重,并导致了不可预测的行为,我很难查明其根源。对我来说特别难以看到的是,我在应用程序周期的早期只收到过一次这条消息,但是这个单一实例足以以我认为归因于 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)
Run Code Online (Sandbox Code Playgroud)
这意味着每当我想刷新屏幕时我几乎都可以调用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
Run Code Online (Sandbox Code Playgroud)
metalDrawableDriver以及修改我的draw()循环以驱动在设置标志并调用后执行哪种更新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()
Run Code Online (Sandbox Code Playgroud)
这可能看起来很迂回,但这是我发现获得一致的用户驱动的显示更新的唯一机制。
我希望这篇文章描述了一个无错误且可行的解决方案,Metal 开发人员将来可能会发现有用。
| 归档时间: |
|
| 查看次数: |
1881 次 |
| 最近记录: |