I'm trying to write a fragment shader in Metal but can't get my head around how to pass in single values (e.g. float, float4 or half4). My shaders are as follows:
#include <metal_stdlib>
using namespace metal;
typedef struct {
float4 renderedCoordinate [[position]];
} FullscreenQuadVertex;
vertex FullscreenQuadVertex fullscreenQuad(unsigned int vertex_id [[ vertex_id ]]) {
float4x4 renderedCoordinates = float4x4(float4( -1.0, -1.0, 0.0, 1.0 ),
float4( 1.0, -1.0, 0.0, 1.0 ),
float4( -1.0, 1.0, 0.0, 1.0 ),
float4( 1.0, 1.0, 0.0, 1.0 ));
FullscreenQuadVertex outVertex;
outVertex.renderedCoordinate = renderedCoordinates[vertex_id];
return outVertex;
}
fragment float4 displayColor(device float4 *color [[ buffer(0) ]]) {
// return float4(0.2, 0.5, 0.8, 1);
return *color;
}
And I'm passing the color in from an MTKView subclass like this:
import MetalKit
class MetalView: MTKView {
var color = NSColor(deviceRed: 0.2, green: 0.4, blue: 0.8, alpha: 1)
var pipeline: MTLRenderPipelineState!
var colorBuffer: MTLBuffer!
init() {
super.init(frame: CGRect.zero, device: nil)
setup()
}
required init(coder: NSCoder) {
super.init(coder: coder)
setup()
}
func setup() {
device = MTLCreateSystemDefaultDevice()
colorPixelFormat = .bgra8Unorm
// setup render pipeline for displaying the off screen buffer
guard let library = device?.makeDefaultLibrary() else {
fatalError("Failed to make Metal library")
}
let pipelineDescriptor = MTLRenderPipelineDescriptor()
pipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm
pipelineDescriptor.colorAttachments[0].isBlendingEnabled = false
pipelineDescriptor.vertexFunction = library.makeFunction(name: "fullscreenQuad")
pipelineDescriptor.fragmentFunction = library.makeFunction(name: "displayColor")
do {
pipeline = try device?.makeRenderPipelineState(descriptor: pipelineDescriptor)
} catch {
fatalError("Failed to make render pipeline state")
}
colorBuffer = device?.makeBuffer(length: MemoryLayout<float4>.stride, options: .storageModeManaged)
updateBackgroundColor()
}
func updateBackgroundColor() {
var colorArray = [color.blueComponent, color.greenComponent, color.redComponent, color.alphaComponent].map { Float($0) }
var data = Data(buffer: UnsafeBufferPointer(start: &colorArray, count: colorArray.count))
colorBuffer.contents().copyMemory(from: &data, byteCount: data.count)
colorBuffer.didModifyRange(0..<data.count)
}
override func draw(_ dirtyRect: NSRect) {
drawColor()
}
func drawColor() {
guard let commandQueue = device?.makeCommandQueue() else { return }
guard let commandBuffer = commandQueue.makeCommandBuffer() else { return }
guard let renderPassDescriptor = currentRenderPassDescriptor else { return }
guard let currentDrawable = currentDrawable else { return }
guard let commandEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) else { return }
commandEncoder.setRenderPipelineState(pipeline)
commandEncoder.setFragmentBuffer(colorBuffer, offset: 0, index: 0)
commandEncoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4)
commandEncoder.endEncoding()
commandBuffer.present(currentDrawable)
commandBuffer.commit()
}
}
Despite passing in what Iβd expect to be a shade of blue, all I see is black. Testing a hard coded colour returned from the the fragment shader works fine.
I'm not the most fluent in the use use UnsafePointers with Data types, so not sure if the problem is in setting the buffer data, or the way I'm actually trying to pass the buffer data into the shader. I did try the shader with an attribute type of [[ color(0) ]], however as far as I can see these aren't supported on macOS (or I was doing it wrong π€·π»ββοΈ).
[[color(0)]]
on an input doesn't mean it takes an app-provided color. It means the color that is in the render target that you're about to overwrite. It's to allow for custom blending. And, correct, it's not available for macOS. β Manassas