Search code examples
swiftmetal

Metal seems to interpret bgra8Unorm as an rgba color


In an Apple Metal app I am trying to edit texture contents. Texture uses bgra8Unorm pixel format and is created like this:

fileprivate var texture: MTLTexture!
private var buffer: MTLBuffer!
    
private var width: Int!
private var height: Int!

...

buffer = device.makeBuffer(length: width * height * 4, options: .storageModeManaged)
        
let textureDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .bgra8Unorm, width: width, height: height, mipmapped: false)
texture = buffer.makeTexture(descriptor: textureDescriptor, offset: 0, bytesPerRow: width * 4)

I am setting pixel color as following:

func setPixel(x: Int, y: Int, color: NSColor) {
    let ptr = buffer.contents().bindMemory(to: UInt32.self, capacity: buffer.length / 4)

    var b: CGFloat = 0
    var g: CGFloat = 0
    var r: CGFloat = 0
    var a: CGFloat = 0
    color.getRed(&r, green: &g, blue: &b, alpha: &a)
        
    let index = y * width + x
    ptr[index] = (UInt32(b * 255) << 24) | (UInt32(g * 255) << 16) | (UInt32(r * 255) << 8) | UInt32(a * 255)
    buffer.didModifyRange(index..<(index + 1))
}

Main pixel format is also set to bgra8Unorm inheriting MTKView:

init() {
        super.init(frame: .zero, device: MTLCreateSystemDefaultDevice())
        colorPixelFormat = .bgra8Unorm
        clearColor = MTLClearColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0)
}

Fragment shader doesn't do anything interesting at all:

fragment float4 basic_fragment(
                              VertexOut inVert [[ stage_in ]],
                              texture2d<float> tex2D [[ texture(0) ]],
                              sampler sampler2D [[ sampler(0) ]]
                              ) {
    return tex2D.sample(sampler2D, inVert.tex);
}

However, when I am calling all this to set the color of the pixel to be NSColor.orange is appears to be blue.

Blue color instead of the orange

Running the debugger and capturing the GPU frame, I can see that the value in the buffer is 8388607 which looks like maxed Alpha, Red, and almost maxed Green. But somehow Metal seems to interpret this color as being in rgba format, despite me setting everything to bgra

Am I doing something wrong?


Solution

  • Am I doing something wrong?

    bgra means that 4 components b, g, r and a appears in this order in memory. So, in little endian UInt32, b should be the LS byte and a the MS byte.

    Please try this:

        ptr[index] = (UInt32(a * 255) << 24) | (UInt32(r * 255) << 16) | (UInt32(g * 255) << 8) | UInt32(b * 255)