blob: acf4931d614499d6fa1f5a651914c795625d3e5f [file] [log] [blame]
/*
* 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 <GrDirectContext.h>
#include <SkSurface.h>
#include <algorithm>
#include "VulkanManager.h"
#include "utils/Color.h"
#include "utils/TraceUtils.h"
namespace android {
namespace uirenderer {
namespace renderthread {
static int InvertTransform(int transform) {
switch (transform) {
case ANATIVEWINDOW_TRANSFORM_ROTATE_90:
return ANATIVEWINDOW_TRANSFORM_ROTATE_270;
case ANATIVEWINDOW_TRANSFORM_ROTATE_180:
return ANATIVEWINDOW_TRANSFORM_ROTATE_180;
case ANATIVEWINDOW_TRANSFORM_ROTATE_270:
return ANATIVEWINDOW_TRANSFORM_ROTATE_90;
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 ANATIVEWINDOW_TRANSFORM_ROTATE_90:
return SkMatrix::MakeAll(0, -1, height, 1, 0, 0, 0, 0, 1);
case ANATIVEWINDOW_TRANSFORM_ROTATE_180:
return SkMatrix::MakeAll(-1, 0, width, 0, -1, height, 0, 0, 1);
case ANATIVEWINDOW_TRANSFORM_ROTATE_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();
}
static bool ConnectAndSetWindowDefaults(ANativeWindow* window) {
ATRACE_CALL();
int err = native_window_api_connect(window, NATIVE_WINDOW_API_EGL);
if (err != 0) {
ALOGE("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) {
ALOGE("native_window->setSwapInterval(1) failed: %s (%d)", strerror(-err), err);
return false;
}
err = native_window_set_shared_buffer_mode(window, false);
if (err != 0) {
ALOGE("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) {
ALOGE("native_window_set_auto_refresh(false) failed: %s (%d)", strerror(-err), err);
return false;
}
err = native_window_set_scaling_mode(window, NATIVE_WINDOW_SCALING_MODE_FREEZE);
if (err != 0) {
ALOGE("native_window_set_scaling_mode(NATIVE_WINDOW_SCALING_MODE_FREEZE) failed: %s (%d)",
strerror(-err), err);
return false;
}
// Let consumer drive the size of the buffers.
err = native_window_set_buffers_dimensions(window, 0, 0);
if (err != 0) {
ALOGE("native_window_set_buffers_dimensions(0,0) failed: %s (%d)", strerror(-err), err);
return false;
}
// Enable auto prerotation, so when buffer size is driven by the consumer
// and the transform hint specifies a 90 or 270 degree rotation, the width
// and height used for buffer pre-allocation and dequeueBuffer will be
// additionally swapped.
err = native_window_set_auto_prerotation(window, true);
if (err != 0) {
ALOGE("VulkanSurface::UpdateWindow() native_window_set_auto_prerotation failed: %s (%d)",
strerror(-err), err);
return false;
}
return true;
}
VulkanSurface* VulkanSurface::Create(ANativeWindow* window, ColorMode colorMode,
SkColorType colorType, sk_sp<SkColorSpace> colorSpace,
GrDirectContext* grContext, const VulkanManager& vkManager,
uint32_t extraBuffers) {
// Connect and set native window to default configurations.
if (!ConnectAndSetWindowDefaults(window)) {
return nullptr;
}
// Initialize WindowInfo struct.
WindowInfo windowInfo;
if (!InitializeWindowInfoStruct(window, colorMode, colorType, colorSpace, vkManager,
extraBuffers, &windowInfo)) {
return nullptr;
}
// Now we attempt to modify the window.
if (!UpdateWindow(window, windowInfo)) {
return nullptr;
}
return new VulkanSurface(window, windowInfo, grContext);
}
bool VulkanSurface::InitializeWindowInfoStruct(ANativeWindow* window, ColorMode colorMode,
SkColorType colorType,
sk_sp<SkColorSpace> colorSpace,
const VulkanManager& vkManager,
uint32_t extraBuffers, WindowInfo* outWindowInfo) {
ATRACE_CALL();
int width, height;
int err = window->query(window, NATIVE_WINDOW_DEFAULT_WIDTH, &width);
if (err != 0 || width < 0) {
ALOGE("window->query failed: %s (%d) value=%d", strerror(-err), err, width);
return false;
}
err = window->query(window, NATIVE_WINDOW_DEFAULT_HEIGHT, &height);
if (err != 0 || height < 0) {
ALOGE("window->query failed: %s (%d) value=%d", strerror(-err), err, height);
return false;
}
outWindowInfo->size = SkISize::Make(width, height);
int query_value;
err = window->query(window, NATIVE_WINDOW_TRANSFORM_HINT, &query_value);
if (err != 0 || query_value < 0) {
ALOGE("window->query failed: %s (%d) value=%d", strerror(-err), err, query_value);
return false;
}
outWindowInfo->transform = query_value;
outWindowInfo->actualSize = outWindowInfo->size;
if (outWindowInfo->transform & ANATIVEWINDOW_TRANSFORM_ROTATE_90) {
outWindowInfo->actualSize.set(outWindowInfo->size.height(), outWindowInfo->size.width());
}
outWindowInfo->preTransform =
GetPreTransformMatrix(outWindowInfo->size, outWindowInfo->transform);
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 false;
}
outWindowInfo->bufferCount =
static_cast<uint32_t>(query_value) + sTargetBufferCount + extraBuffers;
err = window->query(window, NATIVE_WINDOW_MAX_BUFFER_COUNT, &query_value);
if (err != 0 || query_value < 0) {
ALOGE("window->query failed: %s (%d) value=%d", strerror(-err), err, query_value);
return false;
}
if (outWindowInfo->bufferCount > static_cast<uint32_t>(query_value)) {
// Application must settle for fewer images than desired:
outWindowInfo->bufferCount = static_cast<uint32_t>(query_value);
}
outWindowInfo->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) {
outWindowInfo->dataspace = HAL_DATASPACE_V0_SCRGB;
} else if (memcmp(&surfaceGamut, &SkNamedGamut::kDisplayP3, sizeof(surfaceGamut)) == 0) {
outWindowInfo->dataspace = HAL_DATASPACE_DISPLAY_P3;
} else {
LOG_ALWAYS_FATAL("Unreachable: unsupported wide color space.");
}
}
outWindowInfo->bufferFormat = ColorTypeToBufferFormat(colorType);
VkFormat vkPixelFormat = VK_FORMAT_R8G8B8A8_UNORM;
if (outWindowInfo->bufferFormat == AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT) {
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;
// Currently Skia requires the images to be color attachments and support all transfer
// operations.
imageFormatInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT |
VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
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;
VkResult res = vkManager.mGetPhysicalDeviceImageFormatProperties2(
vkManager.mPhysicalDevice, &imageFormatInfo, &imgFormProps);
if (VK_SUCCESS != res) {
ALOGE("Failed to query GetPhysicalDeviceImageFormatProperties2");
return false;
}
uint64_t consumerUsage;
err = native_window_get_consumer_usage(window, &consumerUsage);
if (err != 0) {
ALOGE("native_window_get_consumer_usage failed: %s (%d)", strerror(-err), err);
return false;
}
outWindowInfo->windowUsageFlags = consumerUsage | hwbUsage.androidHardwareBufferUsage;
return true;
}
bool VulkanSurface::UpdateWindow(ANativeWindow* window, const WindowInfo& windowInfo) {
ATRACE_CALL();
int err = native_window_set_buffers_format(window, windowInfo.bufferFormat);
if (err != 0) {
ALOGE("VulkanSurface::UpdateWindow() native_window_set_buffers_format(%d) failed: %s (%d)",
windowInfo.bufferFormat, 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;
}
// 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;
}
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 true;
}
VulkanSurface::VulkanSurface(ANativeWindow* window, const WindowInfo& windowInfo,
GrDirectContext* grContext)
: mNativeWindow(window), mWindowInfo(windowInfo), mGrContext(grContext) {}
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;
// Query the transform hint synced from the initial Surface connect or last queueBuffer. The
// auto prerotation on the buffer is based on the same transform hint in use by the producer.
int transformHint = 0;
int err =
mNativeWindow->query(mNativeWindow.get(), NATIVE_WINDOW_TRANSFORM_HINT, &transformHint);
// Since auto pre-rotation is enabled, dequeueBuffer to get the consumer driven buffer size
// from ANativeWindowBuffer.
ANativeWindowBuffer* buffer;
int fence_fd;
err = mNativeWindow->dequeueBuffer(mNativeWindow.get(), &buffer, &fence_fd);
if (err != 0) {
ALOGE("dequeueBuffer failed: %s (%d)", strerror(-err), err);
return nullptr;
}
SkISize actualSize = SkISize::Make(buffer->width, buffer->height);
if (actualSize != mWindowInfo.actualSize || transformHint != mWindowInfo.transform) {
if (actualSize != mWindowInfo.actualSize) {
// reset the NativeBufferInfo (including SkSurface) associated with the old buffers. The
// new NativeBufferInfo storage will be populated lazily as we dequeue each new buffer.
mWindowInfo.actualSize = actualSize;
releaseBuffers();
}
if (transformHint != mWindowInfo.transform) {
err = native_window_set_buffers_transform(mNativeWindow.get(),
InvertTransform(transformHint));
if (err != 0) {
ALOGE("native_window_set_buffers_transform(%d) failed: %s (%d)", transformHint,
strerror(-err), err);
mNativeWindow->cancelBuffer(mNativeWindow.get(), buffer, fence_fd);
return nullptr;
}
mWindowInfo.transform = transformHint;
}
mWindowInfo.size = actualSize;
if (mWindowInfo.transform & NATIVE_WINDOW_TRANSFORM_ROT_90) {
mWindowInfo.size.set(actualSize.height(), actualSize.width());
}
mWindowInfo.preTransform = GetPreTransformMatrix(mWindowInfo.size, mWindowInfo.transform);
}
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 */