/* * Copyright 2015 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #import #if !TARGET_OS_IPHONE #import "RTCNSGLVideoView.h" #import #import #import #import "RTCDefaultShader.h" #import "RTCI420TextureCache.h" #import "base/RTCLogging.h" #import "base/RTCVideoFrame.h" @interface RTC_OBJC_TYPE (RTCNSGLVideoView) () // `videoFrame` is set when we receive a frame from a worker thread and is read // from the display link callback so atomicity is required. @property(atomic, strong) RTC_OBJC_TYPE(RTCVideoFrame) * videoFrame; @property(atomic, strong) RTCI420TextureCache *i420TextureCache; - (void)drawFrame; @end static CVReturn OnDisplayLinkFired(CVDisplayLinkRef displayLink, const CVTimeStamp *now, const CVTimeStamp *outputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext) { RTC_OBJC_TYPE(RTCNSGLVideoView) *view = (__bridge RTC_OBJC_TYPE(RTCNSGLVideoView) *)displayLinkContext; [view drawFrame]; return kCVReturnSuccess; } @implementation RTC_OBJC_TYPE (RTCNSGLVideoView) { CVDisplayLinkRef _displayLink; RTC_OBJC_TYPE(RTCVideoFrame) * _lastDrawnFrame; id _shader; } @synthesize delegate = _delegate; @synthesize videoFrame = _videoFrame; @synthesize i420TextureCache = _i420TextureCache; - (instancetype)initWithFrame:(NSRect)frame pixelFormat:(NSOpenGLPixelFormat *)format { return [self initWithFrame:frame pixelFormat:format shader:[[RTCDefaultShader alloc] init]]; } - (instancetype)initWithFrame:(NSRect)frame pixelFormat:(NSOpenGLPixelFormat *)format shader:(id)shader { if (self = [super initWithFrame:frame pixelFormat:format]) { _shader = shader; } return self; } - (void)dealloc { [self teardownDisplayLink]; } - (void)drawRect:(NSRect)rect { [self drawFrame]; } - (void)reshape { [super reshape]; NSRect frame = [self frame]; [self ensureGLContext]; CGLLockContext([[self openGLContext] CGLContextObj]); glViewport(0, 0, frame.size.width, frame.size.height); CGLUnlockContext([[self openGLContext] CGLContextObj]); } - (void)lockFocus { NSOpenGLContext *context = [self openGLContext]; [super lockFocus]; if ([context view] != self) { [context setView:self]; } [context makeCurrentContext]; } - (void)prepareOpenGL { [super prepareOpenGL]; [self ensureGLContext]; glDisable(GL_DITHER); [self setupDisplayLink]; } - (void)clearGLContext { [self ensureGLContext]; self.i420TextureCache = nil; [super clearGLContext]; } #pragma mark - RTC_OBJC_TYPE(RTCVideoRenderer) // These methods may be called on non-main thread. - (void)setSize:(CGSize)size { dispatch_async(dispatch_get_main_queue(), ^{ [self.delegate videoView:self didChangeVideoSize:size]; }); } - (void)renderFrame:(RTC_OBJC_TYPE(RTCVideoFrame) *)frame { self.videoFrame = frame; } #pragma mark - Private - (void)drawFrame { RTC_OBJC_TYPE(RTCVideoFrame) *frame = self.videoFrame; if (!frame || frame == _lastDrawnFrame) { return; } // This method may be called from CVDisplayLink callback which isn't on the // main thread so we have to lock the GL context before drawing. NSOpenGLContext *context = [self openGLContext]; CGLLockContext([context CGLContextObj]); [self ensureGLContext]; glClear(GL_COLOR_BUFFER_BIT); // Rendering native CVPixelBuffer is not supported on OS X. // TODO(magjed): Add support for NV12 texture cache on OS X. frame = [frame newI420VideoFrame]; if (!self.i420TextureCache) { self.i420TextureCache = [[RTCI420TextureCache alloc] initWithContext:context]; } RTCI420TextureCache *i420TextureCache = self.i420TextureCache; if (i420TextureCache) { [i420TextureCache uploadFrameToTextures:frame]; [_shader applyShadingForFrameWithWidth:frame.width height:frame.height rotation:frame.rotation yPlane:i420TextureCache.yTexture uPlane:i420TextureCache.uTexture vPlane:i420TextureCache.vTexture]; [context flushBuffer]; _lastDrawnFrame = frame; } CGLUnlockContext([context CGLContextObj]); } - (void)setupDisplayLink { if (_displayLink) { return; } // Synchronize buffer swaps with vertical refresh rate. GLint swapInt = 1; [[self openGLContext] setValues:&swapInt forParameter:NSOpenGLCPSwapInterval]; // Create display link. CVDisplayLinkCreateWithActiveCGDisplays(&_displayLink); CVDisplayLinkSetOutputCallback(_displayLink, &OnDisplayLinkFired, (__bridge void *)self); // Set the display link for the current renderer. CGLContextObj cglContext = [[self openGLContext] CGLContextObj]; CGLPixelFormatObj cglPixelFormat = [[self pixelFormat] CGLPixelFormatObj]; CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext( _displayLink, cglContext, cglPixelFormat); CVDisplayLinkStart(_displayLink); } - (void)teardownDisplayLink { if (!_displayLink) { return; } CVDisplayLinkRelease(_displayLink); _displayLink = NULL; } - (void)ensureGLContext { NSOpenGLContext* context = [self openGLContext]; NSAssert(context, @"context shouldn't be nil"); if ([NSOpenGLContext currentContext] != context) { [context makeCurrentContext]; } } @end #endif // !TARGET_OS_IPHONE