Metal渲染YUV紋理

解碼設(shè)置

設(shè)置ItemOutput的pixelFormat為420v或者420f

- (void)p_createPlayerItemOutput {
    if (!self.itemOutput) {
        NSMutableDictionary *pixBuffAttributes = [NSMutableDictionary dictionary];
                [pixBuffAttributes setObject:@(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange)
                                      forKey:(id)kCVPixelBufferPixelFormatTypeKey];

        self.itemOutput = [[AVPlayerItemVideoOutput alloc] initWithPixelBufferAttributes:pixBuffAttributes];
        [self.itemOutput setDelegate:self queue:operationQueue];
        [self.itemOutput requestNotificationOfMediaDataChangeWithAdvanceInterval:ONE_FRAME_DURATION];
        [_playerItem addOutput:self.itemOutput];
    }
}

preview&textureCache創(chuàng)建

1旅赢、初始化MTKView
2飒硅、設(shè)置MTKView的device

- (void)p_initPreview:(CGRect)frame {
    self.preivew = [[MTKView alloc] initWithFrame:frame];
    self.preivew.device = MTLCreateSystemDefaultDevice();
    self.preivew.delegate = self;
//    self.preivew.framebufferOnly = NO;
    self.viewportSize = (vector_uint2){self.preivew.drawableSize.width, self.preivew.drawableSize.height};
    CVMetalTextureCacheCreate(NULL, NULL, self.preivew.device, NULL, &_textureCache);
}

pipeLine創(chuàng)建

1恢氯、通過MTKView的device創(chuàng)建Libary
2北苟、通過Libary獲取頂點函數(shù)和片源函數(shù)
3以躯、創(chuàng)建MTLRenderPipelineDescriptor,并設(shè)置頂點函數(shù)蟆盐、片源函數(shù)移必、pixelFormat
4室谚、通過MTLRenderPipelineDescriptor生成MTLRenderPipelineState,生成耗性能崔泵,須持有
5秒赤、通過MTKView的device創(chuàng)建MTLCommandQueue,生成耗性能管削,須持有

- (void)p_setupPipeline {
    NSString *bundlePath = [[NSBundle bundleForClass:[self class]].resourcePath
                                stringByAppendingPathComponent:@"/ZZH_Sharder.bundle"];
    NSBundle *resource_bundle = [NSBundle bundleWithPath:bundlePath];

    NSError *error;
    if (@available(iOS 10.0, *)) {
        id<MTLLibrary> defaultLibrary = [self.preivew.device newDefaultLibraryWithBundle:resource_bundle error:&error];
        if (error) {
            ZZHDebugLog(@"%@", error);
        }
        id<MTLFunction> vertexFunc = [defaultLibrary newFunctionWithName:@"vertexShader"];
        id<MTLFunction> fragmentFunc = [defaultLibrary newFunctionWithName:@"samplingShader"];
        
        MTLRenderPipelineDescriptor *des = [[MTLRenderPipelineDescriptor alloc] init];
        des.vertexFunction = vertexFunc;
        des.fragmentFunction = fragmentFunc;
        des.colorAttachments[0].pixelFormat = self.preivew.colorPixelFormat;
        self.pipelineState = [self.preivew.device newRenderPipelineStateWithDescriptor:des error:nil];
        self.commandQueue = [self.preivew.device newCommandQueue];
    } else {
        
    }
}

注意點

1倒脓、MTLLibary初始化

- (nullable id <MTLLibrary>)newDefaultLibrary;
- (nullable id <MTLLibrary>)newDefaultLibraryWithBundle:(NSBundle *)bundle error:(__autoreleasing NSError **)error

只會掃描mainBundle的metal文件,如果metal文件不再mainbundle需要通過指定bundle的方法初始化

初始化頂點坐標、紋理坐標

- (void)p_setupVertex {
    static const ZZHVertex quadVertices[] =
    {   // 頂點坐標,分別是x惶桐、y、z饲做、w;    紋理坐標遏弱,x盆均、y;
        { {  1.0, -1.0, 0.0, 1.0 },  { 1.f, 1.f } },
        { { -1.0, -1.0, 0.0, 1.0 },  { 0.f, 1.f } },
        { { -1.0,  1.0, 0.0, 1.0 },  { 0.f, 0.f } },
        
        { {  1.0, -1.0, 0.0, 1.0 },  { 1.f, 1.f } },
        { { -1.0,  1.0, 0.0, 1.0 },  { 0.f, 0.f } },
        { {  1.0,  1.0, 0.0, 1.0 },  { 1.f, 0.f } },
    };
    self.vertices = [self.preivew.device newBufferWithBytes:quadVertices
                                                     length:sizeof(quadVertices)
                                                    options:MTLResourceStorageModeShared]; // 創(chuàng)建頂點緩存
    self.numVertices = sizeof(quadVertices) / sizeof(ZZHVertex); // 頂點個數(shù)
}

注意點

1漱逸、頂點坐標原點在中心點泪姨,紋理坐標原點在左下角
2、紋理的生成是由圖片像素來生成的饰抒,而圖像的存儲是從左上角開始的肮砾,對應于上圖,就是圖像左上角像素生成的紋理部分就在紋理左下角處袋坑,即 上下顛倒仗处。

設(shè)置轉(zhuǎn)換矩陣

- (void)p_setupMatrix { // 設(shè)置好轉(zhuǎn)換的矩陣
    matrix_float3x3 kColorConversion601FullRangeMatrix = (matrix_float3x3){
        (simd_float3){1.164,  1.164, 1.164},
        (simd_float3){0.0, -0.213, 2.112},
        (simd_float3){1.793, -0.533,   0.0},
    };
    // shader 會將數(shù)據(jù)歸一化,而 uv 的取值區(qū)間本身存在-128到正128 然后歸一化到0-1 為了正確計算成rgb,
    // 則需要歸一化到 -0.5 - 0.5的區(qū)間
    vector_float3 kColorConversion601VideoRangeOffset = (vector_float3){ 0, -0.5, -0.5}; // 這個是偏移
    
    ZZHConvertMatrix matrix;
    // 設(shè)置參數(shù)
    matrix.matrix = kColorConversion601FullRangeMatrix;
    matrix.offset = kColorConversion601VideoRangeOffset;
    
    self.convertMatrix = [self.preivew.device newBufferWithBytes:&matrix
                                                          length:sizeof(ZZHConvertMatrix)
                                                         options:MTLResourceStorageModeShared];
}

注意點

1婆誓、YUV和rgb的轉(zhuǎn)換矩陣主要依賴yuv的顏色空間決策
2吃环、shader 會將數(shù)據(jù)歸一化,而 uv 的取值區(qū)間本身存在-128到正128 然后歸一化到0-1 為了正確計算成rgb洋幻, 則需要歸一化到 -0.5 - 0.5的區(qū)間郁轻。同時對VideoRange的y需要減掉16/255.0

幀渲染

1、通過commandQuene生成當前這次渲染所需要的MTLCommandBuffer
2鞋屈、通過MTKView獲取MTLRenderPassDescriptor
3范咨、設(shè)置MTLRenderPassDescriptor的clearColor
4故觅、通過MTLCommandBuffer和MTLRenderPassDescriptor生成MTLRenderCommandEncoder
5厂庇、encoder設(shè)置viewPort
6、encoder設(shè)置renderPipelineState
7输吏、encoder設(shè)置頂點buffer
8权旷、encoder上傳紋理
9、encoder設(shè)置片源buffer(轉(zhuǎn)換矩陣)
10贯溅、encoder drawing
11拄氯、encoder endEncoding
12、commandBuffer 顯示
13它浅、commandBuffer commit

- (void)displayPixelBuffer:(CVPixelBufferRef)pixelBuffer {
    /* 兩種方法獲取顏色空間信息
    CFTypeRef colorAttachments = CVBufferGetAttachment(pixelBuffer, kCVImageBufferYCbCrMatrixKey, NULL);
    OSType pixelFormat         = CVPixelBufferGetPixelFormatType(pixelBuffer);
    CFDictionaryRef cfMetadataDic = CMCopyDictionaryOfAttachments(NULL, pixelBuffer, kCMAttachmentMode_ShouldPropagate);
    NSDictionary *nsMetadataDic = (__bridge NSDictionary *_Nonnull)(cfMetadataDic);
     //發(fā)現(xiàn)用pixelFormatType創(chuàng)建的format跟pixelbuffer的format是一樣的
    CFDictionaryRef formatDes = CVPixelFormatDescriptionCreateWithPixelFormatType(kCFAllocatorDefault, CVPixelBufferGetPixelFormatType(pixelBuffer));
    **/
    id<MTLCommandBuffer> commandBuffer = [self.commandQueue commandBuffer];
    MTLRenderPassDescriptor *renderPassDes = self.preivew.currentRenderPassDescriptor;
    if (renderPassDes && pixelBuffer) {
        renderPassDes.colorAttachments[0].clearColor = MTLClearColorMake(0, 0.f, 0.f, 1.f);
        id<MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDes];
        [renderEncoder setViewport:(MTLViewport){0.0, 0.0, self.viewportSize.x, self.viewportSize.y, -1.0, 1.0 }]; // 設(shè)置顯示區(qū)域
        [renderEncoder setRenderPipelineState:self.pipelineState]; // 設(shè)置渲染管道译柏,以保證頂點和片元兩個shader會被調(diào)用
        [renderEncoder setVertexBuffer:self.vertices offset:0 atIndex:ZZHVertexInputIndexVertices];
        [self p_setupTextureWithEncoder:renderEncoder pixelBuffer:pixelBuffer];
        [renderEncoder setFragmentBuffer:self.convertMatrix offset:0 atIndex:ZZHFragmentInputIndexMatrix];
        [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:self.numVertices];
        [renderEncoder endEncoding];
        [commandBuffer presentDrawable:self.preivew.currentDrawable];
    }
    [commandBuffer commit];
}

紋理上傳

- (void)p_setupTextureWithEncoder:(id<MTLRenderCommandEncoder>)encoder pixelBuffer:(CVPixelBufferRef)pixelBuffer {
    id<MTLTexture> textureY = nil;
    id<MTLTexture> textureUV = nil;
    size_t width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0);
    size_t height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0);
    
    CVMetalTextureRef yTexture = NULL;
    CVReturn status = CVMetalTextureCacheCreateTextureFromImage(NULL, self.textureCache, pixelBuffer, NULL, MTLPixelFormatR8Unorm, width, height, 0, &yTexture);
    if (status == kCVReturnSuccess) {
        textureY = CVMetalTextureGetTexture(yTexture);
        CFRelease(yTexture);
    }
    
    width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1);
    height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1);
    CVMetalTextureRef uvTexture = NULL;
    status = CVMetalTextureCacheCreateTextureFromImage(NULL, self.textureCache, pixelBuffer, NULL, MTLPixelFormatRG8Unorm, width, height, 1, &uvTexture);
    if (status == kCVReturnSuccess) {
        textureUV = CVMetalTextureGetTexture(uvTexture);
        CFRelease(uvTexture);
    }
    if (textureY && textureUV) {
        [encoder setFragmentTexture:textureY atIndex:ZZHFragmentTextureIndexTextureY];
        [encoder setFragmentTexture:textureUV atIndex:ZZHFragmentTextureIndexTextureUV];
    }
}

注意點

1、uv數(shù)據(jù)是通過RG格式上傳到一個紋理的
2姐霍、紋理寬高需要是當前plane的寬高

metal

typedef struct {
    float4 clipSpacePosition [[position]];
    float2 textureCoordinate;
} RasterizerData;
// vertex_id是頂點shader每次處理的index鄙麦,用于定位當前的頂點
// buffer表明是緩存數(shù)據(jù),ZZHVertexInputIndexVertices是索引
vertex RasterizerData vertexShader(uint vertexID [[vertex_id]], constant ZZHVertex *vertexArray [[buffer(ZZHVertexInputIndexVertices)]]) {
    RasterizerData out;
    out.clipSpacePosition = vertexArray[vertexID].position;
    out.textureCoordinate = vertexArray[vertexID].textureCoordinate;
    return out;
}
//stage_in :標識該數(shù)據(jù)來自vertex的輸出
// texture表明是紋理數(shù)據(jù)镊折,LYFragmentTextureIndexTextureY是索引
fragment float4 samplingShader(RasterizerData input [[stage_in]], texture2d<float> textureY [[texture(ZZHFragmentTextureIndexTextureY)]], texture2d<float> textureUV [[texture(ZZHFragmentTextureIndexTextureUV)]], constant ZZHConvertMatrix *convertMatrix [[buffer(ZZHFragmentInputIndexMatrix)]]) {
    constexpr sampler textureSampler (mag_filter::linear, min_filter::linear);
    float3 yuv = float3(textureY.sample(textureSampler, input.textureCoordinate).r, textureUV.sample(textureSampler, input.textureCoordinate).rg);
    float3 rgb = convertMatrix->matrix * (yuv + convertMatrix->offset);
    return float4(rgb, 1.0);
}

注意點

1胯府、metal引用pod文件需要通過modlemap的方式引用
2、[[buffer(ZZHFragmentInputIndexMatrix)]] : buffer表明是緩存數(shù)據(jù)恨胚,ZZHVertexInputIndexVertices是索引骂因。對應encoder的接口

- (void)setFragmentBuffer:(nullable id <MTLBuffer>)buffer offset:(NSUInteger)offset atIndex:(NSUInteger)index;
- (void)setVertexBuffer:(nullable id <MTLBuffer>)buffer offset:(NSUInteger)offset atIndex:(NSUInteger)index;

3、[[vertex_id]] :vertex_id是頂點shader每次處理的index赃泡,用于定位當前的頂點
4寒波、[[stage_in]] : stage_in :標識該數(shù)據(jù)來自vertex的輸出
5、Metal中的內(nèi)存訪問主要有兩種方式:Device模式和Constant模式升熊,由代碼中顯式指定俄烁。
Device模式是比較通用的訪問模式,使用限制比較少僚碎,而Constant模式是為了多次讀取而設(shè)計的快速訪問只讀模式猴娩,通過Constant內(nèi)存模式訪問的參數(shù)的數(shù)據(jù)的字節(jié)數(shù)量是固定的,特點總結(jié)為:
Device支持讀寫,并且沒有size的限制卷中;
Constant是只讀矛双,并且限定大小蟆豫;
如何選擇Device和Constant模式议忽?
先看數(shù)據(jù)size是否會變化,再看訪問的頻率高低十减,只有那些固定size且經(jīng)常訪問的部分適合使用constant模式栈幸,其他的均用Device。
6帮辟、Sampler是采樣器速址,決定如何對一個紋理進行采樣操作。尋址模式由驹,過濾模式芍锚,歸一化坐標,比較函數(shù)蔓榄。
在Metal程序里初始化的采樣器必須使用constexpr修飾符聲明并炮。
采樣器指針和引用是不支持的,將會導致編譯錯誤甥郑。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末逃魄,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子澜搅,更是在濱河造成了極大的恐慌伍俘,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,744評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件店展,死亡現(xiàn)場離奇詭異养篓,居然都是意外死亡,警方通過查閱死者的電腦和手機赂蕴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評論 3 392
  • 文/潘曉璐 我一進店門柳弄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人概说,你說我怎么就攤上這事碧注。” “怎么了糖赔?”我有些...
    開封第一講書人閱讀 163,105評論 0 353
  • 文/不壞的土叔 我叫張陵萍丐,是天一觀的道長。 經(jīng)常有香客問我放典,道長逝变,這世上最難降的妖魔是什么基茵? 我笑而不...
    開封第一講書人閱讀 58,242評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮壳影,結(jié)果婚禮上拱层,老公的妹妹穿的比我還像新娘。我一直安慰自己宴咧,他們只是感情好根灯,可當我...
    茶點故事閱讀 67,269評論 6 389
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著掺栅,像睡著了一般烙肺。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上氧卧,一...
    開封第一講書人閱讀 51,215評論 1 299
  • 那天桃笙,我揣著相機與錄音,去河邊找鬼假抄。 笑死怎栽,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的宿饱。 我是一名探鬼主播,決...
    沈念sama閱讀 40,096評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼脚祟,長吁一口氣:“原來是場噩夢啊……” “哼谬以!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起由桌,我...
    開封第一講書人閱讀 38,939評論 0 274
  • 序言:老撾萬榮一對情侶失蹤为黎,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后行您,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體铭乾,經(jīng)...
    沈念sama閱讀 45,354評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,573評論 2 333
  • 正文 我和宋清朗相戀三年娃循,在試婚紗的時候發(fā)現(xiàn)自己被綠了炕檩。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,745評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡捌斧,死狀恐怖笛质,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情捞蚂,我是刑警寧澤妇押,帶...
    沈念sama閱讀 35,448評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站姓迅,受9級特大地震影響敲霍,放射性物質(zhì)發(fā)生泄漏俊马。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,048評論 3 327
  • 文/蒙蒙 一肩杈、第九天 我趴在偏房一處隱蔽的房頂上張望潭袱。 院中可真熱鬧,春花似錦锋恬、人聲如沸屯换。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽彤悔。三九已至,卻和暖如春索守,著一層夾襖步出監(jiān)牢的瞬間晕窑,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評論 1 269
  • 我被黑心中介騙來泰國打工卵佛, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留杨赤,地道東北人。 一個月前我還...
    沈念sama閱讀 47,776評論 2 369
  • 正文 我出身青樓截汪,卻偏偏與公主長得像疾牲,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子衙解,可洞房花燭夜當晚...
    茶點故事閱讀 44,652評論 2 354

推薦閱讀更多精彩內(nèi)容