blob: ec06dcb35e7f8008c853ecfd720fc117772581cc [file] [log] [blame]
/*
* Copyright 2017 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SkTypes.h"
#if defined(SK_BUILD_FOR_ANDROID) && __ANDROID_API__ >= 26
#define GL_GLEXT_PROTOTYPES
#define EGL_EGLEXT_PROTOTYPES
#include "GrAHardwareBufferImageGenerator.h"
#include <android/hardware_buffer.h>
#include "GrBackendSurface.h"
#include "GrContext.h"
#include "GrContextPriv.h"
#include "GrProxyProvider.h"
#include "GrResourceCache.h"
#include "GrResourceProvider.h"
#include "GrTexture.h"
#include "GrTextureProxy.h"
#include "SkMessageBus.h"
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES/gl.h>
#include <GLES/glext.h>
#include <cutils/properties.h>
// Direct access to private framework and HAL data to workaround Adreno 330 driver bugs.
// DO NOT actually call anything from these headers; they are included only to access private
// structs.
#include "../../../../frameworks/native/libs/nativebase/include/nativebase/nativebase.h"
#include "../../../../hardware/qcom/display/libgralloc/gralloc_priv.h"
class BufferCleanupHelper {
public:
BufferCleanupHelper(EGLImageKHR image, EGLDisplay display)
: fImage(image)
, fDisplay(display) { }
~BufferCleanupHelper() {
eglDestroyImageKHR(fDisplay, fImage);
}
private:
EGLImageKHR fImage;
EGLDisplay fDisplay;
};
std::unique_ptr<SkImageGenerator> GrAHardwareBufferImageGenerator::Make(
AHardwareBuffer* graphicBuffer, SkAlphaType alphaType, sk_sp<SkColorSpace> colorSpace) {
AHardwareBuffer_Desc bufferDesc;
AHardwareBuffer_describe(graphicBuffer, &bufferDesc);
SkColorType colorType;
switch (bufferDesc.format) {
case AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM:
colorType = kRGBA_8888_SkColorType;
break;
case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT:
colorType = kRGBA_F16_SkColorType;
break;
case AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM:
colorType = kRGB_565_SkColorType;
break;
default:
return nullptr;
}
SkImageInfo info = SkImageInfo::Make(bufferDesc.width, bufferDesc.height, colorType,
alphaType, std::move(colorSpace));
return std::unique_ptr<SkImageGenerator>(new GrAHardwareBufferImageGenerator(info, graphicBuffer,
alphaType));
}
GrAHardwareBufferImageGenerator::GrAHardwareBufferImageGenerator(const SkImageInfo& info,
AHardwareBuffer* graphicBuffer, SkAlphaType alphaType)
: INHERITED(info)
, fGraphicBuffer(graphicBuffer) {
AHardwareBuffer_acquire(fGraphicBuffer);
}
GrAHardwareBufferImageGenerator::~GrAHardwareBufferImageGenerator() {
AHardwareBuffer_release(fGraphicBuffer);
this->clear();
}
void GrAHardwareBufferImageGenerator::clear() {
if (fOriginalTexture) {
// Notify the original cache that it can free the last ref, so it happens on the correct
// thread.
GrGpuResourceFreedMessage msg { fOriginalTexture, fOwningContextID };
SkMessageBus<GrGpuResourceFreedMessage>::Post(msg);
fOriginalTexture = nullptr;
}
}
void GrAHardwareBufferImageGenerator::deleteImageTexture(void* context) {
BufferCleanupHelper* cleanupHelper = static_cast<BufferCleanupHelper*>(context);
delete cleanupHelper;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#if SK_SUPPORT_GPU
sk_sp<GrTextureProxy> GrAHardwareBufferImageGenerator::onGenerateTexture(
GrContext* context, const SkImageInfo& info, const SkIPoint& origin,
SkTransferFunctionBehavior, bool willNeedMipMaps) {
auto proxy = this->makeProxy(context);
if (!proxy) {
return nullptr;
}
bool makingASubset = true;
if (0 == origin.fX && 0 == origin.fY &&
info.width() == getInfo().width() && info.height() == getInfo().height()) {
makingASubset = false;
if (!willNeedMipMaps || GrMipMapped::kYes == proxy->mipMapped()) {
// If the caller wants the full texture and we have the correct mip support, we're done
return proxy;
}
}
// Otherwise, make a copy for the requested subset or for mip maps.
SkIRect subset = SkIRect::MakeXYWH(origin.fX, origin.fY, info.width(), info.height());
GrMipMapped mipMapped = willNeedMipMaps ? GrMipMapped::kYes : GrMipMapped::kNo;
sk_sp<GrTextureProxy> texProxy = GrSurfaceProxy::Copy(context, proxy.get(), mipMapped,
subset, SkBudgeted::kYes);
if (!makingASubset && texProxy) {
// We are in this case if we wanted the full texture, but we will be mip mapping the
// texture. Therefore we want to update the cached texture so that we point to the
// mipped version instead of the old one.
SkASSERT(willNeedMipMaps);
SkASSERT(GrMipMapped::kYes == texProxy->mipMapped());
// The only way we should get into here is if we just made a new texture in makeProxy or
// we found a cached texture in the same context. Thus the current and cached contexts
// should match.
SkASSERT(context->uniqueID() == fOwningContextID);
// Clear out the old cached texture.
this->clear();
// We need to get the actual GrTexture so force instantiation of the GrTextureProxy
texProxy->instantiate(context->contextPriv().resourceProvider());
GrTexture* texture = texProxy->priv().peekTexture();
SkASSERT(texture);
fOriginalTexture = texture;
}
return texProxy;
}
#endif
namespace {
sk_sp<GrTexture> createGLTextureFromPrivateHandle(GrContext* context,
const SkImageInfo& info, const AHardwareBuffer* hardware_buffer) {
if (!hardware_buffer) {
SkDebugf("createGLTextureFromPrivateHandle: Cannot work without AHardwareBuffer");
return nullptr;
}
GrPixelConfig pixelConfig = kUnknown_GrPixelConfig;
size_t bytesPerPixel = 0;
GrGLenum format = GL_INVALID_ENUM;
GrGLenum type = GL_INVALID_ENUM;
switch (info.colorType()) {
case kRGBA_8888_SkColorType:
pixelConfig = kRGBA_8888_GrPixelConfig;
bytesPerPixel = 4;
format = GL_RGBA;
type = GL_UNSIGNED_BYTE;
break;
case kRGB_565_SkColorType:
pixelConfig = kRGB_565_GrPixelConfig;
bytesPerPixel = 2;
format = GL_RGB;
type = GL_UNSIGNED_SHORT_5_6_5;
break;
default:
SkDebugf("createGLTextureFromPrivateHandle: Unsupported color type %i", int(info.colorType()));
return nullptr;
}
EGLClientBuffer clientBuffer = eglGetNativeClientBufferANDROID(hardware_buffer);
const native_handle_t* native_handle = reinterpret_cast<const ANativeWindowBuffer*>(clientBuffer)->handle;
const bool handleIsAsExpected = private_handle_t::validate(native_handle) == 0;
if (!handleIsAsExpected) {
SkDebugf("createGLTextureFromPrivateHandle: GraphicBuffer doesn't seem to map to gralloc private handle.");
return nullptr;
}
const private_handle_t* hnd = static_cast<const private_handle_t*>(native_handle);
const int imageWidth = info.width();
const int imageHeight = info.height();
const int bufferWidth = hnd->width; // May be aligned and be larger than the actual image.
const int bufferHeight = hnd->height;
if (imageWidth > bufferWidth || imageHeight > bufferHeight) {
SkDebugf("createGLTextureFromPrivateHandle: image is larger than the buffer. This is not supposed to happen.");
return nullptr;
}
const size_t bufferRowBytes = bufferWidth * bytesPerPixel;
// We access as many rows as the image has, aligned to the width of the buffer.
const size_t minBufferSize = bufferRowBytes * imageHeight;
if (hnd->size < 0 || static_cast<size_t>(hnd->size) < minBufferSize) {
SkDebugf("createGLTextureFromPrivateHandle: buffer is smaller than expected or invalid.");
return nullptr;
}
const char* bufferData = reinterpret_cast<const char*>(hnd->base);
GrGLuint texID;
glGenTextures(1, &texID);
glBindTexture(GL_TEXTURE_2D, texID);
if (imageWidth == bufferWidth) {
// Take the quick path if possible
glTexImage2D(GL_TEXTURE_2D, 0, format, imageWidth, imageHeight, 0, format, type, bufferData);
} else {
// The buffer has some extra space for alignment. Copy row by row.
// First allocate the texture storage without filling it.
glTexImage2D(GL_TEXTURE_2D, 0, format, imageWidth, imageHeight, 0, format, type, 0);
for (int y = 0; y < imageHeight; ++y) {
const void* bufferRowAddr = bufferData + static_cast<size_t>(y) * bufferRowBytes;
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, y, imageWidth, 1, format, type, bufferRowAddr);
}
}
int gl_error;
bool has_gl_error = false;
while (GL_NO_ERROR != (gl_error = glGetError())) {
SkDebugf("createGLTextureFromPrivateHandle: glGetError reports %i", gl_error);
has_gl_error = true;
}
if (has_gl_error) {
SkDebugf("createGLTextureFromPrivateHandle: discarding results, because we had GL errors.");
glDeleteTextures(1, &texID);
return nullptr;
}
GrGLTextureInfo textureInfo;
textureInfo.fTarget = GL_TEXTURE_2D;
textureInfo.fID = texID;
GrBackendTexture backendTex(imageWidth, imageHeight, pixelConfig, textureInfo);
if (backendTex.width() <= 0 || backendTex.height() <= 0) {
SkDebugf("createGLTextureFromPrivateHandle: Failed at GrBackendTexture initialization.");
glDeleteTextures(1, &texID);
return nullptr;
}
sk_sp<GrTexture> tex = context->contextPriv().resourceProvider()->wrapBackendTexture(
backendTex, kAdopt_GrWrapOwnership);
if (!tex) {
SkDebugf("createGLTextureFromPrivateHandle: Failed at wrapBackendTexture()");
glDeleteTextures(1, &texID);
return nullptr;
}
// Compared to the regular code, we don't need a release helper: We gave up ownership of the GL
// texture and don't create any other resources (like the EGLImage).
return tex;
}
sk_sp<GrTexture> createNativeBufferTexture(GrContext* context, const SkImageInfo& info,
const AHardwareBuffer* fGraphicBuffer, void(*deleteImageTexture)(void*)) {
EGLClientBuffer clientBuffer = eglGetNativeClientBufferANDROID(fGraphicBuffer);
EGLint attribs[] = { EGL_IMAGE_PRESERVED_KHR, EGL_TRUE,
EGL_NONE };
EGLDisplay display = eglGetCurrentDisplay();
EGLImageKHR image = eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID,
clientBuffer, attribs);
if (EGL_NO_IMAGE_KHR == image) {
SkDebugf("Could not create EGL image, err = (%#x)", (int) eglGetError() );
return nullptr;
}
GrGLuint texID;
glGenTextures(1, &texID);
if (!texID) {
eglDestroyImageKHR(display, image);
return nullptr;
}
glBindTexture(GL_TEXTURE_EXTERNAL_OES, texID);
GLenum status = GL_NO_ERROR;
if ((status = glGetError()) != GL_NO_ERROR) {
SkDebugf("glBindTexture failed (%#x)", (int) status);
glDeleteTextures(1, &texID);
eglDestroyImageKHR(display, image);
return nullptr;
}
glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, image);
if ((status = glGetError()) != GL_NO_ERROR) {
SkDebugf("glEGLImageTargetTexture2DOES failed (%#x)", (int) status);
glDeleteTextures(1, &texID);
eglDestroyImageKHR(display, image);
return nullptr;
}
context->resetContext(kTextureBinding_GrGLBackendState);
GrGLTextureInfo textureInfo;
textureInfo.fTarget = GL_TEXTURE_EXTERNAL_OES;
textureInfo.fID = texID;
GrPixelConfig pixelConfig;
switch (info.colorType()) {
case kRGBA_8888_SkColorType:
pixelConfig = kRGBA_8888_GrPixelConfig;
break;
case kRGBA_F16_SkColorType:
pixelConfig = kRGBA_half_GrPixelConfig;
break;
case kRGB_565_SkColorType:
pixelConfig = kRGB_565_GrPixelConfig;
break;
default:
glDeleteTextures(1, &texID);
eglDestroyImageKHR(display, image);
return nullptr;
}
GrBackendTexture backendTex(info.width(), info.height(), pixelConfig, textureInfo);
if (backendTex.width() <= 0 || backendTex.height() <= 0) {
glDeleteTextures(1, &texID);
eglDestroyImageKHR(display, image);
return nullptr;
}
sk_sp<GrTexture> tex = context->contextPriv().resourceProvider()->wrapBackendTexture(
backendTex, kAdopt_GrWrapOwnership);
if (!tex) {
glDeleteTextures(1, &texID);
eglDestroyImageKHR(display, image);
return nullptr;
}
sk_sp<GrReleaseProcHelper> releaseHelper(
new GrReleaseProcHelper(deleteImageTexture, new BufferCleanupHelper(image, display)));
tex->setRelease(std::move(releaseHelper));
return tex;
}
} // anonymous namespace
sk_sp<GrTextureProxy> GrAHardwareBufferImageGenerator::makeProxy(GrContext* context) {
if (context->abandoned() || kOpenGL_GrBackend != context->contextPriv().getBackend()) {
// Check if GrContext is not abandoned and the backend is GL.
return nullptr;
}
auto proxyProvider = context->contextPriv().proxyProvider();
// return a cached GrTexture if invoked with the same context
if (fOriginalTexture && fOwningContextID == context->uniqueID()) {
return proxyProvider->createWrapped(sk_ref_sp(fOriginalTexture),
kTopLeft_GrSurfaceOrigin);
}
while (GL_NO_ERROR != glGetError()) {} //clear GL errors
// On Adreno 330 drivers sampling from EGLImage bound to GL_TEXTURE_EXTERNAL_OES texture targets
// leads to artifacts. Copy image data into a regular GL texture instead.
static const bool gl_tex_workaround_enabled =
[] () -> bool {
const int value = property_get_bool("skia.force_gl_texture", 0);
SkDebugf("GrAHardwareBufferImageGenerator::makeProxy skia.force_gl_texture=%i", value);
return bool(value);
}();
sk_sp<GrTexture> tex;
if (gl_tex_workaround_enabled) {
tex = createGLTextureFromPrivateHandle(context, getInfo(), fGraphicBuffer);
}
if (!tex) {
// The workaround is disabled or failed. Fall back to regular, native buffer access.
tex = createNativeBufferTexture(context, getInfo(), fGraphicBuffer, &deleteImageTexture);
}
if (!tex) {
return nullptr;
}
// We fail this assert, if the context has changed. This will be fully handled after
// skbug.com/6812 is ready.
SkASSERT(!fOriginalTexture);
this->clear();
fOriginalTexture = tex.get();
fOwningContextID = context->uniqueID();
// Attach our texture to this context's resource cache. This ensures that deletion will happen
// in the correct thread/context. This adds the only ref to the texture that will persist from
// this point. To trigger GrTexture deletion a message is sent by generator dtor or by
// makeProxy when it is invoked with a different context.
//TODO: GrResourceCache should delete GrTexture, when GrContext is deleted. Currently
//TODO: SkMessageBus ignores messages for deleted contexts and GrTexture will leak.
context->contextPriv().getResourceCache()->insertCrossContextGpuResource(fOriginalTexture);
return proxyProvider->createWrapped(std::move(tex), kTopLeft_GrSurfaceOrigin);
}
bool GrAHardwareBufferImageGenerator::onIsValid(GrContext* context) const {
if (nullptr == context) {
return false; //CPU backend is not supported, because hardware buffer can be swizzled
}
// TODO: add Vulkan support
return kOpenGL_GrBackend == context->contextPriv().getBackend();
}
#endif //SK_BUILD_FOR_ANDROID_FRAMEWORK