blob: 3837ff6ea687ae18f7dbae6490b0a5596d662457 [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "YUVConverter.h"
#include <assert.h>
#include <stdio.h>
#include <string>
#include "OpenGLESDispatch/DispatchTables.h"
#include "host-common/feature_control.h"
#include "host-common/opengl/misc.h"
namespace gfxstream {
namespace gl {
#define FATAL(fmt,...) do { \
fprintf(stderr, "%s: FATAL: " fmt "\n", __func__, ##__VA_ARGS__); \
assert(false); \
} while(0)
#define YUV_CONVERTER_DEBUG 0
#if YUV_CONVERTER_DEBUG
#define YUV_DEBUG_LOG(fmt, ...) \
fprintf(stderr, "yuv-converter: %s:%d " fmt "\n", __func__, __LINE__, \
##__VA_ARGS__);
#else
#define YUV_DEBUG_LOG(fmt, ...)
#endif
bool isInterleaved(FrameworkFormat format) {
switch (format) {
case FRAMEWORK_FORMAT_NV12:
case FRAMEWORK_FORMAT_P010:
return true;
case FRAMEWORK_FORMAT_YUV_420_888:
return feature_is_enabled(kFeature_YUV420888toNV21);
case FRAMEWORK_FORMAT_YV12:
return false;
default:
FATAL("Invalid for format:%d", format);
return false;
}
}
enum class YUVInterleaveDirection {
VU = 0,
UV = 1,
};
YUVInterleaveDirection getInterleaveDirection(FrameworkFormat format) {
if (!isInterleaved(format)) {
FATAL("Format:%d not interleaved", format);
}
switch (format) {
case FRAMEWORK_FORMAT_NV12:
case FRAMEWORK_FORMAT_P010:
return YUVInterleaveDirection::UV;
case FRAMEWORK_FORMAT_YUV_420_888:
if (feature_is_enabled(kFeature_YUV420888toNV21)) {
return YUVInterleaveDirection::VU;
}
FATAL("Format:%d not interleaved", format);
return YUVInterleaveDirection::UV;
case FRAMEWORK_FORMAT_YV12:
default:
FATAL("Format:%d not interleaved", format);
return YUVInterleaveDirection::UV;
}
}
GLint getGlTextureFormat(FrameworkFormat format, YUVPlane plane) {
switch (format) {
case FRAMEWORK_FORMAT_YV12:
switch (plane) {
case YUVPlane::Y:
case YUVPlane::U:
case YUVPlane::V:
return GL_R8;
case YUVPlane::UV:
FATAL("Invalid plane:%d for format:%d", plane, format);
return 0;
}
case FRAMEWORK_FORMAT_YUV_420_888:
if (feature_is_enabled(kFeature_YUV420888toNV21)) {
switch (plane) {
case YUVPlane::Y:
return GL_R8;
case YUVPlane::UV:
return GL_RG8;
case YUVPlane::U:
case YUVPlane::V:
FATAL("Invalid plane:%d for format:%d", plane, format);
return 0;
}
} else {
switch (plane) {
case YUVPlane::Y:
case YUVPlane::U:
case YUVPlane::V:
return GL_R8;
case YUVPlane::UV:
FATAL("Invalid plane:%d for format:%d", plane, format);
return 0;
}
}
case FRAMEWORK_FORMAT_NV12:
switch (plane) {
case YUVPlane::Y:
return GL_R8;
case YUVPlane::UV:
return GL_RG8;
case YUVPlane::U:
case YUVPlane::V:
FATAL("Invalid plane:%d for format:%d", plane, format);
return 0;
}
case FRAMEWORK_FORMAT_P010:
switch (plane) {
case YUVPlane::Y:
return GL_R16UI;
case YUVPlane::UV:
return GL_RG16UI;
case YUVPlane::U:
case YUVPlane::V:
FATAL("Invalid plane:%d for format:%d", plane, format);
return 0;
}
default:
FATAL("Invalid format:%d", format);
return 0;
}
}
GLenum getGlPixelFormat(FrameworkFormat format, YUVPlane plane) {
switch (format) {
case FRAMEWORK_FORMAT_YV12:
switch (plane) {
case YUVPlane::Y:
case YUVPlane::U:
case YUVPlane::V:
return GL_RED;
case YUVPlane::UV:
FATAL("Invalid plane:%d for format:%d", plane, format);
return 0;
}
case FRAMEWORK_FORMAT_YUV_420_888:
if (feature_is_enabled(kFeature_YUV420888toNV21)) {
switch (plane) {
case YUVPlane::Y:
return GL_RED;
case YUVPlane::UV:
return GL_RG;
case YUVPlane::U:
case YUVPlane::V:
FATAL("Invalid plane:%d for format:%d", plane, format);
return 0;
}
} else {
switch (plane) {
case YUVPlane::Y:
case YUVPlane::U:
case YUVPlane::V:
return GL_RED;
case YUVPlane::UV:
FATAL("Invalid plane:%d for format:%d", plane, format);
return 0;
}
}
case FRAMEWORK_FORMAT_NV12:
switch (plane) {
case YUVPlane::Y:
return GL_RED;
case YUVPlane::UV:
return GL_RG;
case YUVPlane::U:
case YUVPlane::V:
FATAL("Invalid plane:%d for format:%d", plane, format);
return 0;
}
case FRAMEWORK_FORMAT_P010:
switch (plane) {
case YUVPlane::Y:
return GL_RED_INTEGER;
case YUVPlane::UV:
return GL_RG_INTEGER;
case YUVPlane::U:
case YUVPlane::V:
FATAL("Invalid plane:%d for format:%d", plane, format);
return 0;
}
default:
FATAL("Invalid format:%d", format);
return 0;
}
}
GLsizei getGlPixelType(FrameworkFormat format, YUVPlane plane) {
switch (format) {
case FRAMEWORK_FORMAT_YV12:
switch (plane) {
case YUVPlane::Y:
case YUVPlane::U:
case YUVPlane::V:
return GL_UNSIGNED_BYTE;
case YUVPlane::UV:
FATAL("Invalid plane:%d for format:%d", plane, format);
return 0;
}
case FRAMEWORK_FORMAT_YUV_420_888:
if (feature_is_enabled(kFeature_YUV420888toNV21)) {
switch (plane) {
case YUVPlane::Y:
case YUVPlane::UV:
return GL_UNSIGNED_BYTE;
case YUVPlane::U:
case YUVPlane::V:
FATAL("Invalid plane:%d for format:%d", plane, format);
return 0;
}
} else {
switch (plane) {
case YUVPlane::Y:
case YUVPlane::U:
case YUVPlane::V:
return GL_UNSIGNED_BYTE;
case YUVPlane::UV:
FATAL("Invalid plane:%d for format:%d", plane, format);
return 0;
}
}
case FRAMEWORK_FORMAT_NV12:
switch (plane) {
case YUVPlane::Y:
case YUVPlane::UV:
return GL_UNSIGNED_BYTE;
case YUVPlane::U:
case YUVPlane::V:
FATAL("Invalid plane:%d for format:%d", plane, format);
return 0;
}
case FRAMEWORK_FORMAT_P010:
switch (plane) {
case YUVPlane::Y:
case YUVPlane::UV:
return GL_UNSIGNED_SHORT;
case YUVPlane::U:
case YUVPlane::V:
FATAL("Invalid plane:%d for format:%d", plane, format);
return 0;
}
default:
FATAL("Invalid format:%d", format);
return 0;
}
}
// NV12 and YUV420 are all packed
static void NV12ToYUV420PlanarInPlaceConvert(int nWidth,
int nHeight,
uint8_t* pFrame,
uint8_t* pQuad) {
std::vector<uint8_t> tmp;
if (pQuad == nullptr) {
tmp.resize(nWidth * nHeight / 4);
pQuad = tmp.data();
}
int nPitch = nWidth;
uint8_t *puv = pFrame + nPitch * nHeight, *pu = puv,
*pv = puv + nPitch * nHeight / 4;
for (int y = 0; y < nHeight / 2; y++) {
for (int x = 0; x < nWidth / 2; x++) {
pu[y * nPitch / 2 + x] = puv[y * nPitch + x * 2];
pQuad[y * nWidth / 2 + x] = puv[y * nPitch + x * 2 + 1];
}
}
memcpy(pv, pQuad, nWidth * nHeight / 4);
}
inline uint32_t alignToPower2(uint32_t val, uint32_t align) {
return (val + (align - 1)) & ~(align - 1);
}
// getYUVOffsets(), given a YUV-formatted buffer that is arranged
// according to the spec
// https://developer.android.com/reference/android/graphics/ImageFormat.html#YUV
// In particular, Android YUV widths are aligned to 16 pixels.
// Inputs:
// |yv12|: the YUV-formatted buffer
// Outputs:
// |yOffsetBytes|: offset into |yv12| of the start of the Y component
// |uOffsetBytes|: offset into |yv12| of the start of the U component
// |vOffsetBytes|: offset into |yv12| of the start of the V component
static void getYUVOffsets(int width,
int height,
FrameworkFormat format,
uint32_t* yWidth,
uint32_t* yHeight,
uint32_t* yOffsetBytes,
uint32_t* yStridePixels,
uint32_t* yStrideBytes,
uint32_t* uWidth,
uint32_t* uHeight,
uint32_t* uOffsetBytes,
uint32_t* uStridePixels,
uint32_t* uStrideBytes,
uint32_t* vWidth,
uint32_t* vHeight,
uint32_t* vOffsetBytes,
uint32_t* vStridePixels,
uint32_t* vStrideBytes) {
switch (format) {
case FRAMEWORK_FORMAT_YV12: {
*yWidth = width;
*yHeight = height;
*yOffsetBytes = 0;
// Luma stride is 32 bytes aligned in minigbm, 16 in goldfish
// gralloc.
*yStridePixels = alignToPower2(width, emugl::getGrallocImplementation() == MINIGBM
? 32 : 16);
*yStrideBytes = *yStridePixels;
// Chroma stride is 16 bytes aligned.
*vWidth = width / 2;
*vHeight = height / 2;
*vOffsetBytes = (*yStrideBytes) * (*yHeight);
*vStridePixels = alignToPower2((*yStridePixels) / 2, 16);
*vStrideBytes = (*vStridePixels);
*uWidth = width / 2;
*uHeight = height / 2;
*uOffsetBytes = (*vOffsetBytes) + ((*vStrideBytes) * (*vHeight));
*uStridePixels = alignToPower2((*yStridePixels) / 2, 16);
*uStrideBytes = *uStridePixels;
break;
}
case FRAMEWORK_FORMAT_YUV_420_888: {
if (feature_is_enabled(kFeature_YUV420888toNV21)) {
*yWidth = width;
*yHeight = height;
*yOffsetBytes = 0;
*yStridePixels = width;
*yStrideBytes = *yStridePixels;
*vWidth = width / 2;
*vHeight = height / 2;
*vOffsetBytes = (*yStrideBytes) * (*yHeight);
*vStridePixels = (*yStridePixels) / 2;
*vStrideBytes = (*vStridePixels);
*uWidth = width / 2;
*uHeight = height / 2;
*uOffsetBytes = (*vOffsetBytes) + 1;
*uStridePixels = (*yStridePixels) / 2;
*uStrideBytes = *uStridePixels;
} else {
*yWidth = width;
*yHeight = height;
*yOffsetBytes = 0;
*yStridePixels = width;
*yStrideBytes = *yStridePixels;
*uWidth = width / 2;
*uHeight = height / 2;
*uOffsetBytes = (*yStrideBytes) * (*yHeight);
*uStridePixels = (*yStridePixels) / 2;
*uStrideBytes = *uStridePixels;
*vWidth = width / 2;
*vHeight = height / 2;
*vOffsetBytes = (*uOffsetBytes) + ((*uStrideBytes) * (*uHeight));
*vStridePixels = (*yStridePixels) / 2;
*vStrideBytes = (*vStridePixels);
}
break;
}
case FRAMEWORK_FORMAT_NV12: {
*yWidth = width;
*yHeight = height;
*yOffsetBytes = 0;
*yStridePixels = width;
*yStrideBytes = *yStridePixels;
*uWidth = width / 2;
*uHeight = height / 2;
*uOffsetBytes = (*yStrideBytes) * (*yHeight);
*uStridePixels = (*yStridePixels) / 2;
*uStrideBytes = *uStridePixels;
*vWidth = width / 2;
*vHeight = height / 2;
*vOffsetBytes = (*uOffsetBytes) + 1;
*vStridePixels = (*yStridePixels) / 2;
*vStrideBytes = (*vStridePixels);
break;
}
case FRAMEWORK_FORMAT_P010: {
*yWidth = width;
*yHeight = height;
*yOffsetBytes = 0;
*yStridePixels = width;
*yStrideBytes = (*yStridePixels) * /*bytes per pixel=*/2;
*uWidth = width / 2;
*uHeight = height / 2;
*uOffsetBytes = (*yStrideBytes) * (*yHeight);
*uStridePixels = (*uWidth);
*uStrideBytes = *uStridePixels * /*bytes per pixel=*/2;
*vWidth = width / 2;
*vHeight = height / 2;
*vOffsetBytes = (*uOffsetBytes) + 2;
*vStridePixels = (*vWidth);
*vStrideBytes = (*vStridePixels) * /*bytes per pixel=*/2;
break;
}
case FRAMEWORK_FORMAT_GL_COMPATIBLE: {
FATAL("Input not a YUV format! (FRAMEWORK_FORMAT_GL_COMPATIBLE)");
}
default: {
FATAL("Unknown format: 0x%x", format);
}
}
}
// Allocates an OpenGL texture that is large enough for a single plane of
// a YUV buffer of the given format and returns the texture name in the
// `outTextureName` argument.
void YUVConverter::createYUVGLTex(GLenum textureUnit,
GLsizei width,
GLsizei height,
FrameworkFormat format,
YUVPlane plane,
GLuint* outTextureName) {
YUV_DEBUG_LOG("w:%d h:%d format:%d plane:%d", width, height, format, plane);
s_gles2.glActiveTexture(textureUnit);
s_gles2.glGenTextures(1, outTextureName);
s_gles2.glBindTexture(GL_TEXTURE_2D, *outTextureName);
s_gles2.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
s_gles2.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
GLint unprevAlignment = 0;
s_gles2.glGetIntegerv(GL_UNPACK_ALIGNMENT, &unprevAlignment);
s_gles2.glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
const GLint textureFormat = getGlTextureFormat(format, plane);
const GLenum pixelFormat = getGlPixelFormat(format, plane);
const GLenum pixelType = getGlPixelType(format, plane);
s_gles2.glTexImage2D(GL_TEXTURE_2D, 0, textureFormat, width, height, 0, pixelFormat, pixelType, NULL);
s_gles2.glPixelStorei(GL_UNPACK_ALIGNMENT, unprevAlignment);
s_gles2.glActiveTexture(GL_TEXTURE0);
}
static void readYUVTex(GLuint tex, FrameworkFormat format, YUVPlane plane, void* pixels,
uint32_t pixelsStride) {
YUV_DEBUG_LOG("format%d plane:%d pixels:%p", format, plane, pixels);
GLuint prevTexture = 0;
s_gles2.glGetIntegerv(GL_TEXTURE_BINDING_2D, (GLint*)&prevTexture);
s_gles2.glBindTexture(GL_TEXTURE_2D, tex);
GLint prevAlignment = 0;
s_gles2.glGetIntegerv(GL_PACK_ALIGNMENT, &prevAlignment);
s_gles2.glPixelStorei(GL_PACK_ALIGNMENT, 1);
GLint prevStride = 0;
s_gles2.glGetIntegerv(GL_PACK_ROW_LENGTH, &prevStride);
s_gles2.glPixelStorei(GL_PACK_ROW_LENGTH, pixelsStride);
const GLenum pixelFormat = getGlPixelFormat(format, plane);
const GLenum pixelType = getGlPixelType(format, plane);
if (s_gles2.glGetTexImage) {
s_gles2.glGetTexImage(GL_TEXTURE_2D, 0, pixelFormat, pixelType, pixels);
} else {
YUV_DEBUG_LOG("empty glGetTexImage");
}
s_gles2.glPixelStorei(GL_PACK_ROW_LENGTH, prevStride);
s_gles2.glPixelStorei(GL_PACK_ALIGNMENT, prevAlignment);
s_gles2.glBindTexture(GL_TEXTURE_2D, prevTexture);
}
// Updates a given YUV buffer's plane texture at the coordinates
// (x, y, width, height), with the raw YUV data in |pixels|. We
// cannot view the result properly until after conversion; this is
// to be used only as input to the conversion shader.
static void subUpdateYUVGLTex(GLenum texture_unit,
GLuint tex,
int x,
int y,
int width,
int height,
FrameworkFormat format,
YUVPlane plane,
const void* pixels) {
YUV_DEBUG_LOG("x:%d y:%d w:%d h:%d format:%d plane:%d", x, y, width, height, format, plane);
const GLenum pixelFormat = getGlPixelFormat(format, plane);
const GLenum pixelType = getGlPixelType(format, plane);
s_gles2.glActiveTexture(texture_unit);
s_gles2.glBindTexture(GL_TEXTURE_2D, tex);
GLint unprevAlignment = 0;
s_gles2.glGetIntegerv(GL_UNPACK_ALIGNMENT, &unprevAlignment);
s_gles2.glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
s_gles2.glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, pixelFormat, pixelType, pixels);
s_gles2.glPixelStorei(GL_UNPACK_ALIGNMENT, unprevAlignment);
s_gles2.glActiveTexture(GL_TEXTURE0);
}
void YUVConverter::createYUVGLShader() {
YUV_DEBUG_LOG("format:%d", mFormat);
// P010 needs uint samplers.
if (mFormat == FRAMEWORK_FORMAT_P010 && !mHasGlsl3Support) {
return;
}
static const char kVertShader[] = R"(
precision highp float;
attribute mediump vec4 aPosition;
attribute highp vec2 aTexCoord;
varying highp vec2 vTexCoord;
void main(void) {
gl_Position = aPosition;
vTexCoord = aTexCoord;
}
)";
static const char kFragShaderVersion3[] = R"(#version 300 es)";
static const char kFragShaderBegin[] = R"(
precision highp float;
varying highp vec2 vTexCoord;
uniform highp float uYWidthCutoff;
uniform highp float uUVWidthCutoff;
)";
static const char kSamplerUniforms[] = R"(
uniform sampler2D uSamplerY;
uniform sampler2D uSamplerU;
uniform sampler2D uSamplerV;
)";
static const char kSamplerUniformsUint[] = R"(
uniform usampler2D uSamplerY;
uniform usampler2D uSamplerU;
uniform usampler2D uSamplerV;
)";
static const char kFragShaderMainBegin[] = R"(
void main(void) {
highp vec2 yTexCoords = vTexCoord;
highp vec2 uvTexCoords = vTexCoord;
// For textures with extra padding for alignment (e.g. YV12 pads to 16),
// scale the coordinates to only sample from the non-padded area.
yTexCoords.x *= uYWidthCutoff;
uvTexCoords.x *= uUVWidthCutoff;
highp vec3 yuv;
)";
static const char kSampleY[] = R"(
yuv[0] = texture2D(uSamplerY, yTexCoords).r;
)";
static const char kSampleUV[] = R"(
yuv[1] = texture2D(uSamplerU, uvTexCoords).r;
yuv[2] = texture2D(uSamplerV, uvTexCoords).r;
)";
static const char kSampleInterleavedUV[] = R"(
// Note: uSamplerU and vSamplerV refer to the same texture.
yuv[1] = texture2D(uSamplerU, uvTexCoords).r;
yuv[2] = texture2D(uSamplerV, uvTexCoords).g;
)";
static const char kSampleInterleavedVU[] = R"(
// Note: uSamplerU and vSamplerV refer to the same texture.
yuv[1] = texture2D(uSamplerU, uvTexCoords).g;
yuv[2] = texture2D(uSamplerV, uvTexCoords).r;
)";
static const char kSampleP010[] = R"(
uint yRaw = texture(uSamplerY, yTexCoords).r;
uint uRaw = texture(uSamplerU, uvTexCoords).r;
uint vRaw = texture(uSamplerV, uvTexCoords).g;
// P010 values are stored in the upper 10-bits of 16-bit unsigned shorts.
yuv[0] = float(yRaw >> 6) / 1023.0;
yuv[1] = float(uRaw >> 6) / 1023.0;
yuv[2] = float(vRaw >> 6) / 1023.0;
)";
static const char kFragShaderMainEnd[] = R"(
yuv[0] = yuv[0] - 0.0625;
yuv[1] = 0.96 * (yuv[1] - 0.5);
yuv[2] = (yuv[2] - 0.5);
highp float yscale = 1.1643835616438356;
highp vec3 rgb = mat3( yscale, yscale, yscale,
0, -0.39176229009491365, 2.017232142857143,
1.5960267857142856, -0.8129676472377708, 0) * yuv;
gl_FragColor = vec4(rgb, 1.0);
}
)";
std::string vertShaderSource(kVertShader);
std::string fragShaderSource;
if (mFormat == FRAMEWORK_FORMAT_P010) {
fragShaderSource += kFragShaderVersion3;
}
fragShaderSource += kFragShaderBegin;
if (mFormat == FRAMEWORK_FORMAT_P010) {
fragShaderSource += kSamplerUniformsUint;
} else {
fragShaderSource += kSamplerUniforms;
}
fragShaderSource += kFragShaderMainBegin;
switch (mFormat) {
case FRAMEWORK_FORMAT_NV12:
case FRAMEWORK_FORMAT_YUV_420_888:
case FRAMEWORK_FORMAT_YV12:
fragShaderSource += kSampleY;
if (isInterleaved(mFormat)) {
if (getInterleaveDirection(mFormat) == YUVInterleaveDirection::UV) {
fragShaderSource += kSampleInterleavedUV;
} else {
fragShaderSource += kSampleInterleavedVU;
}
} else {
fragShaderSource += kSampleUV;
}
break;
case FRAMEWORK_FORMAT_P010:
fragShaderSource += kSampleP010;
break;
default:
FATAL("%s: invalid format:%d", __FUNCTION__, mFormat);
return;
}
fragShaderSource += kFragShaderMainEnd;
YUV_DEBUG_LOG("format:%d vert-source:%s frag-source:%s", mFormat, vertShaderSource.c_str(), fragShaderSource.c_str());
const GLchar* const vertShaderSourceChars = vertShaderSource.c_str();
const GLchar* const fragShaderSourceChars = fragShaderSource.c_str();
const GLint vertShaderSourceLen = vertShaderSource.length();
const GLint fragShaderSourceLen = fragShaderSource.length();
GLuint vertShader = s_gles2.glCreateShader(GL_VERTEX_SHADER);
GLuint fragShader = s_gles2.glCreateShader(GL_FRAGMENT_SHADER);
s_gles2.glShaderSource(vertShader, 1, &vertShaderSourceChars, &vertShaderSourceLen);
s_gles2.glShaderSource(fragShader, 1, &fragShaderSourceChars, &fragShaderSourceLen);
s_gles2.glCompileShader(vertShader);
s_gles2.glCompileShader(fragShader);
for (GLuint shader : {vertShader, fragShader}) {
GLint status = GL_FALSE;
s_gles2.glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
if (status == GL_FALSE) {
GLchar error[1024];
s_gles2.glGetShaderInfoLog(shader, sizeof(error), nullptr, &error[0]);
FATAL("Failed to compile YUV conversion shader: %s", error);
s_gles2.glDeleteShader(shader);
return;
}
}
mProgram = s_gles2.glCreateProgram();
s_gles2.glAttachShader(mProgram, vertShader);
s_gles2.glAttachShader(mProgram, fragShader);
s_gles2.glLinkProgram(mProgram);
GLint status = GL_FALSE;
s_gles2.glGetProgramiv(mProgram, GL_LINK_STATUS, &status);
if (status == GL_FALSE) {
GLchar error[1024];
s_gles2.glGetProgramInfoLog(mProgram, sizeof(error), 0, &error[0]);
FATAL("Failed to link YUV conversion program: %s", error);
s_gles2.glDeleteProgram(mProgram);
mProgram = 0;
return;
}
mUniformLocYWidthCutoff = s_gles2.glGetUniformLocation(mProgram, "uYWidthCutoff");
mUniformLocUVWidthCutoff = s_gles2.glGetUniformLocation(mProgram, "uUVWidthCutoff");
mUniformLocSamplerY = s_gles2.glGetUniformLocation(mProgram, "uSamplerY");
mUniformLocSamplerU = s_gles2.glGetUniformLocation(mProgram, "uSamplerU");
mUniformLocSamplerV = s_gles2.glGetUniformLocation(mProgram, "uSamplerV");
mAttributeLocPos = s_gles2.glGetAttribLocation(mProgram, "aPosition");
mAttributeLocTexCoord = s_gles2.glGetAttribLocation(mProgram, "aTexCoord");
s_gles2.glDeleteShader(vertShader);
s_gles2.glDeleteShader(fragShader);
}
void YUVConverter::createYUVGLFullscreenQuad() {
s_gles2.glGenBuffers(1, &mQuadVertexBuffer);
s_gles2.glGenBuffers(1, &mQuadIndexBuffer);
static const float kVertices[] = {
+1, -1, +0, +1, +0,
+1, +1, +0, +1, +1,
-1, +1, +0, +0, +1,
-1, -1, +0, +0, +0,
};
static const GLubyte kIndices[] = { 0, 1, 2, 2, 3, 0 };
s_gles2.glBindBuffer(GL_ARRAY_BUFFER, mQuadVertexBuffer);
s_gles2.glBufferData(GL_ARRAY_BUFFER, sizeof(kVertices), kVertices, GL_STATIC_DRAW);
s_gles2.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mQuadIndexBuffer);
s_gles2.glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(kIndices), kIndices, GL_STATIC_DRAW);
}
static void doYUVConversionDraw(GLuint program,
GLint uniformLocYWidthCutoff,
GLint uniformLocUVWidthCutoff,
GLint uniformLocYSampler,
GLint uniformLocUSampler,
GLint uniformLocVSampler,
GLint attributeLocTexCoord,
GLint attributeLocPos,
GLuint quadVertexBuffer,
GLuint quadIndexBuffer,
float uYWidthCutoff,
float uUVWidthCutoff) {
const GLsizei kVertexAttribStride = 5 * sizeof(GL_FLOAT);
const GLvoid* kVertexAttribPosOffset = (GLvoid*)0;
const GLvoid* kVertexAttribCoordOffset = (GLvoid*)(3 * sizeof(GL_FLOAT));
s_gles2.glUseProgram(program);
s_gles2.glUniform1f(uniformLocYWidthCutoff, uYWidthCutoff);
s_gles2.glUniform1f(uniformLocUVWidthCutoff, uUVWidthCutoff);
s_gles2.glUniform1i(uniformLocYSampler, 0);
s_gles2.glUniform1i(uniformLocUSampler, 1);
s_gles2.glUniform1i(uniformLocVSampler, 2);
s_gles2.glBindBuffer(GL_ARRAY_BUFFER, quadVertexBuffer);
s_gles2.glEnableVertexAttribArray(attributeLocPos);
s_gles2.glEnableVertexAttribArray(attributeLocTexCoord);
s_gles2.glVertexAttribPointer(attributeLocPos, 3, GL_FLOAT, false,
kVertexAttribStride,
kVertexAttribPosOffset);
s_gles2.glVertexAttribPointer(attributeLocTexCoord, 2, GL_FLOAT, false,
kVertexAttribStride,
kVertexAttribCoordOffset);
s_gles2.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, quadIndexBuffer);
s_gles2.glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, 0);
s_gles2.glDisableVertexAttribArray(attributeLocPos);
s_gles2.glDisableVertexAttribArray(attributeLocTexCoord);
}
// initialize(): allocate GPU memory for YUV components,
// and create shaders and vertex data.
YUVConverter::YUVConverter(int width, int height, FrameworkFormat format)
: mWidth(width),
mHeight(height),
mFormat(format),
mColorBufferFormat(format) {}
void YUVConverter::init(int width, int height, FrameworkFormat format) {
YUV_DEBUG_LOG("w:%d h:%d format:%d", width, height, format);
uint32_t yWidth, yHeight = 0, yOffsetBytes, yStridePixels = 0, yStrideBytes;
uint32_t uWidth, uHeight = 0, uOffsetBytes, uStridePixels = 0, uStrideBytes;
uint32_t vWidth, vHeight = 0, vOffsetBytes, vStridePixels = 0, vStrideBytes;
getYUVOffsets(width, height, mFormat,
&yWidth, &yHeight, &yOffsetBytes, &yStridePixels, &yStrideBytes,
&uWidth, &uHeight, &uOffsetBytes, &uStridePixels, &uStrideBytes,
&vWidth, &vHeight, &vOffsetBytes, &vStridePixels, &vStrideBytes);
mWidth = width;
mHeight = height;
if (!mTextureY) {
createYUVGLTex(GL_TEXTURE0, yStridePixels, yHeight, mFormat, YUVPlane::Y, &mTextureY);
}
if (isInterleaved(mFormat)) {
if (!mTextureU) {
createYUVGLTex(GL_TEXTURE1, uStridePixels, uHeight, mFormat, YUVPlane::UV, &mTextureU);
mTextureV = mTextureU;
}
} else {
if (!mTextureU) {
createYUVGLTex(GL_TEXTURE1, uStridePixels, uHeight, mFormat, YUVPlane::U, &mTextureU);
}
if (!mTextureV) {
createYUVGLTex(GL_TEXTURE2, vStridePixels, vHeight, mFormat, YUVPlane::V, &mTextureV);
}
}
int glesMajor;
int glesMinor;
emugl::getGlesVersion(&glesMajor, &glesMinor);
mHasGlsl3Support = glesMajor >= 3;
YUV_DEBUG_LOG("YUVConverter has GLSL ES 3 support:%s (major:%d minor:%d", (mHasGlsl3Support ? "yes" : "no"), glesMajor, glesMinor);
createYUVGLShader();
createYUVGLFullscreenQuad();
}
void YUVConverter::saveGLState() {
s_gles2.glGetFloatv(GL_VIEWPORT, mCurrViewport);
s_gles2.glGetIntegerv(GL_ACTIVE_TEXTURE, &mCurrTexUnit);
s_gles2.glGetIntegerv(GL_TEXTURE_BINDING_2D, &mCurrTexBind);
s_gles2.glGetIntegerv(GL_CURRENT_PROGRAM, &mCurrProgram);
s_gles2.glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &mCurrVbo);
s_gles2.glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &mCurrIbo);
}
void YUVConverter::restoreGLState() {
s_gles2.glViewport(mCurrViewport[0], mCurrViewport[1],
mCurrViewport[2], mCurrViewport[3]);
s_gles2.glActiveTexture(mCurrTexUnit);
s_gles2.glUseProgram(mCurrProgram);
s_gles2.glBindBuffer(GL_ARRAY_BUFFER, mCurrVbo);
s_gles2.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mCurrIbo);
}
uint32_t YUVConverter::getDataSize() {
uint32_t align = (mFormat == FRAMEWORK_FORMAT_YV12) ? 16 : 1;
uint32_t yStrideBytes = (mWidth + (align - 1)) & ~(align - 1);
uint32_t uvStride = (yStrideBytes / 2 + (align - 1)) & ~(align - 1);
uint32_t uvHeight = mHeight / 2;
uint32_t dataSize = yStrideBytes * mHeight + 2 * (uvHeight * uvStride);
YUV_DEBUG_LOG("w:%d h:%d format:%d has data size:%d", mWidth, mHeight, mFormat, dataSize);
return dataSize;
}
void YUVConverter::readPixels(uint8_t* pixels, uint32_t pixels_size) {
YUV_DEBUG_LOG("w:%d h:%d format:%d pixels:%p pixels-size:%d", mWidth, mHeight, mFormat, pixels, pixels_size);
uint32_t yWidth, yHeight, yOffsetBytes, yStridePixels, yStrideBytes;
uint32_t uWidth, uHeight, uOffsetBytes, uStridePixels, uStrideBytes;
uint32_t vWidth, vHeight, vOffsetBytes, vStridePixels, vStrideBytes;
getYUVOffsets(mWidth, mHeight, mFormat,
&yWidth, &yHeight, &yOffsetBytes, &yStridePixels, &yStrideBytes,
&uWidth, &uHeight, &uOffsetBytes, &uStridePixels, &uStrideBytes,
&vWidth, &vHeight, &vOffsetBytes, &vStridePixels, &vStrideBytes);
if (isInterleaved(mFormat)) {
readYUVTex(mTextureV, mFormat, YUVPlane::UV, pixels + std::min(uOffsetBytes, vOffsetBytes),
uStridePixels);
} else {
readYUVTex(mTextureU, mFormat, YUVPlane::U, pixels + uOffsetBytes, uStridePixels);
readYUVTex(mTextureV, mFormat, YUVPlane::V, pixels + vOffsetBytes, vStridePixels);
}
if (mFormat == FRAMEWORK_FORMAT_NV12 && mColorBufferFormat == FRAMEWORK_FORMAT_YUV_420_888) {
NV12ToYUV420PlanarInPlaceConvert(mWidth, mHeight, pixels, pixels);
}
// Read the Y plane last because so that we can use it as a scratch space.
readYUVTex(mTextureY, mFormat, YUVPlane::Y, pixels + yOffsetBytes, yStridePixels);
}
void YUVConverter::swapTextures(FrameworkFormat format, GLuint* textures) {
if (isInterleaved(format)) {
std::swap(textures[0], mTextureY);
std::swap(textures[1], mTextureU);
mTextureV = mTextureU;
} else {
std::swap(textures[0], mTextureY);
std::swap(textures[1], mTextureU);
std::swap(textures[2], mTextureV);
}
mFormat = format;
mTexturesSwapped = true;
}
// drawConvert: per-frame updates.
// Update YUV textures, then draw the fullscreen
// quad set up above, which results in a framebuffer
// with the RGB colors.
void YUVConverter::drawConvert(int x, int y, int width, int height, const char* pixels) {
drawConvertFromFormat(mFormat, x, y, width, height, pixels);
}
void YUVConverter::drawConvertFromFormat(FrameworkFormat format, int x, int y, int width,
int height, const char* pixels) {
saveGLState();
if (pixels && (width != mWidth || height != mHeight)) {
reset();
}
bool uploadFormatChanged = !mTexturesSwapped && pixels && (format != mFormat);
bool initNeeded = (mProgram == 0) || uploadFormatChanged;
if (initNeeded) {
if (uploadFormatChanged) {
mFormat = format;
// TODO: missing cherry-picks, put it back
// b/264928117
//mCbFormat = format;
reset();
}
init(width, height, mFormat);
}
if (mFormat == FRAMEWORK_FORMAT_P010 && !mHasGlsl3Support) {
// TODO: perhaps fallback to just software conversion.
return;
}
uint32_t yWidth = 0, yHeight = 0, yOffsetBytes, yStridePixels = 0, yStrideBytes;
uint32_t uWidth = 0, uHeight = 0, uOffsetBytes, uStridePixels = 0, uStrideBytes;
uint32_t vWidth = 0, vHeight = 0, vOffsetBytes, vStridePixels = 0, vStrideBytes;
getYUVOffsets(width, height, mFormat,
&yWidth, &yHeight, &yOffsetBytes, &yStridePixels, &yStrideBytes,
&uWidth, &uHeight, &uOffsetBytes, &uStridePixels, &uStrideBytes,
&vWidth, &vHeight, &vOffsetBytes, &vStridePixels, &vStrideBytes);
YUV_DEBUG_LOG("Updating YUV textures for drawConvert() "
"x:%d y:%d width:%d height:%d "
"yWidth:%d yHeight:%d yOffsetBytes:%d yStridePixels:%d yStrideBytes:%d "
"uWidth:%d uHeight:%d uOffsetBytes:%d uStridePixels:%d uStrideBytes:%d "
"vWidth:%d vHeight:%d vOffsetBytes:%d vStridePixels:%d vStrideBytes:%d ",
x, y, width, height,
yWidth, yHeight, yOffsetBytes, yStridePixels, yStrideBytes,
uWidth, uHeight, uOffsetBytes, uStridePixels, uStrideBytes,
vWidth, vHeight, vOffsetBytes, vStridePixels, vStrideBytes);
s_gles2.glViewport(x, y, width, height);
updateCutoffs(static_cast<float>(yWidth),
static_cast<float>(yStridePixels),
static_cast<float>(uWidth),
static_cast<float>(uStridePixels));
if (pixels) {
subUpdateYUVGLTex(GL_TEXTURE0, mTextureY, x, y, yStridePixels, yHeight, mFormat, YUVPlane::Y, pixels + yOffsetBytes);
if (isInterleaved(mFormat)) {
subUpdateYUVGLTex(GL_TEXTURE1, mTextureU, x, y, uStridePixels, uHeight, mFormat, YUVPlane::UV, pixels + std::min(uOffsetBytes, vOffsetBytes));
} else {
subUpdateYUVGLTex(GL_TEXTURE1, mTextureU, x, y, uStridePixels, uHeight, mFormat, YUVPlane::U, pixels + uOffsetBytes);
subUpdateYUVGLTex(GL_TEXTURE2, mTextureV, x, y, vStridePixels, vHeight, mFormat, YUVPlane::V, pixels + vOffsetBytes);
}
} else {
// special case: draw from texture, only support NV12 for now
// as cuvid's native format is NV12.
// TODO: add more formats if there are such needs in the future.
assert(mFormat == FRAMEWORK_FORMAT_NV12);
}
s_gles2.glActiveTexture(GL_TEXTURE0);
s_gles2.glBindTexture(GL_TEXTURE_2D, mTextureY);
s_gles2.glActiveTexture(GL_TEXTURE1);
s_gles2.glBindTexture(GL_TEXTURE_2D, mTextureU);
s_gles2.glActiveTexture(GL_TEXTURE2);
s_gles2.glBindTexture(GL_TEXTURE_2D, mTextureV);
doYUVConversionDraw(mProgram,
mUniformLocYWidthCutoff,
mUniformLocUVWidthCutoff,
mUniformLocSamplerY,
mUniformLocSamplerU,
mUniformLocSamplerV,
mAttributeLocTexCoord,
mAttributeLocPos,
mQuadVertexBuffer,
mQuadIndexBuffer,
mYWidthCutoff,
mUVWidthCutoff);
restoreGLState();
}
void YUVConverter::updateCutoffs(float yWidth, float yStridePixels,
float uvWidth, float uvStridePixels) {
switch (mFormat) {
case FRAMEWORK_FORMAT_YV12:
mYWidthCutoff = yWidth / yStridePixels;
mUVWidthCutoff = uvWidth / uvStridePixels;
break;
case FRAMEWORK_FORMAT_NV12:
case FRAMEWORK_FORMAT_P010:
case FRAMEWORK_FORMAT_YUV_420_888:
mYWidthCutoff = 1.0f;
mUVWidthCutoff = 1.0f;
break;
case FRAMEWORK_FORMAT_GL_COMPATIBLE:
FATAL("Input not a YUV format!");
}
}
void YUVConverter::reset() {
if (mQuadIndexBuffer) s_gles2.glDeleteBuffers(1, &mQuadIndexBuffer);
if (mQuadVertexBuffer) s_gles2.glDeleteBuffers(1, &mQuadVertexBuffer);
if (mProgram) s_gles2.glDeleteProgram(mProgram);
if (mTextureY) s_gles2.glDeleteTextures(1, &mTextureY);
if (isInterleaved(mFormat)) {
if (mTextureU) s_gles2.glDeleteTextures(1, &mTextureU);
} else {
if (mTextureU) s_gles2.glDeleteTextures(1, &mTextureU);
if (mTextureV) s_gles2.glDeleteTextures(1, &mTextureV);
}
mQuadIndexBuffer = 0;
mQuadVertexBuffer = 0;
mProgram = 0;
mTextureY = 0;
mTextureU = 0;
mTextureV = 0;
}
YUVConverter::~YUVConverter() {
reset();
}
} // namespace gl
} // namespace gfxstream