mirror of
https://github.com/Evolution-X/hardware_interfaces
synced 2026-02-01 11:36:00 +00:00
AServiceManager_getDeclaredPassthroughHal() can replace android_load_sphal_library(). Bug: 316051788 Test: VtsHalGraphicsAllocatorAidl_TargetTest Change-Id: I50021790693c3ec2bce8cb4af96b0d6ee276b2e7
483 lines
18 KiB
C++
483 lines
18 KiB
C++
/*
|
|
* 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 = AServiceManager_openDeclaredPassthroughHal("mapper", mapperSuffix.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;
|
|
}
|
|
|
|
int32_t allocatorVersion() const { return mIAllocatorVersion; }
|
|
};
|
|
|
|
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(GraphicsAllocatorAidlTests, RejectsUnknownUsages) {
|
|
if (allocatorVersion() < 2) {
|
|
GTEST_SKIP() << "Must be version 2+";
|
|
return;
|
|
}
|
|
|
|
constexpr auto FirstInvalidV2Usage = static_cast<BufferUsage>(1LL << 33);
|
|
|
|
BufferUsage invalidUsage;
|
|
if (allocatorVersion() == 2) {
|
|
invalidUsage = FirstInvalidV2Usage;
|
|
} else {
|
|
GTEST_FAIL() << "Unknown version " << allocatorVersion();
|
|
}
|
|
|
|
BufferDescriptorInfo info{
|
|
.name = {"CPU_8888"},
|
|
.width = 64,
|
|
.height = 64,
|
|
.layerCount = 1,
|
|
.format = PixelFormat::RGBA_8888,
|
|
.usage = BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN,
|
|
.reservedSize = 0,
|
|
};
|
|
|
|
// First make sure we can allocate a known usage buffer as expected
|
|
EXPECT_TRUE(isSupported(info));
|
|
EXPECT_TRUE(allocate(info));
|
|
|
|
// Now add the unknown bit and verify it's rejected
|
|
info.usage |= invalidUsage;
|
|
EXPECT_FALSE(isSupported(info)) << "isSupported() returned true for unknown-to-HAL usage";
|
|
EXPECT_FALSE(allocate(info)) << "allocate succeeded for unknown-to-HAL usage";
|
|
}
|
|
|
|
TEST_P(GraphicsAllocatorAidlTests, RejectsUnknownOptions) {
|
|
if (allocatorVersion() < 2) {
|
|
GTEST_SKIP() << "Must be version 2+";
|
|
return;
|
|
}
|
|
|
|
BufferDescriptorInfo info{
|
|
.name = {"CPU_8888"},
|
|
.width = 64,
|
|
.height = 64,
|
|
.layerCount = 1,
|
|
.format = PixelFormat::RGBA_8888,
|
|
.usage = BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN,
|
|
.reservedSize = 0,
|
|
};
|
|
info.additionalOptions.push_back({"android.hardware.graphics.common.NotARealOption", 1});
|
|
|
|
EXPECT_FALSE(isSupported(info)) << "isSupported() returned true for unknown-to-HAL option";
|
|
EXPECT_FALSE(allocate(info)) << "allocate succeeded for unknown-to-HAL option";
|
|
}
|
|
|
|
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);
|
|
}); |