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 š¤·š»āāļø).
Rather than using a buffer for a single color, why not just send the color directly using setFragmentBytes()
? Here's how it would look:
var fragmentColor = vector_float4(Float(color.redComponent), Float(color.greenComponent), Float(color.blueComponent), Float(color.alphaComponent))
commandEncoder.setFragmentBytes(&fragmentColor, length: MemoryLayout.size(ofValue: fragmentColor), index: 0)
And your shader would still use:
fragment float4 displayColor(constant float4 &color [[ buffer(0) ]])
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With