blob: 1c60ecc31a65405cebfe1ae63bc5f7f8243f703a [file] [log] [blame]
denicijad2088152017-04-28 02:14:54 -07001/*
2 * Copyright 2017 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 "RTCMTLRenderer+Private.h"
12
13#import <Metal/Metal.h>
14#import <MetalKit/MetalKit.h>
15
16#import "WebRTC/RTCLogging.h"
17#import "WebRTC/RTCVideoFrame.h"
18
19#include "webrtc/api/video/video_rotation.h"
ehmaldonadoeaaae9e2017-07-07 03:09:51 -070020#include "webrtc/rtc_base/checks.h"
denicijad2088152017-04-28 02:14:54 -070021
22// As defined in shaderSource.
23static NSString *const vertexFunctionName = @"vertexPassthrough";
24static NSString *const fragmentFunctionName = @"fragmentColorConversion";
25
26static NSString *const pipelineDescriptorLabel = @"RTCPipeline";
27static NSString *const commandBufferLabel = @"RTCCommandBuffer";
28static NSString *const renderEncoderLabel = @"RTCEncoder";
29static NSString *const renderEncoderDebugGroup = @"RTCDrawFrame";
30
31static const float cubeVertexData[64] = {
32 -1.0, -1.0, 0.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0,
33
34 // rotation = 90, offset = 16.
35 -1.0, -1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 0.0, -1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0,
36
37 // rotation = 180, offset = 32.
38 -1.0, -1.0, 1.0, 0.0, 1.0, -1.0, 0.0, 0.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0,
39
40 // rotation = 270, offset = 48.
41 -1.0, -1.0, 0.0, 0.0, 1.0, -1.0, 0.0, 1.0, -1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0,
42};
43
44static inline int offsetForRotation(RTCVideoRotation rotation) {
45 switch (rotation) {
46 case RTCVideoRotation_0:
47 return 0;
48 case RTCVideoRotation_90:
49 return 16;
50 case RTCVideoRotation_180:
51 return 32;
52 case RTCVideoRotation_270:
53 return 48;
54 }
55 return 0;
56}
57
58// The max number of command buffers in flight (submitted to GPU).
59// For now setting it up to 1.
60// In future we might use triple buffering method if it improves performance.
61static const NSInteger kMaxInflightBuffers = 1;
62
63@implementation RTCMTLRenderer {
64 __kindof MTKView *_view;
65
66 // Controller.
67 dispatch_semaphore_t _inflight_semaphore;
68
69 // Renderer.
70 id<MTLDevice> _device;
71 id<MTLCommandQueue> _commandQueue;
72 id<MTLLibrary> _defaultLibrary;
73 id<MTLRenderPipelineState> _pipelineState;
74
75 // Buffers.
76 id<MTLBuffer> _vertexBuffer;
77
78 // RTC Frame parameters.
79 int _offset;
80}
81
82- (instancetype)init {
83 if (self = [super init]) {
84 // _offset of 0 is equal to rotation of 0.
85 _offset = 0;
86 _inflight_semaphore = dispatch_semaphore_create(kMaxInflightBuffers);
87 }
88
89 return self;
90}
91
92- (BOOL)addRenderingDestination:(__kindof MTKView *)view {
93 return [self setupWithView:view];
94}
95
96#pragma mark - Private
97
98- (BOOL)setupWithView:(__kindof MTKView *)view {
99 BOOL success = NO;
100 if ([self setupMetal]) {
101 [self setupView:view];
102 [self loadAssets];
103 [self setupBuffers];
104 success = YES;
105 }
106 return success;
107}
108#pragma mark - Inheritance
109
110- (id<MTLDevice>)currentMetalDevice {
111 return _device;
112}
113
114- (NSString *)shaderSource {
115 RTC_NOTREACHED() << "Virtual method not implemented in subclass.";
116 return nil;
117}
118
119- (void)uploadTexturesToRenderEncoder:(id<MTLRenderCommandEncoder>)renderEncoder {
120 RTC_NOTREACHED() << "Virtual method not implemented in subclass.";
121}
122
123- (BOOL)setupTexturesForFrame:(nonnull RTCVideoFrame *)frame {
124 _offset = offsetForRotation(frame.rotation);
125 return YES;
126}
127
128#pragma mark - GPU methods
129
130- (BOOL)setupMetal {
131 // Set the view to use the default device.
132 _device = MTLCreateSystemDefaultDevice();
133 if (!_device) {
134 return NO;
135 }
136
137 // Create a new command queue.
138 _commandQueue = [_device newCommandQueue];
139
140 // Load metal library from source.
141 NSError *libraryError = nil;
142
143 id<MTLLibrary> sourceLibrary =
144 [_device newLibraryWithSource:[self shaderSource] options:NULL error:&libraryError];
145
146 if (libraryError) {
147 RTCLogError(@"Metal: Library with source failed\n%@", libraryError);
148 return NO;
149 }
150
151 if (!sourceLibrary) {
152 RTCLogError(@"Metal: Failed to load library. %@", libraryError);
153 return NO;
154 }
155 _defaultLibrary = sourceLibrary;
156
157 return YES;
158}
159
160- (void)setupView:(__kindof MTKView *)view {
161 view.device = _device;
162
163 view.preferredFramesPerSecond = 30;
164 view.autoResizeDrawable = NO;
165
166 // We need to keep reference to the view as it's needed down the rendering pipeline.
167 _view = view;
168}
169
170- (void)loadAssets {
171 id<MTLFunction> vertexFunction = [_defaultLibrary newFunctionWithName:vertexFunctionName];
172 id<MTLFunction> fragmentFunction = [_defaultLibrary newFunctionWithName:fragmentFunctionName];
173
174 MTLRenderPipelineDescriptor *pipelineDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
175 pipelineDescriptor.label = pipelineDescriptorLabel;
176 pipelineDescriptor.vertexFunction = vertexFunction;
177 pipelineDescriptor.fragmentFunction = fragmentFunction;
178 pipelineDescriptor.colorAttachments[0].pixelFormat = _view.colorPixelFormat;
179 pipelineDescriptor.depthAttachmentPixelFormat = MTLPixelFormatInvalid;
180 NSError *error = nil;
181 _pipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineDescriptor error:&error];
182
183 if (!_pipelineState) {
184 RTCLogError(@"Metal: Failed to create pipeline state. %@", error);
185 }
186}
187
188- (void)setupBuffers {
189 _vertexBuffer = [_device newBufferWithBytes:cubeVertexData
190 length:sizeof(cubeVertexData)
191 options:MTLResourceOptionCPUCacheModeDefault];
192}
193
194- (void)render {
195 // Wait until the inflight (curently sent to GPU) command buffer
196 // has completed the GPU work.
197 dispatch_semaphore_wait(_inflight_semaphore, DISPATCH_TIME_FOREVER);
198
199 id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
200 commandBuffer.label = commandBufferLabel;
201
202 __block dispatch_semaphore_t block_semaphore = _inflight_semaphore;
203 [commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> _Nonnull) {
204 // GPU work completed.
205 dispatch_semaphore_signal(block_semaphore);
206 }];
207
208 MTLRenderPassDescriptor *renderPassDescriptor = _view.currentRenderPassDescriptor;
209 if (renderPassDescriptor) { // Valid drawable.
210 id<MTLRenderCommandEncoder> renderEncoder =
211 [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
212 renderEncoder.label = renderEncoderLabel;
213
214 // Set context state.
215 [renderEncoder pushDebugGroup:renderEncoderDebugGroup];
216 [renderEncoder setRenderPipelineState:_pipelineState];
217 [renderEncoder setVertexBuffer:_vertexBuffer offset:_offset * sizeof(float) atIndex:0];
218 [self uploadTexturesToRenderEncoder:renderEncoder];
219
220 [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip
221 vertexStart:0
222 vertexCount:4
223 instanceCount:1];
224 [renderEncoder popDebugGroup];
225 [renderEncoder endEncoding];
226
227 [commandBuffer presentDrawable:_view.currentDrawable];
228 }
229
230 // CPU work is completed, GPU work can be started.
231 [commandBuffer commit];
232}
233
234#pragma mark - RTCMTLRenderer
235
236- (void)drawFrame:(RTCVideoFrame *)frame {
237 @autoreleasepool {
238 if ([self setupTexturesForFrame:frame]) {
239 [self render];
240 }
241 }
242}
243
244@end