Search code examples
iosswiftopacitymetalmtkview

MTKView Transparency


I can't make my MTKView clear its background. I've set the view's and its layer's isOpaque to false, background color to clear and tried multiple solutions found on google/stackoverflow (most in the code below like loadAction and clearColor of color attachment) but nothing works.

All the background color settings seem to be ignored. Setting loadAction and clearColor of MTLRenderPassColorAttachmentDescriptor does nothing.

I'd like to have my regular UIView's drawn under the MTKView. What am I missing?

            // initialization
            let metal = MTKView(frame: self.view.bounds)
            metal.device = MTLCreateSystemDefaultDevice()
            self.renderer = try! MetalRenderer(mtkView: metal)
            metal.delegate = self.renderer
            self.view.addSubview(metal);
import Foundation
import MetalKit
import simd

public enum MetalError: Error {
    case mtkViewError
    case renderError
}

internal class MetalRenderer: NSObject, MTKViewDelegate {
    private let commandQueue: MTLCommandQueue;
    private let pipelineState: MTLRenderPipelineState
    private var viewportSize: SIMD2<UInt32> = SIMD2(x: 10, y: 10);
    private weak var mtkView: MTKView?
        

    
    init(mtkView: MTKView) throws {

        guard let device = mtkView.device else { 
            print("device not found error")
            throw MetalError.mtkViewError 
        }
        self.mtkView = mtkView

        // Load all the shader files with a .metal file extension in the project.
        guard let defaultLibrary = device.makeDefaultLibrary() else {
            print("Could not find library")
            throw MetalError.mtkViewError
        }
        let vertexFunction = defaultLibrary.makeFunction(name: "vertexShader")
        let fragmentFunction = defaultLibrary.makeFunction(name: "fragmentShader")

        mtkView.layer.isOpaque = false;
        mtkView.layer.backgroundColor = UIColor.clear.cgColor
        mtkView.isOpaque = false;
        mtkView.backgroundColor = .clear

        let pipelineStateDescriptor = MTLRenderPipelineDescriptor();
        pipelineStateDescriptor.label = "Pipeline";
        pipelineStateDescriptor.vertexFunction = vertexFunction;
        pipelineStateDescriptor.fragmentFunction = fragmentFunction;
        pipelineStateDescriptor.isAlphaToCoverageEnabled = true
        pipelineStateDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm;
        pipelineStateDescriptor.colorAttachments[0].isBlendingEnabled = true;
        pipelineStateDescriptor.colorAttachments[0].destinationRGBBlendFactor = .oneMinusSourceAlpha;
        pipelineStateDescriptor.colorAttachments[0].destinationAlphaBlendFactor = .oneMinusSourceAlpha;

        pipelineState = try! device.makeRenderPipelineState(descriptor: pipelineStateDescriptor);
        
        guard let queue = device.makeCommandQueue() else { 
            print("make command queue error")
            throw MetalError.mtkViewError 
        }
        commandQueue = queue
    }


    func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
        viewportSize.x = UInt32(size.width)
        viewportSize.y = UInt32(size.height)
    }

    func draw(in view: MTKView) {

        let vertices: [Vertex] = [
            Vertex(position: SIMD3<Float>(x: 250.0, y: -250.0, z: 0)),
            Vertex(position: SIMD3<Float>(x: -250.0, y: -250.0, z: 0)),
            Vertex(position: SIMD3<Float>(x: 0.0, y: 250.0, z: 0)),
        ]
        
        guard let commandBuffer = commandQueue.makeCommandBuffer() else { 
            print("Couldn't create command buffer")
            return 
        }
        // Create a new command buffer for each render pass to the current drawable.
        commandBuffer.label = "MyCommand";

        // Obtain a renderPassDescriptor generated from the view's drawable textures.
        guard let renderPassDescriptor = view.currentRenderPassDescriptor else {
            print("Couldn't create render pass descriptor")
            return
        }
        
        guard let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) else {
            print("Couldn't create render encoder")
            return
        }

        renderPassDescriptor.colorAttachments[0].loadAction = .clear
        renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(1.0, 0.0, 0.0, 0.5)

        renderEncoder.label = "MyRenderEncoder";
        // Set the region of the drawable to draw into.
        renderEncoder.setViewport(MTLViewport(originX: 0.0, originY: 0.0, width: Double(viewportSize.x), height: Double(viewportSize.y), znear: 0.0, zfar: 1.0))
        renderEncoder.setRenderPipelineState(pipelineState)

        // Pass in the parameter data.
        renderEncoder.setVertexBytes(vertices, length: MemoryLayout<Vertex>.size * vertices.count, index: Int(VertexInputIndexVertices.rawValue))
        renderEncoder.setVertexBytes(&viewportSize, length: MemoryLayout<SIMD2<UInt32>>.size, index: Int(VertexInputIndexViewportSize.rawValue))
        renderEncoder.drawPrimitives(type: MTLPrimitiveType.triangle, vertexStart: 0, vertexCount: 3)
        renderEncoder.endEncoding()

        // Schedule a present once the framebuffer is complete using the current drawable.
        guard let drawable = view.currentDrawable else {
            print("Couldn't get current drawable")
            return
        }

        commandBuffer.present(drawable)

        // Finalize rendering here & push the command buffer to the GPU.
        commandBuffer.commit()
    }
}

Solution

  • Thanks to Frank, the answer was to just set the clearColor property of the view itself, which I missed. I also removed most adjustments in the MTLRenderPipelineDescriptor, who's code is now:

        let pipelineStateDescriptor = MTLRenderPipelineDescriptor();
        pipelineStateDescriptor.label = "Pipeline";
        pipelineStateDescriptor.vertexFunction = vertexFunction;
        pipelineStateDescriptor.fragmentFunction = fragmentFunction;
        pipelineStateDescriptor.colorAttachments[0].pixelFormat = 
        mtkView.colorPixelFormat;
    

    Also no changes necessary to MTLRenderPassDescriptor from currentRenderPassDescriptor.

    EDIT: Also be sure to set isOpaque property of MTKView to false too.