본문 바로가기

프로그래밍/iOS,macOS

[Metal] Render to Texture

 

버텍스

let vertexData:[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,
]

버퍼

let size = vertexData.count * MemoryLayout<Float>.size

// var vertexBuffer:MTLBuffer
vertexBuffer = device.makeBuffer(bytes: vertexData, length: size)
vertexBuffer.label = "VertexBuffer"

 

 

렌더타겟용 텍스처 생성

렌더링에 사용할 텍스처는 MTKTextureLoader 등으로 별로 생성

let texDescriptor = MTLTextureDescriptor()
texDescriptor.textureType = MTLTextureType.type2D
texDescriptor.width = 1024
texDescriptor.height = 1024
texDescriptor.pixelFormat = .bgra8Unorm
texDescriptor.storageMode = .shared
texDescriptor.usage = [.renderTarget, .shaderRead]


// var renderTargetTexture: MTLTexture
renderTargetTexture = device.makeTexture(descriptor: texDescriptor)


// 프레그먼트 쉐이더에 사용할 텍스처는 필요한 형태로 준비(아래는 리소스에서 읽어오는 예)
let textureLoader = MTKTextureLoader(device: device)
renderTexture = loader.newTexture("image", 
                                   scaleFactor: 1.0, 
                                   bundle: nil, 
                                   options[:] )

 

텍스처 타겟 렌더패스

let clearColor = MTLClearColor(red:0.0, green:0.0,blue:0.0, alpha:1.0)

renderPassDescriptor = MTLRenderPassDescritor()
renderPassDescriptor.colorAttachments[0].texture = renderTargetTexture
renderPassDescriptor.colorAttachments[0].loadAction = .clear
renderPassDescriptor.colorAttachments[0].clearColor = clearColor
renderPassDescriptor.colorAttachments[0].storeAction = .store

 

텍스처 타겟 렌더파이프라인

var library = device.makeDefaultLibrary()

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


let pipelineDescriptor = MTLRenderPipelineDescriptor()
pipelineDescriptor.lable = "Render Pipeline"
pipelineDescriptor.vertexDescriptor = vertexDescriptor
pipelineDescriptor.sampleCount = 1
pipelineDescriptor.vertexFunction = library.makeFunction(name: "vertexFunction")
pipelineDescriptor.fragmentFunction = library.makeFunction(name: "fragmentFunction")
pipelineDescriptor.colorAttachments[0].pixelFormat = renderTargetTexture.colorPixelFormat

do {
  // var pipelineState: MTLRenderPipelineState
  try pipelineState = device.makeRenderPipelineState(descriptor: pipelineDescriptor)
} catch let error {

}

 

 

렌더링

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

let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor)
renderEncoder.label = "RenderEncoder"
renderEncoder.setCullMode(.none)
renderEncoder.setRenderPipelineState(pipelineState)
renderEncoder.setVertexBuffer(vertexBuffer, offset:0, index, 0)
renderEncoder.setFragmentTexture( renderTexture, index 0 )

renderEncoder.drawPrimitives(type: .triangleStrip, vertexStart:0, vertexCount: 4)
renderEncoder.endEncoding()

commandBuffer.commit()

 

쉐이더

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

using namespace metal;

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

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

vertex VertexDataOut vertexFunction(VertexData in [[stage_in]]) {
  VertexDataOut out;
  out.position = float4(in.position, 0.0, 1.0);
  out.texCoord = in.texCoord;
  return out
}

fragment float4 fragmentFunction(VertexDataOut in [[stage_in]],
                                 texture2d<float> imageTexutre [[texture(0)]]) {
  constexpr sampler colorSampler(mip_filter::linear,
                                 mag_filter::linear,
                                 min_filter::linear);
  float4 colorSample = imageTexture.sample(colorSampler, in.texCoord);
  return colorSample;

}

 

렌더링 루프

MTKView, SCNView 등 렌더링을 위한 delegate 메쏘드가 호출되면 렌더링을 수행하거나 별도 렌더링을 위한 메쏘드가 없는 상황에서는 직접 렌더링을 위한 루프를 생성해 주어야 한다.

CADisplayLink 를 사용한 렌더링 루프 예

   .
   .
  // CADisplayLink
  let displayLink = CADisplayLink( target:self, selector:#selector(displayUpdate))
  displayLink.add( to: .main, forMode: .default)
   .
   .
   .
   
}


// 콜백
@objc func displayUpdate(displaylink: CADisplayLink) {        
  let cmTime = CMTime(seconds:displaylink.timestamp, preferredTimescale: 1000000)
  
  
  // 렌더링 호출
  
  
}

 

텍스처를 CVPixelBuffer 로 복사

렌더타겟을 텍스처로 지정한 경우 해당 데이터를 CVPixelBuffer로 복사해 사용할 일들이 있는데, 이때 아래와 같이 생성해둔 pixel buffer로 데이터를 복사해 사용할 수 있다.

var pixelBuffer:CVPixelBuffer?
CVPixelBufferCreate(kCFAllocatorDefault,
                    texture.width,
                    texture.height,
                    kCVPixelFormatType_32BGRA,
                    nil,
                    &pixelBuffer)
  .
  .
  
CVPixelBufferLockBaseAddress(pixelBuffer, [] )
let pixelBufferBytes = CVPixelBufferGetBaseAddress(pixelBuffer)!
let bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer)
let region = MTLRegionMake2D(0, 0, texture.width, texture.height)
texture.getBytes(pixelBufferBytes, bytesPerRow: bytesPerRow, from: region, mipmapLevel:0)
CVPixelBufferUnlockBaseAddress(pixelBuffer, [])