Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing a float or color into a Metal fragment shader from Swift

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 šŸ¤·šŸ»ā€ā™‚ļø).

like image 352
Doug Avatar asked Jan 06 '19 01:01

Doug


1 Answers

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) ]])
like image 156
user1118321 Avatar answered Oct 06 '22 22:10

user1118321