blob: e98fbafa1c005047b3fedbfd9aa782dfea7f386c [file] [log] [blame]
// Copyright 2023 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/graphics/display/lib/edid/dmt-timing.h"
#include <gtest/gtest.h>
#include "src/graphics/display/lib/api-types-cpp/display-timing.h"
#include "src/graphics/display/lib/edid/timings.h"
namespace edid::internal {
namespace {
TEST(DmtTimingInvariantTest, HsyncPolarityIsValid) {
for (const DmtTiming& dmt_timing : kDmtTimings) {
SCOPED_TRACE(testing::Message() << "Failed at DMT ID " << std::hex << dmt_timing.id);
EXPECT_TRUE(dmt_timing.horizontal_sync_polarity == display::SyncPolarity::kPositive ||
dmt_timing.horizontal_sync_polarity == display::SyncPolarity::kNegative);
}
}
TEST(DmtTimingInvariantTest, VsyncPolarityIsValid) {
for (const DmtTiming& dmt_timing : kDmtTimings) {
SCOPED_TRACE(testing::Message() << "Failed at DMT ID " << std::hex << dmt_timing.id);
EXPECT_TRUE(dmt_timing.vertical_sync_polarity == display::SyncPolarity::kPositive ||
dmt_timing.vertical_sync_polarity == display::SyncPolarity::kNegative);
}
}
TEST(DmtTimingInvariantTest, FieldsPerFrameIsValid) {
for (const DmtTiming& dmt_timing : kDmtTimings) {
SCOPED_TRACE(testing::Message() << "Failed at DMT ID " << std::hex << dmt_timing.id);
EXPECT_TRUE(dmt_timing.fields_per_frame == display::FieldsPerFrame::kInterlaced ||
dmt_timing.fields_per_frame == display::FieldsPerFrame::kProgressive);
}
}
TEST(DmtTimingInvariantTest, HorizontalBlankMatchesDefinition) {
for (const DmtTiming& dmt_timing : kDmtTimings) {
SCOPED_TRACE(testing::Message() << "Failed at DMT ID " << std::hex << dmt_timing.id);
EXPECT_EQ(dmt_timing.horizontal_blank_px, dmt_timing.horizontal_back_porch_px +
dmt_timing.horizontal_sync_width_px +
dmt_timing.horizontal_front_porch_px);
}
}
TEST(DmtTimingInvariantTest, HorizontalBlankStartMatchesDefinition) {
for (const DmtTiming& dmt_timing : kDmtTimings) {
SCOPED_TRACE(testing::Message() << "Failed at DMT ID " << std::hex << dmt_timing.id);
EXPECT_EQ(dmt_timing.horizontal_blank_start_px,
dmt_timing.horizontal_active_px + dmt_timing.horizontal_right_border_px);
}
}
TEST(DmtTimingInvariantTest, HorizontalSyncStartMatchesDefinition) {
for (const DmtTiming& dmt_timing : kDmtTimings) {
SCOPED_TRACE(testing::Message() << "Failed at DMT ID " << std::hex << dmt_timing.id);
EXPECT_EQ(dmt_timing.horitontal_sync_start_px, dmt_timing.horizontal_active_px +
dmt_timing.horizontal_right_border_px +
dmt_timing.horizontal_front_porch_px);
}
}
TEST(DmtTimingInvariantTest, HorizontalTotalMatchesDefinition) {
for (const DmtTiming& dmt_timing : kDmtTimings) {
SCOPED_TRACE(testing::Message() << "Failed at DMT ID " << std::hex << dmt_timing.id);
EXPECT_EQ(dmt_timing.horizontal_total_px,
dmt_timing.horizontal_active_px + dmt_timing.horizontal_right_border_px +
dmt_timing.horizontal_left_border_px + dmt_timing.horizontal_blank_px);
}
}
TEST(DmtTimingInvariantTest, VerticalActiveIsEvenForInterlacedModes) {
for (const DmtTiming& dmt_timing : kDmtTimings) {
SCOPED_TRACE(testing::Message() << "Failed at DMT ID " << std::hex << dmt_timing.id);
if (dmt_timing.fields_per_frame == display::FieldsPerFrame::kInterlaced) {
EXPECT_EQ(dmt_timing.vertical_active_lines % 2, 0);
}
}
}
TEST(DmtTimingInvariantTest, VerticalBlankMatchesDefinition) {
for (const DmtTiming& dmt_timing : kDmtTimings) {
SCOPED_TRACE(testing::Message() << "Failed at DMT ID " << std::hex << dmt_timing.id);
EXPECT_EQ(dmt_timing.vertical_blank_lines, dmt_timing.vertical_back_porch_lines +
dmt_timing.vertical_sync_width_lines +
dmt_timing.vertical_front_porch_lines);
}
}
TEST(DmtTimingInvariantTest, VerticalBlankStartMatchesDefinition) {
for (const DmtTiming& dmt_timing : kDmtTimings) {
SCOPED_TRACE(testing::Message() << "Failed at DMT ID " << std::hex << dmt_timing.id);
EXPECT_EQ(dmt_timing.vertical_blank_start_lines,
dmt_timing.vertical_active_lines + dmt_timing.vertical_bottom_border_lines);
}
}
TEST(DmtTimingInvariantTest, VerticalSyncStartMatchesDefinition) {
for (const DmtTiming& dmt_timing : kDmtTimings) {
SCOPED_TRACE(testing::Message() << "Failed at DMT ID " << std::hex << dmt_timing.id);
EXPECT_EQ(dmt_timing.vertical_sync_start_lines, dmt_timing.vertical_active_lines +
dmt_timing.vertical_bottom_border_lines +
dmt_timing.vertical_front_porch_lines);
}
}
TEST(DmtTimingInvariantTest, VerticalTotalMatchesDefinition) {
for (const DmtTiming& dmt_timing : kDmtTimings) {
SCOPED_TRACE(testing::Message() << "Failed at DMT ID " << std::hex << dmt_timing.id);
switch (dmt_timing.fields_per_frame) {
case display::FieldsPerFrame::kProgressive:
EXPECT_EQ(dmt_timing.vertical_total_lines,
dmt_timing.vertical_active_lines + dmt_timing.vertical_bottom_border_lines +
dmt_timing.vertical_blank_lines + dmt_timing.vertical_top_border_lines);
break;
case display::FieldsPerFrame::kInterlaced:
// For interlaced display modes, `vertical_total_lines` may equal to
// `vertical_active_lines + vertical_bottom_border_lines
// + vertical_blank_lines * 2 + vertical_top_border_lines`
// if the vblank does not alternate or
// `vertical_active_lines + vertical_bottom_border_lines
// + vertical_blank_lines * 2 + vertical_top_border_lines + 1`
// if the vblank alternates.
EXPECT_GE(dmt_timing.vertical_total_lines,
dmt_timing.vertical_active_lines + dmt_timing.vertical_bottom_border_lines +
dmt_timing.vertical_blank_lines * 2 + dmt_timing.vertical_top_border_lines);
EXPECT_LE(dmt_timing.vertical_total_lines, dmt_timing.vertical_active_lines +
dmt_timing.vertical_bottom_border_lines +
dmt_timing.vertical_blank_lines * 2 +
dmt_timing.vertical_top_border_lines + 1);
break;
}
}
}
TEST(DmtTimingInvariantTest, VerticalFieldRefreshRateMatchesDefinition) {
for (const DmtTiming& dmt_timing : kDmtTimings) {
SCOPED_TRACE(testing::Message() << "Failed at DMT ID " << std::hex << dmt_timing.id);
// In the test below, we recompute the vertical field refresh rate using
// other timing values (including pixel clock), and compare it with the
// value documented in the standard.
//
// We also considered recomputing pixel clock using the documented vertical
// field refresh rate and other values. However, this ended up adding more
// complexity because the test needs to also account for the deviation
// between the computed pixel clock and the documented pixel clock, which
// is caused by loss of precision in the DMT standard's calculation of the
// refresh rate.
const int fields_per_frame =
dmt_timing.fields_per_frame == display::FieldsPerFrame::kInterlaced ? 2 : 1;
// The multiplication won't overflow and cause an undefined behavior.
// `dmt_timing.pixel_clock_khz` is a 31-bit value and 1'000'000 < 2^20.
// Thus the multiplication is less than 2^52 - 1.
const int64_t pixel_clock_millihertz = int64_t{dmt_timing.pixel_clock_khz} * 1'000'000;
// The multiplication won't overflow and cause an undefined behavior.
// Both `dmt_timing.horizontal_total_px` and `dmt_timing.vertical_total_lines`
// are at most 2^16 - 1, thus the multiplication is less than 2^32 - 1.
const int64_t pixels_per_frame =
int64_t{dmt_timing.horizontal_total_px} * dmt_timing.vertical_total_lines;
// calculated_vertical_field_refresh_rate_millihertz
// = round(pixel_clock_millihertz * fields_per_frame / pixels_per_frame)
// = (pixel_clock_millihertz * fields_per_frame + pixels_per_frame / 2) div
// pixels_per_frame
//
// Since `pixel_clock_millihertz * fields_per_frame` < 2^53 - 1 and
// `pixels_per_frame` < 2^32 - 1, the divisor, dividend and the quotient
// won't overflow in int64_t.
const int64_t calculated_vertical_field_refresh_rate_millihertz =
(pixel_clock_millihertz * fields_per_frame + pixels_per_frame / 2) / pixels_per_frame;
EXPECT_EQ(int64_t{dmt_timing.vertical_field_refresh_rate_millihertz},
calculated_vertical_field_refresh_rate_millihertz);
}
}
TEST(DmtTimingToDisplayTiming, InterlacedWithAlternatingVblank) {
constexpr DmtTiming kDmtTiming = {
.id = 0x99,
// This field is computed to be consistent with the other fields,
// which have synthetic values.
.vertical_field_refresh_rate_millihertz = 61'049,
.pixel_clock_khz = 1'000'000,
.fields_per_frame = display::FieldsPerFrame::kInterlaced,
.horizontal_sync_polarity = display::SyncPolarity::kPositive,
.vertical_sync_polarity = display::SyncPolarity::kNegative,
.horizontal_total_px = 0x10'10,
.horizontal_active_px = 0x0a'0a,
.horizontal_blank_start_px = 0x0a'0a,
.horizontal_blank_px = 0x06'06,
.horitontal_sync_start_px = 0x0b'0b,
.horizontal_right_border_px = 0,
.horizontal_front_porch_px = 0x01'01,
.horizontal_sync_width_px = 0x02'02,
.horizontal_back_porch_px = 0x03'03,
.horizontal_left_border_px = 0,
.vertical_total_lines = 0x1f'1f,
.vertical_active_lines = 0x07'07,
.vertical_blank_start_lines = 0x07'07,
.vertical_blank_lines = 0x0c'0c,
.vertical_sync_start_lines = 0x0a'0a,
.vertical_bottom_border_lines = 0,
.vertical_front_porch_lines = 0x03'03,
.vertical_sync_width_lines = 0x04'04,
.vertical_back_porch_lines = 0x05'05,
.vertical_top_border_lines = 0,
};
const display::DisplayTiming kConverted = ToDisplayTiming(kDmtTiming);
EXPECT_EQ(kConverted.horizontal_total_px(), 0x10'10);
EXPECT_EQ(kConverted.horizontal_active_px, 0x0a'0a);
EXPECT_EQ(kConverted.horizontal_blank_px(), 0x06'06);
EXPECT_EQ(kConverted.horizontal_front_porch_px, 0x01'01);
EXPECT_EQ(kConverted.horizontal_sync_width_px, 0x02'02);
EXPECT_EQ(kConverted.horizontal_back_porch_px, 0x03'03);
EXPECT_EQ(kConverted.vertical_total_lines(), 0x1f'1f);
EXPECT_EQ(kConverted.vertical_active_lines, 0x07'07);
EXPECT_EQ(kConverted.vertical_blank_lines(), 0x0c'0c);
EXPECT_EQ(kConverted.vertical_front_porch_lines, 0x03'03);
EXPECT_EQ(kConverted.vertical_sync_width_lines, 0x04'04);
EXPECT_EQ(kConverted.vertical_back_porch_lines, 0x05'05);
EXPECT_EQ(kConverted.pixel_clock_frequency_hz, 1'000'000'000);
EXPECT_EQ(kConverted.fields_per_frame, display::FieldsPerFrame::kInterlaced);
EXPECT_EQ(kConverted.hsync_polarity, display::SyncPolarity::kPositive);
EXPECT_EQ(kConverted.vsync_polarity, display::SyncPolarity::kNegative);
EXPECT_EQ(kConverted.vblank_alternates, false);
EXPECT_EQ(kConverted.pixel_repetition, false);
EXPECT_EQ(kConverted.vertical_field_refresh_rate_millihertz(), 61'049);
}
TEST(DmtTimingToDisplayTiming, InterlacedWithConstantVblank) {
constexpr DmtTiming kDmtTiming = {
.id = 0x99,
// This field is computed to be consistent with the other fields,
// which have synthetic values.
.vertical_field_refresh_rate_millihertz = 61'042,
.pixel_clock_khz = 1'000'000,
.fields_per_frame = display::FieldsPerFrame::kInterlaced,
.horizontal_sync_polarity = display::SyncPolarity::kPositive,
.vertical_sync_polarity = display::SyncPolarity::kNegative,
.horizontal_total_px = 0x10'10,
.horizontal_active_px = 0x0a'0a,
.horizontal_blank_start_px = 0x0a'0a,
.horizontal_blank_px = 0x06'06,
.horitontal_sync_start_px = 0x0b'0b,
.horizontal_right_border_px = 0,
.horizontal_front_porch_px = 0x01'01,
.horizontal_sync_width_px = 0x02'02,
.horizontal_back_porch_px = 0x03'03,
.horizontal_left_border_px = 0,
.vertical_total_lines = 0x1f'20,
.vertical_active_lines = 0x07'07,
.vertical_blank_start_lines = 0x07'07,
.vertical_blank_lines = 0x0c'0c,
.vertical_sync_start_lines = 0x0a'0a,
.vertical_bottom_border_lines = 0,
.vertical_front_porch_lines = 0x03'03,
.vertical_sync_width_lines = 0x04'04,
.vertical_back_porch_lines = 0x05'05,
.vertical_top_border_lines = 0,
};
const display::DisplayTiming kConverted = ToDisplayTiming(kDmtTiming);
EXPECT_EQ(kConverted.horizontal_total_px(), 0x10'10);
EXPECT_EQ(kConverted.horizontal_active_px, 0x0a'0a);
EXPECT_EQ(kConverted.horizontal_blank_px(), 0x06'06);
EXPECT_EQ(kConverted.horizontal_front_porch_px, 0x01'01);
EXPECT_EQ(kConverted.horizontal_sync_width_px, 0x02'02);
EXPECT_EQ(kConverted.horizontal_back_porch_px, 0x03'03);
EXPECT_EQ(kConverted.vertical_total_lines(), 0x1f'20);
EXPECT_EQ(kConverted.vertical_active_lines, 0x07'07);
EXPECT_EQ(kConverted.vertical_blank_lines(), 0x0c'0c);
EXPECT_EQ(kConverted.vertical_front_porch_lines, 0x03'03);
EXPECT_EQ(kConverted.vertical_sync_width_lines, 0x04'04);
EXPECT_EQ(kConverted.vertical_back_porch_lines, 0x05'05);
EXPECT_EQ(kConverted.pixel_clock_frequency_hz, 1'000'000'000);
EXPECT_EQ(kConverted.fields_per_frame, display::FieldsPerFrame::kInterlaced);
EXPECT_EQ(kConverted.hsync_polarity, display::SyncPolarity::kPositive);
EXPECT_EQ(kConverted.vsync_polarity, display::SyncPolarity::kNegative);
EXPECT_EQ(kConverted.vblank_alternates, true);
EXPECT_EQ(kConverted.pixel_repetition, false);
EXPECT_EQ(kConverted.vertical_field_refresh_rate_millihertz(), 61'042);
}
TEST(DmtTimingToDisplayTiming, Progressive) {
constexpr DmtTiming kDmtTiming = {
.id = 0x99,
// This field is computed to be consistent with the other fields,
// which have synthetic values.
.vertical_field_refresh_rate_millihertz = 49'804,
.pixel_clock_khz = 1'000'000,
.fields_per_frame = display::FieldsPerFrame::kProgressive,
.horizontal_sync_polarity = display::SyncPolarity::kPositive,
.vertical_sync_polarity = display::SyncPolarity::kNegative,
.horizontal_total_px = 0x10'10,
.horizontal_active_px = 0x0a'0a,
.horizontal_blank_start_px = 0x0a'0a,
.horizontal_blank_px = 0x06'06,
.horitontal_sync_start_px = 0x0b'0b,
.horizontal_right_border_px = 0,
.horizontal_front_porch_px = 0x01'01,
.horizontal_sync_width_px = 0x02'02,
.horizontal_back_porch_px = 0x03'03,
.horizontal_left_border_px = 0,
.vertical_total_lines = 0x13'13,
.vertical_active_lines = 0x07'07,
.vertical_blank_start_lines = 0x07'07,
.vertical_blank_lines = 0x0c'0c,
.vertical_sync_start_lines = 0x0a'0a,
.vertical_bottom_border_lines = 0,
.vertical_front_porch_lines = 0x03'03,
.vertical_sync_width_lines = 0x04'04,
.vertical_back_porch_lines = 0x05'05,
.vertical_top_border_lines = 0,
};
const display::DisplayTiming kConverted = ToDisplayTiming(kDmtTiming);
EXPECT_EQ(kConverted.horizontal_total_px(), 0x10'10);
EXPECT_EQ(kConverted.horizontal_active_px, 0x0a'0a);
EXPECT_EQ(kConverted.horizontal_blank_px(), 0x06'06);
EXPECT_EQ(kConverted.horizontal_front_porch_px, 0x01'01);
EXPECT_EQ(kConverted.horizontal_sync_width_px, 0x02'02);
EXPECT_EQ(kConverted.horizontal_back_porch_px, 0x03'03);
EXPECT_EQ(kConverted.vertical_total_lines(), 0x13'13);
EXPECT_EQ(kConverted.vertical_active_lines, 0x07'07);
EXPECT_EQ(kConverted.vertical_blank_lines(), 0x0c'0c);
EXPECT_EQ(kConverted.vertical_front_porch_lines, 0x03'03);
EXPECT_EQ(kConverted.vertical_sync_width_lines, 0x04'04);
EXPECT_EQ(kConverted.vertical_back_porch_lines, 0x05'05);
EXPECT_EQ(kConverted.pixel_clock_frequency_hz, 1'000'000'000);
EXPECT_EQ(kConverted.fields_per_frame, display::FieldsPerFrame::kProgressive);
EXPECT_EQ(kConverted.hsync_polarity, display::SyncPolarity::kPositive);
EXPECT_EQ(kConverted.vsync_polarity, display::SyncPolarity::kNegative);
EXPECT_EQ(kConverted.vblank_alternates, false);
EXPECT_EQ(kConverted.pixel_repetition, false);
EXPECT_EQ(kConverted.vertical_field_refresh_rate_millihertz(), 49'804);
}
TEST(DmtTimingToDisplayTiming, ProgressiveWithBorder) {
constexpr DmtTiming kDmtTiming = {
.id = 0x99,
// This field is computed to be consistent with the other fields,
// which have synthetic values.
.vertical_field_refresh_rate_millihertz = 48'125,
.pixel_clock_khz = 1'000'000,
.fields_per_frame = display::FieldsPerFrame::kProgressive,
.horizontal_sync_polarity = display::SyncPolarity::kPositive,
.vertical_sync_polarity = display::SyncPolarity::kNegative,
.horizontal_total_px = 0x10'40,
.horizontal_active_px = 0x0a'0a,
.horizontal_blank_start_px = 0x0a'1a,
.horizontal_blank_px = 0x06'06,
.horitontal_sync_start_px = 0x0b'1b,
.horizontal_right_border_px = 0x00'10,
.horizontal_front_porch_px = 0x01'01,
.horizontal_sync_width_px = 0x02'02,
.horizontal_back_porch_px = 0x03'03,
.horizontal_left_border_px = 0x00'20,
.vertical_total_lines = 0x13'83,
.vertical_active_lines = 0x07'07,
.vertical_blank_start_lines = 0x07'37,
.vertical_blank_lines = 0x0c'0c,
.vertical_sync_start_lines = 0x0a'3a,
.vertical_bottom_border_lines = 0x00'30,
.vertical_front_porch_lines = 0x03'03,
.vertical_sync_width_lines = 0x04'04,
.vertical_back_porch_lines = 0x05'05,
.vertical_top_border_lines = 0x00'40,
};
const display::DisplayTiming kConverted = ToDisplayTiming(kDmtTiming);
EXPECT_EQ(kConverted.horizontal_total_px(), 0x10'40);
EXPECT_EQ(kConverted.horizontal_active_px, 0x0a'0a);
EXPECT_EQ(kConverted.horizontal_blank_px(), 0x06'36);
EXPECT_EQ(kConverted.horizontal_front_porch_px, 0x01'11);
EXPECT_EQ(kConverted.horizontal_sync_width_px, 0x02'02);
EXPECT_EQ(kConverted.horizontal_back_porch_px, 0x03'23);
EXPECT_EQ(kConverted.vertical_total_lines(), 0x13'83);
EXPECT_EQ(kConverted.vertical_active_lines, 0x07'07);
EXPECT_EQ(kConverted.vertical_blank_lines(), 0x0c'7c);
EXPECT_EQ(kConverted.vertical_front_porch_lines, 0x03'33);
EXPECT_EQ(kConverted.vertical_sync_width_lines, 0x04'04);
EXPECT_EQ(kConverted.vertical_back_porch_lines, 0x05'45);
EXPECT_EQ(kConverted.pixel_clock_frequency_hz, 1'000'000'000);
EXPECT_EQ(kConverted.fields_per_frame, display::FieldsPerFrame::kProgressive);
EXPECT_EQ(kConverted.hsync_polarity, display::SyncPolarity::kPositive);
EXPECT_EQ(kConverted.vsync_polarity, display::SyncPolarity::kNegative);
EXPECT_EQ(kConverted.vblank_alternates, false);
EXPECT_EQ(kConverted.pixel_repetition, false);
EXPECT_EQ(kConverted.vertical_field_refresh_rate_millihertz(), 48'125);
}
} // namespace
} // namespace edid::internal