| /* |
| * 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 "VtsHalGraphicsAllocatorAidl_TargetTest" |
| |
| #include <aidl/Vintf.h> |
| #include <aidl/android/hardware/graphics/allocator/AllocationError.h> |
| #include <aidl/android/hardware/graphics/allocator/AllocationResult.h> |
| #include <aidl/android/hardware/graphics/allocator/IAllocator.h> |
| #include <aidl/android/hardware/graphics/common/BufferUsage.h> |
| #include <aidl/android/hardware/graphics/common/PixelFormat.h> |
| #include <aidlcommonsupport/NativeHandle.h> |
| #include <android/binder_manager.h> |
| #include <android/dlext.h> |
| #include <android/hardware/graphics/mapper/4.0/IMapper.h> |
| #include <android/hardware/graphics/mapper/IMapper.h> |
| #include <dlfcn.h> |
| #include <gtest/gtest.h> |
| #include <hidl/GtestPrinter.h> |
| #include <hidl/ServiceManagement.h> |
| #include <hwui/Bitmap.h> |
| #include <renderthread/EglManager.h> |
| #include <utils/GLUtils.h> |
| #include <vndk/hardware_buffer.h> |
| #include <vndksupport/linker.h> |
| #include <initializer_list> |
| #include <optional> |
| #include <string> |
| #include <tuple> |
| |
| using namespace aidl::android::hardware::graphics::allocator; |
| using namespace aidl::android::hardware::graphics::common; |
| using namespace android; |
| using namespace android::hardware; |
| 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; |
| |
| typedef AIMapper_Error (*AIMapper_loadIMapperFn)(AIMapper* _Nullable* _Nonnull outImplementation); |
| |
| inline BufferUsage operator|(BufferUsage lhs, BufferUsage rhs) { |
| using T = std::underlying_type_t<BufferUsage>; |
| return static_cast<BufferUsage>(static_cast<T>(lhs) | static_cast<T>(rhs)); |
| } |
| |
| 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<const char*>(info.name.data())}, |
| .width = static_cast<uint32_t>(info.width), |
| .height = static_cast<uint32_t>(info.height), |
| .layerCount = static_cast<uint32_t>(info.layerCount), |
| .format = static_cast<hardware::graphics::common::V1_2::PixelFormat>(info.format), |
| .usage = static_cast<uint64_t>(info.usage), |
| .reservedSize = 0, |
| }; |
| } |
| |
| class GraphicsTestsBase; |
| |
| class BufferHandle { |
| GraphicsTestsBase& mTestBase; |
| native_handle_t* mRawHandle; |
| bool mImported = false; |
| uint32_t mStride; |
| const BufferDescriptorInfo mInfo; |
| |
| BufferHandle(const BufferHandle&) = delete; |
| void operator=(const BufferHandle&) = delete; |
| |
| public: |
| 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(); |
| |
| uint32_t stride() const { return mStride; } |
| |
| AHardwareBuffer_Desc describe() const { |
| return { |
| .width = static_cast<uint32_t>(mInfo.width), |
| .height = static_cast<uint32_t>(mInfo.height), |
| .layers = static_cast<uint32_t>(mInfo.layerCount), |
| .format = static_cast<uint32_t>(mInfo.format), |
| .usage = static_cast<uint64_t>(mInfo.usage), |
| .stride = stride(), |
| .rfu0 = 0, |
| .rfu1 = 0, |
| }; |
| } |
| |
| AHardwareBuffer* createAHardwareBuffer() const { |
| auto desc = describe(); |
| AHardwareBuffer* buffer = nullptr; |
| int err = AHardwareBuffer_createFromHandle( |
| &desc, mRawHandle, AHARDWAREBUFFER_CREATE_FROM_HANDLE_METHOD_CLONE, &buffer); |
| EXPECT_EQ(0, err) << "Failed to AHardwareBuffer_createFromHandle"; |
| return err ? nullptr : buffer; |
| } |
| }; |
| |
| class GraphicsTestsBase { |
| private: |
| friend class BufferHandle; |
| int32_t mIAllocatorVersion = 1; |
| std::shared_ptr<IAllocator> mAllocator; |
| sp<IMapper4> mMapper4; |
| AIMapper* mAIMapper = nullptr; |
| |
| protected: |
| void Initialize(std::string allocatorService) { |
| mAllocator = IAllocator::fromBinder( |
| ndk::SpAIBinder(AServiceManager_checkService(allocatorService.c_str()))); |
| 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"; |
| } |
| |
| private: |
| BufferDescriptor createDescriptor(const BufferDescriptorInfo& descriptorInfo) { |
| BufferDescriptor descriptor; |
| mMapper4->createDescriptor( |
| convert(descriptorInfo), [&](const auto& tmpError, const auto& tmpDescriptor) { |
| ASSERT_EQ(Error::NONE, tmpError) << "failed to create descriptor"; |
| descriptor = tmpDescriptor; |
| }); |
| |
| return descriptor; |
| } |
| |
| public: |
| std::unique_ptr<BufferHandle> allocate(const BufferDescriptorInfo& descriptorInfo) { |
| AllocationResult 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) { |
| 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<BufferHandle>(*this, dupFromAidl(result.buffers[0]), false, |
| result.stride, descriptorInfo); |
| } |
| } |
| |
| bool isSupported(const BufferDescriptorInfo& descriptorInfo) { |
| bool ret = false; |
| 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; |
| } |
| }; |
| |
| 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<std::string> { |
| public: |
| void SetUp() override { Initialize(GetParam()); } |
| |
| void TearDown() override {} |
| }; |
| |
| struct FlushMethod { |
| std::string name; |
| std::function<void(EglManager&)> func; |
| }; |
| |
| class GraphicsFrontBufferTests |
| : public GraphicsTestsBase, |
| public ::testing::TestWithParam<std::tuple<std::string, FlushMethod>> { |
| private: |
| EglManager eglManager; |
| std::function<void(EglManager&)> flush; |
| |
| public: |
| void SetUp() override { |
| 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) { |
| EGLClientBuffer clientBuffer = eglGetNativeClientBufferANDROID(buffer); |
| AutoEglImage eglImage(eglManager.eglDisplay(), clientBuffer); |
| AutoSkiaGlTexture glTexture; |
| AutoGLFramebuffer glFbo; |
| glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, eglImage.image); |
| glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, |
| glTexture.mTexture, 0); |
| |
| AHardwareBuffer_Desc desc; |
| AHardwareBuffer_describe(buffer, &desc); |
| glViewport(0, 0, desc.width, desc.height); |
| glDisable(GL_STENCIL_TEST); |
| glDisable(GL_SCISSOR_TEST); |
| glClearColor(red, green, blue, alpha); |
| glClear(GL_COLOR_BUFFER_BIT); |
| flush(eglManager); |
| } |
| |
| void fillWithGpu(AHardwareBuffer* buffer, /*RGBA*/ uint32_t color) { |
| // Keep it simple for now |
| static_assert(__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__); |
| float a = float((color >> 24) & 0xff) / 255.0f; |
| float b = float((color >> 16) & 0xff) / 255.0f; |
| float g = float((color >> 8) & 0xff) / 255.0f; |
| float r = float((color)&0xff) / 255.0f; |
| fillWithGpu(buffer, r, g, b, a); |
| } |
| }; |
| |
| TEST_P(GraphicsAllocatorAidlTests, CanAllocate) { |
| auto buffer = allocate({ |
| .name = {"CPU_8888"}, |
| .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(GraphicsFrontBufferTests, FrontBufferGpuToCpu) { |
| BufferDescriptorInfo info{ |
| .name = {"CPU_8888"}, |
| .width = 64, |
| .height = 64, |
| .layerCount = 1, |
| .format = PixelFormat::RGBA_8888, |
| .usage = BufferUsage::GPU_RENDER_TARGET | BufferUsage::CPU_READ_OFTEN | |
| BufferUsage::FRONT_BUFFER, |
| .reservedSize = 0, |
| }; |
| const bool supported = isSupported(info); |
| auto buffer = allocate(info); |
| if (!supported) { |
| ASSERT_EQ(nullptr, buffer.get()) |
| << "Allocation succeeded, but IMapper::isSupported was false"; |
| GTEST_SKIP(); |
| } else { |
| ASSERT_NE(nullptr, buffer.get()) << "Allocation failed, but IMapper::isSupported was true"; |
| } |
| |
| AHardwareBuffer* ahb = buffer->createAHardwareBuffer(); |
| ASSERT_NE(nullptr, ahb); |
| |
| // We draw 3 times with 3 different colors to ensure the flush is consistently flushing. |
| // Particularly for glFlush() there's occasions where it seems something triggers a flush |
| // to happen even though glFlush itself isn't consistently doing so, but for FRONT_BUFFER |
| // bound buffers it is supposed to consistently flush. |
| for (uint32_t color : {0xFF0000FFu, 0x00FF00FFu, 0x0000FFFFu}) { |
| fillWithGpu(ahb, color); |
| uint32_t* addr; |
| ASSERT_EQ(0, AHardwareBuffer_lock(ahb, AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN, -1, nullptr, |
| (void**)&addr)); |
| // Spot check a few pixels |
| EXPECT_EQ(color, addr[0]); |
| EXPECT_EQ(color, addr[32 + (32 * buffer->stride())]); |
| AHardwareBuffer_unlock(ahb, nullptr); |
| } |
| |
| AHardwareBuffer_release(ahb); |
| } |
| |
| TEST_P(GraphicsFrontBufferTests, FrontBufferGpuToGpu) { |
| BufferDescriptorInfo info{ |
| .name = {"CPU_8888"}, |
| .width = 64, |
| .height = 64, |
| .layerCount = 1, |
| .format = PixelFormat::RGBA_8888, |
| .usage = BufferUsage::GPU_RENDER_TARGET | BufferUsage::GPU_TEXTURE | |
| BufferUsage::FRONT_BUFFER, |
| .reservedSize = 0, |
| }; |
| const bool supported = isSupported(info); |
| auto buffer = allocate(info); |
| if (!supported) { |
| ASSERT_EQ(nullptr, buffer.get()) |
| << "Allocation succeeded, but IMapper::isSupported was false"; |
| GTEST_SKIP(); |
| } else { |
| ASSERT_NE(nullptr, buffer.get()) << "Allocation failed, but IMapper::isSupported was true"; |
| } |
| |
| AHardwareBuffer* ahb = buffer->createAHardwareBuffer(); |
| ASSERT_NE(nullptr, ahb); |
| |
| // We draw 3 times with 3 different colors to ensure the flush is consistently flushing. |
| // Particularly for glFlush() there's occasions where it seems something triggers a flush |
| // to happen even though glFlush itself isn't consistently doing so, but for FRONT_BUFFER |
| // bound buffers it is supposed to consistently flush. |
| for (uint32_t color : {0xFF0000FFu, 0x00FF00FFu, 0x0000FFFFu}) { |
| fillWithGpu(ahb, color); |
| sk_sp<Bitmap> hwBitmap = Bitmap::createFrom(ahb, SkColorSpace::MakeSRGB()); |
| SkBitmap cpuBitmap = hwBitmap->getSkBitmap(); |
| // Spot check a few pixels |
| EXPECT_EQ(color, *cpuBitmap.getAddr32(0, 0)); |
| EXPECT_EQ(color, *cpuBitmap.getAddr32(16, 30)); |
| } |
| |
| AHardwareBuffer_release(ahb); |
| } |
| |
| GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GraphicsAllocatorAidlTests); |
| INSTANTIATE_TEST_CASE_P(PerInstance, GraphicsAllocatorAidlTests, |
| testing::ValuesIn(getAidlHalInstanceNames(IAllocator::descriptor)), |
| PrintInstanceNameToString); |
| |
| const auto FlushMethodsValues = testing::Values( |
| FlushMethod{"glFinish", [](EglManager&) { glFinish(); }}, |
| FlushMethod{"glFlush", |
| [](EglManager&) { |
| glFlush(); |
| // Since the goal is to verify that glFlush() actually flushes, we can't |
| // wait on any sort of fence since that will change behavior So instead we |
| // just sleep & hope |
| sleep(1); |
| }}, |
| FlushMethod{"eglClientWaitSync", [](EglManager& eglManager) { |
| EGLDisplay display = eglManager.eglDisplay(); |
| EGLSyncKHR fence = eglCreateSyncKHR(display, EGL_SYNC_FENCE_KHR, nullptr); |
| eglClientWaitSyncKHR(display, fence, EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, |
| EGL_FOREVER_KHR); |
| eglDestroySyncKHR(display, fence); |
| }}); |
| GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GraphicsFrontBufferTests); |
| INSTANTIATE_TEST_CASE_P( |
| PerInstance, GraphicsFrontBufferTests, |
| testing::Combine(testing::ValuesIn(getAidlHalInstanceNames(IAllocator::descriptor)), |
| FlushMethodsValues), |
| [](auto info) -> std::string { |
| std::string name = std::to_string(info.index) + "/" + std::get<1>(info.param).name; |
| return Sanitize(name); |
| }); |