前言
Metal入門教程(一)圖片繪制
Metal入門教程(二)三維變換
Metal入門教程(三)攝像頭采集渲染
Metal入門教程(四)灰度計(jì)算
Metal入門教程(五)視頻渲染
Metal入門教程(六)邊界檢測(cè)
Metal入門教程(七)天空盒全景
前面的教程介紹了Metal的圖片繪制、三維變換、視頻渲染钦铁、天空盒黎比,用計(jì)算管道實(shí)現(xiàn)灰度計(jì)算和sobel邊界檢測(cè)书妻,這次探究Metal與OpenGL ES的交互米诉。
Metal系列教程的代碼地址惊橱;
OpenGL ES系列教程在這里正林;
你的star和fork是我的源動(dòng)力杈绸,你的意見(jiàn)能讓我走得更遠(yuǎn)篡殷。
正文
UIImage是我們常用的圖像類,可以轉(zhuǎn)成CVPixelBufferRef,表示存儲(chǔ)在內(nèi)存的圖像數(shù)據(jù);
id<MTLTexture> 是Metal的紋理诱篷,表示的是存儲(chǔ)在顯存的圖像數(shù)據(jù)橙凳;
GLuint 是OpenGL ES的紋理茴肥,表示的是存儲(chǔ)在顯存的圖像數(shù)據(jù)础锐。
CVPixelBufferRef <==> Metal紋理在Metal入門教程(五)視頻渲染有詳細(xì)的介紹意推;
CVPixelBufferRef <==> OpenGL ES紋理在OpenGL ES文集也有相關(guān)的介紹趟薄;
本文就是基于CVPixelBufferRef也切,將Metal的紋理轉(zhuǎn)成CVPixelBufferRef讨越,再用CVPixelBufferRef轉(zhuǎn)成OpenGL ES的紋理漫雕,實(shí)現(xiàn)Metal到OpenGL ES的交互。
效果展示
具體步驟
1、Metal渲染
Metal渲染包括兩步,分別是渲染到紋理和渲染到屏幕吭历。
紋理是由CVMetalTextureGetTexture
方法生成,需要事先創(chuàng)建CVOpenGLESTextureCacheRef,再CVMetalTextureCacheCreateTextureFromImage
創(chuàng)建具體的CVOpenGLESTextureRef夯辖,具體的創(chuàng)建過(guò)程如下:
- (void)setupRenderTargetWithSize:(CGSize)size {
CFDictionaryRef empty= CFDictionaryCreate(kCFAllocatorDefault,
NULL,
NULL,
0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFMutableDictionaryRef attrs= CFDictionaryCreateMutable(kCFAllocatorDefault,
1,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFDictionarySetValue(attrs,
kCVPixelBufferIOSurfacePropertiesKey,
empty);
CVPixelBufferRef renderTarget;
CVPixelBufferCreate(kCFAllocatorDefault, size.width, size.height,
kCVPixelFormatType_32BGRA,
attrs,
&renderTarget);
size_t width = CVPixelBufferGetWidthOfPlane(renderTarget, 0);
size_t height = CVPixelBufferGetHeightOfPlane(renderTarget, 0);
MTLPixelFormat pixelFormat = MTLPixelFormatBGRA8Unorm;
CVMetalTextureRef texture = NULL;
CVReturn status = CVMetalTextureCacheCreateTextureFromImage(NULL, self.textureCache, renderTarget, NULL, pixelFormat, width, height, 0, &texture);
if(status == kCVReturnSuccess)
{
self.destTexture = CVMetalTextureGetTexture(texture);
self.renderPixelBuffer = renderTarget;
CFRelease(texture);
}
else {
NSAssert(NO, @"CVMetalTextureCacheCreateTextureFromImage fail");
}
}
渲染到紋理用的是computeShader實(shí)現(xiàn),設(shè)置對(duì)應(yīng)的輸入和輸出紋理即可蛮瞄。
需要注意的是,這次給commandBuffer添加了結(jié)束回調(diào)无蜂。在Metal渲染完畢后,會(huì)把CVPixelBufferRef里面的數(shù)據(jù)轉(zhuǎn)成UIImage午绳,同時(shí)也會(huì)傳給OpenGL ES渲染:
// 渲染到紋理
{
// 創(chuàng)建計(jì)算指令的編碼器
id<MTLComputeCommandEncoder> renderToTextureEncoder = [commandBuffer computeCommandEncoder];
// 設(shè)置計(jì)算管道,以調(diào)用shaders.metal中的內(nèi)核計(jì)算函數(shù)
[renderToTextureEncoder setComputePipelineState:self.computePipelineState];
// 輸入紋理
[renderToTextureEncoder setTexture:self.sourceTexture
atIndex:LYFragmentTextureIndexTextureSource];
// 輸出紋理
[renderToTextureEncoder setTexture:self.destTexture
atIndex:LYFragmentTextureIndexTextureDest];
// 計(jì)算區(qū)域
[renderToTextureEncoder dispatchThreadgroups:self.groupCount
threadsPerThreadgroup:self.groupSize];
// 調(diào)用endEncoding釋放編碼器妓笙,下個(gè)encoder才能創(chuàng)建
[renderToTextureEncoder endEncoding];
}
[commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> buffer) {
if (kCVReturnSuccess == CVPixelBufferLockBaseAddress(self.renderPixelBuffer,
kCVPixelBufferLock_ReadOnly)) { // 獲取到buffer
dispatch_async(dispatch_get_main_queue(), ^{
UIImage *image = [self lyGetImageFromPixelBuffer:self.renderPixelBuffer]; // 轉(zhuǎn)成UIImage
if (!self.imageView) {
self.imageView = [[UIImageView alloc] initWithImage:image];
[self.view addSubview:self.imageView];
}
// OpenGL ES渲染
[self.glView displayPixelBuffer:self.renderPixelBuffer];
// 釋放資源
CVPixelBufferUnlockBaseAddress(self.renderPixelBuffer, kCVPixelBufferLock_ReadOnly);
});
}
}];
最后再把上一步的輸出紋理作為輸入,直接渲染到屏幕。
2鼎姐、UIImageView渲染
UIImageView顯示的數(shù)據(jù)對(duì)象是UIImage,需要把CVPixelBufferRef轉(zhuǎn)成UIImage更振,具體的步驟如下:
/**
* 根據(jù)CVPixelBufferRef返回圖像
*
* @param pixelBufferRef 像素緩存引用
*
* @return UIImage對(duì)象
*/
- (UIImage *)lyGetImageFromPixelBuffer:(CVPixelBufferRef)pixelBufferRef {
CVImageBufferRef imageBuffer = pixelBufferRef;
CVPixelBufferLockBaseAddress(imageBuffer, 0);
void *baseAddress = CVPixelBufferGetBaseAddress(imageBuffer);
size_t width = CVPixelBufferGetWidth(imageBuffer);
size_t height = CVPixelBufferGetHeight(imageBuffer);
size_t bufferSize = CVPixelBufferGetDataSize(imageBuffer);
size_t bytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 0); //
CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, baseAddress, bufferSize, NULL);
// rgba的時(shí)候是kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrderDefault炕桨,這樣會(huì)導(dǎo)致出現(xiàn)藍(lán)色的圖片
CGImageRef cgImage = CGImageCreate(width, height, 8, 32, bytesPerRow, rgbColorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst, provider, NULL, true, kCGRenderingIntentDefault);
UIImage *image = [UIImage imageWithCGImage:cgImage];
CGImageRelease(cgImage);
CGDataProviderRelease(provider);
CGColorSpaceRelease(rgbColorSpace);
CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
return image;
}
CGImageCreate
方法的bitmapInfo參數(shù)(第七個(gè))需要特別注意,這里的格式要和CVPixelBufferCreate
方法的pixelFormatType(第四個(gè))對(duì)應(yīng)肯腕,否則會(huì)出現(xiàn)圖片顏色異常的情況献宫。
3、OpenGL ES渲染
OpenGL ES的渲染關(guān)鍵在于把CVPixelBufferRef轉(zhuǎn)成紋理实撒,我們可以用CVOpenGLESTextureCacheCreateTextureFromImage
方法獲取到CVOpenGLESTextureRef姊途,再用CVOpenGLESTextureGetTarget
和CVOpenGLESTextureGetName
獲取紋理的相應(yīng)信息涉瘾,之后便是正常的OpenGL ES渲染邏輯。
遇到的問(wèn)題
1捷兰、OpenGL ES渲染的圖片不透明
按照設(shè)計(jì)立叛,左上角是UIKit渲染的圖片,右上角是OpenGL ES渲染的圖片贡茅,中間是Metal渲染的圖片秘蛇,并UIKit和OpenGL ES渲染區(qū)域應(yīng)該是透明的。但是實(shí)際的效果如下:
OpenGL ES渲染的圖片不透明顶考!
- 首先猜測(cè)的點(diǎn)是因?yàn)閳D片本身不透明赁还,導(dǎo)致底為黑色;這個(gè)通過(guò)UIImageView渲染的效果排除驹沿;
- 其次猜測(cè)的是OpenGL ES渲染的時(shí)候艘策,把透明的部分設(shè)置成黑色;下面一段是shader 代碼:
varying highp vec2 texCoordVarying;
uniform sampler2D inputTexture;
precision mediump float;
void main()
{
lowp vec3 rgb = texture2D(inputTexture, texCoordVarying).rgb;
gl_FragColor = vec4(rgb, 1);
}
通過(guò)上面的代碼可以看出渊季,確實(shí)所有像素點(diǎn)的alpha值都是1柬焕!
但這個(gè)并不是問(wèn)題所在,因?yàn)檫@里只決定三角形區(qū)域內(nèi)的像素點(diǎn)顏色梭域,黑色部分為背景斑举,并不受shader的影響。
- 通過(guò)上面的猜測(cè)病涨,發(fā)現(xiàn)問(wèn)題可能在glClearColor設(shè)置了不透明的顏色富玷。檢查代碼果然有一行
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
,把背景設(shè)置成不透明的黑色既穆!
但在把這段代碼修改成glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
赎懦,發(fā)現(xiàn)黑色背景依舊沒(méi)有消除!這次問(wèn)題出現(xiàn)在哪里幻工? - 沿著我們分析的路徑励两,我們已經(jīng)排除掉shader、GL指令的影響囊颅,當(dāng)渲染出來(lái)透明的區(qū)域時(shí)当悔,理應(yīng)和UIKit的視圖進(jìn)行混合√叽回憶下OpenGL ES教程里的內(nèi)容盲憎,蘋果用CAEAGLLayer來(lái)承載OpenGL ES的內(nèi)容,最終渲染結(jié)果都是CAEAGLLayer上顏色緩沖區(qū)的數(shù)據(jù)胳挎。既然我們判斷渲染結(jié)果沒(méi)有問(wèn)題饼疙,那么應(yīng)該從CAEAGLLayer與UILayer的混合開(kāi)始檢查。通過(guò)排查CAEAGLLayer的屬性設(shè)置慕爬,終于發(fā)現(xiàn)問(wèn)題所在是
eaglLayer.opaque = YES;
窑眯!CAEAGLLayer的不透明屬性設(shè)置為YES屏积,有利于性能的優(yōu)化,但是在demo里是會(huì)造成額外的影響磅甩!
通過(guò)把opaque屬性設(shè)置成NO炊林,問(wèn)題終于解決。
總結(jié)
本文介紹基于CVPixelBufferRef如何把Metal紋理轉(zhuǎn)成OpenGL ES紋理更胖,而OpenGL ES紋理轉(zhuǎn)成Metal紋理的操作也類似铛铁。
在理解轉(zhuǎn)換過(guò)程中,有兩個(gè)等式非常關(guān)鍵:
CVBufferRef = CVImageBufferRef = CVMetalTextureRef
CVImageBufferRef=CVPixelBufferRef
在理清這些數(shù)據(jù)結(jié)構(gòu)后却妨,能更好理解蘋果如何對(duì)CPU和GPU的數(shù)據(jù)進(jìn)行轉(zhuǎn)換饵逐。
預(yù)告,下一篇是對(duì)入門教程的總結(jié)彪标。