2014 snapchat source code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

563 lines
23 KiB

  1. //
  2. // SCManagedCapturePreviewLayerController.m
  3. // Snapchat
  4. //
  5. // Created by Liu Liu on 5/5/15.
  6. // Copyright (c) 2015 Snapchat, Inc. All rights reserved.
  7. //
  8. #import "SCManagedCapturePreviewLayerController.h"
  9. #import "SCBlackCameraDetector.h"
  10. #import "SCCameraTweaks.h"
  11. #import "SCManagedCapturePreviewView.h"
  12. #import "SCManagedCapturer.h"
  13. #import "SCManagedCapturerListener.h"
  14. #import "SCManagedCapturerUtils.h"
  15. #import "SCMetalUtils.h"
  16. #import <SCFoundation/NSData+Random.h>
  17. #import <SCFoundation/SCCoreGraphicsUtils.h>
  18. #import <SCFoundation/SCDeviceName.h>
  19. #import <SCFoundation/SCLog.h>
  20. #import <SCFoundation/SCQueuePerformer.h>
  21. #import <SCFoundation/SCTrace.h>
  22. #import <SCFoundation/SCTraceODPCompatible.h>
  23. #import <SCFoundation/UIScreen+SCSafeAreaInsets.h>
  24. #import <SCGhostToSnappable/SCGhostToSnappableSignal.h>
  25. #import <FBKVOController/FBKVOController.h>
  26. #define SCLogPreviewLayerInfo(fmt, ...) SCLogCoreCameraInfo(@"[PreviewLayerController] " fmt, ##__VA_ARGS__)
  27. #define SCLogPreviewLayerWarning(fmt, ...) SCLogCoreCameraWarning(@"[PreviewLayerController] " fmt, ##__VA_ARGS__)
  28. #define SCLogPreviewLayerError(fmt, ...) SCLogCoreCameraError(@"[PreviewLayerController] " fmt, ##__VA_ARGS__)
  29. const static CGSize kSCManagedCapturePreviewDefaultRenderSize = {
  30. .width = 720, .height = 1280,
  31. };
  32. const static CGSize kSCManagedCapturePreviewRenderSize1080p = {
  33. .width = 1080, .height = 1920,
  34. };
  35. #if !TARGET_IPHONE_SIMULATOR
  36. static NSInteger const kSCMetalCannotAcquireDrawableLimit = 2;
  37. @interface CAMetalLayer (SCSecretFature)
  38. // Call discardContents.
  39. - (void)sc_secretFeature;
  40. @end
  41. @implementation CAMetalLayer (SCSecretFature)
  42. - (void)sc_secretFeature
  43. {
  44. // "discardContents"
  45. char buffer[] = {0x9b, 0x96, 0x8c, 0x9c, 0x9e, 0x8d, 0x9b, 0xbc, 0x90, 0x91, 0x8b, 0x9a, 0x91, 0x8b, 0x8c, 0};
  46. unsigned long len = strlen(buffer);
  47. for (unsigned idx = 0; idx < len; ++idx) {
  48. buffer[idx] = ~buffer[idx];
  49. }
  50. SEL selector = NSSelectorFromString([NSString stringWithUTF8String:buffer]);
  51. if ([self respondsToSelector:selector]) {
  52. NSMethodSignature *signature = [self methodSignatureForSelector:selector];
  53. NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
  54. [invocation setTarget:self];
  55. [invocation setSelector:selector];
  56. [invocation invoke];
  57. }
  58. // For anyone curious, here is the actual implementation for discardContents in 10.3 (With Hopper v4, arm64)
  59. // From glance, this seems pretty safe to call.
  60. // void -[CAMetalLayer(CAMetalLayerPrivate) discardContents](int arg0)
  61. // {
  62. // *(r31 + 0xffffffffffffffe0) = r20;
  63. // *(0xfffffffffffffff0 + r31) = r19;
  64. // r31 = r31 + 0xffffffffffffffe0;
  65. // *(r31 + 0x10) = r29;
  66. // *(0x20 + r31) = r30;
  67. // r29 = r31 + 0x10;
  68. // r19 = *(arg0 + sign_extend_64(*(int32_t *)0x1a6300510));
  69. // if (r19 != 0x0) {
  70. // r0 = loc_1807079dc(*0x1a7811fc8, r19);
  71. // r0 = _CAImageQueueConsumeUnconsumed(*(r19 + 0x10));
  72. // r0 = _CAImageQueueFlush(*(r19 + 0x10));
  73. // r29 = *(r31 + 0x10);
  74. // r30 = *(0x20 + r31);
  75. // r20 = *r31;
  76. // r19 = *(r31 + 0x10);
  77. // r31 = r31 + 0x20;
  78. // r0 = loc_1807079dc(*0x1a7811fc8, zero_extend_64(0x0));
  79. // } else {
  80. // r29 = *(r31 + 0x10);
  81. // r30 = *(0x20 + r31);
  82. // r20 = *r31;
  83. // r19 = *(r31 + 0x10);
  84. // r31 = r31 + 0x20;
  85. // }
  86. // return;
  87. // }
  88. }
  89. @end
  90. #endif
  91. @interface SCManagedCapturePreviewLayerController () <SCManagedCapturerListener>
  92. @property (nonatomic) BOOL renderSuspended;
  93. @end
  94. @implementation SCManagedCapturePreviewLayerController {
  95. SCManagedCapturePreviewView *_view;
  96. CGSize _drawableSize;
  97. SCQueuePerformer *_performer;
  98. FBKVOController *_renderingKVO;
  99. #if !TARGET_IPHONE_SIMULATOR
  100. CAMetalLayer *_metalLayer;
  101. id<MTLCommandQueue> _commandQueue;
  102. id<MTLRenderPipelineState> _renderPipelineState;
  103. CVMetalTextureCacheRef _textureCache;
  104. dispatch_semaphore_t _commandBufferSemaphore;
  105. // If the current view contains an outdated display (or any display)
  106. BOOL _containOutdatedPreview;
  107. // If we called empty outdated display already, but for some reason, hasn't emptied it yet.
  108. BOOL _requireToFlushOutdatedPreview;
  109. NSMutableSet *_tokenSet;
  110. NSUInteger _cannotAcquireDrawable;
  111. #endif
  112. }
  113. + (instancetype)sharedInstance
  114. {
  115. static dispatch_once_t onceToken;
  116. static SCManagedCapturePreviewLayerController *managedCapturePreviewLayerController;
  117. dispatch_once(&onceToken, ^{
  118. managedCapturePreviewLayerController = [[SCManagedCapturePreviewLayerController alloc] init];
  119. });
  120. return managedCapturePreviewLayerController;
  121. }
  122. - (instancetype)init
  123. {
  124. self = [super init];
  125. if (self) {
  126. #if !TARGET_IPHONE_SIMULATOR
  127. // We only allow one renders at a time (Sorry, no double / triple buffering).
  128. // It has to be created early here, otherwise integrity of other parts of the code is not
  129. // guaranteed.
  130. // TODO: I need to reason more about the initialization sequence.
  131. _commandBufferSemaphore = dispatch_semaphore_create(1);
  132. // Set _renderSuspended to be YES so that we won't render until it is fully setup.
  133. _renderSuspended = YES;
  134. _tokenSet = [NSMutableSet set];
  135. #endif
  136. // If the screen is less than default size, we should fallback.
  137. CGFloat nativeScale = [UIScreen mainScreen].nativeScale;
  138. CGSize screenSize = [UIScreen mainScreen].fixedCoordinateSpace.bounds.size;
  139. CGSize renderSize = [SCDeviceName isIphoneX] ? kSCManagedCapturePreviewRenderSize1080p
  140. : kSCManagedCapturePreviewDefaultRenderSize;
  141. if (screenSize.width * nativeScale < renderSize.width) {
  142. _drawableSize = CGSizeMake(screenSize.width * nativeScale, screenSize.height * nativeScale);
  143. } else {
  144. _drawableSize = SCSizeIntegral(
  145. SCSizeCropToAspectRatio(renderSize, SCSizeGetAspectRatio(SCManagedCapturerAllScreenSize())));
  146. }
  147. _performer = [[SCQueuePerformer alloc] initWithLabel:"SCManagedCapturePreviewLayerController"
  148. qualityOfService:QOS_CLASS_USER_INITIATED
  149. queueType:DISPATCH_QUEUE_SERIAL
  150. context:SCQueuePerformerContextCoreCamera];
  151. _renderingKVO = [[FBKVOController alloc] initWithObserver:self];
  152. [_renderingKVO observe:self
  153. keyPath:@keypath(self, renderSuspended)
  154. options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
  155. block:^(id observer, id object, NSDictionary *change) {
  156. BOOL oldValue = [change[NSKeyValueChangeOldKey] boolValue];
  157. BOOL newValue = [change[NSKeyValueChangeNewKey] boolValue];
  158. if (oldValue != newValue) {
  159. [[_delegate blackCameraDetectorForManagedCapturePreviewLayerController:self]
  160. capturePreviewDidBecomeVisible:!newValue];
  161. }
  162. }];
  163. }
  164. return self;
  165. }
  166. - (void)pause
  167. {
  168. #if !TARGET_IPHONE_SIMULATOR
  169. SCTraceStart();
  170. SCLogPreviewLayerInfo(@"pause Metal rendering performer waiting");
  171. [_performer performAndWait:^() {
  172. self.renderSuspended = YES;
  173. }];
  174. SCLogPreviewLayerInfo(@"pause Metal rendering performer finished");
  175. #endif
  176. }
  177. - (void)resume
  178. {
  179. #if !TARGET_IPHONE_SIMULATOR
  180. SCTraceStart();
  181. SCLogPreviewLayerInfo(@"resume Metal rendering performer waiting");
  182. [_performer performAndWait:^() {
  183. self.renderSuspended = NO;
  184. }];
  185. SCLogPreviewLayerInfo(@"resume Metal rendering performer finished");
  186. #endif
  187. }
  188. - (void)setupPreviewLayer
  189. {
  190. #if !TARGET_IPHONE_SIMULATOR
  191. SCTraceStart();
  192. SCAssertMainThread();
  193. SC_GUARD_ELSE_RETURN(SCDeviceSupportsMetal());
  194. if (!_metalLayer) {
  195. _metalLayer = [CAMetalLayer new];
  196. SCLogPreviewLayerInfo(@"setup metalLayer:%@", _metalLayer);
  197. if (!_view) {
  198. // Create capture preview view and setup the metal layer
  199. [self view];
  200. } else {
  201. [_view setupMetalLayer:_metalLayer];
  202. }
  203. }
  204. #endif
  205. }
  206. - (UIView *)newStandInViewWithRect:(CGRect)rect
  207. {
  208. return [self.view resizableSnapshotViewFromRect:rect afterScreenUpdates:YES withCapInsets:UIEdgeInsetsZero];
  209. }
  210. - (void)setupRenderPipeline
  211. {
  212. #if !TARGET_IPHONE_SIMULATOR
  213. SCTraceStart();
  214. SC_GUARD_ELSE_RETURN(SCDeviceSupportsMetal());
  215. SCAssertNotMainThread();
  216. id<MTLDevice> device = SCGetManagedCaptureMetalDevice();
  217. id<MTLLibrary> shaderLibrary = [device newDefaultLibrary];
  218. _commandQueue = [device newCommandQueue];
  219. MTLRenderPipelineDescriptor *renderPipelineDescriptor = [MTLRenderPipelineDescriptor new];
  220. renderPipelineDescriptor.colorAttachments[0].pixelFormat = MTLPixelFormatBGRA8Unorm;
  221. renderPipelineDescriptor.vertexFunction = [shaderLibrary newFunctionWithName:@"yuv_vertex_reshape"];
  222. renderPipelineDescriptor.fragmentFunction = [shaderLibrary newFunctionWithName:@"yuv_fragment_texture"];
  223. MTLVertexDescriptor *vertexDescriptor = [MTLVertexDescriptor vertexDescriptor];
  224. vertexDescriptor.attributes[0].format = MTLVertexFormatFloat2; // position
  225. vertexDescriptor.attributes[0].offset = 0;
  226. vertexDescriptor.attributes[0].bufferIndex = 0;
  227. vertexDescriptor.attributes[1].format = MTLVertexFormatFloat2; // texCoords
  228. vertexDescriptor.attributes[1].offset = 2 * sizeof(float);
  229. vertexDescriptor.attributes[1].bufferIndex = 0;
  230. vertexDescriptor.layouts[0].stepRate = 1;
  231. vertexDescriptor.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex;
  232. vertexDescriptor.layouts[0].stride = 4 * sizeof(float);
  233. renderPipelineDescriptor.vertexDescriptor = vertexDescriptor;
  234. _renderPipelineState = [device newRenderPipelineStateWithDescriptor:renderPipelineDescriptor error:nil];
  235. CVMetalTextureCacheCreate(kCFAllocatorDefault, nil, device, nil, &_textureCache);
  236. _metalLayer.device = device;
  237. _metalLayer.drawableSize = _drawableSize;
  238. _metalLayer.pixelFormat = MTLPixelFormatBGRA8Unorm;
  239. _metalLayer.framebufferOnly = YES; // It is default to Yes.
  240. [_performer performAndWait:^() {
  241. self.renderSuspended = NO;
  242. }];
  243. SCLogPreviewLayerInfo(@"did setup render pipeline");
  244. #endif
  245. }
  246. - (UIView *)view
  247. {
  248. SCTraceStart();
  249. SCAssertMainThread();
  250. if (!_view) {
  251. #if TARGET_IPHONE_SIMULATOR
  252. _view = [[SCManagedCapturePreviewView alloc] initWithFrame:[UIScreen mainScreen].fixedCoordinateSpace.bounds
  253. aspectRatio:SCSizeGetAspectRatio(_drawableSize)
  254. metalLayer:nil];
  255. #else
  256. _view = [[SCManagedCapturePreviewView alloc] initWithFrame:[UIScreen mainScreen].fixedCoordinateSpace.bounds
  257. aspectRatio:SCSizeGetAspectRatio(_drawableSize)
  258. metalLayer:_metalLayer];
  259. SCLogPreviewLayerInfo(@"created SCManagedCapturePreviewView:%@", _view);
  260. #endif
  261. }
  262. return _view;
  263. }
  264. - (void)setManagedCapturer:(id<SCCapturer>)managedCapturer
  265. {
  266. SCTraceStart();
  267. SCLogPreviewLayerInfo(@"setManagedCapturer:%@", managedCapturer);
  268. if (SCDeviceSupportsMetal()) {
  269. [managedCapturer addSampleBufferDisplayController:self context:SCCapturerContext];
  270. }
  271. [managedCapturer addListener:self];
  272. }
  273. - (void)applicationDidEnterBackground
  274. {
  275. #if !TARGET_IPHONE_SIMULATOR
  276. SCTraceStart();
  277. SCAssertMainThread();
  278. SC_GUARD_ELSE_RETURN(SCDeviceSupportsMetal());
  279. SCLogPreviewLayerInfo(@"applicationDidEnterBackground waiting for performer");
  280. [_performer performAndWait:^() {
  281. CVMetalTextureCacheFlush(_textureCache, 0);
  282. [_tokenSet removeAllObjects];
  283. self.renderSuspended = YES;
  284. }];
  285. SCLogPreviewLayerInfo(@"applicationDidEnterBackground signal performer finishes");
  286. #endif
  287. }
  288. - (void)applicationWillResignActive
  289. {
  290. SC_GUARD_ELSE_RETURN(SCDeviceSupportsMetal());
  291. SCTraceStart();
  292. SCAssertMainThread();
  293. #if !TARGET_IPHONE_SIMULATOR
  294. SCLogPreviewLayerInfo(@"pause Metal rendering");
  295. [_performer performAndWait:^() {
  296. self.renderSuspended = YES;
  297. }];
  298. #endif
  299. }
  300. - (void)applicationDidBecomeActive
  301. {
  302. SC_GUARD_ELSE_RETURN(SCDeviceSupportsMetal());
  303. SCTraceStart();
  304. SCAssertMainThread();
  305. #if !TARGET_IPHONE_SIMULATOR
  306. SCLogPreviewLayerInfo(@"resume Metal rendering waiting for performer");
  307. [_performer performAndWait:^() {
  308. self.renderSuspended = NO;
  309. }];
  310. SCLogPreviewLayerInfo(@"resume Metal rendering performer finished");
  311. #endif
  312. }
  313. - (void)applicationWillEnterForeground
  314. {
  315. #if !TARGET_IPHONE_SIMULATOR
  316. SCTraceStart();
  317. SCAssertMainThread();
  318. SC_GUARD_ELSE_RETURN(SCDeviceSupportsMetal());
  319. SCLogPreviewLayerInfo(@"applicationWillEnterForeground waiting for performer");
  320. [_performer performAndWait:^() {
  321. self.renderSuspended = NO;
  322. if (_containOutdatedPreview && _tokenSet.count == 0) {
  323. [self _flushOutdatedPreview];
  324. }
  325. }];
  326. SCLogPreviewLayerInfo(@"applicationWillEnterForeground performer finished");
  327. #endif
  328. }
  329. - (NSString *)keepDisplayingOutdatedPreview
  330. {
  331. SCTraceStart();
  332. NSString *token = [NSData randomBase64EncodedStringOfLength:8];
  333. #if !TARGET_IPHONE_SIMULATOR
  334. SCLogPreviewLayerInfo(@"keepDisplayingOutdatedPreview waiting for performer");
  335. [_performer performAndWait:^() {
  336. [_tokenSet addObject:token];
  337. }];
  338. SCLogPreviewLayerInfo(@"keepDisplayingOutdatedPreview performer finished");
  339. #endif
  340. return token;
  341. }
  342. - (void)endDisplayingOutdatedPreview:(NSString *)keepToken
  343. {
  344. #if !TARGET_IPHONE_SIMULATOR
  345. SC_GUARD_ELSE_RETURN(SCDeviceSupportsMetal());
  346. // I simply use a lock for this. If it becomes a bottleneck, I can figure something else out.
  347. SCTraceStart();
  348. SCLogPreviewLayerInfo(@"endDisplayingOutdatedPreview waiting for performer");
  349. [_performer performAndWait:^() {
  350. [_tokenSet removeObject:keepToken];
  351. if (_tokenSet.count == 0 && _requireToFlushOutdatedPreview && _containOutdatedPreview && !_renderSuspended) {
  352. [self _flushOutdatedPreview];
  353. }
  354. }];
  355. SCLogPreviewLayerInfo(@"endDisplayingOutdatedPreview performer finished");
  356. #endif
  357. }
  358. #pragma mark - SCManagedSampleBufferDisplayController
  359. - (void)enqueueSampleBuffer:(CMSampleBufferRef)sampleBuffer
  360. {
  361. #if !TARGET_IPHONE_SIMULATOR
  362. // Just drop the frame if it is rendering.
  363. SC_GUARD_ELSE_RUN_AND_RETURN_VALUE(dispatch_semaphore_wait(_commandBufferSemaphore, DISPATCH_TIME_NOW) == 0,
  364. SCLogPreviewLayerInfo(@"waiting for commandBufferSemaphore signaled"), );
  365. // Just drop the frame, simple.
  366. [_performer performAndWait:^() {
  367. if (_renderSuspended) {
  368. SCLogGeneralInfo(@"Preview rendering suspends and current sample buffer is dropped");
  369. dispatch_semaphore_signal(_commandBufferSemaphore);
  370. return;
  371. }
  372. @autoreleasepool {
  373. const BOOL isFirstPreviewFrame = !_containOutdatedPreview;
  374. if (isFirstPreviewFrame) {
  375. // Signal that we receieved the first frame (otherwise this will be YES already).
  376. SCGhostToSnappableSignalDidReceiveFirstPreviewFrame();
  377. sc_create_g2s_ticket_f func = [_delegate g2sTicketForManagedCapturePreviewLayerController:self];
  378. SCG2SActivateManiphestTicketQueueWithTicketCreationFunction(func);
  379. }
  380. CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
  381. CVPixelBufferLockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly);
  382. size_t pixelWidth = CVPixelBufferGetWidth(imageBuffer);
  383. size_t pixelHeight = CVPixelBufferGetHeight(imageBuffer);
  384. id<MTLTexture> yTexture =
  385. SCMetalTextureFromPixelBuffer(imageBuffer, 0, MTLPixelFormatR8Unorm, _textureCache);
  386. id<MTLTexture> cbCrTexture =
  387. SCMetalTextureFromPixelBuffer(imageBuffer, 1, MTLPixelFormatRG8Unorm, _textureCache);
  388. CVPixelBufferUnlockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly);
  389. SC_GUARD_ELSE_RUN_AND_RETURN(yTexture && cbCrTexture, dispatch_semaphore_signal(_commandBufferSemaphore));
  390. id<MTLCommandBuffer> commandBuffer = _commandQueue.commandBuffer;
  391. id<CAMetalDrawable> drawable = _metalLayer.nextDrawable;
  392. if (!drawable) {
  393. // Count how many times I cannot acquire drawable.
  394. ++_cannotAcquireDrawable;
  395. if (_cannotAcquireDrawable >= kSCMetalCannotAcquireDrawableLimit) {
  396. // Calling [_metalLayer discardContents] to flush the CAImageQueue
  397. SCLogGeneralInfo(@"Cannot acquire drawable, reboot Metal ..");
  398. [_metalLayer sc_secretFeature];
  399. }
  400. dispatch_semaphore_signal(_commandBufferSemaphore);
  401. return;
  402. }
  403. _cannotAcquireDrawable = 0; // Reset to 0 in case we can acquire drawable.
  404. MTLRenderPassDescriptor *renderPassDescriptor = [MTLRenderPassDescriptor new];
  405. renderPassDescriptor.colorAttachments[0].texture = drawable.texture;
  406. id<MTLRenderCommandEncoder> renderEncoder =
  407. [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
  408. [renderEncoder setRenderPipelineState:_renderPipelineState];
  409. [renderEncoder setFragmentTexture:yTexture atIndex:0];
  410. [renderEncoder setFragmentTexture:cbCrTexture atIndex:1];
  411. // TODO: Prob this out of the image buffer.
  412. // 90 clock-wise rotated texture coordinate.
  413. // Also do aspect fill.
  414. float normalizedHeight, normalizedWidth;
  415. if (pixelWidth * _drawableSize.width > _drawableSize.height * pixelHeight) {
  416. normalizedHeight = 1.0;
  417. normalizedWidth = pixelWidth * (_drawableSize.width / pixelHeight) / _drawableSize.height;
  418. } else {
  419. normalizedHeight = pixelHeight * (_drawableSize.height / pixelWidth) / _drawableSize.width;
  420. normalizedWidth = 1.0;
  421. }
  422. const float vertices[] = {
  423. -normalizedHeight, -normalizedWidth, 1, 1, // lower left -> upper right
  424. normalizedHeight, -normalizedWidth, 1, 0, // lower right -> lower right
  425. -normalizedHeight, normalizedWidth, 0, 1, // upper left -> upper left
  426. normalizedHeight, normalizedWidth, 0, 0, // upper right -> lower left
  427. };
  428. [renderEncoder setVertexBytes:vertices length:sizeof(vertices) atIndex:0];
  429. [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
  430. [renderEncoder endEncoding];
  431. // I need to set a minimum duration for the drawable.
  432. // There is a bug on iOS 10.3, if I present as soon as I can, I am keeping the GPU
  433. // at 30fps even you swipe between views, that causes undesirable visual jarring.
  434. // By set a minimum duration, even it is incrediably small (I tried 10ms, and here 60fps works),
  435. // the OS seems can adjust the frame rate much better when swiping.
  436. // This is an iOS 10.3 new method.
  437. if ([commandBuffer respondsToSelector:@selector(presentDrawable:afterMinimumDuration:)]) {
  438. [(id)commandBuffer presentDrawable:drawable afterMinimumDuration:(1.0 / 60)];
  439. } else {
  440. [commandBuffer presentDrawable:drawable];
  441. }
  442. [commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> commandBuffer) {
  443. dispatch_semaphore_signal(_commandBufferSemaphore);
  444. }];
  445. if (isFirstPreviewFrame) {
  446. if ([drawable respondsToSelector:@selector(addPresentedHandler:)] &&
  447. [drawable respondsToSelector:@selector(presentedTime)]) {
  448. [(id)drawable addPresentedHandler:^(id<MTLDrawable> presentedDrawable) {
  449. SCGhostToSnappableSignalDidRenderFirstPreviewFrame([(id)presentedDrawable presentedTime]);
  450. }];
  451. } else {
  452. [commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> commandBuffer) {
  453. // Using CACurrentMediaTime to approximate.
  454. SCGhostToSnappableSignalDidRenderFirstPreviewFrame(CACurrentMediaTime());
  455. }];
  456. }
  457. }
  458. // We enqueued an sample buffer to display, therefore, it contains an outdated display (to be clean up).
  459. _containOutdatedPreview = YES;
  460. [commandBuffer commit];
  461. }
  462. }];
  463. #endif
  464. }
  465. - (void)flushOutdatedPreview
  466. {
  467. SCTraceStart();
  468. #if !TARGET_IPHONE_SIMULATOR
  469. // This method cannot drop frames (otherwise we will have residual on the screen).
  470. SCLogPreviewLayerInfo(@"flushOutdatedPreview waiting for performer");
  471. [_performer performAndWait:^() {
  472. _requireToFlushOutdatedPreview = YES;
  473. SC_GUARD_ELSE_RETURN(!_renderSuspended);
  474. // Have to make sure we have no token left before return.
  475. SC_GUARD_ELSE_RETURN(_tokenSet.count == 0);
  476. [self _flushOutdatedPreview];
  477. }];
  478. SCLogPreviewLayerInfo(@"flushOutdatedPreview performer finished");
  479. #endif
  480. }
  481. - (void)_flushOutdatedPreview
  482. {
  483. SCTraceStart();
  484. SCAssertPerformer(_performer);
  485. #if !TARGET_IPHONE_SIMULATOR
  486. SCLogPreviewLayerInfo(@"flushOutdatedPreview containOutdatedPreview:%d", _containOutdatedPreview);
  487. // I don't care if this has renderSuspended or not, assuming I did the right thing.
  488. // Emptied, no need to do this any more on foregrounding.
  489. SC_GUARD_ELSE_RETURN(_containOutdatedPreview);
  490. _containOutdatedPreview = NO;
  491. _requireToFlushOutdatedPreview = NO;
  492. [_metalLayer sc_secretFeature];
  493. #endif
  494. }
  495. #pragma mark - SCManagedCapturerListener
  496. - (void)managedCapturer:(id<SCCapturer>)managedCapturer
  497. didChangeVideoPreviewLayer:(AVCaptureVideoPreviewLayer *)videoPreviewLayer
  498. {
  499. SCTraceStart();
  500. SCAssertMainThread();
  501. // Force to load the view
  502. [self view];
  503. _view.videoPreviewLayer = videoPreviewLayer;
  504. SCLogPreviewLayerInfo(@"didChangeVideoPreviewLayer:%@", videoPreviewLayer);
  505. }
  506. - (void)managedCapturer:(id<SCCapturer>)managedCapturer didChangeVideoPreviewGLView:(LSAGLView *)videoPreviewGLView
  507. {
  508. SCTraceStart();
  509. SCAssertMainThread();
  510. // Force to load the view
  511. [self view];
  512. _view.videoPreviewGLView = videoPreviewGLView;
  513. SCLogPreviewLayerInfo(@"didChangeVideoPreviewGLView:%@", videoPreviewGLView);
  514. }
  515. @end