summaryrefslogtreecommitdiff
path: root/libs/hwui/renderthread/VulkanSurface.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'libs/hwui/renderthread/VulkanSurface.cpp')
-rw-r--r--libs/hwui/renderthread/VulkanSurface.cpp536
1 files changed, 536 insertions, 0 deletions
diff --git a/libs/hwui/renderthread/VulkanSurface.cpp b/libs/hwui/renderthread/VulkanSurface.cpp
new file mode 100644
index 000000000000..c03c3a896e26
--- /dev/null
+++ b/libs/hwui/renderthread/VulkanSurface.cpp
@@ -0,0 +1,536 @@
+/*
+ * 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 <algorithm>
+#include <SkSurface.h>
+
+#include "VulkanManager.h"
+#include "utils/TraceUtils.h"
+#include "utils/Color.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) {
+
+ 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);
+
+ windowInfo.bufferCount = std::max<uint32_t>(VulkanSurface::sMaxBufferCount, 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;
+ }
+
+ uint64_t producerUsage =
+ AHARDWAREBUFFER_USAGE_GPU_FRAMEBUFFER | AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE;
+ uint64_t consumerUsage;
+ native_window_get_consumer_usage(window, &consumerUsage);
+ windowInfo.windowUsageFlags = consumerUsage | producerUsage;
+
+ /*
+ * 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;
+ }
+
+ // Lower layer insists that we have at least two buffers.
+ err = native_window_set_buffer_count(window, std::max(2, windowInfo.bufferCount));
+ if (err != 0) {
+ ALOGE("VulkanSurface::UpdateWindow() native_window_set_buffer_count(%d) 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 < VulkanSurface::sMaxBufferCount; 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 dequeue index to invalid in case of error and only reset it to the correct
+ // value at the end of the function if everything dequeued correctly.
+ mDequeuedIndex = -1;
+
+ //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;
+ }
+ }
+
+ mDequeuedIndex = idx;
+ return bufferInfo;
+}
+
+bool VulkanSurface::presentCurrentBuffer(const SkRect& dirtyRect, int semaphoreFd) {
+ if (!dirtyRect.isEmpty()) {
+ SkRect transformedRect;
+ mWindowInfo.preTransform.mapRect(&transformedRect, dirtyRect);
+
+ SkIRect transformedIRect;
+ transformedRect.roundOut(&transformedIRect);
+ transformedIRect.intersect(0, 0, mWindowInfo.size.fWidth, mWindowInfo.size.fHeight);
+
+ // map to bottom-left coordinate system
+ android_native_rect_t aRect;
+ aRect.left = transformedIRect.x();
+ aRect.top = mWindowInfo.size.fHeight - (transformedIRect.y() + transformedIRect.height());
+ aRect.right = aRect.left + transformedIRect.width();
+ aRect.bottom = aRect.top - transformedIRect.height();
+
+ 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);
+ }
+
+ VulkanSurface::NativeBufferInfo& currentBuffer = mNativeBuffers[mDequeuedIndex];
+ 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() {
+ VulkanSurface::NativeBufferInfo& currentBuffer = mNativeBuffers[mDequeuedIndex];
+ return currentBuffer.hasValidContents ? (mPresentCount - currentBuffer.lastPresentedCount) : 0;
+}
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */