diff --git a/CMakeLists.txt b/CMakeLists.txt index d972954df8a49d0d35643c3cc6ab05bdbd8eea9e..6e15b5152598be9eb92c79abb4db20af8c515baf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,7 @@ FIND_PACKAGE(Qt5Core REQUIRED) FIND_PACKAGE(Qt5MacExtras REQUIRED) FIND_PACKAGE(Qt5Widgets REQUIRED) FIND_PACKAGE(LibRingClient REQUIRED) +FIND_PACKAGE(OpenGL REQUIRED) EXECUTE_PROCESS(COMMAND git submodule update --init WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) @@ -79,6 +80,7 @@ INCLUDE_DIRECTORIES(SYSTEM ${Qt5MacExtras_INCLUDE_DIRS}) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}) INCLUDE_DIRECTORIES( ${CMAKE_CURRENT_SOURCE_DIR}) INCLUDE_DIRECTORIES(${LIB_RING_CLIENT_INCLUDE_DIR}) +INCLUDE_DIRECTORIES(${OPENGL_INCLUDE_DIR}) SET(CMAKE_MACOSX_RPATH ON) SET(CMAKE_SKIP_BUILD_RPATH FALSE) @@ -179,6 +181,8 @@ SET(ringclient_BACKENDS SET(ringclient_VIEWS src/views/CallView.mm src/views/CallView.h + src/views/CallLayer.mm + src/views/CallLayer.h src/views/ITProgressIndicator.mm src/views/ITProgressIndicator.h src/views/RingOutlineView.mm @@ -420,6 +424,7 @@ TARGET_LINK_LIBRARIES( ${PROJ_NAME} ${Qt5Core_LIBRARIES} ${Qt5MacExtras_LIBRARIES} ${Qt5Widgets_LIBRARIES} + ${OPENGL_LIBRARIES} -lqrencode ) diff --git a/src/CurrentCallVC.mm b/src/CurrentCallVC.mm index 249b96c66838a437606bd6c3c4e3305f6bdcbff7..bc90d0732006a7bf3275850eaf7bf34ee104e0c5 100644 --- a/src/CurrentCallVC.mm +++ b/src/CurrentCallVC.mm @@ -48,6 +48,7 @@ #import "ChatVC.h" #import "BrokerVC.h" #import "views/IconButton.h" +#import "views/CallLayer.h" @interface RendererConnectionsHolder : NSObject @@ -255,11 +256,6 @@ actionHash[ (int)UserActionModel::Action::MUTE_AUDIO] = muteAudioButton; actionHash[ (int)UserActionModel::Action::MUTE_VIDEO] = muteVideoButton; - [videoView setWantsLayer:YES]; - [videoView.layer setBackgroundColor:[NSColor blackColor].CGColor]; - [videoView.layer setFrame:videoView.frame]; - [videoView.layer setContentsGravity:kCAGravityResizeAspect]; - [previewView setWantsLayer:YES]; [previewView.layer setBackgroundColor:[NSColor blackColor].CGColor]; [previewView.layer setContentsGravity:kCAGravityResizeAspectFill]; @@ -392,7 +388,7 @@ &Video::Renderer::frameUpdated, [=]() { [self renderer:Video::PreviewManager::instance().previewRenderer() - renderFrameForView:previewView]; + renderFrameForPreviewView:previewView]; }); }); @@ -407,7 +403,7 @@ &Video::Renderer::frameUpdated, [=]() { [self renderer:Video::PreviewManager::instance().previewRenderer() - renderFrameForView:previewView]; + renderFrameForPreviewView:previewView]; }); } @@ -419,7 +415,7 @@ videoHolder.frameUpdated = QObject::connect(renderer, &Video::Renderer::frameUpdated, [=]() { - [self renderer:renderer renderFrameForView:videoView]; + [self renderer:renderer renderFrameForDistantView:videoView]; }); videoHolder.started = QObject::connect(renderer, @@ -429,7 +425,7 @@ videoHolder.frameUpdated = QObject::connect(renderer, &Video::Renderer::frameUpdated, [=]() { - [self renderer:renderer renderFrameForView:videoView]; + [self renderer:renderer renderFrameForDistantView:videoView]; }); }); @@ -437,11 +433,11 @@ &Video::Renderer::stopped, [=]() { QObject::disconnect(videoHolder.frameUpdated); - [videoView.layer setContents:nil]; + [(CallLayer*)videoView.layer setVideoRunning:NO]; }); } --(void) renderer: (Video::Renderer*)renderer renderFrameForView:(NSView*) view +-(void) renderer: (Video::Renderer*)renderer renderFrameForPreviewView:(NSView*) view { QSize res = renderer->size(); @@ -450,7 +446,6 @@ if (!frame_data) return; - CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGContextRef newContext = CGBitmapContextCreate(frame_data, res.width(), @@ -474,6 +469,20 @@ CFRelease(newImage); } +-(void) renderer: (Video::Renderer*)renderer renderFrameForDistantView:(CallView*) view +{ + QSize res = renderer->size(); + + auto frame_ptr = renderer->currentFrame(); + if (!frame_ptr.ptr) + return; + + CallLayer* callLayer = (CallLayer*) view.layer; + + [callLayer setCurrentFrame:std::move(frame_ptr) ofSize:res]; + [callLayer setVideoRunning:YES]; +} + - (void) initFrame { [self.view setFrame:self.view.superview.bounds]; @@ -538,7 +547,6 @@ QObject::disconnect(previewHolder.frameUpdated); QObject::disconnect(previewHolder.stopped); QObject::disconnect(previewHolder.started); - [videoView.layer setContents:nil]; [previewView.layer setContents:nil]; [_brokerPopoverVC performClose:self]; diff --git a/src/views/CallLayer.h b/src/views/CallLayer.h new file mode 100644 index 0000000000000000000000000000000000000000..6844025d94d7387fcd1c5781f4199ed22b748ef5 --- /dev/null +++ b/src/views/CallLayer.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017 Savoir-faire Linux Inc. + * Author: Anthony Léonard <anthony.leonard@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 <QSize> +#import <video/renderer.h> + +@interface CallLayer : NSOpenGLLayer + +@property BOOL videoRunning; + +- (void) setCurrentFrame:(Video::Frame)framePtr ofSize:(QSize)frameSize; + +@end diff --git a/src/views/CallLayer.mm b/src/views/CallLayer.mm new file mode 100644 index 0000000000000000000000000000000000000000..f1a6ea9bba9ea91a225990f3774773bfd4222a5f --- /dev/null +++ b/src/views/CallLayer.mm @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2017 Savoir-faire Linux Inc. + * Author: Anthony Léonard <anthony.leonard@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 "CallLayer.h" +#import <OpenGL/gl3.h> + +static const GLchar* vShaderSrc = R"glsl( +#version 150 + +in vec2 in_Pos; +in vec2 in_TexCoord; +uniform vec2 in_Scaling; + +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); +} +)glsl"; + +static const GLchar* fShaderSrc = R"glsl( +#version 150 + +out vec4 fragColor; +in vec2 texCoord; + +uniform sampler2D tex; + +void main() +{ + fragColor = texture(tex, texCoord); +} +)glsl"; + +@implementation CallLayer + +// OpenGL handlers +GLuint tex, vbo, vShader, fShader, sProg, vao; + +// Last frame data and attributes +Video::Frame currentFrame; +QSize currentFrameSize; +BOOL currentFrameDisplayed; +NSLock* currentFrameLk; + +- (id) init +{ + self = [super init]; + if (self) { + currentFrameLk = [[NSLock alloc] init]; + [self setVideoRunning:NO]; + } + return self; +} + +// This setter is redefined so we can initialize the OpenGL context when this one is +// setup by the UI (which seems to be done just before the first draw attempt and not in init method); +- (void)setOpenGLContext:(NSOpenGLContext *)openGLContext +{ + [super setOpenGLContext:openGLContext]; + + if (openGLContext) { + GLfloat vertices[] = { + -1.0, 1.0, 0.0, 0.0, // Top-left + 1.0, 1.0, 1.0, 0.0, // Top-right + -1.0, -1.0, 0.0, 1.0, // Bottom-left + 1.0, -1.0, 1.0, 1.0 // Bottom-right + }; + + [openGLContext makeCurrentContext]; + + // VAO + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + + // VBO + glGenBuffers(1, &vbo); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + + // Vertex shader + vShader = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(vShader, 1, &vShaderSrc, NULL); + glCompileShader(vShader); + + // Fragment shader + fShader = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(fShader, 1, &fShaderSrc, NULL); + glCompileShader(fShader); + + // Program + sProg = glCreateProgram(); + glAttachShader(sProg, vShader); + glAttachShader(sProg, fShader); + glBindFragDataLocation(sProg, 0, "fragColor"); + glLinkProgram(sProg); + glUseProgram(sProg); + + // Vertices position attrib + GLuint inPosAttrib = glGetAttribLocation(sProg, "in_Pos"); + glEnableVertexAttribArray(inPosAttrib); + glVertexAttribPointer(inPosAttrib, 2, GL_FLOAT, GL_FALSE, 4*sizeof(GLfloat), 0); + + // Texture position attrib + GLuint inTexCoordAttrib = glGetAttribLocation(sProg, "in_TexCoord"); + glEnableVertexAttribArray(inTexCoordAttrib); + glVertexAttribPointer(inTexCoordAttrib, 2, GL_FLOAT, GL_FALSE, 4*sizeof(GLfloat), (void*)(2*sizeof(GLfloat))); + + // Texture + glGenTextures(1, &tex); + glBindTexture(GL_TEXTURE_2D, tex); + 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); + } +} + +- (NSOpenGLPixelFormat *)openGLPixelFormatForDisplayMask:(uint32_t)mask +{ + NSOpenGLPixelFormatAttribute attrs[] = { + NSOpenGLPFANoRecovery, + NSOpenGLPFAColorSize, 24, + NSOpenGLPFAAlphaSize, 8, + NSOpenGLPFADoubleBuffer, + NSOpenGLPFAScreenMask, + mask, + NSOpenGLPFAAccelerated, + NSOpenGLPFAOpenGLProfile, + NSOpenGLProfileVersion3_2Core, + 0 + }; + + NSOpenGLPixelFormat* pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs]; + + return pixelFormat; +} + +- (BOOL)isAsynchronous +{ + return YES; +} + +- (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, currentFrameSize.width(), currentFrameSize.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, currentFrame.ptr); + currentFrameDisplayed = YES; + } + // To ensure that we will not divide by zero + if (!currentFrameSize.isEmpty()) { + // 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)currentFrameSize.width())/((float)currentFrameSize.height()); + float ratio = viewRatio * (1/frameRatio); + + GLint inScalingUniform = glGetUniformLocation(sProg, "in_Scaling"); + + if (ratio < 1.0) + glUniform2f(inScalingUniform, 1.0, ratio); + else + glUniform2f(inScalingUniform, 1.0/ratio, 1.0); + } + [currentFrameLk unlock]; + + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + if([self videoRunning]) + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); +} + +- (void) setCurrentFrame:(Video::Frame)framePtr ofSize:(QSize)frameSize +{ + [currentFrameLk lock]; + currentFrame = std::move(framePtr); + currentFrameSize = frameSize; + currentFrameDisplayed = NO; + [currentFrameLk unlock]; +} + +@end diff --git a/src/views/CallView.mm b/src/views/CallView.mm index 8e029357b006c02336f09ea8722bc76b9af322ae..c70338a841b4cd249058399ad8fde8a06b53d061 100644 --- a/src/views/CallView.mm +++ b/src/views/CallView.mm @@ -18,6 +18,7 @@ */ #import "CallView.h" +#import "CallLayer.h" #import <QItemSelectionModel> #import <QAbstractProxyModel> @@ -49,6 +50,7 @@ if (self) { [self registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, nil]]; + [self setWantsLayer:YES]; } [self.window setAcceptsMouseMovedEvents:YES]; @@ -64,6 +66,11 @@ return self; } +- (CALayer *)makeBackingLayer +{ + return (CALayer*) [[CallLayer alloc] init]; +} + #pragma mark - Destination Operations