Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c073252f authored by Greg Daniel's avatar Greg Daniel
Browse files

Add support for uploading to AHBs using Vulkan.

Test: manual build and testing on blueline.

Change-Id: I240a5f1e3ba34677b03131eba36ffd8783d99041
parent f38041dd
Loading
Loading
Loading
Loading
+267 −111
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@

#include "hwui/Bitmap.h"
#include "renderthread/EglManager.h"
#include "renderthread/VulkanManager.h"
#include "thread/ThreadBase.h"
#include "utils/TimeUtils.h"

@@ -25,7 +26,9 @@
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <GLES3/gl3.h>
#include <GrContext.h>
#include <SkCanvas.h>
#include <SkImage.h>
#include <utils/GLUtils.h>
#include <utils/Trace.h>
#include <utils/TraceUtils.h>
@@ -33,59 +36,248 @@

namespace android::uirenderer {

static std::mutex sLock{};
static sp<ThreadBase> sUploadThread = nullptr;
static renderthread::EglManager sEglManager;
static int sPendingUploads = 0;
static nsecs_t sLastUpload = 0;
class AHBUploader;
// This helper uploader classes allows us to upload using either EGL or Vulkan using the same
// interface.
static sp<AHBUploader> sUploader = nullptr;

static bool shouldTimeOutLocked() {
    nsecs_t durationSince = systemTime() - sLastUpload;
struct FormatInfo {
    PixelFormat pixelFormat;
    GLint format, type;
    VkFormat vkFormat;
    bool isSupported = false;
    bool valid = true;
};

class AHBUploader : public RefBase {
public:
    virtual ~AHBUploader() {}

    // Called to start creation of the Vulkan and EGL contexts on another thread before we actually
    // need to do an upload.
    void initialize() {
        onInitialize();
    }

    void destroy() {
        std::lock_guard _lock{mLock};
        LOG_ALWAYS_FATAL_IF(mPendingUploads, "terminate called while uploads in progress");
        if (mUploadThread) {
            mUploadThread->requestExit();
            mUploadThread->join();
            mUploadThread = nullptr;
        }
        onDestroy();
    }

    bool uploadHardwareBitmap(const SkBitmap& bitmap, const FormatInfo& format,
                              sp<GraphicBuffer> graphicBuffer) {
        ATRACE_CALL();
        beginUpload();
        bool result = onUploadHardwareBitmap(bitmap, format, graphicBuffer);
        endUpload();
        return result;
    }

    void postIdleTimeoutCheck() {
        mUploadThread->queue().postDelayed(5000_ms, [this](){ this->idleTimeoutCheck(); });
    }

protected:
    std::mutex mLock;
    sp<ThreadBase> mUploadThread = nullptr;

private:
    virtual void onInitialize() = 0;
    virtual void onIdle() = 0;
    virtual void onDestroy() = 0;

    virtual bool onUploadHardwareBitmap(const SkBitmap& bitmap, const FormatInfo& format,
                                        sp<GraphicBuffer> graphicBuffer) = 0;
    virtual void onBeginUpload() = 0;

    bool shouldTimeOutLocked() {
        nsecs_t durationSince = systemTime() - mLastUpload;
        return durationSince > 2000_ms;
    }

static void checkIdleTimeout() {
    std::lock_guard _lock{sLock};
    if (sPendingUploads == 0 && shouldTimeOutLocked()) {
        sEglManager.destroy();
    void idleTimeoutCheck() {
        std::lock_guard _lock{mLock};
        if (mPendingUploads == 0 && shouldTimeOutLocked()) {
            onIdle();
        } else {
        sUploadThread->queue().postDelayed(5000_ms, checkIdleTimeout);
            this->postIdleTimeoutCheck();
        }
    }

static void beginUpload() {
    std::lock_guard _lock{sLock};
    sPendingUploads++;
    void beginUpload() {
        std::lock_guard _lock{mLock};
        mPendingUploads++;

    if (!sUploadThread) {
        sUploadThread = new ThreadBase{};
        if (!mUploadThread) {
            mUploadThread = new ThreadBase{};
        }
        if (!mUploadThread->isRunning()) {
            mUploadThread->start("GrallocUploadThread");
        }

        onBeginUpload();
    }

    void endUpload() {
        std::lock_guard _lock{mLock};
        mPendingUploads--;
        mLastUpload = systemTime();
    }

    int mPendingUploads = 0;
    nsecs_t mLastUpload = 0;
};

#define FENCE_TIMEOUT 2000000000

    if (!sUploadThread->isRunning()) {
        sUploadThread->start("GrallocUploadThread");
class EGLUploader : public AHBUploader {
private:
    void onInitialize() override {}
    void onDestroy() override {
        mEglManager.destroy();
    }
    void onIdle() override {
        mEglManager.destroy();
    }

    if (!sEglManager.hasEglContext()) {
        sUploadThread->queue().runSync([]() {
            sEglManager.initialize();
    void onBeginUpload() override {
        if (!mEglManager.hasEglContext()) {
            mUploadThread->queue().runSync([this]() {
                this->mEglManager.initialize();
                glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
            });
        sUploadThread->queue().postDelayed(5000_ms, checkIdleTimeout);

            this->postIdleTimeoutCheck();
        }
    }

static void endUpload() {
    std::lock_guard _lock{sLock};
    sPendingUploads--;
    sLastUpload = systemTime();

    EGLDisplay getUploadEglDisplay() {
        std::lock_guard _lock{mLock};
        LOG_ALWAYS_FATAL_IF(!mEglManager.hasEglContext(), "Forgot to begin an upload?");
        return mEglManager.eglDisplay();
    }

    bool onUploadHardwareBitmap(const SkBitmap& bitmap, const FormatInfo& format,
                                sp<GraphicBuffer> graphicBuffer) override {
        ATRACE_CALL();

        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)graphicBuffer->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 false;
        }

static EGLDisplay getUploadEglDisplay() {
    std::lock_guard _lock{sLock};
    LOG_ALWAYS_FATAL_IF(!sEglManager.hasEglContext(), "Forgot to begin an upload?");
    return sEglManager.eglDisplay();
        {
            ATRACE_FORMAT("CPU -> gralloc transfer (%dx%d)", bitmap.width(), bitmap.height());
            EGLSyncKHR fence = mUploadThread->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 true;
    }

    renderthread::EglManager mEglManager;
};

class VkUploader : public AHBUploader {
private:
    void onInitialize() override {
        std::lock_guard _lock{mLock};
        if (!mUploadThread) {
            mUploadThread = new ThreadBase{};
        }
        if (!mUploadThread->isRunning()) {
            mUploadThread->start("GrallocUploadThread");
        }

        mUploadThread->queue().post([this]() {
            std::lock_guard _lock{mVkLock};
            if (!mVulkanManager.hasVkContext()) {
                mVulkanManager.initialize();
            }
        });
    }
    void onDestroy() override {
        mGrContext.reset();
        mVulkanManager.destroy();
    }
    void onIdle() override {
        mGrContext.reset();
    }

    void onBeginUpload() override {
        {
            std::lock_guard _lock{mVkLock};
            if (!mVulkanManager.hasVkContext()) {
                LOG_ALWAYS_FATAL_IF(mGrContext,
                    "GrContext exists with no VulkanManager for vulkan uploads");
                mUploadThread->queue().runSync([this]() {
                    mVulkanManager.initialize();
                });
            }
        }
        if (!mGrContext) {
            GrContextOptions options;
            mGrContext = mVulkanManager.createContext(options);
            LOG_ALWAYS_FATAL_IF(!mGrContext, "failed to create GrContext for vulkan uploads");
            this->postIdleTimeoutCheck();
        }
    }

    bool onUploadHardwareBitmap(const SkBitmap& bitmap, const FormatInfo& format,
                                sp<GraphicBuffer> graphicBuffer) override {
        ATRACE_CALL();

        std::lock_guard _lock{mLock};

        sk_sp<SkImage> image = SkImage::MakeFromAHardwareBufferWithData(mGrContext.get(),
            bitmap.pixmap(), reinterpret_cast<AHardwareBuffer*>(graphicBuffer.get()));
        return (image.get() != nullptr);
    }

    sk_sp<GrContext> mGrContext;
    renderthread::VulkanManager mVulkanManager;
    std::mutex mVkLock;
};

bool HardwareBitmapUploader::hasFP16Support() {
    static std::once_flag sOnce;
    static bool hasFP16Support = false;
@@ -105,16 +297,7 @@ bool HardwareBitmapUploader::hasFP16Support() {
    return hasFP16Support;
}

#define FENCE_TIMEOUT 2000000000

struct FormatInfo {
    PixelFormat pixelFormat;
    GLint format, type;
    bool isSupported = false;
    bool valid = true;
};

static FormatInfo determineFormat(const SkBitmap& skBitmap) {
static FormatInfo determineFormat(const SkBitmap& skBitmap, bool usingGL) {
    FormatInfo formatInfo;
    switch (skBitmap.info().colorType()) {
        case kRGBA_8888_SkColorType:
@@ -124,15 +307,18 @@ static FormatInfo determineFormat(const SkBitmap& skBitmap) {
            formatInfo.pixelFormat = PIXEL_FORMAT_RGBA_8888;
            formatInfo.format = GL_RGBA;
            formatInfo.type = GL_UNSIGNED_BYTE;
            formatInfo.vkFormat = VK_FORMAT_R8G8B8A8_UNORM;
            break;
        case kRGBA_F16_SkColorType:
            formatInfo.isSupported = HardwareBitmapUploader::hasFP16Support();
            if (formatInfo.isSupported) {
                formatInfo.type = GL_HALF_FLOAT;
                formatInfo.pixelFormat = PIXEL_FORMAT_RGBA_FP16;
                formatInfo.vkFormat = VK_FORMAT_R16G16B16A16_SFLOAT;
            } else {
                formatInfo.type = GL_UNSIGNED_BYTE;
                formatInfo.pixelFormat = PIXEL_FORMAT_RGBA_8888;
                formatInfo.vkFormat = VK_FORMAT_R8G8B8A8_UNORM;
            }
            formatInfo.format = GL_RGBA;
            break;
@@ -141,12 +327,14 @@ static FormatInfo determineFormat(const SkBitmap& skBitmap) {
            formatInfo.pixelFormat = PIXEL_FORMAT_RGB_565;
            formatInfo.format = GL_RGB;
            formatInfo.type = GL_UNSIGNED_SHORT_5_6_5;
            formatInfo.vkFormat = VK_FORMAT_R5G6B5_UNORM_PACK16;
            break;
        case kGray_8_SkColorType:
            formatInfo.isSupported = true;
            formatInfo.isSupported = usingGL;
            formatInfo.pixelFormat = PIXEL_FORMAT_RGBA_8888;
            formatInfo.format = GL_LUMINANCE;
            formatInfo.type = GL_UNSIGNED_BYTE;
            formatInfo.vkFormat = VK_FORMAT_R8G8B8A8_UNORM;
            break;
        default:
            ALOGW("unable to create hardware bitmap of colortype: %d", skBitmap.info().colorType());
@@ -172,29 +360,37 @@ static SkBitmap makeHwCompatible(const FormatInfo& format, const SkBitmap& sourc
    }
}

class ScopedUploadRequest {
public:
    ScopedUploadRequest() { beginUpload(); }
    ~ScopedUploadRequest() { endUpload(); }
};

static void createUploader(bool usingGL) {
    static std::mutex lock;
    std::lock_guard _lock{lock};
    if (!sUploader.get()) {
        if (usingGL) {
            sUploader = new EGLUploader();
        } else {
            sUploader = new VkUploader();
        }
    }
}

sk_sp<Bitmap> HardwareBitmapUploader::allocateHardwareBitmap(const SkBitmap& sourceBitmap) {
    ATRACE_CALL();

    FormatInfo format = determineFormat(sourceBitmap);
    bool usingGL = uirenderer::Properties::getRenderPipelineType() ==
            uirenderer::RenderPipelineType::SkiaGL;

    FormatInfo format = determineFormat(sourceBitmap, usingGL);
    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()) +
            std::string("Bitmap::allocateHardwareBitmap pid [") + std::to_string(getpid()) +
                    "]");

    status_t error = buffer->initCheck();
@@ -203,64 +399,24 @@ sk_sp<Bitmap> HardwareBitmapUploader::allocateHardwareBitmap(const SkBitmap& sou
        return nullptr;
    }

    EGLDisplay display = getUploadEglDisplay();
    createUploader(usingGL);

    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());
    if (!sUploader->uploadHardwareBitmap(bitmap, format, buffer)) {
        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 Bitmap::createFrom(buffer, bitmap.colorType(), bitmap.refColorSpace(),
                              bitmap.alphaType(), Bitmap::computePalette(bitmap));
}

    return Bitmap::createFrom(buffer.get(), bitmap.colorType(), bitmap.refColorSpace(),
                              bitmap.alphaType(), Bitmap::computePalette(bitmap));
void HardwareBitmapUploader::initialize() {
    bool usingGL = uirenderer::Properties::getRenderPipelineType() ==
            uirenderer::RenderPipelineType::SkiaGL;
    createUploader(usingGL);
    sUploader->initialize();
}

void HardwareBitmapUploader::terminate() {
    std::lock_guard _lock{sLock};
    LOG_ALWAYS_FATAL_IF(sPendingUploads, "terminate called while uploads in progress");
    if (sUploadThread) {
        sUploadThread->requestExit();
        sUploadThread->join();
        sUploadThread = nullptr;
    }
    sEglManager.destroy();
    sUploader->destroy();
}

}  // namespace android::uirenderer
+3 −1
Original line number Diff line number Diff line
@@ -22,9 +22,11 @@ namespace android::uirenderer {

class ANDROID_API HardwareBitmapUploader {
public:
    static sk_sp<Bitmap> allocateHardwareBitmap(const SkBitmap& sourceBitmap);
    static void initialize();
    static void terminate();

    static sk_sp<Bitmap> allocateHardwareBitmap(const SkBitmap& sourceBitmap);

    static bool hasFP16Support();
};

+2 −0
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@
#include "utils/FatVector.h"
#include "utils/TimeUtils.h"
#include "utils/TraceUtils.h"
#include "../HardwareBitmapUploader.h"

#ifdef HWUI_GLES_WRAP_ENABLED
#include "debug/GlesDriver.h"
@@ -415,6 +416,7 @@ void RenderThread::preload() {
    if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) {
        requireVkContext();
    }
    HardwareBitmapUploader::initialize();
}

} /* namespace renderthread */