1033 lines
37 KiB
C++
1033 lines
37 KiB
C++
/*
|
|
* 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.
|
|
*yStridePixels = alignToPower2(width, 32);
|
|
*yStrideBytes = *yStridePixels;
|
|
|
|
// Chroma stride is 16 bytes aligned.
|
|
*vWidth = width / 2;
|
|
*vHeight = height / 2;
|
|
*vOffsetBytes = (*yStrideBytes) * (*yHeight);
|
|
*vStridePixels = (*yStridePixels) / 2;
|
|
*vStrideBytes = (*vStridePixels);
|
|
|
|
*uWidth = width / 2;
|
|
*uHeight = height / 2;
|
|
*uOffsetBytes = (*vOffsetBytes) + ((*vStrideBytes) * (*vHeight));
|
|
*uStridePixels = (*yStridePixels) / 2;
|
|
*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
|