mirror of
https://github.com/Evolution-X/hardware_interfaces
synced 2026-02-01 11:36:00 +00:00
Allows for passing arbitrary additional options to IAllocator Can be used to control things such as compression level without exhausting usage bits Currently there are no standard options defined so this only allows for vendor-internal extensions currently. Fixes: 257075040 Test: VtsHalGraphicsAllocatorAidl_TargetTest Change-Id: I37b730f5ba141b08d458866c6d03a39b27124e02
482 lines
18 KiB
C++
482 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 = 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;
|
|
}
|
|
|
|
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);
|
|
}); |