Add support for uploading to AHBs using Vulkan.
Test: manual build and testing on blueline.
Change-Id: I240a5f1e3ba34677b03131eba36ffd8783d99041
diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp
index aeeb32c..8f7e814 100644
--- a/libs/hwui/HardwareBitmapUploader.cpp
+++ b/libs/hwui/HardwareBitmapUploader.cpp
@@ -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,58 +36,247 @@
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;
- return durationSince > 2000_ms;
-}
+struct FormatInfo {
+ PixelFormat pixelFormat;
+ GLint format, type;
+ VkFormat vkFormat;
+ bool isSupported = false;
+ bool valid = true;
+};
-static void checkIdleTimeout() {
- std::lock_guard _lock{sLock};
- if (sPendingUploads == 0 && shouldTimeOutLocked()) {
- sEglManager.destroy();
- } else {
- sUploadThread->queue().postDelayed(5000_ms, checkIdleTimeout);
- }
-}
+class AHBUploader : public RefBase {
+public:
+ virtual ~AHBUploader() {}
-static void beginUpload() {
- std::lock_guard _lock{sLock};
- sPendingUploads++;
-
- if (!sUploadThread) {
- sUploadThread = new ThreadBase{};
+ // Called to start creation of the Vulkan and EGL contexts on another thread before we actually
+ // need to do an upload.
+ void initialize() {
+ onInitialize();
}
- if (!sUploadThread->isRunning()) {
- sUploadThread->start("GrallocUploadThread");
+ 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();
}
- if (!sEglManager.hasEglContext()) {
- sUploadThread->queue().runSync([]() {
- sEglManager.initialize();
- glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+ 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;
+ }
+
+ void idleTimeoutCheck() {
+ std::lock_guard _lock{mLock};
+ if (mPendingUploads == 0 && shouldTimeOutLocked()) {
+ onIdle();
+ } else {
+ this->postIdleTimeoutCheck();
+ }
+ }
+
+ void beginUpload() {
+ std::lock_guard _lock{mLock};
+ mPendingUploads++;
+
+ 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
+
+class EGLUploader : public AHBUploader {
+private:
+ void onInitialize() override {}
+ void onDestroy() override {
+ mEglManager.destroy();
+ }
+ void onIdle() override {
+ mEglManager.destroy();
+ }
+
+ void onBeginUpload() override {
+ if (!mEglManager.hasEglContext()) {
+ mUploadThread->queue().runSync([this]() {
+ this->mEglManager.initialize();
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+ });
+
+ this->postIdleTimeoutCheck();
+ }
+ }
+
+
+ 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;
+ }
+
+ {
+ 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();
+ }
});
- sUploadThread->queue().postDelayed(5000_ms, checkIdleTimeout);
}
-}
+ void onDestroy() override {
+ mGrContext.reset();
+ mVulkanManager.destroy();
+ }
+ void onIdle() override {
+ mGrContext.reset();
+ }
-static void endUpload() {
- std::lock_guard _lock{sLock};
- sPendingUploads--;
- sLastUpload = systemTime();
-}
+ 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();
+ }
+ }
-static EGLDisplay getUploadEglDisplay() {
- std::lock_guard _lock{sLock};
- LOG_ALWAYS_FATAL_IF(!sEglManager.hasEglContext(), "Forgot to begin an upload?");
- return sEglManager.eglDisplay();
-}
+ 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;
@@ -105,16 +297,7 @@
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 @@
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 @@
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 @@
}
}
-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 @@
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.get(), bitmap.colorType(), bitmap.refColorSpace(),
+ return Bitmap::createFrom(buffer, 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