2017-02-05 11 views
2

私はリアルタイムのデータをMetalにプロットするためのビューを作成しています。ポイントプリミティブを使用してサンプルを描画しています。頂点と均一なデータの両方をトリプルバッファリングしています。私が抱えている問題は、currentDrawableへの呼び出しが返されるまでにかかる時間が予測できないように思えることです。ほとんどの場合、描画可能な準備ができていない場合がほとんどで、フレーム全体が使用可能になるまで待たなければなりません。通常、currentDrawableが返す時間は〜0.07msです(これは私が期待するものです)が、それ以外の時間は1/60秒です。これによりメインスレッド全体がブロックされます。currentDrawableの予期せぬ復帰時間

私はiPhone 6SプラスとiPad Airでこの問題を見ています。私はまだMacでこの動作を見たことがない(私は2016 MPBにAMD 460 GPUを持っている)。私の推測では、これは何とかiOSデバイスのGPUがTBDRベースであるという事実と関係しているということです。私は帯域幅が限られているとは思っていません。なぜなら、私が描いているサンプルの数や数がまったく同じでも、まったく同じ動作を得るからです。私は、静的な正弦波を描く最小限の例を書いた問題を説明するために

。これは単純な例です。私は通常、私がユニフォームと同じようにサンプルを現在の頂点バッファにmemcpyしています。これが私が頂点データと制服を三重にバッファリングしている理由です。しかし、問題を説明するにはまだ十分です。このビューをストーリーボードのベースビューとして設定して実行してください。いくつかの実行では正常に動作します。他の時間currentDrawableは16.67ミリ秒の戻り時間で始まり、その後数秒後に0.07ミリ秒にジャンプし、しばらくしてから16.67に戻る。なんらかの理由でデバイスを回転させると、16.67から0.07にジャンプするようです。

MTKViewサブクラスこれにおそらく関連

import MetalKit 

let N = 500 

class MetalGraph: MTKView { 
    typealias Vertex = Int32 

    struct Uniforms { 
     var offset: UInt32 
     var numSamples: UInt32 
    } 

    // Data 
    var uniforms = Uniforms(offset: 0, numSamples: UInt32(N)) 

    // Buffers 
    var vertexBuffers = [MTLBuffer]() 
    var uniformBuffers = [MTLBuffer]() 
    var inflightBufferSemaphore = DispatchSemaphore(value: 3) 
    var inflightBufferIndex = 0 

    // Metal State 
    var commandQueue: MTLCommandQueue! 
    var pipeline: MTLRenderPipelineState! 


    // Setup 

    override func awakeFromNib() { 
     super.awakeFromNib() 

     device = MTLCreateSystemDefaultDevice() 
     commandQueue = device?.makeCommandQueue() 
     colorPixelFormat = .bgra8Unorm 

     setupPipeline() 
     setupBuffers() 
    } 

    func setupPipeline() { 
     let library = device?.newDefaultLibrary() 

     let descriptor = MTLRenderPipelineDescriptor() 
     descriptor.colorAttachments[0].pixelFormat = .bgra8Unorm 
     descriptor.vertexFunction = library?.makeFunction(name: "vertexFunction") 
     descriptor.fragmentFunction = library?.makeFunction(name: "fragmentFunction") 

     pipeline = try! device?.makeRenderPipelineState(descriptor: descriptor) 
    } 

    func setupBuffers() { 
     // Produces a dummy sine wave with N samples, 2 periods, with a range of [0, 1000] 
     let vertices: [Vertex] = (0..<N).map { 
      let periods = 2.0 
      let scaled = Double($0)/(Double(N)-1) * periods * 2 * .pi 
      let value = (sin(scaled) + 1) * 500 // Transform from range [-1, 1] to [0, 1000] 
      return Vertex(value) 
     } 

     let vertexBytes = MemoryLayout<Vertex>.size * vertices.count 
     let uniformBytes = MemoryLayout<Uniforms>.size 

     for _ in 0..<3 { 
      vertexBuffers .append(device!.makeBuffer(bytes: vertices, length: vertexBytes)) 
      uniformBuffers.append(device!.makeBuffer(bytes: &uniforms, length: uniformBytes)) 
     } 
    } 



    // Drawing 

    func updateUniformBuffers() { 
     uniforms.offset = (uniforms.offset + 1) % UInt32(N) 

     memcpy(
      uniformBuffers[inflightBufferIndex].contents(), 
      &uniforms, 
      MemoryLayout<Uniforms>.size 
     ) 
    } 

    override func draw(_ rect: CGRect) { 
     _ = inflightBufferSemaphore.wait(timeout: .distantFuture) 

     updateUniformBuffers() 

     let start = CACurrentMediaTime() 
     guard let drawable = currentDrawable else { return } 
     print(String(format: "Grab Drawable: %.3f ms", (CACurrentMediaTime() - start) * 1000)) 

     guard let passDescriptor = currentRenderPassDescriptor else { return } 

     passDescriptor.colorAttachments[0].loadAction = .clear 
     passDescriptor.colorAttachments[0].storeAction = .store 
     passDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0.2, 0.2, 0.2, 1) 

     let commandBuffer = commandQueue.makeCommandBuffer() 

     let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: passDescriptor) 
     encoder.setRenderPipelineState(pipeline) 
     encoder.setVertexBuffer(vertexBuffers[inflightBufferIndex], offset: 0, at: 0) 
     encoder.setVertexBuffer(uniformBuffers[inflightBufferIndex], offset: 0, at: 1) 
     encoder.drawPrimitives(type: .point, vertexStart: 0, vertexCount: N) 
     encoder.endEncoding() 

     commandBuffer.addCompletedHandler { _ in 
      self.inflightBufferSemaphore.signal() 
     } 
     commandBuffer.present(drawable) 
     commandBuffer.commit() 

     inflightBufferIndex = (inflightBufferIndex + 1) % 3 
    } 
} 

シェーダ

#include <metal_stdlib> 
using namespace metal; 

struct VertexIn { 
    int32_t value; 
}; 

struct VertexOut { 
    float4 pos [[position]]; 
    float pointSize [[point_size]]; 
}; 

struct Uniforms { 
    uint32_t offset; 
    uint32_t numSamples; 
}; 

vertex VertexOut vertexFunction(device VertexIn *vertices [[buffer(0)]], 
           constant Uniforms *uniforms [[buffer(1)]], 
           uint vid [[vertex_id]]) 
{ 
    // I'm using the vertex index to evenly spread the 
    // samples out in the x direction 
    float xIndex = float((vid + (uniforms->numSamples - uniforms->offset)) % uniforms->numSamples); 
    float x = (float(xIndex)/float(uniforms->numSamples - 1)) * 2.0f - 1.0f; 

    // Transforming the values from the range [0, 1000] to [-1, 1] 
    float y = (float)vertices[vid].value/500.0f - 1.0f ; 

    VertexOut vOut; 
    vOut.pos = {x, y, 1, 1}; 
    vOut.pointSize = 3; 

    return vOut; 
} 

fragment half4 fragmentFunction() { 
    return half4(1, 1, 1, 1); 
} 

:私が見てきたすべての例では、inflightBufferSemaphoreは(セマフォが通知される直前に、commandBufferのcompletionHandlerの内側にインクリメントされそれは私には意味をなさない)。私がそのラインを持っているとき、フレームバッファが順不同で表示されているかのように、奇妙なジッター効果を得ます。この線を描画関数の一番下に移動すると問題は解決しますが、それは私にはあまり意味がありません。 currentDrawableの復帰時間が予測不可能なことに関連するかどうかはわかりませんが、これらの2つの問題が同じ根本的な問題から出てきていると感じています。

ご協力いただければ幸いです。

答えて

1

[T] currentDrawableへの呼び出しが予期しないように思える時間です。ほとんどの場合、描画可能な準備ができていない場合がほとんどで、フレーム全体が使用可能になるまで待たなければなりません。

うーん、はい。これは明示的に文書化されている。 Metal Programming Guideから:

重要:描画可能なリソースのほんのセットは方法があるまで、そう時間をレンダリング長いフレームが一時的にこれらのリソースを使い果たし、そのCPUのスレッドをブロックするためにnextDrawableメソッド呼び出しを引き起こす可能性がありますが、完了しました。高価なCPUストールを回避するために、CAMetalLayerオブジェクトのnextDrawableメソッドを呼び出す前に描画可能リソースを必要としないすべてのフレーム毎の操作を実行します。docs for CAMetalLayer.nextDrawable()から

新しい描画可能なオブジェクトが使用可能になるまで、このメソッドはブロック、現在のCPUスレッドを呼び出します。小さな描画可能リソースしかないので、長いGPUフレーム時間は一時的にそれらのリソースを使い果たし、GPUレンダリングが完了するまでこの呼び出しがブロックされるようにします。最良の結果を得るには、nextDrawable()コールを、他のフレーム単位のCPU処理に比べてできるだけ遅くスケジュールします。

さらに、あなたのコードについては奇妙なことがあります。 currentDrawableをリクエストしていますが、何もしていません。 currentRenderPassDescriptorは自動的にcurrentDrawableのテクスチャを使用するように設定されています。したがって、currentDrawableを自分でリクエストしていないとどうなりますか?

+0

私の印象は、3つのドロウアブルが流通していたことです。セマフォは、利用可能になるまで待っていることを確認します。私が待っているのはセマフォーかcurrentDrawableかどうかは関係ありませんが、ネットの結果は同じになるでしょう。しかし、セマフォのポイントは何ですか?それにもかかわらず、この待機は、メインキューのタイマーのように、コードの他の部分に大きな影響を与えます。これが期待される動作であると言っていますか?残りのコードでは回避策を見つける必要があります。 – vegather

+0

私は現在のドロアブルを使っています(...)。 currentRenderPassDescriptorを呼び出すと、とにかくcurrentDrawableが実質的に呼び出されます。私はそれを使って遊んだし、currentDrawableを現在のところ(...)で直接呼び出しても問題にはならない。その場合、currentRenderPassDescriptorは予期しない動作をします。 – vegather

+0

この予期しない動作についての説明はありますか? – vtruant

関連する問題