1/* 2 * Copyright 2018 The WebRTC Project Authors. All rights reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 11#import "RTCMTLRGBRenderer.h" 12 13#import <Metal/Metal.h> 14#import <MetalKit/MetalKit.h> 15 16#import "RTCMTLRenderer+Private.h" 17#import "base/RTCLogging.h" 18#import "base/RTCVideoFrame.h" 19#import "base/RTCVideoFrameBuffer.h" 20#import "components/video_frame_buffer/RTCCVPixelBuffer.h" 21 22#include "rtc_base/checks.h" 23 24static NSString *const shaderSource = MTL_STRINGIFY( 25 using namespace metal; 26 27 typedef struct { 28 packed_float2 position; 29 packed_float2 texcoord; 30 } Vertex; 31 32 typedef struct { 33 float4 position[[position]]; 34 float2 texcoord; 35 } VertexIO; 36 37 vertex VertexIO vertexPassthrough(constant Vertex *verticies[[buffer(0)]], 38 uint vid[[vertex_id]]) { 39 VertexIO out; 40 constant Vertex &v = verticies[vid]; 41 out.position = float4(float2(v.position), 0.0, 1.0); 42 out.texcoord = v.texcoord; 43 return out; 44 } 45 46 fragment half4 fragmentColorConversion(VertexIO in[[stage_in]], 47 texture2d<half, access::sample> texture[[texture(0)]], 48 constant bool &isARGB[[buffer(0)]]) { 49 constexpr sampler s(address::clamp_to_edge, filter::linear); 50 51 half4 out = texture.sample(s, in.texcoord); 52 if (isARGB) { 53 out = half4(out.g, out.b, out.a, out.r); 54 } 55 56 return out; 57 }); 58 59@implementation RTCMTLRGBRenderer { 60 // Textures. 61 CVMetalTextureCacheRef _textureCache; 62 id<MTLTexture> _texture; 63 64 // Uniforms. 65 id<MTLBuffer> _uniformsBuffer; 66} 67 68- (BOOL)addRenderingDestination:(__kindof MTKView *)view { 69 if ([super addRenderingDestination:view]) { 70 return [self initializeTextureCache]; 71 } 72 return NO; 73} 74 75- (BOOL)initializeTextureCache { 76 CVReturn status = CVMetalTextureCacheCreate(kCFAllocatorDefault, nil, [self currentMetalDevice], 77 nil, &_textureCache); 78 if (status != kCVReturnSuccess) { 79 RTCLogError(@"Metal: Failed to initialize metal texture cache. Return status is %d", status); 80 return NO; 81 } 82 83 return YES; 84} 85 86- (NSString *)shaderSource { 87 return shaderSource; 88} 89 90- (void)getWidth:(nonnull int *)width 91 height:(nonnull int *)height 92 cropWidth:(nonnull int *)cropWidth 93 cropHeight:(nonnull int *)cropHeight 94 cropX:(nonnull int *)cropX 95 cropY:(nonnull int *)cropY 96 ofFrame:(nonnull RTC_OBJC_TYPE(RTCVideoFrame) *)frame { 97 RTC_OBJC_TYPE(RTCCVPixelBuffer) *pixelBuffer = (RTC_OBJC_TYPE(RTCCVPixelBuffer) *)frame.buffer; 98 *width = CVPixelBufferGetWidth(pixelBuffer.pixelBuffer); 99 *height = CVPixelBufferGetHeight(pixelBuffer.pixelBuffer); 100 *cropWidth = pixelBuffer.cropWidth; 101 *cropHeight = pixelBuffer.cropHeight; 102 *cropX = pixelBuffer.cropX; 103 *cropY = pixelBuffer.cropY; 104} 105 106- (BOOL)setupTexturesForFrame:(nonnull RTC_OBJC_TYPE(RTCVideoFrame) *)frame { 107 RTC_DCHECK([frame.buffer isKindOfClass:[RTC_OBJC_TYPE(RTCCVPixelBuffer) class]]); 108 if (![super setupTexturesForFrame:frame]) { 109 return NO; 110 } 111 CVPixelBufferRef pixelBuffer = ((RTC_OBJC_TYPE(RTCCVPixelBuffer) *)frame.buffer).pixelBuffer; 112 113 id<MTLTexture> gpuTexture = nil; 114 CVMetalTextureRef textureOut = nullptr; 115 bool isARGB; 116 117 int width = CVPixelBufferGetWidth(pixelBuffer); 118 int height = CVPixelBufferGetHeight(pixelBuffer); 119 OSType pixelFormat = CVPixelBufferGetPixelFormatType(pixelBuffer); 120 121 MTLPixelFormat mtlPixelFormat; 122 if (pixelFormat == kCVPixelFormatType_32BGRA) { 123 mtlPixelFormat = MTLPixelFormatBGRA8Unorm; 124 isARGB = false; 125 } else if (pixelFormat == kCVPixelFormatType_32ARGB) { 126 mtlPixelFormat = MTLPixelFormatRGBA8Unorm; 127 isARGB = true; 128 } else { 129 RTC_NOTREACHED(); 130 return NO; 131 } 132 133 CVReturn result = CVMetalTextureCacheCreateTextureFromImage( 134 kCFAllocatorDefault, _textureCache, pixelBuffer, nil, mtlPixelFormat, 135 width, height, 0, &textureOut); 136 if (result == kCVReturnSuccess) { 137 gpuTexture = CVMetalTextureGetTexture(textureOut); 138 } 139 CVBufferRelease(textureOut); 140 141 if (gpuTexture != nil) { 142 _texture = gpuTexture; 143 _uniformsBuffer = 144 [[self currentMetalDevice] newBufferWithBytes:&isARGB 145 length:sizeof(isARGB) 146 options:MTLResourceCPUCacheModeDefaultCache]; 147 return YES; 148 } 149 150 return NO; 151} 152 153- (void)uploadTexturesToRenderEncoder:(id<MTLRenderCommandEncoder>)renderEncoder { 154 [renderEncoder setFragmentTexture:_texture atIndex:0]; 155 [renderEncoder setFragmentBuffer:_uniformsBuffer offset:0 atIndex:0]; 156} 157 158- (void)dealloc { 159 if (_textureCache) { 160 CFRelease(_textureCache); 161 } 162} 163 164@end 165