Work around broken EGLImage sampling via access to private HAL data

On Adreno 330, sampling in GLSL from a native Android window buffer,
bound to an EGLImage and external GL texture, will lead to artifacts.
Some rows in the image data will occasionally be null. The same happens
when copying such a texture or reading it back to CPU memory.

To work around this issue, copy the underlying buffer data into a
regular GL texture and sample from there. For this to work, access to
internal HAL data structures is required; therefore this patch is
highly platform-specific.

This issue affects all CtsUiRenderingTestCases that use
Bitmap.Config.HARDWARE, testBitmapConfigFromHardwareToHardware being the
minimal example amongst them.

Issue: FP2P-425
Test: adb shell setprop skia.force_gl_texture 1
Test: run cts -m CtsUiRenderingTestCases
  -t android.uirendering.cts.testclasses.HardwareBitmapTests#testBitmapConfigFromHardwareToHardware
Change-Id: I7192b6e2c38f3294de682ee1e46c3da34af2cc70
diff --git a/src/gpu/GrAHardwareBufferImageGenerator.cpp b/src/gpu/GrAHardwareBufferImageGenerator.cpp
index 085d27a..ec06dcb 100644
--- a/src/gpu/GrAHardwareBufferImageGenerator.cpp
+++ b/src/gpu/GrAHardwareBufferImageGenerator.cpp
@@ -29,6 +29,14 @@
 #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)
@@ -146,21 +154,118 @@
 }
 #endif
 
-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.
+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;
     }
 
-    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);
+    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;
     }
 
-    while (GL_NO_ERROR != glGetError()) {} //clear GL errors
+    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,
@@ -200,7 +305,7 @@
     textureInfo.fID = texID;
 
     GrPixelConfig pixelConfig;
-    switch (getInfo().colorType()) {
+    switch (info.colorType()) {
     case kRGBA_8888_SkColorType:
         pixelConfig = kRGBA_8888_GrPixelConfig;
         break;
@@ -216,7 +321,7 @@
         return nullptr;
     }
 
-    GrBackendTexture backendTex(getInfo().width(), getInfo().height(), pixelConfig, textureInfo);
+    GrBackendTexture backendTex(info.width(), info.height(), pixelConfig, textureInfo);
     if (backendTex.width() <= 0 || backendTex.height() <= 0) {
         glDeleteTextures(1, &texID);
         eglDestroyImageKHR(display, image);
@@ -234,6 +339,50 @@
 
     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);