blob: 68486dcfd0760c46296d97f4ffe498d4229e3375 [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
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020019#include "api/video/video_rotation.h"
20#include "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
JT Teha4888f02018-05-30 16:45:36 +000031static 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,
denicijad2088152017-04-28 02:14:54 -070033
JT Teha4888f02018-05-30 16:45:36 +000034 // 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) {
denicijad2088152017-04-28 02:14:54 -070045 switch (rotation) {
JT Teha4888f02018-05-30 16:45:36 +000046 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;
denicijad2088152017-04-28 02:14:54 -070054 }
JT Teha4888f02018-05-30 16:45:36 +000055 return 0;
denicijad2088152017-04-28 02:14:54 -070056}
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
JT Teha4888f02018-05-30 16:45:36 +000078 // RTC Frame parameters.
79 int _offset;
denicijad2088152017-04-28 02:14:54 -070080}
81
82- (instancetype)init {
83 if (self = [super init]) {
JT Teha4888f02018-05-30 16:45:36 +000084 // _offset of 0 is equal to rotation of 0.
85 _offset = 0;
denicijad2088152017-04-28 02:14:54 -070086 _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]) {
JT Teha4888f02018-05-30 16:45:36 +0000101 [self setupView:view];
denicijad2088152017-04-28 02:14:54 -0700102 [self loadAssets];
JT Teha4888f02018-05-30 16:45:36 +0000103 [self setupBuffers];
denicijad2088152017-04-28 02:14:54 -0700104 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 {
JT Teha4888f02018-05-30 16:45:36 +0000124 _offset = offsetForRotation(frame.rotation);
denicijad2088152017-04-28 02:14:54 -0700125 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;
Peter Hanspers1c62b982018-05-03 14:06:04 +0200142 NSString *shaderSource = [self shaderSource];
denicijad2088152017-04-28 02:14:54 -0700143
144 id<MTLLibrary> sourceLibrary =
Peter Hanspers1c62b982018-05-03 14:06:04 +0200145 [_device newLibraryWithSource:shaderSource options:NULL error:&libraryError];
denicijad2088152017-04-28 02:14:54 -0700146
147 if (libraryError) {
148 RTCLogError(@"Metal: Library with source failed\n%@", libraryError);
149 return NO;
150 }
151
152 if (!sourceLibrary) {
153 RTCLogError(@"Metal: Failed to load library. %@", libraryError);
154 return NO;
155 }
156 _defaultLibrary = sourceLibrary;
157
158 return YES;
159}
160
JT Teha4888f02018-05-30 16:45:36 +0000161- (void)setupView:(__kindof MTKView *)view {
162 view.device = _device;
163
164 view.preferredFramesPerSecond = 30;
165 view.autoResizeDrawable = NO;
166
167 // We need to keep reference to the view as it's needed down the rendering pipeline.
168 _view = view;
169}
170
denicijad2088152017-04-28 02:14:54 -0700171- (void)loadAssets {
172 id<MTLFunction> vertexFunction = [_defaultLibrary newFunctionWithName:vertexFunctionName];
173 id<MTLFunction> fragmentFunction = [_defaultLibrary newFunctionWithName:fragmentFunctionName];
174
175 MTLRenderPipelineDescriptor *pipelineDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
176 pipelineDescriptor.label = pipelineDescriptorLabel;
177 pipelineDescriptor.vertexFunction = vertexFunction;
178 pipelineDescriptor.fragmentFunction = fragmentFunction;
179 pipelineDescriptor.colorAttachments[0].pixelFormat = _view.colorPixelFormat;
180 pipelineDescriptor.depthAttachmentPixelFormat = MTLPixelFormatInvalid;
181 NSError *error = nil;
182 _pipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineDescriptor error:&error];
183
184 if (!_pipelineState) {
185 RTCLogError(@"Metal: Failed to create pipeline state. %@", error);
186 }
187}
188
JT Teha4888f02018-05-30 16:45:36 +0000189- (void)setupBuffers {
190 _vertexBuffer = [_device newBufferWithBytes:cubeVertexData
191 length:sizeof(cubeVertexData)
192 options:MTLResourceOptionCPUCacheModeDefault];
193}
194
denicijad2088152017-04-28 02:14:54 -0700195- (void)render {
196 // Wait until the inflight (curently sent to GPU) command buffer
197 // has completed the GPU work.
198 dispatch_semaphore_wait(_inflight_semaphore, DISPATCH_TIME_FOREVER);
199
200 id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
201 commandBuffer.label = commandBufferLabel;
202
203 __block dispatch_semaphore_t block_semaphore = _inflight_semaphore;
204 [commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> _Nonnull) {
205 // GPU work completed.
206 dispatch_semaphore_signal(block_semaphore);
207 }];
208
209 MTLRenderPassDescriptor *renderPassDescriptor = _view.currentRenderPassDescriptor;
210 if (renderPassDescriptor) { // Valid drawable.
211 id<MTLRenderCommandEncoder> renderEncoder =
212 [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
213 renderEncoder.label = renderEncoderLabel;
214
215 // Set context state.
216 [renderEncoder pushDebugGroup:renderEncoderDebugGroup];
217 [renderEncoder setRenderPipelineState:_pipelineState];
JT Teha4888f02018-05-30 16:45:36 +0000218 [renderEncoder setVertexBuffer:_vertexBuffer offset:_offset * sizeof(float) atIndex:0];
denicijad2088152017-04-28 02:14:54 -0700219 [self uploadTexturesToRenderEncoder:renderEncoder];
220
221 [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip
222 vertexStart:0
223 vertexCount:4
224 instanceCount:1];
225 [renderEncoder popDebugGroup];
226 [renderEncoder endEncoding];
227
228 [commandBuffer presentDrawable:_view.currentDrawable];
229 }
230
231 // CPU work is completed, GPU work can be started.
232 [commandBuffer commit];
233}
234
235#pragma mark - RTCMTLRenderer
236
237- (void)drawFrame:(RTCVideoFrame *)frame {
238 @autoreleasepool {
239 if ([self setupTexturesForFrame:frame]) {
240 [self render];
241 }
242 }
243}
244
245@end