what's new
* 动态启动图
* 动态聊天背景
* 支持8.0.1
* 更新越狱包8.0.1
* 更新已注入助手的8.0.1未签名包
* 更新越狱源安装包
This commit is contained in:
DKJone
2021-01-29 16:35:34 +08:00
parent 25a3f7f760
commit 888af8954e
125 changed files with 10144 additions and 40 deletions
@@ -0,0 +1,54 @@
// QGHWDMetalRenderer.h
// Tencent is pleased to support the open source community by making vap available.
//
// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
//
// Licensed under the MIT License (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at
//
// http://opensource.org/licenses/MIT
//
// Unless required by applicable law or agreed to in writing, software distributed under the License is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
// either express or implied. See the License for the specific language governing permissions and
// limitations under the License.
#import <UIKit/UIKit.h>
#import <Metal/Metal.h>
#import "VAPMacros.h"
UIKIT_EXTERN NSString *const kHWDVertexFunctionName;
UIKIT_EXTERN NSString *const kHWDYUVFragmentFunctionName;
extern matrix_float3x3 const kColorConversionMatrix601Default;
extern matrix_float3x3 const kColorConversionMatrix601FullRangeDefault;
extern matrix_float3x3 const kColorConversionMatrix709Default;
extern matrix_float3x3 const kColorConversionMatrix709FullRangeDefault;
extern matrix_float3x3 const kBlurWeightMatrixDefault;
extern id<MTLDevice> kQGHWDMetalRendererDevice;
#if TARGET_OS_SIMULATOR//模拟器
@interface QGHWDMetalRenderer : NSObject
@property (nonatomic, assign) QGHWDTextureBlendMode blendMode;
- (instancetype)initWithMetalLayer:(id)layer blendMode:(QGHWDTextureBlendMode)mode;
- (void)renderPixelBuffer:(CVPixelBufferRef)pixelBuffer metalLayer:(id)layer;
- (void)dispose;
@end
#else
@interface QGHWDMetalRenderer : NSObject
@property (nonatomic, assign) QGHWDTextureBlendMode blendMode;
- (instancetype)initWithMetalLayer:(CAMetalLayer *)layer blendMode:(QGHWDTextureBlendMode)mode;
- (void)renderPixelBuffer:(CVPixelBufferRef)pixelBuffer metalLayer:(CAMetalLayer *)layer;
- (void)dispose;
@end
#endif
@@ -0,0 +1,321 @@
// QGHWDMetalRenderer.m
// Tencent is pleased to support the open source community by making vap available.
//
// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
//
// Licensed under the MIT License (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at
//
// http://opensource.org/licenses/MIT
//
// Unless required by applicable law or agreed to in writing, software distributed under the License is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
// either express or implied. See the License for the specific language governing permissions and
// limitations under the License.
#import "QGHWDMetalRenderer.h"
#import "QGHWDShaderTypes.h"
#import "QGVAPLogger.h"
#import <simd/simd.h>
#import <MetalKit/MetalKit.h>
#import "UIDevice+VAPUtil.h"
#import "QGVAPMetalUtil.h"
#import "QGVAPMetalShaderFunctionLoader.h"
#pragma mark - constants
NSString *const kHWDVertexFunctionName = @"hwd_vertexShader";
NSString *const kHWDYUVFragmentFunctionName = @"hwd_yuvFragmentShader";
static NSInteger const kQuadVerticesConstantsRow = 4;
static NSInteger const kQuadVerticesConstantsColumn = 32;
static NSInteger const kHWDVertexCount = 4;
id<MTLDevice> kQGHWDMetalRendererDevice;
// BT.601, which is the standard for SDTV.
matrix_float3x3 const kColorConversionMatrix601Default = {{
{1.164, 1.164, 1.164},
{0.0, -0.392, 2.017},
{1.596, -0.813, 0.0}
}};
/*矩阵形式!!!
1.0 0.0 1.4
[1.0 -0.343 -0.711 ]
1.0 1.765 0.0
*/
//ITU BT.601 Full Range
matrix_float3x3 const kColorConversionMatrix601FullRangeDefault = {{
{1.0, 1.0, 1.0},
{0.0, -0.34413, 1.772},
{1.402, -0.71414, 0.0}
}};
// BT.709, which is the standard for HDTV.
matrix_float3x3 const kColorConversionMatrix709Default = {{
{1.164, 1.164, 1.164},
{0.0, -0.213, 2.112},
{1.793, -0.533, 0.0}
}};
// BT.709 Full Range.
matrix_float3x3 const kColorConversionMatrix709FullRangeDefault = {{
{1.0, 1.0, 1.0},
{0.0, -.18732, 1.8556},
{1.57481, -.46813, 0.0}
}};
// Blur weight matrix.
matrix_float3x3 const kBlurWeightMatrixDefault = {{
{0.0625, 0.125, 0.0625},
{0.125, 0.25, 0.125},
{0.0625, 0.125, 0.0625}
}};
//QGHWDVertex 顶点坐标+纹理坐标(rgb+alpha
static const float kQuadVerticesConstants[kQuadVerticesConstantsRow][kQuadVerticesConstantsColumn] = {
//左侧alpha
{-1.0, -1.0, 0.0, 1.0, 0.5, 1.0, 0.0, 1.0,
-1.0, 1.0, 0.0, 1.0, 0.5, 0.0, 0.0, 0.0,
1.0, -1.0, 0.0, 1.0, 1.0, 1.0, 0.5, 1.0,
1.0, 1.0, 0.0, 1.0, 1.0, 0.0, 0.5, 0.0},
//右侧alpha
{-1.0, -1.0, 0.0, 1.0, 0.0, 1.0, 0.5, 1.0,
-1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.5, 0.0,
1.0, -1.0, 0.0, 1.0, 0.5, 1.0, 1.0, 1.0,
1.0, 1.0, 0.0, 1.0, 0.5, 0.0, 1.0, 0.0},
//顶部alpha
{-1.0, -1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.5,
-1.0, 1.0, 0.0, 1.0, 0.0, 0.5, 0.0, 0.0,
1.0, -1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.5,
1.0, 1.0, 0.0, 1.0, 1.0, 0.5, 1.0, 0.0},
//底部alpha
{-1.0, -1.0, 0.0, 1.0, 0.0, 0.5, 0.0, 1.0,
-1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.5,
1.0, -1.0, 0.0, 1.0, 1.0, 0.5, 1.0, 1.0,
1.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.5}
};
#if TARGET_OS_SIMULATOR//模拟器
#else
@interface QGHWDMetalRenderer () {
BOOL _renderingResourcesDisposed; //用以标记渲染资源是否被回收
matrix_float3x3 _currentColorConversionMatrix;
}
@property (nonatomic, strong) id<MTLBuffer> vertexBuffer;
@property (nonatomic, strong) id<MTLBuffer> yuvMatrixBuffer;
@property (nonatomic, strong) id<MTLRenderPipelineState> pipelineState;//This will keep track of the compiled render pipeline youre about to create.
@property (nonatomic, strong) id<MTLCommandQueue> commandQueue;
@property (nonatomic, assign) int vertexCount;
@property (nonatomic, assign) CVMetalTextureCacheRef videoTextureCache;//need release
@property (nonatomic, strong) QGVAPMetalShaderFunctionLoader *shaderFuncLoader;
@end
@implementation QGHWDMetalRenderer
#pragma mark - main
- (instancetype)initWithMetalLayer:(CAMetalLayer *)layer blendMode:(QGHWDTextureBlendMode)mode {
self = [super init];
if (self) {
_blendMode = mode;
if (!kQGHWDMetalRendererDevice) {
kQGHWDMetalRendererDevice = MTLCreateSystemDefaultDevice();
}
layer.device = kQGHWDMetalRendererDevice;
[self setupConstants];
[self setupPipelineStatesWithMetalLayer:layer];
}
return self;
}
/**
回收渲染数据,减少内存占用
*/
- (void)dispose {
_commandQueue = nil;
_pipelineState = nil;
_vertexBuffer = nil;
_yuvMatrixBuffer = nil;
_shaderFuncLoader = nil;
if (_videoTextureCache) {
CVMetalTextureCacheFlush(_videoTextureCache, 0);
CFRelease(_videoTextureCache);
_videoTextureCache = NULL;
}
_renderingResourcesDisposed = YES;
}
- (void)dealloc {
[self dispose];
}
- (void)setupConstants {
//buffers
const void *vertices = [self suitableQuadVertices];
NSUInteger allocationSize = kQuadVerticesConstantsColumn * sizeof(float);
_vertexBuffer = [kQGHWDMetalRendererDevice newBufferWithBytes:vertices length:allocationSize options:kDefaultMTLResourceOption];
_vertexCount = kHWDVertexCount;
_currentColorConversionMatrix = kColorConversionMatrix601FullRangeDefault;
struct ColorParameters yuvMatrixs[] = {{_currentColorConversionMatrix,{0.5, 0.5}}};
NSUInteger yuvMatrixsDataSize = sizeof(struct ColorParameters);
_yuvMatrixBuffer = [kQGHWDMetalRendererDevice newBufferWithBytes:yuvMatrixs length:yuvMatrixsDataSize options:kDefaultMTLResourceOption];
}
- (void)updateMetalPropertiesIfNeed:(CVPixelBufferRef)pixelBuffer {
if (!pixelBuffer) {
return ;
}
CFTypeRef yCbCrMatrixType = CVBufferGetAttachment(pixelBuffer, kCVImageBufferYCbCrMatrixKey, NULL);
matrix_float3x3 matrix = kColorConversionMatrix601FullRangeDefault;
if (CFStringCompare(yCbCrMatrixType, kCVImageBufferYCbCrMatrix_ITU_R_709_2, 0) == kCFCompareEqualTo) {
matrix = kColorConversionMatrix709FullRangeDefault;
}
if (simd_equal(_currentColorConversionMatrix, matrix)) {
return ;
}
_currentColorConversionMatrix = matrix;
struct ColorParameters yuvMatrixs[] = {{_currentColorConversionMatrix,{0.5, 0.5}}};
NSUInteger yuvMatrixsDataSize = sizeof(struct ColorParameters);
_yuvMatrixBuffer = [kQGHWDMetalRendererDevice newBufferWithBytes:yuvMatrixs length:yuvMatrixsDataSize options:kDefaultMTLResourceOption];
}
- (void)setupPipelineStatesWithMetalLayer:(CAMetalLayer *)metalLayer {
self.shaderFuncLoader = [[QGVAPMetalShaderFunctionLoader alloc] initWithDevice:kQGHWDMetalRendererDevice];
id<MTLFunction> vertexProgram = [self.shaderFuncLoader loadFunctionWithName:kHWDVertexFunctionName];
id<MTLFunction> fragmentProgram = [self.shaderFuncLoader loadFunctionWithName:kHWDYUVFragmentFunctionName];
if (!vertexProgram || !fragmentProgram) {
VAP_Error(kQGVAPModuleCommon, @"setupPipelineStatesWithMetalLayer fail! cuz: shader load fail");
NSAssert(0, @"check if .metal files been compiled to correct target!");
return ;
}
MTLRenderPipelineDescriptor *pipelineStateDescriptor = [MTLRenderPipelineDescriptor new];
pipelineStateDescriptor.vertexFunction = vertexProgram;
pipelineStateDescriptor.fragmentFunction = fragmentProgram;
pipelineStateDescriptor.colorAttachments[0].pixelFormat = metalLayer.pixelFormat;
NSError *psError = nil;
id<MTLRenderPipelineState> pipelineState = [kQGHWDMetalRendererDevice newRenderPipelineStateWithDescriptor:pipelineStateDescriptor error:&psError];
if (!pipelineState || psError) {
VAP_Error(kQGVAPModuleCommon, @"newRenderPipelineStateWithDescriptor error!:%@", psError);
return ;
}
self.pipelineState = pipelineState;
self.commandQueue = [kQGHWDMetalRendererDevice newCommandQueue];
CVReturn textureCacheError = CVMetalTextureCacheCreate(kCFAllocatorDefault, nil, kQGHWDMetalRendererDevice, nil, &_videoTextureCache);
if (textureCacheError != kCVReturnSuccess) {
VAP_Error(kQGVAPModuleCommon, @"create texture cache fail!:%@", textureCacheError);
return ;
}
}
/**
使用metal渲染管线渲染CVPixelBufferRef,若有需要融合的图层则通过对应的管线一并渲染
@param pixelBuffer 图像数据
@param layer metalLayer
*/
- (void)renderPixelBuffer:(CVPixelBufferRef)pixelBuffer metalLayer:(CAMetalLayer *)layer {
if (!layer.superlayer || layer.bounds.size.width <= 0 || layer.bounds.size.height <= 0) {
//https://forums.developer.apple.com/thread/26278
VAP_Error(kQGVAPModuleCommon, @"quit rendering cuz layer.superlayer or size error is nil! superlayer:%@ height:%@ width:%@", layer.superlayer, @(layer.bounds.size.height), @(layer.bounds.size.width));
return ;
}
[self reconstructIfNeed:layer];
if (pixelBuffer == NULL || !self.commandQueue || !self.pipelineState) {
VAP_Error(kQGVAPModuleCommon, @"quit rendering cuz pixelbuffer is nil!");
return ;
}
[self updateMetalPropertiesIfNeed:pixelBuffer];
CVMetalTextureCacheFlush(_videoTextureCache, 0);
CVMetalTextureRef yTextureRef = nil, uvTextureRef = nil;
size_t yWidth = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0);
size_t yHeight = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0);
size_t uvWidth = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1);
size_t uvHeight = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1);
//注意格式!r8Unorm
CVReturn yStatus = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, _videoTextureCache, pixelBuffer, nil, MTLPixelFormatR8Unorm, yWidth, yHeight, 0, &yTextureRef);
//注意格式!rg8Unorm
CVReturn uvStatus = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, _videoTextureCache, pixelBuffer, nil, MTLPixelFormatRG8Unorm, uvWidth, uvHeight, 1, &uvTextureRef);
if (yStatus != kCVReturnSuccess || uvStatus != kCVReturnSuccess) {
VAP_Error(kQGVAPModuleCommon, @"quit rendering cuz failing getting yuv texture-yStatus%@:uvStatus%@", @(yStatus), @(uvStatus));
return ;
}
id<MTLTexture> yTexture = CVMetalTextureGetTexture(yTextureRef);
id<MTLTexture> uvTexture = CVMetalTextureGetTexture(uvTextureRef);
CVBufferRelease(yTextureRef);
CVBufferRelease(uvTextureRef);
CVMetalTextureCacheFlush(_videoTextureCache, 0);
yTextureRef = NULL;
uvTextureRef = NULL;
if (!yTexture || !uvTexture || !layer) {
VAP_Error(kQGVAPModuleCommon, @"quit rendering cuz content is nil! y:%@ uv:%@, layer:%@", @(yTexture != nil), @(uvTexture != nil), @(layer != nil));
return ;
}
if (layer.drawableSize.width <= 0 || layer.drawableSize.height <= 0) {
VAP_Error(kQGVAPModuleCommon, @"quit rendering cuz drawableSize is 0");
return ;
}
id<CAMetalDrawable> drawable = layer.nextDrawable;
if (!drawable) {
VAP_Error(kQGVAPModuleCommon, @"quit rendering cuz nextDrawable is nil!");
return ;
}
MTLRenderPassDescriptor *renderPassDescriptor = [MTLRenderPassDescriptor new];
renderPassDescriptor.colorAttachments[0].texture = drawable.texture; //which returns the texture in which you need to draw in order for something to appear on the screen.
renderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear; //“set the texture to the clear color before doing any drawing,”
renderPassDescriptor.colorAttachments[0].clearColor =MTLClearColorMake(1.0, 1.0, 1.0, 1.0);
id<MTLCommandBuffer> commandBuffer = [self.commandQueue commandBuffer];
id<MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
[renderEncoder setRenderPipelineState:self.pipelineState];
[renderEncoder setVertexBuffer:self.vertexBuffer offset:0 atIndex:0];
[renderEncoder setFragmentBuffer:self.yuvMatrixBuffer offset:0 atIndex:0];
[renderEncoder setFragmentTexture:yTexture atIndex:QGHWDYUVFragmentTextureIndexLuma];
[renderEncoder setFragmentTexture:uvTexture atIndex:QGHWDYUVFragmentTextureIndexChroma];
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:self.vertexCount instanceCount:1];
[renderEncoder endEncoding];
[commandBuffer presentDrawable:drawable];
[commandBuffer commit];
}
#pragma mark - private
/**
在必要的时候重建渲染数据,以便渲染
@param layer metalLayer
*/
- (void)reconstructIfNeed:(CAMetalLayer *)layer {
if (_renderingResourcesDisposed) {
[self setupConstants];
[self setupPipelineStatesWithMetalLayer:layer];
_renderingResourcesDisposed = NO;
}
}
- (const void *)suitableQuadVertices {
switch (self.blendMode) {
case QGHWDTextureBlendMode_AlphaLeft:
return kQuadVerticesConstants[0];
case QGHWDTextureBlendMode_AlphaRight:
return kQuadVerticesConstants[1];
case QGHWDTextureBlendMode_AlphaTop:
return kQuadVerticesConstants[2];
case QGHWDTextureBlendMode_AlphaBottom:
return kQuadVerticesConstants[3];
default:
break;
}
return kQuadVerticesConstants[0];
}
@end
#endif
@@ -0,0 +1,47 @@
// QGHWDMetalView.h
// Tencent is pleased to support the open source community by making vap available.
//
// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
//
// Licensed under the MIT License (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at
//
// http://opensource.org/licenses/MIT
//
// Unless required by applicable law or agreed to in writing, software distributed under the License is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
// either express or implied. See the License for the specific language governing permissions and
// limitations under the License.
#import <UIKit/UIKit.h>
#import "VAPMacros.h"
//https://developer.apple.com/library/archive/documentation/Miscellaneous/Conceptual/MetalProgrammingGuide/Device/Device.html#//apple_ref/doc/uid/TP40014221-CH2-SW1
/*
2018-12-31 00:01:51.349229+0800 MetalTest[28134:2050088] [DYMTLInitPlatform] platform initialization successful
2018-12-31 00:01:51.413574+0800 MetalTest[28134:2050043] Metal GPU Frame Capture Enabled
2018-12-31 00:01:51.414037+0800 MetalTest[28134:2050043] Metal API Validation Enabled
2018-12-31 00:01:54.008682+0800 MetalTest[28134:2050086] Execution of the command buffer was aborted due to an error during execution. Insufficient Permission (to submit GPU work from background) (IOAF code 6)
2018-12-31 00:01:54.009053+0800 MetalTest[28134:2050086] Execution of the command buffer was aborted due to an error during execution. Insufficient Permission (to submit GPU work from background) (IOAF code 6)
2018-12-31 00:01:54.011370+0800 MetalTest[28134:2050086] Execution of the command buffer was aborted due to an error during execution. Insufficient Permission (to submit GPU work from background) (IOAF code 6)
2018-12-31 00:01:54.011710+0800 MetalTest[28134:2050086] Execution of the command buffer was aborted due to an error during execution. Insufficient Permission (to submit GPU work from background) (IOAF code 6)
*/
@protocol QGHWDMetelViewDelegate <NSObject>
- (void)onMetalViewUnavailable;
@end
@interface QGHWDMetalView : UIView
@property (nonatomic, weak) id<QGHWDMetelViewDelegate> delegate;
@property (nonatomic, assign) QGHWDTextureBlendMode blendMode;
- (instancetype)initWithFrame:(CGRect)frame blendMode:(QGHWDTextureBlendMode)mode;
- (void)display:(CVPixelBufferRef)pixelBuffer;
- (void)dispose;
@end
@@ -0,0 +1,134 @@
// QGHWDMetalView.m
// Tencent is pleased to support the open source community by making vap available.
//
// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
//
// Licensed under the MIT License (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at
//
// http://opensource.org/licenses/MIT
//
// Unless required by applicable law or agreed to in writing, software distributed under the License is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
// either express or implied. See the License for the specific language governing permissions and
// limitations under the License.
#import "QGHWDMetalView.h"
#import "QGVAPLogger.h"
#import "QGHWDMetalRenderer.h"
#if TARGET_OS_SIMULATOR//模拟器
@implementation QGHWDMetalView
- (instancetype)initWithFrame:(CGRect)frame blendMode:(QGHWDTextureBlendMode)mode {
return [self initWithFrame:frame];
}
- (void)display:(CVPixelBufferRef)pixelBuffer {}
-(void)dispose {}
@end
#else
@interface QGHWDMetalView ()
@property (nonatomic, strong) CAMetalLayer *metalLayer;
@property (nonatomic, strong) QGHWDMetalRenderer *renderer;
@property (nonatomic, assign) BOOL drawableSizeShouldUpdate;
@end
@implementation QGHWDMetalView
#pragma mark - override
+ (Class)layerClass {
return [CAMetalLayer class];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
NSAssert(0, @"initWithCoder: has not been implemented");
return nil;
}
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
_drawableSizeShouldUpdate = YES;
_blendMode = QGHWDTextureBlendMode_AlphaLeft;
}
return self;
}
- (void)didMoveToWindow {
[super didMoveToWindow];
self.drawableSizeShouldUpdate = YES;
}
- (void)layoutSubviews {
[super layoutSubviews];
self.drawableSizeShouldUpdate = YES;
}
- (void)dealloc {
[self onMetalViewUnavailable];
}
#pragma mark - main
- (instancetype)initWithFrame:(CGRect)frame blendMode:(QGHWDTextureBlendMode)mode {
if (self = [super initWithFrame:frame]) {
_drawableSizeShouldUpdate = YES;
_blendMode = QGHWDTextureBlendMode_AlphaLeft;
_metalLayer = (CAMetalLayer *)self.layer;
_metalLayer.frame = self.frame;
_metalLayer.opaque = NO;
_blendMode = mode;
_renderer = [[QGHWDMetalRenderer alloc] initWithMetalLayer:_metalLayer blendMode:mode];
_metalLayer.contentsScale = [UIScreen mainScreen].scale;
_metalLayer.pixelFormat = MTLPixelFormatBGRA8Unorm;
_metalLayer.framebufferOnly = YES;
}
return self;
}
- (void)display:(CVPixelBufferRef)pixelBuffer {
if (!self.window) {
VAP_Event(kQGVAPModuleCommon, @"quit display pixelbuffer, cuz window is nil!");
[self onMetalViewUnavailable];
return ;
}
if (self.drawableSizeShouldUpdate) {
CGFloat nativeScale = [UIScreen mainScreen].nativeScale;
CGSize drawableSize = CGSizeMake(CGRectGetWidth(self.bounds)*nativeScale, CGRectGetHeight(self.bounds)*nativeScale);
self.metalLayer.drawableSize = drawableSize;
VAP_Event(kQGVAPModuleCommon, @"update drawablesize :%@", [NSValue valueWithCGSize:drawableSize]);
self.drawableSizeShouldUpdate = NO;
}
self.renderer.blendMode = self.blendMode;
[self.renderer renderPixelBuffer:pixelBuffer metalLayer:self.metalLayer];
}
/**
资源回收
*/
- (void)dispose {
[self.renderer dispose];
}
#pragma mark - private
- (void)onMetalViewUnavailable{
if ([self.delegate respondsToSelector:@selector(onMetalViewUnavailable)]) {
[self.delegate onMetalViewUnavailable];
}
}
@end
#endif
@@ -0,0 +1,48 @@
// QGVAPMetalRenderer.h
// Tencent is pleased to support the open source community by making vap available.
//
// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
//
// Licensed under the MIT License (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at
//
// http://opensource.org/licenses/MIT
//
// Unless required by applicable law or agreed to in writing, software distributed under the License is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
// either express or implied. See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
#import "QGVAPConfigModel.h"
#import <Metal/Metal.h>
#import "VAPMacros.h"
#if TARGET_OS_SIMULATOR//模拟器
@interface QGVAPMetalRenderer : NSObject
@property (nonatomic, strong) QGVAPCommonInfo *commonInfo;
- (instancetype)initWithMetalLayer:(id)layer;
- (void)renderPixelBuffer:(CVPixelBufferRef)pixelBuffer metalLayer:(id)layer mergeInfos:(NSArray<QGVAPMergedInfo *> *)infos;
- (void)dispose;
@end
#else
@interface QGVAPMetalRenderer : NSObject
@property (nonatomic, strong) QGVAPCommonInfo *commonInfo;
@property (nonatomic, strong) QGVAPMaskInfo *maskInfo;
- (instancetype)initWithMetalLayer:(CAMetalLayer *)layer;
- (void)renderPixelBuffer:(CVPixelBufferRef)pixelBuffer metalLayer:(CAMetalLayer *)layer mergeInfos:(NSArray<QGVAPMergedInfo *> *)infos;
- (void)dispose;
@end
#endif
@@ -0,0 +1,425 @@
// QGVAPMetalRenderer.m
// Tencent is pleased to support the open source community by making vap available.
//
// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
//
// Licensed under the MIT License (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at
//
// http://opensource.org/licenses/MIT
//
// Unless required by applicable law or agreed to in writing, software distributed under the License is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
// either express or implied. See the License for the specific language governing permissions and
// limitations under the License.
#import "QGVAPMetalRenderer.h"
#import "QGHWDMetalRenderer.h"
#import <MetalKit/MetalKit.h>
#import "QGVAPLogger.h"
#import <simd/simd.h>
#import "UIDevice+VAPUtil.h"
#import "QGVAPMetalUtil.h"
#import "QGVAPMetalShaderFunctionLoader.h"
#if TARGET_OS_SIMULATOR//模拟器
#else
@interface QGVAPMetalRenderer () {
BOOL _renderingResourcesDisposed; //用以标记渲染资源是否被回收
matrix_float3x3 _currentColorConversionMatrix;
}
@property (nonatomic, strong) id<MTLBuffer> vertexBuffer;
@property (nonatomic, strong) id<MTLBuffer> yuvMatrixBuffer;
@property (nonatomic, strong) id<MTLBuffer> maskBlurBuffer;
@property (nonatomic, strong) id<MTLRenderPipelineState> attachmentPipelineState;
@property (nonatomic, strong) id<MTLRenderPipelineState> defaultMainPipelineState;
@property (nonatomic, strong) id<MTLRenderPipelineState> mainPipelineStateForMask; //带遮罩处理的主流程管线
@property (nonatomic, strong) id<MTLRenderPipelineState> mainPipelineStateForMaskBlur; //带遮罩模糊处理的主流程管线
@property (nonatomic, strong) id<MTLCommandQueue> commandQueue;
@property (nonatomic, assign) CVMetalTextureCacheRef videoTextureCache;//need release
@property (nonatomic, strong) QGVAPMetalShaderFunctionLoader *shaderFuncLoader;
@property (nonatomic, weak) CAMetalLayer *metalLayer;
@end
@implementation QGVAPMetalRenderer
- (instancetype)initWithMetalLayer:(CAMetalLayer *)layer {
if (self = [super init]) {
if (!kQGHWDMetalRendererDevice) {
kQGHWDMetalRendererDevice = MTLCreateSystemDefaultDevice();
}
layer.device = kQGHWDMetalRendererDevice;
_metalLayer = layer;
[self setupRenderContext];
}
return self;
}
#pragma mark - main
- (void)renderPixelBuffer:(CVPixelBufferRef)pixelBuffer metalLayer:(CAMetalLayer *)layer mergeInfos:(NSArray<QGVAPMergedInfo *> *)infos {
if (!layer.superlayer || layer.bounds.size.width <= 0 || layer.bounds.size.height <= 0) {
//https://forums.developer.apple.com/thread/26278
VAP_Error(kQGVAPModuleCommon, @"quit rendering cuz layer.superlayer or size error is nil! superlayer:%@ height:%@ width:%@", layer.superlayer, @(layer.bounds.size.height), @(layer.bounds.size.width));
return ;
}
[self reconstructIfNeed:layer];
if (pixelBuffer == NULL || !self.commandQueue) {
VAP_Error(kQGVAPModuleCommon, @"quit rendering cuz pixelbuffer is nil!");
return ;
}
[self updateMetalPropertiesIfNeed:pixelBuffer];
CVMetalTextureCacheFlush(_videoTextureCache, 0);
CVMetalTextureRef yTextureRef = nil, uvTextureRef = nil;
size_t yWidth = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0);
size_t yHeight = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0);
size_t uvWidth = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1);
size_t uvHeight = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1);
//注意格式!r8Unorm
CVReturn yStatus = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, _videoTextureCache, pixelBuffer, nil, MTLPixelFormatR8Unorm, yWidth, yHeight, 0, &yTextureRef);
//注意格式!rg8Unorm
CVReturn uvStatus = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, _videoTextureCache, pixelBuffer, nil, MTLPixelFormatRG8Unorm, uvWidth, uvHeight, 1, &uvTextureRef);
if (yStatus != kCVReturnSuccess || uvStatus != kCVReturnSuccess) {
VAP_Error(kQGVAPModuleCommon, @"quit rendering cuz failing getting yuv texture-yStatus%@:uvStatus%@", @(yStatus), @(uvStatus));
return ;
}
id<MTLTexture> yTexture = CVMetalTextureGetTexture(yTextureRef);
id<MTLTexture> uvTexture = CVMetalTextureGetTexture(uvTextureRef);
CVBufferRelease(yTextureRef);
CVBufferRelease(uvTextureRef);
CVMetalTextureCacheFlush(_videoTextureCache, 0);
yTextureRef = NULL;
uvTextureRef = NULL;
if (!yTexture || !uvTexture || !layer) {
VAP_Error(kQGVAPModuleCommon, @"quit rendering cuz content is nil! y:%@ uv:%@, layer:%@", @(yTexture != nil), @(uvTexture != nil), @(layer != nil));
return ;
}
if (layer.drawableSize.width <= 0 || layer.drawableSize.height <= 0) {
VAP_Error(kQGVAPModuleCommon, @"quit rendering cuz drawableSize is 0");
return ;
}
id<CAMetalDrawable> drawable = layer.nextDrawable;
if (!drawable) {
VAP_Error(kQGVAPModuleCommon, @"quit rendering cuz nextDrawable is nil!");
return ;
}
MTLRenderPassDescriptor *renderPassDescriptor = [MTLRenderPassDescriptor new];
renderPassDescriptor.colorAttachments[0].texture = drawable.texture; //which returns the texture in which you need to draw in order for something to appear on the screen.
renderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear; //“set the texture to the clear color before doing any drawing,”
renderPassDescriptor.colorAttachments[0].clearColor =MTLClearColorMake(0.0, 0.0, 0.0, 0.0);
id<MTLCommandBuffer> commandBuffer = [self.commandQueue commandBuffer];
id<MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
if (renderEncoder == nil) {
VAP_Error(kQGVAPModuleCommon, @"quit rendering cuz renderEncoder:%p or self.pipelineState:%p is nil!", renderEncoder);
return ;
}
if (self.vertexBuffer == nil || self.yuvMatrixBuffer == nil) {
VAP_Error(kQGVAPModuleCommon, @"quit rendering cuz vertexBuffer:%p or yuvMatrixBuffer:%p is nil!", self.vertexBuffer, self.yuvMatrixBuffer);
return ;
}
[self drawBackground:yTexture uvTexture:uvTexture encoder:renderEncoder];
[self drawMergedAttachments:infos yTexture:yTexture uvTexture:uvTexture renderEncoder:renderEncoder metalLayer:layer];
[renderEncoder endEncoding];
[commandBuffer presentDrawable:drawable];
[commandBuffer commit];
}
- (void)drawBackground:(id<MTLTexture>)yTexture uvTexture:(id<MTLTexture>)uvTexture encoder:(id<MTLRenderCommandEncoder>)renderEncoder {
if (self.maskInfo) {
id<MTLTexture> maskTexture = self.maskInfo.texture;
if (!maskTexture) {
VAP_Error(kQGVAPModuleCommon, @"maskTexture error! maskTexture is nil");
return;
}
if (!self.mainPipelineStateForMask) {
VAP_Error(kQGVAPModuleCommon, @"maskPipelineState error! maskTexture is nil");
return;
}
if (self.maskInfo.blurLength > 0) {
[renderEncoder setRenderPipelineState:self.mainPipelineStateForMaskBlur];
[renderEncoder setVertexBuffer:self.vertexBuffer offset:0 atIndex:0];
[renderEncoder setFragmentBuffer:self.yuvMatrixBuffer offset:0 atIndex:0];
[renderEncoder setFragmentBuffer:self.maskBlurBuffer offset:0 atIndex:1];
[renderEncoder setFragmentTexture:yTexture atIndex:QGHWDYUVFragmentTextureIndexLuma];
[renderEncoder setFragmentTexture:uvTexture atIndex:QGHWDYUVFragmentTextureIndexChroma];
[renderEncoder setFragmentTexture:maskTexture atIndex:QGHWDYUVFragmentTextureIndexAttachmentStart];
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4 instanceCount:1];
} else {
[renderEncoder setRenderPipelineState:self.mainPipelineStateForMask];
[renderEncoder setVertexBuffer:self.vertexBuffer offset:0 atIndex:0];
[renderEncoder setFragmentBuffer:self.yuvMatrixBuffer offset:0 atIndex:0];
[renderEncoder setFragmentTexture:yTexture atIndex:QGHWDYUVFragmentTextureIndexLuma];
[renderEncoder setFragmentTexture:uvTexture atIndex:QGHWDYUVFragmentTextureIndexChroma];
[renderEncoder setFragmentTexture:maskTexture atIndex:QGHWDYUVFragmentTextureIndexAttachmentStart];
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4 instanceCount:1];
}
} else {
if (!self.defaultMainPipelineState) {
VAP_Error(kQGVAPModuleCommon, @"yuvPipelineState error! maskTexture is nil");
return;
}
[renderEncoder setRenderPipelineState:self.defaultMainPipelineState];
[renderEncoder setVertexBuffer:self.vertexBuffer offset:0 atIndex:0];
[renderEncoder setFragmentBuffer:self.yuvMatrixBuffer offset:0 atIndex:0];
[renderEncoder setFragmentTexture:yTexture atIndex:QGHWDYUVFragmentTextureIndexLuma];
[renderEncoder setFragmentTexture:uvTexture atIndex:QGHWDYUVFragmentTextureIndexChroma];
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4 instanceCount:1];
}
}
- (void)dispose {
_commandQueue = nil;
_vertexBuffer = nil;
_yuvMatrixBuffer = nil;
_attachmentPipelineState = nil;
_shaderFuncLoader = nil;
if (_videoTextureCache) {
CVMetalTextureCacheFlush(_videoTextureCache, 0);
CFRelease(_videoTextureCache);
_videoTextureCache = NULL;
}
_renderingResourcesDisposed = YES;
_mainPipelineStateForMask = nil;
_defaultMainPipelineState = nil;
}
-(void)dealloc {
[self dispose];
}
- (void)setupRenderContext {
//constants
_currentColorConversionMatrix = kColorConversionMatrix601FullRangeDefault;
struct ColorParameters yuvMatrixs[] = {{_currentColorConversionMatrix,{0.5, 0.5}}};
NSUInteger yuvMatrixsDataSize = sizeof(struct ColorParameters);
_yuvMatrixBuffer = [kQGHWDMetalRendererDevice newBufferWithBytes:yuvMatrixs length:yuvMatrixsDataSize options:kDefaultMTLResourceOption];
//function loader
self.shaderFuncLoader = [[QGVAPMetalShaderFunctionLoader alloc] initWithDevice:kQGHWDMetalRendererDevice];
//command queue
self.commandQueue = [kQGHWDMetalRendererDevice newCommandQueue];
//texture cache
CVReturn textureCacheError = CVMetalTextureCacheCreate(kCFAllocatorDefault, nil, kQGHWDMetalRendererDevice, nil, &_videoTextureCache);
if (textureCacheError != kCVReturnSuccess) {
VAP_Error(kQGVAPModuleCommon, @"create texture cache fail!:%@", textureCacheError);
}
}
- (void)drawMergedAttachments:(NSArray<QGVAPMergedInfo *> *)infos
yTexture:(id<MTLTexture>)yTexture
uvTexture:(id<MTLTexture>)uvTexture
renderEncoder:(id<MTLRenderCommandEncoder>)encoder
metalLayer:(CAMetalLayer *)layer {
if (infos.count == 0) {
return ;
}
if (!encoder || !self.commonInfo || !self.attachmentPipelineState) {
VAP_Error(kQGVAPModuleCommon, @"renderMergedAttachments error! infos:%@ encoder:%p commonInfo:%@ attachmentPipelineState:%p", @(infos.count), encoder, @(self.commonInfo != nil), self.attachmentPipelineState);
return ;
}
if (yTexture == nil || uvTexture == nil) {
VAP_Error(kQGVAPModuleCommon, @"renderMergedAttachments error! cuz yTexture:%p or uvTexture:%p is nil!", yTexture, uvTexture);
return ;
}
[infos enumerateObjectsUsingBlock:^(QGVAPMergedInfo * _Nonnull mergeInfo, NSUInteger idx, BOOL * _Nonnull stop) {
[encoder setRenderPipelineState:self.attachmentPipelineState];
id<MTLTexture> sourceTexture = mergeInfo.source.texture;//图片纹理
id<MTLBuffer> vertexBuffer = [mergeInfo vertexBufferWithContainerSize:self.commonInfo.size maskContianerSize:self.commonInfo.videoSize device:kQGHWDMetalRendererDevice];
id<MTLBuffer> colorParamsBuffer = mergeInfo.source.colorParamsBuffer;
id<MTLBuffer> yuvMatrixBuffer = self.yuvMatrixBuffer;
if (!sourceTexture || !vertexBuffer || !colorParamsBuffer || !yuvMatrixBuffer) {
//VAP_Error(kQGVAPModuleCommon, @"quit attachment:%p cuz-source:%p vertex:%p",mergeInfo, sourceTexture, vertexBuffer);
return ;
}
[encoder setVertexBuffer:vertexBuffer offset:0 atIndex:0];
[encoder setFragmentBuffer:yuvMatrixBuffer offset:0 atIndex:0];
[encoder setFragmentBuffer:colorParamsBuffer offset:0 atIndex:1];
//遮罩信息在视频流中
[encoder setFragmentTexture:yTexture atIndex:QGHWDYUVFragmentTextureIndexLuma];
[encoder setFragmentTexture:uvTexture atIndex:QGHWDYUVFragmentTextureIndexChroma];
[encoder setFragmentTexture:sourceTexture atIndex:QGHWDYUVFragmentTextureIndexAttachmentStart];
[encoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4 instanceCount:1];
}];
}
#pragma mark - setter&getter
- (id<MTLBuffer>)maskBlurBuffer {
if (!_maskBlurBuffer) {
struct MaskParameters parameters[] = {{kBlurWeightMatrixDefault, 3, 0.01}};
NSUInteger parametersSize = sizeof(struct MaskParameters);
_maskBlurBuffer = [kQGHWDMetalRendererDevice newBufferWithBytes:parameters length:parametersSize options:kDefaultMTLResourceOption];
}
return _maskBlurBuffer;
}
- (void)setCommonInfo:(QGVAPCommonInfo *)commonInfo {
_commonInfo = commonInfo;
[self updateMainVertexBuffer];
}
- (void)setMaskInfo:(QGVAPMaskInfo *)maskInfo {
if (maskInfo && (!maskInfo.data || maskInfo.dataSize.width <= 0 || maskInfo.dataSize.height <= 0)) {
VAP_Error(kQGVAPModuleCommon, @"setMaskInfo fail: data:%@, size:%@", maskInfo.data, NSStringFromCGSize(maskInfo.dataSize));
return;
}
if (_maskInfo == maskInfo) {
return ;
}
_maskInfo = maskInfo;
if (_vertexBuffer) {
[self updateMainVertexBuffer];
}
}
#pragma mark - pipelines
- (id<MTLRenderPipelineState>)createPipelineState:(NSString *)vertexFunction fragmentFunction:(NSString *)fragmentFunction {
id<MTLFunction> vertexProgram = [self.shaderFuncLoader loadFunctionWithName:vertexFunction];
id<MTLFunction> fragmentProgram = [self.shaderFuncLoader loadFunctionWithName:fragmentFunction];
if (!vertexProgram || !fragmentProgram) {
VAP_Error(kQGVAPModuleCommon, @"setupPipelineStatesWithMetalLayer fail! cuz: shader load fail!");
NSAssert(0, @"check if .metal files been compiled to correct target!");
return nil;
}
//融混方程
//https://objccn.io/issue-3-1/
//https://www.andersriggelsen.dk/glblendfunc.php
MTLRenderPipelineDescriptor *pipelineStateDescriptor = [MTLRenderPipelineDescriptor new];
pipelineStateDescriptor.vertexFunction = vertexProgram;
pipelineStateDescriptor.fragmentFunction = fragmentProgram;
pipelineStateDescriptor.colorAttachments[0].pixelFormat = _metalLayer.pixelFormat;
[pipelineStateDescriptor.colorAttachments[0] setBlendingEnabled:YES];
pipelineStateDescriptor.colorAttachments[0].rgbBlendOperation = MTLBlendOperationAdd;
pipelineStateDescriptor.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd;
pipelineStateDescriptor.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorSourceAlpha;
pipelineStateDescriptor.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorSourceAlpha;
pipelineStateDescriptor.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
pipelineStateDescriptor.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
NSError *psError = nil;
id<MTLRenderPipelineState> pipelineState = [kQGHWDMetalRendererDevice newRenderPipelineStateWithDescriptor:pipelineStateDescriptor error:&psError];
if (!pipelineState || psError) {
VAP_Error(kQGVAPModuleCommon, @"newRenderPipelineStateWithDescriptor error!:%@", psError);
return nil;
}
return pipelineState;
}
- (id<MTLRenderPipelineState>)defaultMainPipelineState {
if (!_defaultMainPipelineState) {
_defaultMainPipelineState = [self createPipelineState:kVAPVertexFunctionName fragmentFunction:kVAPYUVFragmentFunctionName];
}
return _defaultMainPipelineState;
}
- (id<MTLRenderPipelineState>)mainPipelineStateForMask {
if (!_mainPipelineStateForMask) {
_mainPipelineStateForMask = [self createPipelineState:kVAPVertexFunctionName fragmentFunction:kVAPMaskFragmentFunctionName];
}
return _mainPipelineStateForMask;
}
- (id<MTLRenderPipelineState>)attachmentPipelineState {
if (!_attachmentPipelineState) {
_attachmentPipelineState = [self createPipelineState:kVAPAttachmentVertexFunctionName fragmentFunction:kVAPAttachmentFragmentFunctionName];
}
return _attachmentPipelineState;
}
- (id<MTLRenderPipelineState>)mainPipelineStateForMaskBlur {
if (!_mainPipelineStateForMaskBlur) {
_mainPipelineStateForMaskBlur = [self createPipelineState:kVAPVertexFunctionName fragmentFunction:kVAPMaskBlurFragmentFunctionName];
}
return _mainPipelineStateForMaskBlur;
}
#pragma mark - private
- (void)reconstructIfNeed:(CAMetalLayer *)layer {
if (_renderingResourcesDisposed) {
[self setupRenderContext];
_renderingResourcesDisposed = NO;
}
}
- (void)updateMetalPropertiesIfNeed:(CVPixelBufferRef)pixelBuffer {
if (!pixelBuffer) {
return ;
}
CFTypeRef yCbCrMatrixType = CVBufferGetAttachment(pixelBuffer, kCVImageBufferYCbCrMatrixKey, NULL);
matrix_float3x3 matrix = kColorConversionMatrix601FullRangeDefault;
if (CFStringCompare(yCbCrMatrixType, kCVImageBufferYCbCrMatrix_ITU_R_709_2, 0) == kCFCompareEqualTo) {
matrix = kColorConversionMatrix709FullRangeDefault;
}
if (simd_equal(_currentColorConversionMatrix, matrix)) {
return ;
}
_currentColorConversionMatrix = matrix;
struct ColorParameters yuvMatrixs[] = {{_currentColorConversionMatrix,{0.5, 0.5}}};
NSUInteger yuvMatrixsDataSize = sizeof(struct ColorParameters);
_yuvMatrixBuffer = [kQGHWDMetalRendererDevice newBufferWithBytes:yuvMatrixs length:yuvMatrixsDataSize options:kDefaultMTLResourceOption];
}
- (void)updateMainVertexBuffer {
const int colunmCountForVertices = 4, colunmCountForCoordinate = 2, vertexDataLength = 40; //顶点(x,y,z,w),纹理坐标(x,x),数组长度
static float vertexData[vertexDataLength]; //顶点+纹理坐标数据
float rgbCoordinates[8], alphaCoordinates[8];
float maskCoordinates[8] = {0};
const void *vertices = kVAPMTLVerticesIdentity;
genMTLTextureCoordinates(self.commonInfo.rgbAreaRect, self.commonInfo.videoSize, rgbCoordinates, NO, 0);
genMTLTextureCoordinates(self.commonInfo.alphaAreaRect, self.commonInfo.videoSize, alphaCoordinates, NO, 0);
if (self.maskInfo) {
genMTLTextureCoordinates(self.maskInfo.sampleRect, self.maskInfo.dataSize, maskCoordinates, NO, 0);
}
int indexForVertexData = 0;
//顶点数据+坐标。==> 这里的写法需有优化一下
for (int i = 0; i < 4 * colunmCountForVertices; i ++) {
//顶点数据
vertexData[indexForVertexData++] = ((float*)vertices)[i];
//逐行处理
if (i%colunmCountForVertices == colunmCountForVertices-1) {
int row = i/colunmCountForVertices;
//rgb纹理坐标
vertexData[indexForVertexData++] = ((float*)rgbCoordinates)[row*colunmCountForCoordinate];
vertexData[indexForVertexData++] = ((float*)rgbCoordinates)[row*colunmCountForCoordinate+1];
//alpha纹理坐标
vertexData[indexForVertexData++] = ((float*)alphaCoordinates)[row*colunmCountForCoordinate];
vertexData[indexForVertexData++] = ((float*)alphaCoordinates)[row*colunmCountForCoordinate+1];
//mask纹理坐标
vertexData[indexForVertexData++] = ((float*)maskCoordinates)[row*colunmCountForCoordinate];
vertexData[indexForVertexData++] = ((float*)maskCoordinates)[row*colunmCountForCoordinate+1];
}
}
NSUInteger allocationSize = vertexDataLength * sizeof(float);
id<MTLBuffer> vertexBuffer = [kQGHWDMetalRendererDevice newBufferWithBytes:vertexData length:allocationSize options:kDefaultMTLResourceOption];
_vertexBuffer = vertexBuffer;
}
@end
#endif
@@ -0,0 +1,37 @@
// QGVAPMetalView.h
// Tencent is pleased to support the open source community by making vap available.
//
// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
//
// Licensed under the MIT License (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at
//
// http://opensource.org/licenses/MIT
//
// Unless required by applicable law or agreed to in writing, software distributed under the License is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
// either express or implied. See the License for the specific language governing permissions and
// limitations under the License.
#import <UIKit/UIKit.h>
#import "QGVAPConfigModel.h"
@class QGVAPMaskInfo;
@protocol QGVAPMetalViewDelegate <NSObject>
- (void)onMetalViewUnavailable;
@end
@interface QGVAPMetalView : UIView
@property (nonatomic, weak) id<QGVAPMetalViewDelegate> delegate;
@property (nonatomic, strong) QGVAPCommonInfo *commonInfo;
@property (nonatomic, strong) QGVAPMaskInfo *maskInfo;
- (void)display:(CVPixelBufferRef)pixelBuffer mergeInfos:(NSArray<QGVAPMergedInfo *> *)infos;
- (void)dispose;
@end
@@ -0,0 +1,132 @@
// QGVAPMetalView.m
// Tencent is pleased to support the open source community by making vap available.
//
// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
//
// Licensed under the MIT License (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at
//
// http://opensource.org/licenses/MIT
//
// Unless required by applicable law or agreed to in writing, software distributed under the License is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
// either express or implied. See the License for the specific language governing permissions and
// limitations under the License.
#import "QGVAPMetalView.h"
#import "QGVAPMetalRenderer.h"
#import "QGVAPLogger.h"
#if TARGET_OS_SIMULATOR//模拟器
@implementation QGVAPMetalView
- (void)display:(CVPixelBufferRef)pixelBuffer mergeInfos:(NSArray<QGVAPMergedInfo *> *)infos {}
- (void)dispose {}
@end
#else
@interface QGVAPMetalView ()
@property (nonatomic, strong) CAMetalLayer *metalLayer;
@property (nonatomic, strong) QGVAPMetalRenderer *renderer;
@property (nonatomic, assign) BOOL drawableSizeShouldUpdate;
@end
@implementation QGVAPMetalView
#pragma mark - override
+ (Class)layerClass {
return [CAMetalLayer class];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
NSAssert(0, @"initWithCoder: has not been implemented");
if (self = [super initWithCoder:aDecoder]) {
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
_drawableSizeShouldUpdate = YES;
_metalLayer = (CAMetalLayer *)self.layer;
_metalLayer.frame = self.frame;
_metalLayer.opaque = NO;
_renderer = [[QGVAPMetalRenderer alloc] initWithMetalLayer:_metalLayer];
_metalLayer.contentsScale = [UIScreen mainScreen].scale;
_metalLayer.pixelFormat = MTLPixelFormatBGRA8Unorm;
_metalLayer.framebufferOnly = YES;
}
return self;
}
- (void)didMoveToWindow {
[super didMoveToWindow];
self.drawableSizeShouldUpdate = YES;
}
- (void)layoutSubviews {
[super layoutSubviews];
self.drawableSizeShouldUpdate = YES;
}
- (void)dealloc {
[self onMetalViewUnavailable];
}
#pragma mark - getter&setter
- (QGVAPCommonInfo *)commonInfo {
return self.renderer.commonInfo;
}
- (void)setCommonInfo:(QGVAPCommonInfo *)commonInfo {
[self.renderer setCommonInfo:commonInfo];
}
- (void)setMaskInfo:(QGVAPMaskInfo *)maskInfo {
[self.renderer setMaskInfo:maskInfo];
}
#pragma mark - main
- (void)display:(CVPixelBufferRef)pixelBuffer mergeInfos:(NSArray<QGVAPMergedInfo *> *)infos {
if (!self.window) {
VAP_Event(kQGVAPModuleCommon, @"quit display pixelbuffer, cuz window is nil!");
[self onMetalViewUnavailable];
return ;
}
if (self.drawableSizeShouldUpdate) {
CGFloat nativeScale = [UIScreen mainScreen].nativeScale;
CGSize drawableSize = CGSizeMake(CGRectGetWidth(self.bounds)*nativeScale, CGRectGetHeight(self.bounds)*nativeScale);
self.metalLayer.drawableSize = drawableSize;
VAP_Event(kQGVAPModuleCommon, @"update drawablesize :%@", [NSValue valueWithCGSize:drawableSize]);
self.drawableSizeShouldUpdate = NO;
}
[self.renderer renderPixelBuffer:pixelBuffer metalLayer:self.metalLayer mergeInfos:infos];
}
- (void)dispose {
[self.renderer dispose];
}
#pragma mark - private
- (void)onMetalViewUnavailable{
if ([self.delegate respondsToSelector:@selector(onMetalViewUnavailable)]) {
[self.delegate onMetalViewUnavailable];
}
}
@end
#endif
@@ -0,0 +1,41 @@
// QGHWDMP4OpenGLView.h
// Tencent is pleased to support the open source community by making vap available.
//
// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
//
// Licensed under the MIT License (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at
//
// http://opensource.org/licenses/MIT
//
// Unless required by applicable law or agreed to in writing, software distributed under the License is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
// either express or implied. See the License for the specific language governing permissions and
// limitations under the License.
#import <UIKit/UIKit.h>
#import <OpenGLES/ES2/gl.h>
#import <OpenGLES/ES2/glext.h>
#import "UIView+VAP.h"
@protocol QGHWDMP4OpenGLViewDelegate <NSObject>
- (void)onViewUnavailableStatus;
@end
@interface QGHWDMP4OpenGLView : UIView
@property (nonatomic, strong) EAGLContext *glContext;
@property (nonatomic, weak) id<QGHWDMP4OpenGLViewDelegate> displayDelegate;
@property (nonatomic, assign) QGHWDTextureBlendMode blendMode;
@property (nonatomic, assign) BOOL pause;
- (void)setupGL;
- (void)displayPixelBuffer:(CVPixelBufferRef)pixelBuffer;
- (void)dispose;
//update glcontext's viewport size by layer bounds
- (void)updateBackingSize;
@end
@@ -0,0 +1,645 @@
// QGHWDMP4OpenGLView.m
// Tencent is pleased to support the open source community by making vap available.
//
// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
//
// Licensed under the MIT License (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at
//
// http://opensource.org/licenses/MIT
//
// Unless required by applicable law or agreed to in writing, software distributed under the License is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
// either express or implied. See the License for the specific language governing permissions and
// limitations under the License.
#import "QGHWDMP4OpenGLView.h"
#import <QuartzCore/QuartzCore.h>
#import <AVFoundation/AVUtilities.h>
#import <mach/mach_time.h>
#import <GLKit/GLKit.h>
#import "VAPMacros.h"
// Uniform index.
enum {
HWD_UNIFORM_Y,
HWD_UNIFORM_UV,
HWD_UNIFORM_COLOR_CONVERSION_MATRIX,
HWD_NUM_UNIFORMS
};
GLint hwd_uniforms[HWD_NUM_UNIFORMS];
// Attribute index.
enum {
ATTRIB_VERTEX,
ATTRIB_TEXCOORD_RGB,
ATTRIB_TEXCOORD_ALPHA,
NUM_ATTRIBUTES
};
// BT.709-HDTV.
static const GLfloat kColorConversion709[] = {
1.164, 1.164, 1.164,
0.0, -0.213, 2.112,
1.793, -0.533, 0.0,
};
// BT.601 full range-http://www.equasys.de/colorconversion.html
const GLfloat kColorConversion601FullRange[] = {
1.0, 1.0, 1.0,
0.0, -0.343, 1.765,
1.4, -0.711, 0.0,
};
// texture coords for blend
const GLfloat textureCoordLeft[] = { // 左侧
0.5, 0.0,
0.0, 0.0,
0.5, 1.0,
0.0, 1.0
};
const GLfloat textureCoordRight[] = { // 右侧
1.0, 0.0,
0.5, 0.0,
1.0, 1.0,
0.5, 1.0
};
const GLfloat textureCoordTop[] = { // 上侧
1.0, 0.0,
0.0, 0.0,
1.0, 0.5,
0.0, 0.5
};
const GLfloat textureCoordBottom[] = { // 下侧
1.0, 0.5,
0.0, 0.5,
1.0, 1.0,
0.0, 1.0
};
#undef cos
#undef sin
NSString *const kVertexShaderSource = SHADER_STRING
(
attribute vec4 position;
attribute vec2 RGBTexCoord;
attribute vec2 alphaTexCoord;
varying vec2 RGBTexCoordVarying;
varying vec2 alphaTexCoordVarying;
void main()
{
float preferredRotation = 3.14;
mat4 rotationMatrix = mat4(cos(preferredRotation), -sin(preferredRotation), 0.0, 0.0,sin(preferredRotation),cos(preferredRotation), 0.0, 0.0,0.0,0.0,1.0,0.0,0.0,0.0, 0.0,1.0);
gl_Position = rotationMatrix * position;
RGBTexCoordVarying = RGBTexCoord;
alphaTexCoordVarying = alphaTexCoord;
}
);
NSString *const kFragmentShaderSource = SHADER_STRING
(
varying highp vec2 RGBTexCoordVarying;
varying highp vec2 alphaTexCoordVarying;
precision mediump float;
uniform sampler2D SamplerY;
uniform sampler2D SamplerUV;
uniform mat3 colorConversionMatrix;
void main()
{
mediump vec3 yuv_rgb;
lowp vec3 rgb_rgb;
mediump vec3 yuv_alpha;
lowp vec3 rgb_alpha;
// Subtract constants to map the video range start at 0
yuv_rgb.x = (texture2D(SamplerY, RGBTexCoordVarying).r);// - (16.0/255.0));
yuv_rgb.yz = (texture2D(SamplerUV, RGBTexCoordVarying).ra - vec2(0.5, 0.5));
rgb_rgb = colorConversionMatrix * yuv_rgb;
yuv_alpha.x = (texture2D(SamplerY, alphaTexCoordVarying).r);// - (16.0/255.0));
yuv_alpha.yz = (texture2D(SamplerUV, alphaTexCoordVarying).ra - vec2(0.5, 0.5));
rgb_alpha = colorConversionMatrix * yuv_alpha;
gl_FragColor = vec4(rgb_rgb,rgb_alpha.r);
// gl_FragColor = vec4(1, 0, 0, 1);
}
);
@interface QGHWDMP4OpenGLView() {
GLint _backingWidth;
GLint _backingHeight;
CVOpenGLESTextureRef _lumaTexture;
CVOpenGLESTextureRef _chromaTexture;
CVOpenGLESTextureCacheRef _textureCache;
GLuint _frameBufferHandle;
GLuint _colorBufferHandle;
const GLfloat *_preferredConversion;
}
@property GLuint program;
- (void)setupBuffers;
- (void)cleanupTextures;
- (BOOL)isValidateProgram:(GLuint)prog;
- (BOOL)loadShaders;
- (BOOL)compileShader:(GLuint *)shader type:(GLenum)type URL:(NSURL *)URL;
- (BOOL)linkProgram:(GLuint)prog;
@end
@implementation QGHWDMP4OpenGLView
+ (Class)layerClass {
return [CAEAGLLayer class];
}
- (id)initWithCoder:(NSCoder *)aDecoder {
if ((self = [super initWithCoder:aDecoder])) {
if (![self commonInit]) {
return nil;
}
}
return self;
}
- (instancetype)init {
if (self = [super init]) {
if (![self commonInit]) {
return nil;
}
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
if (![self commonInit]) {
return nil;
}
}
return self;
}
- (BOOL)commonInit {
self.contentScaleFactor = [[UIScreen mainScreen] scale];
CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
eaglLayer.opaque = NO;
eaglLayer.drawableProperties = @{ kEAGLDrawablePropertyRetainedBacking :[NSNumber numberWithBool:NO],
kEAGLDrawablePropertyColorFormat : kEAGLColorFormatRGBA8};
_glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
if (!_glContext || ![EAGLContext setCurrentContext:_glContext] || ![self loadShaders]) {
return NO;
}
_preferredConversion = kColorConversion709;
return YES;
}
- (void)dealloc {
[self cleanupTextures];
if(_textureCache) {
CFRelease(_textureCache);
}
if ([self.displayDelegate respondsToSelector:@selector(onViewUnavailableStatus)]) {
[self.displayDelegate onViewUnavailableStatus];
}
}
# pragma mark - OpenGL setup
/**
初始化opengl环境
*/
- (void)setupGL {
VAP_Info(kQGVAPModuleCommon, @"setupGL");
[EAGLContext setCurrentContext:_glContext];
[self setupBuffers];
[self loadShaders];
glUseProgram(self.program);
glUniform1i(hwd_uniforms[HWD_UNIFORM_Y], 0);
glUniform1i(hwd_uniforms[HWD_UNIFORM_UV], 1);
glUniformMatrix3fv(hwd_uniforms[HWD_UNIFORM_COLOR_CONVERSION_MATRIX], 1, GL_FALSE, _preferredConversion);
// Create CVOpenGLESTextureCacheRef for optimal CVPixelBufferRef to GLES texture conversion.
if (!_textureCache) {
CVReturn err = CVOpenGLESTextureCacheCreate(kCFAllocatorDefault, NULL, _glContext, NULL, &_textureCache);
if (err != noErr) {
VAP_Event(kQGVAPModuleCommon, @"Error at CVOpenGLESTextureCacheCreate %d", err);
return;
}
}
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
}
#pragma mark - Utilities
- (void)setupBuffers {
glDisable(GL_DEPTH_TEST);
glEnableVertexAttribArray(ATTRIB_VERTEX);
glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), 0);
glEnableVertexAttribArray(ATTRIB_TEXCOORD_RGB);
glVertexAttribPointer(ATTRIB_TEXCOORD_RGB, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), 0);
glEnableVertexAttribArray(ATTRIB_TEXCOORD_ALPHA);
glVertexAttribPointer(ATTRIB_TEXCOORD_ALPHA, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), 0);
glGenFramebuffers(1, &_frameBufferHandle);
glBindFramebuffer(GL_FRAMEBUFFER, _frameBufferHandle);
glGenRenderbuffers(1, &_colorBufferHandle);
glBindRenderbuffer(GL_RENDERBUFFER, _colorBufferHandle);
[_glContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer *)self.layer];
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &_backingWidth);
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &_backingHeight);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _colorBufferHandle);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
VAP_Event(kQGVAPModuleCommon, @"Failed to make complete framebuffer object %x", glCheckFramebufferStatus(GL_FRAMEBUFFER));
}
}
- (void)layoutSubviews {
[super layoutSubviews];
[self updateBackingSize];
}
/**
根据容器大小更新渲染尺寸
*/
- (void)updateBackingSize {
if ([EAGLContext currentContext] != _glContext) {
[EAGLContext setCurrentContext:_glContext];
}
[_glContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer *)self.layer];
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &_backingWidth);
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &_backingHeight);
}
- (void)cleanupTextures {
if (_lumaTexture) {
CFRelease(_lumaTexture);
_lumaTexture = NULL;
}
if (_chromaTexture) {
CFRelease(_chromaTexture);
_chromaTexture = NULL;
}
CVOpenGLESTextureCacheFlush(_textureCache, 0);
}
#pragma mark - OpenGLES drawing
/**
上屏
@param pixelBuffer 硬解出来的samplebuffer数据
*/
- (void)displayPixelBuffer:(CVPixelBufferRef)pixelBuffer {
if (!self.window && [self.displayDelegate respondsToSelector:@selector(onViewUnavailableStatus)]) {
[self.displayDelegate onViewUnavailableStatus];
return ;
}
if ([EAGLContext currentContext] != _glContext) {
[EAGLContext setCurrentContext:_glContext];
}
CVReturn err;
if (pixelBuffer != NULL) {
int frameWidth = (int)CVPixelBufferGetWidth(pixelBuffer);
int frameHeight = (int)CVPixelBufferGetHeight(pixelBuffer);
if (!_textureCache) {
VAP_Event(kQGVAPModuleCommon, @"No video texture cache");
return;
}
[self cleanupTextures];
_preferredConversion = kColorConversion601FullRange;
//y
glActiveTexture(GL_TEXTURE0);
err = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault,
_textureCache,
pixelBuffer,
NULL,
GL_TEXTURE_2D,
GL_LUMINANCE,
frameWidth,
frameHeight,
GL_LUMINANCE,
GL_UNSIGNED_BYTE,
0,
&_lumaTexture);
if (err) {
VAP_Event(kQGVAPModuleCommon, @"Error at CVOpenGLESTextureCacheCreateTextureFromImage %d", err);
}
glBindTexture(CVOpenGLESTextureGetTarget(_lumaTexture), CVOpenGLESTextureGetName(_lumaTexture));
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// uv
glActiveTexture(GL_TEXTURE1);
err = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault,
_textureCache,
pixelBuffer,
NULL,
GL_TEXTURE_2D,
GL_LUMINANCE_ALPHA,
frameWidth / 2.0,
frameHeight / 2.0,
GL_LUMINANCE_ALPHA,
GL_UNSIGNED_BYTE,
1,
&_chromaTexture);
if (err) {
VAP_Error(kQGVAPModuleCommon, @"Error at CVOpenGLESTextureCacheCreateTextureFromImage %d", err);
}
glBindTexture(CVOpenGLESTextureGetTarget(_chromaTexture), CVOpenGLESTextureGetName(_chromaTexture));
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindFramebuffer(GL_FRAMEBUFFER, _frameBufferHandle);
// Set the view port to the entire view.
glViewport(0, 0, _backingWidth, _backingHeight);
}
// glClearColor(0.1f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(self.program);
glUniformMatrix3fv(hwd_uniforms[HWD_UNIFORM_COLOR_CONVERSION_MATRIX], 1, GL_FALSE, _preferredConversion);
// 根据视频的方向和高宽比设置四个顶点。
CGRect vertexRect = AVMakeRectWithAspectRatioInsideRect(CGSizeMake(_backingWidth, _backingHeight), self.layer.bounds);
// 计算归一化四坐标来绘制坐标系。
CGSize normalizedSamplingSize = CGSizeMake(0.0, 0.0);
CGSize cropScaleAmount = CGSizeMake(vertexRect.size.width/self.layer.bounds.size.width, vertexRect.size.height/self.layer.bounds.size.height);
if (cropScaleAmount.width > cropScaleAmount.height) {
normalizedSamplingSize.width = 1.0;
normalizedSamplingSize.height = cropScaleAmount.height/cropScaleAmount.width;
} else {
normalizedSamplingSize.width = 1.0;
normalizedSamplingSize.height = cropScaleAmount.width/cropScaleAmount.height;
}
GLfloat quadVertexData [] = {
-1 * normalizedSamplingSize.width, -1 * normalizedSamplingSize.height,
normalizedSamplingSize.width, -1 * normalizedSamplingSize.height,
-1 * normalizedSamplingSize.width, normalizedSamplingSize.height,
normalizedSamplingSize.width, normalizedSamplingSize.height,
};
// 更新顶点数据
glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, quadVertexData);
glEnableVertexAttribArray(ATTRIB_VERTEX);
glVertexAttribPointer(ATTRIB_TEXCOORD_RGB, 2, GL_FLOAT, 0, 0, [self quadTextureRGBData]);
glEnableVertexAttribArray(ATTRIB_TEXCOORD_RGB);
glVertexAttribPointer(ATTRIB_TEXCOORD_ALPHA, 2, GL_FLOAT, 0, 0, [self quedTextureAlphaData]);
glEnableVertexAttribArray(ATTRIB_TEXCOORD_ALPHA);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindRenderbuffer(GL_RENDERBUFFER, _colorBufferHandle);
if ([EAGLContext currentContext] == _glContext && !self.pause && self.window && [UIApplication sharedApplication].applicationState != UIApplicationStateBackground) {
[_glContext presentRenderbuffer:GL_RENDERBUFFER];
}
}
- (const void *)quedTextureAlphaData {
switch (self.blendMode) {
case QGHWDTextureBlendMode_AlphaLeft:
return textureCoordLeft;
case QGHWDTextureBlendMode_AlphaRight:
return textureCoordRight;
case QGHWDTextureBlendMode_AlphaTop:
return textureCoordTop;
case QGHWDTextureBlendMode_AlphaBottom:
return textureCoordBottom;
default:
return textureCoordLeft;
}
}
- (const void *)quadTextureRGBData {
switch (self.blendMode) {
case QGHWDTextureBlendMode_AlphaLeft:
return textureCoordRight;
case QGHWDTextureBlendMode_AlphaRight:
return textureCoordLeft;
case QGHWDTextureBlendMode_AlphaTop:
return textureCoordBottom;
case QGHWDTextureBlendMode_AlphaBottom:
return textureCoordTop;
default:
return textureCoordRight;
}
}
#pragma mark - OpenGL ES 2 shader compilation
- (BOOL)loadShaders {
GLuint vShader, fShader;
self.program = glCreateProgram();
// Create and compile the vertex shader.
if (![self compileShader:&vShader type:GL_VERTEX_SHADER source:kVertexShaderSource]) {
VAP_Error(kQGVAPModuleCommon, @"Failed to compile vertex shader");
return NO;
}
// Create and compile fragment shader.
if (![self compileShader:&fShader type:GL_FRAGMENT_SHADER source:kFragmentShaderSource]) {
VAP_Error(kQGVAPModuleCommon, @"Failed to compile fragment shader");
return NO;
}
// Attach vertex shader to program.
glAttachShader(self.program, vShader);
// Attach fragment shader to program.
glAttachShader(self.program, fShader);
// Bind attribute locations. This needs to be done prior to linking.
glBindAttribLocation(self.program, ATTRIB_VERTEX, "position");
glBindAttribLocation(self.program, ATTRIB_TEXCOORD_RGB, "RGBTexCoord");
glBindAttribLocation(self.program, ATTRIB_TEXCOORD_ALPHA, "alphaTexCoord");
// Link the program.
if (![self linkProgram:self.program]) {
VAP_Event(kQGVAPModuleCommon, @"Failed to link program: %d", self.program);
if (vShader) {
glDeleteShader(vShader);
vShader = 0;
}
if (fShader) {
glDeleteShader(fShader);
fShader = 0;
}
if (self.program) {
glDeleteProgram(self.program);
self.program = 0;
}
return NO;
}
// Get uniforms' location.
hwd_uniforms[HWD_UNIFORM_Y] = glGetUniformLocation(self.program, "SamplerY");
hwd_uniforms[HWD_UNIFORM_UV] = glGetUniformLocation(self.program, "SamplerUV");
hwd_uniforms[HWD_UNIFORM_COLOR_CONVERSION_MATRIX] = glGetUniformLocation(self.program, "colorConversionMatrix");
// Release vertex and fragment shaders.
if (vShader) {
glDetachShader(self.program, vShader);
glDeleteShader(vShader);
}
if (fShader) {
glDetachShader(self.program, fShader);
glDeleteShader(fShader);
}
return YES;
}
- (BOOL)compileShader:(GLuint *)shader type:(GLenum)type source:(const NSString *)sourceString {
GLint status;
const GLchar *source;
source = (GLchar *)[sourceString UTF8String];
*shader = glCreateShader(type);
glShaderSource(*shader, 1, &source, NULL);
glCompileShader(*shader);
#if defined(DEBUG)
GLint lengthOfLog;
glGetShaderiv(*shader, GL_INFO_LOG_LENGTH, &lengthOfLog);
if (lengthOfLog > 0) {
GLchar *log = (GLchar *)malloc(lengthOfLog);
glGetShaderInfoLog(*shader, lengthOfLog, &lengthOfLog, log);
VAP_Info(kQGVAPModuleCommon, @"MODULE_DECODE Shader compile log:\n%s", log)
free(log);
}
#endif
glGetShaderiv(*shader, GL_COMPILE_STATUS, &status);
if (status == 0) {
glDeleteShader(*shader);
return NO;
}
return YES;
}
- (BOOL)compileShader:(GLuint *)shader type:(GLenum)type URL:(NSURL *)URL {
VAP_Info(kQGVAPModuleCommon, @"compileShader");
NSError *error;
NSString *sourceString = [[NSString alloc] initWithContentsOfURL:URL encoding:NSUTF8StringEncoding error:&error];
if (sourceString == nil) {
VAP_Event(kQGVAPModuleCommon, @"Failed to load vertex shader: %@", [error localizedDescription]);
return NO;
}
const GLchar *source;
source = (GLchar *)[sourceString UTF8String];
*shader = glCreateShader(type);
glShaderSource(*shader, 1, &source, NULL);
glCompileShader(*shader);
#if defined(DEBUG)
GLint lengthOfLog;
glGetShaderiv(*shader, GL_INFO_LOG_LENGTH, &lengthOfLog);
if (lengthOfLog > 0) {
GLchar *log = (GLchar *)malloc(lengthOfLog);
glGetShaderInfoLog(*shader, lengthOfLog, &lengthOfLog, log);
VAP_Info(kQGVAPModuleCommon, @"Shader compile log:\n%s", log);
free(log);
}
#endif
GLint status;
glGetShaderiv(*shader, GL_COMPILE_STATUS, &status);
if (status == 0) {
glDeleteShader(*shader);
return NO;
}
return YES;
}
- (BOOL)linkProgram:(GLuint)prog {
GLint status;
glLinkProgram(prog);
#if defined(DEBUG)
GLint lengthOfLog;
glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &lengthOfLog);
if (lengthOfLog > 0) {
GLchar *log = (GLchar *)malloc(lengthOfLog);
glGetProgramInfoLog(prog, lengthOfLog, &lengthOfLog, log);
VAP_Info(kQGVAPModuleCommon, @"Program link log:\n%s", log);
free(log);
}
#endif
glGetProgramiv(prog, GL_LINK_STATUS, &status);
if (status == 0) {
return NO;
}
return YES;
}
- (BOOL)isValidateProgram:(GLuint)prog {
GLint logLength, status;
glValidateProgram(prog);
glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLength);
if (logLength > 0) {
GLchar *log = (GLchar *)malloc(logLength);
glGetProgramInfoLog(prog, logLength, &logLength, log);
VAP_Info(kQGVAPModuleCommon, @"Program validate log:\n%s", log);
free(log);
}
glGetProgramiv(prog, GL_VALIDATE_STATUS, &status);
if (status == 0) {
VAP_Event(kQGVAPModuleCommon, @"program is not valid:%@",@(status));
return NO;
}
VAP_Info(kQGVAPModuleCommon, @"programe is valid");
return YES;
}
- (void)dispose {
glDisableVertexAttribArray(ATTRIB_VERTEX);
glDisableVertexAttribArray(ATTRIB_TEXCOORD_RGB);
glDisableVertexAttribArray(ATTRIB_TEXCOORD_ALPHA);
}
@end