/* * Copyright (C) 2019 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 "VulkanSurface.h" #include #include #include "VulkanManager.h" #include "utils/Color.h" #include "utils/TraceUtils.h" namespace android { namespace uirenderer { namespace renderthread { static bool IsTransformSupported(int transform) { // For now, only support pure rotations, not flip or flip-and-rotate, until we have // more time to test them and build sample code. As far as I know we never actually // use anything besides pure rotations anyway. return transform == 0 || transform == NATIVE_WINDOW_TRANSFORM_ROT_90 || transform == NATIVE_WINDOW_TRANSFORM_ROT_180 || transform == NATIVE_WINDOW_TRANSFORM_ROT_270; } static int InvertTransform(int transform) { switch (transform) { case NATIVE_WINDOW_TRANSFORM_ROT_90: return NATIVE_WINDOW_TRANSFORM_ROT_270; case NATIVE_WINDOW_TRANSFORM_ROT_180: return NATIVE_WINDOW_TRANSFORM_ROT_180; case NATIVE_WINDOW_TRANSFORM_ROT_270: return NATIVE_WINDOW_TRANSFORM_ROT_90; default: return 0; } } static int ConvertVkTransformToNative(VkSurfaceTransformFlagsKHR transform) { switch (transform) { case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR: return NATIVE_WINDOW_TRANSFORM_ROT_270; case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR: return NATIVE_WINDOW_TRANSFORM_ROT_180; case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR: return NATIVE_WINDOW_TRANSFORM_ROT_90; case VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR: case VK_SURFACE_TRANSFORM_INHERIT_BIT_KHR: default: return 0; } } static SkMatrix GetPreTransformMatrix(SkISize windowSize, int transform) { const int width = windowSize.width(); const int height = windowSize.height(); switch (transform) { case 0: return SkMatrix::I(); case NATIVE_WINDOW_TRANSFORM_ROT_90: return SkMatrix::MakeAll(0, -1, height, 1, 0, 0, 0, 0, 1); case NATIVE_WINDOW_TRANSFORM_ROT_180: return SkMatrix::MakeAll(-1, 0, width, 0, -1, height, 0, 0, 1); case NATIVE_WINDOW_TRANSFORM_ROT_270: return SkMatrix::MakeAll(0, 1, 0, -1, 0, width, 0, 0, 1); default: LOG_ALWAYS_FATAL("Unsupported Window Transform (%d)", transform); } return SkMatrix::I(); } void VulkanSurface::ComputeWindowSizeAndTransform(WindowInfo* windowInfo, const SkISize& minSize, const SkISize& maxSize) { SkISize& windowSize = windowInfo->size; // clamp width & height to handle currentExtent of -1 and protect us from broken hints if (windowSize.width() < minSize.width() || windowSize.width() > maxSize.width() || windowSize.height() < minSize.height() || windowSize.height() > maxSize.height()) { int width = std::min(maxSize.width(), std::max(minSize.width(), windowSize.width())); int height = std::min(maxSize.height(), std::max(minSize.height(), windowSize.height())); ALOGE("Invalid Window Dimensions [%d, %d]; clamping to [%d, %d]", windowSize.width(), windowSize.height(), width, height); windowSize.set(width, height); } windowInfo->actualSize = windowSize; if (windowInfo->transform & HAL_TRANSFORM_ROT_90) { windowInfo->actualSize.set(windowSize.height(), windowSize.width()); } windowInfo->preTransform = GetPreTransformMatrix(windowInfo->size, windowInfo->transform); } static bool ResetNativeWindow(ANativeWindow* window) { // -- Reset the native window -- // The native window might have been used previously, and had its properties // changed from defaults. That will affect the answer we get for queries // like MIN_UNDEQUEUED_BUFFERS. Reset to a known/default state before we // attempt such queries. int err = native_window_api_connect(window, NATIVE_WINDOW_API_EGL); if (err != 0) { ALOGW("native_window_api_connect failed: %s (%d)", strerror(-err), err); return false; } // this will match what we do on GL so pick that here. err = window->setSwapInterval(window, 1); if (err != 0) { ALOGW("native_window->setSwapInterval(1) failed: %s (%d)", strerror(-err), err); return false; } err = native_window_set_shared_buffer_mode(window, false); if (err != 0) { ALOGW("native_window_set_shared_buffer_mode(false) failed: %s (%d)", strerror(-err), err); return false; } err = native_window_set_auto_refresh(window, false); if (err != 0) { ALOGW("native_window_set_auto_refresh(false) failed: %s (%d)", strerror(-err), err); return false; } return true; } class VkSurfaceAutoDeleter { public: VkSurfaceAutoDeleter(VkInstance instance, VkSurfaceKHR surface, PFN_vkDestroySurfaceKHR destroySurfaceKHR) : mInstance(instance), mSurface(surface), mDestroySurfaceKHR(destroySurfaceKHR) {} ~VkSurfaceAutoDeleter() { destroy(); } void destroy() { if (mSurface != VK_NULL_HANDLE) { mDestroySurfaceKHR(mInstance, mSurface, nullptr); mSurface = VK_NULL_HANDLE; } } private: VkInstance mInstance; VkSurfaceKHR mSurface; PFN_vkDestroySurfaceKHR mDestroySurfaceKHR; }; VulkanSurface* VulkanSurface::Create(ANativeWindow* window, ColorMode colorMode, SkColorType colorType, sk_sp colorSpace, GrContext* grContext, const VulkanManager& vkManager, uint32_t extraBuffers) { VkAndroidSurfaceCreateInfoKHR surfaceCreateInfo; memset(&surfaceCreateInfo, 0, sizeof(VkAndroidSurfaceCreateInfoKHR)); surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR; surfaceCreateInfo.pNext = nullptr; surfaceCreateInfo.flags = 0; surfaceCreateInfo.window = window; VkSurfaceKHR vkSurface = VK_NULL_HANDLE; VkResult res = vkManager.mCreateAndroidSurfaceKHR(vkManager.mInstance, &surfaceCreateInfo, nullptr, &vkSurface); if (VK_SUCCESS != res) { ALOGE("VulkanSurface::Create() vkCreateAndroidSurfaceKHR failed (%d)", res); return nullptr; } VkSurfaceAutoDeleter vkSurfaceDeleter(vkManager.mInstance, vkSurface, vkManager.mDestroySurfaceKHR); SkDEBUGCODE(VkBool32 supported; res = vkManager.mGetPhysicalDeviceSurfaceSupportKHR( vkManager.mPhysicalDevice, vkManager.mPresentQueueIndex, vkSurface, &supported); // All physical devices and queue families on Android must be capable of // presentation with any native window. SkASSERT(VK_SUCCESS == res && supported);); // check for capabilities VkSurfaceCapabilitiesKHR caps; res = vkManager.mGetPhysicalDeviceSurfaceCapabilitiesKHR(vkManager.mPhysicalDevice, vkSurface, &caps); if (VK_SUCCESS != res) { ALOGE("VulkanSurface::Create() vkGetPhysicalDeviceSurfaceCapabilitiesKHR failed (%d)", res); return nullptr; } LOG_ALWAYS_FATAL_IF(0 == (caps.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR)); /* * We must destroy the VK Surface before attempting to update the window as doing so after * will cause the native window to be modified in unexpected ways. */ vkSurfaceDeleter.destroy(); /* * Populate Window Info struct */ WindowInfo windowInfo; windowInfo.transform = ConvertVkTransformToNative(caps.supportedTransforms); windowInfo.size = SkISize::Make(caps.currentExtent.width, caps.currentExtent.height); const SkISize minSize = SkISize::Make(caps.minImageExtent.width, caps.minImageExtent.height); const SkISize maxSize = SkISize::Make(caps.maxImageExtent.width, caps.maxImageExtent.height); ComputeWindowSizeAndTransform(&windowInfo, minSize, maxSize); int query_value; int err = window->query(window, NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, &query_value); if (err != 0 || query_value < 0) { ALOGE("window->query failed: %s (%d) value=%d", strerror(-err), err, query_value); return nullptr; } auto min_undequeued_buffers = static_cast(query_value); windowInfo.bufferCount = min_undequeued_buffers + std::max(sTargetBufferCount + extraBuffers, caps.minImageCount); if (caps.maxImageCount > 0 && windowInfo.bufferCount > caps.maxImageCount) { // Application must settle for fewer images than desired: windowInfo.bufferCount = caps.maxImageCount; } // Currently Skia requires the images to be color attachments and support all transfer // operations. VkImageUsageFlags usageFlags = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; LOG_ALWAYS_FATAL_IF((caps.supportedUsageFlags & usageFlags) != usageFlags); windowInfo.dataspace = HAL_DATASPACE_V0_SRGB; if (colorMode == ColorMode::WideColorGamut) { skcms_Matrix3x3 surfaceGamut; LOG_ALWAYS_FATAL_IF(!colorSpace->toXYZD50(&surfaceGamut), "Could not get gamut matrix from color space"); if (memcmp(&surfaceGamut, &SkNamedGamut::kSRGB, sizeof(surfaceGamut)) == 0) { windowInfo.dataspace = HAL_DATASPACE_V0_SCRGB; } else if (memcmp(&surfaceGamut, &SkNamedGamut::kDCIP3, sizeof(surfaceGamut)) == 0) { windowInfo.dataspace = HAL_DATASPACE_DISPLAY_P3; } else { LOG_ALWAYS_FATAL("Unreachable: unsupported wide color space."); } } windowInfo.pixelFormat = ColorTypeToPixelFormat(colorType); VkFormat vkPixelFormat = VK_FORMAT_R8G8B8A8_UNORM; if (windowInfo.pixelFormat == PIXEL_FORMAT_RGBA_FP16) { vkPixelFormat = VK_FORMAT_R16G16B16A16_SFLOAT; } LOG_ALWAYS_FATAL_IF(nullptr == vkManager.mGetPhysicalDeviceImageFormatProperties2, "vkGetPhysicalDeviceImageFormatProperties2 is missing"); VkPhysicalDeviceExternalImageFormatInfo externalImageFormatInfo; externalImageFormatInfo.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO; externalImageFormatInfo.pNext = nullptr; externalImageFormatInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID; VkPhysicalDeviceImageFormatInfo2 imageFormatInfo; imageFormatInfo.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2; imageFormatInfo.pNext = &externalImageFormatInfo; imageFormatInfo.format = vkPixelFormat; imageFormatInfo.type = VK_IMAGE_TYPE_2D; imageFormatInfo.tiling = VK_IMAGE_TILING_OPTIMAL; imageFormatInfo.usage = usageFlags; imageFormatInfo.flags = 0; VkAndroidHardwareBufferUsageANDROID hwbUsage; hwbUsage.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_USAGE_ANDROID; hwbUsage.pNext = nullptr; VkImageFormatProperties2 imgFormProps; imgFormProps.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2; imgFormProps.pNext = &hwbUsage; res = vkManager.mGetPhysicalDeviceImageFormatProperties2(vkManager.mPhysicalDevice, &imageFormatInfo, &imgFormProps); if (VK_SUCCESS != res) { ALOGE("Failed to query GetPhysicalDeviceImageFormatProperties2"); return nullptr; } uint64_t consumerUsage; native_window_get_consumer_usage(window, &consumerUsage); windowInfo.windowUsageFlags = consumerUsage | hwbUsage.androidHardwareBufferUsage; /* * Now we attempt to modify the window! */ if (!UpdateWindow(window, windowInfo)) { return nullptr; } return new VulkanSurface(window, windowInfo, minSize, maxSize, grContext); } bool VulkanSurface::UpdateWindow(ANativeWindow* window, const WindowInfo& windowInfo) { ATRACE_CALL(); if (!ResetNativeWindow(window)) { return false; } // -- Configure the native window -- int err = native_window_set_buffers_format(window, windowInfo.pixelFormat); if (err != 0) { ALOGE("VulkanSurface::UpdateWindow() native_window_set_buffers_format(%d) failed: %s (%d)", windowInfo.pixelFormat, strerror(-err), err); return false; } err = native_window_set_buffers_data_space(window, windowInfo.dataspace); if (err != 0) { ALOGE("VulkanSurface::UpdateWindow() native_window_set_buffers_data_space(%d) " "failed: %s (%d)", windowInfo.dataspace, strerror(-err), err); return false; } const SkISize& size = windowInfo.actualSize; err = native_window_set_buffers_dimensions(window, size.width(), size.height()); if (err != 0) { ALOGE("VulkanSurface::UpdateWindow() native_window_set_buffers_dimensions(%d,%d) " "failed: %s (%d)", size.width(), size.height(), strerror(-err), err); return false; } // native_window_set_buffers_transform() expects the transform the app is requesting that // the compositor perform during composition. With native windows, pre-transform works by // rendering with the same transform the compositor is applying (as in Vulkan), but // then requesting the inverse transform, so that when the compositor does // it's job the two transforms cancel each other out and the compositor ends // up applying an identity transform to the app's buffer. err = native_window_set_buffers_transform(window, InvertTransform(windowInfo.transform)); if (err != 0) { ALOGE("VulkanSurface::UpdateWindow() native_window_set_buffers_transform(%d) " "failed: %s (%d)", windowInfo.transform, strerror(-err), err); return false; } // Vulkan defaults to NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW, but this is different than // HWUI's expectation err = native_window_set_scaling_mode(window, NATIVE_WINDOW_SCALING_MODE_FREEZE); if (err != 0) { ALOGE("VulkanSurface::UpdateWindow() native_window_set_scaling_mode(SCALE_TO_WINDOW) " "failed: %s (%d)", strerror(-err), err); return false; } err = native_window_set_buffer_count(window, windowInfo.bufferCount); if (err != 0) { ALOGE("VulkanSurface::UpdateWindow() native_window_set_buffer_count(%zu) failed: %s (%d)", windowInfo.bufferCount, strerror(-err), err); return false; } err = native_window_set_usage(window, windowInfo.windowUsageFlags); if (err != 0) { ALOGE("VulkanSurface::UpdateWindow() native_window_set_usage failed: %s (%d)", strerror(-err), err); return false; } return err == 0; } VulkanSurface::VulkanSurface(ANativeWindow* window, const WindowInfo& windowInfo, SkISize minWindowSize, SkISize maxWindowSize, GrContext* grContext) : mNativeWindow(window) , mWindowInfo(windowInfo) , mGrContext(grContext) , mMinWindowSize(minWindowSize) , mMaxWindowSize(maxWindowSize) {} VulkanSurface::~VulkanSurface() { releaseBuffers(); // release the native window to be available for use by other clients int err = native_window_api_disconnect(mNativeWindow.get(), NATIVE_WINDOW_API_EGL); ALOGW_IF(err != 0, "native_window_api_disconnect failed: %s (%d)", strerror(-err), err); } void VulkanSurface::releaseBuffers() { for (uint32_t i = 0; i < mWindowInfo.bufferCount; i++) { VulkanSurface::NativeBufferInfo& bufferInfo = mNativeBuffers[i]; if (bufferInfo.buffer.get() != nullptr && bufferInfo.dequeued) { int err = mNativeWindow->cancelBuffer(mNativeWindow.get(), bufferInfo.buffer.get(), bufferInfo.dequeue_fence); if (err != 0) { ALOGE("cancelBuffer[%u] failed during destroy: %s (%d)", i, strerror(-err), err); } bufferInfo.dequeued = false; if (bufferInfo.dequeue_fence >= 0) { close(bufferInfo.dequeue_fence); bufferInfo.dequeue_fence = -1; } } LOG_ALWAYS_FATAL_IF(bufferInfo.dequeued); LOG_ALWAYS_FATAL_IF(bufferInfo.dequeue_fence != -1); bufferInfo.skSurface.reset(); bufferInfo.buffer.clear(); bufferInfo.hasValidContents = false; bufferInfo.lastPresentedCount = 0; } } VulkanSurface::NativeBufferInfo* VulkanSurface::dequeueNativeBuffer() { // Set the mCurrentBufferInfo to invalid in case of error and only reset it to the correct // value at the end of the function if everything dequeued correctly. mCurrentBufferInfo = nullptr; // check if the native window has been resized or rotated and update accordingly SkISize newSize = SkISize::MakeEmpty(); int transformHint = 0; mNativeWindow->query(mNativeWindow.get(), NATIVE_WINDOW_WIDTH, &newSize.fWidth); mNativeWindow->query(mNativeWindow.get(), NATIVE_WINDOW_HEIGHT, &newSize.fHeight); mNativeWindow->query(mNativeWindow.get(), NATIVE_WINDOW_TRANSFORM_HINT, &transformHint); if (newSize != mWindowInfo.actualSize || transformHint != mWindowInfo.transform) { WindowInfo newWindowInfo = mWindowInfo; newWindowInfo.size = newSize; newWindowInfo.transform = IsTransformSupported(transformHint) ? transformHint : 0; ComputeWindowSizeAndTransform(&newWindowInfo, mMinWindowSize, mMaxWindowSize); int err = 0; if (newWindowInfo.actualSize != mWindowInfo.actualSize) { // reset the native buffers and update the window err = native_window_set_buffers_dimensions(mNativeWindow.get(), newWindowInfo.actualSize.width(), newWindowInfo.actualSize.height()); if (err != 0) { ALOGE("native_window_set_buffers_dimensions(%d,%d) failed: %s (%d)", newWindowInfo.actualSize.width(), newWindowInfo.actualSize.height(), strerror(-err), err); return nullptr; } // reset the NativeBufferInfo (including SkSurface) associated with the old buffers. The // new NativeBufferInfo storage will be populated lazily as we dequeue each new buffer. releaseBuffers(); // TODO should we ask the nativewindow to allocate buffers? } if (newWindowInfo.transform != mWindowInfo.transform) { err = native_window_set_buffers_transform(mNativeWindow.get(), InvertTransform(newWindowInfo.transform)); if (err != 0) { ALOGE("native_window_set_buffers_transform(%d) failed: %s (%d)", newWindowInfo.transform, strerror(-err), err); newWindowInfo.transform = mWindowInfo.transform; ComputeWindowSizeAndTransform(&newWindowInfo, mMinWindowSize, mMaxWindowSize); } } mWindowInfo = newWindowInfo; } ANativeWindowBuffer* buffer; int fence_fd; int err = mNativeWindow->dequeueBuffer(mNativeWindow.get(), &buffer, &fence_fd); if (err != 0) { ALOGE("dequeueBuffer failed: %s (%d)", strerror(-err), err); return nullptr; } uint32_t idx; for (idx = 0; idx < mWindowInfo.bufferCount; idx++) { if (mNativeBuffers[idx].buffer.get() == buffer) { mNativeBuffers[idx].dequeued = true; mNativeBuffers[idx].dequeue_fence = fence_fd; break; } else if (mNativeBuffers[idx].buffer.get() == nullptr) { // increasing the number of buffers we have allocated mNativeBuffers[idx].buffer = buffer; mNativeBuffers[idx].dequeued = true; mNativeBuffers[idx].dequeue_fence = fence_fd; break; } } if (idx == mWindowInfo.bufferCount) { ALOGE("dequeueBuffer returned unrecognized buffer"); mNativeWindow->cancelBuffer(mNativeWindow.get(), buffer, fence_fd); return nullptr; } VulkanSurface::NativeBufferInfo* bufferInfo = &mNativeBuffers[idx]; if (bufferInfo->skSurface.get() == nullptr) { bufferInfo->skSurface = SkSurface::MakeFromAHardwareBuffer( mGrContext, ANativeWindowBuffer_getHardwareBuffer(bufferInfo->buffer.get()), kTopLeft_GrSurfaceOrigin, DataSpaceToColorSpace(mWindowInfo.dataspace), nullptr); if (bufferInfo->skSurface.get() == nullptr) { ALOGE("SkSurface::MakeFromAHardwareBuffer failed"); mNativeWindow->cancelBuffer(mNativeWindow.get(), buffer, fence_fd); return nullptr; } } mCurrentBufferInfo = bufferInfo; return bufferInfo; } bool VulkanSurface::presentCurrentBuffer(const SkRect& dirtyRect, int semaphoreFd) { if (!dirtyRect.isEmpty()) { // native_window_set_surface_damage takes a rectangle in prerotated space // with a bottom-left origin. That is, top > bottom. // The dirtyRect is also in prerotated space, so we just need to switch it to // a bottom-left origin space. SkIRect irect; dirtyRect.roundOut(&irect); android_native_rect_t aRect; aRect.left = irect.left(); aRect.top = logicalHeight() - irect.top(); aRect.right = irect.right(); aRect.bottom = logicalHeight() - irect.bottom(); int err = native_window_set_surface_damage(mNativeWindow.get(), &aRect, 1); ALOGE_IF(err != 0, "native_window_set_surface_damage failed: %s (%d)", strerror(-err), err); } LOG_ALWAYS_FATAL_IF(!mCurrentBufferInfo); VulkanSurface::NativeBufferInfo& currentBuffer = *mCurrentBufferInfo; int queuedFd = (semaphoreFd != -1) ? semaphoreFd : currentBuffer.dequeue_fence; int err = mNativeWindow->queueBuffer(mNativeWindow.get(), currentBuffer.buffer.get(), queuedFd); currentBuffer.dequeued = false; // queueBuffer always closes fence, even on error if (err != 0) { ALOGE("queueBuffer failed: %s (%d)", strerror(-err), err); mNativeWindow->cancelBuffer(mNativeWindow.get(), currentBuffer.buffer.get(), currentBuffer.dequeue_fence); } else { currentBuffer.hasValidContents = true; currentBuffer.lastPresentedCount = mPresentCount; mPresentCount++; } if (currentBuffer.dequeue_fence >= 0) { close(currentBuffer.dequeue_fence); currentBuffer.dequeue_fence = -1; } return err == 0; } int VulkanSurface::getCurrentBuffersAge() { LOG_ALWAYS_FATAL_IF(!mCurrentBufferInfo); VulkanSurface::NativeBufferInfo& currentBuffer = *mCurrentBufferInfo; return currentBuffer.hasValidContents ? (mPresentCount - currentBuffer.lastPresentedCount) : 0; } } /* namespace renderthread */ } /* namespace uirenderer */ } /* namespace android */