1 /*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "YUVConverter.h"
18
19 #include "DispatchTables.h"
20
21 #include <assert.h>
22 #include <stdio.h>
23 #include <string.h>
24
25 #define FATAL(fmt,...) do { \
26 fprintf(stderr, "%s: FATAL: " fmt "\n", __func__, ##__VA_ARGS__); \
27 assert(false); \
28 } while(0)
29
getPlanarYUVSizes(int width,int height,FrameworkFormat format,uint32_t * nBytes_out,uint32_t * yStride_out,uint32_t * cStride_out,uint32_t * cHeight_out)30 static void getPlanarYUVSizes(int width, int height,
31 FrameworkFormat format,
32 uint32_t* nBytes_out,
33 uint32_t* yStride_out,
34 uint32_t* cStride_out,
35 uint32_t* cHeight_out) {
36 assert(nBytes_out);
37 assert(yStride_out);
38 assert(cStride_out);
39 assert(cHeight_out);
40
41 uint32_t align;
42 switch (format) {
43 case FRAMEWORK_FORMAT_YV12:
44 align = 16;
45 break;
46 case FRAMEWORK_FORMAT_YUV_420_888:
47 align = 1;
48 break;
49 case FRAMEWORK_FORMAT_GL_COMPATIBLE:
50 FATAL("Input not a YUV format!");
51 }
52
53 // 16-alignment means we need to get the
54 // smallest multiple of 16 pixels that is
55 // greater than or equal to |width| for |yStride| and
56 // greater than or equal to |width / 2| for |cStride|
57 uint32_t yStride = (width + (align - 1)) & ~(align - 1);
58 uint32_t cStride = (yStride / 2 + (align - 1)) & ~(align - 1);
59 uint32_t cHeight = height / 2;
60
61 *nBytes_out = yStride * height + 2 * (cStride * cHeight);
62 *yStride_out = yStride;
63 *cStride_out = cStride;
64 *cHeight_out = cHeight;
65 }
66
67 // getYUVSizes(): given |width| and |height|, return
68 // the total number of bytes of the YUV-formatted buffer
69 // in |nBytes_out|, and store aligned width and C height
70 // in |yStride_out|/|cStride_out| and |cHeight_out|.
getYUVSizes(int width,int height,FrameworkFormat format,uint32_t * nBytes_out,uint32_t * yStride_out,uint32_t * cStride_out,uint32_t * cHeight_out)71 static void getYUVSizes(int width, int height,
72 FrameworkFormat format,
73 uint32_t* nBytes_out,
74 uint32_t* yStride_out,
75 uint32_t* cStride_out,
76 uint32_t* cHeight_out) {
77 switch (format) {
78 case FRAMEWORK_FORMAT_YV12:
79 case FRAMEWORK_FORMAT_YUV_420_888:
80 getPlanarYUVSizes(width, height, format,
81 nBytes_out,
82 yStride_out,
83 cStride_out,
84 cHeight_out);
85 break;
86 case FRAMEWORK_FORMAT_GL_COMPATIBLE:
87 FATAL("Input not a YUV format!");
88 }
89 }
90
getPlanarYUVOffsets(int width,int height,FrameworkFormat format,uint32_t * yoff,uint32_t * uoff,uint32_t * voff,uint32_t * alignwidth,uint32_t * alignwidthc)91 static void getPlanarYUVOffsets(int width, int height, FrameworkFormat format,
92 uint32_t* yoff, uint32_t* uoff, uint32_t* voff,
93 uint32_t* alignwidth, uint32_t* alignwidthc) {
94 uint32_t totalSize, yStride, cStride, cHeight;
95 cStride = 0;
96 yStride = 0;
97 totalSize = 0;
98 getYUVSizes(width, height, format, &totalSize, &yStride, &cStride, &cHeight);
99 int cSize = cStride * height / 2;
100
101 switch (format) {
102 case FRAMEWORK_FORMAT_YV12:
103 // In our use cases (so far), the buffer should
104 // always begin with Y.
105 *yoff = 0;
106 // The U and V planes are switched around so physically
107 // YV12 is more of a "YVU" format
108 *voff = (*yoff) + yStride * height;
109 *uoff = (*voff) + cSize;
110 *alignwidth = yStride;
111 *alignwidthc = cStride;
112 break;
113 case FRAMEWORK_FORMAT_YUV_420_888:
114 *yoff = 0;
115 *uoff = (*yoff) + yStride * height;
116 *voff = (*uoff) + cSize;
117 *alignwidth = yStride;
118 *alignwidthc = cStride;
119 break;
120 case FRAMEWORK_FORMAT_GL_COMPATIBLE:
121 FATAL("Input not a YUV format!");
122 }
123 }
124
125 // getYUVOffsets(), given a YUV-formatted buffer that is arranged
126 // according to the spec
127 // https://developer.android.com/reference/android/graphics/ImageFormat.html#YUV
128 // In particular, Android YUV widths are aligned to 16 pixels.
129 // Inputs:
130 // |yv12|: the YUV-formatted buffer
131 // Outputs:
132 // |yoff|: offset into |yv12| of the start of the Y component
133 // |uoff|: offset into |yv12| of the start of the U component
134 // |voff|: offset into |yv12| of the start of the V component
getYUVOffsets(int width,int height,FrameworkFormat format,uint32_t * yoff,uint32_t * uoff,uint32_t * voff,uint32_t * alignwidth,uint32_t * alignwidthc)135 static void getYUVOffsets(int width, int height, FrameworkFormat format,
136 uint32_t* yoff, uint32_t* uoff, uint32_t* voff,
137 uint32_t* alignwidth, uint32_t* alignwidthc) {
138 switch (format) {
139 case FRAMEWORK_FORMAT_YV12:
140 case FRAMEWORK_FORMAT_YUV_420_888:
141 getPlanarYUVOffsets(width, height, format,
142 yoff, uoff, voff,
143 alignwidth, alignwidthc);
144 break;
145 case FRAMEWORK_FORMAT_GL_COMPATIBLE:
146 FATAL("Input not a YUV format!");
147 }
148 }
149
150 // createYUVGLTex() allocates GPU memory that is enough
151 // to hold the raw data of the YV12 buffer.
152 // The memory is in the form of an OpenGL texture
153 // with one component (GL_LUMINANCE) and
154 // of type GL_UNSIGNED_BYTE.
155 // In order to process all Y, U, V components
156 // simultaneously in conversion, the simple thing to do
157 // is to use multiple texture units, hence
158 // the |texture_unit| argument.
159 // Returns a new OpenGL texture object in |texName_out|
160 // that is to be cleaned up by the caller.
createYUVGLTex(GLenum texture_unit,GLsizei width,GLsizei height,GLuint * texName_out)161 static void createYUVGLTex(GLenum texture_unit,
162 GLsizei width,
163 GLsizei height,
164 GLuint* texName_out) {
165 assert(texName_out);
166
167 s_gles2.glActiveTexture(texture_unit);
168 s_gles2.glGenTextures(1, texName_out);
169 s_gles2.glBindTexture(GL_TEXTURE_2D, *texName_out);
170 s_gles2.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
171 s_gles2.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
172 s_gles2.glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE,
173 width, height, 0,
174 GL_LUMINANCE, GL_UNSIGNED_BYTE,
175 NULL);
176 s_gles2.glActiveTexture(GL_TEXTURE0);
177 }
178
179 // subUpdateYUVGLTex() updates a given YUV texture
180 // at the coordinates (x, y, width, height),
181 // with the raw YUV data in |pixels|.
182 // We cannot view the result properly until
183 // after conversion; this is to be used only
184 // as input to the conversion shader.
subUpdateYUVGLTex(GLenum texture_unit,GLuint tex,int x,int y,int width,int height,void * pixels)185 static void subUpdateYUVGLTex(GLenum texture_unit,
186 GLuint tex,
187 int x, int y, int width, int height,
188 void* pixels) {
189 s_gles2.glActiveTexture(texture_unit);
190 s_gles2.glBindTexture(GL_TEXTURE_2D, tex);
191 s_gles2.glTexSubImage2D(GL_TEXTURE_2D, 0,
192 x, y, width, height,
193 GL_LUMINANCE, GL_UNSIGNED_BYTE,
194 pixels);
195 s_gles2.glActiveTexture(GL_TEXTURE0);
196 }
197
198 // createYUVGLShader() defines the vertex/fragment
199 // shader that does the actual work of converting
200 // YUV to RGB. The resulting shaders and program
201 // are stored in |vshader_out|, |fshader_out|,
202 // and |program_out|.
createYUVGLShader(GLuint * vshader_out,GLuint * fshader_out,GLuint * program_out,GLint * ywidthcutoffloc_out,GLint * cwidthcutoffloc_out,GLint * ysamplerloc_out,GLint * usamplerloc_out,GLint * vsamplerloc_out)203 static void createYUVGLShader(GLuint* vshader_out,
204 GLuint* fshader_out,
205 GLuint* program_out,
206 GLint* ywidthcutoffloc_out,
207 GLint* cwidthcutoffloc_out,
208 GLint* ysamplerloc_out,
209 GLint* usamplerloc_out,
210 GLint* vsamplerloc_out) {
211 assert(vshader_out);
212 assert(fshader_out);
213 assert(program_out);
214
215 static const char kVShader[] = R"(
216 precision highp float;
217 attribute mediump vec4 position;
218 attribute highp vec2 inCoord;
219 varying highp vec2 outCoord;
220 void main(void) {
221 gl_Position = position;
222 outCoord = inCoord;
223 }
224 )";
225 const GLchar* const kVShaders =
226 static_cast<const GLchar*>(kVShader);
227
228 // Based on:
229 // http://stackoverflow.com/questions/11093061/yv12-to-rgb-using-glsl-in-ios-result-image-attached
230 // + account for 16-pixel alignment using |yWidthCutoff| / |cWidthCutoff|
231 // + use conversion matrix in
232 // frameworks/av/media/libstagefright/colorconversion/ColorConverter.cpp (YUV420p)
233 // + more precision from
234 // https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.601_conversion
235 static const char kFShader[] = R"(
236 precision highp float;
237 varying highp vec2 outCoord;
238 uniform highp float yWidthCutoff;
239 uniform highp float cWidthCutoff;
240 uniform sampler2D ysampler;
241 uniform sampler2D usampler;
242 uniform sampler2D vsampler;
243 void main(void) {
244 highp vec2 cutoffCoordsY;
245 highp vec2 cutoffCoordsC;
246 highp vec3 yuv;
247 highp vec3 rgb;
248 cutoffCoordsY.x = outCoord.x * yWidthCutoff;
249 cutoffCoordsY.y = outCoord.y;
250 cutoffCoordsC.x = outCoord.x * cWidthCutoff;
251 cutoffCoordsC.y = outCoord.y;
252 yuv[0] = texture2D(ysampler, cutoffCoordsY).r - 0.0625;
253 yuv[1] = texture2D(usampler, cutoffCoordsC).r - 0.5;
254 yuv[2] = texture2D(vsampler, cutoffCoordsC).r - 0.5;
255 highp float yscale = 1.1643835616438356;
256 rgb = mat3(yscale, yscale, yscale,
257 0, -0.39176229009491365, 2.017232142857143,
258 1.5960267857142856, -0.8129676472377708, 0) * yuv;
259 gl_FragColor = vec4(rgb, 1);
260 }
261 )";
262
263 const GLchar* const kFShaders =
264 static_cast<const GLchar*>(kFShader);
265
266 *vshader_out = s_gles2.glCreateShader(GL_VERTEX_SHADER);
267 *fshader_out = s_gles2.glCreateShader(GL_FRAGMENT_SHADER);
268
269 const GLint vtextLen = strlen(kVShader);
270 const GLint ftextLen = strlen(kFShader);
271 s_gles2.glShaderSource(*vshader_out, 1, &kVShaders, &vtextLen);
272 s_gles2.glShaderSource(*fshader_out, 1, &kFShaders, &ftextLen);
273 s_gles2.glCompileShader(*vshader_out);
274 s_gles2.glCompileShader(*fshader_out);
275
276 *program_out = s_gles2.glCreateProgram();
277 s_gles2.glAttachShader(*program_out, *vshader_out);
278 s_gles2.glAttachShader(*program_out, *fshader_out);
279 s_gles2.glLinkProgram(*program_out);
280
281 s_gles2.glUseProgram(*program_out);
282 *ywidthcutoffloc_out = s_gles2.glGetUniformLocation(*program_out, "yWidthCutoff");
283 *cwidthcutoffloc_out = s_gles2.glGetUniformLocation(*program_out, "cWidthCutoff");
284 *ysamplerloc_out = s_gles2.glGetUniformLocation(*program_out, "ysampler");
285 *usamplerloc_out = s_gles2.glGetUniformLocation(*program_out, "usampler");
286 *vsamplerloc_out = s_gles2.glGetUniformLocation(*program_out, "vsampler");
287 s_gles2.glUseProgram(0);
288 }
289
290 // When converting YUV to RGB with shaders,
291 // we are using the OpenGL graphics pipeline to do compute,
292 // so we need to express the place to store the result
293 // with triangles and draw calls.
294 // createYUVGLFullscreenQuad() defines a fullscreen quad
295 // with position and texture coordinates.
296 // The quad will be textured with the resulting RGB colors,
297 // and we will read back the pixels from the framebuffer
298 // to retrieve our RGB result.
createYUVGLFullscreenQuad(GLuint * vbuf_out,GLuint * ibuf_out,int picture_width,int aligned_width)299 static void createYUVGLFullscreenQuad(GLuint* vbuf_out,
300 GLuint* ibuf_out,
301 int picture_width,
302 int aligned_width) {
303 assert(vbuf_out);
304 assert(ibuf_out);
305
306 s_gles2.glGenBuffers(1, vbuf_out);
307 s_gles2.glGenBuffers(1, ibuf_out);
308
309 static const float kVertices[] = {
310 +1, -1, +0, +1, +0,
311 +1, +1, +0, +1, +1,
312 -1, +1, +0, +0, +1,
313 -1, -1, +0, +0, +0,
314 };
315
316 static const GLubyte kIndices[] = { 0, 1, 2, 2, 3, 0 };
317
318 s_gles2.glBindBuffer(GL_ARRAY_BUFFER, *vbuf_out);
319 s_gles2.glBufferData(GL_ARRAY_BUFFER, sizeof(kVertices), kVertices,
320 GL_STATIC_DRAW);
321 s_gles2.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, *ibuf_out);
322 s_gles2.glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(kIndices), kIndices,
323 GL_STATIC_DRAW);
324 }
325
326 // doYUVConversionDraw() does the actual work of setting up
327 // and submitting draw commands to the GPU.
328 // It uses the textures, shaders, and fullscreen quad defined above
329 // and executes the pipeline on them.
330 // Note, however, that it is up to the caller to dig out
331 // the result of the draw.
doYUVConversionDraw(GLuint program,GLint yWidthCutoffLoc,GLint cWidthCutoffLoc,GLint ySamplerLoc,GLint uSamplerLoc,GLint vSamplerLoc,GLuint vbuf,GLuint ibuf,int width,int ywidth,int halfwidth,int cwidth,float yWidthCutoff,float cWidthCutoff)332 static void doYUVConversionDraw(GLuint program,
333 GLint yWidthCutoffLoc,
334 GLint cWidthCutoffLoc,
335 GLint ySamplerLoc,
336 GLint uSamplerLoc,
337 GLint vSamplerLoc,
338 GLuint vbuf, GLuint ibuf,
339 int width, int ywidth,
340 int halfwidth, int cwidth,
341 float yWidthCutoff,
342 float cWidthCutoff) {
343
344 const GLsizei kVertexAttribStride = 5 * sizeof(GL_FLOAT);
345 const GLvoid* kVertexAttribPosOffset = (GLvoid*)0;
346 const GLvoid* kVertexAttribCoordOffset = (GLvoid*)(3 * sizeof(GL_FLOAT));
347
348 s_gles2.glUseProgram(program);
349
350 GLint posLoc = s_gles2.glGetAttribLocation(program, "position");
351 GLint coordLoc = s_gles2.glGetAttribLocation(program, "inCoord");
352
353 s_gles2.glUniform1f(yWidthCutoffLoc, yWidthCutoff);
354 s_gles2.glUniform1f(cWidthCutoffLoc, cWidthCutoff);
355
356 s_gles2.glUniform1i(ySamplerLoc, 0);
357 s_gles2.glUniform1i(uSamplerLoc, 1);
358 s_gles2.glUniform1i(vSamplerLoc, 2);
359
360 s_gles2.glBindBuffer(GL_ARRAY_BUFFER, vbuf);
361 s_gles2.glEnableVertexAttribArray(posLoc);
362 s_gles2.glEnableVertexAttribArray(coordLoc);
363
364 s_gles2.glVertexAttribPointer(posLoc, 3, GL_FLOAT, false,
365 kVertexAttribStride,
366 kVertexAttribPosOffset);
367 s_gles2.glVertexAttribPointer(coordLoc, 2, GL_FLOAT, false,
368 kVertexAttribStride,
369 kVertexAttribCoordOffset);
370
371 s_gles2.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibuf);
372 s_gles2.glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, 0);
373
374 s_gles2.glDisableVertexAttribArray(posLoc);
375 s_gles2.glDisableVertexAttribArray(coordLoc);
376 }
377
378 // initialize(): allocate GPU memory for YUV components,
379 // and create shaders and vertex data.
YUVConverter(int width,int height,FrameworkFormat format)380 YUVConverter::YUVConverter(int width, int height, FrameworkFormat format) : mFormat(format){
381
382 uint32_t totalSize, yStride, cStride, cHeight;
383 totalSize = 0;
384 getYUVSizes(width, height, mFormat, &totalSize, &yStride, &cStride, &cHeight);
385
386 uint32_t yoff, uoff, voff,
387 ywidth, cwidth, cheight;
388 getYUVOffsets(width, height, mFormat,
389 &yoff, &uoff, &voff,
390 &ywidth, &cwidth);
391 cheight = height / 2;
392
393 createYUVGLTex(GL_TEXTURE0, ywidth, height, &mYtex);
394 createYUVGLTex(GL_TEXTURE1, cwidth, cheight, &mUtex);
395 createYUVGLTex(GL_TEXTURE2, cwidth, cheight, &mVtex);
396
397 createYUVGLShader(&mVshader, &mFshader,
398 &mProgram,
399 &mYWidthCutoffLoc,
400 &mCWidthCutoffLoc,
401 &mYSamplerLoc,
402 &mUSamplerLoc,
403 &mVSamplerLoc);
404
405 createYUVGLFullscreenQuad(&mVbuf, &mIbuf, width, ywidth);
406 }
407
408 // drawConvert: per-frame updates.
409 // Update YUV textures, then draw the fullscreen
410 // quad set up above, which results in a framebuffer
411 // with the RGB colors.
drawConvert(int x,int y,int width,int height,char * pixels)412 void YUVConverter::drawConvert(int x, int y,
413 int width, int height,
414 char* pixels) {
415 s_gles2.glViewport(x, y, width, height);
416
417 uint32_t yoff, uoff, voff,
418 ywidth, cwidth, cheight;
419 getYUVOffsets(width, height, mFormat,
420 &yoff, &uoff, &voff,
421 &ywidth, &cwidth);
422 cheight = height / 2;
423
424 subUpdateYUVGLTex(GL_TEXTURE0, mYtex,
425 x, y, ywidth, height,
426 pixels + yoff);
427 subUpdateYUVGLTex(GL_TEXTURE1, mUtex,
428 x, y, cwidth, cheight,
429 pixels + uoff);
430 subUpdateYUVGLTex(GL_TEXTURE2, mVtex,
431 x, y, cwidth, cheight,
432 pixels + voff);
433
434
435 updateCutoffs(width, ywidth, width / 2, cwidth);
436
437 doYUVConversionDraw(mProgram,
438 mYWidthCutoffLoc,
439 mCWidthCutoffLoc,
440 mYSamplerLoc,
441 mUSamplerLoc,
442 mVSamplerLoc,
443 mVbuf, mIbuf,
444 width, ywidth,
445 width / 2, cwidth,
446 mYWidthCutoff,
447 mCWidthCutoff);
448 }
449
updateCutoffs(float width,float ywidth,float halfwidth,float cwidth)450 void YUVConverter::updateCutoffs(float width, float ywidth,
451 float halfwidth, float cwidth) {
452 switch (mFormat) {
453 case FRAMEWORK_FORMAT_YV12:
454 mYWidthCutoff = ((float)width) / ((float)ywidth);
455 mCWidthCutoff = ((float)halfwidth) / ((float)cwidth);
456 break;
457 case FRAMEWORK_FORMAT_YUV_420_888:
458 mYWidthCutoff = 1.0f;
459 mCWidthCutoff = 1.0f;
460 break;
461 case FRAMEWORK_FORMAT_GL_COMPATIBLE:
462 FATAL("Input not a YUV format!");
463 }
464 }
465
~YUVConverter()466 YUVConverter::~YUVConverter() {
467 if (mIbuf) s_gles2.glDeleteBuffers(1, &mIbuf);
468 if (mVbuf) s_gles2.glDeleteBuffers(1, &mVbuf);
469 if (mProgram) s_gles2.glDeleteProgram(mProgram);
470 if (mFshader) s_gles2.glDeleteShader(mFshader);
471 if (mVshader) s_gles2.glDeleteShader(mVshader);
472 if (mYtex) s_gles2.glDeleteTextures(1, &mYtex);
473 if (mUtex) s_gles2.glDeleteTextures(1, &mUtex);
474 if (mVtex) s_gles2.glDeleteTextures(1, &mVtex);
475 }
476