Рендеринг прямоугольника с использованием металла - PullRequest
1 голос
/ 14 июля 2020

Я пытаюсь визуализировать прямоугольник с помощью Metal. Но прямоугольник перекошен как на скриншоте. Я хотел бы понять, что здесь не так.

Похоже, что вершины прямоугольника не загружаются правильно с использованием индекса вершины. Я пытаюсь следовать примеру из этой статьи - https://coldfunction.com/mgen/p/5a

введите описание изображения здесь

Ниже приведен код для MetalView и используемый шейдер -

import Cocoa
import Metal

// Swift doesn't allow to extend a protocol with another protocol; however, we can do default implementation for a specific protocol.
extension NSObjectProtocol {
    /// Makes the receiving value accessible within the passed block parameter.
    /// - parameter block: Closure executing a given task on the receiving function value.
    public func setUp(_ block: (Self)->Void) {
        block(self)
    }
    
    /// Makes the receiving value accessible within the passed block parameter and ends up returning the modified value.
    /// - parameter block: Closure executing a given task on the receiving function value.
    /// - returns: The modified value
    public func set(_ block: (Self)->Void) -> Self {
        block(self)
        return self
    }
}


extension MetalView {
    private struct VertexInput {
        var position: SIMD4<Float>
        var rgba: SIMD4<Float>
    }
}

/// `NSView` handling the first basic metal commands.
final class MetalView: NSView {
    private let device: MTLDevice
    private let queue: MTLCommandQueue
    private let vertexBuffer: MTLBuffer
    private let indexCount: Int
    private let indexBuffer: MTLBuffer
//    private let rectBuffer: MTLBuffer
    private let renderPipeline: MTLRenderPipelineState
    
    init?(frame: NSRect, device: MTLDevice, queue: MTLCommandQueue) {
        // Setup the Device and Command Queue (non-transient objects: expensive to create. Do save it)
        (self.device, self.queue) = (device, queue)
        self.queue.label = App.bundleIdentifier + ".queue"
        
        // Setup shader library
        guard let library = device.makeDefaultLibrary(),
            let vertexFunc = library.makeFunction(name: "rect_vertex"),
            let fragmentFunc = library.makeFunction(name: "rect_fragment") else { return nil }
        
        // Setup pipeline (non-transient)
        let pipelineDescriptor = MTLRenderPipelineDescriptor().set {
            $0.vertexFunction = vertexFunc
            $0.fragmentFunction = fragmentFunc
            $0.colorAttachments[0].pixelFormat = .bgra8Unorm   // 8-bit unsigned integer [0, 255]
        }
        guard let pipelineState = try? device.makeRenderPipelineState(descriptor: pipelineDescriptor) else { return nil }
        self.renderPipeline = pipelineState
        
        // Setup buffer (non-transient). Coordinates defined in clip space: [-1,+1]
        let vertices =  [VertexInput(position: SIMD4(-0.5, -0.5, 0.0, 1.0), rgba: SIMD4(1.0, 1.0, 1.0, 1.0)),
                         VertexInput(position: SIMD4(0.5, 0.5, 0.0, 1.0), rgba: SIMD4(0.0, 1.0, 1.0, 1.0)),
                         VertexInput(position: SIMD4(-0.5, 0.5, 0.0, 0.0), rgba: SIMD4(1.0, 0.0, 0.0, 1.0)),
                         VertexInput(position: SIMD4(0.5, -0.5, 0.0, 1.0), rgba: SIMD4(0.0, 1.0, 1.0, 1.0))]
        let size = vertices.count * MemoryLayout<VertexInput>.stride
        guard let buffer = device.makeBuffer(bytes: vertices, length: size, options: .cpuCacheModeWriteCombined) else { return nil }
        self.vertexBuffer = buffer.set { $0.label = App.bundleIdentifier + ".buffer" }
        
        // set index info
        let indexInfo : [UInt16] = [2, 1, 0, 0, 3, 1];
        let indexCount = indexInfo.count * MemoryLayout<UInt16>.stride
        guard let indexBuffer = device.makeBuffer(bytes: indexInfo, length: indexCount, options: .cpuCacheModeWriteCombined) else { return nil }
        self.indexBuffer = indexBuffer.set { $0.label = App.bundleIdentifier + ".buffer" }
        self.indexCount = indexInfo.count
        
        super.init(frame: frame)
        
        // Setup layer (backing layer)
        self.wantsLayer = true
        self.metalLayer.setUp { (layer) in
            layer.device = device
            layer.pixelFormat = .bgra8Unorm
            layer.framebufferOnly = true
        }
    }
    
    required init?(coder aDecoder: NSCoder) { fatalError() }
    private var metalLayer: CAMetalLayer { self.layer as! CAMetalLayer }
    override func makeBackingLayer() -> CALayer { CAMetalLayer() }
    
    override func viewDidMoveToWindow() {
        super.viewDidMoveToWindow()
        
        guard let window = self.window else { return }
        self.metalLayer.contentsScale = window.backingScaleFactor
        self.redraw()
    }
    
    override func setBoundsSize(_ newSize: NSSize) {
        super.setBoundsSize(newSize)
        self.metalLayer.drawableSize = convertToBacking(bounds).size
        self.redraw()
    }
    
    override func setFrameSize(_ newSize: NSSize) {
        super.setFrameSize(newSize)
        self.metalLayer.drawableSize = convertToBacking(bounds).size
        self.redraw()
    }
}

extension MetalView {
    /// Draws a triangle in the metal layer drawable.
    private func redraw() {
        // Setup Command Buffer (transient)
        guard let drawable = self.metalLayer.nextDrawable(),
              let commandBuffer = self.queue.makeCommandBuffer() else { return }
        
        let renderPass = MTLRenderPassDescriptor().set {
            $0.colorAttachments[0].setUp { (attachment) in
                attachment.texture = drawable.texture
                attachment.clearColor = MTLClearColor(red: 0.6, green: 0.6, blue: 0.6, alpha: 1)
                attachment.loadAction = .clear
                attachment.storeAction = .store
            }
        }
        
        guard let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPass) else { return }
        encoder.setRenderPipelineState(self.renderPipeline)
        encoder.setVertexBuffer(self.vertexBuffer, offset: 0, index: 0)
        encoder.drawIndexedPrimitives(type: .triangle, indexCount: self.indexCount, indexType: .uint16, indexBuffer: self.indexBuffer, indexBufferOffset: 0)
//        encoder.setFrontFacing(.counterClockwise)
        encoder.endEncoding()
        
        // Present drawable is a convenience completion block that will get executed once your command buffer finishes, and will output the final texture to screen.
        commandBuffer.present(drawable)
        commandBuffer.commit()
    }
}

Shader.metal

#include <metal_stdlib>
using namespace metal;

struct VertexInput {
 float4 position [[ position ]];
 float4 rgba;
};

vertex VertexInput rect_vertex(device VertexInput const* const vertices [[buffer(0)]], uint vid [[vertex_id]]) {
    return vertices[vid];
}

fragment float4 rect_fragment(VertexInput vert [[stage_in]]) {
    return vert.rgba;
}

1 Ответ

2 голосов
/ 14 июля 2020

Ваша третья вершина имеет 0 в позиции w, тогда как она должна иметь 1

...