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