Metal Camera開發1:讀取渲染結果生成UIImage
本文件通過Metal compute shader對攝像頭當前捕獲的畫面進行簡單的Gamma校正,繪製到螢幕(MTKView)及將渲染結果儲存成UIImage。文件最後簡要討論了Metal compute shader的dispatchThreadgroups配置問題。
文件結構:
- 配置AVCaptureSession獲取攝像頭當前畫面
- 初始化Compute Shader環境
- 編寫Gamma校正shader程式碼
- 渲染Compute Shader處理後的紋理到螢幕
- 讀取Metal渲染結果並生成UIImage
- 討論:Metal compute shader合理的dispatchThreadgroups設定
1. 配置AVCaptureSession獲取攝像頭當前畫面
參考我之前的文件iOS VideoToolbox硬編H.265(HEVC)H.264(AVC):1 概述進行攝像頭的配置,簡單起見,令攝像頭輸出畫面為豎直方向的RGBA資料,後續文件再實踐Metal Shader實現YUV轉RGB,然後進行各種濾鏡的疊加,參考程式碼如下。
let device = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
let input = try? AVCaptureDeviceInput(device: device)
if session.canAddInput(input) {
session.addInput(input)
}
let output = AVCaptureVideoDataOutput()
output.videoSettings = [kCVPixelBufferPixelFormatTypeKey as AnyHashable : kCVPixelFormatType_32BGRA]
output.setSampleBufferDelegate(self, queue: DispatchQueue(label: "CamOutputQueue"))
if session.canAddOutput(output) {
session.addOutput(output)
}
if session.canSetSessionPreset(AVCaptureSessionPreset1920x1080) {
session.canSetSessionPreset(AVCaptureSessionPreset1920x1080)
}
session.beginConfiguration()
for (_, connection) in output.connections.enumerated() {
for (_, port) in (connection as! AVCaptureConnection).inputPorts.enumerated() {
if (port as! AVCaptureInputPort).mediaType == AVMediaTypeVideo {
videoConnection = connection as? AVCaptureConnection
break
}
}
if videoConnection != nil {
break;
}
}
if (videoConnection?.isVideoOrientationSupported)! {
videoConnection?.videoOrientation = .portrait
}
session.commitConfiguration()
session.startRunning()
2. 初始化Compute Shader環境
Core Video給Metal提供了類似OpenGL ES建立紋理的介面CVMetalTextureCache。除此之外,還需進行Metal要求的MTLLibrary等準備工作,參考程式碼如下。
var textureCache : CVMetalTextureCache?
var imageTexture: MTLTexture?
var commandQueue: MTLCommandQueue?
var library: MTLLibrary?
var pipeline: MTLComputePipelineState?
//------------
device = MTLCreateSystemDefaultDevice()
mtlView.device = device
mtlView.framebufferOnly = false
mtlView.clearColor = MTLClearColor(red: 0, green: 0, blue: 0, alpha: 1)
library = device?.newDefaultLibrary()
guard let function = library?.makeFunction(name: "gamma_filter") else {
fatalError()
}
pipeline = try! device?.makeComputePipelineState(function: function)
commandQueue = device?.makeCommandQueue()
CVMetalTextureCacheCreate(kCFAllocatorDefault, nil, device!, nil, &textureCache)
由於要讀取螢幕上顯示的畫面,需將MTKView.framebufferOnly屬性設定為false。
3. 編寫Gamma校正shader程式碼
inTexture
表示攝像頭當前捕獲的畫面,outTexture
表示處理後的資料,將會渲染到螢幕。
#include <metal_stdlib>
using namespace metal;
kernel void gamma_filter(
texture2d<float, access::read> inTexture [[texture(0)]],
texture2d<float, access::write> outTexture [[texture(1)]],
uint2 gid [[thread_position_in_grid]])
{
float4 inColor = inTexture.read(gid);
const float4 outColor = float4(pow(inColor.rgb, float3(0.4/* gamma校正引數 */)), inColor.a);
outTexture.write(outColor, gid);
}
4. 渲染Compute Shader處理後的紋理到螢幕
在MTKViewDelegate的draw(in view: MTKView)
方法中繪製Compute Shader處理後的紋理到螢幕,參考程式碼如下。
guard let texture = imageTexture else {
return
}
guard let drawable = view.currentDrawable else {
return
}
guard let commandBuffer = commandQueue?.makeCommandBuffer() else {
return
}
let encoder = commandBuffer.makeComputeCommandEncoder()
encoder.setComputePipelineState(pipeline!)
encoder.setTexture(texture, at: 0)
encoder.setTexture(drawable.texture, at: 1)
let threads = MTLSize(width: 16, height: 16, depth: 1)
let threadgroups = MTLSize(width: texture.width / threads.width,
height: texture.height / threads.height,
depth: 1)
encoder.dispatchThreadgroups(threadgroups, threadsPerThreadgroup: threads)
encoder.endEncoding()
commandBuffer.present(drawable)
commandBuffer.commit()
關鍵程式碼encoder.setTexture(drawable.texture, at: 1)
指示compute shader將gamma校正結果寫到MTKView.currentDrawable.texture。
5. 讀取Metal渲染結果並生成UIImage
類似OpenGL ES的glReadPixels操作,需要注意大小端位元組序及UIKit與Metal紋理座標系的差異。由第4節渲染Compute Shader處理後的紋理到螢幕可知,MTKView.currentDrawable.texture是當前的渲染結果紋理,讀取Metal渲染結果問題就成了MTLTexture轉換成UIImage問題,可藉助Core Graphics介面實現,參考程式碼如下。
let image = currentDrawable?.texture.toUIImage()
為方便後續開發,給MTLTexture新增轉換成UIImage介面。
public extension MTLTexture {
public func toUIImage() -> UIImage {
let bytesPerPixel: Int = 4
let imageByteCount = self.width * self.height * bytesPerPixel
let bytesPerRow = self.width * bytesPerPixel
var src = [UInt8](repeating: 0, count: Int(imageByteCount))
let region = MTLRegionMake2D(0, 0, self.width, self.height)
self.getBytes(&src, bytesPerRow: bytesPerRow, from: region, mipmapLevel: 0)
let bitmapInfo = CGBitmapInfo(rawValue: (CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.noneSkipFirst.rawValue))
let colorSpace = CGColorSpaceCreateDeviceRGB()
let bitsPerComponent = 8
let context = CGContext(data: &src, width: self.width, height: self.height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo.rawValue);
let dstImageFilter = context?.makeImage();
return UIImage(cgImage: dstImageFilter!, scale: 0.0, orientation: UIImageOrientation.downMirrored) // 對於本文件,不需要downMirrored,因為第1節強制攝像頭輸出portrait方向影象
}
}
6. 討論:compute shader合理的dispatchThreadgroups設定
第4節渲染Compute Shader處理後的紋理到螢幕簡單設定了dispatchThreadgroups,那麼合理的dispatchThreadgroups值應該是多少呢?可參考官方文件:Working with threads and threadgroups,參考設定程式碼如下。
let w = pipeline!.threadExecutionWidth
let h = pipeline!.maxTotalThreadsPerThreadgroup / w
let threadsPerThreadgroup = MTLSizeMake(w, h, 1)
let threadgroupsPerGrid = MTLSize(width: (texture.width + w - 1) / w,
height: (texture.height + h - 1) / h,
depth: 1)
使用上述程式碼,在iPhone 7p上計算1080p畫面,GPU耗時略有下降。
相關文章
- iOS開發-圖片UIImageiOSUI
- [譯]Metal 渲染管線教程
- 原始碼閱讀:SDWebImage(十九)——UIImage+ForceDecode/UIImage+GIF/UIImage+MultiFormat原始碼WebUIORM
- 3D高斯(1)從json讀取資料渲染3DJSON
- 按概率生成隨機結果,自己控制字元結果的生成類似彩票系統隨機字元
- PLSQL Language Reference-PL/SQL子程式-PL/SQL函式結果快取-開啟函式結果快取SQL函式快取
- Sparkstreaming讀取Kafka訊息再結合SparkSQL,將結果儲存到HBaseSparkKafkaSQL
- Android Camera開發指南Android
- Metal 系列教程(1)- Metal 介紹及基本使用
- mysql group by 取想要的結果MySql
- PHP PDO獲取結果集PHP
- Metal實現YUV轉RGB渲染視訊
- API介面開發(一):介面開發返回結果解決方案API
- 一個Accecc_Token生成和快取和讀取類,微信/小程式開發必須學快取
- exonerate結果整理,獲取target序列
- diff詳解,讀懂diff結果
- sysbench安裝、使用、結果解讀
- Metal入門教程(三)攝像頭採集渲染
- 獲取任務的執行結果
- LoadRunner測試結果分析(1)
- 讀取本地Excel檔案生成echartsExcelEcharts
- PTA (學生成績讀取與排序)排序
- ccrendertexture to uiimageUI
- 讀取popen輸出結果時未截斷字串導致的命令列注入字串命令列
- "".indexOf()的作用,以及結果標識 1、0、-1Index
- oracle result cache 結果集快取的使用Oracle快取
- 關於獲取事件相應的結果事件
- python執行shell並獲取結果Python
- Java 讀取txt檔案生成Word文件Java
- python讀取和生成excel檔案PythonExcel
- KALDI-IO庫的生成與讀取
- 2013年JavaScript開發人員調查結果JavaScript
- Vue 原始碼解讀(10)—— 編譯器 之 生成渲染函式Vue原始碼編譯函式
- STM32定時器觸發ADC多通道連續取樣,DMA快取結果定時器快取
- RK3399 camera驅動開發
- Android平臺Camera開發實踐指南Android
- 《Web安全開發指南》讀後總結Web
- MySQL 查詢結果取交集的實現方法MySql