Duckstation/src/common/display_timing.cpp
Connor McLaughlin 2149ab4d69 Initial commit
2019-09-11 14:00:42 +10:00

375 lines
13 KiB
C++

#include "display_timing.h"
#include "YBaseLib/String.h"
#include "common/state_wrapper.h"
DisplayTiming::DisplayTiming() = default;
void DisplayTiming::ResetClock(SimulationTime start_time)
{
m_clock_start_time = start_time;
}
SimulationTime DisplayTiming::GetTime(SimulationTime time) const
{
return GetSimulationTimeDifference(m_clock_start_time, time);
}
s32 DisplayTiming::GetTimeInFrame(SimulationTime time) const
{
return static_cast<s32>(GetTime(time) % m_vertical_total_duration);
}
void DisplayTiming::SetPixelClock(double clock)
{
m_pixel_clock = clock;
UpdateHorizontalFrequency();
UpdateVerticalFrequency();
}
void DisplayTiming::SetHorizontalVisible(s32 visible)
{
m_horizontal_visible = visible;
UpdateHorizontalFrequency();
}
void DisplayTiming::SetHorizontalSyncRange(s32 start, s32 end)
{
Assert(start <= end);
m_horizontal_front_porch = start - m_horizontal_visible;
m_horizontal_sync_length = end - start + 1;
UpdateHorizontalFrequency();
}
void DisplayTiming::SetHorizontalSyncLength(s32 start, s32 length)
{
m_horizontal_front_porch = start - m_horizontal_visible;
m_horizontal_sync_length = length;
UpdateHorizontalFrequency();
}
void DisplayTiming::SetHorizontalBackPorch(s32 bp)
{
m_horizontal_back_porch = bp;
UpdateHorizontalFrequency();
}
void DisplayTiming::SetHorizontalTotal(s32 total)
{
m_horizontal_back_porch = total - (m_horizontal_visible + m_horizontal_front_porch + m_horizontal_sync_length);
UpdateHorizontalFrequency();
}
void DisplayTiming::SetVerticalVisible(s32 visible)
{
m_vertical_visible = visible;
UpdateVerticalFrequency();
}
void DisplayTiming::SetVerticalSyncRange(s32 start, s32 end)
{
Assert(start <= end);
m_vertical_front_porch = start - m_vertical_visible;
m_vertical_sync_length = end - start + 1;
UpdateVerticalFrequency();
}
void DisplayTiming::SetVerticalSyncLength(s32 start, s32 length)
{
m_vertical_front_porch = start - m_vertical_visible;
m_vertical_sync_length = length;
UpdateVerticalFrequency();
}
void DisplayTiming::SetVerticalBackPorch(s32 bp)
{
m_vertical_back_porch = bp;
UpdateVerticalFrequency();
}
void DisplayTiming::SetVerticalTotal(s32 total)
{
m_vertical_back_porch = total - (m_vertical_visible + m_vertical_front_porch + m_vertical_sync_length);
UpdateVerticalFrequency();
}
DisplayTiming::Snapshot DisplayTiming::GetSnapshot(SimulationTime time) const
{
Snapshot ss;
if (m_clock_enable && IsValid())
{
const s32 time_in_frame = GetTimeInFrame(time);
const s32 line_number = time_in_frame / m_horizontal_total_duration;
const s32 time_in_line = time_in_frame % m_horizontal_total_duration;
ss.current_line = static_cast<u32>(line_number);
ss.current_pixel = static_cast<u32>(time_in_line / m_horizontal_pixel_duration);
ss.in_vertical_blank = (time_in_frame >= m_vertical_active_duration);
ss.in_horizontal_blank = (!ss.in_vertical_blank && (time_in_line >= m_horizontal_sync_start_time &&
time_in_line < m_horizontal_sync_end_time));
ss.vsync_active = (time_in_frame >= m_vertical_sync_start_time && line_number < m_vertical_sync_end_time);
ss.hsync_active =
(!ss.vsync_active && (time_in_line >= m_horizontal_sync_start_time && time_in_line < m_horizontal_sync_end_time));
ss.display_active = !(ss.in_horizontal_blank | ss.in_vertical_blank);
}
else
{
ss.current_line = 0;
ss.current_pixel = 0;
ss.display_active = false;
ss.in_horizontal_blank = false;
ss.in_vertical_blank = false;
ss.hsync_active = false;
ss.vsync_active = false;
}
return ss;
}
bool DisplayTiming::IsDisplayActive(SimulationTime time) const
{
if (!m_clock_enable || !IsValid())
return false;
const s32 time_in_frame = GetTimeInFrame(time);
return (time_in_frame < m_vertical_active_duration &&
(time_in_frame % m_horizontal_total_duration) < m_horizontal_active_duration);
}
bool DisplayTiming::InVerticalBlank(SimulationTime time) const
{
if (!m_clock_enable || !IsValid())
return false;
const s32 time_in_frame = GetTimeInFrame(time);
return (time_in_frame >= m_vertical_active_duration);
}
bool DisplayTiming::InHorizontalSync(SimulationTime time) const
{
if (!m_clock_enable || !IsValid())
return false;
const s32 time_in_frame = GetTimeInFrame(time);
if (time_in_frame >= m_vertical_sync_start_time && time_in_frame < m_vertical_sync_end_time)
{
// In vsync.
return false;
}
const s32 time_in_line = time_in_frame % m_horizontal_total_duration;
return (time_in_line >= m_horizontal_sync_start_time && time_in_frame < m_horizontal_sync_end_time);
}
bool DisplayTiming::InVerticalSync(SimulationTime time) const
{
const s32 time_in_frame = GetTimeInFrame(time);
return (time_in_frame >= m_vertical_sync_start_time && time_in_frame < m_vertical_sync_end_time);
}
u32 DisplayTiming::GetCurrentLine(SimulationTime time) const
{
if (!m_clock_enable || !IsValid())
return 0;
const s32 time_in_frame = GetTimeInFrame(time);
return static_cast<u32>(time_in_frame / m_horizontal_total_duration);
}
SimulationTime DisplayTiming::GetTimeUntilVSync(SimulationTime time) const
{
if (!m_clock_enable || !IsValid())
return 0;
const s32 time_in_frame = GetTimeInFrame(time);
if (time_in_frame < m_vertical_sync_start_time)
return m_vertical_sync_start_time - time_in_frame;
else
return (m_vertical_total_duration - time_in_frame) + m_vertical_sync_start_time;
}
SimulationTime DisplayTiming::GetTimeUntilVBlank(SimulationTime time) const
{
if (!m_clock_enable || !IsValid())
return 0;
const s32 time_in_frame = GetTimeInFrame(time);
if (time_in_frame >= m_vertical_active_duration)
return ((m_vertical_total_duration - time_in_frame) + m_vertical_active_duration);
else
return (m_vertical_active_duration - time_in_frame);
}
void DisplayTiming::ToString(String* str) const
{
const s32 horizontal_sync_start = m_horizontal_visible + m_horizontal_front_porch;
const s32 vertical_sync_start = m_vertical_visible + m_vertical_front_porch;
str->Format("%dx%d | %.3f KHz, %u Total, %d-%d Sync | %.3fhz, %d Total, %d-%d Sync", m_horizontal_visible,
m_vertical_visible, m_horizontal_frequency / 1000.0, m_horizontal_total, horizontal_sync_start,
horizontal_sync_start + m_horizontal_sync_length, m_vertical_frequency, m_vertical_total,
vertical_sync_start, vertical_sync_start + m_vertical_sync_length);
}
bool DisplayTiming::FrequenciesMatch(const DisplayTiming& timing) const
{
return std::tie(m_pixel_clock, m_horizontal_visible, m_horizontal_front_porch, m_horizontal_sync_length,
m_horizontal_back_porch, m_horizontal_frequency, m_vertical_visible, m_vertical_front_porch,
m_vertical_sync_length, m_vertical_back_porch, m_vertical_frequency) ==
std::tie(timing.m_pixel_clock, timing.m_horizontal_visible, timing.m_horizontal_front_porch,
timing.m_horizontal_sync_length, timing.m_horizontal_back_porch, timing.m_horizontal_frequency,
timing.m_vertical_visible, timing.m_vertical_front_porch, timing.m_vertical_sync_length,
timing.m_vertical_back_porch, timing.m_vertical_frequency);
}
bool DisplayTiming::DoState(StateWrapper& sw)
{
sw.Do(&m_clock_start_time);
sw.Do(&m_horizontal_visible);
sw.Do(&m_horizontal_front_porch);
sw.Do(&m_horizontal_sync_length);
sw.Do(&m_horizontal_back_porch);
sw.Do(&m_vertical_visible);
sw.Do(&m_vertical_front_porch);
sw.Do(&m_vertical_sync_length);
sw.Do(&m_vertical_back_porch);
sw.Do(&m_horizontal_total);
sw.Do(&m_vertical_total);
sw.Do(&m_pixel_clock);
sw.Do(&m_horizontal_frequency);
sw.Do(&m_vertical_frequency);
sw.Do(&m_horizontal_pixel_duration);
sw.Do(&m_horizontal_active_duration);
sw.Do(&m_horizontal_sync_start_time);
sw.Do(&m_horizontal_sync_end_time);
sw.Do(&m_horizontal_total_duration);
sw.Do(&m_vertical_active_duration);
sw.Do(&m_vertical_sync_start_time);
sw.Do(&m_vertical_sync_end_time);
sw.Do(&m_vertical_total_duration);
sw.Do(&m_clock_enable);
sw.Do(&m_valid);
return !sw.HasError();
}
void DisplayTiming::Reset()
{
m_clock_start_time = 0;
m_horizontal_visible = 0;
m_horizontal_front_porch = 0;
m_horizontal_sync_length = 0;
m_horizontal_back_porch = 0;
m_vertical_visible = 0;
m_vertical_front_porch = 0;
m_vertical_sync_length = 0;
m_vertical_back_porch = 0;
m_horizontal_total = 0;
m_vertical_total = 0;
m_pixel_clock = 0.0;
m_horizontal_frequency = 0.0f;
m_vertical_frequency = 0.0f;
m_horizontal_pixel_duration = 0;
m_horizontal_active_duration = 0;
m_horizontal_sync_start_time = 0;
m_horizontal_sync_end_time = 0;
m_horizontal_total_duration = 0;
m_vertical_active_duration = 0;
m_vertical_sync_start_time = 0;
m_vertical_sync_end_time = 0;
m_vertical_total_duration = 0;
m_clock_enable = false;
m_valid = false;
}
DisplayTiming& DisplayTiming::operator=(const DisplayTiming& timing)
{
m_clock_start_time = timing.m_clock_start_time;
m_horizontal_visible = timing.m_horizontal_visible;
m_horizontal_front_porch = timing.m_horizontal_front_porch;
m_horizontal_sync_length = timing.m_horizontal_sync_length;
m_horizontal_back_porch = timing.m_horizontal_back_porch;
m_vertical_visible = timing.m_vertical_visible;
m_vertical_front_porch = timing.m_vertical_front_porch;
m_vertical_sync_length = timing.m_vertical_sync_length;
m_vertical_back_porch = timing.m_vertical_back_porch;
m_horizontal_total = timing.m_horizontal_total;
m_vertical_total = timing.m_vertical_total;
m_pixel_clock = timing.m_pixel_clock;
m_horizontal_frequency = timing.m_horizontal_frequency;
m_vertical_frequency = timing.m_vertical_frequency;
m_horizontal_pixel_duration = timing.m_horizontal_pixel_duration;
m_horizontal_active_duration = timing.m_horizontal_active_duration;
m_horizontal_sync_start_time = timing.m_horizontal_sync_start_time;
m_horizontal_sync_end_time = timing.m_horizontal_sync_end_time;
m_horizontal_total_duration = timing.m_horizontal_total_duration;
m_vertical_active_duration = timing.m_vertical_active_duration;
m_vertical_sync_start_time = timing.m_vertical_sync_start_time;
m_vertical_sync_end_time = timing.m_vertical_sync_end_time;
m_vertical_total_duration = timing.m_vertical_total_duration;
m_clock_enable = timing.m_clock_enable;
m_valid = timing.m_valid;
return *this;
}
void DisplayTiming::UpdateHorizontalFrequency()
{
if (m_pixel_clock == 0.0 || m_horizontal_visible <= 0 || m_horizontal_front_porch < 0 ||
m_horizontal_sync_length < 0 || m_horizontal_back_porch < 0)
{
m_horizontal_total = 0;
m_horizontal_frequency = 0.0;
m_horizontal_active_duration = 0;
m_horizontal_sync_start_time = 0;
m_horizontal_sync_end_time = 0;
m_horizontal_total_duration = 0;
UpdateVerticalFrequency();
return;
}
m_horizontal_total =
m_horizontal_visible + m_horizontal_front_porch + m_horizontal_sync_length + m_horizontal_back_porch;
const double pixel_period = 1.0 / m_pixel_clock;
const double active_duration_s = pixel_period * static_cast<double>(m_horizontal_visible);
const double sync_start_time_s = pixel_period * static_cast<double>(m_horizontal_visible + m_horizontal_front_porch);
const double sync_end_time_s = sync_start_time_s + (pixel_period * static_cast<double>(m_horizontal_sync_length));
const double total_duration_s = pixel_period * static_cast<double>(m_horizontal_total);
m_horizontal_frequency = m_pixel_clock / static_cast<double>(m_horizontal_total);
m_horizontal_pixel_duration = static_cast<u32>(1000000000.0 * pixel_period);
m_horizontal_active_duration = static_cast<u32>(1000000000.0 * active_duration_s);
m_horizontal_sync_start_time = static_cast<u32>(1000000000.0 * sync_start_time_s);
m_horizontal_sync_end_time = static_cast<u32>(1000000000.0 * sync_end_time_s);
m_horizontal_total_duration = static_cast<u32>(1000000000.0 * total_duration_s);
UpdateVerticalFrequency();
}
void DisplayTiming::UpdateVerticalFrequency()
{
if (m_vertical_visible <= 0 || m_vertical_front_porch < 0 || m_vertical_sync_length < 0 || m_vertical_back_porch < 0)
{
m_vertical_total = 0;
m_vertical_frequency = 0;
m_vertical_active_duration = 0;
m_vertical_sync_start_time = 0;
m_vertical_sync_end_time = 0;
m_vertical_total_duration = 0;
UpdateValid();
return;
}
m_vertical_total = m_vertical_visible + m_vertical_front_porch + m_vertical_sync_length + m_vertical_back_porch;
m_vertical_frequency = m_horizontal_frequency / static_cast<double>(m_vertical_total);
m_vertical_active_duration = m_horizontal_total_duration * m_vertical_visible;
m_vertical_sync_start_time = m_horizontal_total_duration * (m_vertical_visible + m_vertical_front_porch);
m_vertical_sync_end_time = m_vertical_sync_start_time + (m_horizontal_total_duration * m_vertical_sync_length);
m_vertical_total_duration = m_horizontal_total_duration * m_vertical_total;
UpdateValid();
}
void DisplayTiming::UpdateValid()
{
m_valid = (m_horizontal_total_duration > 0 && m_vertical_total_duration > 0);
}