From 97f313746fda8cb15680a212080179be619f665e Mon Sep 17 00:00:00 2001 From: John Reck Date: Tue, 15 Nov 2022 16:29:21 -0500 Subject: [PATCH] IMapper 5 - the Stable C approach Test: make VtsHalGraphicsMapperStableC_TargetTest VtsHalGraphicsAllocatorAidl_TargetTest Change-Id: I910b27f388e3fb7261425dd4b2133885c05edd37 Merged-In: I910b27f388e3fb7261425dd4b2133885c05edd37 --- .../compatibility_matrix.current.xml | 4 +- graphics/Android.bp | 4 +- graphics/allocator/aidl/Android.bp | 2 +- .../allocator/BufferDescriptorInfo.aidl | 44 + .../graphics/allocator/IAllocator.aidl | 6 + .../allocator/BufferDescriptorInfo.aidl | 65 + .../graphics/allocator/IAllocator.aidl | 38 + graphics/allocator/aidl/vts/Android.bp | 1 + ...VtsHalGraphicsAllocatorAidl_TargetTest.cpp | 235 ++- graphics/mapper/stable-c/Android.bp | 104 ++ .../mapper/stable-c/implutils/impltests.cpp | 314 ++++ .../mapper/utils/IMapperMetadataTypes.h | 576 ++++++ .../graphics/mapper/utils/IMapperProvider.h | 222 +++ .../hardware/graphics/mapper/IMapper.h | 689 ++++++++ ...VtsHalGraphicsMapperStableC_TargetTest.cpp | 1565 +++++++++++++++++ 15 files changed, 3769 insertions(+), 100 deletions(-) create mode 100644 graphics/allocator/aidl/aidl_api/android.hardware.graphics.allocator/current/android/hardware/graphics/allocator/BufferDescriptorInfo.aidl create mode 100644 graphics/allocator/aidl/android/hardware/graphics/allocator/BufferDescriptorInfo.aidl create mode 100644 graphics/mapper/stable-c/Android.bp create mode 100644 graphics/mapper/stable-c/implutils/impltests.cpp create mode 100644 graphics/mapper/stable-c/implutils/include/android/hardware/graphics/mapper/utils/IMapperMetadataTypes.h create mode 100644 graphics/mapper/stable-c/implutils/include/android/hardware/graphics/mapper/utils/IMapperProvider.h create mode 100644 graphics/mapper/stable-c/include/android/hardware/graphics/mapper/IMapper.h create mode 100644 graphics/mapper/stable-c/vts/VtsHalGraphicsMapperStableC_TargetTest.cpp diff --git a/compatibility_matrices/compatibility_matrix.current.xml b/compatibility_matrices/compatibility_matrix.current.xml index 7110ac06e5..89e1263569 100644 --- a/compatibility_matrices/compatibility_matrix.current.xml +++ b/compatibility_matrices/compatibility_matrix.current.xml @@ -319,7 +319,7 @@ android.hardware.graphics.allocator - 1 + 1-2 IAllocator default @@ -344,7 +344,7 @@ default - + android.hardware.graphics.mapper 2.1 diff --git a/graphics/Android.bp b/graphics/Android.bp index b48844d475..cdd81ed54a 100644 --- a/graphics/Android.bp +++ b/graphics/Android.bp @@ -19,14 +19,14 @@ package { cc_defaults { name: "android.hardware.graphics.allocator-ndk_static", static_libs: [ - "android.hardware.graphics.allocator-V1-ndk", + "android.hardware.graphics.allocator-V2-ndk", ], } cc_defaults { name: "android.hardware.graphics.allocator-ndk_shared", shared_libs: [ - "android.hardware.graphics.allocator-V1-ndk", + "android.hardware.graphics.allocator-V2-ndk", ], } diff --git a/graphics/allocator/aidl/Android.bp b/graphics/allocator/aidl/Android.bp index 6dc983c4d6..b0f0f77d1d 100644 --- a/graphics/allocator/aidl/Android.bp +++ b/graphics/allocator/aidl/Android.bp @@ -14,7 +14,7 @@ aidl_interface { enabled: true, support_system_process: true, }, - vndk_use_version: "1", + vndk_use_version: "2", srcs: ["android/hardware/graphics/allocator/*.aidl"], imports: [ "android.hardware.common-V2", diff --git a/graphics/allocator/aidl/aidl_api/android.hardware.graphics.allocator/current/android/hardware/graphics/allocator/BufferDescriptorInfo.aidl b/graphics/allocator/aidl/aidl_api/android.hardware.graphics.allocator/current/android/hardware/graphics/allocator/BufferDescriptorInfo.aidl new file mode 100644 index 0000000000..980e246bb8 --- /dev/null +++ b/graphics/allocator/aidl/aidl_api/android.hardware.graphics.allocator/current/android/hardware/graphics/allocator/BufferDescriptorInfo.aidl @@ -0,0 +1,44 @@ +/* + * Copyright 2022 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m -update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.graphics.allocator; +@VintfStability +parcelable BufferDescriptorInfo { + byte[128] name; + int width; + int height; + int layerCount; + android.hardware.graphics.common.PixelFormat format = android.hardware.graphics.common.PixelFormat.UNSPECIFIED; + android.hardware.graphics.common.BufferUsage usage = android.hardware.graphics.common.BufferUsage.CPU_READ_NEVER; + long reservedSize; +} diff --git a/graphics/allocator/aidl/aidl_api/android.hardware.graphics.allocator/current/android/hardware/graphics/allocator/IAllocator.aidl b/graphics/allocator/aidl/aidl_api/android.hardware.graphics.allocator/current/android/hardware/graphics/allocator/IAllocator.aidl index fe0b0a214b..48bef16fb5 100644 --- a/graphics/allocator/aidl/aidl_api/android.hardware.graphics.allocator/current/android/hardware/graphics/allocator/IAllocator.aidl +++ b/graphics/allocator/aidl/aidl_api/android.hardware.graphics.allocator/current/android/hardware/graphics/allocator/IAllocator.aidl @@ -34,5 +34,11 @@ package android.hardware.graphics.allocator; @VintfStability interface IAllocator { + /** + * @deprecated As of android.hardware.graphics.allocator-V2, this is deprecated & replaced with allocate2 + */ android.hardware.graphics.allocator.AllocationResult allocate(in byte[] descriptor, in int count); + android.hardware.graphics.allocator.AllocationResult allocate2(in android.hardware.graphics.allocator.BufferDescriptorInfo descriptor, in int count); + boolean isSupported(in android.hardware.graphics.allocator.BufferDescriptorInfo descriptor); + String getIMapperLibrarySuffix(); } diff --git a/graphics/allocator/aidl/android/hardware/graphics/allocator/BufferDescriptorInfo.aidl b/graphics/allocator/aidl/android/hardware/graphics/allocator/BufferDescriptorInfo.aidl new file mode 100644 index 0000000000..ffc50b8541 --- /dev/null +++ b/graphics/allocator/aidl/android/hardware/graphics/allocator/BufferDescriptorInfo.aidl @@ -0,0 +1,65 @@ +/* + * Copyright 2022 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. + */ + +package android.hardware.graphics.allocator; + +import android.hardware.graphics.common.BufferUsage; +import android.hardware.graphics.common.PixelFormat; + +@VintfStability +parcelable BufferDescriptorInfo { + /** + * The name of the buffer in ASCII. Useful for debugging/tracing. + */ + byte[128] name; + + /** + * The width specifies how many columns of pixels must be in the + * allocated buffer, but does not necessarily represent the offset in + * columns between the same column in adjacent rows. The rows may be + * padded. + */ + int width; + + /** + * The height specifies how many rows of pixels must be in the + * allocated buffer. + */ + int height; + + /** + * The number of image layers that must be in the allocated buffer. + */ + int layerCount; + + /** + * Buffer pixel format. See PixelFormat.aidl in graphics/common for + * valid values + */ + PixelFormat format = PixelFormat.UNSPECIFIED; + + /** + * Buffer usage mask; valid flags can be found in the definition of + * BufferUsage.aidl in graphics/common + */ + BufferUsage usage = BufferUsage.CPU_READ_NEVER; + + /** + * The size in bytes of the reserved region associated with the buffer. + * See getReservedRegion for more information. + */ + long reservedSize; +} diff --git a/graphics/allocator/aidl/android/hardware/graphics/allocator/IAllocator.aidl b/graphics/allocator/aidl/android/hardware/graphics/allocator/IAllocator.aidl index 92dfd4f8ab..71cebd6717 100644 --- a/graphics/allocator/aidl/android/hardware/graphics/allocator/IAllocator.aidl +++ b/graphics/allocator/aidl/android/hardware/graphics/allocator/IAllocator.aidl @@ -17,6 +17,7 @@ package android.hardware.graphics.allocator; import android.hardware.graphics.allocator.AllocationResult; +import android.hardware.graphics.allocator.BufferDescriptorInfo; @VintfStability interface IAllocator { @@ -31,6 +32,43 @@ interface IAllocator { * @param count The number of buffers to allocate. * @return An AllocationResult containing the result of the allocation * @throws AllocationError on failure + * @deprecated As of android.hardware.graphics.allocator-V2, this is deprecated & replaced with + * allocate2 */ AllocationResult allocate(in byte[] descriptor, in int count); + + /** + * Allocates buffers with the properties specified by the descriptor. + * + * Allocations should be optimized for usage bits provided in the + * descriptor. + * + * @param descriptor Properties of the buffers to allocate. This must be + * obtained from IMapper::createDescriptor(). + * @param count The number of buffers to allocate. + * @return An AllocationResult containing the result of the allocation + * @throws AllocationError on failure + */ + AllocationResult allocate2(in BufferDescriptorInfo descriptor, in int count); + + /** + * Test whether the given BufferDescriptorInfo is allocatable. + * + * If this function returns true, it means that a buffer with the given + * description can be allocated on this implementation, unless resource + * exhaustion occurs. If this function returns false, it means that the + * allocation of the given description will never succeed. + * + * @param description the description of the buffer + * @return supported whether the description is supported + */ + boolean isSupported(in BufferDescriptorInfo descriptor); + + /** + * Retrieve the library suffix to load for the IMapper SP-HAL. This library must implement the + * IMapper stable-C interface (android/hardware/graphics/mapper/IMapper.h). + * + * The library that will attempt to be loaded is "/vendor/lib[64]/hw/mapper..so" + */ + String getIMapperLibrarySuffix(); } diff --git a/graphics/allocator/aidl/vts/Android.bp b/graphics/allocator/aidl/vts/Android.bp index a38af14730..630ab2a2f0 100644 --- a/graphics/allocator/aidl/vts/Android.bp +++ b/graphics/allocator/aidl/vts/Android.bp @@ -55,6 +55,7 @@ cc_test { ], header_libs: [ "libhwui_internal_headers", + "libimapper_stablec", ], cflags: [ "-Wall", diff --git a/graphics/allocator/aidl/vts/VtsHalGraphicsAllocatorAidl_TargetTest.cpp b/graphics/allocator/aidl/vts/VtsHalGraphicsAllocatorAidl_TargetTest.cpp index 59af5cf375..09f1c1566b 100644 --- a/graphics/allocator/aidl/vts/VtsHalGraphicsAllocatorAidl_TargetTest.cpp +++ b/graphics/allocator/aidl/vts/VtsHalGraphicsAllocatorAidl_TargetTest.cpp @@ -25,7 +25,10 @@ #include #include #include +#include #include +#include +#include #include #include #include @@ -33,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -42,60 +46,70 @@ using namespace aidl::android::hardware::graphics::allocator; using namespace aidl::android::hardware::graphics::common; using namespace android; using namespace android::hardware; -using namespace android::hardware::graphics::mapper::V4_0; +using IMapper4 = android::hardware::graphics::mapper::V4_0::IMapper; +using Error = android::hardware::graphics::mapper::V4_0::Error; +using android::hardware::graphics::mapper::V4_0::BufferDescriptor; using android::uirenderer::AutoEglImage; using android::uirenderer::AutoGLFramebuffer; using android::uirenderer::AutoSkiaGlTexture; using android::uirenderer::renderthread::EglManager; -static constexpr uint64_t pack(const std::initializer_list& usages) { - uint64_t ret = 0; - for (const auto u : usages) { - ret |= static_cast(u); - } - return ret; +typedef AIMapper_Error (*AIMapper_loadIMapperFn)(AIMapper* _Nullable* _Nonnull outImplementation); + +inline BufferUsage operator|(BufferUsage lhs, BufferUsage rhs) { + using T = std::underlying_type_t; + return static_cast(static_cast(lhs) | static_cast(rhs)); } -static constexpr hardware::graphics::common::V1_2::PixelFormat cast(PixelFormat format) { - return static_cast(format); +inline BufferUsage& operator|=(BufferUsage& lhs, BufferUsage rhs) { + lhs = lhs | rhs; + return lhs; } +static IMapper4::BufferDescriptorInfo convert(const BufferDescriptorInfo& info) { + return IMapper4::BufferDescriptorInfo{ + .name{reinterpret_cast(info.name.data())}, + .width = static_cast(info.width), + .height = static_cast(info.height), + .layerCount = static_cast(info.layerCount), + .format = static_cast(info.format), + .usage = static_cast(info.usage), + .reservedSize = 0, + }; +} + +class GraphicsTestsBase; + class BufferHandle { - sp mMapper; + GraphicsTestsBase& mTestBase; native_handle_t* mRawHandle; bool mImported = false; uint32_t mStride; - const IMapper::BufferDescriptorInfo mInfo; + const BufferDescriptorInfo mInfo; BufferHandle(const BufferHandle&) = delete; void operator=(const BufferHandle&) = delete; public: - BufferHandle(const sp mapper, native_handle_t* handle, bool imported, uint32_t stride, - const IMapper::BufferDescriptorInfo& info) - : mMapper(mapper), mRawHandle(handle), mImported(imported), mStride(stride), mInfo(info) {} + BufferHandle(GraphicsTestsBase& testBase, native_handle_t* handle, bool imported, + uint32_t stride, const BufferDescriptorInfo& info) + : mTestBase(testBase), + mRawHandle(handle), + mImported(imported), + mStride(stride), + mInfo(info) {} - ~BufferHandle() { - if (mRawHandle == nullptr) return; - - if (mImported) { - Error error = mMapper->freeBuffer(mRawHandle); - EXPECT_EQ(Error::NONE, error) << "failed to free buffer " << mRawHandle; - } else { - native_handle_close(mRawHandle); - native_handle_delete(mRawHandle); - } - } + ~BufferHandle(); uint32_t stride() const { return mStride; } AHardwareBuffer_Desc describe() const { return { - .width = mInfo.width, - .height = mInfo.height, - .layers = mInfo.layerCount, + .width = static_cast(mInfo.width), + .height = static_cast(mInfo.height), + .layers = static_cast(mInfo.layerCount), .format = static_cast(mInfo.format), - .usage = mInfo.usage, + .usage = static_cast(mInfo.usage), .stride = stride(), .rfu0 = 0, .rfu1 = 0, @@ -114,25 +128,43 @@ class BufferHandle { class GraphicsTestsBase { private: + friend class BufferHandle; + int32_t mIAllocatorVersion = 1; std::shared_ptr mAllocator; - sp mMapper; + sp mMapper4; + AIMapper* mAIMapper = nullptr; protected: - void Initialize(std::string allocatorService, std::string mapperService) { + void Initialize(std::string allocatorService) { mAllocator = IAllocator::fromBinder( ndk::SpAIBinder(AServiceManager_checkService(allocatorService.c_str()))); - mMapper = IMapper::getService(mapperService); + ASSERT_TRUE(mAllocator->getInterfaceVersion(&mIAllocatorVersion).isOk()); + if (mIAllocatorVersion >= 2) { + std::string mapperSuffix; + auto status = mAllocator->getIMapperLibrarySuffix(&mapperSuffix); + ASSERT_TRUE(status.isOk()); + std::string lib_name = "mapper." + mapperSuffix + ".so"; + void* so = android_load_sphal_library(lib_name.c_str(), RTLD_LOCAL | RTLD_NOW); + ASSERT_NE(nullptr, so) << "Failed to load " << lib_name; + auto loadIMapper = (AIMapper_loadIMapperFn)dlsym(so, "AIMapper_loadIMapper"); + ASSERT_NE(nullptr, loadIMapper) << "AIMapper_locaIMapper missing from " << lib_name; + ASSERT_EQ(AIMAPPER_ERROR_NONE, loadIMapper(&mAIMapper)); + ASSERT_NE(mAIMapper, nullptr); + } else { + // Don't have IMapper 5, fall back to IMapper 4 + mMapper4 = IMapper4::getService(); + ASSERT_NE(nullptr, mMapper4.get()) << "failed to get mapper service"; + ASSERT_FALSE(mMapper4->isRemote()) << "mapper is not in passthrough mode"; + } ASSERT_NE(nullptr, mAllocator.get()) << "failed to get allocator service"; - ASSERT_NE(nullptr, mMapper.get()) << "failed to get mapper service"; - ASSERT_FALSE(mMapper->isRemote()) << "mapper is not in passthrough mode"; } - public: - BufferDescriptor createDescriptor(const IMapper::BufferDescriptorInfo& descriptorInfo) { + private: + BufferDescriptor createDescriptor(const BufferDescriptorInfo& descriptorInfo) { BufferDescriptor descriptor; - mMapper->createDescriptor( - descriptorInfo, [&](const auto& tmpError, const auto& tmpDescriptor) { + mMapper4->createDescriptor( + convert(descriptorInfo), [&](const auto& tmpError, const auto& tmpDescriptor) { ASSERT_EQ(Error::NONE, tmpError) << "failed to create descriptor"; descriptor = tmpDescriptor; }); @@ -140,14 +172,22 @@ class GraphicsTestsBase { return descriptor; } - std::unique_ptr allocate(const IMapper::BufferDescriptorInfo& descriptorInfo) { - auto descriptor = createDescriptor(descriptorInfo); - if (::testing::Test::HasFatalFailure()) { - return nullptr; - } - + public: + std::unique_ptr allocate(const BufferDescriptorInfo& descriptorInfo) { AllocationResult result; - auto status = mAllocator->allocate(descriptor, 1, &result); + ::ndk::ScopedAStatus status; + if (mIAllocatorVersion >= 2) { + status = mAllocator->allocate2(descriptorInfo, 1, &result); + } else { + auto descriptor = createDescriptor(descriptorInfo); + if (::testing::Test::HasFatalFailure()) { + return nullptr; + } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + status = mAllocator->allocate(descriptor, 1, &result); +#pragma clang diagnostic pop // deprecation + } if (!status.isOk()) { status_t error = status.getExceptionCode(); if (error == EX_SERVICE_SPECIFIC) { @@ -158,28 +198,48 @@ class GraphicsTestsBase { } return nullptr; } else { - return std::make_unique(mMapper, dupFromAidl(result.buffers[0]), false, + return std::make_unique(*this, dupFromAidl(result.buffers[0]), false, result.stride, descriptorInfo); } } - bool isSupported(const IMapper::BufferDescriptorInfo& descriptorInfo) { + bool isSupported(const BufferDescriptorInfo& descriptorInfo) { bool ret = false; - EXPECT_TRUE(mMapper->isSupported(descriptorInfo, - [&](auto error, bool supported) { - ASSERT_EQ(Error::NONE, error); - ret = supported; - }) - .isOk()); + if (mIAllocatorVersion >= 2) { + EXPECT_TRUE(mAllocator->isSupported(descriptorInfo, &ret).isOk()); + } else { + EXPECT_TRUE(mMapper4->isSupported(convert(descriptorInfo), + [&](auto error, bool supported) { + ASSERT_EQ(Error::NONE, error); + ret = supported; + }) + .isOk()); + } return ret; } }; -class GraphicsAllocatorAidlTests - : public GraphicsTestsBase, - public ::testing::TestWithParam> { +BufferHandle::~BufferHandle() { + if (mRawHandle == nullptr) return; + + if (mImported) { + if (mTestBase.mAIMapper) { + AIMapper_Error error = mTestBase.mAIMapper->v5.freeBuffer(mRawHandle); + EXPECT_EQ(AIMAPPER_ERROR_NONE, error); + } else { + Error error = mTestBase.mMapper4->freeBuffer(mRawHandle); + EXPECT_EQ(Error::NONE, error) << "failed to free buffer " << mRawHandle; + } + } else { + native_handle_close(mRawHandle); + native_handle_delete(mRawHandle); + } +} + +class GraphicsAllocatorAidlTests : public GraphicsTestsBase, + public ::testing::TestWithParam { public: - void SetUp() override { Initialize(std::get<0>(GetParam()), std::get<1>(GetParam())); } + void SetUp() override { Initialize(GetParam()); } void TearDown() override {} }; @@ -191,22 +251,22 @@ struct FlushMethod { class GraphicsFrontBufferTests : public GraphicsTestsBase, - public ::testing::TestWithParam> { + public ::testing::TestWithParam> { private: EglManager eglManager; std::function flush; public: void SetUp() override { - Initialize(std::get<0>(GetParam()), std::get<1>(GetParam())); - flush = std::get<2>(GetParam()).func; + Initialize(std::get<0>(GetParam())); + flush = std::get<1>(GetParam()).func; eglManager.initialize(); } void TearDown() override { eglManager.destroy(); } void fillWithGpu(AHardwareBuffer* buffer, float red, float green, float blue, float alpha) { - const EGLClientBuffer clientBuffer = eglGetNativeClientBufferANDROID(buffer); + EGLClientBuffer clientBuffer = eglGetNativeClientBufferANDROID(buffer); AutoEglImage eglImage(eglManager.eglDisplay(), clientBuffer); AutoSkiaGlTexture glTexture; AutoGLFramebuffer glFbo; @@ -235,26 +295,14 @@ class GraphicsFrontBufferTests } }; -TEST_P(GraphicsAllocatorAidlTests, CreateDescriptorBasic) { - ASSERT_NO_FATAL_FAILURE(createDescriptor({ - .name = "CPU_8888", - .width = 64, - .height = 64, - .layerCount = 1, - .format = cast(PixelFormat::RGBA_8888), - .usage = pack({BufferUsage::CPU_WRITE_OFTEN, BufferUsage::CPU_READ_OFTEN}), - .reservedSize = 0, - })); -} - TEST_P(GraphicsAllocatorAidlTests, CanAllocate) { auto buffer = allocate({ - .name = "CPU_8888", + .name = {"CPU_8888"}, .width = 64, .height = 64, .layerCount = 1, - .format = cast(PixelFormat::RGBA_8888), - .usage = pack({BufferUsage::CPU_WRITE_OFTEN, BufferUsage::CPU_READ_OFTEN}), + .format = PixelFormat::RGBA_8888, + .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN, .reservedSize = 0, }); ASSERT_NE(nullptr, buffer.get()); @@ -262,14 +310,14 @@ TEST_P(GraphicsAllocatorAidlTests, CanAllocate) { } TEST_P(GraphicsFrontBufferTests, FrontBufferGpuToCpu) { - IMapper::BufferDescriptorInfo info{ - .name = "CPU_8888", + BufferDescriptorInfo info{ + .name = {"CPU_8888"}, .width = 64, .height = 64, .layerCount = 1, - .format = cast(PixelFormat::RGBA_8888), - .usage = pack({BufferUsage::GPU_RENDER_TARGET, BufferUsage::CPU_READ_OFTEN, - BufferUsage::FRONT_BUFFER}), + .format = PixelFormat::RGBA_8888, + .usage = BufferUsage::GPU_RENDER_TARGET | BufferUsage::CPU_READ_OFTEN | + BufferUsage::FRONT_BUFFER, .reservedSize = 0, }; const bool supported = isSupported(info); @@ -304,14 +352,14 @@ TEST_P(GraphicsFrontBufferTests, FrontBufferGpuToCpu) { } TEST_P(GraphicsFrontBufferTests, FrontBufferGpuToGpu) { - IMapper::BufferDescriptorInfo info{ - .name = "CPU_8888", + BufferDescriptorInfo info{ + .name = {"CPU_8888"}, .width = 64, .height = 64, .layerCount = 1, - .format = cast(PixelFormat::RGBA_8888), - .usage = pack({BufferUsage::GPU_RENDER_TARGET, BufferUsage::GPU_TEXTURE, - BufferUsage::FRONT_BUFFER}), + .format = PixelFormat::RGBA_8888, + .usage = BufferUsage::GPU_RENDER_TARGET | BufferUsage::GPU_TEXTURE | + BufferUsage::FRONT_BUFFER, .reservedSize = 0, }; const bool supported = isSupported(info); @@ -344,11 +392,9 @@ TEST_P(GraphicsFrontBufferTests, FrontBufferGpuToGpu) { } GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GraphicsAllocatorAidlTests); -INSTANTIATE_TEST_CASE_P( - PerInstance, GraphicsAllocatorAidlTests, - testing::Combine(testing::ValuesIn(getAidlHalInstanceNames(IAllocator::descriptor)), - testing::ValuesIn(getAllHalInstanceNames(IMapper::descriptor))), - PrintInstanceTupleNameToString<>); +INSTANTIATE_TEST_CASE_P(PerInstance, GraphicsAllocatorAidlTests, + testing::ValuesIn(getAidlHalInstanceNames(IAllocator::descriptor)), + PrintInstanceNameToString); const auto FlushMethodsValues = testing::Values( FlushMethod{"glFinish", [](EglManager&) { glFinish(); }}, @@ -362,7 +408,7 @@ const auto FlushMethodsValues = testing::Values( }}, FlushMethod{"eglClientWaitSync", [](EglManager& eglManager) { EGLDisplay display = eglManager.eglDisplay(); - EGLSyncKHR fence = eglCreateSyncKHR(display, EGL_SYNC_FENCE_KHR, NULL); + EGLSyncKHR fence = eglCreateSyncKHR(display, EGL_SYNC_FENCE_KHR, nullptr); eglClientWaitSyncKHR(display, fence, EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, EGL_FOREVER_KHR); eglDestroySyncKHR(display, fence); @@ -371,9 +417,8 @@ GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GraphicsFrontBufferTests); INSTANTIATE_TEST_CASE_P( PerInstance, GraphicsFrontBufferTests, testing::Combine(testing::ValuesIn(getAidlHalInstanceNames(IAllocator::descriptor)), - testing::ValuesIn(getAllHalInstanceNames(IMapper::descriptor)), FlushMethodsValues), [](auto info) -> std::string { - std::string name = std::to_string(info.index) + "/" + std::get<2>(info.param).name; + std::string name = std::to_string(info.index) + "/" + std::get<1>(info.param).name; return Sanitize(name); - }); + }); \ No newline at end of file diff --git a/graphics/mapper/stable-c/Android.bp b/graphics/mapper/stable-c/Android.bp new file mode 100644 index 0000000000..c03f67ea47 --- /dev/null +++ b/graphics/mapper/stable-c/Android.bp @@ -0,0 +1,104 @@ +/** + * Copyright (c) 2022, 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. + */ + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "hardware_interfaces_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["hardware_interfaces_license"], +} + +cc_library_headers { + name: "libimapper_stablec", + export_include_dirs: ["include"], + vendor_available: true, + header_libs: [ + "libarect_headers", + ], + export_header_lib_headers: [ + "libarect_headers", + ], +} + +cc_library_headers { + name: "libimapper_providerutils", + vendor_available: true, + export_include_dirs: ["implutils/include"], + header_libs: [ + "libbase_headers", + "libimapper_stablec", + ], + export_header_lib_headers: [ + "libbase_headers", + "libimapper_stablec", + ], +} + +cc_test { + name: "libimapper_providerutils_tests", + defaults: [ + "android.hardware.graphics.allocator-ndk_shared", + "android.hardware.graphics.common-ndk_shared", + ], + header_libs: [ + "libimapper_providerutils", + ], + srcs: [ + "implutils/impltests.cpp", + ], + visibility: [":__subpackages__"], + cpp_std: "experimental", +} + +cc_test { + name: "VtsHalGraphicsMapperStableC_TargetTest", + cpp_std: "experimental", + defaults: [ + "VtsHalTargetTestDefaults", + "use_libaidlvintf_gtest_helper_static", + "android.hardware.graphics.allocator-ndk_shared", + "android.hardware.graphics.common-ndk_shared", + ], + srcs: [ + "vts/VtsHalGraphicsMapperStableC_TargetTest.cpp", + ], + + shared_libs: [ + "libbinder_ndk", + "libbase", + "libsync", + "libvndksupport", + ], + static_libs: [ + "libaidlcommonsupport", + "libgralloctypes", + "libgtest", + ], + header_libs: [ + "libimapper_stablec", + "libimapper_providerutils", + ], + cflags: [ + "-Wall", + "-Werror", + ], + test_suites: [ + "general-tests", + "vts", + ], +} diff --git a/graphics/mapper/stable-c/implutils/impltests.cpp b/graphics/mapper/stable-c/implutils/impltests.cpp new file mode 100644 index 0000000000..9c5d70b498 --- /dev/null +++ b/graphics/mapper/stable-c/implutils/impltests.cpp @@ -0,0 +1,314 @@ +/* + * Copyright (C) 2022 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 + +#include +#include +#include + +using namespace ::android::hardware::graphics::mapper; +using namespace ::aidl::android::hardware::graphics::common; + +// These tests are primarily interested in hitting all the different *types* that can be +// serialized/deserialized than in exhaustively testing all the StandardMetadataTypes. +// Exhaustive testing of the actual metadata types is relegated for IMapper's VTS suite +// where meaning & correctness of values are more narrowly defined (eg, read-only values) + +TEST(Metadata, setGetBufferId) { + using BufferId = StandardMetadata::value; + + std::vector buffer; + buffer.resize(12, 0); + *reinterpret_cast(buffer.data()) = 42; + + EXPECT_EQ(8, BufferId::encode(18, buffer.data(), 0)); + EXPECT_EQ(42, *reinterpret_cast(buffer.data())); + EXPECT_EQ(8, BufferId::encode(18, buffer.data(), buffer.size())); + EXPECT_EQ(18, *reinterpret_cast(buffer.data())); + EXPECT_FALSE(BufferId::decode(buffer.data(), 0)); + auto read = BufferId::decode(buffer.data(), buffer.size()); + EXPECT_TRUE(read.has_value()); + EXPECT_EQ(18, read.value_or(0)); +} + +TEST(Metadata, setGetDataspace) { + using DataspaceValue = StandardMetadata::value; + using intType = std::underlying_type_t; + std::vector buffer; + buffer.resize(12, 0); + + EXPECT_EQ(4, DataspaceValue::encode(Dataspace::BT2020, buffer.data(), 0)); + EXPECT_EQ(0, *reinterpret_cast(buffer.data())); + EXPECT_EQ(4, DataspaceValue::encode(Dataspace::BT2020, buffer.data(), buffer.size())); + EXPECT_EQ(static_cast(Dataspace::BT2020), *reinterpret_cast(buffer.data())); + EXPECT_FALSE(DataspaceValue::decode(buffer.data(), 0)); + auto read = DataspaceValue::decode(buffer.data(), buffer.size()); + ASSERT_TRUE(read.has_value()); + EXPECT_EQ(Dataspace::BT2020, *read); +} + +TEST(Metadata, setGetValidName) { + using NameValue = StandardMetadata::value; + + std::vector buffer; + buffer.resize(100, 'a'); + buffer[buffer.size() - 1] = '\0'; + + // len("Hello") + sizeof(int64) + constexpr int expectedSize = 5 + sizeof(int64_t); + EXPECT_EQ(expectedSize, NameValue::encode("Hello", buffer.data(), buffer.size())); + EXPECT_EQ(5, *reinterpret_cast(buffer.data())); + // Verify didn't write past the end of the desired size + EXPECT_EQ('a', buffer[expectedSize]); + + auto readValue = NameValue::decode(buffer.data(), buffer.size()); + ASSERT_TRUE(readValue.has_value()); + EXPECT_EQ(5, readValue->length()); + EXPECT_EQ("Hello", *readValue); +} + +TEST(Metadata, setGetInvalidName) { + using NameValue = StandardMetadata::value; + + std::vector buffer; + buffer.resize(12, 'a'); + buffer[buffer.size() - 1] = '\0'; + + // len("This is a long string") + sizeof(int64) + constexpr int expectedSize = 21 + sizeof(int64_t); + EXPECT_EQ(expectedSize, + NameValue::encode("This is a long string", buffer.data(), buffer.size())); + EXPECT_EQ(21, *reinterpret_cast(buffer.data())); + // Verify didn't write the too-long string + EXPECT_EQ('a', buffer[9]); + EXPECT_EQ('\0', buffer[buffer.size() - 1]); + + auto readValue = NameValue::decode(buffer.data(), buffer.size()); + EXPECT_FALSE(readValue.has_value()); + readValue = NameValue::decode(buffer.data(), 0); + ASSERT_FALSE(readValue.has_value()); +} + +TEST(Metadata, wouldOverflowName) { + using NameValue = StandardMetadata::value; + std::vector buffer(100, 0); + + // int_max + sizeof(int64) overflows int32 + std::string_view bad_string{"badbeef", std::numeric_limits::max()}; + EXPECT_EQ(-AIMAPPER_ERROR_BAD_VALUE, + NameValue::encode(bad_string, buffer.data(), buffer.size())); + + // check barely overflows + bad_string = std::string_view{"badbeef", std::numeric_limits::max() - 7}; + EXPECT_EQ(-AIMAPPER_ERROR_BAD_VALUE, + NameValue::encode(bad_string, buffer.data(), buffer.size())); +} + +TEST(Metadata, setGetCompression) { + using CompressionValue = StandardMetadata::value; + ExtendableType myCompression{"bestest_compression_ever", 42}; + std::vector buffer(100, '\0'); + const int expectedSize = myCompression.name.length() + sizeof(int64_t) + sizeof(int64_t); + EXPECT_EQ(expectedSize, CompressionValue::encode(myCompression, buffer.data(), 0)); + EXPECT_EQ(0, buffer[0]); + EXPECT_EQ(expectedSize, CompressionValue::encode(myCompression, buffer.data(), buffer.size())); + EXPECT_EQ(myCompression.name.length(), *reinterpret_cast(buffer.data())); + EXPECT_FALSE(CompressionValue::decode(buffer.data(), 0).has_value()); + auto read = CompressionValue::decode(buffer.data(), buffer.size()); + ASSERT_TRUE(read.has_value()); + EXPECT_EQ(myCompression, read.value()); +} + +TEST(Metadata, setGetPlaneLayout) { + using PlaneLayoutValue = StandardMetadata::value; + PlaneLayout myPlaneLayout; + myPlaneLayout.offsetInBytes = 10; + myPlaneLayout.sampleIncrementInBits = 11; + myPlaneLayout.strideInBytes = 12; + myPlaneLayout.widthInSamples = 13; + myPlaneLayout.heightInSamples = 14; + myPlaneLayout.totalSizeInBytes = 15; + myPlaneLayout.horizontalSubsampling = 16; + myPlaneLayout.verticalSubsampling = 17; + + myPlaneLayout.components.resize(3); + for (int i = 0; i < myPlaneLayout.components.size(); i++) { + auto& it = myPlaneLayout.components[i]; + it.type = ExtendableType{"Plane ID", 40 + i}; + it.offsetInBits = 20 + i; + it.sizeInBits = 30 + i; + } + + std::vector layouts{myPlaneLayout, PlaneLayout{}}; + + std::vector buffer(5000, '\0'); + constexpr int componentSize = 8 + (4 * sizeof(int64_t)); + constexpr int firstLayoutSize = (8 + 1) * sizeof(int64_t) + (3 * componentSize); + constexpr int secondLayoutSize = (8 + 1) * sizeof(int64_t); + constexpr int expectedSize = firstLayoutSize + secondLayoutSize + sizeof(int64_t); + EXPECT_EQ(expectedSize, PlaneLayoutValue::encode(layouts, buffer.data(), 0)); + EXPECT_EQ(0, buffer[0]); + EXPECT_EQ(expectedSize, PlaneLayoutValue::encode(layouts, buffer.data(), buffer.size())); + EXPECT_EQ(3, reinterpret_cast(buffer.data())[1]); + EXPECT_EQ(8, reinterpret_cast(buffer.data())[2]); + EXPECT_EQ(40, reinterpret_cast(buffer.data())[4]); + EXPECT_EQ(31, reinterpret_cast(buffer.data())[11]); + EXPECT_EQ(22, reinterpret_cast(buffer.data())[15]); + EXPECT_EQ(10, reinterpret_cast(buffer.data())[17]); + EXPECT_EQ(11, reinterpret_cast(buffer.data())[18]); + EXPECT_FALSE(PlaneLayoutValue::decode(buffer.data(), 0).has_value()); + auto read = PlaneLayoutValue::decode(buffer.data(), buffer.size()); + ASSERT_TRUE(read.has_value()); + EXPECT_EQ(layouts, *read); +} + +TEST(Metadata, setGetRects) { + using RectsValue = StandardMetadata::value; + std::vector buffer(500, 0); + std::vector cropRects{2}; + cropRects[0] = Rect{10, 11, 12, 13}; + cropRects[1] = Rect{20, 21, 22, 23}; + + constexpr int expectedSize = sizeof(int64_t) + (8 * sizeof(int32_t)); + EXPECT_EQ(expectedSize, RectsValue::encode(cropRects, buffer.data(), buffer.size())); + EXPECT_EQ(2, reinterpret_cast(buffer.data())[0]); + EXPECT_EQ(10, reinterpret_cast(buffer.data())[2]); + auto read = RectsValue::decode(buffer.data(), buffer.size()); + ASSERT_TRUE(read.has_value()); + EXPECT_EQ(cropRects.size(), read->size()); + EXPECT_EQ(cropRects, *read); +} + +TEST(Metadata, setGetSmpte2086) { + using Smpte2086Value = StandardMetadata::value; + Smpte2086 source; + source.minLuminance = 12.335f; + source.maxLuminance = 452.889f; + source.whitePoint = XyColor{-6.f, -9.f}; + source.primaryRed = XyColor{.1f, .2f}; + source.primaryGreen = XyColor{.3f, .4f}; + source.primaryBlue = XyColor{.5f, .6f}; + + constexpr int expectedSize = 10 * sizeof(float); + std::vector buffer(500, 0); + EXPECT_EQ(expectedSize, Smpte2086Value::encode(source, buffer.data(), buffer.size())); + auto read = Smpte2086Value::decode(buffer.data(), buffer.size()); + ASSERT_TRUE(read.has_value()); + ASSERT_TRUE(read->has_value()); + EXPECT_EQ(source, read->value()); + + // A valid encoding of a nullopt + read = Smpte2086Value::decode(nullptr, 0); + ASSERT_TRUE(read.has_value()); + EXPECT_FALSE(read->has_value()); +} + +TEST(Metadata, setGetCta861_3) { + using Cta861_3Value = StandardMetadata::value; + Cta861_3 source; + source.maxFrameAverageLightLevel = 244.55f; + source.maxContentLightLevel = 202.202f; + + constexpr int expectedSize = 2 * sizeof(float); + std::vector buffer(500, 0); + EXPECT_EQ(expectedSize, Cta861_3Value::encode(source, buffer.data(), buffer.size())); + auto read = Cta861_3Value::decode(buffer.data(), buffer.size()); + ASSERT_TRUE(read.has_value()); + ASSERT_TRUE(read->has_value()); + EXPECT_EQ(source, read->value()); + + // A valid encoding of a nullopt + read = Cta861_3Value::decode(nullptr, 0); + ASSERT_TRUE(read.has_value()); + EXPECT_FALSE(read->has_value()); +} + +TEST(Metadata, setGetSmpte2094_10) { + using SMPTE2094_10Value = StandardMetadata::value; + + std::vector buffer(500, 0); + EXPECT_EQ(0, SMPTE2094_10Value::encode(std::nullopt, buffer.data(), buffer.size())); + auto read = SMPTE2094_10Value::decode(buffer.data(), 0); + ASSERT_TRUE(read.has_value()); + EXPECT_FALSE(read->has_value()); + + const std::vector emptyBuffer; + EXPECT_EQ(sizeof(int64_t), + SMPTE2094_10Value::encode(emptyBuffer, buffer.data(), buffer.size())); + read = SMPTE2094_10Value::decode(buffer.data(), buffer.size()); + ASSERT_TRUE(read.has_value()); + ASSERT_TRUE(read->has_value()); + EXPECT_EQ(0, read->value().size()); + + const std::vector simpleBuffer{0, 1, 2, 3, 4, 5}; + EXPECT_EQ(sizeof(int64_t) + 6, + SMPTE2094_10Value::encode(simpleBuffer, buffer.data(), buffer.size())); + read = SMPTE2094_10Value::decode(buffer.data(), buffer.size()); + ASSERT_TRUE(read.has_value()); + ASSERT_TRUE(read->has_value()); + EXPECT_EQ(6, read->value().size()); + EXPECT_EQ(simpleBuffer, read->value()); +} + +TEST(MetadataProvider, bufferId) { + using BufferId = StandardMetadata::value; + std::vector buffer(500, 0); + int result = provideStandardMetadata(StandardMetadataType::BUFFER_ID, buffer.data(), + buffer.size(), [](auto&& provide) { + if constexpr (T == StandardMetadataType::BUFFER_ID) { + return provide(42); + } + return 0; + }); + + EXPECT_EQ(8, result); + auto read = BufferId::decode(buffer.data(), buffer.size()); + EXPECT_EQ(42, read.value_or(0)); +} + +TEST(MetadataProvider, allJumpsWork) { + const auto& values = ndk::internal::enum_values; + auto get = [](StandardMetadataType type) -> int { + return provideStandardMetadata(type, nullptr, 0, [](auto&&) { + return static_cast(T) + 100; + }); + }; + + for (auto& type : values) { + const int expected = type == StandardMetadataType::INVALID ? -AIMAPPER_ERROR_UNSUPPORTED + : static_cast(type) + 100; + EXPECT_EQ(expected, get(type)); + } +} + +TEST(MetadataProvider, invalid) { + int result = provideStandardMetadata(StandardMetadataType::INVALID, nullptr, 0, + [](auto&&) { return 10; }); + + EXPECT_EQ(-AIMAPPER_ERROR_UNSUPPORTED, result); +} + +TEST(MetadataProvider, outOfBounds) { + int result = provideStandardMetadata(static_cast(-1), nullptr, 0, + [](auto&&) { return 10; }); + EXPECT_EQ(-AIMAPPER_ERROR_UNSUPPORTED, result) << "-1 should have resulted in UNSUPPORTED"; + + result = provideStandardMetadata(static_cast(100), nullptr, 0, + [](auto&&) { return 10; }); + EXPECT_EQ(-AIMAPPER_ERROR_UNSUPPORTED, result) + << "100 (out of range) should have resulted in UNSUPPORTED"; +} diff --git a/graphics/mapper/stable-c/implutils/include/android/hardware/graphics/mapper/utils/IMapperMetadataTypes.h b/graphics/mapper/stable-c/implutils/include/android/hardware/graphics/mapper/utils/IMapperMetadataTypes.h new file mode 100644 index 0000000000..7861af87fc --- /dev/null +++ b/graphics/mapper/stable-c/implutils/include/android/hardware/graphics/mapper/utils/IMapperMetadataTypes.h @@ -0,0 +1,576 @@ +/* + * Copyright (C) 2022 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. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace android::hardware::graphics::mapper { + +using ::aidl::android::hardware::graphics::common::BlendMode; +using ::aidl::android::hardware::graphics::common::BufferUsage; +using ::aidl::android::hardware::graphics::common::Cta861_3; +using ::aidl::android::hardware::graphics::common::Dataspace; +using ::aidl::android::hardware::graphics::common::ExtendableType; +using ::aidl::android::hardware::graphics::common::PixelFormat; +using ::aidl::android::hardware::graphics::common::PlaneLayout; +using ::aidl::android::hardware::graphics::common::PlaneLayoutComponent; +using ::aidl::android::hardware::graphics::common::Rect; +using ::aidl::android::hardware::graphics::common::Smpte2086; +using ::aidl::android::hardware::graphics::common::StandardMetadataType; +using ::aidl::android::hardware::graphics::common::XyColor; + +class MetadataWriter { + private: + uint8_t* _Nonnull mDest; + size_t mSizeRemaining = 0; + int32_t mDesiredSize = 0; + + void* _Nullable reserve(size_t sizeToWrite) { + if (mDesiredSize < 0) { + // Error state + return nullptr; + } + if (__builtin_add_overflow(mDesiredSize, sizeToWrite, &mDesiredSize)) { + // Overflowed, abort writing any further data + mDesiredSize = -AIMAPPER_ERROR_BAD_VALUE; + mSizeRemaining = 0; + return nullptr; + } + if (sizeToWrite > mSizeRemaining) { + mSizeRemaining = 0; + return nullptr; + } else { + mSizeRemaining -= sizeToWrite; + uint8_t* whereToWrite = mDest; + mDest += sizeToWrite; + return whereToWrite; + } + } + + public: + explicit MetadataWriter(void* _Nullable destBuffer, size_t destBufferSize) + : mDest(reinterpret_cast(destBuffer)), mSizeRemaining(destBufferSize) {} + + int32_t desiredSize() const { return mDesiredSize; } + + template >> + MetadataWriter& write(T value) { + auto sizeToWrite = sizeof(T); + if (void* dest = reserve(sizeToWrite)) { + memcpy(dest, &value, sizeToWrite); + } + return *this; + } + + MetadataWriter& write(float value) { + auto sizeToWrite = sizeof(float); + if (void* dest = reserve(sizeToWrite)) { + memcpy(dest, &value, sizeToWrite); + } + return *this; + } + + MetadataWriter& write(const std::string_view& value) { + auto sizeToWrite = value.length(); + write(sizeToWrite); + if (void* dest = reserve(sizeToWrite)) { + memcpy(dest, value.data(), sizeToWrite); + } + return *this; + } + + MetadataWriter& write(const std::vector& value) { + auto sizeToWrite = value.size(); + write(sizeToWrite); + if (void* dest = reserve(sizeToWrite)) { + memcpy(dest, value.data(), sizeToWrite); + } + return *this; + } + + MetadataWriter& write(const ExtendableType& value) { + return write(value.name).write(value.value); + } + + MetadataWriter& write(const XyColor& value) { return write(value.x).write(value.y); } +}; + +class MetadataReader { + private: + const uint8_t* _Nonnull mSrc; + size_t mSizeRemaining = 0; + bool mOk = true; + + const void* _Nullable advance(size_t size) { + if (mOk && mSizeRemaining >= size) { + const void* buf = mSrc; + mSrc += size; + mSizeRemaining -= size; + return buf; + } + mOk = false; + return nullptr; + } + + public: + explicit MetadataReader(const void* _Nonnull metadata, size_t metadataSize) + : mSrc(reinterpret_cast(metadata)), mSizeRemaining(metadataSize) {} + + [[nodiscard]] size_t remaining() const { return mSizeRemaining; } + [[nodiscard]] bool ok() const { return mOk; } + + template >> + MetadataReader& read(T& dest) { + if (const void* src = advance(sizeof(T))) { + memcpy(&dest, src, sizeof(T)); + } + return *this; + } + + MetadataReader& read(float& dest) { + if (const void* src = advance(sizeof(float))) { + memcpy(&dest, src, sizeof(float)); + } + return *this; + } + + MetadataReader& read(std::string& dest) { + dest = readString(); + return *this; + } + + MetadataReader& read(ExtendableType& dest) { + dest.name = readString(); + read(dest.value); + return *this; + } + + MetadataReader& read(XyColor& dest) { + read(dest.x); + read(dest.y); + return *this; + } + + template >> + [[nodiscard]] std::optional readInt() { + auto sizeToRead = sizeof(T); + if (const void* src = advance(sizeof(T))) { + T ret; + memcpy(&ret, src, sizeToRead); + return ret; + } + return std::nullopt; + } + + [[nodiscard]] std::string_view readString() { + auto lengthOpt = readInt(); + if (!lengthOpt) { + return std::string_view{}; + } + size_t length = lengthOpt.value(); + if (const void* src = advance(length)) { + return std::string_view{reinterpret_cast(src), length}; + } + return std::string_view{}; + } + + [[nodiscard]] std::optional readExtendable() { + ExtendableType ret; + ret.name = readString(); + auto value = readInt(); + if (value) { + ret.value = value.value(); + return ret; + } else { + return std::nullopt; + } + } + + [[nodiscard]] std::vector readBuffer() { + std::vector ret; + size_t length = readInt().value_or(0); + if (const void* src = advance(length)) { + ret.resize(length); + memcpy(ret.data(), src, length); + } + return ret; + } +}; + +template +struct MetadataValue {}; + +template +struct MetadataValue>> { + [[nodiscard]] static int32_t encode(T value, void* _Nullable destBuffer, + size_t destBufferSize) { + return MetadataWriter{destBuffer, destBufferSize}.write(value).desiredSize(); + } + + [[nodiscard]] static std::optional decode(const void* _Nonnull metadata, + size_t metadataSize) { + return MetadataReader{metadata, metadataSize}.readInt(); + } +}; + +template +struct MetadataValue>> { + [[nodiscard]] static int32_t encode(T value, void* _Nullable destBuffer, + size_t destBufferSize) { + return MetadataWriter{destBuffer, destBufferSize} + .write(static_cast>(value)) + .desiredSize(); + } + + [[nodiscard]] static std::optional decode(const void* _Nonnull metadata, + size_t metadataSize) { + std::underlying_type_t temp; + return MetadataReader{metadata, metadataSize}.read(temp).ok() + ? std::optional(static_cast(temp)) + : std::nullopt; + } +}; + +template <> +struct MetadataValue { + [[nodiscard]] static int32_t encode(const std::string_view& value, void* _Nullable destBuffer, + size_t destBufferSize) { + return MetadataWriter{destBuffer, destBufferSize}.write(value).desiredSize(); + } + + [[nodiscard]] static std::optional decode(const void* _Nonnull metadata, + size_t metadataSize) { + auto reader = MetadataReader{metadata, metadataSize}; + auto result = reader.readString(); + return reader.ok() ? std::optional{result} : std::nullopt; + } +}; + +template <> +struct MetadataValue { + static_assert(sizeof(int64_t) == sizeof(ExtendableType::value)); + + [[nodiscard]] static int32_t encode(const ExtendableType& value, void* _Nullable destBuffer, + size_t destBufferSize) { + return MetadataWriter{destBuffer, destBufferSize}.write(value).desiredSize(); + } + + [[nodiscard]] static std::optional decode(const void* _Nonnull metadata, + size_t metadataSize) { + return MetadataReader{metadata, metadataSize}.readExtendable(); + } +}; + +template <> +struct MetadataValue> { + [[nodiscard]] static int32_t encode(const std::vector& values, + void* _Nullable destBuffer, size_t destBufferSize) { + MetadataWriter writer{destBuffer, destBufferSize}; + writer.write(values.size()); + for (const auto& value : values) { + writer.write(value.components.size()); + for (const auto& component : value.components) { + writer.write(component.type) + .write(component.offsetInBits) + .write(component.sizeInBits); + } + writer.write(value.offsetInBytes) + .write(value.sampleIncrementInBits) + .write(value.strideInBytes) + .write(value.widthInSamples) + .write(value.heightInSamples) + .write(value.totalSizeInBytes) + .write(value.horizontalSubsampling) + .write(value.verticalSubsampling); + } + return writer.desiredSize(); + } + + using DecodeResult = std::optional>; + [[nodiscard]] static DecodeResult decode(const void* _Nonnull metadata, size_t metadataSize) { + std::vector values; + MetadataReader reader{metadata, metadataSize}; + auto numPlanes = reader.readInt().value_or(0); + values.reserve(numPlanes); + for (int i = 0; i < numPlanes && reader.ok(); i++) { + PlaneLayout& value = values.emplace_back(); + auto numPlaneComponents = reader.readInt().value_or(0); + value.components.reserve(numPlaneComponents); + for (int i = 0; i < numPlaneComponents && reader.ok(); i++) { + PlaneLayoutComponent& component = value.components.emplace_back(); + reader.read(component.type) + .read(component.offsetInBits) + .read(component.sizeInBits); + } + reader.read(value.offsetInBytes) + .read(value.sampleIncrementInBits) + .read(value.strideInBytes) + .read(value.widthInSamples) + .read(value.heightInSamples) + .read(value.totalSizeInBytes) + .read(value.horizontalSubsampling) + .read(value.verticalSubsampling); + } + return reader.ok() ? DecodeResult{std::move(values)} : std::nullopt; + } +}; + +template <> +struct MetadataValue> { + [[nodiscard]] static int32_t encode(const std::vector& value, void* _Nullable destBuffer, + size_t destBufferSize) { + MetadataWriter writer{destBuffer, destBufferSize}; + writer.write(value.size()); + for (auto& rect : value) { + writer.write(rect.left) + .write(rect.top) + .write(rect.right) + .write(rect.bottom); + } + return writer.desiredSize(); + } + + using DecodeResult = std::optional>; + [[nodiscard]] static DecodeResult decode(const void* _Nonnull metadata, size_t metadataSize) { + MetadataReader reader{metadata, metadataSize}; + std::vector value; + auto numRects = reader.readInt().value_or(0); + value.reserve(numRects); + for (int i = 0; i < numRects && reader.ok(); i++) { + Rect& rect = value.emplace_back(); + reader.read(rect.left) + .read(rect.top) + .read(rect.right) + .read(rect.bottom); + } + return reader.ok() ? DecodeResult{std::move(value)} : std::nullopt; + } +}; + +template <> +struct MetadataValue> { + [[nodiscard]] static int32_t encode(const std::optional& optValue, + void* _Nullable destBuffer, size_t destBufferSize) { + if (optValue.has_value()) { + const auto& value = *optValue; + return MetadataWriter{destBuffer, destBufferSize} + .write(value.primaryRed) + .write(value.primaryGreen) + .write(value.primaryBlue) + .write(value.whitePoint) + .write(value.maxLuminance) + .write(value.minLuminance) + .desiredSize(); + } else { + return 0; + } + } + + // Double optional because the value type itself is an optional<> + using DecodeResult = std::optional>; + [[nodiscard]] static DecodeResult decode(const void* _Nullable metadata, size_t metadataSize) { + std::optional optValue{std::nullopt}; + if (metadataSize > 0) { + Smpte2086 value; + MetadataReader reader{metadata, metadataSize}; + reader.read(value.primaryRed) + .read(value.primaryGreen) + .read(value.primaryBlue) + .read(value.whitePoint) + .read(value.maxLuminance) + .read(value.minLuminance); + if (reader.ok()) { + optValue = std::move(value); + } else { + return std::nullopt; + } + } + return DecodeResult{std::move(optValue)}; + } +}; + +template <> +struct MetadataValue> { + [[nodiscard]] static int32_t encode(const std::optional& optValue, + void* _Nullable destBuffer, size_t destBufferSize) { + if (optValue.has_value()) { + const auto& value = *optValue; + return MetadataWriter{destBuffer, destBufferSize} + .write(value.maxContentLightLevel) + .write(value.maxFrameAverageLightLevel) + .desiredSize(); + } else { + return 0; + } + } + + // Double optional because the value type itself is an optional<> + using DecodeResult = std::optional>; + [[nodiscard]] static DecodeResult decode(const void* _Nullable metadata, size_t metadataSize) { + std::optional optValue{std::nullopt}; + if (metadataSize > 0) { + MetadataReader reader{metadata, metadataSize}; + Cta861_3 value; + reader.read(value.maxContentLightLevel).read(value.maxFrameAverageLightLevel); + if (reader.ok()) { + optValue = std::move(value); + } else { + return std::nullopt; + } + } + return DecodeResult{std::move(optValue)}; + } +}; + +template <> +struct MetadataValue>> { + [[nodiscard]] static int32_t encode(const std::optional>& value, + void* _Nullable destBuffer, size_t destBufferSize) { + if (!value.has_value()) { + return 0; + } + return MetadataWriter{destBuffer, destBufferSize}.write(*value).desiredSize(); + } + + using DecodeResult = std::optional>>; + [[nodiscard]] static DecodeResult decode(const void* _Nonnull metadata, size_t metadataSize) { + std::optional> optValue; + if (metadataSize > 0) { + MetadataReader reader{metadata, metadataSize}; + auto value = reader.readBuffer(); + if (reader.ok()) { + optValue = std::move(value); + } else { + return std::nullopt; + } + } + return DecodeResult{std::move(optValue)}; + } +}; + +template +struct StandardMetadata {}; + +#define DEFINE_TYPE(name, typeArg) \ + template <> \ + struct StandardMetadata { \ + using value_type = typeArg; \ + using value = MetadataValue; \ + static_assert( \ + StandardMetadataType::name == \ + ndk::internal::enum_values[static_cast( \ + StandardMetadataType::name)], \ + "StandardMetadataType must have equivalent value to index"); \ + } + +DEFINE_TYPE(BUFFER_ID, uint64_t); +DEFINE_TYPE(NAME, std::string); +DEFINE_TYPE(WIDTH, uint64_t); +DEFINE_TYPE(HEIGHT, uint64_t); +DEFINE_TYPE(LAYER_COUNT, uint64_t); +DEFINE_TYPE(PIXEL_FORMAT_REQUESTED, PixelFormat); +DEFINE_TYPE(PIXEL_FORMAT_FOURCC, uint32_t); +DEFINE_TYPE(PIXEL_FORMAT_MODIFIER, uint64_t); +DEFINE_TYPE(USAGE, BufferUsage); +DEFINE_TYPE(ALLOCATION_SIZE, uint64_t); +DEFINE_TYPE(PROTECTED_CONTENT, uint64_t); +DEFINE_TYPE(COMPRESSION, ExtendableType); +DEFINE_TYPE(INTERLACED, ExtendableType); +DEFINE_TYPE(CHROMA_SITING, ExtendableType); +DEFINE_TYPE(PLANE_LAYOUTS, std::vector); +DEFINE_TYPE(CROP, std::vector); +DEFINE_TYPE(DATASPACE, Dataspace); +DEFINE_TYPE(BLEND_MODE, BlendMode); +DEFINE_TYPE(SMPTE2086, std::optional); +DEFINE_TYPE(CTA861_3, std::optional); +DEFINE_TYPE(SMPTE2094_10, std::optional>); +DEFINE_TYPE(SMPTE2094_40, std::optional>); + +#undef DEFINE_TYPE + +template +void invokeWithStandardMetadata(F&& f, StandardMetadataType type, std::index_sequence) { + // Setup the jump table, mapping from each type to a springboard that invokes the template + // function with the appropriate concrete type + using F_PTR = decltype(&f); + using THUNK = void (*)(F_PTR); + static constexpr auto jump = std::array{[](F_PTR fp) { + constexpr StandardMetadataType type = ndk::internal::enum_values[I]; + if constexpr (type != StandardMetadataType::INVALID) { + (*fp)(StandardMetadata{}); + } + }...}; + + auto index = static_cast(type); + if (index >= 0 && index < jump.size()) { + jump[index](&f); + } +} + +template .size()>> +int32_t provideStandardMetadata(StandardMetadataType type, void* _Nullable destBuffer, + size_t destBufferSize, F&& f) { + int32_t retVal = -AIMAPPER_ERROR_UNSUPPORTED; + invokeWithStandardMetadata( + [&](StandardMetadata) { + retVal = f.template operator()( + [&](const typename StandardMetadata::value_type& value) -> int32_t { + return StandardMetadata::value::encode(value, destBuffer, + destBufferSize); + }); + }, + type, StandardMetadataSequence{}); + return retVal; +} + +template .size()>> +AIMapper_Error applyStandardMetadata(StandardMetadataType type, const void* _Nonnull metadata, + size_t metadataSize, F&& f) { + AIMapper_Error retVal = AIMAPPER_ERROR_UNSUPPORTED; + invokeWithStandardMetadata( + [&](StandardMetadata) { + auto value = StandardMetadata::value::decode(metadata, metadataSize); + if (value.has_value()) { + retVal = f.template operator()(std::move(*value)); + } else { + retVal = AIMAPPER_ERROR_BAD_VALUE; + } + }, + type, StandardMetadataSequence{}); + return retVal; +} + +} // namespace android::hardware::graphics::mapper \ No newline at end of file diff --git a/graphics/mapper/stable-c/implutils/include/android/hardware/graphics/mapper/utils/IMapperProvider.h b/graphics/mapper/stable-c/implutils/include/android/hardware/graphics/mapper/utils/IMapperProvider.h new file mode 100644 index 0000000000..957fdc95ed --- /dev/null +++ b/graphics/mapper/stable-c/implutils/include/android/hardware/graphics/mapper/utils/IMapperProvider.h @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2022 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. + */ + +#pragma once + +#include +#include +#include + +#include +#include +#include + +/** + * Helper utilities for providing an IMapper-StableC implementation. + */ + +namespace vendor::mapper { + +/** + * Extend from this interface to provide Version 5 of the IMapper interface + */ +struct IMapperV5Impl { + static const auto version = AIMAPPER_VERSION_5; + virtual ~IMapperV5Impl() = default; + + virtual AIMapper_Error importBuffer(const native_handle_t* _Nonnull handle, + buffer_handle_t _Nullable* _Nonnull outBufferHandle) = 0; + + virtual AIMapper_Error freeBuffer(buffer_handle_t _Nonnull buffer) = 0; + + virtual AIMapper_Error getTransportSize(buffer_handle_t _Nonnull buffer, + uint32_t* _Nonnull outNumFds, + uint32_t* _Nonnull outNumInts) = 0; + + virtual AIMapper_Error lock(buffer_handle_t _Nonnull buffer, uint64_t cpuUsage, + ARect accessRegion, int acquireFence, + void* _Nullable* _Nonnull outData) = 0; + + virtual AIMapper_Error unlock(buffer_handle_t _Nonnull buffer, int* _Nonnull releaseFence) = 0; + + virtual AIMapper_Error flushLockedBuffer(buffer_handle_t _Nonnull buffer) = 0; + + virtual AIMapper_Error rereadLockedBuffer(buffer_handle_t _Nonnull buffer) = 0; + + virtual int32_t getMetadata(buffer_handle_t _Nonnull buffer, AIMapper_MetadataType metadataType, + void* _Nullable destBuffer, size_t destBufferSize) = 0; + + virtual int32_t getStandardMetadata(buffer_handle_t _Nonnull buffer, + int64_t standardMetadataType, void* _Nullable destBuffer, + size_t destBufferSize) = 0; + + virtual AIMapper_Error setMetadata(buffer_handle_t _Nonnull buffer, + AIMapper_MetadataType metadataType, + const void* _Nonnull metadata, size_t metadataSize) = 0; + + virtual AIMapper_Error setStandardMetadata(buffer_handle_t _Nonnull buffer, + int64_t standardMetadataType, + const void* _Nonnull metadata, + size_t metadataSize) = 0; + + virtual AIMapper_Error listSupportedMetadataTypes( + const AIMapper_MetadataTypeDescription* _Nullable* _Nonnull outDescriptionList, + size_t* _Nonnull outNumberOfDescriptions) = 0; + + virtual AIMapper_Error dumpBuffer(buffer_handle_t _Nonnull bufferHandle, + AIMapper_DumpBufferCallback _Nonnull dumpBufferCallback, + void* _Null_unspecified context) = 0; + + virtual AIMapper_Error dumpAllBuffers( + AIMapper_BeginDumpBufferCallback _Nonnull beginDumpBufferCallback, + AIMapper_DumpBufferCallback _Nonnull dumpBufferCallback, + void* _Null_unspecified context) = 0; + + virtual AIMapper_Error getReservedRegion(buffer_handle_t _Nonnull buffer, + void* _Nullable* _Nonnull outReservedRegion, + uint64_t* _Nonnull outReservedSize) = 0; +}; + +namespace provider { +#ifndef __cpp_inline_variables +#error "Only C++17 & newer is supported; inline variables is missing" +#endif + +inline void* _Nullable sIMapperInstance = nullptr; +} // namespace provider + +template +class IMapperProvider { + private: + static_assert(IMPL::version >= AIMAPPER_VERSION_5, "Must be at least AIMAPPER_VERSION_5"); + static_assert(std::is_final_v, "Implementation must be final"); + static_assert(std::is_constructible_v, "Implementation must have a no-args constructor"); + + std::once_flag mLoadOnceFlag; + std::optional mImpl; + AIMapper mMapper = {}; + + static IMPL& impl() { + return *reinterpret_cast*>(provider::sIMapperInstance)->mImpl; + } + + void bindV5() { + mMapper.v5 = { + .importBuffer = [](const native_handle_t* _Nonnull handle, + buffer_handle_t _Nullable* _Nonnull outBufferHandle) + -> AIMapper_Error { return impl().importBuffer(handle, outBufferHandle); }, + + .freeBuffer = [](buffer_handle_t _Nonnull buffer) -> AIMapper_Error { + return impl().freeBuffer(buffer); + }, + + .getTransportSize = [](buffer_handle_t _Nonnull buffer, + uint32_t* _Nonnull outNumFds, + uint32_t* _Nonnull outNumInts) -> AIMapper_Error { + return impl().getTransportSize(buffer, outNumFds, outNumInts); + }, + + .lock = [](buffer_handle_t _Nonnull buffer, uint64_t cpuUsage, ARect accessRegion, + int acquireFence, void* _Nullable* _Nonnull outData) -> AIMapper_Error { + return impl().lock(buffer, cpuUsage, accessRegion, acquireFence, outData); + }, + + .unlock = [](buffer_handle_t _Nonnull buffer, int* _Nonnull releaseFence) + -> AIMapper_Error { return impl().unlock(buffer, releaseFence); }, + + .flushLockedBuffer = [](buffer_handle_t _Nonnull buffer) -> AIMapper_Error { + return impl().flushLockedBuffer(buffer); + }, + + .rereadLockedBuffer = [](buffer_handle_t _Nonnull buffer) -> AIMapper_Error { + return impl().rereadLockedBuffer(buffer); + }, + + .getMetadata = [](buffer_handle_t _Nonnull buffer, + AIMapper_MetadataType metadataType, void* _Nullable destBuffer, + size_t destBufferSize) -> int32_t { + return impl().getMetadata(buffer, metadataType, destBuffer, destBufferSize); + }, + + .getStandardMetadata = [](buffer_handle_t _Nonnull buffer, + int64_t standardMetadataType, void* _Nullable destBuffer, + size_t destBufferSize) -> int32_t { + return impl().getStandardMetadata(buffer, standardMetadataType, destBuffer, + destBufferSize); + }, + + .setMetadata = [](buffer_handle_t _Nonnull buffer, + AIMapper_MetadataType metadataType, const void* _Nonnull metadata, + size_t metadataSize) -> AIMapper_Error { + return impl().setMetadata(buffer, metadataType, metadata, metadataSize); + }, + + .setStandardMetadata = + [](buffer_handle_t _Nonnull buffer, int64_t standardMetadataType, + const void* _Nonnull metadata, size_t metadataSize) -> AIMapper_Error { + return impl().setStandardMetadata(buffer, standardMetadataType, metadata, + metadataSize); + }, + + .listSupportedMetadataTypes = + [](const AIMapper_MetadataTypeDescription* _Nullable* _Nonnull outDescriptionList, + size_t* _Nonnull outNumberOfDescriptions) -> AIMapper_Error { + return impl().listSupportedMetadataTypes(outDescriptionList, + outNumberOfDescriptions); + }, + + .dumpBuffer = [](buffer_handle_t _Nonnull bufferHandle, + AIMapper_DumpBufferCallback _Nonnull dumpBufferCallback, + void* _Null_unspecified context) -> AIMapper_Error { + return impl().dumpBuffer(bufferHandle, dumpBufferCallback, context); + }, + + .dumpAllBuffers = + [](AIMapper_BeginDumpBufferCallback _Nonnull beginDumpBufferCallback, + AIMapper_DumpBufferCallback _Nonnull dumpBufferCallback, + void* _Null_unspecified context) { + return impl().dumpAllBuffers(beginDumpBufferCallback, + dumpBufferCallback, context); + }, + + .getReservedRegion = [](buffer_handle_t _Nonnull buffer, + void* _Nullable* _Nonnull outReservedRegion, + uint64_t* _Nonnull outReservedSize) -> AIMapper_Error { + return impl().getReservedRegion(buffer, outReservedRegion, outReservedSize); + }, + }; + } + + public: + explicit IMapperProvider() = default; + + AIMapper_Error load(AIMapper* _Nullable* _Nonnull outImplementation) { + std::call_once(mLoadOnceFlag, [this] { + LOG_ALWAYS_FATAL_IF(provider::sIMapperInstance != nullptr, + "AIMapper implementation already loaded!"); + provider::sIMapperInstance = this; + mImpl.emplace(); + mMapper.version = IMPL::version; + if (IMPL::version >= AIMAPPER_VERSION_5) { + bindV5(); + } + }); + *outImplementation = &mMapper; + return AIMAPPER_ERROR_NONE; + } +}; + +} // namespace vendor::mapper diff --git a/graphics/mapper/stable-c/include/android/hardware/graphics/mapper/IMapper.h b/graphics/mapper/stable-c/include/android/hardware/graphics/mapper/IMapper.h new file mode 100644 index 0000000000..f27b0f4ce7 --- /dev/null +++ b/graphics/mapper/stable-c/include/android/hardware/graphics/mapper/IMapper.h @@ -0,0 +1,689 @@ +/* + * Copyright (C) 2022 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. + */ + +/** + * IMapper Stable-C HAL interface + * + * This file represents the sphal interface between libui & the IMapper HAL implementation. + * A vendor implementation of this interface is retrieved by looking up the vendor imapper + * implementation library via the IAllocator AIDL interface. + * + * This interface is not intended for general use. + */ + +#pragma once + +#include +#include +#include +#include + +#include +#include + +__BEGIN_DECLS + +/** + * AIMapper versioning + * + * IMapper versions 0-1 are pre-treble + * IMapper versions 2-4 are HIDL + * C-style AIMapper API starts at 5 + */ +enum AIMapper_Version : uint32_t { + AIMAPPER_VERSION_5 = 5, +}; + +/** + * Possible AIMapper errors + * Values are the same as IMapper 4.0's Error type for simplicity + */ +enum AIMapper_Error : int32_t { + /** + * No error. + */ + AIMAPPER_ERROR_NONE = 0, + /** + * Invalid BufferDescriptor. + */ + AIMAPPER_ERROR_BAD_DESCRIPTOR = 1, + /** + * Invalid buffer handle. + */ + AIMAPPER_ERROR_BAD_BUFFER = 2, + /** + * Invalid HardwareBufferDescription. + */ + AIMAPPER_ERROR_BAD_VALUE = 3, + /** + * Resource unavailable. + */ + AIMAPPER_ERROR_NO_RESOURCES = 5, + /** + * Permanent failure. + */ + AIMAPPER_ERROR_UNSUPPORTED = 7, +}; + +/** + * MetadataType represents the different types of buffer metadata that could be + * associated with a buffer. It is used by IMapper to help get and set buffer metadata + * on the buffer's native handle. + * + * Standard buffer metadata will have the name field set to + * "android.hardware.graphics.common.StandardMetadataType" and will contain values + * from StandardMetadataType.aidl. + * + * Vendor-provided metadata should be prefixed with a "vendor.mycompanyname.*" namespace. It is + * recommended that the metadata follows the pattern of StandardMetadaType.aidl. That is, an + * aidl-defined enum with @VendorStability on it and the naming then matching that type such + * as "vendor.mycompanyname.graphics.common.MetadataType" with the value field then set to the + * aidl's enum value. + * + * Each company should create their own enum & namespace. The name + * field prevents values from different companies from colliding. + */ +typedef struct AIMapper_MetadataType { + const char* _Nonnull name; + int64_t value; +} AIMapper_MetadataType; + +typedef struct AIMapper_MetadataTypeDescription { + /** + * The `name` of the metadataType must be valid for the lifetime of the process + */ + AIMapper_MetadataType metadataType; + /** + * description should contain a string representation of the MetadataType. + * + * For example: "MyExampleMetadataType is a 64-bit timestamp in nanoseconds + * that indicates when a buffer is decoded. It is set by the media HAL after + * a buffer is decoded. It is used by the display HAL for hardware + * synchronization". + * + * This field is required for any non-StandardMetadataTypes. For StandardMetadataTypes this + * field may be null. The lifetime of this pointer must be valid for the duration of the + * process (that is, a static const char*). + */ + const char* _Nullable description; + /** + * isGettable represents if the MetadataType can be get. + */ + bool isGettable; + /** + * isSettable represents if the MetadataType can be set. + */ + bool isSettable; + + /** Reserved for future use; must be zero-initialized currently */ + uint8_t reserved[32]; +} AIMapper_MetadataTypeDescription; + +/** + * Callback that is passed to dumpBuffer. + * + * @param context The caller-provided void* that was passed to dumpBuffer. + * @param metadataType The type of the metadata passed to the callback + * @param value A pointer to the value of the metadata. The lifetime of this pointer is only + * valid for the duration of the call + * @param valueSize The size of the value buffer. + */ +typedef void (*AIMapper_DumpBufferCallback)(void* _Null_unspecified context, + AIMapper_MetadataType metadataType, + const void* _Nonnull value, size_t valueSize); + +/** + * Callback that is passed to dumpAllBuffers. + * + * Indicates that a buffer is about to be dumped. Will be followed by N calls to + * AIMapper_DumpBufferCallback for all the metadata for this buffer. + * + * @param context The caller-provided void* that was passed to dumpAllBuffers. + */ +typedef void (*AIMapper_BeginDumpBufferCallback)(void* _Null_unspecified context); + +/** + * Implementation of AIMAPPER_VERSION_5 + * All functions must not be null & must provide a valid implementation. + */ +typedef struct AIMapperV5 { + /** + * Imports a raw buffer handle to create an imported buffer handle for use + * with the rest of the mapper or with other in-process libraries. + * + * A buffer handle is considered raw when it is cloned (e.g., with + * `native_handle_clone()`) from another buffer handle locally, or when it + * is received from another HAL server/client or another process. A raw + * buffer handle must not be used to access the underlying graphic + * buffer. It must be imported to create an imported handle first. + * + * This function must at least validate the raw handle before creating the + * imported handle. It must also support importing the same raw handle + * multiple times to create multiple imported handles. The imported handle + * must be considered valid everywhere in the process, including in + * another instance of the mapper. + * + * Because of passthrough HALs, a raw buffer handle received from a HAL + * may actually have been imported in the process. importBuffer() must treat + * such a handle as if it is raw and must not return `BAD_BUFFER`. The + * returned handle is independent from the input handle as usual, and + * freeBuffer() must be called on it when it is no longer needed. + * + * @param handle Raw buffer handle to import. + * @param outBufferHandle The resulting imported buffer handle. + * @return Error status of the call, which may be + * - `NONE` upon success. + * - `BAD_BUFFER` if the raw handle is invalid. + * - `NO_RESOURCES` if the raw handle cannot be imported due to + * unavailability of resources. + */ + AIMapper_Error (*_Nonnull importBuffer)(const native_handle_t* _Nonnull handle, + buffer_handle_t _Nullable* _Nonnull outBufferHandle); + + /** + * Frees a buffer handle. Buffer handles returned by importBuffer() must be + * freed with this function when no longer needed. + * + * This function must free up all resources allocated by importBuffer() for + * the imported handle. For example, if the imported handle was created + * with `native_handle_create()`, this function must call + * `native_handle_close()` and `native_handle_delete()`. + * + * @param buffer Imported buffer handle. + * @return error Error status of the call, which may be + * - `NONE` upon success. + * - `BAD_BUFFER` if the buffer is invalid. + */ + AIMapper_Error (*_Nonnull freeBuffer)(buffer_handle_t _Nonnull buffer); + + /** + * Calculates the transport size of a buffer. An imported buffer handle is a + * raw buffer handle with the process-local runtime data appended. This + * function, for example, allows a caller to omit the process-local runtime + * data at the tail when serializing the imported buffer handle. + * + * Note that a client might or might not omit the process-local runtime data + * when sending an imported buffer handle. The mapper must support both + * cases on the receiving end. + * + * @param buffer Buffer to get the transport size from. + * @param outNumFds The number of file descriptors needed for transport. + * @param outNumInts The number of integers needed for transport. + * @return error Error status of the call, which may be + * - `NONE` upon success. + * - `BAD_BUFFER` if the buffer is invalid. + */ + AIMapper_Error (*_Nonnull getTransportSize)(buffer_handle_t _Nonnull buffer, + uint32_t* _Nonnull outNumFds, + uint32_t* _Nonnull outNumInts); + + /** + * Locks the given buffer for the specified CPU usage. + * + * Locking the same buffer simultaneously from multiple threads is + * permitted, but if any of the threads attempt to lock the buffer for + * writing, the behavior is undefined, except that it must not cause + * process termination or block the client indefinitely. Leaving the + * buffer content in an indeterminate state or returning an error are both + * acceptable. + * + * 1D buffers (width = size in bytes, height = 1, pixel_format = BLOB) must + * "lock in place". The buffers must be directly accessible via mapping. + * + * The client must not modify the content of the buffer outside of + * @p accessRegion, and the device need not guarantee that content outside + * of @p accessRegion is valid for reading. The result of reading or writing + * outside of @p accessRegion is undefined, except that it must not cause + * process termination. + * + * An accessRegion of all-zeros means the entire buffer. That is, it is + * equivalent to '(0,0)-(buffer width, buffer height)'. + * + * This function can lock both single-planar and multi-planar formats. The caller + * should use get() to get information about the buffer they are locking. + * get() can be used to get information about the planes, offsets, stride, + * etc. + * + * This function must also work on buffers with + * `AHARDWAREBUFFER_FORMAT_Y8Cb8Cr8_*` if supported by the device, as well + * as with any other formats requested by multimedia codecs when they are + * configured with a flexible-YUV-compatible color format. + * + * On success, @p data must be filled with a pointer to the locked buffer + * memory. This address will represent the top-left corner of the entire + * buffer, even if @p accessRegion does not begin at the top-left corner. + * + * The locked buffer must adhere to the format requested at allocation time + * in the BufferDescriptorInfo. + * + * @param buffer Buffer to lock. + * @param cpuUsage CPU usage flags to request. See BufferUsage.aidl for possible values. + * @param accessRegion Portion of the buffer that the client intends to + * access. + * @param acquireFence Handle containing a file descriptor referring to a + * sync fence object, which will be signaled when it is safe for the + * mapper to lock the buffer. @p acquireFence may be an empty fence (-1) if + * it is already safe to lock. Ownership is passed to the callee and it is the + * implementations responsibility to ensure it is closed even when an error + * occurs. + * @param outData CPU-accessible pointer to the buffer data. + * @return error Error status of the call, which may be + * - `NONE` upon success. + * - `BAD_BUFFER` if the buffer is invalid or is incompatible with this + * function. + * - `BAD_VALUE` if @p cpuUsage is 0, contains non-CPU usage flags, or + * is incompatible with the buffer. Also if the @p accessRegion is + * outside the bounds of the buffer or the accessRegion is invalid. + * - `NO_RESOURCES` if the buffer cannot be locked at this time. Note + * that locking may succeed at a later time. + * @return data CPU-accessible pointer to the buffer data. + */ + AIMapper_Error (*_Nonnull lock)(buffer_handle_t _Nonnull buffer, uint64_t cpuUsage, + ARect accessRegion, int acquireFence, + void* _Nullable* _Nonnull outData); + + /** + * Unlocks a buffer to indicate all CPU accesses to the buffer have + * completed. + * + * @param buffer Buffer to unlock. + * @param releaseFence Handle containing a file descriptor referring to a + * sync fence object. The sync fence object will be signaled when the + * mapper has completed any pending work. @p releaseFence may be an + * empty fence (-1). + * @return error Error status of the call, which may be + * - `NONE` upon success. + * - `BAD_BUFFER` if the buffer is invalid or not locked. + */ + AIMapper_Error (*_Nonnull unlock)(buffer_handle_t _Nonnull buffer, int* _Nonnull releaseFence); + + /** + * Flushes the contents of a locked buffer. + * + * This function flushes the CPUs caches for the range of all the buffer's + * planes and metadata. This should behave similarly to unlock() except the + * buffer should remain mapped to the CPU. + * + * The client is still responsible for calling unlock() when it is done + * with all CPU accesses to the buffer. + * + * If non-CPU blocks are simultaneously writing the buffer, the locked + * copy should still be flushed but what happens is undefined except that + * it should not cause any crashes. + * + * @param buffer Buffer to flush. + * @return error Error status of the call, which may be + * - `NONE` upon success. + * - `BAD_BUFFER` if the buffer is invalid or not locked. + */ + AIMapper_Error (*_Nonnull flushLockedBuffer)(buffer_handle_t _Nonnull buffer); + + /** + * Rereads the contents of a locked buffer. + * + * This should fetch the most recent copy of the locked buffer. + * + * It may reread locked copies of the buffer in other processes. + * + * The client is still responsible for calling unlock() when it is done + * with all CPU accesses to the buffer. + * + * @param buffer Buffer to reread. + * @return error Error status of the call, which may be + * - `NONE` upon success. + * - `BAD_BUFFER` if the buffer is invalid or not locked. + * - `NO_RESOURCES` if the buffer cannot be reread at this time. Note + * that rereading may succeed at a later time. + */ + AIMapper_Error (*_Nonnull rereadLockedBuffer)(buffer_handle_t _Nonnull buffer); + + /** + * Description for get(...), set(...) and getFromBufferDescriptorInfo(...) + * + * ------------ Overview ----------------------------------- + * Gralloc 4 adds support for getting and setting buffer metadata on a buffer. + * + * To get buffer metadata, the client passes in a buffer handle and a token that + * represents the type of buffer metadata they would like to get. IMapper returns + * a byte stream that contains the buffer metadata. To set the buffer metadata, the + * client passes in a buffer handle and a token that represents the type of buffer + * metadata they would like to set and a byte stream that contains the buffer metadata + * they are setting. + * + * Buffer metadata is global for a buffer. When the metadata is set on the buffer + * in a process, the updated metadata should be available to all other processes. + * Please see "Storing and Propagating Metadata" below for more details. + * + * The getter and setter functions have been optimized for easy vendor extension. + * They do not require a formal extension to add support for getting and setting + * vendor defined buffer metadata. See "Buffer Metadata Token" and + * "Buffer Metadata Stream" below for more details. + * + * ------------ Storing and Propagating Metadata ----------- + * Buffer metadata must be global. Any changes to the metadata must be propagated + * to all other processes immediately. Vendors may chose how they would like support + * this functionality. + * + * We recommend supporting this functionality by allocating an extra page of shared + * memory and storing it in the buffer's native_handle_t. The buffer metadata can + * be stored in the extra page of shared memory. Set operations are automatically + * propagated to all other processes. + * + * ------------ Buffer Metadata Synchronization ------------ + * There are no explicit buffer metadata synchronization primitives. Many devices + * before gralloc 4 already support getting and setting of global buffer metadata + * with no explicit synchronization primitives. Adding synchronization primitives + * would just add unnecessary complexity. + * + * The general rule is if a process has permission to write to a buffer, they + * have permission to write to the buffer's writable metadata. If a process has permission + * to read from a buffer, they have permission to read the buffer's metadata. + * + * There is one exception to this rule. Fences CANNOT be used to protect a buffer's + * metadata. A process should finish writing to a buffer's metadata before + * sending the buffer to another process that will read or write to the buffer. + * This exception is needed because sometimes userspace needs to read the + * buffer's metadata before the buffer's contents are ready. + * + * As a simple example: an app renders to a buffer and then displays the buffer. + * In this example when the app renders to the buffer, both the buffer and its + * metadata need to be updated. The app's process queues up its work on the GPU + * and gets back an acquire fence. The app's process must update the buffer's + * metadata before enqueuing the buffer to SurfaceFlinger. The app process CANNOT + * update the buffer's metadata after enqueuing the buffer. When HardwareComposer + * receives the buffer, it is immediately safe to read the buffer's metadata + * and use it to program the display driver. To read the buffer's contents, + * display driver must still wait on the acquire fence. + * + * ------------ Buffer Metadata Token ---------------------- + * In order to allow arbitrary vendor defined metadata, the token used to access + * metadata is defined defined as a struct that has a string representing + * the enum type and an int that represents the enum value. The string protects + * different enum values from colliding. + * + * The token struct (MetadataType) is defined as a C struct since it + * is passed into a C function. The standard buffer metadata types are NOT + * defined as a C enum but instead as an AIDL enum to allow for broader usage across + * other HALs and libraries. By putting the enum in the + * stable AIDL (hardware/interfaces/graphics/common/aidl/android/hardware/ + * graphics/common/StandardMetadataType.aidl), vendors will be able to optionally + * choose to support future standard buffer metadata types without upgrading + * IMapper versions. For more information see the description of "struct MetadataType". + * + * ------------ Buffer Metadata Stream --------------------- + * The buffer metadata is get and set as a void* buffer. By getting + * and setting buffer metadata as a generic buffer, vendors can use the standard + * getters and setter functions defined here. Vendors do NOT need to add their own + * getters and setter functions for each new type of buffer metadata. + * + * Converting buffer metadata into a byte stream can be non-trivial. For the standard + * buffer metadata types defined in StandardMetadataType.aidl, there are also + * support functions that will encode the buffer metadata into a byte stream + * and decode the buffer metadata from a byte stream. We STRONGLY recommend using + * these support functions. The framework will use them when getting and setting + * metadata. The support functions are defined in + * frameworks/native/libs/gralloc/types/include/gralloctypes/Gralloc4.h. + */ + + /** + * Gets the buffer metadata for a given MetadataType. + * + * Buffer metadata can be changed after allocation so clients should avoid "caching" + * the buffer metadata. For example, if the video resolution changes and the buffers + * are not reallocated, several buffer metadata values may change without warning. + * Clients should not expect the values to be constant. They should requery them every + * frame. The only exception is buffer metadata that is determined at allocation + * time. For StandardMetadataType values, only BUFFER_ID, NAME, WIDTH, + * HEIGHT, LAYER_COUNT, PIXEL_FORMAT_REQUESTED and USAGE are safe to cache because + * they are determined at allocation time. + * + * @param buffer Buffer containing desired metadata + * @param metadataType MetadataType for the metadata value being queried + * @param destBuffer Pointer to a buffer in which to store the result of the get() call; if + * null, the computed output size or error must still be returned. + * @param destBufferSize How large the destBuffer buffer is. If destBuffer is null this must be + * 0. + * @return The number of bytes written to `destBuffer` or which would have been written + * if `destBufferSize` was large enough. + * A negative value indicates an error, which may be + * - `BAD_BUFFER` if the raw handle is invalid. + * - `UNSUPPORTED` when metadataType is unknown/unsupported. + * IMapper must support getting all StandardMetadataType.aidl values defined + * at the time the device first launches. + */ + int32_t (*_Nonnull getMetadata)(buffer_handle_t _Nonnull buffer, + AIMapper_MetadataType metadataType, void* _Nullable destBuffer, + size_t destBufferSize); + + /** + * Gets the buffer metadata for a StandardMetadataType. + * + * This is equivalent to `getMetadata` when passed an AIMapper_MetadataType with name + * set to "android.hardware.graphics.common.StandardMetadataType" + * + * Buffer metadata can be changed after allocation so clients should avoid "caching" + * the buffer metadata. For example, if the video resolution changes and the buffers + * are not reallocated, several buffer metadata values may change without warning. + * Clients should not expect the values to be constant. They should requery them every + * frame. The only exception is buffer metadata that is determined at allocation + * time. For StandardMetadataType values, only BUFFER_ID, NAME, WIDTH, + * HEIGHT, LAYER_COUNT, PIXEL_FORMAT_REQUESTED and USAGE are safe to cache because + * they are determined at allocation time. + * + * @param buffer Buffer containing desired metadata + * @param standardMetadataType StandardMetadataType for the metadata value being queried + * @param destBuffer Pointer to a buffer in which to store the result of the get() call; if + * null, the computed output size or error must still be returned. + * @param destBufferSize How large the destBuffer buffer is. If destBuffer is null this must be + * 0. + * @return The number of bytes written to `destBuffer` or which would have been written + * if `destBufferSize` was large enough. + * A negative value indicates an error, which may be + * - `BAD_BUFFER` if the raw handle is invalid. + * - `UNSUPPORTED` when metadataType is unknown/unsupported. + * IMapper must support getting all StandardMetadataType.aidl values defined + * at the time the device first launches. + */ + int32_t (*_Nonnull getStandardMetadata)(buffer_handle_t _Nonnull buffer, + int64_t standardMetadataType, + void* _Nullable destBuffer, size_t destBufferSize); + + /** + * Sets the global value for a given MetadataType. + * + * Metadata fields are not required to be settable. This function can + * return Error::UNSUPPORTED whenever it doesn't support setting a + * particular Metadata field. + * + * The framework will attempt to set the following StandardMetadataType + * values: DATASPACE, SMPTE2086, CTA861_3, SMPTE2094_40 and BLEND_MODE. + * We require everyone to support setting those fields. If a device's Composer + * implementation supports a field, it should be supported here. Over time these + * metadata fields will be moved out of Composer/BufferQueue/etc. and into the + * buffer's Metadata fields. + * + * @param buffer Buffer receiving desired metadata + * @param metadataType MetadataType for the metadata value being set + * @param metadata Pointer to a buffer of bytes representing the value associated with + * @param metadataSize The size of the metadata buffer + * @return error Error status of the call, which may be + * - `NONE` upon success. + * - `BAD_BUFFER` if the raw handle is invalid. + * - `BAD_VALUE` when the field is constant and can never be set (such as + * BUFFER_ID, NAME, WIDTH, HEIGHT, LAYER_COUNT, PIXEL_FORMAT_REQUESTED and + * USAGE) + * - `NO_RESOURCES` if the set cannot be fulfilled due to unavailability of + * resources. + * - `UNSUPPORTED` when metadataType is unknown/unsupported or setting + * it is unsupported. Unsupported should also be returned if the metadata + * is malformed. + */ + AIMapper_Error (*_Nonnull setMetadata)(buffer_handle_t _Nonnull buffer, + AIMapper_MetadataType metadataType, + const void* _Nonnull metadata, size_t metadataSize); + + /** + * Sets the global value for a given MetadataType. + * + * This is equivalent to `setMetadata` when passed an AIMapper_MetadataType with name + * set to "android.hardware.graphics.common.StandardMetadataType" + * + * Metadata fields are not required to be settable. This function can + * return Error::UNSUPPORTED whenever it doesn't support setting a + * particular Metadata field. + * + * The framework will attempt to set the following StandardMetadataType + * values: DATASPACE, SMPTE2086, CTA861_3, SMPTE2094_40 and BLEND_MODE. + * We require everyone to support setting those fields. If a device's Composer + * implementation supports a field, it should be supported here. Over time these + * metadata fields will be moved out of Composer/BufferQueue/etc. and into the + * buffer's Metadata fields. + * + * @param buffer Buffer receiving desired metadata + * @param standardMetadataType StandardMetadataType for the metadata value being set + * @param metadata Pointer to a buffer of bytes representing the value associated with + * @param metadataSize The size of the metadata buffer + * @return error Error status of the call, which may be + * - `NONE` upon success. + * - `BAD_BUFFER` if the raw handle is invalid. + * - `BAD_VALUE` when the field is constant and can never be set (such as + * BUFFER_ID, NAME, WIDTH, HEIGHT, LAYER_COUNT, PIXEL_FORMAT_REQUESTED and + * USAGE) + * - `NO_RESOURCES` if the set cannot be fulfilled due to unavailability of + * resources. + * - `UNSUPPORTED` when metadataType is unknown/unsupported or setting + * it is unsupported. Unsupported should also be returned if the metadata + * is malformed. + */ + AIMapper_Error (*_Nonnull setStandardMetadata)(buffer_handle_t _Nonnull buffer, + int64_t standardMetadataType, + const void* _Nonnull metadata, + size_t metadataSize); + + /** + * Lists all the MetadataTypes supported by IMapper as well as a description + * of each supported MetadataType. For StandardMetadataTypes, the description + * string can be left empty. + * + * This list is expected to be static & thus the returned array must be valid for the + * lifetime of the process. + * + * @param outDescriptionList The list of descriptions + * @param outNumberOfDescriptions How many descriptions are in `outDescriptionList` + * @return error Error status of the call, which may be + * - `NONE` upon success. + * - `UNSUPPORTED` if there's any error + */ + AIMapper_Error (*_Nonnull listSupportedMetadataTypes)( + const AIMapper_MetadataTypeDescription* _Nullable* _Nonnull outDescriptionList, + size_t* _Nonnull outNumberOfDescriptions); + + /** + * Dumps a buffer's metadata. + * + * @param buffer The buffer to dump the metadata for + * @param dumpBufferCallback Callback that will be invoked for each of the metadata fields + * @param context A caller-provided context to be passed to the dumpBufferCallback + * @return error Error status of the call, which may be + * - `NONE` upon success. + * - `BAD_BUFFER` if the raw handle is invalid. + * - `NO_RESOURCES` if the get cannot be fulfilled due to unavailability of + * resources. + */ + AIMapper_Error (*_Nonnull dumpBuffer)(buffer_handle_t _Nonnull buffer, + AIMapper_DumpBufferCallback _Nonnull dumpBufferCallback, + void* _Null_unspecified context); + + /** + * Dump the metadata for all imported buffers in the current process + * + * The HAL implementation should invoke beginDumpCallback before dumping a buffer's metadata, + * followed by N calls to dumpBufferCallback for that buffer's metadata fields. The call + * sequence should follow this pseudocode: + * + * for (auto buffer : gListOfImportedBuffers) { + * beginDumpCallback(context); + * for (auto metadata : buffer->allMetadata()) { + * dumpBufferCallback(context, metadata...); + * } + * } + * + * @param beginDumpCallback Signals that a buffer is about to be dumped + * @param dumpBufferCallback Callback that will be invoked for each of the metadata fields + * @param context A caller-provided context to be passed to beginDumpCallback and + * dumpBufferCallback + * @return error Error status of the call, which may be + * - `NONE` upon success. + * - `BAD_BUFFER` if the raw handle is invalid. + * - `NO_RESOURCES` if the get cannot be fulfilled due to unavailability of + * resources. + */ + AIMapper_Error (*_Nonnull dumpAllBuffers)( + AIMapper_BeginDumpBufferCallback _Nonnull beginDumpCallback, + AIMapper_DumpBufferCallback _Nonnull dumpBufferCallback, + void* _Null_unspecified context); + + /** + * Returns the region of shared memory associated with the buffer that is + * reserved for client use. + * + * The shared memory may be allocated from any shared memory allocator. + * The shared memory must be CPU-accessible and virtually contiguous. The + * starting address must be word-aligned. + * + * This function may only be called after importBuffer() has been called by the + * client. The reserved region must remain accessible until freeBuffer() has + * been called. After freeBuffer() has been called, the client must not access + * the reserved region. + * + * This reserved memory may be used in future versions of Android to + * help clients implement backwards compatible features without requiring + * IAllocator/IMapper updates. + * + * @param buffer Imported buffer handle. + * @param outReservedRegion CPU-accessible pointer to the reserved region + * @param outReservedSize the size of the reservedRegion that was requested + * in the BufferDescriptorInfo. + * @return error Error status of the call, which may be + * - `NONE` upon success. + * - `BAD_BUFFER` if the buffer is invalid. + */ + AIMapper_Error (*_Nonnull getReservedRegion)(buffer_handle_t _Nonnull buffer, + void* _Nullable* _Nonnull outReservedRegion, + uint64_t* _Nonnull outReservedSize); + +} AIMapperV5; + +/** + * Return value for AIMapper_loadIMapper + * + * Note: This struct's size is not fixed and callers must never store it by-value as a result. + * Only fields up to those covered by `version` are allowed to be accessed. + */ +typedef struct AIMapper { + alignas(alignof(max_align_t)) AIMapper_Version version; + AIMapperV5 v5; +} AIMapper; + +/** + * Loads the vendor-provided implementation of AIMapper + * @return Error status of the call. + * - `NONE` upon success + * - `UNSUPPORTED` if no implementation is available + */ +AIMapper_Error AIMapper_loadIMapper(AIMapper* _Nullable* _Nonnull outImplementation); + +__END_DECLS \ No newline at end of file diff --git a/graphics/mapper/stable-c/vts/VtsHalGraphicsMapperStableC_TargetTest.cpp b/graphics/mapper/stable-c/vts/VtsHalGraphicsMapperStableC_TargetTest.cpp new file mode 100644 index 0000000000..6ab11a305d --- /dev/null +++ b/graphics/mapper/stable-c/vts/VtsHalGraphicsMapperStableC_TargetTest.cpp @@ -0,0 +1,1565 @@ +/* + * Copyright 2022 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. + */ + +#undef LOG_TAG +#define LOG_TAG "VtsHalGraphicsMapperStableC_TargetTest" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace aidl::android::hardware::graphics::allocator; +using namespace aidl::android::hardware::graphics::common; +using namespace android; +using namespace android::hardware; +using namespace ::android::hardware::graphics::mapper; + +typedef AIMapper_Error (*AIMapper_loadIMapperFn)(AIMapper* _Nullable* _Nonnull outImplementation); + +inline constexpr BufferUsage operator|(BufferUsage lhs, BufferUsage rhs) { + using T = std::underlying_type_t; + return static_cast(static_cast(lhs) | static_cast(rhs)); +} + +inline BufferUsage& operator|=(BufferUsage& lhs, BufferUsage rhs) { + lhs = lhs | rhs; + return lhs; +} + +struct YCbCr { + android_ycbcr yCbCr; + int64_t horizontalSubSampling; + int64_t verticalSubSampling; +}; + +class BufferHandle { + AIMapper* mIMapper; + buffer_handle_t mHandle = nullptr; + + public: + explicit BufferHandle(AIMapper* mapper, native_handle_t* rawHandle) : mIMapper(mapper) { + EXPECT_EQ(AIMAPPER_ERROR_NONE, mIMapper->v5.importBuffer(rawHandle, &mHandle)); + } + + explicit BufferHandle(BufferHandle&& other) { *this = std::move(other); } + + BufferHandle& operator=(BufferHandle&& other) noexcept { + reset(); + mIMapper = other.mIMapper; + mHandle = other.mHandle; + other.mHandle = nullptr; + return *this; + } + + ~BufferHandle() { reset(); } + + constexpr explicit operator bool() const noexcept { return mHandle != nullptr; } + + buffer_handle_t operator*() const noexcept { return mHandle; } + + void reset() { + if (mHandle != nullptr) { + EXPECT_EQ(AIMAPPER_ERROR_NONE, mIMapper->v5.freeBuffer(mHandle)); + mHandle = nullptr; + } + } +}; + +class BufferAllocation { + AIMapper* mIMapper; + native_handle_t* mRawHandle; + uint32_t mStride; + const BufferDescriptorInfo mInfo; + + public: + BufferAllocation(const BufferAllocation&) = delete; + void operator=(const BufferAllocation&) = delete; + + BufferAllocation(AIMapper* mapper, native_handle_t* handle, uint32_t stride, + const BufferDescriptorInfo& info) + : mIMapper(mapper), mRawHandle(handle), mStride(stride), mInfo(info) {} + + ~BufferAllocation() { + if (mRawHandle == nullptr) return; + + native_handle_close(mRawHandle); + native_handle_delete(mRawHandle); + } + + uint32_t stride() const { return mStride; } + const BufferDescriptorInfo& info() const { return mInfo; } + + BufferHandle import() { return BufferHandle{mIMapper, mRawHandle}; } + + const native_handle_t* rawHandle() const { return mRawHandle; } +}; + +class GraphicsTestsBase { + private: + friend class BufferAllocation; + int32_t mIAllocatorVersion = 1; + std::shared_ptr mAllocator; + AIMapper* mIMapper = nullptr; + AIMapper_loadIMapperFn mIMapperLoader; + + protected: + void Initialize(std::shared_ptr allocator) { + mAllocator = allocator; + ASSERT_NE(nullptr, mAllocator.get()) << "failed to get allocator service"; + ASSERT_TRUE(mAllocator->getInterfaceVersion(&mIAllocatorVersion).isOk()); + ASSERT_GE(mIAllocatorVersion, 2); + std::string mapperSuffix; + auto status = mAllocator->getIMapperLibrarySuffix(&mapperSuffix); + ASSERT_TRUE(status.isOk()) << "Failed to get IMapper library suffix"; + std::string lib_name = "mapper." + mapperSuffix + ".so"; + void* so = android_load_sphal_library(lib_name.c_str(), RTLD_LOCAL | RTLD_NOW); + ASSERT_NE(nullptr, so) << "Failed to load " << lib_name; + mIMapperLoader = (AIMapper_loadIMapperFn)dlsym(so, "AIMapper_loadIMapper"); + ASSERT_NE(nullptr, mIMapperLoader) << "AIMapper_locaIMapper missing from " << lib_name; + ASSERT_EQ(AIMAPPER_ERROR_NONE, mIMapperLoader(&mIMapper)); + ASSERT_NE(mIMapper, nullptr); + } + + public: + AIMapper_loadIMapperFn getIMapperLoader() const { return mIMapperLoader; } + + std::unique_ptr allocate(const BufferDescriptorInfo& descriptorInfo) { + AllocationResult result; + ::ndk::ScopedAStatus status = mAllocator->allocate2(descriptorInfo, 1, &result); + if (!status.isOk()) { + status_t error = status.getExceptionCode(); + if (error == EX_SERVICE_SPECIFIC) { + error = status.getServiceSpecificError(); + EXPECT_NE(OK, error) << "Failed to set error properly"; + } else { + EXPECT_EQ(OK, error) << "Allocation transport failure"; + } + return nullptr; + } else { + return std::make_unique(mIMapper, dupFromAidl(result.buffers[0]), + result.stride, descriptorInfo); + } + } + + std::unique_ptr allocateGeneric() { + return allocate({ + .name = {"VTS_TEMP"}, + .width = 64, + .height = 64, + .layerCount = 1, + .format = PixelFormat::RGBA_8888, + .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN, + .reservedSize = 0, + }); + } + + bool isSupported(const BufferDescriptorInfo& descriptorInfo) { + bool ret = false; + EXPECT_TRUE(mAllocator->isSupported(descriptorInfo, &ret).isOk()); + return ret; + } + + AIMapper* mapper() const { return mIMapper; } + + template + auto getStandardMetadata(buffer_handle_t bufferHandle) + -> decltype(StandardMetadata::value::decode(nullptr, 0)) { + using Value = typename StandardMetadata::value; + std::vector buffer; + // Initial guess + buffer.resize(512); + int32_t sizeRequired = mapper()->v5.getStandardMetadata( + bufferHandle, static_cast(T), buffer.data(), buffer.size()); + if (sizeRequired < 0) { + EXPECT_EQ(-AIMAPPER_ERROR_UNSUPPORTED, sizeRequired) + << "Received something other than UNSUPPORTED from valid getStandardMetadata " + "call"; + return std::nullopt; + } + if (sizeRequired > buffer.size()) { + buffer.resize(sizeRequired); + sizeRequired = mapper()->v5.getStandardMetadata(bufferHandle, static_cast(T), + buffer.data(), buffer.size()); + } + if (sizeRequired < 0 || sizeRequired >= buffer.size()) { + ADD_FAILURE() << "getStandardMetadata failed, received " << sizeRequired + << " with buffer size " << buffer.size(); + // Generate a fail type + return std::nullopt; + } + return Value::decode(buffer.data(), sizeRequired); + } + + template + AIMapper_Error setStandardMetadata(buffer_handle_t bufferHandle, + const typename StandardMetadata::value_type& value) { + using Value = typename StandardMetadata::value; + int32_t sizeRequired = Value::encode(value, nullptr, 0); + if (sizeRequired < 0) { + EXPECT_GE(sizeRequired, 0) << "Failed to calculate required size"; + return static_cast(-sizeRequired); + } + std::vector buffer; + buffer.resize(sizeRequired); + sizeRequired = Value::encode(value, buffer.data(), buffer.size()); + if (sizeRequired < 0 || sizeRequired > buffer.size()) { + ADD_FAILURE() << "Failed to encode with calculated size " << sizeRequired + << "; buffer size" << buffer.size(); + return static_cast(-sizeRequired); + } + return mapper()->v5.setStandardMetadata(bufferHandle, static_cast(T), + buffer.data(), sizeRequired); + } + + void verifyRGBA8888PlaneLayouts(const std::vector& planeLayouts) { + ASSERT_EQ(1, planeLayouts.size()); + + const auto& planeLayout = planeLayouts.front(); + + ASSERT_EQ(4, planeLayout.components.size()); + + int64_t offsetInBitsR = -1; + int64_t offsetInBitsG = -1; + int64_t offsetInBitsB = -1; + int64_t offsetInBitsA = -1; + + for (const auto& component : planeLayout.components) { + if (!gralloc4::isStandardPlaneLayoutComponentType(component.type)) { + continue; + } + EXPECT_EQ(8, component.sizeInBits); + if (component.type.value == gralloc4::PlaneLayoutComponentType_R.value) { + offsetInBitsR = component.offsetInBits; + } + if (component.type.value == gralloc4::PlaneLayoutComponentType_G.value) { + offsetInBitsG = component.offsetInBits; + } + if (component.type.value == gralloc4::PlaneLayoutComponentType_B.value) { + offsetInBitsB = component.offsetInBits; + } + if (component.type.value == gralloc4::PlaneLayoutComponentType_A.value) { + offsetInBitsA = component.offsetInBits; + } + } + + EXPECT_EQ(0, offsetInBitsR); + EXPECT_EQ(8, offsetInBitsG); + EXPECT_EQ(16, offsetInBitsB); + EXPECT_EQ(24, offsetInBitsA); + + EXPECT_EQ(0, planeLayout.offsetInBytes); + EXPECT_EQ(32, planeLayout.sampleIncrementInBits); + // Skip testing stride because any stride is valid + EXPECT_LE(planeLayout.widthInSamples * planeLayout.heightInSamples * 4, + planeLayout.totalSizeInBytes); + EXPECT_EQ(1, planeLayout.horizontalSubsampling); + EXPECT_EQ(1, planeLayout.verticalSubsampling); + } + + void fillRGBA8888(uint8_t* data, uint32_t height, size_t strideInBytes, size_t widthInBytes) { + for (uint32_t y = 0; y < height; y++) { + memset(data, y, widthInBytes); + data += strideInBytes; + } + } + + void verifyRGBA8888(const buffer_handle_t bufferHandle, const uint8_t* data, uint32_t height, + size_t strideInBytes, size_t widthInBytes) { + auto decodeResult = getStandardMetadata(bufferHandle); + ASSERT_TRUE(decodeResult.has_value()); + const auto& planeLayouts = *decodeResult; + ASSERT_TRUE(planeLayouts.size() > 0); + + verifyRGBA8888PlaneLayouts(planeLayouts); + + for (uint32_t y = 0; y < height; y++) { + for (size_t i = 0; i < widthInBytes; i++) { + EXPECT_EQ(static_cast(y), data[i]); + } + data += strideInBytes; + } + } + + void traverseYCbCrData(const android_ycbcr& yCbCr, int32_t width, int32_t height, + int64_t hSubsampling, int64_t vSubsampling, + std::function traverseFuncion) { + auto yData = static_cast(yCbCr.y); + auto cbData = static_cast(yCbCr.cb); + auto crData = static_cast(yCbCr.cr); + auto yStride = yCbCr.ystride; + auto cStride = yCbCr.cstride; + auto chromaStep = yCbCr.chroma_step; + + for (uint32_t y = 0; y < height; y++) { + for (uint32_t x = 0; x < width; x++) { + auto val = static_cast(height * y + x); + + traverseFuncion(yData + yStride * y + x, val); + + if (y % vSubsampling == 0 && x % hSubsampling == 0) { + uint32_t subSampleX = x / hSubsampling; + uint32_t subSampleY = y / vSubsampling; + const auto subSampleOffset = cStride * subSampleY + chromaStep * subSampleX; + const auto subSampleVal = + static_cast(height * subSampleY + subSampleX); + + traverseFuncion(cbData + subSampleOffset, subSampleVal); + traverseFuncion(crData + subSampleOffset, subSampleVal + 1); + } + } + } + } + + void fillYCbCrData(const android_ycbcr& yCbCr, int32_t width, int32_t height, + int64_t hSubsampling, int64_t vSubsampling) { + traverseYCbCrData(yCbCr, width, height, hSubsampling, vSubsampling, + [](auto address, auto fillingData) { *address = fillingData; }); + } + + void verifyYCbCrData(const android_ycbcr& yCbCr, int32_t width, int32_t height, + int64_t hSubsampling, int64_t vSubsampling) { + traverseYCbCrData( + yCbCr, width, height, hSubsampling, vSubsampling, + [](auto address, auto expectedData) { EXPECT_EQ(*address, expectedData); }); + } + + constexpr uint64_t bitsToBytes(int64_t bits) { return bits / 8; } + constexpr uint64_t bytesToBits(int64_t bytes) { return bytes * 8; } + + void getAndroidYCbCr(buffer_handle_t bufferHandle, uint8_t* data, android_ycbcr* outYCbCr, + int64_t* hSubsampling, int64_t* vSubsampling) { + auto decodeResult = getStandardMetadata(bufferHandle); + ASSERT_TRUE(decodeResult.has_value()); + const auto& planeLayouts = *decodeResult; + ASSERT_TRUE(planeLayouts.size() > 0); + + outYCbCr->y = nullptr; + outYCbCr->cb = nullptr; + outYCbCr->cr = nullptr; + outYCbCr->ystride = 0; + outYCbCr->cstride = 0; + outYCbCr->chroma_step = 0; + + for (const auto& planeLayout : planeLayouts) { + for (const auto& planeLayoutComponent : planeLayout.components) { + if (!gralloc4::isStandardPlaneLayoutComponentType(planeLayoutComponent.type)) { + continue; + } + ASSERT_EQ(0, planeLayoutComponent.offsetInBits % 8); + + uint8_t* tmpData = data + planeLayout.offsetInBytes + + bitsToBytes(planeLayoutComponent.offsetInBits); + uint64_t sampleIncrementInBytes; + + auto type = static_cast(planeLayoutComponent.type.value); + switch (type) { + case PlaneLayoutComponentType::Y: + ASSERT_EQ(nullptr, outYCbCr->y); + ASSERT_EQ(8, planeLayoutComponent.sizeInBits); + ASSERT_EQ(8, planeLayout.sampleIncrementInBits); + outYCbCr->y = tmpData; + outYCbCr->ystride = planeLayout.strideInBytes; + break; + + case PlaneLayoutComponentType::CB: + case PlaneLayoutComponentType::CR: + ASSERT_EQ(0, planeLayout.sampleIncrementInBits % 8); + + sampleIncrementInBytes = planeLayout.sampleIncrementInBits / 8; + ASSERT_TRUE(sampleIncrementInBytes == 1 || sampleIncrementInBytes == 2); + + if (outYCbCr->cstride == 0 && outYCbCr->chroma_step == 0) { + outYCbCr->cstride = planeLayout.strideInBytes; + outYCbCr->chroma_step = sampleIncrementInBytes; + } else { + ASSERT_EQ(outYCbCr->cstride, planeLayout.strideInBytes); + ASSERT_EQ(outYCbCr->chroma_step, sampleIncrementInBytes); + } + + if (*hSubsampling == 0 && *vSubsampling == 0) { + *hSubsampling = planeLayout.horizontalSubsampling; + *vSubsampling = planeLayout.verticalSubsampling; + } else { + ASSERT_EQ(*hSubsampling, planeLayout.horizontalSubsampling); + ASSERT_EQ(*vSubsampling, planeLayout.verticalSubsampling); + } + + if (type == PlaneLayoutComponentType::CB) { + ASSERT_EQ(nullptr, outYCbCr->cb); + outYCbCr->cb = tmpData; + } else { + ASSERT_EQ(nullptr, outYCbCr->cr); + outYCbCr->cr = tmpData; + } + break; + default: + break; + }; + } + } + + ASSERT_NE(nullptr, outYCbCr->y); + ASSERT_NE(nullptr, outYCbCr->cb); + ASSERT_NE(nullptr, outYCbCr->cr); + } + + YCbCr getAndroidYCbCr_P010(const native_handle_t* bufferHandle, uint8_t* data) { + YCbCr yCbCr_P010; + auto decodeResult = getStandardMetadata(bufferHandle); + if (!decodeResult.has_value()) { + ADD_FAILURE() << "failed to get plane layout"; + return YCbCr{}; + } + const auto& planeLayouts = *decodeResult; + EXPECT_EQ(2, planeLayouts.size()); + EXPECT_EQ(1, planeLayouts[0].components.size()); + EXPECT_EQ(2, planeLayouts[1].components.size()); + + yCbCr_P010.yCbCr.y = nullptr; + yCbCr_P010.yCbCr.cb = nullptr; + yCbCr_P010.yCbCr.cr = nullptr; + yCbCr_P010.yCbCr.ystride = 0; + yCbCr_P010.yCbCr.cstride = 0; + yCbCr_P010.yCbCr.chroma_step = 0; + int64_t cb_offset = 0; + int64_t cr_offset = 0; + + for (const auto& planeLayout : planeLayouts) { + for (const auto& planeLayoutComponent : planeLayout.components) { + if (!gralloc4::isStandardPlaneLayoutComponentType(planeLayoutComponent.type)) { + continue; + } + + uint8_t* tmpData = data + planeLayout.offsetInBytes + + bitsToBytes(planeLayoutComponent.offsetInBits); + uint64_t sampleIncrementInBytes = 0; + auto type = static_cast(planeLayoutComponent.type.value); + switch (type) { + case PlaneLayoutComponentType::Y: + // For specs refer: + // https://docs.microsoft.com/en-us/windows/win32/medfound/10-bit-and-16-bit-yuv-video-formats + EXPECT_EQ(6, planeLayoutComponent.offsetInBits); + EXPECT_EQ(nullptr, yCbCr_P010.yCbCr.y); + EXPECT_EQ(10, planeLayoutComponent.sizeInBits); + EXPECT_EQ(16, planeLayout.sampleIncrementInBits); + + yCbCr_P010.yCbCr.y = tmpData; + yCbCr_P010.yCbCr.ystride = planeLayout.strideInBytes; + break; + + case PlaneLayoutComponentType::CB: + case PlaneLayoutComponentType::CR: + sampleIncrementInBytes = bitsToBytes(planeLayout.sampleIncrementInBits); + EXPECT_EQ(4, sampleIncrementInBytes); + + if (yCbCr_P010.yCbCr.cstride == 0 && yCbCr_P010.yCbCr.chroma_step == 0) { + yCbCr_P010.yCbCr.cstride = planeLayout.strideInBytes; + yCbCr_P010.yCbCr.chroma_step = sampleIncrementInBytes; + } else { + EXPECT_EQ(yCbCr_P010.yCbCr.cstride, planeLayout.strideInBytes); + EXPECT_EQ(yCbCr_P010.yCbCr.chroma_step, sampleIncrementInBytes); + } + + if (yCbCr_P010.horizontalSubSampling == 0 && + yCbCr_P010.verticalSubSampling == 0) { + yCbCr_P010.horizontalSubSampling = planeLayout.horizontalSubsampling; + yCbCr_P010.verticalSubSampling = planeLayout.verticalSubsampling; + } else { + EXPECT_EQ(yCbCr_P010.horizontalSubSampling, + planeLayout.horizontalSubsampling); + EXPECT_EQ(yCbCr_P010.verticalSubSampling, + planeLayout.verticalSubsampling); + } + + if (type == PlaneLayoutComponentType::CB) { + EXPECT_EQ(nullptr, yCbCr_P010.yCbCr.cb); + yCbCr_P010.yCbCr.cb = tmpData; + cb_offset = planeLayoutComponent.offsetInBits; + } else { + EXPECT_EQ(nullptr, yCbCr_P010.yCbCr.cr); + yCbCr_P010.yCbCr.cr = tmpData; + cr_offset = planeLayoutComponent.offsetInBits; + } + break; + default: + break; + }; + } + } + + EXPECT_EQ(cb_offset + bytesToBits(2), cr_offset); + EXPECT_NE(nullptr, yCbCr_P010.yCbCr.y); + EXPECT_NE(nullptr, yCbCr_P010.yCbCr.cb); + EXPECT_NE(nullptr, yCbCr_P010.yCbCr.cr); + return yCbCr_P010; + } +}; + +class GraphicsMapperStableCTests + : public GraphicsTestsBase, + public ::testing::TestWithParam>> { + public: + void SetUp() override { Initialize(std::get<1>(GetParam())); } + + void TearDown() override {} +}; + +TEST_P(GraphicsMapperStableCTests, AllV5CallbacksDefined) { + ASSERT_GE(mapper()->version, AIMAPPER_VERSION_5); + + EXPECT_TRUE(mapper()->v5.importBuffer); + EXPECT_TRUE(mapper()->v5.freeBuffer); + EXPECT_TRUE(mapper()->v5.getTransportSize); + EXPECT_TRUE(mapper()->v5.lock); + EXPECT_TRUE(mapper()->v5.unlock); + EXPECT_TRUE(mapper()->v5.flushLockedBuffer); + EXPECT_TRUE(mapper()->v5.rereadLockedBuffer); + EXPECT_TRUE(mapper()->v5.getMetadata); + EXPECT_TRUE(mapper()->v5.getStandardMetadata); + EXPECT_TRUE(mapper()->v5.setMetadata); + EXPECT_TRUE(mapper()->v5.setStandardMetadata); + EXPECT_TRUE(mapper()->v5.listSupportedMetadataTypes); + EXPECT_TRUE(mapper()->v5.dumpBuffer); + EXPECT_TRUE(mapper()->v5.getReservedRegion); +} + +TEST_P(GraphicsMapperStableCTests, DualLoadIsIdentical) { + ASSERT_GE(mapper()->version, AIMAPPER_VERSION_5); + AIMapper* secondMapper; + ASSERT_EQ(AIMAPPER_ERROR_NONE, getIMapperLoader()(&secondMapper)); + + EXPECT_EQ(secondMapper->v5.importBuffer, mapper()->v5.importBuffer); + EXPECT_EQ(secondMapper->v5.freeBuffer, mapper()->v5.freeBuffer); + EXPECT_EQ(secondMapper->v5.getTransportSize, mapper()->v5.getTransportSize); + EXPECT_EQ(secondMapper->v5.lock, mapper()->v5.lock); + EXPECT_EQ(secondMapper->v5.unlock, mapper()->v5.unlock); + EXPECT_EQ(secondMapper->v5.flushLockedBuffer, mapper()->v5.flushLockedBuffer); + EXPECT_EQ(secondMapper->v5.rereadLockedBuffer, mapper()->v5.rereadLockedBuffer); + EXPECT_EQ(secondMapper->v5.getMetadata, mapper()->v5.getMetadata); + EXPECT_EQ(secondMapper->v5.getStandardMetadata, mapper()->v5.getStandardMetadata); + EXPECT_EQ(secondMapper->v5.setMetadata, mapper()->v5.setMetadata); + EXPECT_EQ(secondMapper->v5.setStandardMetadata, mapper()->v5.setStandardMetadata); + EXPECT_EQ(secondMapper->v5.listSupportedMetadataTypes, mapper()->v5.listSupportedMetadataTypes); + EXPECT_EQ(secondMapper->v5.dumpBuffer, mapper()->v5.dumpBuffer); + EXPECT_EQ(secondMapper->v5.getReservedRegion, mapper()->v5.getReservedRegion); +} + +TEST_P(GraphicsMapperStableCTests, CanAllocate) { + auto buffer = allocate({ + .name = {"VTS_TEMP"}, + .width = 64, + .height = 64, + .layerCount = 1, + .format = PixelFormat::RGBA_8888, + .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN, + .reservedSize = 0, + }); + ASSERT_NE(nullptr, buffer.get()); + EXPECT_GE(buffer->stride(), 64); +} + +TEST_P(GraphicsMapperStableCTests, ImportFreeBuffer) { + auto buffer = allocate({ + .name = {"VTS_TEMP"}, + .width = 64, + .height = 64, + .layerCount = 1, + .format = PixelFormat::RGBA_8888, + .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN, + .reservedSize = 0, + }); + ASSERT_NE(nullptr, buffer.get()); + EXPECT_GE(buffer->stride(), 64); + + { + auto import1 = buffer->import(); + auto import2 = buffer->import(); + EXPECT_TRUE(import1); + EXPECT_TRUE(import2); + EXPECT_NE(*import1, *import2); + } +} + +/** + * Test IMapper::importBuffer and IMapper::freeBuffer cross mapper instances. + */ +TEST_P(GraphicsMapperStableCTests, ImportFreeBufferSingleton) { + auto buffer = allocate({ + .name = {"VTS_TEMP"}, + .width = 64, + .height = 64, + .layerCount = 1, + .format = PixelFormat::RGBA_8888, + .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN, + .reservedSize = 0, + }); + ASSERT_NE(nullptr, buffer.get()); + EXPECT_GE(buffer->stride(), 64); + + buffer_handle_t bufferHandle = nullptr; + ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.importBuffer(buffer->rawHandle(), &bufferHandle)); + ASSERT_NE(nullptr, bufferHandle); + + AIMapper* secondMapper; + ASSERT_EQ(AIMAPPER_ERROR_NONE, getIMapperLoader()(&secondMapper)); + ASSERT_EQ(AIMAPPER_ERROR_NONE, secondMapper->v5.freeBuffer(bufferHandle)); +} + +/** + * Test IMapper::importBuffer with invalid buffers. + */ +TEST_P(GraphicsMapperStableCTests, ImportBufferNegative) { + native_handle_t* invalidHandle = nullptr; + buffer_handle_t bufferHandle = nullptr; + EXPECT_EQ(AIMAPPER_ERROR_BAD_BUFFER, mapper()->v5.importBuffer(invalidHandle, &bufferHandle)) + << "importBuffer with nullptr did not fail with BAD_BUFFER"; + + invalidHandle = native_handle_create(0, 0); + EXPECT_EQ(AIMAPPER_ERROR_BAD_BUFFER, mapper()->v5.importBuffer(invalidHandle, &bufferHandle)) + << "importBuffer with invalid handle did not fail with BAD_BUFFER"; + native_handle_delete(invalidHandle); +} + +/** + * Test IMapper::freeBuffer with invalid buffers. + */ +TEST_P(GraphicsMapperStableCTests, FreeBufferNegative) { + native_handle_t* bufferHandle = nullptr; + EXPECT_EQ(AIMAPPER_ERROR_BAD_BUFFER, mapper()->v5.freeBuffer(bufferHandle)) + << "freeBuffer with nullptr did not fail with BAD_BUFFER"; + + bufferHandle = native_handle_create(0, 0); + EXPECT_EQ(AIMAPPER_ERROR_BAD_BUFFER, mapper()->v5.freeBuffer(bufferHandle)) + << "freeBuffer with invalid handle did not fail with BAD_BUFFER"; + native_handle_delete(bufferHandle); + + auto buffer = allocateGeneric(); + EXPECT_EQ(AIMAPPER_ERROR_BAD_BUFFER, mapper()->v5.freeBuffer(buffer->rawHandle())) + << "freeBuffer with un-imported handle did not fail with BAD_BUFFER"; +} + +/** + * Test IMapper::lock and IMapper::unlock. + */ +TEST_P(GraphicsMapperStableCTests, LockUnlockBasic) { + constexpr auto usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN; + auto buffer = allocate({ + .name = {"VTS_TEMP"}, + .width = 64, + .height = 64, + .layerCount = 1, + .format = PixelFormat::RGBA_8888, + .usage = usage, + .reservedSize = 0, + }); + ASSERT_NE(nullptr, buffer.get()); + + // lock buffer for writing + const auto& info = buffer->info(); + const auto stride = buffer->stride(); + const ARect region{0, 0, info.width, info.height}; + auto handle = buffer->import(); + uint8_t* data = nullptr; + ASSERT_EQ(AIMAPPER_ERROR_NONE, + mapper()->v5.lock(*handle, static_cast(usage), region, -1, (void**)&data)); + + // RGBA_8888 + fillRGBA8888(data, info.height, stride * 4, info.width * 4); + + int releaseFence = -1; + ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*handle, &releaseFence)); + + // lock again for reading + ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.lock(*handle, static_cast(usage), region, + releaseFence, (void**)&data)); + releaseFence = -1; + + ASSERT_NO_FATAL_FAILURE(verifyRGBA8888(*handle, data, info.height, stride * 4, info.width * 4)); + + releaseFence = -1; + ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*handle, &releaseFence)); + if (releaseFence != -1) { + close(releaseFence); + } +} + +/** + * Test multiple operations associated with different color formats + */ +TEST_P(GraphicsMapperStableCTests, Lock_YCRCB_420_SP) { + BufferDescriptorInfo info{ + .name = {"VTS_TEMP"}, + .width = 64, + .height = 64, + .layerCount = 1, + .format = PixelFormat::YCRCB_420_SP, + .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN, + .reservedSize = 0, + }; + auto buffer = allocate(info); + if (!buffer) { + ASSERT_FALSE(isSupported(info)); + GTEST_SUCCEED() << "YCRCB_420_SP format is unsupported"; + return; + } + + // lock buffer for writing + const ARect region{0, 0, info.width, info.height}; + auto handle = buffer->import(); + uint8_t* data = nullptr; + ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.lock(*handle, static_cast(info.usage), + region, -1, (void**)&data)); + + android_ycbcr yCbCr; + int64_t hSubsampling = 0; + int64_t vSubsampling = 0; + ASSERT_NO_FATAL_FAILURE(getAndroidYCbCr(*handle, data, &yCbCr, &hSubsampling, &vSubsampling)); + + constexpr uint32_t kCbCrSubSampleFactor = 2; + ASSERT_EQ(kCbCrSubSampleFactor, hSubsampling); + ASSERT_EQ(kCbCrSubSampleFactor, vSubsampling); + + auto cbData = static_cast(yCbCr.cb); + auto crData = static_cast(yCbCr.cr); + ASSERT_EQ(crData + 1, cbData); + ASSERT_EQ(2, yCbCr.chroma_step); + + fillYCbCrData(yCbCr, info.width, info.height, hSubsampling, vSubsampling); + + int releaseFence = -1; + ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*handle, &releaseFence)); + + // lock again for reading + ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.lock(*handle, static_cast(info.usage), + region, releaseFence, (void**)&data)); + releaseFence = -1; + + ASSERT_NO_FATAL_FAILURE(getAndroidYCbCr(*handle, data, &yCbCr, &hSubsampling, &vSubsampling)); + + verifyYCbCrData(yCbCr, info.width, info.height, hSubsampling, vSubsampling); + + releaseFence = -1; + ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*handle, &releaseFence)); + if (releaseFence != -1) { + close(releaseFence); + } +} + +TEST_P(GraphicsMapperStableCTests, YV12SubsampleMetadata) { + BufferDescriptorInfo info{ + .name = {"VTS_TEMP"}, + .width = 64, + .height = 64, + .layerCount = 1, + .format = PixelFormat::YV12, + .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN, + .reservedSize = 0, + }; + auto buffer = allocate(info); + ASSERT_NE(nullptr, buffer.get()); + + // lock buffer for writing + const ARect region{0, 0, info.width, info.height}; + auto handle = buffer->import(); + uint8_t* data = nullptr; + ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.lock(*handle, static_cast(info.usage), + region, -1, (void**)&data)); + + auto decodeResult = getStandardMetadata(*handle); + ASSERT_TRUE(decodeResult.has_value()); + const auto& planeLayouts = *decodeResult; + + ASSERT_EQ(3, planeLayouts.size()); + + auto yPlane = planeLayouts[0]; + auto crPlane = planeLayouts[1]; + auto cbPlane = planeLayouts[2]; + + constexpr uint32_t kCbCrSubSampleFactor = 2; + EXPECT_EQ(kCbCrSubSampleFactor, crPlane.horizontalSubsampling); + EXPECT_EQ(kCbCrSubSampleFactor, crPlane.verticalSubsampling); + + EXPECT_EQ(kCbCrSubSampleFactor, cbPlane.horizontalSubsampling); + EXPECT_EQ(kCbCrSubSampleFactor, cbPlane.verticalSubsampling); + + const long chromaSampleWidth = info.width / kCbCrSubSampleFactor; + const long chromaSampleHeight = info.height / kCbCrSubSampleFactor; + + EXPECT_EQ(info.width, yPlane.widthInSamples); + EXPECT_EQ(info.height, yPlane.heightInSamples); + + EXPECT_EQ(chromaSampleWidth, crPlane.widthInSamples); + EXPECT_EQ(chromaSampleHeight, crPlane.heightInSamples); + + EXPECT_EQ(chromaSampleWidth, cbPlane.widthInSamples); + EXPECT_EQ(chromaSampleHeight, cbPlane.heightInSamples); + + EXPECT_LE(crPlane.widthInSamples, crPlane.strideInBytes); + EXPECT_LE(cbPlane.widthInSamples, cbPlane.strideInBytes); + + int releaseFence = -1; + ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*handle, &releaseFence)); + if (releaseFence != -1) { + close(releaseFence); + } +} + +TEST_P(GraphicsMapperStableCTests, Lock_YV12) { + BufferDescriptorInfo info{ + .name = {"VTS_TEMP"}, + .width = 64, + .height = 64, + .layerCount = 1, + .format = PixelFormat::YV12, + .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN, + .reservedSize = 0, + }; + auto buffer = allocate(info); + ASSERT_NE(nullptr, buffer.get()); + + // lock buffer for writing + const ARect region{0, 0, info.width, info.height}; + auto handle = buffer->import(); + uint8_t* data = nullptr; + ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.lock(*handle, static_cast(info.usage), + region, -1, (void**)&data)); + + android_ycbcr yCbCr; + int64_t hSubsampling = 0; + int64_t vSubsampling = 0; + ASSERT_NO_FATAL_FAILURE(getAndroidYCbCr(*handle, data, &yCbCr, &hSubsampling, &vSubsampling)); + + constexpr uint32_t kCbCrSubSampleFactor = 2; + ASSERT_EQ(kCbCrSubSampleFactor, hSubsampling); + ASSERT_EQ(kCbCrSubSampleFactor, vSubsampling); + + auto cbData = static_cast(yCbCr.cb); + auto crData = static_cast(yCbCr.cr); + ASSERT_EQ(crData + yCbCr.cstride * info.height / vSubsampling, cbData); + ASSERT_EQ(1, yCbCr.chroma_step); + + fillYCbCrData(yCbCr, info.width, info.height, hSubsampling, vSubsampling); + + int releaseFence = -1; + ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*handle, &releaseFence)); + + // lock again for reading + ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.lock(*handle, static_cast(info.usage), + region, releaseFence, (void**)&data)); + releaseFence = -1; + + ASSERT_NO_FATAL_FAILURE(getAndroidYCbCr(*handle, data, &yCbCr, &hSubsampling, &vSubsampling)); + + verifyYCbCrData(yCbCr, info.width, info.height, hSubsampling, vSubsampling); + + ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*handle, &releaseFence)); + if (releaseFence != -1) { + close(releaseFence); + } +} + +TEST_P(GraphicsMapperStableCTests, Lock_YCBCR_420_888) { + BufferDescriptorInfo info{ + .name = {"VTS_TEMP"}, + .width = 64, + .height = 64, + .layerCount = 1, + .format = PixelFormat::YCBCR_420_888, + .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN, + .reservedSize = 0, + }; + auto buffer = allocate(info); + ASSERT_NE(nullptr, buffer.get()); + + // lock buffer for writing + const ARect region{0, 0, info.width, info.height}; + auto handle = buffer->import(); + uint8_t* data = nullptr; + ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.lock(*handle, static_cast(info.usage), + region, -1, (void**)&data)); + + android_ycbcr yCbCr; + int64_t hSubsampling = 0; + int64_t vSubsampling = 0; + ASSERT_NO_FATAL_FAILURE(getAndroidYCbCr(*handle, data, &yCbCr, &hSubsampling, &vSubsampling)); + + constexpr uint32_t kCbCrSubSampleFactor = 2; + ASSERT_EQ(kCbCrSubSampleFactor, hSubsampling); + ASSERT_EQ(kCbCrSubSampleFactor, vSubsampling); + + fillYCbCrData(yCbCr, info.width, info.height, hSubsampling, vSubsampling); + + int releaseFence = -1; + ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*handle, &releaseFence)); + + // lock again for reading + ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.lock(*handle, static_cast(info.usage), + region, releaseFence, (void**)&data)); + releaseFence = -1; + + ASSERT_NO_FATAL_FAILURE(getAndroidYCbCr(*handle, data, &yCbCr, &hSubsampling, &vSubsampling)); + + verifyYCbCrData(yCbCr, info.width, info.height, hSubsampling, vSubsampling); + + ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*handle, &releaseFence)); + if (releaseFence != -1) { + close(releaseFence); + } +} + +TEST_P(GraphicsMapperStableCTests, Lock_RAW10) { + BufferDescriptorInfo info{ + .name = {"VTS_TEMP"}, + .width = 64, + .height = 64, + .layerCount = 1, + .format = PixelFormat::RAW10, + .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN, + .reservedSize = 0, + }; + auto buffer = allocate(info); + if (!buffer) { + ASSERT_FALSE(isSupported(info)); + GTEST_SUCCEED() << "RAW10 format is unsupported"; + return; + } + + // lock buffer for writing + const ARect region{0, 0, info.width, info.height}; + auto handle = buffer->import(); + uint8_t* data = nullptr; + ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.lock(*handle, static_cast(info.usage), + region, -1, (void**)&data)); + + auto decodeResult = getStandardMetadata(*handle); + ASSERT_TRUE(decodeResult.has_value()); + const auto& planeLayouts = *decodeResult; + + ASSERT_EQ(1, planeLayouts.size()); + auto planeLayout = planeLayouts[0]; + + EXPECT_EQ(0, planeLayout.sampleIncrementInBits); + EXPECT_EQ(1, planeLayout.horizontalSubsampling); + EXPECT_EQ(1, planeLayout.verticalSubsampling); + + ASSERT_EQ(1, planeLayout.components.size()); + auto planeLayoutComponent = planeLayout.components[0]; + + EXPECT_EQ(PlaneLayoutComponentType::RAW, + static_cast(planeLayoutComponent.type.value)); + EXPECT_EQ(0, planeLayoutComponent.offsetInBits % 8); + EXPECT_EQ(-1, planeLayoutComponent.sizeInBits); + + int releaseFence = -1; + ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*handle, &releaseFence)); + if (releaseFence != -1) { + close(releaseFence); + } +} + +TEST_P(GraphicsMapperStableCTests, Lock_RAW12) { + BufferDescriptorInfo info{ + .name = {"VTS_TEMP"}, + .width = 64, + .height = 64, + .layerCount = 1, + .format = PixelFormat::RAW12, + .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN, + .reservedSize = 0, + }; + auto buffer = allocate(info); + if (!buffer) { + ASSERT_FALSE(isSupported(info)); + GTEST_SUCCEED() << "RAW12 format is unsupported"; + return; + } + + // lock buffer for writing + const ARect region{0, 0, info.width, info.height}; + auto handle = buffer->import(); + uint8_t* data = nullptr; + ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.lock(*handle, static_cast(info.usage), + region, -1, (void**)&data)); + + auto decodeResult = getStandardMetadata(*handle); + ASSERT_TRUE(decodeResult.has_value()); + const auto& planeLayouts = *decodeResult; + + ASSERT_EQ(1, planeLayouts.size()); + auto planeLayout = planeLayouts[0]; + + EXPECT_EQ(0, planeLayout.sampleIncrementInBits); + EXPECT_EQ(1, planeLayout.horizontalSubsampling); + EXPECT_EQ(1, planeLayout.verticalSubsampling); + + ASSERT_EQ(1, planeLayout.components.size()); + auto planeLayoutComponent = planeLayout.components[0]; + + EXPECT_EQ(PlaneLayoutComponentType::RAW, + static_cast(planeLayoutComponent.type.value)); + EXPECT_EQ(0, planeLayoutComponent.offsetInBits % 8); + EXPECT_EQ(-1, planeLayoutComponent.sizeInBits); + + int releaseFence = -1; + ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*handle, &releaseFence)); + if (releaseFence != -1) { + close(releaseFence); + } +} + +TEST_P(GraphicsMapperStableCTests, Lock_YCBCR_P010) { + BufferDescriptorInfo info{ + .name = {"VTS_TEMP"}, + .width = 64, + .height = 64, + .layerCount = 1, + .format = PixelFormat::YCBCR_P010, + .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN, + .reservedSize = 0, + }; + auto buffer = allocate(info); + if (!buffer) { + ASSERT_FALSE(isSupported(info)); + GTEST_SUCCEED() << "YCBCR_P010 format is unsupported"; + return; + } + + // lock buffer for writing + const ARect region{0, 0, info.width, info.height}; + auto handle = buffer->import(); + uint8_t* data = nullptr; + ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.lock(*handle, static_cast(info.usage), + region, -1, (void**)&data)); + + YCbCr yCbCr; + ASSERT_NO_FATAL_FAILURE(yCbCr = getAndroidYCbCr_P010(*handle, data)); + + constexpr uint32_t kCbCrSubSampleFactor = 2; + ASSERT_EQ(kCbCrSubSampleFactor, yCbCr.horizontalSubSampling); + ASSERT_EQ(kCbCrSubSampleFactor, yCbCr.verticalSubSampling); + + ASSERT_EQ(0, info.height % 2); + + // fill the data + fillYCbCrData(yCbCr.yCbCr, info.width, info.height, yCbCr.horizontalSubSampling, + yCbCr.verticalSubSampling); + // verify the YCbCr data + verifyYCbCrData(yCbCr.yCbCr, info.width, info.height, yCbCr.horizontalSubSampling, + yCbCr.verticalSubSampling); + + int releaseFence = -1; + ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*handle, &releaseFence)); + if (releaseFence != -1) { + close(releaseFence); + } +} + +TEST_P(GraphicsMapperStableCTests, LockBadAccessRegion) { + auto buffer = allocateGeneric(); + ASSERT_NE(nullptr, buffer); + const auto& info = buffer->info(); + + // lock buffer for writing + const ARect region{0, 0, info.width * 2, info.height * 2}; + auto handle = buffer->import(); + uint8_t* data = nullptr; + EXPECT_EQ(AIMAPPER_ERROR_BAD_VALUE, mapper()->v5.lock(*handle, static_cast(info.usage), + region, -1, (void**)&data)); +} + +TEST_P(GraphicsMapperStableCTests, UnlockNegative) { + native_handle_t* invalidHandle = nullptr; + int releaseFence = -1; + EXPECT_EQ(AIMAPPER_ERROR_BAD_BUFFER, mapper()->v5.unlock(invalidHandle, &releaseFence)) + << "unlock with nullptr did not fail with BAD_BUFFER"; + + invalidHandle = native_handle_create(0, 0); + EXPECT_EQ(AIMAPPER_ERROR_BAD_BUFFER, mapper()->v5.unlock(invalidHandle, &releaseFence)) + << "unlock with invalid handle did not fail with BAD_BUFFER"; + native_handle_delete(invalidHandle); + + auto buffer = allocateGeneric(); + EXPECT_EQ(AIMAPPER_ERROR_BAD_BUFFER, mapper()->v5.unlock(buffer->rawHandle(), &releaseFence)) + << "unlock with un-imported handle did not fail with BAD_BUFFER"; +} + +TEST_P(GraphicsMapperStableCTests, UnlockNotImported) { + int releaseFence = -1; + auto buffer = allocateGeneric(); + ASSERT_TRUE(buffer); + EXPECT_EQ(AIMAPPER_ERROR_BAD_BUFFER, mapper()->v5.unlock(buffer->rawHandle(), &releaseFence)) + << "unlock with un-imported handle did not fail with BAD_BUFFER"; +} + +TEST_P(GraphicsMapperStableCTests, UnlockNotLocked) { + int releaseFence = -1; + auto buffer = allocateGeneric(); + ASSERT_TRUE(buffer); + auto bufferHandle = buffer->import(); + ASSERT_TRUE(bufferHandle); + EXPECT_EQ(AIMAPPER_ERROR_BAD_BUFFER, mapper()->v5.unlock(*bufferHandle, &releaseFence)) + << "unlock with unlocked handle did not fail with BAD_BUFFER"; +} + +TEST_P(GraphicsMapperStableCTests, LockUnlockNested) { + auto buffer = allocateGeneric(); + ASSERT_TRUE(buffer); + auto bufferHandle = buffer->import(); + ASSERT_TRUE(bufferHandle); + const ARect region{0, 0, buffer->info().width, buffer->info().height}; + auto usage = static_cast(buffer->info().usage); + auto handle = buffer->import(); + uint8_t* data = nullptr; + EXPECT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.lock(*handle, usage, region, -1, (void**)&data)); + EXPECT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.lock(*handle, usage, region, -1, (void**)&data)) + << "Second lock failed"; + int releaseFence = -1; + EXPECT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*handle, &releaseFence)); + if (releaseFence != -1) { + close(releaseFence); + releaseFence = -1; + } + EXPECT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*handle, &releaseFence)) + << "Second unlock failed"; + if (releaseFence != -1) { + close(releaseFence); + releaseFence = -1; + } + EXPECT_EQ(AIMAPPER_ERROR_BAD_BUFFER, mapper()->v5.unlock(*handle, &releaseFence)) + << "Third, unmatched, unlock should have failed with BAD_BUFFER"; +} + +TEST_P(GraphicsMapperStableCTests, FlushRereadBasic) { + auto buffer = allocateGeneric(); + ASSERT_TRUE(buffer); + auto bufferHandle = buffer->import(); + ASSERT_TRUE(bufferHandle); + const auto& info = buffer->info(); + const auto stride = buffer->stride(); + const ARect region{0, 0, buffer->info().width, buffer->info().height}; + + auto writeHandle = buffer->import(); + auto readHandle = buffer->import(); + ASSERT_TRUE(writeHandle && readHandle); + + // lock buffer for writing + + uint8_t* writeData; + EXPECT_EQ(AIMAPPER_ERROR_NONE, + mapper()->v5.lock(*writeHandle, static_cast(BufferUsage::CPU_WRITE_OFTEN), + region, -1, (void**)&writeData)); + + uint8_t* readData; + EXPECT_EQ(AIMAPPER_ERROR_NONE, + mapper()->v5.lock(*readHandle, static_cast(BufferUsage::CPU_READ_OFTEN), + region, -1, (void**)&readData)); + + fillRGBA8888(writeData, info.height, stride * 4, info.width * 4); + + EXPECT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.flushLockedBuffer(*writeHandle)); + EXPECT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.rereadLockedBuffer(*readHandle)); + + ASSERT_NO_FATAL_FAILURE( + verifyRGBA8888(*readHandle, readData, info.height, stride * 4, info.width * 4)); + + int releaseFence = -1; + + EXPECT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*readHandle, &releaseFence)); + if (releaseFence != -1) { + close(releaseFence); + releaseFence = -1; + } + + EXPECT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*writeHandle, &releaseFence)); + if (releaseFence != -1) { + close(releaseFence); + releaseFence = -1; + } +} + +TEST_P(GraphicsMapperStableCTests, FlushLockedBufferBadBuffer) { + // Amazingly this is enough to make the compiler happy even though flushLockedBuffer + // is _Nonnull :shrug: + buffer_handle_t badBuffer = nullptr; + EXPECT_EQ(AIMAPPER_ERROR_BAD_BUFFER, mapper()->v5.flushLockedBuffer(badBuffer)); +} + +TEST_P(GraphicsMapperStableCTests, RereadLockedBufferBadBuffer) { + buffer_handle_t badBuffer = nullptr; + EXPECT_EQ(AIMAPPER_ERROR_BAD_BUFFER, mapper()->v5.rereadLockedBuffer(badBuffer)); +} + +TEST_P(GraphicsMapperStableCTests, GetBufferId) { + auto buffer = allocateGeneric(); + auto bufferHandle = buffer->import(); + auto bufferId = getStandardMetadata(*bufferHandle); + ASSERT_TRUE(bufferId.has_value()); + + auto buffer2 = allocateGeneric(); + auto bufferHandle2 = buffer2->import(); + auto bufferId2 = getStandardMetadata(*bufferHandle2); + ASSERT_TRUE(bufferId2.has_value()); + + EXPECT_NE(*bufferId, *bufferId2); +} + +TEST_P(GraphicsMapperStableCTests, GetName) { + auto buffer = allocate({ + .name = {"Hello, World!"}, + .width = 64, + .height = 64, + .layerCount = 1, + .format = PixelFormat::RGBA_8888, + .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN, + .reservedSize = 0, + }); + auto bufferHandle = buffer->import(); + auto name = getStandardMetadata(*bufferHandle); + ASSERT_TRUE(name.has_value()); + EXPECT_EQ(*name, "Hello, World!"); +} + +TEST_P(GraphicsMapperStableCTests, GetWidthHeight) { + auto buffer = allocate({ + .name = {"Hello, World!"}, + .width = 64, + .height = 128, + .layerCount = 1, + .format = PixelFormat::RGBA_8888, + .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN, + .reservedSize = 0, + }); + auto bufferHandle = buffer->import(); + auto value = getStandardMetadata(*bufferHandle); + ASSERT_TRUE(value.has_value()); + EXPECT_EQ(*value, 64); + value = getStandardMetadata(*bufferHandle); + ASSERT_TRUE(value.has_value()); + EXPECT_EQ(*value, 128); +} + +TEST_P(GraphicsMapperStableCTests, GetLayerCount) { + auto buffer = allocateGeneric(); + auto bufferHandle = buffer->import(); + auto value = getStandardMetadata(*bufferHandle); + ASSERT_TRUE(value.has_value()); + EXPECT_EQ(*value, buffer->info().layerCount); +} + +TEST_P(GraphicsMapperStableCTests, GetPixelFormatRequested) { + auto buffer = allocateGeneric(); + auto bufferHandle = buffer->import(); + auto value = getStandardMetadata(*bufferHandle); + ASSERT_TRUE(value.has_value()); + EXPECT_EQ(*value, buffer->info().format); +} + +TEST_P(GraphicsMapperStableCTests, GetPixelFormatFourCC) { + auto buffer = allocate({ + .name = {"Hello, World!"}, + .width = 64, + .height = 128, + .layerCount = 1, + .format = PixelFormat::RGBA_8888, + .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN, + .reservedSize = 0, + }); + { + auto bufferHandle = buffer->import(); + auto value = getStandardMetadata(*bufferHandle); + ASSERT_TRUE(value.has_value()); + EXPECT_EQ(*value, DRM_FORMAT_ABGR8888); + } + + buffer = allocate({ + .name = {"yv12"}, + .width = 64, + .height = 128, + .layerCount = 1, + .format = PixelFormat::YV12, + .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN, + .reservedSize = 0, + }); + { + auto bufferHandle = buffer->import(); + auto value = getStandardMetadata(*bufferHandle); + ASSERT_TRUE(value.has_value()); + EXPECT_EQ(*value, DRM_FORMAT_YVU420); + } +} + +TEST_P(GraphicsMapperStableCTests, GetPixelFormatModifier) { + auto buffer = allocateGeneric(); + auto bufferHandle = buffer->import(); + auto value = getStandardMetadata(*bufferHandle); + ASSERT_TRUE(value.has_value()); + // Only the upper 8-bits are defined and is just the vendor ID, the lower 56 bits are + // then vendor specific. So there's not anything useful to assert here beyond just that + // we successfully queried a value +} + +TEST_P(GraphicsMapperStableCTests, GetUsage) { + auto buffer = allocateGeneric(); + auto bufferHandle = buffer->import(); + auto value = getStandardMetadata(*bufferHandle); + ASSERT_TRUE(value.has_value()); + EXPECT_EQ(buffer->info().usage, *value); +} + +TEST_P(GraphicsMapperStableCTests, GetAllocationSize) { + auto buffer = allocateGeneric(); + auto bufferHandle = buffer->import(); + auto value = getStandardMetadata(*bufferHandle); + ASSERT_TRUE(value.has_value()); + const auto estimatedSize = buffer->stride() * buffer->info().height * 4; + // This buffer has CPU usage, so we expect at least stride * height * 4 since it should be + // generally linear uncompressed. + EXPECT_GE(*value, estimatedSize) + << "Expected allocation size to be at least stride * height * 4bpp"; + // Might need refining, but hopefully this a generous-enough upper-bound? + EXPECT_LT(*value, estimatedSize * 2) + << "Expected allocation size to less than double stride * height * 4bpp"; +} + +TEST_P(GraphicsMapperStableCTests, GetProtectedContent) { + const BufferDescriptorInfo info{ + .name = {"prot8888"}, + .width = 64, + .height = 64, + .layerCount = 1, + .format = PixelFormat::RGBA_8888, + .usage = BufferUsage::PROTECTED | BufferUsage::COMPOSER_OVERLAY, + .reservedSize = 0, + }; + auto buffer = allocate(info); + if (!buffer) { + ASSERT_FALSE(isSupported(info)) + << "Allocation of trivial sized buffer failed, so isSupported() must be false"; + GTEST_SUCCEED() << "PROTECTED RGBA_8888 is unsupported"; + return; + } + auto bufferHandle = buffer->import(); + auto value = getStandardMetadata(*bufferHandle); + ASSERT_TRUE(value.has_value()); + EXPECT_EQ(*value, 1); +} + +TEST_P(GraphicsMapperStableCTests, GetCompression) { + auto buffer = allocateGeneric(); + ASSERT_TRUE(buffer); + auto bufferHandle = buffer->import(); + ASSERT_TRUE(bufferHandle); + auto value = getStandardMetadata(*bufferHandle); + ASSERT_TRUE(value.has_value()); + EXPECT_EQ(gralloc4::Compression_None.name, value->name); + EXPECT_EQ(gralloc4::Compression_None.value, value->value); +} + +TEST_P(GraphicsMapperStableCTests, GetInterlaced) { + auto buffer = allocateGeneric(); + ASSERT_TRUE(buffer); + auto bufferHandle = buffer->import(); + ASSERT_TRUE(bufferHandle); + auto value = getStandardMetadata(*bufferHandle); + ASSERT_TRUE(value.has_value()); + EXPECT_EQ(gralloc4::Interlaced_None.name, value->name); + EXPECT_EQ(gralloc4::Interlaced_None.value, value->value); +} + +TEST_P(GraphicsMapperStableCTests, GetChromaSiting) { + auto buffer = allocateGeneric(); + ASSERT_TRUE(buffer); + auto bufferHandle = buffer->import(); + ASSERT_TRUE(bufferHandle); + auto value = getStandardMetadata(*bufferHandle); + ASSERT_TRUE(value.has_value()); + EXPECT_EQ(gralloc4::ChromaSiting_None.name, value->name); + EXPECT_EQ(gralloc4::ChromaSiting_None.value, value->value); +} + +TEST_P(GraphicsMapperStableCTests, GetPlaneLayouts) { + auto buffer = allocateGeneric(); + ASSERT_TRUE(buffer); + auto bufferHandle = buffer->import(); + ASSERT_TRUE(bufferHandle); + auto value = getStandardMetadata(*bufferHandle); + ASSERT_TRUE(value.has_value()); + ASSERT_NO_FATAL_FAILURE(verifyRGBA8888PlaneLayouts(*value)); +} + +TEST_P(GraphicsMapperStableCTests, GetCrop) { + auto buffer = allocateGeneric(); + ASSERT_TRUE(buffer); + auto bufferHandle = buffer->import(); + ASSERT_TRUE(bufferHandle); + auto value = getStandardMetadata(*bufferHandle); + ASSERT_TRUE(value.has_value()); + EXPECT_EQ(1, value->size()); + const Rect expected{0, 0, buffer->info().width, buffer->info().height}; + EXPECT_EQ(expected, value->at(0)); +} + +TEST_P(GraphicsMapperStableCTests, GetSetDataspace) { + auto buffer = allocateGeneric(); + ASSERT_TRUE(buffer); + auto bufferHandle = buffer->import(); + ASSERT_TRUE(bufferHandle); + auto value = getStandardMetadata(*bufferHandle); + ASSERT_TRUE(value.has_value()); + EXPECT_EQ(Dataspace::UNKNOWN, *value); + EXPECT_EQ(AIMAPPER_ERROR_NONE, setStandardMetadata( + *bufferHandle, Dataspace::DISPLAY_P3)); + value = getStandardMetadata(*bufferHandle); + ASSERT_TRUE(value.has_value()); + EXPECT_EQ(Dataspace::DISPLAY_P3, *value); +} + +TEST_P(GraphicsMapperStableCTests, GetSetBlendMode) { + auto buffer = allocateGeneric(); + ASSERT_TRUE(buffer); + auto bufferHandle = buffer->import(); + ASSERT_TRUE(bufferHandle); + auto value = getStandardMetadata(*bufferHandle); + ASSERT_TRUE(value.has_value()); + EXPECT_EQ(BlendMode::INVALID, *value); + EXPECT_EQ(AIMAPPER_ERROR_NONE, setStandardMetadata( + *bufferHandle, BlendMode::COVERAGE)); + value = getStandardMetadata(*bufferHandle); + ASSERT_TRUE(value.has_value()); + EXPECT_EQ(BlendMode::COVERAGE, *value); +} + +TEST_P(GraphicsMapperStableCTests, GetSetSmpte2086) { + auto buffer = allocateGeneric(); + ASSERT_TRUE(buffer); + auto bufferHandle = buffer->import(); + ASSERT_TRUE(bufferHandle); + auto value = getStandardMetadata(*bufferHandle); + ASSERT_TRUE(value.has_value()); + EXPECT_FALSE(value->has_value()); + + // TODO: Maybe use something resembling real values, but validation isn't supposed to happen + // here anyway so :shrug: + const Smpte2086 awesomeHdr{ + XyColor{1.f, 1.f}, XyColor{2.f, 2.f}, XyColor{3.f, 3.f}, + XyColor{400.f, 1000.f}, 100000.0f, 0.0001f, + }; + EXPECT_EQ(AIMAPPER_ERROR_NONE, + setStandardMetadata(*bufferHandle, awesomeHdr)); + value = getStandardMetadata(*bufferHandle); + ASSERT_TRUE(value.has_value()); + ASSERT_TRUE(value->has_value()); + EXPECT_EQ(awesomeHdr, *value); + + EXPECT_EQ(AIMAPPER_ERROR_NONE, + setStandardMetadata(*bufferHandle, std::nullopt)); + value = getStandardMetadata(*bufferHandle); + ASSERT_TRUE(value.has_value()); + EXPECT_FALSE(value->has_value()); +} + +TEST_P(GraphicsMapperStableCTests, GetCta861_3) { + auto buffer = allocateGeneric(); + ASSERT_TRUE(buffer); + auto bufferHandle = buffer->import(); + ASSERT_TRUE(bufferHandle); + auto value = getStandardMetadata(*bufferHandle); + ASSERT_TRUE(value.has_value()); + EXPECT_FALSE(value->has_value()); + + const Cta861_3 genericHlgish{1000.f, 140.f}; + EXPECT_EQ(AIMAPPER_ERROR_NONE, + setStandardMetadata(*bufferHandle, genericHlgish)); + value = getStandardMetadata(*bufferHandle); + ASSERT_TRUE(value.has_value()); + ASSERT_TRUE(value->has_value()); + EXPECT_EQ(genericHlgish, *value); + + EXPECT_EQ(AIMAPPER_ERROR_NONE, + setStandardMetadata(*bufferHandle, std::nullopt)); + value = getStandardMetadata(*bufferHandle); + ASSERT_TRUE(value.has_value()); + EXPECT_FALSE(value->has_value()); +} + +TEST_P(GraphicsMapperStableCTests, GetSmpte2094_10) { + auto buffer = allocateGeneric(); + ASSERT_TRUE(buffer); + auto bufferHandle = buffer->import(); + ASSERT_TRUE(bufferHandle); + auto value = getStandardMetadata(*bufferHandle); + if (value.has_value()) { + EXPECT_FALSE(value->has_value()); + } +} + +TEST_P(GraphicsMapperStableCTests, GetSmpte2094_40) { + auto buffer = allocateGeneric(); + ASSERT_TRUE(buffer); + auto bufferHandle = buffer->import(); + ASSERT_TRUE(bufferHandle); + auto value = getStandardMetadata(*bufferHandle); + ASSERT_TRUE(value.has_value()); + EXPECT_FALSE(value->has_value()); +} + +std::vector>> getIAllocatorsAtLeastVersion( + int32_t minVersion) { + auto instanceNames = getAidlHalInstanceNames(IAllocator::descriptor); + std::vector>> filteredInstances; + filteredInstances.reserve(instanceNames.size()); + for (const auto& name : instanceNames) { + auto allocator = + IAllocator::fromBinder(ndk::SpAIBinder(AServiceManager_checkService(name.c_str()))); + int32_t version = 0; + if (allocator->getInterfaceVersion(&version).isOk()) { + if (version >= minVersion) { + filteredInstances.emplace_back(name, std::move(allocator)); + } + } + } + return filteredInstances; +} + +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GraphicsMapperStableCTests); +INSTANTIATE_TEST_CASE_P(PerInstance, GraphicsMapperStableCTests, + testing::ValuesIn(getIAllocatorsAtLeastVersion(2)), + [](auto info) -> std::string { + std::string name = + std::to_string(info.index) + "/" + std::get<0>(info.param); + return Sanitize(name); + }); \ No newline at end of file