Search code examples
iosswiftmetalmetalkitmtkview

iOS - Metal not rendering color with low saturation and alpha


I'm using MTKView to render some triangles. Everything works fine until I decrease the color's saturation and opacity value which makes the triangle completely transparent. Creating SwiftUI's Color with the same values shows correctly. This only happens for colors with "low" saturation, if the color has 100% saturation (like #FF0000), it still renders fine even with just 1% opacity.
I noticed if I change the colorPixelFormat of the MTKView, the result will change. So not sure if I only need to change the colorPixelFormat to fix this, in that case, I don't know which one either as I have limited knowledge about graphics. Here is an example for color #FF8888:

  • bgra8Unorm: minimum 55% opacity for it to render
  • bgra8Unorm_srgb: minimum 77% opacity for it to render and the color is a lot lighter than what it should be.

In Swift, I store the colors as [Float], in MSL, it will be converted to float4*. Nothing fancy with the vertex and fragment functions, just returning the input. This is not very likely where the issue lies as other colors work.
Some code to show my setup:

    // MTKView's etup
    clearColor = MTLClearColor(red: 0, green: 0, blue: 0, alpha: 0)
    isOpaque = false
    layer.magnificationFilter = .nearest
    layer.minificationFilter = .nearest
    // State setup
    let pipelineDescriptor = MTLRenderPipelineDescriptor()
    pipelineDescriptor.vertexFunction = vertexFunction
    pipelineDescriptor.fragmentFunction = fragmentFunction
    pipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm

    pipelineState = try? device.makeRenderPipelineState(descriptor: pipelineDescriptor)
    // draw method setup
    guard let vertexBuffer = vertexBuffer,
          let indexBuffer = indexBuffer,
          let indexCount = indexCount,
          let colorBuffer = colorBuffer,
          let pipelineState = pipelineState,
          let discriptor = view.currentRenderPassDescriptor,
          let commandBuffer = commandQueue.makeCommandBuffer(),
          let commandEncoder = commandBuffer.makeRenderCommandEncoder(
            descriptor: discriptor
          ),
          let drawable = view.currentDrawable else {
      return
    }

    commandEncoder.setRenderPipelineState(pipelineState)
    commandEncoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
    commandEncoder.setVertexBuffer(colorBuffer, offset: 0, index: 1)
    commandEncoder.drawIndexedPrimitives(
      type: .triangle,
      indexCount: indexCount,
      indexType: .uint32,
      indexBuffer: indexBuffer,
      indexBufferOffset: 0
    )

    commandEncoder.endEncoding()
    commandBuffer.present(drawable)
    commandBuffer.commit()

Solution

  • Found this answer: https://stackoverflow.com/a/51414692/8355412

    So basically, I only need to premultiply alpha to each of my rgb component