Metal学习(8) - 离屏渲染

599 阅读2分钟

ShaderType.h

#include <simd/simd.h>

typedef enum ShaderVertexInputIndex
{
    ShaderVertexInputIndexVertices     = 0,
    ShaderVertexInputIndexViewportSize = 1,
} ShaderVertexInputIndex;

/// 纹理索引
typedef enum ShaderTextureIndex
{
    ShaderTextureIndexInput  = 0,
    ShaderTextureIndexOutput = 1,
} ShaderTextureIndex;

typedef struct
{
    vector_float2 position;
    vector_float2 textureCoordinate;
} ShaderVertex;

Shader.metal

#include <metal_stdlib>
using namespace metal;

#include "ShaderType.h"

struct RasterizerData
{
    float4 position [[position]];
    float2 textureCoordinate;
};

vertex RasterizerData vertexShader(const uint vertexID [[vertex_id]],
                                   constant ShaderVertex *vertices [[buffer(ShaderVertexInputIndexVertices)]])
{
    RasterizerData out;
    out.position = vector_float4(vertices[vertexID].position.x, vertices[vertexID].position.y, 0.0, 1.0);
    out.textureCoordinate = vertices[vertexID].textureCoordinate;
    return out;
}

fragment float4 pretreatmentFragmentShader(RasterizerData in [[stage_in]],
                               texture2d<half> colorTexture [[texture(ShaderTextureIndexInput)]])
{
    constexpr sampler textureSampler (mag_filter::linear, min_filter::linear);
    const float2 coordinate0 = float2(in.textureCoordinate.x-0.1, 1.0-in.textureCoordinate.y);
    const float2 coordinate1 = float2(in.textureCoordinate.x-0.2, 1.0-in.textureCoordinate.y);
    const half4 colorSample0 = colorTexture.sample(textureSampler, coordinate0);
    const half4 colorSample1 = colorTexture.sample(textureSampler, coordinate1);
    return float4(colorSample0*0.7+colorSample1*0.3);
}

fragment float4 fragmentShader(RasterizerData in [[stage_in]],
                               texture2d<half> baseTexture [[texture(ShaderTextureIndexInput)]],
                               texture2d<half> colorTexture [[texture(ShaderTextureIndexOutput)]])
{
    constexpr sampler textureSampler (mag_filter::linear, min_filter::linear);
    const half4 colorSample0 = baseTexture.sample(textureSampler, in.textureCoordinate);
    const half4 colorSample1 = colorTexture.sample(textureSampler, in.textureCoordinate);
    return float4(colorSample0*0.6+colorSample1*0.4);
}

MetalRender.swift

import UIKit
import MetalKit
import simd

class MetalRender: NSObject {
    private var commandQueue: MTLCommandQueue?
    private var offScreenRenderPassDescriptor: MTLRenderPassDescriptor?
    private var offScreenPipelineState: MTLRenderPipelineState?
    private var renderPipelineState: MTLRenderPipelineState?
    private var inputTexture: MTLTexture?
    private var outputTexture: MTLTexture?
    private let triangleVertices: [ShaderVertex] = {
        let vertex0 = ShaderVertex(position: vector_float2(x: -1, y: -1), textureCoordinate: vector_float2(x: 0, y: 0))
        let vertex1 = ShaderVertex(position: vector_float2(-1, 1), textureCoordinate: vector_float2(0, 1))
        let vertex2 = ShaderVertex(position: vector_float2(1, -1), textureCoordinate: vector_float2(1, 0))
        let vertex3 = ShaderVertex(position: vector_float2(1, 1), textureCoordinate: vector_float2(1, 1))
        let array: [ShaderVertex] = [vertex0, vertex1, vertex2, vertex3]
        return array
    }()

    private override init() {
        super.init()
    }
    convenience init(_ view: MTKView) {
        self.init()

        let device = view.device

        let defaultLibrary = device?.makeDefaultLibrary()
        let vertexFunction = defaultLibrary?.makeFunction(name: "vertexShader")
        let pretreatmentFragmentFunction = defaultLibrary?.makeFunction(name: "pretreatmentFragmentShader")
        let fragmentFunction = defaultLibrary?.makeFunction(name: "fragmentShader")

        commandQueue = device?.makeCommandQueue()

        guard let image = flipImage(UIImage(named: "02")), let cgImage = image.cgImage else { return }
        let textureLoader = MTKTextureLoader(device: device!)
        inputTexture = try? textureLoader.newTexture(cgImage: cgImage, options: [MTKTextureLoader.Option.SRGB : false])

        let descriptor = MTLTextureDescriptor()
        descriptor.textureType = inputTexture!.textureType
        descriptor.pixelFormat = inputTexture!.pixelFormat
        descriptor.width = inputTexture!.width
        descriptor.height = inputTexture!.height
        // 因为是离屏渲染,必须有 .renderTarget
        descriptor.usage = [.renderTarget, .shaderRead]
        outputTexture = device!.makeTexture(descriptor: descriptor)

        // 创建离屏渲染管道的描述
        offScreenRenderPassDescriptor = MTLRenderPassDescriptor()
        // 设置指向屏幕外纹理
        offScreenRenderPassDescriptor?.colorAttachments[0].texture = outputTexture
        // 加载操作
        offScreenRenderPassDescriptor?.colorAttachments[0].loadAction = .clear
        // 清屏颜色
        offScreenRenderPassDescriptor?.colorAttachments[0].clearColor = MTLClearColor(red: 1, green: 0, blue: 0, alpha: 1)
        // 存储操作
        offScreenRenderPassDescriptor?.colorAttachments[0].storeAction = .store

        let offScreenPipelineStateDescriptor = MTLRenderPipelineDescriptor()
        offScreenPipelineStateDescriptor.label = "OffScreen Pipeline"
        offScreenPipelineStateDescriptor.vertexFunction = vertexFunction
        offScreenPipelineStateDescriptor.fragmentFunction = pretreatmentFragmentFunction
        offScreenPipelineStateDescriptor.colorAttachments[0].pixelFormat = outputTexture!.pixelFormat

        let renderPipelineStateDescriptor = MTLRenderPipelineDescriptor()
        renderPipelineStateDescriptor.label = "Render Pipeline"
        renderPipelineStateDescriptor.vertexFunction = vertexFunction
        renderPipelineStateDescriptor.fragmentFunction = fragmentFunction
        renderPipelineStateDescriptor.colorAttachments[0].pixelFormat = view.colorPixelFormat
        do {
            offScreenPipelineState = try device?.makeRenderPipelineState(descriptor: offScreenPipelineStateDescriptor)
            renderPipelineState = try device?.makeRenderPipelineState(descriptor: renderPipelineStateDescriptor)
        }catch {
            print(error)
        }
    }
}

// MARK: - private

private extension MetalRender {
    /// 翻转上下颠倒的图片
    func flipImage(_ image: UIImage?) -> UIImage? {
        guard let image = image else  { return nil }
        UIGraphicsBeginImageContextWithOptions(image.size, false, UIScreen.main.scale)
        let ctx = UIGraphicsGetCurrentContext()
        ctx?.translateBy(x: 0, y: image.size.height)
        ctx?.scaleBy(x: 1, y: -1)
        image.draw(in: CGRect(origin: .zero, size: image.size))
        let reslut = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return reslut
    }
}


// MARK: - MTKViewDelegate

extension MetalRender: MTKViewDelegate {
    func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
    }
    func draw(in view: MTKView) {

        guard offScreenPipelineState != nil else { return }
        guard renderPipelineState != nil else { return }

        let commandBuffer = commandQueue?.makeCommandBuffer()
        commandBuffer?.label = "MyCommand"

        if let offScreenRenderPassDescriptor = offScreenRenderPassDescriptor, let renderPassDescriptor = view.currentRenderPassDescriptor {

            // 离屏渲染
            let offScreenRenderEncoder = commandBuffer?.makeRenderCommandEncoder(descriptor: offScreenRenderPassDescriptor)
            offScreenRenderEncoder?.label = "OffScreen Encoder"
            offScreenRenderEncoder?.setRenderPipelineState(offScreenPipelineState!)
            offScreenRenderEncoder?.setVertexBytes(triangleVertices, length: triangleVertices.count*MemoryLayout<ShaderVertex>.size, index: Int(ShaderVertexInputIndexVertices.rawValue))
            offScreenRenderEncoder?.setFragmentTexture(inputTexture, index: Int(ShaderTextureIndexInput.rawValue))
            offScreenRenderEncoder?.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4)
            offScreenRenderEncoder?.endEncoding()

            // 正常渲染
            let renderEncoder = commandBuffer?.makeRenderCommandEncoder(descriptor: renderPassDescriptor)
            renderEncoder?.label = "Render Encoder"
            renderEncoder?.setViewport(MTLViewport(originX: 0, originY: 0, width: Double(view.drawableSize.width), height: Double(view.drawableSize.height), znear: -1, zfar: 1))
            renderEncoder?.setRenderPipelineState(renderPipelineState!)
            renderEncoder?.setVertexBytes(triangleVertices, length: triangleVertices.count*MemoryLayout<ShaderVertex>.size, index: Int(ShaderVertexInputIndexVertices.rawValue))
            renderEncoder?.setFragmentTexture(inputTexture, index: Int(ShaderTextureIndexInput.rawValue))
            renderEncoder?.setFragmentTexture(outputTexture, index: Int(ShaderTextureIndexOutput.rawValue))
            renderEncoder?.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4)
            renderEncoder?.endEncoding()

            if let drawable = view.currentDrawable {
                commandBuffer?.present(drawable)
            }
            commandBuffer?.addCompletedHandler({ buffer in
                let ciImage = CIImage(mtlTexture: self.outputTexture!)
                let image = UIImage(ciImage: ciImage!)
            })
        }
        commandBuffer?.commit()
        /*
         当样本提交命令缓冲区时,Metal 会依次执行两个渲染管道。
         当Metal检测到第一个渲染管道写入屏幕外纹理,第二个管道读取该屏幕外纹理时,它会阻止第二个管道执行,直到GPU完成第一个管道。
         */
    }
}

原始图片:

image.png

outputTexture:

image.png