/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "hwui/Bitmap.h" #include "renderthread/EglManager.h" #include "renderthread/VulkanManager.h" #include "thread/ThreadBase.h" #include "utils/TimeUtils.h" namespace android::uirenderer { static constexpr auto kThreadTimeout = 60000_ms; class AHBUploader; // This helper uploader classes allows us to upload using either EGL or Vulkan using the same // interface. static sp sUploader = nullptr; struct FormatInfo { AHardwareBuffer_Format bufferFormat; GLint format, type; VkFormat vkFormat; bool isSupported = false; bool valid = true; }; class AHBUploader : public RefBase { public: virtual ~AHBUploader() {} 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, AHardwareBuffer* ahb) { ATRACE_CALL(); beginUpload(); bool result = onUploadHardwareBitmap(bitmap, format, ahb); endUpload(); return result; } void postIdleTimeoutCheck() { mUploadThread->queue().postDelayed(kThreadTimeout, [this]() { this->idleTimeoutCheck(); }); } protected: std::mutex mLock; sp mUploadThread = nullptr; private: virtual void onIdle() = 0; virtual void onDestroy() = 0; virtual bool onUploadHardwareBitmap(const SkBitmap& bitmap, const FormatInfo& format, AHardwareBuffer* ahb) = 0; virtual void onBeginUpload() = 0; bool shouldTimeOutLocked() { nsecs_t durationSince = systemTime() - mLastUpload; return durationSince > kThreadTimeout; } 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 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, AHardwareBuffer* ahb) 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 buffer // The EGL image is later bound to a 2D texture const EGLClientBuffer clientBuffer = eglGetNativeClientBufferANDROID(ahb); 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); if (GLUtils::dumpGLErrors()) { return EGL_NO_SYNC_KHR; } // 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()); if (GLUtils::dumpGLErrors()) { return EGL_NO_SYNC_KHR; } EGLSyncKHR uploadFence = eglCreateSyncKHR(eglGetCurrentDisplay(), EGL_SYNC_FENCE_KHR, NULL); if (uploadFence == EGL_NO_SYNC_KHR) { ALOGW("Could not create sync fence %#x", eglGetError()); }; glFlush(); GLUtils::dumpGLErrors(); return uploadFence; }); if (fence == EGL_NO_SYNC_KHR) { return false; } EGLint waitStatus = eglClientWaitSyncKHR(display, fence, 0, FENCE_TIMEOUT); ALOGE_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 onDestroy() override { std::lock_guard _lock{mVkLock}; mGrContext.reset(); mVulkanManagerStrong.clear(); } void onIdle() override { onDestroy(); } void onBeginUpload() override {} bool onUploadHardwareBitmap(const SkBitmap& bitmap, const FormatInfo& format, AHardwareBuffer* ahb) override { bool uploadSucceeded = false; mUploadThread->queue().runSync([this, &uploadSucceeded, bitmap, ahb]() { ATRACE_CALL(); std::lock_guard _lock{mVkLock}; renderthread::VulkanManager* vkManager = getVulkanManager(); if (!vkManager->hasVkContext()) { LOG_ALWAYS_FATAL_IF(mGrContext, "GrContext exists with no VulkanManager for vulkan uploads"); vkManager->initialize(); } if (!mGrContext) { GrContextOptions options; mGrContext = vkManager->createContext(options, renderthread::VulkanManager::ContextType::kUploadThread); LOG_ALWAYS_FATAL_IF(!mGrContext, "failed to create GrContext for vulkan uploads"); this->postIdleTimeoutCheck(); } sk_sp image = SkImages::TextureFromAHardwareBufferWithData(mGrContext.get(), bitmap.pixmap(), ahb); mGrContext->submit(GrSyncCpu::kYes); uploadSucceeded = (image.get() != nullptr); }); return uploadSucceeded; } /* must be called on the upload thread after the vkLock has been acquired */ renderthread::VulkanManager* getVulkanManager() { if (!mVulkanManagerStrong) { mVulkanManagerStrong = mVulkanManagerWeak.promote(); // create a new manager if we couldn't promote the weak ref if (!mVulkanManagerStrong) { mVulkanManagerStrong = renderthread::VulkanManager::getInstance(); mGrContext.reset(); mVulkanManagerWeak = mVulkanManagerStrong; } } return mVulkanManagerStrong.get(); } sk_sp mGrContext; sp mVulkanManagerStrong; wp mVulkanManagerWeak; std::mutex mVkLock; }; static bool checkSupport(AHardwareBuffer_Format format) { AHardwareBuffer_Desc desc = { .width = 1, .height = 1, .layers = 1, .format = format, .usage = AHARDWAREBUFFER_USAGE_CPU_READ_NEVER | AHARDWAREBUFFER_USAGE_CPU_WRITE_NEVER | AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE, }; UniqueAHardwareBuffer buffer = allocateAHardwareBuffer(desc); return buffer != nullptr; } bool HardwareBitmapUploader::hasFP16Support() { static bool hasFP16Support = checkSupport(AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT); return hasFP16Support; } bool HardwareBitmapUploader::has1010102Support() { static bool has101012Support = checkSupport(AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM); return has101012Support; } bool HardwareBitmapUploader::hasAlpha8Support() { static bool hasAlpha8Support = checkSupport(AHARDWAREBUFFER_FORMAT_R8_UNORM); return hasAlpha8Support; } static FormatInfo determineFormat(const SkBitmap& skBitmap, bool usingGL) { FormatInfo formatInfo; switch (skBitmap.info().colorType()) { case kRGBA_8888_SkColorType: formatInfo.isSupported = true; [[fallthrough]]; // ARGB_4444 is upconverted to RGBA_8888 case kARGB_4444_SkColorType: formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM; 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.bufferFormat = AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT; formatInfo.vkFormat = VK_FORMAT_R16G16B16A16_SFLOAT; } else { formatInfo.type = GL_UNSIGNED_BYTE; formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM; formatInfo.vkFormat = VK_FORMAT_R8G8B8A8_UNORM; } formatInfo.format = GL_RGBA; break; case kRGB_565_SkColorType: formatInfo.isSupported = true; formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM; 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.bufferFormat = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM; formatInfo.format = GL_LUMINANCE; formatInfo.type = GL_UNSIGNED_BYTE; formatInfo.vkFormat = VK_FORMAT_R8G8B8A8_UNORM; break; case kRGBA_1010102_SkColorType: formatInfo.isSupported = HardwareBitmapUploader::has1010102Support(); if (formatInfo.isSupported) { formatInfo.type = GL_UNSIGNED_INT_2_10_10_10_REV; formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM; formatInfo.vkFormat = VK_FORMAT_A2B10G10R10_UNORM_PACK32; } else { formatInfo.type = GL_UNSIGNED_BYTE; formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM; formatInfo.vkFormat = VK_FORMAT_R8G8B8A8_UNORM; } formatInfo.format = GL_RGBA; break; case kAlpha_8_SkColorType: formatInfo.isSupported = HardwareBitmapUploader::hasAlpha8Support(); if (formatInfo.isSupported) { formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R8_UNORM; formatInfo.format = GL_RED; formatInfo.type = GL_UNSIGNED_BYTE; formatInfo.vkFormat = VK_FORMAT_R8_UNORM; } else { formatInfo.type = GL_UNSIGNED_BYTE; formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM; formatInfo.vkFormat = VK_FORMAT_R8G8B8A8_UNORM; formatInfo.format = GL_RGBA; } 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; bitmap.allocPixels(source.info().makeColorType(kN32_SkColorType)); bitmap.writePixels(source.pixmap()); 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 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); AHardwareBuffer_Desc desc = { .width = static_cast(bitmap.width()), .height = static_cast(bitmap.height()), .layers = 1, .format = format.bufferFormat, .usage = AHARDWAREBUFFER_USAGE_CPU_READ_NEVER | AHARDWAREBUFFER_USAGE_CPU_WRITE_NEVER | AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE, }; UniqueAHardwareBuffer ahb = allocateAHardwareBuffer(desc); if (!ahb) { ALOGW("allocateHardwareBitmap() failed in AHardwareBuffer_allocate()"); return nullptr; }; createUploader(usingGL); if (!sUploader->uploadHardwareBitmap(bitmap, format, ahb.get())) { return nullptr; } return Bitmap::createFrom(ahb.get(), bitmap.colorType(), bitmap.refColorSpace(), bitmap.alphaType(), Bitmap::computePalette(bitmap)); } void HardwareBitmapUploader::initialize() { bool usingGL = uirenderer::Properties::getRenderPipelineType() == uirenderer::RenderPipelineType::SkiaGL; createUploader(usingGL); } void HardwareBitmapUploader::terminate() { if (sUploader) { sUploader->destroy(); } } } // namespace android::uirenderer