Я пытаюсь визуализировать прямоугольник с помощью 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;
}