Рендеринг геометрии через вычислительный конвейер Metal с использованием SceneKit

#ios #swift #shader #scenekit #metal

#iOS #быстрый #шейдер #scenekit #Металлические #swift

Вопрос:

Я новичок в Металле, и у меня есть следующий вопрос.

У меня есть простая настройка для отображения треугольника на экране после прохождения геометрии через вычислительный конвейер. Я могу протестировать вычислительный конвейер и конвейер рендеринга по отдельности, но не могу заставить их работать вместе. В частности, я получаю сообщение об ошибке зависания графического процессора…

Вот моя попытка просто пройти через треугольник, вычисление ничего не делает, кроме прохождения команд рендеринга, как показано ниже

Большая часть этого кода была адаптирована из образца Apple. Код в примере использует вид металла и написан на Objective-C коде, который я показываю, с использованием Swift with SceneKit в качестве основы.

Я строю простой треугольник следующим образом,

 // my swift variables in the ViewController
var vertexBuffer: MTLBuffer!
var renderPipelineState: MTLRenderPipelineState!
let sharedLibrary = sharedMetalRenderingDevice.device.makeDefaultLibrary()!
var _icbArgumentBuffer: MTLBuffer!
var _indirectCommandBuffer: MTLIndirectCommandBuffer!
var computePipelineState: MTLComputePipelineState!

// I call this inside viewDidLoad()
func setupMetalResources() {
    guard let device = sceneView.device else {
        assertionFailure()
        return
    }
    
    struct TriangleVertex {
        var position: vector_float3
    }
    
    let vertices: [TriangleVertex] = [
        TriangleVertex(position: vector_float3( 0.0, 0.5, 1)),
        TriangleVertex(position: vector_float3( -0.5, -0.5, 1)),
        TriangleVertex(position: vector_float3( 0.5, 0.5, 1))
    ]
    
    self.vertexBuffer = device.makeBuffer(
        bytes: vertices,
        length: MemoryLayout<TriangleVertex>.size * vertices.count,
        options: .cpuCacheModeWriteCombined)
    
    let vertexFunc = sharedLibrary.makeFunction(name: "passthrough_vertex")
    let fragmentFunc = sharedLibrary.makeFunction(name: "passthrough_fragment")
    
    let pipelineDescriptor = MTLRenderPipelineDescriptor()
    pipelineDescriptor.vertexFunction = vertexFunc
    pipelineDescriptor.fragmentFunction = fragmentFunc
    
    pipelineDescriptor.colorAttachments[0].pixelFormat = sceneView.colorPixelFormat
    pipelineDescriptor.depthAttachmentPixelFormat = sceneView.depthPixelFormat
    pipelineDescriptor.supportIndirectCommandBuffers = true
    
    guard let pipeline = try? device.makeRenderPipelineState(descriptor: pipelineDescriptor)
    else {
        assertionFailure()
        return
    }
    
    self.renderPipelineState = pipeline
    
    let cullF = sharedLibrary.makeFunction(name: "testCull")
    let ag = cullF?.makeArgumentEncoder(bufferIndex: 1)
    
    let icbDesc = MTLIndirectCommandBufferDescriptor()
    icbDesc.commandTypes = .draw
    icbDesc.inheritBuffers = false
    icbDesc.maxVertexBufferBindCount = 3
    icbDesc.maxFragmentBufferBindCount = 0
    
    _indirectCommandBuffer = sharedMetalRenderingDevice.device.makeIndirectCommandBuffer(descriptor: icbDesc, maxCommandCount: 3, options: .storageModePrivate)
    
    _icbArgumentBuffer = sharedMetalRenderingDevice.device.makeBuffer(length: ag!.encodedLength, options: .storageModeShared)
    ag?.setArgumentBuffer(_icbArgumentBuffer, offset: 0)
    ag?.setIndirectCommandBuffer(_indirectCommandBuffer, index: 0)
    do {
        computePipelineState = try sharedMetalRenderingDevice.device.makeComputePipelineState(function: t!)
    } catch {
        
    }
}

// This is the SCNSceneRendererDelegate’s -> didRenderScene of SceneKit (my sceneview)
func renderer(_ renderer: SCNSceneRenderer, didRenderScene scene: SCNScene, atTime time: TimeInterval) {
    guard let renderEncoder = renderer.currentRenderCommandEncoder else { return }
    let myRange: Range = 0..<65536

    let commandBuffer = renderer.commandQueue?.makeCommandBuffer()
    let blit = commandBuffer?.makeBlitCommandEncoder()
    blit?.resetCommandsInBuffer(_indirectCommandBuffer, range: myRange)
    blit?.endEncoding()

    let computeEncoder = commandBuffer?.makeComputeCommandEncoder()
    computeEncoder!.setComputePipelineState(computePipelineState!)
    computeEncoder!.setBuffer(vertexBuffer, offset: 0, index: 0)
    computeEncoder!.setBuffer(_icbArgumentBuffer, offset: 0, index: 1)
    computeEncoder!.useResource( _indirectCommandBuffer, usage: .write)
    computeEncoder!.dispatchThreads(MTLSize(width: 1, height: 1, depth: 1), threadsPerThreadgroup: MTLSize(width: 1, height: 1, depth: 1))
    computeEncoder!.endEncoding()

    let optimBlit = commandBuffer?.makeBlitCommandEncoder()
    optimBlit?.optimizeIndirectCommandBuffer(_indirectCommandBuffer, range: myRange)
    optimBlit?.endEncoding()

    renderEncoder.setCullMode(.back)
    renderEncoder.setRenderPipelineState(renderPipelineState)
    renderEncoder.useResource(vertexBuffer, usage: .read)
    // If I comment the entire compute encoder and pass the vertex buffer to the render encoder, it works fine
    // The below 2 lines are how I pass the vertex buffer into the render pass 
    //        renderEncoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0) 
    //        renderEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3)

    renderEncoder.executeCommandsInBuffer( _indirectCommandBuffer, range: myRange)
    //        renderEncoder.endEncoding() // uncommenting this causes "invalid usage because encoding has ended."
    commandBuffer?.commit() // I get a GPU Hang error 
    //        commandBuffer?.waitUntilCompleted() // uncommenting this causes the screen to go black and nothing shows  
}


// This is the Metal shader code
struct Vertex
{
    float4 position [[position]];
};
struct Vertex1
{
    float3 position;
};


vertex Vertex passthrough_vertex(const device Vertex1 *vertices [[buffer(0)]],
                                 constant simd_float4x4amp; modelViewProjectionTransform [[buffer(1)]],
                                 uint vid [[vertex_id]])
{
    Vertex out;
    out.position = modelViewProjectionTransform * float4(vertices[vid].position,1);
//    out.position = float4(vertices[vid].position.x, vertices[vid].position.y, vertices[vid].position.z, 1);
    return out;
}

fragment float4 passthrough_fragment(Vertex inVertex [[stage_in]])
{
    return float4(1,0,0,1);
}

typedef struct ICBContainer
{
    command_buffer commandBuffer [[ id(0) ]];
} ICBContainer;


kernel void
testCull(uint                      objectIndex   [[ thread_position_in_grid ]],
         device Vertex1            *vertices      [[ buffer(0) ]],
         device ICBContainer       *icb_container [[ buffer(1) ]])
{
    render_command cmd(icb_container->commandBuffer, objectIndex);
    
    cmd.set_vertex_buffer(vertices, 0);
    cmd.draw_primitives(primitive_type::triangle, 0, 3, 1, 1);
}
  

Может ли кто-нибудь указать на ошибку или направить меня в правильном направлении, чтобы решить эту проблему с рендерингом?

Ответ №1:

У меня была похожая проблема / вопрос, который был решен по этой ссылке:https://gist.github.com/0xLeif/bc0d908bd7c5758d2f7766b8458ed4fd

Я пытаюсь заставить это работать с моим конкретным приложением, но я новичок в metal и все еще пытаюсь заставить его работать с кучей разных треугольников…