blob: dc8184130caa2f131eb564fc3a0e6a204f1f4cfe [file] [log] [blame]
/*
* Copyright (C) 2011 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 "ColorBufferGl.h"
#include <GLES2/gl2ext.h>
#include <stdio.h>
#include <string.h>
#include "BorrowedImageGl.h"
#include "DebugGl.h"
#include "OpenGLESDispatch/DispatchTables.h"
#include "OpenGLESDispatch/EGLDispatch.h"
#include "RenderThreadInfoGl.h"
#include "TextureDraw.h"
#include "TextureResize.h"
#include "gl/YUVConverter.h"
#include "glestranslator/include/GLcommon/GLutils.h"
#include "host-common/GfxstreamFatalError.h"
#include "host-common/opengl/misc.h"
#define DEBUG_CB_FBO 0
using android::base::ManagedDescriptor;
using emugl::ABORT_REASON_OTHER;
using emugl::FatalError;
using gfxstream::GLESApi;
using gfxstream::GLESApi_CM;
using gfxstream::GLESApi_2;
namespace {
// Lazily create and bind a framebuffer object to the current host context.
// |fbo| is the address of the framebuffer object name.
// |tex| is the name of a texture that is attached to the framebuffer object
// on creation only. I.e. all rendering operations will target it.
// returns true in case of success, false on failure.
bool bindFbo(GLuint* fbo, GLuint tex, bool ensureTextureAttached) {
if (*fbo) {
// fbo already exist - just bind
s_gles2.glBindFramebuffer(GL_FRAMEBUFFER, *fbo);
if (ensureTextureAttached) {
s_gles2.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0_OES,
GL_TEXTURE_2D, tex, 0);
}
return true;
}
s_gles2.glGenFramebuffers(1, fbo);
s_gles2.glBindFramebuffer(GL_FRAMEBUFFER, *fbo);
s_gles2.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0_OES,
GL_TEXTURE_2D, tex, 0);
#if DEBUG_CB_FBO
GLenum status = s_gles2.glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE_OES) {
ERR("ColorBuffer::bindFbo: FBO not complete: %#x\n", status);
s_gles2.glBindFramebuffer(GL_FRAMEBUFFER, 0);
s_gles2.glDeleteFramebuffers(1, fbo);
*fbo = 0;
return false;
}
#endif
return true;
}
void unbindFbo() {
s_gles2.glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
}
static GLenum sGetUnsizedColorBufferFormat(GLenum format) {
switch (format) {
case GL_R8:
return GL_RED;
case GL_RG8:
return GL_RG;
case GL_RGB8:
case GL_RGB565:
case GL_RGB16F:
return GL_RGB;
case GL_RGBA8:
case GL_RGB5_A1_OES:
case GL_RGBA4_OES:
case GL_UNSIGNED_INT_10_10_10_2_OES:
case GL_RGB10_A2:
case GL_RGBA16F:
return GL_RGBA;
case GL_BGRA8_EXT:
case GL_BGR10_A2_ANGLEX:
return GL_BGRA_EXT;
default: // already unsized
return format;
}
}
static bool sGetFormatParameters(GLint* internalFormat,
GLenum* texFormat,
GLenum* pixelType,
int* bytesPerPixel,
GLint* sizedInternalFormat,
bool* isBlob) {
if (!internalFormat) {
fprintf(stderr, "%s: error: internal format not provided\n", __func__);
return false;
}
*isBlob = false;
switch (*internalFormat) {
case GL_RGB:
case GL_RGB8:
*texFormat = GL_RGB;
*pixelType = GL_UNSIGNED_BYTE;
*bytesPerPixel = 3;
*sizedInternalFormat = GL_RGB8;
return true;
case GL_RGB565_OES:
*texFormat = GL_RGB;
*pixelType = GL_UNSIGNED_SHORT_5_6_5;
*bytesPerPixel = 2;
*sizedInternalFormat = GL_RGB565;
return true;
case GL_RGBA:
case GL_RGBA8:
case GL_RGB5_A1_OES:
case GL_RGBA4_OES:
*texFormat = GL_RGBA;
*pixelType = GL_UNSIGNED_BYTE;
*bytesPerPixel = 4;
*sizedInternalFormat = GL_RGBA8;
return true;
case GL_UNSIGNED_INT_10_10_10_2_OES:
*texFormat = GL_RGBA;
*pixelType = GL_UNSIGNED_SHORT;
*bytesPerPixel = 4;
*sizedInternalFormat = GL_UNSIGNED_INT_10_10_10_2_OES;
return true;
case GL_RGB10_A2:
*texFormat = GL_RGBA;
*pixelType = GL_UNSIGNED_INT_2_10_10_10_REV;
*bytesPerPixel = 4;
*sizedInternalFormat = GL_RGB10_A2;
return true;
case GL_RGB16F:
*texFormat = GL_RGB;
*pixelType = GL_HALF_FLOAT;
*bytesPerPixel = 6;
*sizedInternalFormat = GL_RGB16F;
return true;
case GL_RGBA16F:
*texFormat = GL_RGBA;
*pixelType = GL_HALF_FLOAT;
*bytesPerPixel = 8;
*sizedInternalFormat = GL_RGBA16F;
return true;
case GL_LUMINANCE:
*texFormat = GL_LUMINANCE;
*pixelType = GL_UNSIGNED_BYTE;
*bytesPerPixel = 1;
*sizedInternalFormat = GL_R8;
*isBlob = true;
return true;
case GL_BGRA_EXT:
*texFormat = GL_BGRA_EXT;
*pixelType = GL_UNSIGNED_BYTE;
*bytesPerPixel = 4;
*sizedInternalFormat = GL_BGRA8_EXT;
return true;
case GL_BGR10_A2_ANGLEX:
*texFormat = GL_RGBA;
*pixelType = GL_UNSIGNED_INT_2_10_10_10_REV;
*bytesPerPixel = 4;
*internalFormat = GL_RGB10_A2_EXT;
// GL_BGR10_A2_ANGLEX is actually not a valid GL format. We should
// replace it with a normal GL internal format instead.
*sizedInternalFormat = GL_BGR10_A2_ANGLEX;
return true;
case GL_R8:
case GL_RED:
*texFormat = GL_RED;
*pixelType = GL_UNSIGNED_BYTE;
*bytesPerPixel = 1;
*sizedInternalFormat = GL_R8;
return true;
case GL_RG8:
case GL_RG:
*texFormat = GL_RG;
*pixelType = GL_UNSIGNED_BYTE;
*bytesPerPixel = 2;
*sizedInternalFormat = GL_RG8;
return true;
default:
fprintf(stderr, "%s: Unknown format 0x%x\n", __func__,
*internalFormat);
return false;
}
}
// static
ColorBuffer* ColorBuffer::create(EGLDisplay p_display,
int p_width,
int p_height,
GLint p_internalFormat,
FrameworkFormat p_frameworkFormat,
HandleType hndl,
ContextHelper* helper,
TextureDraw* textureDraw,
bool fastBlitSupported,
bool vulkanOnly) {
GLenum texFormat = 0;
GLenum pixelType = GL_UNSIGNED_BYTE;
int bytesPerPixel = 4;
GLint p_sizedInternalFormat = GL_RGBA8;
bool isBlob = false;;
if (!sGetFormatParameters(&p_internalFormat, &texFormat, &pixelType,
&bytesPerPixel, &p_sizedInternalFormat,
&isBlob)) {
fprintf(stderr, "ColorBuffer::create invalid format 0x%x\n",
p_internalFormat);
return NULL;
}
const unsigned long bufsize = ((unsigned long)bytesPerPixel) * p_width
* p_height;
// This constructor is private, so std::make_unique can't be used.
std::unique_ptr<ColorBuffer> cb{new ColorBuffer(p_display, hndl, helper, textureDraw)};
cb->m_width = p_width;
cb->m_height = p_height;
cb->m_internalFormat = p_internalFormat;
cb->m_sizedInternalFormat = p_sizedInternalFormat;
cb->m_format = texFormat;
cb->m_type = pixelType;
cb->m_frameworkFormat = p_frameworkFormat;
cb->m_fastBlitSupported = fastBlitSupported;
cb->m_numBytes = (size_t)bufsize;
cb->m_vulkanOnly = vulkanOnly;
if (vulkanOnly) {
return cb.release();
}
RecursiveScopedContextBind context(helper);
if (!context.isOk()) {
return NULL;
}
GL_SCOPED_DEBUG_GROUP("ColorBuffer::create(handle:%d)", hndl);
GLint prevUnpackAlignment;
s_gles2.glGetIntegerv(GL_UNPACK_ALIGNMENT, &prevUnpackAlignment);
s_gles2.glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
s_gles2.glGenTextures(1, &cb->m_tex);
s_gles2.glBindTexture(GL_TEXTURE_2D, cb->m_tex);
s_gles2.glTexImage2D(GL_TEXTURE_2D, 0, p_internalFormat, p_width, p_height,
0, texFormat, pixelType, nullptr);
s_gles2.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
s_gles2.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
s_gles2.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
s_gles2.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// Swizzle B/R channel for BGR10_A2 images.
if (p_sizedInternalFormat == GL_BGR10_A2_ANGLEX) {
s_gles2.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED);
s_gles2.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE);
cb->m_BRSwizzle = true;
}
//
// create another texture for that colorbuffer for blit
//
s_gles2.glGenTextures(1, &cb->m_blitTex);
s_gles2.glBindTexture(GL_TEXTURE_2D, cb->m_blitTex);
s_gles2.glTexImage2D(GL_TEXTURE_2D, 0, p_internalFormat, p_width, p_height,
0, texFormat, pixelType, NULL);
s_gles2.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
s_gles2.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
s_gles2.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
s_gles2.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// Swizzle B/R channel for BGR10_A2 images.
if (p_sizedInternalFormat == GL_BGR10_A2_ANGLEX) {
s_gles2.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED);
s_gles2.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE);
cb->m_BRSwizzle = true;
}
cb->m_eglImage = s_egl.eglCreateImageKHR(
p_display, s_egl.eglGetCurrentContext(), EGL_GL_TEXTURE_2D_KHR,
(EGLClientBuffer)SafePointerFromUInt(cb->m_tex), NULL);
cb->m_blitEGLImage = s_egl.eglCreateImageKHR(
p_display, s_egl.eglGetCurrentContext(), EGL_GL_TEXTURE_2D_KHR,
(EGLClientBuffer)SafePointerFromUInt(cb->m_blitTex), NULL);
cb->m_resizer = new TextureResize(p_width, p_height);
switch (cb->m_frameworkFormat) {
case FRAMEWORK_FORMAT_GL_COMPATIBLE:
break;
default: // Any YUV format
cb->m_yuv_converter.reset(
new YUVConverter(p_width, p_height, cb->m_frameworkFormat));
break;
}
// desktop GL only: use GL_UNSIGNED_INT_8_8_8_8_REV for faster readback.
if (emugl::getRenderer() == SELECTED_RENDERER_HOST) {
#define GL_UNSIGNED_INT_8_8_8_8 0x8035
#define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367
cb->m_asyncReadbackType = GL_UNSIGNED_INT_8_8_8_8_REV;
}
s_gles2.glPixelStorei(GL_UNPACK_ALIGNMENT, prevUnpackAlignment);
s_gles2.glFinish();
return cb.release();
}
ColorBuffer::ColorBuffer(EGLDisplay display, HandleType hndl, ContextHelper* helper,
TextureDraw* textureDraw)
: m_display(display), m_helper(helper), m_textureDraw(textureDraw), mHndl(hndl) {}
ColorBuffer::~ColorBuffer() {
if (m_vulkanOnly) {
return;
}
RecursiveScopedContextBind context(m_helper);
if (m_blitEGLImage) {
s_egl.eglDestroyImageKHR(m_display, m_blitEGLImage);
}
if (m_eglImage) {
s_egl.eglDestroyImageKHR(m_display, m_eglImage);
}
if (m_fbo) {
s_gles2.glDeleteFramebuffers(1, &m_fbo);
}
if (m_yuv_conversion_fbo) {
s_gles2.glDeleteFramebuffers(1, &m_yuv_conversion_fbo);
}
if (m_scaleRotationFbo) {
s_gles2.glDeleteFramebuffers(1, &m_scaleRotationFbo);
}
m_yuv_converter.reset();
GLuint tex[2] = {m_tex, m_blitTex};
s_gles2.glDeleteTextures(2, tex);
if (m_memoryObject) {
s_gles2.glDeleteMemoryObjectsEXT(1, &m_memoryObject);
}
delete m_resizer;
}
void ColorBuffer::readPixels(int x,
int y,
int width,
int height,
GLenum p_format,
GLenum p_type,
void* pixels) {
RecursiveScopedContextBind context(m_helper);
if (!context.isOk()) {
return;
}
GL_SCOPED_DEBUG_GROUP("ColorBuffer::readPixels(handle:%d fbo:%d tex:%d)", mHndl, m_fbo, m_tex);
p_format = sGetUnsizedColorBufferFormat(p_format);
touch();
waitSync();
if (bindFbo(&m_fbo, m_tex, m_needFboReattach)) {
m_needFboReattach = false;
GLint prevAlignment = 0;
s_gles2.glGetIntegerv(GL_PACK_ALIGNMENT, &prevAlignment);
s_gles2.glPixelStorei(GL_PACK_ALIGNMENT, 1);
s_gles2.glReadPixels(x, y, width, height, p_format, p_type, pixels);
s_gles2.glPixelStorei(GL_PACK_ALIGNMENT, prevAlignment);
unbindFbo();
}
}
void ColorBuffer::readPixelsScaled(int width, int height, GLenum p_format, GLenum p_type,
int rotation, void* pixels, emugl::Rect rect) {
RecursiveScopedContextBind context(m_helper);
if (!context.isOk()) {
return;
}
bool useSnipping = rect.size.w != 0 && rect.size.h != 0;
// Boundary check
if (useSnipping &&
(rect.pos.x < 0 || rect.pos.y < 0 || rect.pos.x + rect.size.w > width ||
rect.pos.y + rect.size.h > height)) {
ERR("readPixelsScaled failed. Out-of-bound rectangle: (%d, %d) [%d x %d]"
" with screen [%d x %d]",
rect.pos.x, rect.pos.y, rect.size.w, rect.size.h);
return;
}
p_format = sGetUnsizedColorBufferFormat(p_format);
touch();
waitSync();
GLuint tex = m_resizer->update(m_tex, width, height, rotation);
if (bindFbo(&m_scaleRotationFbo, tex, m_needFboReattach)) {
m_needFboReattach = false;
GLint prevAlignment = 0;
s_gles2.glGetIntegerv(GL_PACK_ALIGNMENT, &prevAlignment);
s_gles2.glPixelStorei(GL_PACK_ALIGNMENT, 1);
// SwANGLE does not suppot glReadPixels with 3 channels.
// In fact, the spec only require RGBA8888 format support. Supports for
// other formats are optional.
bool needConvert4To3Channel =
p_format == GL_RGB && p_type == GL_UNSIGNED_BYTE &&
(emugl::getRenderer() == SELECTED_RENDERER_SWIFTSHADER_INDIRECT ||
emugl::getRenderer() == SELECTED_RENDERER_ANGLE_INDIRECT);
std::vector<uint8_t> tmpPixels;
void* readPixelsDst = pixels;
if (needConvert4To3Channel) {
tmpPixels.resize(width * height * 4);
p_format = GL_RGBA;
readPixelsDst = tmpPixels.data();
}
if (useSnipping) {
s_gles2.glReadPixels(rect.pos.x, rect.pos.y, rect.size.w,
rect.size.h, p_format, p_type, readPixelsDst);
} else {
s_gles2.glReadPixels(0, 0, width, height, p_format, p_type,
readPixelsDst);
}
if (needConvert4To3Channel) {
uint8_t* src = tmpPixels.data();
uint8_t* dst = static_cast<uint8_t*>(pixels);
for (int h = 0; h < height; h++) {
for (int w = 0; w < width; w++) {
memcpy(dst, src, 3);
dst += 3;
src += 4;
}
}
}
s_gles2.glPixelStorei(GL_PACK_ALIGNMENT, prevAlignment);
unbindFbo();
}
}
void ColorBuffer::readPixelsYUVCached(int x,
int y,
int width,
int height,
void* pixels,
uint32_t pixels_size) {
RecursiveScopedContextBind context(m_helper);
if (!context.isOk()) {
return;
}
touch();
waitSync();
#if DEBUG_CB_FBO
fprintf(stderr, "%s %d request width %d height %d\n", __func__, __LINE__,
width, height);
memset(pixels, 0x00, pixels_size);
assert(m_yuv_converter.get());
#endif
if (!m_vulkanOnly) {
m_yuv_converter->readPixels((uint8_t*)pixels, pixels_size);
} else {
GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) <<
"Unexpected function call when m_vulkanOnly";
}
return;
}
void ColorBuffer::reformat(GLint internalformat, GLenum type) {
GLenum texFormat = internalformat;
GLenum pixelType = GL_UNSIGNED_BYTE;
GLint sizedInternalFormat = GL_RGBA8;
int bpp = 4;
bool isBlob = false;
if (!sGetFormatParameters(&internalformat, &texFormat, &pixelType, &bpp,
&sizedInternalFormat, &isBlob)) {
fprintf(stderr, "%s: WARNING: reformat failed. internal format: 0x%x\n",
__func__, internalformat);
}
// BUG: 143607546
//
// During reformatting, sGetFormatParameters can be too
// opinionated and override the guest's intended choice for the
// pixel type. If the guest wanted GL_UNSIGNED_SHORT_5_6_5 as
// the pixel type, and the incoming internal format is not
// explicitly sized, sGetFormatParameters will pick a default of
// GL_UNSIGNED BYTE, which goes against guest expectations.
//
// This happens only on older API levels where gralloc.cpp in
// goldfish-opengl communicated HAL_PIXEL_FORMAT_RGB_565 as GL
// format GL_RGB, pixel type GL_UNSIGNED_SHORT_5_6_5. Newer
// system images communicate HAL_PIXEL_FORMAT_RGB_565 as GL
// format GL_RGB565, which allows sGetFormatParameters to work
// correctly.
if (pixelType != type) {
pixelType = type;
}
s_gles2.glBindTexture(GL_TEXTURE_2D, m_tex);
s_gles2.glTexImage2D(GL_TEXTURE_2D, 0, internalformat, m_width, m_height,
0, texFormat, pixelType, nullptr);
s_gles2.glBindTexture(GL_TEXTURE_2D, m_blitTex);
s_gles2.glTexImage2D(GL_TEXTURE_2D, 0, internalformat, m_width, m_height,
0, texFormat, pixelType, nullptr);
// EGL images need to be recreated because the EGL_KHR_image_base spec
// states that respecifying an image (i.e. glTexImage2D) will generally
// result in orphaning of the EGL image.
s_egl.eglDestroyImageKHR(m_display, m_eglImage);
m_eglImage = s_egl.eglCreateImageKHR(
m_display, s_egl.eglGetCurrentContext(), EGL_GL_TEXTURE_2D_KHR,
(EGLClientBuffer)SafePointerFromUInt(m_tex), NULL);
s_egl.eglDestroyImageKHR(m_display, m_blitEGLImage);
m_blitEGLImage = s_egl.eglCreateImageKHR(
m_display, s_egl.eglGetCurrentContext(), EGL_GL_TEXTURE_2D_KHR,
(EGLClientBuffer)SafePointerFromUInt(m_blitTex), NULL);
s_gles2.glBindTexture(GL_TEXTURE_2D, 0);
m_internalFormat = internalformat;
m_format = texFormat;
m_type = pixelType;
m_sizedInternalFormat = sizedInternalFormat;
m_numBytes = bpp * m_width * m_height;
}
void ColorBuffer::swapYUVTextures(uint32_t type, uint32_t* textures) {
if (type == FRAMEWORK_FORMAT_NV12) {
if (!m_vulkanOnly) {
m_yuv_converter->swapTextures(type, textures);
} else {
GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER))
<< "Unexpected function call when m_vulkanOnly";
}
} else {
fprintf(stderr,
"%s: ERROR: format other than NV12 is not supported: 0x%x\n",
__func__, type);
}
}
void ColorBuffer::subUpdate(int x,
int y,
int width,
int height,
GLenum p_format,
GLenum p_type,
void* pixels) {
if (m_vulkanOnly) {
return;
}
subUpdateFromFrameworkFormat(x, y, width, height, m_frameworkFormat, p_format, p_type, pixels);
}
void ColorBuffer::subUpdateFromFrameworkFormat(int x, int y, int width, int height,
FrameworkFormat fwkFormat, GLenum p_format,
GLenum p_type, void* pixels) {
const GLenum p_unsizedFormat = sGetUnsizedColorBufferFormat(p_format);
RecursiveScopedContextBind context(m_helper);
if (!context.isOk()) {
return;
}
GL_SCOPED_DEBUG_GROUP("ColorBuffer::subUpdate(handle:%d fbo:%d tex:%d)", mHndl, m_fbo, m_tex);
touch();
if (m_needFormatCheck) {
if (p_type != m_type || p_format != m_format) {
reformat((GLint)p_format, p_type);
}
m_needFormatCheck = false;
}
if (m_frameworkFormat != FRAMEWORK_FORMAT_GL_COMPATIBLE || fwkFormat != m_frameworkFormat) {
assert(m_yuv_converter.get());
// This FBO will convert the YUV frame to RGB
// and render it to |m_tex|.
bindFbo(&m_yuv_conversion_fbo, m_tex, m_needFboReattach);
m_yuv_converter->drawConvertFromFormat(fwkFormat, x, y, width, height, (char*)pixels);
unbindFbo();
// |m_tex| still needs to be bound afterwards
s_gles2.glBindTexture(GL_TEXTURE_2D, m_tex);
} else {
s_gles2.glBindTexture(GL_TEXTURE_2D, m_tex);
s_gles2.glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
s_gles2.glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, p_unsizedFormat,
p_type, pixels);
}
if (m_fastBlitSupported) {
s_gles2.glFlush();
m_sync = (GLsync)s_egl.eglSetImageFenceANDROID(m_display, m_eglImage);
}
}
bool ColorBuffer::replaceContents(const void* newContents, size_t numBytes) {
if (m_vulkanOnly) {
return false;
}
RecursiveScopedContextBind context(m_helper);
if (!context.isOk()) {
fprintf(stderr, "%s: Failed: Could not get current context\n", __func__);
return false;
}
if (m_numBytes != numBytes) {
fprintf(stderr,
"%s: Error: Tried to replace contents of ColorBuffer with "
"%zu bytes (expected %zu; GL format info: 0x%x 0x%x 0x%x); ",
__func__,
numBytes,
m_numBytes,
m_internalFormat,
m_format,
m_type);
return false;
}
touch();
s_gles2.glBindTexture(GL_TEXTURE_2D, m_tex);
s_gles2.glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
s_gles2.glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_width, m_height, m_format,
m_type, newContents);
if (m_fastBlitSupported) {
s_gles2.glFlush();
m_sync = (GLsync)s_egl.eglSetImageFenceANDROID(m_display, m_eglImage);
}
return true;
}
bool ColorBuffer::readContents(size_t* numBytes, void* pixels) {
if (m_yuv_converter) {
// common code path for vk & gles
*numBytes = m_yuv_converter->getDataSize();
if (pixels) {
readPixelsYUVCached(0, 0, 0, 0, pixels, *numBytes);
}
return true;
} else {
*numBytes = m_numBytes;
if (!pixels) return true;
RecursiveScopedContextBind context(m_helper);
readPixels(0, 0, m_width, m_height, m_format, m_type, pixels);
return true;
}
}
bool ColorBuffer::blitFromCurrentReadBuffer() {
RenderThreadInfoGl* const tInfo = RenderThreadInfoGl::get();
if (!tInfo) {
GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER))
<< "Render thread GL not available.";
}
if (!tInfo->currContext.get()) {
// no Current context
return false;
}
touch();
if (m_fastBlitSupported) {
s_egl.eglBlitFromCurrentReadBufferANDROID(m_display, m_eglImage);
m_sync = (GLsync)s_egl.eglSetImageFenceANDROID(m_display, m_eglImage);
} else {
// Copy the content of the current read surface into m_blitEGLImage.
// This is done by creating a temporary texture, bind it to the EGLImage
// then call glCopyTexSubImage2D().
GLuint tmpTex;
GLint currTexBind;
if (tInfo->currContext->clientVersion() > GLESApi_CM) {
s_gles2.glGetIntegerv(GL_TEXTURE_BINDING_2D, &currTexBind);
s_gles2.glGenTextures(1, &tmpTex);
s_gles2.glBindTexture(GL_TEXTURE_2D, tmpTex);
s_gles2.glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, m_blitEGLImage);
const bool isGles3 = tInfo->currContext->clientVersion() > GLESApi_2;
GLint prev_read_fbo = 0;
if (isGles3) {
// Make sure that we unbind any existing GL_READ_FRAMEBUFFER
// before calling glCopyTexSubImage2D, otherwise we may blit
// from the guest's current read framebuffer instead of the EGL
// read buffer.
s_gles2.glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &prev_read_fbo);
if (prev_read_fbo != 0) {
s_gles2.glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
}
} else {
// On GLES 2, there are not separate read/draw framebuffers,
// only GL_FRAMEBUFFER. Per the EGL 1.4 spec section 3.9.3,
// the draw surface must be bound to the calling thread's
// current context, so GL_FRAMEBUFFER should be 0. However, the
// error case is not strongly defined and generating a new error
// may break existing apps.
//
// Instead of the obviously wrong behavior of posting whatever
// GL_FRAMEBUFFER is currently bound to, fix up the
// GL_FRAMEBUFFER if it is non-zero.
s_gles2.glGetIntegerv(GL_FRAMEBUFFER_BINDING, &prev_read_fbo);
if (prev_read_fbo != 0) {
s_gles2.glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
}
// If the read buffer is multisampled, we need to resolve.
GLint samples;
s_gles2.glGetIntegerv(GL_SAMPLE_BUFFERS, &samples);
if (isGles3 && samples > 0) {
s_gles2.glBindTexture(GL_TEXTURE_2D, 0);
GLuint resolve_fbo;
GLint prev_draw_fbo;
s_gles2.glGenFramebuffers(1, &resolve_fbo);
s_gles2.glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &prev_draw_fbo);
s_gles2.glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolve_fbo);
s_gles2.glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
tmpTex, 0);
s_gles2.glBlitFramebuffer(0, 0, m_width, m_height, 0, 0, m_width,
m_height, GL_COLOR_BUFFER_BIT,
GL_NEAREST);
s_gles2.glBindFramebuffer(GL_DRAW_FRAMEBUFFER,
(GLuint)prev_draw_fbo);
s_gles2.glDeleteFramebuffers(1, &resolve_fbo);
s_gles2.glBindTexture(GL_TEXTURE_2D, tmpTex);
} else {
// If the buffer is not multisampled, perform a normal texture copy.
s_gles2.glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, m_width,
m_height);
}
if (prev_read_fbo != 0) {
if (isGles3) {
s_gles2.glBindFramebuffer(GL_READ_FRAMEBUFFER,
(GLuint)prev_read_fbo);
} else {
s_gles2.glBindFramebuffer(GL_FRAMEBUFFER,
(GLuint)prev_read_fbo);
}
}
s_gles2.glDeleteTextures(1, &tmpTex);
s_gles2.glBindTexture(GL_TEXTURE_2D, currTexBind);
// clear GL errors, because its possible that the fbo format does not
// match
// the format of the read buffer, in the case of OpenGL ES 3.1 and
// integer
// RGBA formats.
s_gles2.glGetError();
// This is currently for dEQP purposes only; if we actually want these
// integer FBO formats to actually serve to display something for human
// consumption,
// we need to change the egl image to be of the same format,
// or we get some really psychedelic patterns.
} else {
// Like in the GLES 2 path above, correct the case where
// GL_FRAMEBUFFER_OES is not bound to zero so that we don't blit
// from arbitrary framebuffers.
// Use GLES 2 because it internally has the same value as the GLES 1
// API and it doesn't require GL_OES_framebuffer_object.
GLint prev_fbo = 0;
s_gles2.glGetIntegerv(GL_FRAMEBUFFER_BINDING, &prev_fbo);
if (prev_fbo != 0) {
s_gles2.glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
s_gles1.glGetIntegerv(GL_TEXTURE_BINDING_2D, &currTexBind);
s_gles1.glGenTextures(1, &tmpTex);
s_gles1.glBindTexture(GL_TEXTURE_2D, tmpTex);
s_gles1.glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, m_blitEGLImage);
s_gles1.glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, m_width,
m_height);
s_gles1.glDeleteTextures(1, &tmpTex);
s_gles1.glBindTexture(GL_TEXTURE_2D, currTexBind);
if (prev_fbo != 0) {
s_gles2.glBindFramebuffer(GL_FRAMEBUFFER, (GLuint)prev_fbo);
}
}
RecursiveScopedContextBind context(m_helper);
if (!context.isOk()) {
return false;
}
if (!bindFbo(&m_fbo, m_tex, m_needFboReattach)) {
return false;
}
// Save current viewport and match it to the current colorbuffer size.
GLint vport[4] = {
0,
};
s_gles2.glGetIntegerv(GL_VIEWPORT, vport);
s_gles2.glViewport(0, 0, m_width, m_height);
// render m_blitTex
m_textureDraw->draw(m_blitTex, 0., 0, 0);
// Restore previous viewport.
s_gles2.glViewport(vport[0], vport[1], vport[2], vport[3]);
unbindFbo();
}
return true;
}
bool ColorBuffer::bindToTexture() {
if (!m_eglImage) {
return false;
}
RenderThreadInfoGl* const tInfo = RenderThreadInfoGl::get();
if (!tInfo) {
GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER))
<< "Render thread GL not available.";
}
if (!tInfo->currContext.get()) {
return false;
}
touch();
if (tInfo->currContext->clientVersion() > GLESApi_CM) {
s_gles2.glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, m_eglImage);
} else {
s_gles1.glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, m_eglImage);
}
return true;
}
bool ColorBuffer::bindToTexture2() {
if (!m_eglImage) {
return false;
}
s_gles2.glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, m_eglImage);
return true;
}
bool ColorBuffer::bindToRenderbuffer() {
if (!m_eglImage) {
return false;
}
RenderThreadInfoGl* const tInfo = RenderThreadInfoGl::get();
if (!tInfo) {
GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER))
<< "Render thread GL not available.";
}
if (!tInfo->currContext.get()) {
return false;
}
touch();
if (tInfo->currContext->clientVersion() > GLESApi_CM) {
s_gles2.glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER_OES,
m_eglImage);
} else {
s_gles1.glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER_OES,
m_eglImage);
}
return true;
}
GLuint ColorBuffer::getViewportScaledTexture() {
return m_resizer->update(m_tex);
}
void ColorBuffer::setSync(bool debug) {
m_sync = (GLsync)s_egl.eglSetImageFenceANDROID(m_display, m_eglImage);
if (debug) fprintf(stderr, "%s: %u to %p\n", __func__, getHndl(), m_sync);
}
void ColorBuffer::waitSync(bool debug) {
if (debug) fprintf(stderr, "%s: %u sync %p\n", __func__, getHndl(), m_sync);
if (m_sync) {
s_egl.eglWaitImageFenceANDROID(m_display, m_sync);
}
}
bool ColorBuffer::post(GLuint tex, float rotation, float dx, float dy) {
// NOTE: Do not call m_helper->setupContext() here!
waitSync();
return m_textureDraw->draw(tex, rotation, dx, dy);
}
bool ColorBuffer::postWithOverlay(float rotation, float dx, float dy) {
// NOTE: Do not call m_helper->setupContext() here!
waitSync();
return m_textureDraw->drawWithOverlay(getViewportScaledTexture(), rotation, dx, dy);
}
void ColorBuffer::readback(unsigned char* img, bool readbackBgra) {
RecursiveScopedContextBind context(m_helper);
if (!context.isOk()) {
return;
}
touch();
waitSync();
if (bindFbo(&m_fbo, m_tex, m_needFboReattach)) {
m_needFboReattach = false;
// Flip the readback format if RED/BLUE components are swizzled.
bool shouldReadbackBgra = m_BRSwizzle ? !readbackBgra : readbackBgra;
GLenum format = shouldReadbackBgra ? GL_BGRA_EXT : GL_RGBA;
s_gles2.glReadPixels(0, 0, m_width, m_height, format, GL_UNSIGNED_BYTE, img);
unbindFbo();
}
}
void ColorBuffer::readbackAsync(GLuint buffer, bool readbackBgra) {
RecursiveScopedContextBind context(m_helper);
if (!context.isOk()) {
return;
}
touch();
waitSync();
if (bindFbo(&m_fbo, m_tex, m_needFboReattach)) {
m_needFboReattach = false;
s_gles2.glBindBuffer(GL_PIXEL_PACK_BUFFER, buffer);
bool shouldReadbackBgra = m_BRSwizzle ? !readbackBgra : readbackBgra;
GLenum format = shouldReadbackBgra ? GL_BGRA_EXT : GL_RGBA;
s_gles2.glReadPixels(0, 0, m_width, m_height, format, m_asyncReadbackType, 0);
s_gles2.glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
unbindFbo();
}
}
HandleType ColorBuffer::getHndl() const {
return mHndl;
}
void ColorBuffer::onSave(android::base::Stream* stream) {
stream->putBe32(getHndl());
stream->putBe32(static_cast<uint32_t>(m_width));
stream->putBe32(static_cast<uint32_t>(m_height));
stream->putBe32(static_cast<uint32_t>(m_internalFormat));
stream->putBe32(static_cast<uint32_t>(m_frameworkFormat));
// for debug
assert(m_eglImage && m_blitEGLImage);
stream->putBe32(reinterpret_cast<uintptr_t>(m_eglImage));
stream->putBe32(reinterpret_cast<uintptr_t>(m_blitEGLImage));
stream->putBe32(m_needFormatCheck);
}
ColorBuffer* ColorBuffer::onLoad(android::base::Stream* stream,
EGLDisplay p_display,
ContextHelper* helper,
TextureDraw* textureDraw,
bool fastBlitSupported) {
HandleType hndl = static_cast<HandleType>(stream->getBe32());
GLuint width = static_cast<GLuint>(stream->getBe32());
GLuint height = static_cast<GLuint>(stream->getBe32());
GLenum internalFormat = static_cast<GLenum>(stream->getBe32());
FrameworkFormat frameworkFormat =
static_cast<FrameworkFormat>(stream->getBe32());
EGLImageKHR eglImage = reinterpret_cast<EGLImageKHR>(stream->getBe32());
EGLImageKHR blitEGLImage = reinterpret_cast<EGLImageKHR>(stream->getBe32());
uint32_t needFormatCheck = stream->getBe32();
if (!eglImage) {
return create(p_display, width, height, internalFormat, frameworkFormat,
hndl, helper, textureDraw, fastBlitSupported);
}
ColorBuffer* cb = new ColorBuffer(p_display, hndl, helper, textureDraw);
cb->mNeedRestore = true;
cb->m_eglImage = eglImage;
cb->m_blitEGLImage = blitEGLImage;
assert(eglImage && blitEGLImage);
cb->m_width = width;
cb->m_height = height;
cb->m_internalFormat = internalFormat;
cb->m_frameworkFormat = frameworkFormat;
cb->m_fastBlitSupported = fastBlitSupported;
cb->m_needFormatCheck = needFormatCheck;
return cb;
}
void ColorBuffer::restore() {
RecursiveScopedContextBind context(m_helper);
s_gles2.glGenTextures(1, &m_tex);
s_gles2.glBindTexture(GL_TEXTURE_2D, m_tex);
s_gles2.glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, m_eglImage);
s_gles2.glGenTextures(1, &m_blitTex);
s_gles2.glBindTexture(GL_TEXTURE_2D, m_blitTex);
s_gles2.glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, m_blitEGLImage);
m_resizer = new TextureResize(m_width, m_height);
switch (m_frameworkFormat) {
case FRAMEWORK_FORMAT_GL_COMPATIBLE:
break;
default: // any YUV format
m_yuv_converter.reset(
new YUVConverter(m_width, m_height, m_frameworkFormat));
break;
}
}
GLuint ColorBuffer::getTexture() {
touch();
return m_tex;
}
void ColorBuffer::postLayer(const ComposeLayer& l, int frameWidth, int frameHeight) {
if (m_inUse) fprintf(stderr, "%s: cb in use\n", __func__);
waitSync();
m_textureDraw->drawLayer(l, frameWidth, frameHeight, m_width, m_height,
getViewportScaledTexture());
}
bool ColorBuffer::importMemory(ManagedDescriptor externalDescriptor, uint64_t size, bool dedicated,
bool linearTiling, bool vulkanOnly) {
if (m_vulkanOnly) {
return true;
}
RecursiveScopedContextBind context(m_helper);
s_gles2.glCreateMemoryObjectsEXT(1, &m_memoryObject);
if (dedicated) {
static const GLint DEDICATED_FLAG = GL_TRUE;
s_gles2.glMemoryObjectParameterivEXT(m_memoryObject,
GL_DEDICATED_MEMORY_OBJECT_EXT,
&DEDICATED_FLAG);
}
std::optional<ManagedDescriptor::DescriptorType> maybeRawDescriptor = externalDescriptor.get();
if (!maybeRawDescriptor.has_value()) {
GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "Uninitialized external descriptor.";
}
ManagedDescriptor::DescriptorType rawDescriptor = *maybeRawDescriptor;
#ifdef _WIN32
s_gles2.glImportMemoryWin32HandleEXT(m_memoryObject, size, GL_HANDLE_TYPE_OPAQUE_WIN32_EXT,
rawDescriptor);
#else
s_gles2.glImportMemoryFdEXT(m_memoryObject, size, GL_HANDLE_TYPE_OPAQUE_FD_EXT, rawDescriptor);
#endif
GLenum error = s_gles2.glGetError();
if (error == GL_NO_ERROR) {
#ifdef _WIN32
// Let the external descriptor close when going out of scope. From the
// EXT_external_objects_win32 spec: importing a Windows handle does not transfer ownership
// of the handle to the GL implementation. For handle types defined as NT handles, the
// application must release the handle using an appropriate system call when it is no longer
// needed.
#else
// Inform ManagedDescriptor not to close the fd, since the owner of the fd is transferred to
// the GL driver. From the EXT_external_objects_fd spec: a successful import operation
// transfers ownership of <fd> to the GL implementation, and performing any operation on
// <fd> in the application after an import results in undefined behavior.
externalDescriptor.release();
#endif
} else {
ERR("Failed to import external memory object with error: %d", static_cast<int>(error));
return false;
}
GLuint glTiling = linearTiling ? GL_LINEAR_TILING_EXT : GL_OPTIMAL_TILING_EXT;
std::vector<uint8_t> prevContents;
if (!vulkanOnly) {
size_t bytes;
readContents(&bytes, nullptr);
prevContents.resize(bytes, 0);
readContents(&bytes, prevContents.data());
}
s_gles2.glDeleteTextures(1, &m_tex);
s_gles2.glGenTextures(1, &m_tex);
s_gles2.glBindTexture(GL_TEXTURE_2D, m_tex);
// HOST needed because we do not expose this to guest
s_gles2.glTexParameteriHOST(GL_TEXTURE_2D, GL_TEXTURE_TILING_EXT, glTiling);
s_gles2.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
s_gles2.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
s_gles2.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
s_gles2.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
if (m_sizedInternalFormat == GL_BGRA8_EXT ||
m_sizedInternalFormat == GL_BGR10_A2_ANGLEX) {
GLint internalFormat = m_sizedInternalFormat == GL_BGRA8_EXT
? GL_RGBA8
: GL_RGB10_A2_EXT;
s_gles2.glTexStorageMem2DEXT(GL_TEXTURE_2D, 1, internalFormat, m_width,
m_height, m_memoryObject, 0);
s_gles2.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED);
s_gles2.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE);
m_BRSwizzle = true;
} else {
s_gles2.glTexStorageMem2DEXT(GL_TEXTURE_2D, 1, m_sizedInternalFormat, m_width, m_height, m_memoryObject, 0);
m_BRSwizzle = false;
}
s_egl.eglDestroyImageKHR(m_display, m_eglImage);
m_eglImage = s_egl.eglCreateImageKHR(
m_display, s_egl.eglGetCurrentContext(), EGL_GL_TEXTURE_2D_KHR,
(EGLClientBuffer)SafePointerFromUInt(m_tex), NULL);
if (!vulkanOnly) {
replaceContents(prevContents.data(), m_numBytes);
}
return true;
}
bool ColorBuffer::importEglNativePixmap(void* pixmap, bool preserveContent) {
EGLImageKHR image = s_egl.eglCreateImageKHR(m_display, EGL_NO_CONTEXT, EGL_NATIVE_PIXMAP_KHR, pixmap, nullptr);
if (image == EGL_NO_IMAGE_KHR) {
fprintf(stderr, "%s: error: failed to import pixmap\n", __func__);
return false;
}
// Assume pixmap is compatible with ColorBuffer's current dimensions and internal format.
EGLBoolean setInfoRes = s_egl.eglSetImageInfoANDROID(m_display, image, m_width, m_height, m_internalFormat);
if (EGL_TRUE != setInfoRes) {
fprintf(stderr, "%s: error: failed to set image info\n", __func__);
s_egl.eglDestroyImageKHR(m_display, image);
return false;
}
rebindEglImage(image, preserveContent);
return true;
}
bool ColorBuffer::importEglImage(void* nativeEglImage, bool preserveContent) {
EGLImageKHR image = s_egl.eglImportImageANDROID(m_display, (EGLImage)nativeEglImage);
if (image == EGL_NO_IMAGE_KHR) return false;
// Assume nativeEglImage is compatible with ColorBuffer's current dimensions and internal format.
EGLBoolean setInfoRes = s_egl.eglSetImageInfoANDROID(m_display, image, m_width, m_height, m_internalFormat);
if (EGL_TRUE != setInfoRes) {
s_egl.eglDestroyImageKHR(m_display, image);
return false;
}
rebindEglImage(image, preserveContent);
return true;
}
std::vector<uint8_t> ColorBuffer::getContents() {
// Assume there is a current context.
size_t bytes;
readContents(&bytes, nullptr);
std::vector<uint8_t> contents(bytes);
readContents(&bytes, contents.data());
return contents;
}
void ColorBuffer::clearStorage() {
s_gles2.glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES)NULL);
s_egl.eglDestroyImageKHR(m_display, m_eglImage);
m_eglImage = (EGLImageKHR)0;
}
void ColorBuffer::restoreEglImage(EGLImageKHR image) {
s_gles2.glBindTexture(GL_TEXTURE_2D, m_tex);
m_eglImage = image;
s_gles2.glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES)m_eglImage);
}
void ColorBuffer::rebindEglImage(EGLImageKHR image, bool preserveContent) {
RecursiveScopedContextBind context(m_helper);
std::vector<uint8_t> contents;
if (preserveContent) {
contents = getContents();
}
clearStorage();
restoreEglImage(image);
if (preserveContent) {
replaceContents(contents.data(), m_numBytes);
}
}
void ColorBuffer::setInUse(bool inUse) {
m_inUse = inUse;
}
std::unique_ptr<BorrowedImageInfo> ColorBuffer::getBorrowedImageInfo() {
auto info = std::make_unique<BorrowedImageInfoGl>();
info->id = mHndl;
info->width = m_width;
info->height = m_height;
info->texture = m_tex;
info->onCommandsIssued = [this]() { setSync(); };
return info;
}