A better HW Bitmap uploader

Move all HW bitmap upload operations off of RenderThread.
Ensure EGL context outlives all upload requests

Bug: 79250950
Test: builds, boots, systrace is good, CTS bitmap tests pass

Change-Id: I5ace6c516d33b1afdf1a407cd8b183f6b60c22c1
diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp
new file mode 100644
index 0000000..6408ce6
--- /dev/null
+++ b/libs/hwui/HardwareBitmapUploader.cpp
@@ -0,0 +1,292 @@
+/*
+ * Copyright (C) 2018 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 "HardwareBitmapUploader.h"
+
+#include "hwui/Bitmap.h"
+#include "renderthread/EglManager.h"
+#include "thread/ThreadBase.h"
+#include "utils/TimeUtils.h"
+
+#include <EGL/eglext.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#include <GLES3/gl3.h>
+#include <SkCanvas.h>
+#include <utils/GLUtils.h>
+#include <utils/Trace.h>
+#include <utils/TraceUtils.h>
+#include <thread>
+
+namespace android::uirenderer {
+
+static std::mutex sLock{};
+static ThreadBase* sUploadThread = nullptr;
+static renderthread::EglManager sEglManager;
+static int sPendingUploads = 0;
+static nsecs_t sLastUpload = 0;
+
+static bool shouldTimeOutLocked() {
+    nsecs_t durationSince = systemTime() - sLastUpload;
+    return durationSince > 2000_ms;
+}
+
+static void checkIdleTimeout() {
+    std::lock_guard{sLock};
+    if (sPendingUploads == 0 && shouldTimeOutLocked()) {
+        sEglManager.destroy();
+    } else {
+        sUploadThread->queue().postDelayed(5000_ms, checkIdleTimeout);
+    }
+}
+
+static void beginUpload() {
+    std::lock_guard{sLock};
+    sPendingUploads++;
+
+    if (!sUploadThread) {
+        sUploadThread = new ThreadBase{};
+    }
+
+    if (!sUploadThread->isRunning()) {
+        sUploadThread->start("GrallocUploadThread");
+    }
+
+    if (!sEglManager.hasEglContext()) {
+        sUploadThread->queue().runSync([]() {
+            sEglManager.initialize();
+            glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+        });
+        sUploadThread->queue().postDelayed(5000_ms, checkIdleTimeout);
+    }
+}
+
+static void endUpload() {
+    std::lock_guard{sLock};
+    sPendingUploads--;
+    sLastUpload = systemTime();
+}
+
+static EGLDisplay getUploadEglDisplay() {
+    std::lock_guard{sLock};
+    LOG_ALWAYS_FATAL_IF(!sEglManager.hasEglContext(), "Forgot to begin an upload?");
+    return sEglManager.eglDisplay();
+}
+
+static bool hasFP16Support() {
+    static std::once_flag sOnce;
+    static bool hasFP16Support = false;
+
+    // Gralloc shouldn't let us create a USAGE_HW_TEXTURE if GLES is unable to consume it, so
+    // we don't need to double-check the GLES version/extension.
+    std::call_once(sOnce, []() {
+        sp<GraphicBuffer> buffer = new GraphicBuffer(1, 1, PIXEL_FORMAT_RGBA_FP16,
+                                                     GraphicBuffer::USAGE_HW_TEXTURE |
+                                                             GraphicBuffer::USAGE_SW_WRITE_NEVER |
+                                                             GraphicBuffer::USAGE_SW_READ_NEVER,
+                                                     "tempFp16Buffer");
+        status_t error = buffer->initCheck();
+        hasFP16Support = !error;
+    });
+
+    return hasFP16Support;
+}
+
+#define FENCE_TIMEOUT 2000000000
+
+class AutoEglImage {
+public:
+    AutoEglImage(EGLDisplay display, EGLClientBuffer clientBuffer) : mDisplay(display) {
+        EGLint imageAttrs[] = {EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE};
+        image = eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, clientBuffer,
+                                  imageAttrs);
+    }
+
+    ~AutoEglImage() {
+        if (image != EGL_NO_IMAGE_KHR) {
+            eglDestroyImageKHR(mDisplay, image);
+        }
+    }
+
+    EGLImageKHR image = EGL_NO_IMAGE_KHR;
+
+private:
+    EGLDisplay mDisplay = EGL_NO_DISPLAY;
+};
+
+class AutoSkiaGlTexture {
+public:
+    AutoSkiaGlTexture() {
+        glGenTextures(1, &mTexture);
+        glBindTexture(GL_TEXTURE_2D, mTexture);
+    }
+
+    ~AutoSkiaGlTexture() { glDeleteTextures(1, &mTexture); }
+
+private:
+    GLuint mTexture = 0;
+};
+
+struct FormatInfo {
+    PixelFormat pixelFormat;
+    GLint format, type;
+    bool isSupported = false;
+    bool valid = true;
+};
+
+static FormatInfo determineFormat(const SkBitmap& skBitmap) {
+    FormatInfo formatInfo;
+    // TODO: add support for linear blending (when ANDROID_ENABLE_LINEAR_BLENDING is defined)
+    switch (skBitmap.info().colorType()) {
+        case kRGBA_8888_SkColorType:
+            formatInfo.isSupported = true;
+        // ARGB_4444 is upconverted to RGBA_8888
+        case kARGB_4444_SkColorType:
+            formatInfo.pixelFormat = PIXEL_FORMAT_RGBA_8888;
+            formatInfo.format = GL_RGBA;
+            formatInfo.type = GL_UNSIGNED_BYTE;
+            break;
+        case kRGBA_F16_SkColorType:
+            formatInfo.isSupported = hasFP16Support();
+            if (formatInfo.isSupported) {
+                formatInfo.type = GL_HALF_FLOAT;
+                formatInfo.pixelFormat = PIXEL_FORMAT_RGBA_FP16;
+            } else {
+                formatInfo.type = GL_UNSIGNED_BYTE;
+                formatInfo.pixelFormat = PIXEL_FORMAT_RGBA_8888;
+            }
+            formatInfo.format = GL_RGBA;
+            break;
+        case kRGB_565_SkColorType:
+            formatInfo.isSupported = true;
+            formatInfo.pixelFormat = PIXEL_FORMAT_RGB_565;
+            formatInfo.format = GL_RGB;
+            formatInfo.type = GL_UNSIGNED_SHORT_5_6_5;
+            break;
+        case kGray_8_SkColorType:
+            formatInfo.isSupported = true;
+            formatInfo.pixelFormat = PIXEL_FORMAT_RGBA_8888;
+            formatInfo.format = GL_LUMINANCE;
+            formatInfo.type = GL_UNSIGNED_BYTE;
+            break;
+        default:
+            ALOGW("unable to create hardware bitmap of colortype: %d", skBitmap.info().colorType());
+            formatInfo.valid = false;
+    }
+    return formatInfo;
+}
+
+static SkBitmap makeHwCompatible(const FormatInfo& format, const SkBitmap& source) {
+    if (format.isSupported) {
+        return source;
+    } else {
+        SkBitmap bitmap;
+        const SkImageInfo& info = source.info();
+        bitmap.allocPixels(
+                SkImageInfo::MakeN32(info.width(), info.height(), info.alphaType(), nullptr));
+        bitmap.eraseColor(0);
+        if (info.colorType() == kRGBA_F16_SkColorType) {
+            // Drawing RGBA_F16 onto ARGB_8888 is not supported
+            source.readPixels(bitmap.info().makeColorSpace(SkColorSpace::MakeSRGB()),
+                              bitmap.getPixels(), bitmap.rowBytes(), 0, 0);
+        } else {
+            SkCanvas canvas(bitmap);
+            canvas.drawBitmap(source, 0.0f, 0.0f, nullptr);
+        }
+        return bitmap;
+    }
+}
+
+class ScopedUploadRequest {
+public:
+    ScopedUploadRequest() { beginUpload(); }
+    ~ScopedUploadRequest() { endUpload(); }
+};
+
+sk_sp<Bitmap> HardwareBitmapUploader::allocateHardwareBitmap(const SkBitmap& sourceBitmap) {
+    ATRACE_CALL();
+
+    FormatInfo format = determineFormat(sourceBitmap);
+    if (!format.valid) {
+        return nullptr;
+    }
+
+    ScopedUploadRequest _uploadRequest{};
+
+    SkBitmap bitmap = makeHwCompatible(format, sourceBitmap);
+    sp<GraphicBuffer> buffer = new GraphicBuffer(
+            static_cast<uint32_t>(bitmap.width()), static_cast<uint32_t>(bitmap.height()),
+            format.pixelFormat,
+            GraphicBuffer::USAGE_HW_TEXTURE | GraphicBuffer::USAGE_SW_WRITE_NEVER |
+                    GraphicBuffer::USAGE_SW_READ_NEVER,
+            std::string("Bitmap::allocateSkiaHardwareBitmap pid [") + std::to_string(getpid()) +
+                    "]");
+
+    status_t error = buffer->initCheck();
+    if (error < 0) {
+        ALOGW("createGraphicBuffer() failed in GraphicBuffer.create()");
+        return nullptr;
+    }
+
+    EGLDisplay display = getUploadEglDisplay();
+
+    LOG_ALWAYS_FATAL_IF(display == EGL_NO_DISPLAY, "Failed to get EGL_DEFAULT_DISPLAY! err=%s",
+                        uirenderer::renderthread::EglManager::eglErrorString());
+    // We use an EGLImage to access the content of the GraphicBuffer
+    // The EGL image is later bound to a 2D texture
+    EGLClientBuffer clientBuffer = (EGLClientBuffer)buffer->getNativeBuffer();
+    AutoEglImage autoImage(display, clientBuffer);
+    if (autoImage.image == EGL_NO_IMAGE_KHR) {
+        ALOGW("Could not create EGL image, err =%s",
+              uirenderer::renderthread::EglManager::eglErrorString());
+        return nullptr;
+    }
+
+    {
+        ATRACE_FORMAT("CPU -> gralloc transfer (%dx%d)", bitmap.width(), bitmap.height());
+        EGLSyncKHR fence = sUploadThread->queue().runSync([&]() -> EGLSyncKHR {
+            AutoSkiaGlTexture glTexture;
+            glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, autoImage.image);
+            GL_CHECKPOINT(MODERATE);
+
+            // glTexSubImage2D is synchronous in sense that it memcpy() from pointer that we
+            // provide.
+            // But asynchronous in sense that driver may upload texture onto hardware buffer when we
+            // first
+            // use it in drawing
+            glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bitmap.width(), bitmap.height(), format.format,
+                            format.type, bitmap.getPixels());
+            GL_CHECKPOINT(MODERATE);
+
+            EGLSyncKHR uploadFence =
+                    eglCreateSyncKHR(eglGetCurrentDisplay(), EGL_SYNC_FENCE_KHR, NULL);
+            LOG_ALWAYS_FATAL_IF(uploadFence == EGL_NO_SYNC_KHR, "Could not create sync fence %#x",
+                                eglGetError());
+            glFlush();
+            return uploadFence;
+        });
+
+        EGLint waitStatus = eglClientWaitSyncKHR(display, fence, 0, FENCE_TIMEOUT);
+        LOG_ALWAYS_FATAL_IF(waitStatus != EGL_CONDITION_SATISFIED_KHR,
+                            "Failed to wait for the fence %#x", eglGetError());
+
+        eglDestroySyncKHR(display, fence);
+    }
+
+    return sk_sp<Bitmap>(new Bitmap(buffer.get(), bitmap.info()));
+}
+
+};  // namespace android::uirenderer