/* * 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 #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; }; constexpr const char* STANDARD_METADATA_NAME = "android.hardware.graphics.common.StandardMetadataType"; static bool isStandardMetadata(AIMapper_MetadataType metadataType) { return strcmp(STANDARD_METADATA_NAME, metadataType.name) == 0; } static std::string toString(const std::vector types) { std::stringstream buf; buf << "["; for (auto type : types) { buf << toString(type) << ", "; } buf.seekp(-2, buf.cur); buf << "]"; return buf.str(); } 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; int32_t* mIMapperHALVersion = nullptr; 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 = AServiceManager_openDeclaredPassthroughHal("mapper", mapperSuffix.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); mIMapperHALVersion = (int32_t*)dlsym(so, "ANDROID_HAL_MAPPER_VERSION"); } public: AIMapper_loadIMapperFn getIMapperLoader() const { return mIMapperLoader; } int32_t* getHalVersion() const { return mIMapperHALVersion; } 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, VersionChecks) { ASSERT_NE(nullptr, getHalVersion()) << "Resolving ANDROID_HAL_MAPPER_VERSION symbol failed"; int32_t halVersion = *getHalVersion(); EXPECT_EQ(halVersion, AIMAPPER_VERSION_5) << "Unrecognized ANDROID_HAL_MAPPER_VERSION"; EXPECT_EQ(mapper()->version, AIMAPPER_VERSION_5) << "Unrecognized AIMapper::version"; EXPECT_EQ(halVersion, mapper()->version) << "AIMapper version & ANDROID_HAL_MAPPER_VERSION don't agree"; } 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, GetUsage64) { BufferDescriptorInfo info{ .name = {"VTS_TEMP"}, .width = 64, .height = 64, .layerCount = 1, .format = PixelFormat::RGBA_8888, .usage = BufferUsage::FRONT_BUFFER | BufferUsage::GPU_RENDER_TARGET | BufferUsage::COMPOSER_OVERLAY | BufferUsage::GPU_TEXTURE, .reservedSize = 0, }; if (!isSupported(info)) { GTEST_SKIP(); } auto buffer = allocate(info); auto bufferHandle = buffer->import(); auto value = getStandardMetadata(*bufferHandle); ASSERT_TRUE(value.has_value()); using T = std::underlying_type_t; EXPECT_EQ(static_cast(buffer->info().usage), static_cast(*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); if (value.has_value()) { EXPECT_FALSE(value->has_value()); } } TEST_P(GraphicsMapperStableCTests, GetStride) { auto buffer = allocateGeneric(); ASSERT_TRUE(buffer); auto bufferHandle = buffer->import(); ASSERT_TRUE(bufferHandle); auto value = getStandardMetadata(*bufferHandle); ASSERT_TRUE(value.has_value()); EXPECT_EQ(buffer->stride(), *value); } TEST_P(GraphicsMapperStableCTests, SupportsRequiredGettersSetters) { auto buffer = allocateGeneric(); ASSERT_TRUE(buffer); auto bufferHandle = buffer->import(); ASSERT_TRUE(bufferHandle); const AIMapper_MetadataTypeDescription* descriptions = nullptr; size_t descriptionCount = 0; ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.listSupportedMetadataTypes(&descriptions, &descriptionCount)); std::vector requiredGetters = { StandardMetadataType::BUFFER_ID, StandardMetadataType::NAME, StandardMetadataType::WIDTH, StandardMetadataType::HEIGHT, StandardMetadataType::LAYER_COUNT, StandardMetadataType::PIXEL_FORMAT_REQUESTED, StandardMetadataType::PIXEL_FORMAT_FOURCC, StandardMetadataType::PIXEL_FORMAT_MODIFIER, StandardMetadataType::USAGE, StandardMetadataType::ALLOCATION_SIZE, StandardMetadataType::PROTECTED_CONTENT, StandardMetadataType::COMPRESSION, StandardMetadataType::INTERLACED, StandardMetadataType::CHROMA_SITING, StandardMetadataType::PLANE_LAYOUTS, StandardMetadataType::CROP, StandardMetadataType::DATASPACE, StandardMetadataType::BLEND_MODE, StandardMetadataType::SMPTE2086, StandardMetadataType::CTA861_3, StandardMetadataType::STRIDE, }; std::vector requiredSetters = { StandardMetadataType::DATASPACE, StandardMetadataType::BLEND_MODE, StandardMetadataType::SMPTE2086, StandardMetadataType::CTA861_3, }; for (int i = 0; i < descriptionCount; i++) { const auto& it = descriptions[i]; if (isStandardMetadata(it.metadataType)) { EXPECT_GT(it.metadataType.value, static_cast(StandardMetadataType::INVALID)); EXPECT_LT(it.metadataType.value, ndk::internal::enum_values.size()); if (it.isGettable) { std::erase(requiredGetters, static_cast(it.metadataType.value)); } if (it.isSettable) { std::erase(requiredSetters, static_cast(it.metadataType.value)); } } else { EXPECT_NE(nullptr, it.description) << "Non-standard metadata must have a description"; int len = strlen(it.description); EXPECT_GE(len, 0) << "Non-standard metadata must have a description"; } } EXPECT_EQ(0, requiredGetters.size()) << "Missing required getters" << toString(requiredGetters); EXPECT_EQ(0, requiredSetters.size()) << "Missing required setters" << toString(requiredSetters); } /* * Test that verifies that if the optional StandardMetadataTypes have getters, they have * the required setters as well */ TEST_P(GraphicsMapperStableCTests, CheckRequiredSettersIfHasGetters) { auto buffer = allocateGeneric(); ASSERT_TRUE(buffer); auto bufferHandle = buffer->import(); ASSERT_TRUE(bufferHandle); const AIMapper_MetadataTypeDescription* descriptions = nullptr; size_t descriptionCount = 0; ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.listSupportedMetadataTypes(&descriptions, &descriptionCount)); for (int i = 0; i < descriptionCount; i++) { const auto& it = descriptions[i]; if (isStandardMetadata(it.metadataType)) { const auto type = static_cast(it.metadataType.value); switch (type) { case StandardMetadataType::SMPTE2094_10: case StandardMetadataType::SMPTE2094_40: if (it.isGettable) { EXPECT_TRUE(it.isSettable) << "Type " << toString(type) << " must be settable if gettable"; } break; default: break; } } } } TEST_P(GraphicsMapperStableCTests, ListSupportedWorks) { auto buffer = allocateGeneric(); ASSERT_TRUE(buffer); auto bufferHandle = buffer->import(); ASSERT_TRUE(bufferHandle); const AIMapper_MetadataTypeDescription* descriptions = nullptr; size_t descriptionCount = 0; ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.listSupportedMetadataTypes(&descriptions, &descriptionCount)); std::vector metadataBuffer; auto get = [&](AIMapper_MetadataType metadataType) -> int32_t { int32_t size = mapper()->v5.getMetadata(*bufferHandle, metadataType, nullptr, 0); if (size >= 0) { metadataBuffer.resize(size); size = mapper()->v5.getMetadata(*bufferHandle, metadataType, metadataBuffer.data(), metadataBuffer.size()); EXPECT_EQ(size, metadataBuffer.size()); } return size; }; for (int i = 0; i < descriptionCount; i++) { const auto& it = descriptions[i]; if (!isStandardMetadata(it.metadataType)) { continue; } if (!it.isGettable) { EXPECT_FALSE(it.isSettable) << "StandardMetadata that isn't gettable must not be settable"; continue; } EXPECT_GE(get(it.metadataType), 0) << "Get failed for claimed supported getter of " << toString(static_cast(it.metadataType.value)); if (it.isSettable) { EXPECT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.setMetadata(*bufferHandle, it.metadataType, metadataBuffer.data(), metadataBuffer.size())) << "Failed to set metadata for " << toString(static_cast(it.metadataType.value)); } } } TEST_P(GraphicsMapperStableCTests, GetMetadataBadValue) { auto get = [this](StandardMetadataType type) -> AIMapper_Error { // This is a _Nonnull parameter, but this is enough obfuscation to fool the linter buffer_handle_t buffer = nullptr; int32_t ret = mapper()->v5.getStandardMetadata(buffer, static_cast(type), nullptr, 0); return (ret < 0) ? (AIMapper_Error)-ret : AIMAPPER_ERROR_NONE; }; for (auto type : ndk::enum_range()) { if (type == StandardMetadataType::INVALID) { continue; } EXPECT_EQ(AIMAPPER_ERROR_BAD_BUFFER, get(type)) << "Wrong error for " << toString(type); } } TEST_P(GraphicsMapperStableCTests, GetUnsupportedMetadata) { auto buffer = allocateGeneric(); ASSERT_TRUE(buffer); auto bufferHandle = buffer->import(); ASSERT_TRUE(bufferHandle); int result = mapper()->v5.getMetadata(*bufferHandle, {"Fake", 1}, nullptr, 0); EXPECT_EQ(AIMAPPER_ERROR_UNSUPPORTED, -result); result = mapper()->v5.getStandardMetadata( *bufferHandle, static_cast(StandardMetadataType::INVALID), nullptr, 0); EXPECT_EQ(AIMAPPER_ERROR_UNSUPPORTED, -result); constexpr int64_t unknownStandardType = ndk::internal::enum_values.size(); result = mapper()->v5.getStandardMetadata(*bufferHandle, unknownStandardType, nullptr, 0); EXPECT_EQ(AIMAPPER_ERROR_UNSUPPORTED, -result); } 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); });