| /* | |
| * 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 <SkSurface.h> | |
| #include <algorithm> | |
| #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<SkColorSpace> 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<uint32_t>(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 */ |