Metal入門教程(八)Metal與OpenGL ES交互

前言

Metal入門教程(一)圖片繪制
Metal入門教程(二)三維變換
Metal入門教程(三)攝像頭采集渲染
Metal入門教程(四)灰度計算
Metal入門教程(五)視頻渲染
Metal入門教程(六)邊界檢測
Metal入門教程(七)天空盒全景

前面的教程介紹了Metal的圖片繪制、三維變換、視頻渲染、天空盒,用計算管道實現(xiàn)灰度計算和sobel邊界檢測,這次探究Metal與OpenGL ES的交互。

Metal系列教程的代碼地址;
OpenGL ES系列教程在這里

你的star和fork是我的源動力,你的意見能讓我走得更遠

正文

UIImage是我們常用的圖像類,可以轉(zhuǎn)成CVPixelBufferRef,表示存儲在內(nèi)存的圖像數(shù)據(jù);
id<MTLTexture> 是Metal的紋理,表示的是存儲在顯存的圖像數(shù)據(jù);
GLuint 是OpenGL ES的紋理,表示的是存儲在顯存的圖像數(shù)據(jù)。

CVPixelBufferRef <==> Metal紋理Metal入門教程(五)視頻渲染有詳細的介紹;
CVPixelBufferRef <==> OpenGL ES紋理OpenGL ES文集也有相關(guān)的介紹;
本文就是基于CVPixelBufferRef,將Metal的紋理轉(zhuǎn)成CVPixelBufferRef,再用CVPixelBufferRef轉(zhuǎn)成OpenGL ES的紋理,實現(xiàn)Metal到OpenGL ES的交互。

效果展示

三種渲染效果展示

具體步驟

1、Metal渲染

Metal渲染包括兩步,分別是渲染到紋理和渲染到屏幕。
紋理是由CVMetalTextureGetTexture方法生成,需要事先創(chuàng)建CVOpenGLESTextureCacheRef,再CVMetalTextureCacheCreateTextureFromImage創(chuàng)建具體的CVOpenGLESTextureRef,具體的創(chuàng)建過程如下:

- (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實現(xiàn),設(shè)置對應(yīng)的輸入和輸出紋理即可。
需要注意的是,這次給commandBuffer添加了結(jié)束回調(diào)。在Metal渲染完畢后,會把CVPixelBufferRef里面的數(shù)據(jù)轉(zhuǎn)成UIImage,同時也會傳給OpenGL ES渲染:

    // 渲染到紋理
    {
        // 創(chuàng)建計算指令的編碼器
        id<MTLComputeCommandEncoder> renderToTextureEncoder = [commandBuffer computeCommandEncoder];
        // 設(shè)置計算管道,以調(diào)用shaders.metal中的內(nèi)核計算函數(shù)
        [renderToTextureEncoder setComputePipelineState:self.computePipelineState];
        // 輸入紋理
        [renderToTextureEncoder setTexture:self.sourceTexture
                           atIndex:LYFragmentTextureIndexTextureSource];
        // 輸出紋理
        [renderToTextureEncoder setTexture:self.destTexture
                           atIndex:LYFragmentTextureIndexTextureDest];
        // 計算區(qū)域
        [renderToTextureEncoder dispatchThreadgroups:self.groupCount
                       threadsPerThreadgroup:self.groupSize];
        
        // 調(diào)用endEncoding釋放編碼器,下個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ù)對象是UIImage,需要把CVPixelBufferRef轉(zhuǎn)成UIImage,具體的步驟如下:

/**
 *  根據(jù)CVPixelBufferRef返回圖像
 *
 *  @param pixelBufferRef 像素緩存引用
 *
 *  @return UIImage對象
 */
- (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的時候是kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrderDefault,這樣會導(dǎo)致出現(xià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ù)(第七個)需要特別注意,這里的格式要和CVPixelBufferCreate方法的pixelFormatType(第四個)對應(yīng),否則會出現(xiàn)圖片顏色異常的情況。

3、OpenGL ES渲染

OpenGL ES的渲染關(guān)鍵在于把CVPixelBufferRef轉(zhuǎn)成紋理,我們可以用CVOpenGLESTextureCacheCreateTextureFromImage方法獲取到CVOpenGLESTextureRef,再用CVOpenGLESTextureGetTargetCVOpenGLESTextureGetName獲取紋理的相應(yīng)信息,之后便是正常的OpenGL ES渲染邏輯。

遇到的問題

1、OpenGL ES渲染的圖片不透明

按照設(shè)計,左上角是UIKit渲染的圖片,右上角是OpenGL ES渲染的圖片,中間是Metal渲染的圖片,并UIKit和OpenGL ES渲染區(qū)域應(yīng)該是透明的。但是實際的效果如下:


Metal與OpenGL

OpenGL ES渲染的圖片不透明!

  • 首先猜測的點是因為圖片本身不透明,導(dǎo)致底為黑色;這個通過UIImageView渲染的效果排除;
  • 其次猜測的是OpenGL ES渲染的時候,把透明的部分設(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);
}

通過上面的代碼可以看出,確實所有像素點的alpha值都是1!
但這個并不是問題所在,因為這里只決定三角形區(qū)域內(nèi)的像素點顏色,黑色部分為背景,并不受shader的影響。

  • 通過上面的猜測,發(fā)現(xià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)黑色背景依舊沒有消除!這次問題出現(xiàn)在哪里?
  • 沿著我們分析的路徑,我們已經(jīng)排除掉shader、GL指令的影響,當渲染出來透明的區(qū)域時,理應(yīng)和UIKit的視圖進行混合?;貞浵翺penGL ES教程里的內(nèi)容,蘋果用CAEAGLLayer來承載OpenGL ES的內(nèi)容,最終渲染結(jié)果都是CAEAGLLayer上顏色緩沖區(qū)的數(shù)據(jù)。既然我們判斷渲染結(jié)果沒有問題,那么應(yīng)該從CAEAGLLayer與UILayer的混合開始檢查。通過排查CAEAGLLayer的屬性設(shè)置,終于發(fā)現(xiàn)問題所在是eaglLayer.opaque = YES;!CAEAGLLayer的不透明屬性設(shè)置為YES,有利于性能的優(yōu)化,但是在demo里是會造成額外的影響!
    通過把opaque屬性設(shè)置成NO,問題終于解決。

總結(jié)

本文介紹基于CVPixelBufferRef如何把Metal紋理轉(zhuǎn)成OpenGL ES紋理,而OpenGL ES紋理轉(zhuǎn)成Metal紋理的操作也類似。
在理解轉(zhuǎn)換過程中,有兩個等式非常關(guān)鍵:
CVBufferRef = CVImageBufferRef = CVMetalTextureRef
CVImageBufferRef=CVPixelBufferRef
在理清這些數(shù)據(jù)結(jié)構(gòu)后,能更好理解蘋果如何對CPU和GPU的數(shù)據(jù)進行轉(zhuǎn)換。
預(yù)告,下一篇是對入門教程的總結(jié)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容