blob: 4e03fa9d135aae6659d90bbc09a96f846603b9f9 [file] [log] [blame]
Anthony Léonard14e7bf32017-06-08 08:13:16 -04001/*
Sébastien Blin029ffa82019-01-02 17:43:48 -05002 * Copyright (C) 2017-2019 Savoir-faire Linux Inc.
Anthony Léonard14e7bf32017-06-08 08:13:16 -04003 * Author: Anthony Léonard <anthony.leonard@savoirfairelinux.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20#import "CallLayer.h"
21#import <OpenGL/gl3.h>
22
23static const GLchar* vShaderSrc = R"glsl(
24#version 150
25
26in vec2 in_Pos;
27in vec2 in_TexCoord;
28uniform vec2 in_Scaling;
Kateryna Kostiuk06681682020-05-07 20:50:56 -040029uniform mat4 in_rotationMatrix;
Anthony Léonard14e7bf32017-06-08 08:13:16 -040030
31out vec2 texCoord;
32
33void main()
34{
35 texCoord = in_TexCoord;
Kateryna Kostiuk06681682020-05-07 20:50:56 -040036 gl_Position = in_rotationMatrix * vec4(in_Pos.x*in_Scaling.x, in_Pos.y*in_Scaling.y, 0.0, 1.0);
Anthony Léonard14e7bf32017-06-08 08:13:16 -040037}
38)glsl";
39
40static const GLchar* fShaderSrc = R"glsl(
41#version 150
42
43out vec4 fragColor;
44in vec2 texCoord;
45
Kateryna Kostiuk06681682020-05-07 20:50:56 -040046uniform sampler2D tex_y, tex_uv;
Anthony Léonard14e7bf32017-06-08 08:13:16 -040047
48void main()
49{
Kateryna Kostiuk06681682020-05-07 20:50:56 -040050 mediump vec3 yuv, rgb;
51 yuv.x = (texture(tex_y, texCoord).r);
52 yuv.yz = (texture(tex_uv, texCoord).rg - vec2(0.5, 0.5));
53 rgb = mat3( 1, 1, 1,
54 0, -0.3441, 1.7720,
55 1.4020, -0.7141, 0) * yuv;
56 fragColor = vec4(rgb, 1);
Anthony Léonard14e7bf32017-06-08 08:13:16 -040057}
58)glsl";
59
Kateryna Kostiuk06681682020-05-07 20:50:56 -040060@interface CallLayer()
61
62@property BOOL currentFrameDisplayed;
63@property NSLock* currentFrameLk;
64@property CGFloat currentWidth;
65@property CGFloat currentHeight;
66@property CGFloat currentAngle;
67@property CVPixelBufferRef currentFrame;
68
69@end
70
Anthony Léonard14e7bf32017-06-08 08:13:16 -040071@implementation CallLayer
72
73// OpenGL handlers
Kateryna Kostiuk06681682020-05-07 20:50:56 -040074GLuint textureY, textureUV, textureUniformY, textureUniformUV, vbo, vShader, fShader, sProg, vao;
Anthony Léonard14e7bf32017-06-08 08:13:16 -040075
Kateryna Kostiuk06681682020-05-07 20:50:56 -040076@synthesize currentAngle, currentFrameDisplayed, currentFrameLk, currentWidth, currentHeight, currentFrame, videoRunning;
Anthony Léonard14e7bf32017-06-08 08:13:16 -040077
78- (id) init
79{
80 self = [super init];
81 if (self) {
82 currentFrameLk = [[NSLock alloc] init];
83 [self setVideoRunning:NO];
84 }
85 return self;
86}
87
88// This setter is redefined so we can initialize the OpenGL context when this one is
89// setup by the UI (which seems to be done just before the first draw attempt and not in init method);
90- (void)setOpenGLContext:(NSOpenGLContext *)openGLContext
91{
92 [super setOpenGLContext:openGLContext];
93
94 if (openGLContext) {
95 GLfloat vertices[] = {
96 -1.0, 1.0, 0.0, 0.0, // Top-left
97 1.0, 1.0, 1.0, 0.0, // Top-right
98 -1.0, -1.0, 0.0, 1.0, // Bottom-left
99 1.0, -1.0, 1.0, 1.0 // Bottom-right
100 };
101
102 [openGLContext makeCurrentContext];
103
104 // VAO
105 glGenVertexArrays(1, &vao);
106 glBindVertexArray(vao);
107
108 // VBO
109 glGenBuffers(1, &vbo);
110 glBindBuffer(GL_ARRAY_BUFFER, vbo);
111 glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
112
113 // Vertex shader
114 vShader = glCreateShader(GL_VERTEX_SHADER);
115 glShaderSource(vShader, 1, &vShaderSrc, NULL);
116 glCompileShader(vShader);
117
118 // Fragment shader
119 fShader = glCreateShader(GL_FRAGMENT_SHADER);
120 glShaderSource(fShader, 1, &fShaderSrc, NULL);
121 glCompileShader(fShader);
122
123 // Program
124 sProg = glCreateProgram();
125 glAttachShader(sProg, vShader);
126 glAttachShader(sProg, fShader);
127 glBindFragDataLocation(sProg, 0, "fragColor");
128 glLinkProgram(sProg);
129 glUseProgram(sProg);
130
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400131 textureUniformY = glGetUniformLocation(sProg, "tex_y");
132 textureUniformUV = glGetUniformLocation(sProg, "tex_uv");
133
Anthony Léonard14e7bf32017-06-08 08:13:16 -0400134 // Vertices position attrib
135 GLuint inPosAttrib = glGetAttribLocation(sProg, "in_Pos");
136 glEnableVertexAttribArray(inPosAttrib);
137 glVertexAttribPointer(inPosAttrib, 2, GL_FLOAT, GL_FALSE, 4*sizeof(GLfloat), 0);
138
139 // Texture position attrib
140 GLuint inTexCoordAttrib = glGetAttribLocation(sProg, "in_TexCoord");
141 glEnableVertexAttribArray(inTexCoordAttrib);
142 glVertexAttribPointer(inTexCoordAttrib, 2, GL_FLOAT, GL_FALSE, 4*sizeof(GLfloat), (void*)(2*sizeof(GLfloat)));
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400143 // TextureY
144 glActiveTexture(GL_TEXTURE0);
145 glGenTextures(1, &textureY);
146 glBindTexture(GL_TEXTURE_2D, textureY);
147 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
148 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
149 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
150 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
Anthony Léonard14e7bf32017-06-08 08:13:16 -0400151
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400152 // TextureUV
153 glActiveTexture(GL_TEXTURE1);
154 glGenTextures(1, &textureUV);
155 glBindTexture(GL_TEXTURE_2D, textureUV);
Anthony Léonard14e7bf32017-06-08 08:13:16 -0400156 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
157 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
158 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
159 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
160 }
161}
162
163- (NSOpenGLPixelFormat *)openGLPixelFormatForDisplayMask:(uint32_t)mask
164{
165 NSOpenGLPixelFormatAttribute attrs[] = {
166 NSOpenGLPFANoRecovery,
167 NSOpenGLPFAColorSize, 24,
168 NSOpenGLPFAAlphaSize, 8,
169 NSOpenGLPFADoubleBuffer,
170 NSOpenGLPFAScreenMask,
171 mask,
172 NSOpenGLPFAAccelerated,
173 NSOpenGLPFAOpenGLProfile,
174 NSOpenGLProfileVersion3_2Core,
175 0
176 };
177
178 NSOpenGLPixelFormat* pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs];
179
180 return pixelFormat;
181}
182
183- (BOOL)isAsynchronous
184{
185 return YES;
186}
187
188- (void)drawInOpenGLContext:(NSOpenGLContext *)context pixelFormat:(NSOpenGLPixelFormat *)pixelFormat forLayerTime:(CFTimeInterval)t displayTime:(const CVTimeStamp *)ts
189{
190 GLenum errEnum;
Anthony Léonard14e7bf32017-06-08 08:13:16 -0400191 [currentFrameLk lock];
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400192 if(!currentFrameDisplayed && currentFrame) {
193 CVPixelBufferLockBaseAddress(currentFrame, 0);
194 [self configureTexture:textureY
195 index:0
196 uniform:textureUniformY
197 activeTexture:GL_TEXTURE0
198 format:GL_RED
199 fullPlane: YES];
200 [self configureTexture:textureUV
201 index:1
202 uniform:textureUniformUV
203 activeTexture:GL_TEXTURE1
204 format:GL_RG
205 fullPlane: NO];
206 CVPixelBufferUnlockBaseAddress(currentFrame, 0);
207 CVPixelBufferRelease(currentFrame);
Anthony Léonard14e7bf32017-06-08 08:13:16 -0400208 currentFrameDisplayed = YES;
209 }
210 // To ensure that we will not divide by zero
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400211 if (currentFrame && currentWidth && currentHeight) {
Anthony Léonard14e7bf32017-06-08 08:13:16 -0400212 // Compute scaling factor to keep the original aspect ratio of the video
213 CGSize viewSize = self.frame.size;
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400214 float viewRatio = (currentAngle == 90 || currentAngle == -90) ?
215 viewSize.height/viewSize.width : viewSize.width/viewSize.height;
216 float frameRatio = ((float)currentWidth)/((float)currentHeight);
Anthony Léonard14e7bf32017-06-08 08:13:16 -0400217 float ratio = viewRatio * (1/frameRatio);
218
219 GLint inScalingUniform = glGetUniformLocation(sProg, "in_Scaling");
Kateryna Kostiuk1004caa2018-03-29 13:48:07 -0400220 float multiplier = MAX(frameRatio, ratio);
221 if((viewRatio >= 1 && frameRatio >= 1) ||
222 (viewRatio < 1 && frameRatio < 1) ||
223 (ratio > 0.5 && ratio < 1.5) ) {
224 if (ratio > 1.0)
225 glUniform2f(inScalingUniform, 1.0, 1.0 * ratio);
226 else
227 glUniform2f(inScalingUniform, 1.0/ratio, 1.0);
228 } else {
229 if (ratio < 1.0)
230 glUniform2f(inScalingUniform, 1.0, 1.0 * ratio);
231 else
232 glUniform2f(inScalingUniform, 1.0/ratio, 1.0);
Kateryna Kostiuk1004caa2018-03-29 13:48:07 -0400233 }
Anthony Léonard14e7bf32017-06-08 08:13:16 -0400234 }
Anthony Léonard14e7bf32017-06-08 08:13:16 -0400235
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400236 // apply rotation
237 float radians = (currentAngle * M_PI) / 180;
238 float rotation[16] =
239 {
240 cos(radians), -sin(radians), 0.0f, 0.0f,
241 sin(radians), cos(radians), 0.0f, 0.0f,
242 0.0f, 0.0f, 1.0f, 0.0f,
243 0.0f, 0.0f, 0.0f, 1.0f
244 };
245 GLint location = glGetUniformLocation(sProg, "in_rotationMatrix");
246 glUniformMatrix4fv(location, 1, GL_FALSE, rotation);
247
248 [currentFrameLk unlock];
249 glClearColor(0.0f, 0.0f, 0.0f, 0.1f);
Anthony Léonard14e7bf32017-06-08 08:13:16 -0400250 glClear(GL_COLOR_BUFFER_BIT);
251
252 if([self videoRunning])
253 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
254}
255
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400256-(void)configureTexture:(GLuint)texture index:(int)index
257 uniform:(GLuint)uniform
258 activeTexture:(GLenum)activeTexture
259 format:(GLint)format
260 fullPlane:(BOOL)fullPlane {
261 auto plane = CVPixelBufferGetBaseAddressOfPlane(currentFrame, index);
262 auto width = CVPixelBufferGetWidthOfPlane(currentFrame, index);
263 auto height = CVPixelBufferGetHeightOfPlane(currentFrame, index);
264 auto strideWidth = CVPixelBufferGetBytesPerRowOfPlane(currentFrame, index);
265 strideWidth = fullPlane ? strideWidth : strideWidth * 0.5;
266 if(strideWidth > width) {
267 glPixelStorei(GL_UNPACK_ROW_LENGTH, strideWidth);
268 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
269 } else {
270 glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
271 glPixelStorei(GL_UNPACK_ALIGNMENT, 0);
272 }
273 glActiveTexture(activeTexture);
274 glBindTexture(GL_TEXTURE_2D, texture);
275 glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, plane);
276 glUniform1i(uniform, index);
277}
278
279-(void)fillWithBlack {
Anthony Léonard14e7bf32017-06-08 08:13:16 -0400280 [currentFrameLk lock];
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400281 if(currentFrame) {
282 currentFrame = nullptr;
283 currentFrameDisplayed = YES;
284 }
Anthony Léonard14e7bf32017-06-08 08:13:16 -0400285 [currentFrameLk unlock];
286}
287
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400288-(void)renderWithPixelBuffer:(CVPixelBufferRef)buffer size:(CGSize)size rotation: (float)rotation fillFrame: (bool)fill {
289 [currentFrameLk lock];
290 currentFrame = buffer;
291 CFRetain(currentFrame);
292 currentWidth = size.width;
293 currentHeight = size.height;
294 currentAngle = rotation;
295 currentFrameDisplayed = NO;
296 videoRunning = YES;
297 [currentFrameLk unlock];
298}
299-(void)setupView {
300}
301
Anthony Léonard14e7bf32017-06-08 08:13:16 -0400302@end