본문 바로가기

프로그래밍/iOS,macOS

[Metal] 이미지 렌더링~ 사각형 그리기

뷰 컨트롤러


스토리보드에 MTKView 추가하고, mtk view 의 기본적인 설정을 한다.
delegate는 아래 정의할 renderer로 연결

class ViewController: UIViewController {
  @IBOutlet weak var mtkView: MTKView!
  
  var renderer: Renderer = .init()
  
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    self.mtkView.enableSetNeedsDisplay = true
    self.mtkView.device = self.renderer.device
    self.mtkView.delegate = self.renderer
  }
}

 

정점

이미지만을 표시하기 위해 4각형 정의

let kImagePlaneVertex:[Float] = [
   -1.0, -1.0, 0.0, 1.0,
    1.0, -1.0, 1.0, 1.0,
    -1.0, 1.0, 0.0, 0.0,
    1.0, 1.0, 1.0, 0.0,
]

 

렌더러

mtkview의 delegate
draw 메쏘드는 MTKView에서 호출해 주며, 이때 렌더링을 수행한다.
만약 매번 업데이트되어야 하는 경우 CADisplayL
ink 등으로 렌더링 루프를 생성해 사용한다.

extension Renderer:MTKViewDelegate {
  func draw(in view: MTKView) {
    self.render(view: view)
  }
  
  func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize ) { 
  }
}
class Renderer: NSObject {
    var device:MTLDevice!
    var commandQueue: MTLCommandQueue!
    
    var imageVertexBuffer: MTLBuffer!
    var imagePipelineState: MTLRenderPipelineState!
    var imageDepthState:MTLDepthStencilState!
    
    var imageVertexFunction: MTLFunction!
    var imageFragmentFunction: MTLFunction!
    
    
    override init() {
        super.init()
        
        self.device = MTLCreateSystemDefaultDevice()
        initMetal()
    }
    
    .
    .
    .
    .
    .
    
}

 

초기화

func initMetal() {
  guard let defaultLibrary = try? self.device.makeDefaultLibrary(bundle: Bundle(for: Renderer.self)) else {
  print("[Renderer.initMetal] init error")
    return
  }

  imageVertexFunction = defaultLibrary.makeFunction(name: "imageVertexFunction")
  imageFragmentFunction = defaultLibrary.makeFunction(name: "imageFragmentFunction")

  self.commandQueue = self.device.makeCommandQueue()

  let size = kImagePlaneVertexData.count * MemoryLayout<Float>.size
  imageVertexBuffer = self.device.makeBuffer(bytes: kImagePlaneVertexData, length: size)
  imageVertexBuffer.label = "ImageVertexBuffer"


  let imageVertexDescriptor = MTLVertexDescriptor()
  imageVertexDescriptor.attributes[0].format = .float2
  imageVertexDescriptor.attributes[0].offset = 0
  imageVertexDescriptor.attributes[0].bufferIndex = 0
  imageVertexDescriptor.attributes[1].format = .float2
  imageVertexDescriptor.attributes[1].offset = 8
  imageVertexDescriptor.attributes[1].bufferIndex = 0
  imageVertexDescriptor.layouts[0].stride = 16
  imageVertexDescriptor.layouts[0].stepRate = 1
  imageVertexDescriptor.layouts[0].stepFunction = .perVertex


  let imagePipelineDescriptor = MTLRenderPipelineDescriptor()
  imagePipelineDescriptor.label = "ImageRenderPipeline"
  imagePipelineDescriptor.sampleCount = 1
  imagePipelineDescriptor.vertexFunction = imageVertexFunction
  imagePipelineDescriptor.fragmentFunction = imageFragmentFunction
  imagePipelineDescriptor.vertexDescriptor = imageVertexDescriptor
  imagePipelineDescriptor.depthAttachmentPixelFormat = .depth32Float
  imagePipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm

  do {
    try self.imagePipelineState = self.device.makeRenderPipelineState(descriptor: imagePipelineDescriptor)
  } catch let error {
    print("error=\(error.localizedDescription)")
  }

  // depth state
  let depthDescriptor = MTLDepthStencilDescriptor()
  depthDescriptor.depthCompareFunction = .lessEqual
  depthDescriptor.isDepthWriteEnabled = true
  self.imageDepthState = self.device.makeDepthStencilState(descriptor: depthDescriptor)
}

 

렌더링

func render(view:MTKView) {
  print("render")

  guard let renderPass = view.currentRenderPassDescriptor else { return }
  guard let drawable = view.currentDrawable else { return }
  guard let commandBuffer = self.commandQueue.makeCommandBuffer() else { return }
  commandBuffer.label = "RenderCommand"

  guard let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPass) else {
    return
  }

  renderEncoder.label = "RenderEncoder"
  renderEncoder.setCullMode(.front)
  renderEncoder.setRenderPipelineState(self.imagePipelineState)
  renderEncoder.setDepthStencilState(self.imageDepthState)

  renderEncoder.setVertexBuffer(self.imageVertexBuffer, offset: 0, index: 0)
  renderEncoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4)

  renderEncoder.endEncoding()
  commandBuffer.present(drawable)
  commandBuffer.commit()
}

 

 

쉐이더

#include <metal_stdlib>
#include <simd/simd.h>

using namespace metal;


typedef struct {
    float3 position [[attribute(0)]];
    float2 texCoord [[attribute(1)]];
} ImageVertex;

typedef struct {
    float4 position [[position]];
    float2 texCoord;
} ImageOut;


vertex ImageOut imageVertexFunction( ImageVertex in [[stage_in]]) {
    ImageOut out;
    
    
    float4 position = float4(in.position, 1.0);
    out.position = position;
    out.texCoord = in.texCoord;
    return out;
}

fragment float4 imageFragmentFunction(ImageOut in [[stage_in]] ) {
    return float4( 1.0, 0.0, 0.0, 1.0);
}