Проблема обновления MTKView - PullRequest
0 голосов
/ 11 июля 2019

Я создаю массив UIImages через MTKView, и я вижу проблемы с обновлением, которые проявляются только во время составной фазы, но исчезают, как только я взаимодействую с приложением.Другими словами, композиты работают должным образом, но их появление на экране выглядит глючно, пока я не принудительно обновлю их, увеличив / переводя и т. Д.

Я разместил два видео, которые показывают проблему в действии: Glitch1 , Glitch2

Выбранный мной композитный подход заключается в том, что я преобразую каждый UIImage в текстуру MTL, которую я отправляю в буфер рендеринга, установленный в «.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 голосов
/ 21 июля 2019

Прежде всего, домен Metal очень глубокий, и его использование в конструкции MTKView редко документируется, особенно для любых приложений, которые выходят за рамки более традиционной игровой парадигмы.Вот где я оказался в ограниченном опыте, который я накопил с Metal с помощью таких людей, как @warrenm, @ ken-thomases и @modj, чей вклад был очень ценным для меня, и для сообщества Swift / Metalв целомИтак, большое спасибо всем вам.

Во-вторых, всем, кто занимается поиском и устранением неисправностей металла, обратите внимание на следующее: Если вы получаете сообщение:

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

, пожалуйста, неигнорируй это.Это может показаться достаточно безобидным, особенно если об этом сообщают только один раз.Но имейте в виду, что это признак того, что часть вашей реализации имеет недостатки и должна быть устранена, прежде чем вы сможете устранить любые другие связанные с 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)

означало, что я мог в значительной степени вызывать 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 могут найти полезным в будущем.

...