@@ -1,0 +1,118 @@
/**
* @file tests/unit/platform/linux/qemu/test_pixel_format.cpp
* @brief Test the conversion of QEMU scanout pixel formats to BGRX.
*/
#ifdef SUNSHINE_BUILD_QEMU
// test includes
#include "../../../../tests_common.h"
// standard includes
#include <array>
#include <vector>
// local includes
#include <src/platform/linux/qemu/pixel_format.h>
namespace {
/**
* @brief One format conversion case: raw source bytes of one pixel and the expected BGR result.
*/
struct format_case_t {
std::uint32_t format; ///< Pixman format.
std::vector<std::uint8_t> pixel; ///< Source bytes of one pixel, little-endian.
std::array<std::uint8_t, 3> bgr; ///< Expected B, G, R bytes.
};
/**
* @brief Convert a single pixel.
*
* @param format Pixman format.
* @param pixel Source bytes.
* @return Converted B, G, R bytes.
*/
std::array<std::uint8_t, 3> convert_one(std::uint32_t format, const std::vector<std::uint8_t> &pixel) {
auto layout = qemu::pixman_layout(format);
EXPECT_TRUE(layout.has_value()) << std::hex << format;
if (!layout) {
return {};
}
EXPECT_EQ(layout->bytes_per_pixel, (int) pixel.size()) << std::hex << format;
std::array<std::uint8_t, 4> out {};
qemu::convert_row(*layout, pixel.data(), out.data(), 1);
return {out[0], out[1], out[2]};
}
} // namespace
// @tag requirements: [REQ-CAP-002]
TEST(QemuPixelFormatTest, ConvertsEveryPackedFormatQemuEmits) {
const std::vector<format_case_t> cases {
// 32 bpp: pixel value 0xAARRGGBB-style layouts, stored little-endian
{qemu::pixman_format::x8r8g8b8, {0x10, 0x20, 0x30, 0x00}, {0x10, 0x20, 0x30}},
{qemu::pixman_format::a8r8g8b8, {0x10, 0x20, 0x30, 0xff}, {0x10, 0x20, 0x30}},
{qemu::pixman_format::x8b8g8r8, {0x30, 0x20, 0x10, 0x00}, {0x10, 0x20, 0x30}},
{qemu::pixman_format::a8b8g8r8, {0x30, 0x20, 0x10, 0xff}, {0x10, 0x20, 0x30}},
{qemu::pixman_format::b8g8r8x8, {0x00, 0x30, 0x20, 0x10}, {0x10, 0x20, 0x30}},
{qemu::pixman_format::b8g8r8a8, {0xff, 0x30, 0x20, 0x10}, {0x10, 0x20, 0x30}},
{qemu::pixman_format::r8g8b8x8, {0x00, 0x10, 0x20, 0x30}, {0x10, 0x20, 0x30}},
{qemu::pixman_format::r8g8b8a8, {0xff, 0x10, 0x20, 0x30}, {0x10, 0x20, 0x30}},
// 0x3ff red, 0x200 green, 0x004 blue -> 0xff, 0x80, 0x01
{qemu::pixman_format::x2r10g10b10, {0x04, 0x00, 0xf8, 0x3f}, {0x01, 0x80, 0xff}},
// 24 bpp
{qemu::pixman_format::r8g8b8, {0x10, 0x20, 0x30}, {0x10, 0x20, 0x30}},
{qemu::pixman_format::b8g8r8, {0x30, 0x20, 0x10}, {0x10, 0x20, 0x30}},
// 16 bpp: full-scale channels expand to 0xff, zero stays zero
{qemu::pixman_format::r5g6b5, {0x00, 0xf8}, {0x00, 0x00, 0xff}},
{qemu::pixman_format::r5g6b5, {0xe0, 0x07}, {0x00, 0xff, 0x00}},
{qemu::pixman_format::r5g6b5, {0x1f, 0x00}, {0xff, 0x00, 0x00}},
// mid-scale red 0b10000 -> 0x84 (bit replication), green 0b100000 -> 0x82
{qemu::pixman_format::r5g6b5, {0x00, 0x84}, {0x00, 0x82, 0x84}},
{qemu::pixman_format::b5g6r5, {0x1f, 0x00}, {0x00, 0x00, 0xff}},
{qemu::pixman_format::x1r5g5b5, {0x00, 0x7c}, {0x00, 0x00, 0xff}},
{qemu::pixman_format::x1r5g5b5, {0xe0, 0x03}, {0x00, 0xff, 0x00}},
};
for (const auto &c : cases) {
EXPECT_EQ(convert_one(c.format, c.pixel), c.bgr) << "format 0x" << std::hex << c.format;
}
}
// @tag requirements: [REQ-CAP-002]
TEST(QemuPixelFormatTest, UsesCopyAndSwapFastPaths) {
EXPECT_EQ(qemu::pixman_layout(qemu::pixman_format::x8r8g8b8)->kind, qemu::pixel_layout_t::kind_e::bgrx);
EXPECT_EQ(qemu::pixman_layout(qemu::pixman_format::a8r8g8b8)->kind, qemu::pixel_layout_t::kind_e::bgrx);
EXPECT_EQ(qemu::pixman_layout(qemu::pixman_format::x8b8g8r8)->kind, qemu::pixel_layout_t::kind_e::rgbx);
EXPECT_EQ(qemu::pixman_layout(qemu::pixman_format::a8b8g8r8)->kind, qemu::pixel_layout_t::kind_e::rgbx);
EXPECT_EQ(qemu::pixman_layout(qemu::pixman_format::r5g6b5)->kind, qemu::pixel_layout_t::kind_e::packed);
// a whole row through the swap path
auto layout = *qemu::pixman_layout(qemu::pixman_format::x8b8g8r8);
const std::vector<std::uint8_t> row {1, 2, 3, 4, 5, 6, 7, 8};
std::vector<std::uint8_t> out(8);
qemu::convert_row(layout, row.data(), out.data(), 2);
EXPECT_EQ(out, (std::vector<std::uint8_t> {3, 2, 1, 4, 7, 6, 5, 8}));
}
// @tag requirements: [REQ-CAP-002]
TEST(QemuPixelFormatTest, RejectsNonRgbFormats) {
EXPECT_FALSE(qemu::pixman_layout(qemu::pixman_format::a8).has_value());
EXPECT_FALSE(qemu::pixman_layout(0x08040000).has_value()); // c8, palette
EXPECT_FALSE(qemu::pixman_layout(0x08050000).has_value()); // g8, gray
EXPECT_FALSE(qemu::pixman_layout(0x0c028444).has_value()); // a4r4g4b4 at 12 bpp: not byte-aligned
EXPECT_FALSE(qemu::pixman_layout(0x20020fff).has_value()); // channels wider than the pixel
EXPECT_FALSE(qemu::pixman_layout(0).has_value());
}
// @tag requirements: [REQ-CAP-002, REQ-CAP-003]
TEST(QemuPixelFormatTest, MapsDrmFourccToPixman) {
EXPECT_EQ(qemu::pixman_from_drm_fourcc(qemu::drm_fourcc::xrgb8888), qemu::pixman_format::x8r8g8b8);
EXPECT_EQ(qemu::pixman_from_drm_fourcc(qemu::drm_fourcc::argb8888), qemu::pixman_format::a8r8g8b8);
EXPECT_EQ(qemu::pixman_from_drm_fourcc(qemu::drm_fourcc::xbgr8888), qemu::pixman_format::x8b8g8r8);
EXPECT_EQ(qemu::pixman_from_drm_fourcc(qemu::drm_fourcc::abgr8888), qemu::pixman_format::a8b8g8r8);
EXPECT_EQ(qemu::pixman_from_drm_fourcc(qemu::drm_fourcc::bgrx8888), qemu::pixman_format::b8g8r8x8);
EXPECT_EQ(qemu::pixman_from_drm_fourcc(qemu::drm_fourcc::bgra8888), qemu::pixman_format::b8g8r8a8);
EXPECT_EQ(qemu::pixman_from_drm_fourcc(qemu::drm_fourcc::rgbx8888), qemu::pixman_format::r8g8b8x8);
EXPECT_EQ(qemu::pixman_from_drm_fourcc(qemu::drm_fourcc::rgba8888), qemu::pixman_format::r8g8b8a8);
EXPECT_EQ(qemu::pixman_from_drm_fourcc(qemu::drm_fourcc::rgb565), qemu::pixman_format::r5g6b5);
EXPECT_FALSE(qemu::pixman_from_drm_fourcc(0x3231564e).has_value()); // NV12
}
#endif