ladybird/Tests/LibEDID/TestEDID.cpp
Tom 8184870f93 LibEDID: Add a library to parse EDID blobs
This library can be used (for the most part) by kernel drivers as well
as user mode. For this reason FixedPoint is used rather than floating
point, but kept to a minimum.
2022-01-23 22:45:21 +00:00

409 lines
20 KiB
C++

/*
* Copyright (c) 2022, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibEDID/DMT.h>
#include <LibEDID/EDID.h>
#include <LibTest/TestCase.h>
static const u8 edid1_bin[] = {
0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x49, 0x14, 0x34, 0x12,
0x00, 0x00, 0x00, 0x00, 0x2a, 0x18, 0x01, 0x04, 0xa5, 0x1a, 0x13, 0x78,
0x06, 0xee, 0x91, 0xa3, 0x54, 0x4c, 0x99, 0x26, 0x0f, 0x50, 0x54, 0x21,
0x08, 0x00, 0xe1, 0xc0, 0xd1, 0xc0, 0xd1, 0x00, 0xa9, 0x40, 0xb3, 0x00,
0x95, 0x00, 0x81, 0x80, 0x81, 0x40, 0x25, 0x20, 0x00, 0x66, 0x41, 0x00,
0x1a, 0x30, 0x00, 0x1e, 0x33, 0x40, 0x04, 0xc3, 0x10, 0x00, 0x00, 0x18,
0x00, 0x00, 0x00, 0xfd, 0x00, 0x32, 0x7d, 0x1e, 0xa0, 0x78, 0x01, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x51,
0x45, 0x4d, 0x55, 0x20, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x0a,
0x00, 0x00, 0x00, 0xf7, 0x00, 0x0a, 0x00, 0x40, 0x82, 0x00, 0x28, 0x20,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc4, 0x02, 0x03, 0x0a, 0x00,
0x45, 0x7d, 0x65, 0x60, 0x59, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xf2
};
TEST_CASE(edid1)
{
auto edid_load_result = EDID::Parser::from_bytes({ edid1_bin, sizeof(edid1_bin) });
EXPECT(!edid_load_result.is_error());
auto edid = edid_load_result.release_value();
EXPECT(edid.legacy_manufacturer_id() == "RHT");
EXPECT(!edid.aspect_ratio().has_value());
auto screen_size = edid.screen_size();
EXPECT(screen_size.has_value());
EXPECT(screen_size.value().horizontal_cm() == 26);
EXPECT(screen_size.value().vertical_cm() == 19);
auto gamma = edid.gamma();
EXPECT(gamma.has_value());
EXPECT(gamma.value() >= 2.19f && gamma.value() <= 2.21f);
EXPECT(edid.display_product_name() == "QEMU Monitor");
{
static constexpr struct {
unsigned width;
unsigned height;
unsigned refresh_rate;
EDID::Parser::EstablishedTiming::Source source;
u8 dmt_id { 0 };
} expected_established_timings[] = {
{ 640, 480, 60, EDID::Parser::EstablishedTiming::Source::IBM, 0x4 },
{ 800, 600, 60, EDID::Parser::EstablishedTiming::Source::VESA, 0x9 },
{ 1024, 768, 60, EDID::Parser::EstablishedTiming::Source::VESA, 0x10 },
{ 1280, 768, 60, EDID::Parser::EstablishedTiming::Source::VESA, 0x17 },
{ 1360, 768, 60, EDID::Parser::EstablishedTiming::Source::VESA, 0x27 },
{ 1400, 1050, 60, EDID::Parser::EstablishedTiming::Source::VESA, 0x2a },
{ 1792, 1344, 60, EDID::Parser::EstablishedTiming::Source::VESA, 0x3e },
{ 1856, 1392, 60, EDID::Parser::EstablishedTiming::Source::VESA, 0x41 },
{ 1920, 1440, 60, EDID::Parser::EstablishedTiming::Source::VESA, 0x49 }
};
static constexpr size_t expected_established_timings_count = sizeof(expected_established_timings) / sizeof(expected_established_timings[0]);
size_t established_timings_found = 0;
auto result = edid.for_each_established_timing([&](auto& established_timings) {
EXPECT(established_timings_found < expected_established_timings_count);
auto& expected_timings = expected_established_timings[established_timings_found];
EXPECT(established_timings.width() == expected_timings.width);
EXPECT(established_timings.height() == expected_timings.height);
EXPECT(established_timings.refresh_rate() == expected_timings.refresh_rate);
EXPECT(established_timings.source() == expected_timings.source);
EXPECT(established_timings.dmt_id() == expected_timings.dmt_id);
established_timings_found++;
return IterationDecision::Continue;
});
EXPECT(!result.is_error());
EXPECT(result.value() == IterationDecision::Continue);
EXPECT(established_timings_found == expected_established_timings_count);
}
{
static constexpr struct {
unsigned width;
unsigned height;
unsigned refresh_rate;
u8 dmt_id { 0 };
} expected_standard_established_timings[] = {
{ 2048, 1152, 60, 0x54 },
{ 1920, 1080, 60, 0x52 },
{ 1920, 1200, 60, 0x45 },
{ 1600, 1200, 60, 0x33 },
{ 1680, 1050, 60, 0x3a },
{ 1440, 900, 60, 0x2f },
{ 1280, 1024, 60, 0x23 },
{ 1280, 960, 60, 0x20 }
};
static constexpr size_t expected_standard_timings_count = sizeof(expected_standard_established_timings) / sizeof(expected_standard_established_timings[0]);
size_t standard_timings_found = 0;
auto result = edid.for_each_standard_timing([&](auto& standard_timings) {
EXPECT(standard_timings_found < expected_standard_timings_count);
auto& expected_timings = expected_standard_established_timings[standard_timings_found];
EXPECT(standard_timings.dmt_id() == expected_timings.dmt_id);
EXPECT(standard_timings.width() == expected_timings.width);
EXPECT(standard_timings.height() == expected_timings.height);
EXPECT(standard_timings.refresh_rate() == expected_timings.refresh_rate);
standard_timings_found++;
return IterationDecision::Continue;
});
EXPECT(!result.is_error());
EXPECT(result.value() == IterationDecision::Continue);
EXPECT(standard_timings_found == expected_standard_timings_count);
}
{
static constexpr struct {
unsigned block_id;
unsigned width;
unsigned height;
unsigned refresh_rate;
} expected_detailed_timings[] = {
{ 0, 1024, 768, 75 }
};
static constexpr size_t expected_detailed_timings_count = sizeof(expected_detailed_timings) / sizeof(expected_detailed_timings[0]);
size_t detailed_timings_found = 0;
auto result = edid.for_each_detailed_timing([&](auto& detailed_timing, unsigned block_id) {
EXPECT(detailed_timings_found < expected_detailed_timings_count);
auto& expected_timings = expected_detailed_timings[detailed_timings_found];
EXPECT(block_id == expected_timings.block_id);
EXPECT(detailed_timing.horizontal_addressable_pixels() == expected_timings.width);
EXPECT(detailed_timing.vertical_addressable_lines() == expected_timings.height);
EXPECT(detailed_timing.refresh_rate().lround() == expected_timings.refresh_rate);
detailed_timings_found++;
return IterationDecision::Continue;
});
EXPECT(!result.is_error());
EXPECT(result.value() == IterationDecision::Continue);
EXPECT(detailed_timings_found == expected_detailed_timings_count);
}
{
static constexpr u8 expected_vic_ids[] = { 125, 101, 96, 89, 31 };
static constexpr size_t expected_vic_ids_count = sizeof(expected_vic_ids) / sizeof(expected_vic_ids[0]);
size_t vic_ids_found = 0;
auto result = edid.for_each_short_video_descriptor([&](unsigned block_id, bool is_native, EDID::VIC::Details const& vic) {
EXPECT(vic_ids_found < expected_vic_ids_count);
EXPECT(block_id == 1);
EXPECT(!is_native); // none are marked as native
EXPECT(vic.vic_id == expected_vic_ids[vic_ids_found]);
vic_ids_found++;
return IterationDecision::Continue;
});
EXPECT(!result.is_error());
EXPECT(result.value() == IterationDecision::Continue);
EXPECT(vic_ids_found == expected_vic_ids_count);
}
{
// This edid has one CEA861 extension block only
size_t extension_blocks_found = 0;
auto result = edid.for_each_extension_block([&](unsigned block_id, u8 tag, u8 revision, ReadonlyBytes) {
EXPECT(block_id == 1);
EXPECT(tag == 0x2);
EXPECT(revision == 3);
extension_blocks_found++;
return IterationDecision::Continue;
});
EXPECT(!result.is_error());
EXPECT(result.value() == IterationDecision::Continue);
EXPECT(extension_blocks_found == 1);
}
}
static const u8 edid2_bin[] = {
0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x04, 0x72, 0x1d, 0x08,
0xd2, 0x02, 0x96, 0x49, 0x20, 0x1e, 0x01, 0x04, 0xb5, 0x3c, 0x22, 0x78,
0x3b, 0xff, 0x15, 0xa6, 0x53, 0x4a, 0x98, 0x26, 0x0f, 0x50, 0x54, 0xbf,
0xef, 0x80, 0xd1, 0xc0, 0xb3, 0x00, 0x95, 0x00, 0x81, 0x80, 0x81, 0x40,
0x81, 0xc0, 0x01, 0x01, 0x01, 0x01, 0x86, 0x6f, 0x00, 0x3c, 0xa0, 0xa0,
0x0f, 0x50, 0x08, 0x20, 0x35, 0x00, 0x55, 0x50, 0x21, 0x00, 0x00, 0x1e,
0x56, 0x5e, 0x00, 0xa0, 0xa0, 0xa0, 0x29, 0x50, 0x30, 0x20, 0x35, 0x00,
0x55, 0x50, 0x21, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x30,
0x4b, 0x78, 0x78, 0x1e, 0x01, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x00, 0x00, 0x00, 0xfc, 0x00, 0x43, 0x42, 0x32, 0x37, 0x32, 0x55, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x01, 0xc5, 0x02, 0x03, 0x33, 0x71,
0x4c, 0x12, 0x13, 0x04, 0x1f, 0x90, 0x14, 0x05, 0x01, 0x11, 0x02, 0x03,
0x4a, 0x23, 0x09, 0x07, 0x07, 0x83, 0x01, 0x00, 0x00, 0xe2, 0x00, 0xc0,
0x67, 0x03, 0x0c, 0x00, 0x10, 0x00, 0x38, 0x3c, 0xe3, 0x05, 0xe3, 0x01,
0xe3, 0x0f, 0x00, 0x00, 0xe6, 0x06, 0x07, 0x01, 0x60, 0x60, 0x45, 0x01,
0x1d, 0x00, 0x72, 0x51, 0xd0, 0x1e, 0x20, 0x6e, 0x28, 0x55, 0x00, 0x55,
0x50, 0x21, 0x00, 0x00, 0x1e, 0x01, 0x1d, 0x00, 0xbc, 0x52, 0xd0, 0x1e,
0x20, 0xb8, 0x28, 0x55, 0x40, 0x55, 0x50, 0x21, 0x00, 0x00, 0x1e, 0x56,
0x5e, 0x00, 0xa0, 0xa0, 0xa0, 0x29, 0x50, 0x30, 0x20, 0x35, 0x00, 0x55,
0x50, 0x21, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xe1
};
TEST_CASE(edid2)
{
auto edid_load_result = EDID::Parser::from_bytes({ edid2_bin, sizeof(edid2_bin) });
EXPECT(!edid_load_result.is_error());
auto edid = edid_load_result.release_value();
EXPECT(edid.legacy_manufacturer_id() == "ACR");
EXPECT(edid.serial_number() == 1234567890);
auto digital_interface = edid.digital_display();
EXPECT(digital_interface.has_value());
EXPECT(digital_interface.value().color_bit_depth() == EDID::Parser::DigitalDisplay::ColorBitDepth::BPP_10);
EXPECT(digital_interface.value().supported_interface() == EDID::Parser::DigitalDisplay::SupportedInterface::DisplayPort);
EXPECT(!digital_interface.value().features().supports_standby());
EXPECT(!digital_interface.value().features().supports_suspend());
EXPECT(digital_interface.value().features().supports_off());
EXPECT(digital_interface.value().features().preferred_timing_mode_includes_pixel_format_and_refresh_rate());
EXPECT(!digital_interface.value().features().srgb_is_default_color_space());
EXPECT(digital_interface.value().features().frequency() == EDID::Parser::DigitalDisplayFeatures::Frequency::Continuous);
EXPECT(digital_interface.value().features().supported_color_encodings() == EDID::Parser::DigitalDisplayFeatures::SupportedColorEncodings::RGB444_YCrCb444_YCrCb422);
EXPECT(!edid.aspect_ratio().has_value());
auto screen_size = edid.screen_size();
EXPECT(screen_size.has_value());
EXPECT(screen_size.value().horizontal_cm() == 60);
EXPECT(screen_size.value().vertical_cm() == 34);
auto gamma = edid.gamma();
EXPECT(gamma.has_value());
EXPECT(gamma.value() >= 2.19f && gamma.value() <= 2.21f);
EXPECT(edid.display_product_name() == "CB272U");
{
static constexpr struct {
unsigned width;
unsigned height;
unsigned refresh_rate;
EDID::Parser::EstablishedTiming::Source source;
u8 dmt_id { 0 };
} expected_established_timings[] = {
{ 720, 400, 70, EDID::Parser::EstablishedTiming::Source::IBM },
{ 640, 480, 60, EDID::Parser::EstablishedTiming::Source::IBM, 0x4 },
{ 640, 480, 67, EDID::Parser::EstablishedTiming::Source::Apple },
{ 640, 480, 73, EDID::Parser::EstablishedTiming::Source::VESA, 0x5 },
{ 640, 480, 75, EDID::Parser::EstablishedTiming::Source::VESA, 0x6 },
{ 800, 600, 56, EDID::Parser::EstablishedTiming::Source::VESA, 0x8 },
{ 800, 600, 60, EDID::Parser::EstablishedTiming::Source::VESA, 0x9 },
{ 800, 600, 72, EDID::Parser::EstablishedTiming::Source::VESA, 0xa },
{ 800, 600, 75, EDID::Parser::EstablishedTiming::Source::VESA, 0xb },
{ 832, 624, 75, EDID::Parser::EstablishedTiming::Source::Apple },
{ 1024, 768, 60, EDID::Parser::EstablishedTiming::Source::VESA, 0x10 },
{ 1024, 768, 70, EDID::Parser::EstablishedTiming::Source::VESA, 0x11 },
{ 1024, 768, 75, EDID::Parser::EstablishedTiming::Source::VESA, 0x12 },
{ 1280, 1024, 75, EDID::Parser::EstablishedTiming::Source::VESA, 0x24 },
{ 1152, 870, 75, EDID::Parser::EstablishedTiming::Source::Apple }
};
static constexpr size_t expected_established_timings_count = sizeof(expected_established_timings) / sizeof(expected_established_timings[0]);
size_t established_timings_found = 0;
auto result = edid.for_each_established_timing([&](auto& established_timings) {
EXPECT(established_timings_found < expected_established_timings_count);
auto& expected_timings = expected_established_timings[established_timings_found];
EXPECT(established_timings.width() == expected_timings.width);
EXPECT(established_timings.height() == expected_timings.height);
EXPECT(established_timings.refresh_rate() == expected_timings.refresh_rate);
EXPECT(established_timings.source() == expected_timings.source);
EXPECT(established_timings.dmt_id() == expected_timings.dmt_id);
established_timings_found++;
return IterationDecision::Continue;
});
EXPECT(!result.is_error());
EXPECT(result.value() == IterationDecision::Continue);
EXPECT(established_timings_found == expected_established_timings_count);
}
{
static constexpr struct {
unsigned width;
unsigned height;
unsigned refresh_rate;
u8 dmt_id { 0 };
} expected_standard_established_timings[] = {
{ 1920, 1080, 60, 0x52 },
{ 1680, 1050, 60, 0x3a },
{ 1440, 900, 60, 0x2f },
{ 1280, 1024, 60, 0x23 },
{ 1280, 960, 60, 0x20 },
{ 1280, 720, 60, 0x55 },
};
static constexpr size_t expected_standard_timings_count = sizeof(expected_standard_established_timings) / sizeof(expected_standard_established_timings[0]);
size_t standard_timings_found = 0;
auto result = edid.for_each_standard_timing([&](auto& standard_timings) {
EXPECT(standard_timings_found < expected_standard_timings_count);
auto& expected_timings = expected_standard_established_timings[standard_timings_found];
EXPECT(standard_timings.dmt_id() == expected_timings.dmt_id);
EXPECT(standard_timings.width() == expected_timings.width);
EXPECT(standard_timings.height() == expected_timings.height);
EXPECT(standard_timings.refresh_rate() == expected_timings.refresh_rate);
standard_timings_found++;
return IterationDecision::Continue;
});
EXPECT(!result.is_error());
EXPECT(result.value() == IterationDecision::Continue);
EXPECT(standard_timings_found == expected_standard_timings_count);
}
{
static constexpr struct {
unsigned block_id;
unsigned width;
unsigned height;
unsigned refresh_rate;
} expected_detailed_timings[] = {
{ 0, 2560, 1440, 75 },
{ 0, 2560, 1440, 60 },
{ 1, 1280, 720, 60 },
{ 1, 1280, 720, 50 },
{ 1, 2560, 1440, 60 }
};
static constexpr size_t expected_detailed_timings_count = sizeof(expected_detailed_timings) / sizeof(expected_detailed_timings[0]);
size_t detailed_timings_found = 0;
auto result = edid.for_each_detailed_timing([&](auto& detailed_timing, unsigned block_id) {
EXPECT(detailed_timings_found < expected_detailed_timings_count);
auto& expected_timings = expected_detailed_timings[detailed_timings_found];
EXPECT(block_id == expected_timings.block_id);
EXPECT(detailed_timing.horizontal_addressable_pixels() == expected_timings.width);
EXPECT(detailed_timing.vertical_addressable_lines() == expected_timings.height);
EXPECT(detailed_timing.refresh_rate().lround() == expected_timings.refresh_rate);
detailed_timings_found++;
return IterationDecision::Continue;
});
EXPECT(!result.is_error());
EXPECT(result.value() == IterationDecision::Continue);
EXPECT(detailed_timings_found == expected_detailed_timings_count);
}
{
static constexpr u8 expected_vic_ids[] = { 18, 19, 4, 31, 16, 20, 5, 1, 17, 2, 3, 74 };
static constexpr size_t expected_vic_ids_count = sizeof(expected_vic_ids) / sizeof(expected_vic_ids[0]);
size_t vic_ids_found = 0;
auto result = edid.for_each_short_video_descriptor([&](unsigned block_id, bool is_native, EDID::VIC::Details const& vic) {
EXPECT(vic_ids_found < expected_vic_ids_count);
EXPECT(block_id == 1);
EXPECT(is_native == (vic_ids_found == 4)); // the 5th value is marked native
EXPECT(vic.vic_id == expected_vic_ids[vic_ids_found]);
vic_ids_found++;
return IterationDecision::Continue;
});
EXPECT(!result.is_error());
EXPECT(result.value() == IterationDecision::Continue);
EXPECT(vic_ids_found == expected_vic_ids_count);
}
{
// This edid has one CEA861 extension block only
size_t extension_blocks_found = 0;
auto result = edid.for_each_extension_block([&](unsigned block_id, u8 tag, u8 revision, ReadonlyBytes) {
EXPECT(block_id == 1);
EXPECT(tag == 0x2);
EXPECT(revision == 3);
extension_blocks_found++;
return IterationDecision::Continue;
});
EXPECT(!result.is_error());
EXPECT(result.value() == IterationDecision::Continue);
EXPECT(extension_blocks_found == 1);
}
}
TEST_CASE(dmt_find_std_id)
{
auto* dmt = EDID::DMT::find_timing_by_std_id(0xd1, 0xf);
EXPECT(dmt);
EXPECT(dmt->dmt_id == 0x46);
EXPECT(dmt->horizontal_pixels == 1920 && dmt->vertical_lines == 1200);
}
TEST_CASE(dmt_frequency)
{
auto* dmt = EDID::DMT::find_timing_by_dmt_id(0x4);
EXPECT(dmt);
static constexpr FixedPoint<16, u32> expected_vertical_frequency(59.940);
EXPECT(dmt->vertical_frequency_hz() == expected_vertical_frequency);
static constexpr FixedPoint<16, u32> expected_horizontal_frequency(31.469);
EXPECT(dmt->horizontal_frequency_khz() == expected_horizontal_frequency);
}
TEST_CASE(vic)
{
EXPECT(!EDID::VIC::find_details_by_vic_id(0)); // invalid
EXPECT(!EDID::VIC::find_details_by_vic_id(160)); // forbidden range
EXPECT(!EDID::VIC::find_details_by_vic_id(250)); // reserved
auto* vic_def_32 = EDID::VIC::find_details_by_vic_id(32);
EXPECT(vic_def_32);
EXPECT(vic_def_32->vic_id == 32);
auto* vic_def_200 = EDID::VIC::find_details_by_vic_id(200);
EXPECT(vic_def_200);
EXPECT(vic_def_200->vic_id == 200);
for (unsigned vic_id = 0; vic_id <= 0xff; vic_id++) {
auto* vic_def = EDID::VIC::find_details_by_vic_id((u8)vic_id);
if (vic_def) {
EXPECT((vic_id >= 1 && vic_id <= 127) || (vic_id >= 193 && vic_id <= 219));
EXPECT(vic_def->vic_id == vic_id);
} else {
EXPECT(vic_id == 0 || (vic_id >= 128 && vic_id <= 192) || (vic_id >= 220));
}
}
}