diff options
Diffstat (limited to 'libs/hwui/HardwareBitmapUploader.cpp')
| -rw-r--r-- | libs/hwui/HardwareBitmapUploader.cpp | 423 |
1 files changed, 423 insertions, 0 deletions
diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp new file mode 100644 index 000000000000..9bb6031b76ac --- /dev/null +++ b/libs/hwui/HardwareBitmapUploader.cpp @@ -0,0 +1,423 @@ +/* + * 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 "renderthread/VulkanManager.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 <GrContext.h> +#include <SkCanvas.h> +#include <SkImage.h> +#include <utils/GLUtils.h> +#include <utils/Trace.h> +#include <utils/TraceUtils.h> +#include <thread> + +namespace android::uirenderer { + +class AHBUploader; +// This helper uploader classes allows us to upload using either EGL or Vulkan using the same +// interface. +static sp<AHBUploader> sUploader = nullptr; + +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; + } + + 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(); + } + }); + } + 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; + + // 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; +} + +static FormatInfo determineFormat(const SkBitmap& skBitmap, bool usingGL) { + FormatInfo formatInfo; + 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; + 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; + 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; + formatInfo.vkFormat = VK_FORMAT_R5G6B5_UNORM_PACK16; + break; + case kGray_8_SkColorType: + 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()); + 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(info.makeColorType(kN32_SkColorType)); + + SkCanvas canvas(bitmap); + canvas.drawColor(0); + canvas.drawBitmap(source, 0.0f, 0.0f, nullptr); + + return bitmap; + } +} + + +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(); + + bool usingGL = uirenderer::Properties::getRenderPipelineType() == + uirenderer::RenderPipelineType::SkiaGL; + + FormatInfo format = determineFormat(sourceBitmap, usingGL); + if (!format.valid) { + return nullptr; + } + + 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::allocateHardwareBitmap pid [") + std::to_string(getpid()) + + "]"); + + status_t error = buffer->initCheck(); + if (error < 0) { + ALOGW("createGraphicBuffer() failed in GraphicBuffer.create()"); + return nullptr; + } + + createUploader(usingGL); + + if (!sUploader->uploadHardwareBitmap(bitmap, format, buffer)) { + return nullptr; + } + 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() { + if (sUploader) { + sUploader->destroy(); + } +} + +} // namespace android::uirenderer |