video: use openGL when metal is not available

Change-Id: Ic2d19d19da72b71f73f12d3efab75c9bb384d2fb
diff --git a/src/CurrentCallVC.mm b/src/CurrentCallVC.mm
index c51af7a..1316ea8 100644
--- a/src/CurrentCallVC.mm
+++ b/src/CurrentCallVC.mm
@@ -51,6 +51,7 @@
 #import "VideoCommon.h"
 #import "views/GradientView.h"
 #import "views/MovableView.h"
+#import "views/RenderingView.h"
 
 @interface CurrentCallVC () <NSPopoverDelegate> {
     QString convUid_;
@@ -96,12 +97,12 @@
 
 // Video
 @property (unsafe_unretained) IBOutlet CallView *videoView;
-@property (unsafe_unretained) IBOutlet CallMTKView *previewView;
+@property (unsafe_unretained) IBOutlet RenderingView *previewView;
 @property (unsafe_unretained) IBOutlet MovableView *movableBaseForView;
 @property (unsafe_unretained) IBOutlet NSView* hidePreviewBackground;
 @property (unsafe_unretained) IBOutlet NSButton* hidePreviewButton;
 
-@property (unsafe_unretained) IBOutlet CallMTKView *videoMTKView;
+@property (unsafe_unretained) IBOutlet RenderingView *distantView;
 
 @property RendererConnectionsHolder* renderConnections;
 @property QMetaObject::Connection videoStarted;
@@ -373,13 +374,13 @@
             [headerGradientView setHidden:YES];
             [controlsPanel setHidden:YES];
             [controlsStackView setHidden:YES];
-            [self.videoMTKView fillWithBlack];
+            [self.distantView fillWithBlack];
             [self.previewView fillWithBlack];
             [hidePreviewBackground setHidden:YES];
             [self.previewView setHidden: YES];
-            [self.videoMTKView setHidden: YES];
-            self.previewView.stopRendering = true;
-            self.videoMTKView.stopRendering = true;
+            [self.distantView setHidden: YES];
+            self.previewView.videoRunning = NO;
+            self.distantView.videoRunning = NO;
             [backgroundImage setHidden:NO];
             [bluerBackgroundEffect setHidden:NO];
             break;
@@ -392,13 +393,13 @@
             [backgroundImage setHidden:NO];
             [bluerBackgroundEffect setHidden:NO];
             if(!currentCall.isAudioOnly) {
-                [self.videoMTKView fillWithBlack];
+                [self.distantView fillWithBlack];
                 [self.previewView fillWithBlack];
                 [hidePreviewBackground setHidden:YES];
                 [self.previewView setHidden: YES];
-                [self.videoMTKView setHidden: YES];
-                self.previewView.stopRendering = true;
-                self.videoMTKView.stopRendering = true;
+                [self.distantView setHidden: YES];
+                self.previewView.videoRunning = NO;
+                self.distantView.videoRunning = NO;
             }
             break;
         case Status::INACTIVE:
@@ -436,19 +437,19 @@
 
 -(void) setUpVideoCallView {
     [previewView fillWithBlack];
-    [self.videoMTKView fillWithBlack];
+    [self.distantView fillWithBlack];
     [previewView setHidden: NO];
-    [self.videoMTKView setHidden:NO];
-    [hidePreviewBackground setHidden: self.previewView.stopRendering];
+    [self.distantView setHidden:NO];
+    [hidePreviewBackground setHidden: !self.previewView.videoRunning];
     [bluerBackgroundEffect setHidden:YES];
     [backgroundImage setHidden:YES];
-    self.previewView.stopRendering = false;
-    self.videoMTKView.stopRendering = false;
+    self.previewView.videoRunning = true;
+    self.distantView.videoRunning = true;
 }
 
 -(void) setUpAudioOnlyView {
     [self.previewView setHidden: YES];
-    [self.videoMTKView setHidden: YES];
+    [self.distantView setHidden: YES];
     [hidePreviewBackground setHidden: YES];
     [bluerBackgroundEffect setHidden:NO];
     [backgroundImage setHidden:NO];
@@ -536,11 +537,11 @@
                          if (id == lrc::api::video::PREVIEW_RENDERER_ID) {
                              [self.previewView setHidden:NO];
                              [hidePreviewBackground setHidden: NO];
-                             self.previewView.stopRendering = false;
+                             self.previewView.videoRunning = true;
                          } else if ([self isCurrentCall: id]) {
                              [self mouseIsMoving: NO];
-                             self.videoMTKView.stopRendering = false;
-                             [self.videoMTKView setHidden:NO];
+                             self.distantView.videoRunning = true;
+                             [self.distantView setHidden:NO];
                              [bluerBackgroundEffect setHidden:YES];
                              [backgroundImage setHidden:YES];
                          }
@@ -551,11 +552,11 @@
                      [=](const QString& id) {
                          if (id == lrc::api::video::PREVIEW_RENDERER_ID) {
                              [self.previewView setHidden:YES];
-                             self.previewView.stopRendering = true;
+                             self.previewView.videoRunning = false;
                          } else if ([self isCurrentCall: id]) {
                              [self mouseIsMoving: YES];
-                             self.videoMTKView.stopRendering = true;
-                             [self.videoMTKView setHidden:YES];
+                             self.distantView.videoRunning = false;
+                             [self.distantView setHidden:YES];
                              [bluerBackgroundEffect setHidden:NO];
                              [backgroundImage setHidden:NO];
                          }
@@ -569,46 +570,19 @@
                              if(!renderer->isRendering()) {
                                  return;
                              }
-                             [hidePreviewBackground setHidden: self.previewView.stopRendering];
-                             [self renderer: renderer renderFrameForPreviewView:previewView];
+                             [hidePreviewBackground setHidden: !self.previewView.videoRunning];
+                             [self rendererPreview: renderer];
                          } else if ([self isCurrentCall: id]) {
                              auto renderer = &mediaModel->getRenderer(id);
                              if(!renderer->isRendering()) {
                                  return;
                              }
-                             [self renderer:renderer renderFrameForDistantView: self.videoMTKView];
+                             [self rendererDistantView: renderer];
                          }
                      });
 }
 
--(void) renderer: (const lrc::api::video::Renderer*)renderer renderFrameForPreviewView:(CallMTKView*) view
-{
-    @autoreleasepool {
-        auto framePtr = renderer->currentAVFrame();
-        auto frame = framePtr.get();
-        if(!frame || !frame->width || !frame->height) {
-            return;
-        }
-        auto frameSize = CGSizeMake(frame->width, frame->height);
-        auto rotation = 0;
-        if (frame->data[3] != NULL && (CVPixelBufferRef)frame->data[3]) {
-            [view renderWithPixelBuffer:(CVPixelBufferRef)frame->data[3]
-                                   size: frameSize
-                               rotation: rotation
-                              fillFrame: true];
-            return;
-        }
-        else if (CVPixelBufferRef pixelBuffer = [self getBufferForPreviewFromFrame:frame]) {
-            [view renderWithPixelBuffer: pixelBuffer
-                                   size: frameSize
-                               rotation: rotation
-                              fillFrame: true];
-        }
-    }
-}
-
--(void) renderer: (const lrc::api::video::Renderer*)renderer renderFrameForDistantView:(CallMTKView*) view
-{
+-(void) rendererDistantView: (const lrc::api::video::Renderer*)renderer {
     @autoreleasepool {
         auto framePtr = renderer->currentAVFrame();
         auto frame = framePtr.get();
@@ -622,13 +596,13 @@
             rotation = av_display_rotation_get(data);
         }
         if (frame->data[3] != NULL && (CVPixelBufferRef)frame->data[3]) {
-            [view renderWithPixelBuffer: (CVPixelBufferRef)frame->data[3]
+            [self.distantView renderWithPixelBuffer: (CVPixelBufferRef)frame->data[3]
                                    size: frameSize
                                rotation: rotation
                               fillFrame: false];
         }
         if (CVPixelBufferRef pixelBuffer = [self getBufferForDistantViewFromFrame:frame]) {
-            [view renderWithPixelBuffer: pixelBuffer
+            [self.distantView renderWithPixelBuffer: pixelBuffer
                                    size: frameSize
                                rotation: rotation
                               fillFrame: false];
@@ -636,6 +610,34 @@
     }
 }
 
+-(void) rendererPreview: (const lrc::api::video::Renderer*)renderer {
+    @autoreleasepool {
+        auto framePtr = renderer->currentAVFrame();
+        auto frame = framePtr.get();
+        if(!frame || !frame->width || !frame->height)  {
+            return;
+        }
+        auto frameSize = CGSizeMake(frame->width, frame->height);
+        auto rotation = 0;
+        if (auto matrix = av_frame_get_side_data(frame, AV_FRAME_DATA_DISPLAYMATRIX)) {
+            const int32_t* data = reinterpret_cast<int32_t*>(matrix->data);
+            rotation = av_display_rotation_get(data);
+        }
+        if (frame->data[3] != NULL && (CVPixelBufferRef)frame->data[3]) {
+            [self.previewView renderWithPixelBuffer: (CVPixelBufferRef)frame->data[3]
+                                   size: frameSize
+                               rotation: rotation
+                              fillFrame: false];
+        }
+        if (CVPixelBufferRef pixelBuffer = [self getBufferForPreviewFromFrame:frame]) {
+            [self.previewView renderWithPixelBuffer: pixelBuffer
+                                   size: frameSize
+                               rotation: rotation
+                              fillFrame: true];
+        }
+    }
+}
+
 -(CVPixelBufferRef) getBufferForPreviewFromFrame:(const AVFrame*)frame {
     [VideoCommon fillPixelBuffr:&pixelBufferPreview fromFrame:frame bufferPool:&pixelBufferPoolPreview];
     CVPixelBufferRef buffer  = pixelBufferPreview;
@@ -700,7 +702,7 @@
     [backgroundImage setHidden:NO];
     backgroundImage.layer.contents = nil;
     [self.previewView setHidden:YES];
-    [self.videoMTKView setHidden:YES];
+    [self.distantView setHidden:YES];
 
     contactNameLabel.textColor = [NSColor highlightColor];
     contactNameLabel.textColor = [NSColor highlightColor];
diff --git a/src/RecordFileVC.mm b/src/RecordFileVC.mm
index 9a09b6d..edd1ce6 100644
--- a/src/RecordFileVC.mm
+++ b/src/RecordFileVC.mm
@@ -21,8 +21,8 @@
 #import "AppDelegate.h"
 #import "VideoCommon.h"
 #import "views/HoverButton.h"
-#import "views/CallMTKView.h"
 #import "views/NSColor+RingTheme.h"
+#import "views/RenderingView.h"
 #import "NSString+Extensions.h"
 
 //lrc
@@ -32,7 +32,7 @@
 #import <AVFoundation/AVFoundation.h>
 
 @interface RecordFileVC ()
-@property (unsafe_unretained) IBOutlet CallMTKView* previewView;
+@property (unsafe_unretained) IBOutlet RenderingView* previewView;
 
 @property (unsafe_unretained) IBOutlet NSTextField* timeLabel;
 @property (unsafe_unretained) IBOutlet NSTextField* infoLabel;
@@ -106,7 +106,7 @@
 
 #pragma mark - dispaly
 
--(void) renderer: (const lrc::api::video::Renderer*)renderer renderFrameForView:(CallMTKView*) view
+-(void) renderer: (const lrc::api::video::Renderer*)renderer renderFrameForView:(RenderingView*) view
 {
     @autoreleasepool {
         const CGSize frameSize = [VideoCommon fillPixelBuffr:&pixBuf
@@ -270,7 +270,7 @@
     }
     [previewView fillWithBlack];
 
-    self.previewView.stopRendering = false;
+    self.previewView.videoRunning = true;
     [self connectPreviewSignals];
     avModel->stopPreview();
     avModel->startPreview();
diff --git a/src/VideoCommon.mm b/src/VideoCommon.mm
index 5e3be63..70ff004 100644
--- a/src/VideoCommon.mm
+++ b/src/VideoCommon.mm
@@ -122,10 +122,27 @@
         return;
     }
     base = static_cast<uint8_t*>(CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1));
-    for(size_t i = 0; i < frame->height / 2 * bytesPerRowUV / 2; i++ ){
-        *base++ = frame->data[1][i];
-        *base++ = frame->data[2][i];
-   }
+    if (bytesPerRowUV == frame->linesize[1] * 2) {
+        for(size_t i = 0; i < frame->height / 2 * bytesPerRowUV / 2; i++ ) {
+            *base++ = frame->data[1][i];
+            *base++ = frame->data[2][i];
+        }
+    } else {
+        uint32_t size = frame->linesize[1] * frame->height / 2;
+        uint8_t* dstData = new uint8_t[2 * size];
+        for (int i = 0; i < 2 * size; i++){
+            if (i % 2 == 0){
+                dstData[i] = frame->data[1][i/2];
+            }else {
+                dstData[i] = frame->data[2][i/2];
+            }
+        }
+        [VideoCommon copyLineByLineSrc: dstData
+                                toDest: base
+                           srcLinesize: frame->linesize[1] * 2
+                          destLinesize: bytesPerRowUV
+                                height: frame->height/2];
+    }
     CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
 }
 
diff --git a/src/VideoPrefsVC.mm b/src/VideoPrefsVC.mm
index ff0680d..ed9739a 100644
--- a/src/VideoPrefsVC.mm
+++ b/src/VideoPrefsVC.mm
@@ -18,7 +18,7 @@
  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
  */
 #import "VideoPrefsVC.h"
-#import "views/CallMTKView.h"
+#import "views/RenderingView.h"
 #import "AppDelegate.h"
 #import "VideoCommon.h"
 
@@ -36,7 +36,7 @@
 
 @interface VideoPrefsVC ()
 
-@property  IBOutlet CallMTKView* previewView;
+@property  IBOutlet RenderingView* previewView;
 @property (assign) IBOutlet NSPopUpButton* videoDevicesList;
 @property (assign) IBOutlet NSPopUpButton* sizesList;
 @property (assign) IBOutlet NSPopUpButton* ratesList;
@@ -85,7 +85,7 @@
     QObject::disconnect(deviceEvent);
     AppDelegate* appDelegate = (AppDelegate *)[[NSApplication sharedApplication] delegate];
     if (![appDelegate getActiveCalls].size()) {
-        self.previewView.stopRendering = true;
+        self.previewView.videoRunning = false;
         avModel->stopPreview();
         [previewView fillWithBlack];
     }
@@ -162,7 +162,7 @@
                          if (id != lrc::api::video::PREVIEW_RENDERER_ID) {
                              return;
                          }
-                        self.previewView.stopRendering = false;
+                        self.previewView.videoRunning = true;
                         QObject::disconnect(frameUpdated);
                         QObject::disconnect(previewStarted);
                         QObject::disconnect(previewStopped);
@@ -187,7 +187,7 @@
                                                                    ::PREVIEW_RENDERER_ID) {
                                                                    return;
                                                                }
-                                                               self.previewView.stopRendering = true;
+                                                               self.previewView.videoRunning = false;
                                                                QObject::disconnect(previewStopped);
                                                                QObject::disconnect(frameUpdated);
                          });
@@ -203,7 +203,7 @@
                                        bool updatePreview = avModel->getRenderer(lrc::api ::video::PREVIEW_RENDERER_ID).isRendering() && (defaultDevice != currentVideoDevice);
                                        if (updatePreview) {
                                            [previewView fillWithBlack];
-                                           self.previewView.stopRendering = true;
+                                           self.previewView.videoRunning = false;
                                            [self startPreview];
                                        }
                                        [self addDevices];
@@ -212,7 +212,7 @@
 
 #pragma mark - dispaly
 
--(void) renderer: (const lrc::api::video::Renderer*)renderer renderFrameForView:(CallMTKView*) view
+-(void) renderer: (const lrc::api::video::Renderer*)renderer renderFrameForView:(RenderingView*) view
 {
     @autoreleasepool {
         auto framePtr = renderer->currentAVFrame();
@@ -272,7 +272,7 @@
     AppDelegate* appDelegate = (AppDelegate *)[[NSApplication sharedApplication] delegate];
     auto calls = [appDelegate getActiveCalls];
     if (calls.empty()) {
-        self.previewView.stopRendering = false;
+        self.previewView.videoRunning = true;
         [self connectPreviewSignals];
         avModel->stopPreview();
         avModel->startPreview();
diff --git a/src/views/CallLayer.h b/src/views/CallLayer.h
index 86d21c7..f604e29 100644
--- a/src/views/CallLayer.h
+++ b/src/views/CallLayer.h
@@ -20,11 +20,9 @@
 #import <Cocoa/Cocoa.h>
 #import <QSize>
 #import <video/renderer.h>
+#import "VideoRendering.h"
 
-@interface CallLayer : NSOpenGLLayer
+@interface CallLayer : NSOpenGLLayer <VideoRendering>
 
-@property BOOL videoRunning;
-
-- (void) setCurrentFrame:(Video::Frame)framePtr;
 
 @end
diff --git a/src/views/CallLayer.mm b/src/views/CallLayer.mm
index 3dd5fd3..4e03fa9 100644
--- a/src/views/CallLayer.mm
+++ b/src/views/CallLayer.mm
@@ -26,13 +26,14 @@
 in vec2 in_Pos;
 in vec2 in_TexCoord;
 uniform vec2 in_Scaling;
+uniform mat4 in_rotationMatrix;
 
 out vec2 texCoord;
 
 void main()
 {
     texCoord = in_TexCoord;
-    gl_Position = vec4(in_Pos.x*in_Scaling.x, in_Pos.y*in_Scaling.y, 0.0, 1.0);
+    gl_Position = in_rotationMatrix * vec4(in_Pos.x*in_Scaling.x, in_Pos.y*in_Scaling.y, 0.0, 1.0);
 }
 )glsl";
 
@@ -42,23 +43,37 @@
 out vec4 fragColor;
 in vec2 texCoord;
 
-uniform sampler2D tex;
+uniform sampler2D tex_y, tex_uv;
 
 void main()
 {
-    fragColor = texture(tex, texCoord);
+    mediump vec3 yuv, rgb;
+    yuv.x = (texture(tex_y, texCoord).r);
+    yuv.yz = (texture(tex_uv, texCoord).rg - vec2(0.5, 0.5));
+    rgb = mat3( 1,       1,      1,
+                0, -0.3441, 1.7720,
+                1.4020, -0.7141, 0) * yuv;
+    fragColor = vec4(rgb, 1);
 }
 )glsl";
 
+@interface CallLayer()
+
+@property BOOL currentFrameDisplayed;
+@property NSLock* currentFrameLk;
+@property CGFloat currentWidth;
+@property CGFloat currentHeight;
+@property CGFloat currentAngle;
+@property CVPixelBufferRef currentFrame;
+
+@end
+
 @implementation CallLayer
 
 // OpenGL handlers
-GLuint tex, vbo, vShader, fShader, sProg, vao;
+GLuint textureY, textureUV, textureUniformY, textureUniformUV, vbo, vShader, fShader, sProg, vao;
 
-// Last frame data and attributes
-Video::Frame currentFrame;
-BOOL currentFrameDisplayed;
-NSLock* currentFrameLk;
+@synthesize currentAngle, currentFrameDisplayed, currentFrameLk, currentWidth, currentHeight, currentFrame, videoRunning;
 
 - (id) init
 {
@@ -113,6 +128,9 @@
         glLinkProgram(sProg);
         glUseProgram(sProg);
 
+        textureUniformY = glGetUniformLocation(sProg, "tex_y");
+        textureUniformUV = glGetUniformLocation(sProg, "tex_uv");
+
         // Vertices position attrib
         GLuint inPosAttrib = glGetAttribLocation(sProg, "in_Pos");
         glEnableVertexAttribArray(inPosAttrib);
@@ -122,10 +140,19 @@
         GLuint inTexCoordAttrib = glGetAttribLocation(sProg, "in_TexCoord");
         glEnableVertexAttribArray(inTexCoordAttrib);
         glVertexAttribPointer(inTexCoordAttrib, 2, GL_FLOAT, GL_FALSE, 4*sizeof(GLfloat), (void*)(2*sizeof(GLfloat)));
+        // TextureY
+        glActiveTexture(GL_TEXTURE0);
+        glGenTextures(1, &textureY);
+        glBindTexture(GL_TEXTURE_2D, textureY);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
 
-        // Texture
-        glGenTextures(1, &tex);
-        glBindTexture(GL_TEXTURE_2D, tex);
+        // TextureUV
+        glActiveTexture(GL_TEXTURE1);
+        glGenTextures(1, &textureUV);
+        glBindTexture(GL_TEXTURE_2D, textureUV);
         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
@@ -161,25 +188,35 @@
 - (void)drawInOpenGLContext:(NSOpenGLContext *)context pixelFormat:(NSOpenGLPixelFormat *)pixelFormat forLayerTime:(CFTimeInterval)t displayTime:(const CVTimeStamp *)ts
 {
     GLenum errEnum;
-    glBindTexture(GL_TEXTURE_2D, tex);
-
     [currentFrameLk lock];
-    if(!currentFrameDisplayed) {
-        if(currentFrame.ptr) {
-            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, currentFrame.width, currentFrame.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, currentFrame.ptr);
-        }
+    if(!currentFrameDisplayed && currentFrame) {
+        CVPixelBufferLockBaseAddress(currentFrame, 0);
+        [self configureTexture:textureY
+                         index:0
+                       uniform:textureUniformY
+                 activeTexture:GL_TEXTURE0
+                        format:GL_RED
+                     fullPlane: YES];
+        [self configureTexture:textureUV
+                         index:1
+                       uniform:textureUniformUV
+                 activeTexture:GL_TEXTURE1
+                        format:GL_RG
+                     fullPlane: NO];
+        CVPixelBufferUnlockBaseAddress(currentFrame, 0);
+        CVPixelBufferRelease(currentFrame);
         currentFrameDisplayed = YES;
     }
     // To ensure that we will not divide by zero
-    if (currentFrame.ptr && currentFrame.width && currentFrame.height) {
+    if (currentFrame && currentWidth && currentHeight) {
         // Compute scaling factor to keep the original aspect ratio of the video
         CGSize viewSize = self.frame.size;
-        float viewRatio = viewSize.width/viewSize.height;
-        float frameRatio = ((float)currentFrame.width)/((float)currentFrame.height);
+        float viewRatio = (currentAngle == 90 || currentAngle == -90) ?
+           viewSize.height/viewSize.width : viewSize.width/viewSize.height;
+        float frameRatio = ((float)currentWidth)/((float)currentHeight);
         float ratio = viewRatio * (1/frameRatio);
 
         GLint inScalingUniform = glGetUniformLocation(sProg, "in_Scaling");
-
         float multiplier = MAX(frameRatio, ratio);
         if((viewRatio >= 1 && frameRatio >= 1) ||
            (viewRatio < 1 && frameRatio < 1) ||
@@ -193,24 +230,73 @@
                 glUniform2f(inScalingUniform, 1.0, 1.0 * ratio);
             else
                 glUniform2f(inScalingUniform, 1.0/ratio, 1.0);
-
         }
     }
-    [currentFrameLk unlock];
 
-    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+    // apply rotation
+    float radians = (currentAngle * M_PI) / 180;
+    float rotation[16] =
+    {
+         cos(radians), -sin(radians), 0.0f, 0.0f,
+         sin(radians), cos(radians),  0.0f, 0.0f,
+         0.0f,         0.0f,          1.0f, 0.0f,
+         0.0f,         0.0f,          0.0f, 1.0f
+    };
+    GLint location = glGetUniformLocation(sProg, "in_rotationMatrix");
+    glUniformMatrix4fv(location, 1, GL_FALSE, rotation);
+
+    [currentFrameLk unlock];
+    glClearColor(0.0f, 0.0f, 0.0f, 0.1f);
     glClear(GL_COLOR_BUFFER_BIT);
 
     if([self videoRunning])
         glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
 }
 
-- (void) setCurrentFrame:(Video::Frame)framePtr
-{
+-(void)configureTexture:(GLuint)texture index:(int)index
+                uniform:(GLuint)uniform
+          activeTexture:(GLenum)activeTexture
+                 format:(GLint)format
+              fullPlane:(BOOL)fullPlane {
+    auto plane = CVPixelBufferGetBaseAddressOfPlane(currentFrame, index);
+    auto width = CVPixelBufferGetWidthOfPlane(currentFrame, index);
+    auto height = CVPixelBufferGetHeightOfPlane(currentFrame, index);
+    auto strideWidth = CVPixelBufferGetBytesPerRowOfPlane(currentFrame, index);
+    strideWidth = fullPlane ? strideWidth : strideWidth * 0.5;
+    if(strideWidth > width) {
+        glPixelStorei(GL_UNPACK_ROW_LENGTH, strideWidth);
+        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+    } else {
+        glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+        glPixelStorei(GL_UNPACK_ALIGNMENT, 0);
+    }
+    glActiveTexture(activeTexture);
+    glBindTexture(GL_TEXTURE_2D, texture);
+    glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, plane);
+    glUniform1i(uniform, index);
+}
+
+-(void)fillWithBlack {
     [currentFrameLk lock];
-    currentFrame = std::move(framePtr);
-    currentFrameDisplayed = NO;
+    if(currentFrame) {
+        currentFrame = nullptr;
+        currentFrameDisplayed = YES;
+    }
     [currentFrameLk unlock];
 }
 
+-(void)renderWithPixelBuffer:(CVPixelBufferRef)buffer size:(CGSize)size rotation: (float)rotation fillFrame: (bool)fill {
+    [currentFrameLk lock];
+    currentFrame = buffer;
+    CFRetain(currentFrame);
+    currentWidth = size.width;
+    currentHeight = size.height;
+    currentAngle = rotation;
+    currentFrameDisplayed = NO;
+    videoRunning = YES;
+    [currentFrameLk unlock];
+}
+-(void)setupView {
+}
+
 @end
diff --git a/src/views/CallMTKView.h b/src/views/CallMTKView.h
index edf09d5..ab432a4 100644
--- a/src/views/CallMTKView.h
+++ b/src/views/CallMTKView.h
@@ -19,10 +19,7 @@
 
 #import <Cocoa/Cocoa.h>
 #import <MetalKit/MetalKit.h>
+#import "VideoRendering.h"
 
-@interface CallMTKView: MTKView
--(void)renderWithPixelBuffer:(CVPixelBufferRef)buffer size:(CGSize)size rotation: (float)rotation fillFrame: (bool)fill;
--(void)fillWithBlack;
--(void)setupView;
-@property bool stopRendering;
+@interface CallMTKView: MTKView <VideoRendering>
 @end
diff --git a/src/views/CallMTKView.mm b/src/views/CallMTKView.mm
index b49dd46..98d0afc 100644
--- a/src/views/CallMTKView.mm
+++ b/src/views/CallMTKView.mm
@@ -48,6 +48,7 @@
     simd::float4x4 projectionMatrix;
     simd::float4x4 rotationMatrix;
 };
+@synthesize videoRunning;
 
 - (instancetype)initWithFrame:(NSRect)frame
 {
@@ -151,7 +152,7 @@
     if(frameDisplayed) {
         return;
     }
-    if(_stopRendering) {
+    if(!self.videoRunning) {
         self.releaseDrawables;
         return;
     }
diff --git a/src/views/RenderingView.h b/src/views/RenderingView.h
new file mode 100644
index 0000000..1be755f
--- /dev/null
+++ b/src/views/RenderingView.h
@@ -0,0 +1,26 @@
+/*
+*  Copyright (C) 2020 Savoir-faire Linux Inc.
+*  Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
+*
+*  This program is free software; you can redistribute it and/or modify
+*  it under the terms of the GNU General Public License as published by
+*  the Free Software Foundation; either version 3 of the License, or
+*  (at your option) any later version.
+*
+*  This program is distributed in the hope that it will be useful,
+*  but WITHOUT ANY WARRANTY; without even the implied warranty of
+*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*  GNU General Public License for more details.
+*
+*  You should have received a copy of the GNU General Public License
+*  along with this program; if not, write to the Free Software
+*  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+*/
+
+#import <Cocoa/Cocoa.h>
+#import "VideoRendering.h"
+
+@interface RenderingView : NSView <VideoRendering>
+
+@end
+
diff --git a/src/views/RenderingView.mm b/src/views/RenderingView.mm
new file mode 100644
index 0000000..24d8006
--- /dev/null
+++ b/src/views/RenderingView.mm
@@ -0,0 +1,89 @@
+/*
+*  Copyright (C) 2020 Savoir-faire Linux Inc.
+*  Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
+*
+*  This program is free software; you can redistribute it and/or modify
+*  it under the terms of the GNU General Public License as published by
+*  the Free Software Foundation; either version 3 of the License, or
+*  (at your option) any later version.
+*
+*  This program is distributed in the hope that it will be useful,
+*  but WITHOUT ANY WARRANTY; without even the implied warranty of
+*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*  GNU General Public License for more details.
+*
+*  You should have received a copy of the GNU General Public License
+*  along with this program; if not, write to the Free Software
+*  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+*/
+#import "RenderingView.h"
+#import "src/utils.h"
+#import "CallMTKView.h"
+#import "CallLayer.h"
+
+@interface RenderingView()
+
+@property id <VideoRendering> renderer;
+
+@end
+
+@implementation RenderingView
+@synthesize videoRunning, renderer;
+
+- (instancetype)initWithFrame:(NSRect)frame {
+    self = [super initWithFrame:frame];
+    [self commonInit];
+    return self;
+}
+
+- (instancetype)initWithCoder:(NSCoder *)coder {
+    self = [super initWithCoder:coder];
+    [self commonInit];
+    return self;
+}
+
+-(void)commonInit {
+    if ([self metalSupported]) {
+        renderer = [[CallMTKView alloc] initWithFrame:self.frame];
+        NSView* renderView = (NSView*)renderer;
+        [self addSubview: renderView];
+        renderView.translatesAutoresizingMaskIntoConstraints = true;
+        [renderView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
+        [renderView.topAnchor constraintEqualToAnchor:self.topAnchor constant:0].active = YES;
+        [renderView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor constant:0].active = YES;
+        [renderView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:0].active = YES;
+        [renderView.trailingAnchor constraintEqualToAnchor:self.trailingAnchor constant:0].active = YES;
+    } else {
+        [self setLayer:[[CallLayer alloc] init]];
+        [self setWantsLayer:true];
+        CallLayer* callLayer = (CallLayer*)self.layer;
+        renderer = callLayer;
+    }
+}
+
+-(void)renderWithPixelBuffer:(CVPixelBufferRef)buffer size:(CGSize)size rotation: (float)rotation fillFrame: (bool)fill {
+    [renderer renderWithPixelBuffer:buffer size:size rotation:rotation fillFrame:fill];
+}
+
+-(void)fillWithBlack {
+    [renderer fillWithBlack];
+    [self.layer setBackgroundColor:NSColor.blackColor.CGColor];
+}
+
+-(void)setupView {
+    [renderer setupView];
+}
+
+-(void)setVideoRunning:(BOOL)running {
+    // for opengl video running set when new frame received
+    if ([self metalSupported] || !running) {
+        renderer.videoRunning = running;
+    }
+    videoRunning = running;
+}
+
+-(BOOL)metalSupported {
+    return MTLCreateSystemDefaultDevice() != nil;
+}
+
+@end
diff --git a/src/views/VideoRendering.h b/src/views/VideoRendering.h
new file mode 100644
index 0000000..cef76e7
--- /dev/null
+++ b/src/views/VideoRendering.h
@@ -0,0 +1,29 @@
+/*
+*  Copyright (C) 2020 Savoir-faire Linux Inc.
+*  Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
+*
+*  This program is free software; you can redistribute it and/or modify
+*  it under the terms of the GNU General Public License as published by
+*  the Free Software Foundation; either version 3 of the License, or
+*  (at your option) any later version.
+*
+*  This program is distributed in the hope that it will be useful,
+*  but WITHOUT ANY WARRANTY; without even the implied warranty of
+*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*  GNU General Public License for more details.
+*
+*  You should have received a copy of the GNU General Public License
+*  along with this program; if not, write to the Free Software
+*  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+*/
+
+#import <Foundation/Foundation.h>
+#import <video/renderer.h>
+@protocol VideoRendering
+-(void)renderWithPixelBuffer:(CVPixelBufferRef)buffer size:(CGSize)size rotation: (float)rotation fillFrame: (bool)fill;
+-(void)fillWithBlack;
+-(void)setupView;
+@property BOOL videoRunning;
+
+@end
+