C++程序  |  575行  |  23.58 KB

/*
 * 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 */