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