1/* 2 * Copyright 2015 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 <Foundation/Foundation.h> 12 13#if !TARGET_OS_IPHONE 14 15#import "RTCNSGLVideoView.h" 16 17#import <AppKit/NSOpenGL.h> 18#import <CoreVideo/CVDisplayLink.h> 19#import <OpenGL/gl3.h> 20 21#import "RTCDefaultShader.h" 22#import "RTCI420TextureCache.h" 23#import "base/RTCLogging.h" 24#import "base/RTCVideoFrame.h" 25 26@interface RTC_OBJC_TYPE (RTCNSGLVideoView) 27() 28 // |videoFrame| is set when we receive a frame from a worker thread and is read 29 // from the display link callback so atomicity is required. 30 @property(atomic, strong) RTC_OBJC_TYPE(RTCVideoFrame) * 31 videoFrame; 32@property(atomic, strong) RTCI420TextureCache *i420TextureCache; 33 34- (void)drawFrame; 35@end 36 37static CVReturn OnDisplayLinkFired(CVDisplayLinkRef displayLink, 38 const CVTimeStamp *now, 39 const CVTimeStamp *outputTime, 40 CVOptionFlags flagsIn, 41 CVOptionFlags *flagsOut, 42 void *displayLinkContext) { 43 RTC_OBJC_TYPE(RTCNSGLVideoView) *view = 44 (__bridge RTC_OBJC_TYPE(RTCNSGLVideoView) *)displayLinkContext; 45 [view drawFrame]; 46 return kCVReturnSuccess; 47} 48 49@implementation RTC_OBJC_TYPE (RTCNSGLVideoView) { 50 CVDisplayLinkRef _displayLink; 51 RTC_OBJC_TYPE(RTCVideoFrame) * _lastDrawnFrame; 52 id<RTC_OBJC_TYPE(RTCVideoViewShading)> _shader; 53} 54 55@synthesize delegate = _delegate; 56@synthesize videoFrame = _videoFrame; 57@synthesize i420TextureCache = _i420TextureCache; 58 59- (instancetype)initWithFrame:(NSRect)frame pixelFormat:(NSOpenGLPixelFormat *)format { 60 return [self initWithFrame:frame pixelFormat:format shader:[[RTCDefaultShader alloc] init]]; 61} 62 63- (instancetype)initWithFrame:(NSRect)frame 64 pixelFormat:(NSOpenGLPixelFormat *)format 65 shader:(id<RTC_OBJC_TYPE(RTCVideoViewShading)>)shader { 66 if (self = [super initWithFrame:frame pixelFormat:format]) { 67 _shader = shader; 68 } 69 return self; 70} 71 72- (void)dealloc { 73 [self teardownDisplayLink]; 74} 75 76- (void)drawRect:(NSRect)rect { 77 [self drawFrame]; 78} 79 80- (void)reshape { 81 [super reshape]; 82 NSRect frame = [self frame]; 83 [self ensureGLContext]; 84 CGLLockContext([[self openGLContext] CGLContextObj]); 85 glViewport(0, 0, frame.size.width, frame.size.height); 86 CGLUnlockContext([[self openGLContext] CGLContextObj]); 87} 88 89- (void)lockFocus { 90 NSOpenGLContext *context = [self openGLContext]; 91 [super lockFocus]; 92 if ([context view] != self) { 93 [context setView:self]; 94 } 95 [context makeCurrentContext]; 96} 97 98- (void)prepareOpenGL { 99 [super prepareOpenGL]; 100 [self ensureGLContext]; 101 glDisable(GL_DITHER); 102 [self setupDisplayLink]; 103} 104 105- (void)clearGLContext { 106 [self ensureGLContext]; 107 self.i420TextureCache = nil; 108 [super clearGLContext]; 109} 110 111#pragma mark - RTC_OBJC_TYPE(RTCVideoRenderer) 112 113// These methods may be called on non-main thread. 114- (void)setSize:(CGSize)size { 115 dispatch_async(dispatch_get_main_queue(), ^{ 116 [self.delegate videoView:self didChangeVideoSize:size]; 117 }); 118} 119 120- (void)renderFrame:(RTC_OBJC_TYPE(RTCVideoFrame) *)frame { 121 self.videoFrame = frame; 122} 123 124#pragma mark - Private 125 126- (void)drawFrame { 127 RTC_OBJC_TYPE(RTCVideoFrame) *frame = self.videoFrame; 128 if (!frame || frame == _lastDrawnFrame) { 129 return; 130 } 131 // This method may be called from CVDisplayLink callback which isn't on the 132 // main thread so we have to lock the GL context before drawing. 133 NSOpenGLContext *context = [self openGLContext]; 134 CGLLockContext([context CGLContextObj]); 135 136 [self ensureGLContext]; 137 glClear(GL_COLOR_BUFFER_BIT); 138 139 // Rendering native CVPixelBuffer is not supported on OS X. 140 // TODO(magjed): Add support for NV12 texture cache on OS X. 141 frame = [frame newI420VideoFrame]; 142 if (!self.i420TextureCache) { 143 self.i420TextureCache = [[RTCI420TextureCache alloc] initWithContext:context]; 144 } 145 RTCI420TextureCache *i420TextureCache = self.i420TextureCache; 146 if (i420TextureCache) { 147 [i420TextureCache uploadFrameToTextures:frame]; 148 [_shader applyShadingForFrameWithWidth:frame.width 149 height:frame.height 150 rotation:frame.rotation 151 yPlane:i420TextureCache.yTexture 152 uPlane:i420TextureCache.uTexture 153 vPlane:i420TextureCache.vTexture]; 154 [context flushBuffer]; 155 _lastDrawnFrame = frame; 156 } 157 CGLUnlockContext([context CGLContextObj]); 158} 159 160- (void)setupDisplayLink { 161 if (_displayLink) { 162 return; 163 } 164 // Synchronize buffer swaps with vertical refresh rate. 165 GLint swapInt = 1; 166 [[self openGLContext] setValues:&swapInt forParameter:NSOpenGLCPSwapInterval]; 167 168 // Create display link. 169 CVDisplayLinkCreateWithActiveCGDisplays(&_displayLink); 170 CVDisplayLinkSetOutputCallback(_displayLink, 171 &OnDisplayLinkFired, 172 (__bridge void *)self); 173 // Set the display link for the current renderer. 174 CGLContextObj cglContext = [[self openGLContext] CGLContextObj]; 175 CGLPixelFormatObj cglPixelFormat = [[self pixelFormat] CGLPixelFormatObj]; 176 CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext( 177 _displayLink, cglContext, cglPixelFormat); 178 CVDisplayLinkStart(_displayLink); 179} 180 181- (void)teardownDisplayLink { 182 if (!_displayLink) { 183 return; 184 } 185 CVDisplayLinkRelease(_displayLink); 186 _displayLink = NULL; 187} 188 189- (void)ensureGLContext { 190 NSOpenGLContext* context = [self openGLContext]; 191 NSAssert(context, @"context shouldn't be nil"); 192 if ([NSOpenGLContext currentContext] != context) { 193 [context makeCurrentContext]; 194 } 195} 196 197@end 198 199#endif // !TARGET_OS_IPHONE 200