HW: Move VideoInterface variables to Core::System.

This commit is contained in:
Admiral H. Curtiss 2022-10-04 02:24:01 +02:00
parent 052c7395fb
commit 9c944d5734
No known key found for this signature in database
GPG Key ID: F051B4C4044F33FB
4 changed files with 400 additions and 303 deletions

View File

@ -25,6 +25,7 @@
#include "Core/HW/SI/SI.h" #include "Core/HW/SI/SI.h"
#include "Core/HW/SystemTimers.h" #include "Core/HW/SystemTimers.h"
#include "Core/Movie.h" #include "Core/Movie.h"
#include "Core/System.h"
#include "DiscIO/Enums.h" #include "DiscIO/Enums.h"
@ -33,80 +34,90 @@
namespace VideoInterface namespace VideoInterface
{ {
// STATE_TO_SAVE struct VideoInterfaceState::Data
{
// Registers listed in order: // Registers listed in order:
static UVIVerticalTimingRegister m_VerticalTimingRegister; UVIVerticalTimingRegister vertical_timing_register;
static UVIDisplayControlRegister m_DisplayControlRegister; UVIDisplayControlRegister display_control_register;
static UVIHorizontalTiming0 m_HTiming0; UVIHorizontalTiming0 h_timing_0;
static UVIHorizontalTiming1 m_HTiming1; UVIHorizontalTiming1 h_timing_1;
static UVIVBlankTimingRegister m_VBlankTimingOdd; UVIVBlankTimingRegister vblank_timing_odd;
static UVIVBlankTimingRegister m_VBlankTimingEven; UVIVBlankTimingRegister vblank_timing_even;
static UVIBurstBlankingRegister m_BurstBlankingOdd; UVIBurstBlankingRegister burst_blanking_odd;
static UVIBurstBlankingRegister m_BurstBlankingEven; UVIBurstBlankingRegister burst_blanking_even;
static UVIFBInfoRegister m_XFBInfoTop; UVIFBInfoRegister xfb_info_top;
static UVIFBInfoRegister m_XFBInfoBottom; UVIFBInfoRegister xfb_info_bottom;
static UVIFBInfoRegister m_3DFBInfoTop; // Start making your stereoscopic demos! :p UVIFBInfoRegister xfb_3d_info_top; // Start making your stereoscopic demos! :p
static UVIFBInfoRegister m_3DFBInfoBottom; UVIFBInfoRegister xfb_3d_info_bottom;
static std::array<UVIInterruptRegister, 4> m_InterruptRegister; std::array<UVIInterruptRegister, 4> interrupt_register;
static std::array<UVILatchRegister, 2> m_LatchRegister; std::array<UVILatchRegister, 2> latch_register;
static PictureConfigurationRegister m_PictureConfiguration; PictureConfigurationRegister picture_configuration;
static UVIHorizontalScaling m_HorizontalScaling; UVIHorizontalScaling horizontal_scaling;
static SVIFilterCoefTables m_FilterCoefTables; SVIFilterCoefTables filter_coef_tables;
static u32 m_UnkAARegister = 0; // ??? 0x00FF0000 u32 unknown_aa_register = 0; // ??? 0x00FF0000
static u16 m_Clock = 0; // 0: 27MHz, 1: 54MHz u16 clock = 0; // 0: 27MHz, 1: 54MHz
static UVIDTVStatus m_DTVStatus; UVIDTVStatus dtv_status;
static UVIHorizontalStepping m_FBWidth; // Only correct when scaling is enabled? UVIHorizontalStepping fb_width; // Only correct when scaling is enabled?
static UVIBorderBlankRegister m_BorderHBlank; UVIBorderBlankRegister border_hblank;
// 0xcc002076 - 0xcc00207f is full of 0x00FF: unknown // 0xcc002076 - 0xcc00207f is full of 0x00FF: unknown
// 0xcc002080 - 0xcc002100 even more unknown // 0xcc002080 - 0xcc002100 even more unknown
static double s_target_refresh_rate = 0; double target_refresh_rate = 0;
static u32 s_target_refresh_rate_numerator = 0; u32 target_refresh_rate_numerator = 0;
static u32 s_target_refresh_rate_denominator = 1; u32 target_refresh_rate_denominator = 1;
static constexpr std::array<u32, 2> s_clock_freqs{{ u64 ticks_last_line_start; // number of ticks when the current full scanline started
u32 half_line_count; // number of halflines that have occurred for this full frame
u32 half_line_of_next_si_poll; // halfline when next SI poll results should be available
// below indexes are 0-based
u32 even_field_first_hl; // index first halfline of the even field
u32 odd_field_first_hl; // index first halfline of the odd field
u32 even_field_last_hl; // index last halfline of the even field
u32 odd_field_last_hl; // index last halfline of the odd field
};
VideoInterfaceState::VideoInterfaceState() : m_data(std::make_unique<Data>())
{
}
VideoInterfaceState::~VideoInterfaceState() = default;
static constexpr std::array<u32, 2> CLOCK_FREQUENCIES{{
27000000, 27000000,
54000000, 54000000,
}}; }};
static u64 s_ticks_last_line_start; // number of ticks when the current full scanline started static constexpr u32 NUM_HALF_LINES_FOR_SI_POLL = (7 * 2) + 1; // this is how long an SI poll takes
static u32 s_half_line_count; // number of halflines that have occurred for this full frame
static u32 s_half_line_of_next_si_poll; // halfline when next SI poll results should be available
static constexpr u32 num_half_lines_for_si_poll = (7 * 2) + 1; // this is how long an SI poll takes
// below indexes are 0-based
static u32 s_even_field_first_hl; // index first halfline of the even field
static u32 s_odd_field_first_hl; // index first halfline of the odd field
static u32 s_even_field_last_hl; // index last halfline of the even field
static u32 s_odd_field_last_hl; // index last halfline of the odd field
void DoState(PointerWrap& p) void DoState(PointerWrap& p)
{ {
p.DoPOD(m_VerticalTimingRegister); auto& state = Core::System::GetInstance().GetVideoInterfaceState().GetData();
p.DoPOD(m_DisplayControlRegister); p.DoPOD(state.vertical_timing_register);
p.Do(m_HTiming0); p.DoPOD(state.display_control_register);
p.Do(m_HTiming1); p.Do(state.h_timing_0);
p.Do(m_VBlankTimingOdd); p.Do(state.h_timing_1);
p.Do(m_VBlankTimingEven); p.Do(state.vblank_timing_odd);
p.Do(m_BurstBlankingOdd); p.Do(state.vblank_timing_even);
p.Do(m_BurstBlankingEven); p.Do(state.burst_blanking_odd);
p.Do(m_XFBInfoTop); p.Do(state.burst_blanking_even);
p.Do(m_XFBInfoBottom); p.Do(state.xfb_info_top);
p.Do(m_3DFBInfoTop); p.Do(state.xfb_info_bottom);
p.Do(m_3DFBInfoBottom); p.Do(state.xfb_3d_info_top);
p.DoArray(m_InterruptRegister); p.Do(state.xfb_3d_info_bottom);
p.DoArray(m_LatchRegister); p.DoArray(state.interrupt_register);
p.Do(m_PictureConfiguration); p.DoArray(state.latch_register);
p.DoPOD(m_HorizontalScaling); p.Do(state.picture_configuration);
p.Do(m_FilterCoefTables); p.DoPOD(state.horizontal_scaling);
p.Do(m_UnkAARegister); p.Do(state.filter_coef_tables);
p.Do(m_Clock); p.Do(state.unknown_aa_register);
p.Do(m_DTVStatus); p.Do(state.clock);
p.Do(m_FBWidth); p.Do(state.dtv_status);
p.Do(m_BorderHBlank); p.Do(state.fb_width);
p.Do(s_ticks_last_line_start); p.Do(state.border_hblank);
p.Do(s_half_line_count); p.Do(state.ticks_last_line_start);
p.Do(s_half_line_of_next_si_poll); p.Do(state.half_line_count);
p.Do(state.half_line_of_next_si_poll);
UpdateParameters(); UpdateParameters();
} }
@ -114,82 +125,84 @@ void DoState(PointerWrap& p)
// Executed after Init, before game boot // Executed after Init, before game boot
void Preset(bool _bNTSC) void Preset(bool _bNTSC)
{ {
auto& state = Core::System::GetInstance().GetVideoInterfaceState().GetData();
// NOTE: Make sure all registers are set to the correct initial state. The // NOTE: Make sure all registers are set to the correct initial state. The
// variables are not going to start zeroed if another game has been run // variables are not going to start zeroed if another game has been run
// previously (and mutated everything). // previously (and mutated everything).
m_VerticalTimingRegister.EQU = 6; state.vertical_timing_register.EQU = 6;
m_VerticalTimingRegister.ACV = 0; state.vertical_timing_register.ACV = 0;
m_DisplayControlRegister.ENB = 1; state.display_control_register.ENB = 1;
m_DisplayControlRegister.RST = 0; state.display_control_register.RST = 0;
m_DisplayControlRegister.NIN = 0; state.display_control_register.NIN = 0;
m_DisplayControlRegister.DLR = 0; state.display_control_register.DLR = 0;
m_DisplayControlRegister.LE0 = 0; state.display_control_register.LE0 = 0;
m_DisplayControlRegister.LE1 = 0; state.display_control_register.LE1 = 0;
m_DisplayControlRegister.FMT = _bNTSC ? 0 : 1; state.display_control_register.FMT = _bNTSC ? 0 : 1;
m_HTiming0.HLW = 429; state.h_timing_0.HLW = 429;
m_HTiming0.HCE = 105; state.h_timing_0.HCE = 105;
m_HTiming0.HCS = 71; state.h_timing_0.HCS = 71;
m_HTiming1.HSY = 64; state.h_timing_1.HSY = 64;
m_HTiming1.HBE640 = 162; state.h_timing_1.HBE640 = 162;
m_HTiming1.HBS640 = 373; state.h_timing_1.HBS640 = 373;
m_VBlankTimingOdd.PRB = 502; state.vblank_timing_odd.PRB = 502;
m_VBlankTimingOdd.PSB = 5; state.vblank_timing_odd.PSB = 5;
m_VBlankTimingEven.PRB = 503; state.vblank_timing_even.PRB = 503;
m_VBlankTimingEven.PSB = 4; state.vblank_timing_even.PSB = 4;
m_BurstBlankingOdd.BS0 = 12; state.burst_blanking_odd.BS0 = 12;
m_BurstBlankingOdd.BE0 = 520; state.burst_blanking_odd.BE0 = 520;
m_BurstBlankingOdd.BS2 = 12; state.burst_blanking_odd.BS2 = 12;
m_BurstBlankingOdd.BE2 = 520; state.burst_blanking_odd.BE2 = 520;
m_BurstBlankingEven.BS0 = 13; state.burst_blanking_even.BS0 = 13;
m_BurstBlankingEven.BE0 = 519; state.burst_blanking_even.BE0 = 519;
m_BurstBlankingEven.BS2 = 13; state.burst_blanking_even.BS2 = 13;
m_BurstBlankingEven.BE2 = 519; state.burst_blanking_even.BE2 = 519;
m_XFBInfoTop.Hex = 0; state.xfb_info_top.Hex = 0;
m_XFBInfoBottom.Hex = 0; state.xfb_info_bottom.Hex = 0;
m_3DFBInfoTop.Hex = 0; state.xfb_3d_info_top.Hex = 0;
m_3DFBInfoBottom.Hex = 0; state.xfb_3d_info_bottom.Hex = 0;
m_InterruptRegister[0].HCT = 430; state.interrupt_register[0].HCT = 430;
m_InterruptRegister[0].VCT = 263; state.interrupt_register[0].VCT = 263;
m_InterruptRegister[0].IR_MASK = 1; state.interrupt_register[0].IR_MASK = 1;
m_InterruptRegister[0].IR_INT = 0; state.interrupt_register[0].IR_INT = 0;
m_InterruptRegister[1].HCT = 1; state.interrupt_register[1].HCT = 1;
m_InterruptRegister[1].VCT = 1; state.interrupt_register[1].VCT = 1;
m_InterruptRegister[1].IR_MASK = 1; state.interrupt_register[1].IR_MASK = 1;
m_InterruptRegister[1].IR_INT = 0; state.interrupt_register[1].IR_INT = 0;
m_InterruptRegister[2].Hex = 0; state.interrupt_register[2].Hex = 0;
m_InterruptRegister[3].Hex = 0; state.interrupt_register[3].Hex = 0;
m_LatchRegister = {}; state.latch_register = {};
m_PictureConfiguration.STD = 40; state.picture_configuration.STD = 40;
m_PictureConfiguration.WPL = 40; state.picture_configuration.WPL = 40;
m_HorizontalScaling.Hex = 0; state.horizontal_scaling.Hex = 0;
m_FilterCoefTables = {}; state.filter_coef_tables = {};
m_UnkAARegister = 0; state.unknown_aa_register = 0;
DiscIO::Region region = SConfig::GetInstance().m_region; DiscIO::Region region = SConfig::GetInstance().m_region;
// 54MHz, capable of progressive scan // 54MHz, capable of progressive scan
m_Clock = DiscIO::IsNTSC(region); state.clock = DiscIO::IsNTSC(region);
// Say component cable is plugged // Say component cable is plugged
m_DTVStatus.component_plugged = Config::Get(Config::SYSCONF_PROGRESSIVE_SCAN); state.dtv_status.component_plugged = Config::Get(Config::SYSCONF_PROGRESSIVE_SCAN);
m_DTVStatus.ntsc_j = region == DiscIO::Region::NTSC_J; state.dtv_status.ntsc_j = region == DiscIO::Region::NTSC_J;
m_FBWidth.Hex = 0; state.fb_width.Hex = 0;
m_BorderHBlank.Hex = 0; state.border_hblank.Hex = 0;
s_ticks_last_line_start = 0; state.ticks_last_line_start = 0;
s_half_line_count = 0; state.half_line_count = 0;
s_half_line_of_next_si_poll = num_half_lines_for_si_poll; // first sampling starts at vsync state.half_line_of_next_si_poll = NUM_HALF_LINES_FOR_SI_POLL; // first sampling starts at vsync
UpdateParameters(); UpdateParameters();
} }
@ -201,6 +214,8 @@ void Init()
void RegisterMMIO(MMIO::Mapping* mmio, u32 base) void RegisterMMIO(MMIO::Mapping* mmio, u32 base)
{ {
auto& state = Core::System::GetInstance().GetVideoInterfaceState().GetData();
struct MappedVar struct MappedVar
{ {
u32 addr; u32 addr;
@ -208,52 +223,52 @@ void RegisterMMIO(MMIO::Mapping* mmio, u32 base)
}; };
std::array<MappedVar, 46> directly_mapped_vars{{ std::array<MappedVar, 46> directly_mapped_vars{{
{VI_VERTICAL_TIMING, &m_VerticalTimingRegister.Hex}, {VI_VERTICAL_TIMING, &state.vertical_timing_register.Hex},
{VI_HORIZONTAL_TIMING_0_HI, &m_HTiming0.Hi}, {VI_HORIZONTAL_TIMING_0_HI, &state.h_timing_0.Hi},
{VI_HORIZONTAL_TIMING_0_LO, &m_HTiming0.Lo}, {VI_HORIZONTAL_TIMING_0_LO, &state.h_timing_0.Lo},
{VI_HORIZONTAL_TIMING_1_HI, &m_HTiming1.Hi}, {VI_HORIZONTAL_TIMING_1_HI, &state.h_timing_1.Hi},
{VI_HORIZONTAL_TIMING_1_LO, &m_HTiming1.Lo}, {VI_HORIZONTAL_TIMING_1_LO, &state.h_timing_1.Lo},
{VI_VBLANK_TIMING_ODD_HI, &m_VBlankTimingOdd.Hi}, {VI_VBLANK_TIMING_ODD_HI, &state.vblank_timing_odd.Hi},
{VI_VBLANK_TIMING_ODD_LO, &m_VBlankTimingOdd.Lo}, {VI_VBLANK_TIMING_ODD_LO, &state.vblank_timing_odd.Lo},
{VI_VBLANK_TIMING_EVEN_HI, &m_VBlankTimingEven.Hi}, {VI_VBLANK_TIMING_EVEN_HI, &state.vblank_timing_even.Hi},
{VI_VBLANK_TIMING_EVEN_LO, &m_VBlankTimingEven.Lo}, {VI_VBLANK_TIMING_EVEN_LO, &state.vblank_timing_even.Lo},
{VI_BURST_BLANKING_ODD_HI, &m_BurstBlankingOdd.Hi}, {VI_BURST_BLANKING_ODD_HI, &state.burst_blanking_odd.Hi},
{VI_BURST_BLANKING_ODD_LO, &m_BurstBlankingOdd.Lo}, {VI_BURST_BLANKING_ODD_LO, &state.burst_blanking_odd.Lo},
{VI_BURST_BLANKING_EVEN_HI, &m_BurstBlankingEven.Hi}, {VI_BURST_BLANKING_EVEN_HI, &state.burst_blanking_even.Hi},
{VI_BURST_BLANKING_EVEN_LO, &m_BurstBlankingEven.Lo}, {VI_BURST_BLANKING_EVEN_LO, &state.burst_blanking_even.Lo},
{VI_FB_LEFT_TOP_LO, &m_XFBInfoTop.Lo}, {VI_FB_LEFT_TOP_LO, &state.xfb_info_top.Lo},
{VI_FB_RIGHT_TOP_LO, &m_3DFBInfoTop.Lo}, {VI_FB_RIGHT_TOP_LO, &state.xfb_3d_info_top.Lo},
{VI_FB_LEFT_BOTTOM_LO, &m_XFBInfoBottom.Lo}, {VI_FB_LEFT_BOTTOM_LO, &state.xfb_info_bottom.Lo},
{VI_FB_RIGHT_BOTTOM_LO, &m_3DFBInfoBottom.Lo}, {VI_FB_RIGHT_BOTTOM_LO, &state.xfb_3d_info_bottom.Lo},
{VI_PRERETRACE_LO, &m_InterruptRegister[0].Lo}, {VI_PRERETRACE_LO, &state.interrupt_register[0].Lo},
{VI_POSTRETRACE_LO, &m_InterruptRegister[1].Lo}, {VI_POSTRETRACE_LO, &state.interrupt_register[1].Lo},
{VI_DISPLAY_INTERRUPT_2_LO, &m_InterruptRegister[2].Lo}, {VI_DISPLAY_INTERRUPT_2_LO, &state.interrupt_register[2].Lo},
{VI_DISPLAY_INTERRUPT_3_LO, &m_InterruptRegister[3].Lo}, {VI_DISPLAY_INTERRUPT_3_LO, &state.interrupt_register[3].Lo},
{VI_DISPLAY_LATCH_0_HI, &m_LatchRegister[0].Hi}, {VI_DISPLAY_LATCH_0_HI, &state.latch_register[0].Hi},
{VI_DISPLAY_LATCH_0_LO, &m_LatchRegister[0].Lo}, {VI_DISPLAY_LATCH_0_LO, &state.latch_register[0].Lo},
{VI_DISPLAY_LATCH_1_HI, &m_LatchRegister[1].Hi}, {VI_DISPLAY_LATCH_1_HI, &state.latch_register[1].Hi},
{VI_DISPLAY_LATCH_1_LO, &m_LatchRegister[1].Lo}, {VI_DISPLAY_LATCH_1_LO, &state.latch_register[1].Lo},
{VI_HSCALEW, &m_PictureConfiguration.Hex}, {VI_HSCALEW, &state.picture_configuration.Hex},
{VI_HSCALER, &m_HorizontalScaling.Hex}, {VI_HSCALER, &state.horizontal_scaling.Hex},
{VI_FILTER_COEF_0_HI, &m_FilterCoefTables.Tables02[0].Hi}, {VI_FILTER_COEF_0_HI, &state.filter_coef_tables.Tables02[0].Hi},
{VI_FILTER_COEF_0_LO, &m_FilterCoefTables.Tables02[0].Lo}, {VI_FILTER_COEF_0_LO, &state.filter_coef_tables.Tables02[0].Lo},
{VI_FILTER_COEF_1_HI, &m_FilterCoefTables.Tables02[1].Hi}, {VI_FILTER_COEF_1_HI, &state.filter_coef_tables.Tables02[1].Hi},
{VI_FILTER_COEF_1_LO, &m_FilterCoefTables.Tables02[1].Lo}, {VI_FILTER_COEF_1_LO, &state.filter_coef_tables.Tables02[1].Lo},
{VI_FILTER_COEF_2_HI, &m_FilterCoefTables.Tables02[2].Hi}, {VI_FILTER_COEF_2_HI, &state.filter_coef_tables.Tables02[2].Hi},
{VI_FILTER_COEF_2_LO, &m_FilterCoefTables.Tables02[2].Lo}, {VI_FILTER_COEF_2_LO, &state.filter_coef_tables.Tables02[2].Lo},
{VI_FILTER_COEF_3_HI, &m_FilterCoefTables.Tables36[0].Hi}, {VI_FILTER_COEF_3_HI, &state.filter_coef_tables.Tables36[0].Hi},
{VI_FILTER_COEF_3_LO, &m_FilterCoefTables.Tables36[0].Lo}, {VI_FILTER_COEF_3_LO, &state.filter_coef_tables.Tables36[0].Lo},
{VI_FILTER_COEF_4_HI, &m_FilterCoefTables.Tables36[1].Hi}, {VI_FILTER_COEF_4_HI, &state.filter_coef_tables.Tables36[1].Hi},
{VI_FILTER_COEF_4_LO, &m_FilterCoefTables.Tables36[1].Lo}, {VI_FILTER_COEF_4_LO, &state.filter_coef_tables.Tables36[1].Lo},
{VI_FILTER_COEF_5_HI, &m_FilterCoefTables.Tables36[2].Hi}, {VI_FILTER_COEF_5_HI, &state.filter_coef_tables.Tables36[2].Hi},
{VI_FILTER_COEF_5_LO, &m_FilterCoefTables.Tables36[2].Lo}, {VI_FILTER_COEF_5_LO, &state.filter_coef_tables.Tables36[2].Lo},
{VI_FILTER_COEF_6_HI, &m_FilterCoefTables.Tables36[3].Hi}, {VI_FILTER_COEF_6_HI, &state.filter_coef_tables.Tables36[3].Hi},
{VI_FILTER_COEF_6_LO, &m_FilterCoefTables.Tables36[3].Lo}, {VI_FILTER_COEF_6_LO, &state.filter_coef_tables.Tables36[3].Lo},
{VI_CLOCK, &m_Clock}, {VI_CLOCK, &state.clock},
{VI_DTV_STATUS, &m_DTVStatus.Hex}, {VI_DTV_STATUS, &state.dtv_status.Hex},
{VI_FBWIDTH, &m_FBWidth.Hex}, {VI_FBWIDTH, &state.fb_width.Hex},
{VI_BORDER_BLANK_END, &m_BorderHBlank.Lo}, {VI_BORDER_BLANK_END, &state.border_hblank.Lo},
{VI_BORDER_BLANK_START, &m_BorderHBlank.Hi}, {VI_BORDER_BLANK_START, &state.border_hblank.Hi},
}}; }};
// Declare all the boilerplate direct MMIOs. // Declare all the boilerplate direct MMIOs.
@ -264,14 +279,14 @@ void RegisterMMIO(MMIO::Mapping* mmio, u32 base)
} }
std::array<MappedVar, 8> update_params_on_read_vars{{ std::array<MappedVar, 8> update_params_on_read_vars{{
{VI_VERTICAL_TIMING, &m_VerticalTimingRegister.Hex}, {VI_VERTICAL_TIMING, &state.vertical_timing_register.Hex},
{VI_HORIZONTAL_TIMING_0_HI, &m_HTiming0.Hi}, {VI_HORIZONTAL_TIMING_0_HI, &state.h_timing_0.Hi},
{VI_HORIZONTAL_TIMING_0_LO, &m_HTiming0.Lo}, {VI_HORIZONTAL_TIMING_0_LO, &state.h_timing_0.Lo},
{VI_VBLANK_TIMING_ODD_HI, &m_VBlankTimingOdd.Hi}, {VI_VBLANK_TIMING_ODD_HI, &state.vblank_timing_odd.Hi},
{VI_VBLANK_TIMING_ODD_LO, &m_VBlankTimingOdd.Lo}, {VI_VBLANK_TIMING_ODD_LO, &state.vblank_timing_odd.Lo},
{VI_VBLANK_TIMING_EVEN_HI, &m_VBlankTimingEven.Hi}, {VI_VBLANK_TIMING_EVEN_HI, &state.vblank_timing_even.Hi},
{VI_VBLANK_TIMING_EVEN_LO, &m_VBlankTimingEven.Lo}, {VI_VBLANK_TIMING_EVEN_LO, &state.vblank_timing_even.Lo},
{VI_CLOCK, &m_Clock}, {VI_CLOCK, &state.clock},
}}; }};
// Declare all the MMIOs that update timing params. // Declare all the MMIOs that update timing params.
@ -285,35 +300,41 @@ void RegisterMMIO(MMIO::Mapping* mmio, u32 base)
} }
// XFB related MMIOs that require special handling on writes. // XFB related MMIOs that require special handling on writes.
mmio->Register(base | VI_FB_LEFT_TOP_HI, MMIO::DirectRead<u16>(&m_XFBInfoTop.Hi), mmio->Register(base | VI_FB_LEFT_TOP_HI, MMIO::DirectRead<u16>(&state.xfb_info_top.Hi),
MMIO::ComplexWrite<u16>([](u32, u16 val) { MMIO::ComplexWrite<u16>([](u32, u16 val) {
m_XFBInfoTop.Hi = val; auto& state = Core::System::GetInstance().GetVideoInterfaceState().GetData();
if (m_XFBInfoTop.CLRPOFF) state.xfb_info_top.Hi = val;
m_XFBInfoTop.POFF = 0; if (state.xfb_info_top.CLRPOFF)
state.xfb_info_top.POFF = 0;
})); }));
mmio->Register(base | VI_FB_LEFT_BOTTOM_HI, MMIO::DirectRead<u16>(&m_XFBInfoBottom.Hi), mmio->Register(base | VI_FB_LEFT_BOTTOM_HI, MMIO::DirectRead<u16>(&state.xfb_info_bottom.Hi),
MMIO::ComplexWrite<u16>([](u32, u16 val) { MMIO::ComplexWrite<u16>([](u32, u16 val) {
m_XFBInfoBottom.Hi = val; auto& state = Core::System::GetInstance().GetVideoInterfaceState().GetData();
if (m_XFBInfoBottom.CLRPOFF) state.xfb_info_bottom.Hi = val;
m_XFBInfoBottom.POFF = 0; if (state.xfb_info_bottom.CLRPOFF)
state.xfb_info_bottom.POFF = 0;
})); }));
mmio->Register(base | VI_FB_RIGHT_TOP_HI, MMIO::DirectRead<u16>(&m_3DFBInfoTop.Hi), mmio->Register(base | VI_FB_RIGHT_TOP_HI, MMIO::DirectRead<u16>(&state.xfb_3d_info_top.Hi),
MMIO::ComplexWrite<u16>([](u32, u16 val) { MMIO::ComplexWrite<u16>([](u32, u16 val) {
m_3DFBInfoTop.Hi = val; auto& state = Core::System::GetInstance().GetVideoInterfaceState().GetData();
if (m_3DFBInfoTop.CLRPOFF) state.xfb_3d_info_top.Hi = val;
m_3DFBInfoTop.POFF = 0; if (state.xfb_3d_info_top.CLRPOFF)
state.xfb_3d_info_top.POFF = 0;
})); }));
mmio->Register(base | VI_FB_RIGHT_BOTTOM_HI, MMIO::DirectRead<u16>(&m_3DFBInfoBottom.Hi), mmio->Register(base | VI_FB_RIGHT_BOTTOM_HI, MMIO::DirectRead<u16>(&state.xfb_3d_info_bottom.Hi),
MMIO::ComplexWrite<u16>([](u32, u16 val) { MMIO::ComplexWrite<u16>([](u32, u16 val) {
m_3DFBInfoBottom.Hi = val; auto& state = Core::System::GetInstance().GetVideoInterfaceState().GetData();
if (m_3DFBInfoBottom.CLRPOFF) state.xfb_3d_info_bottom.Hi = val;
m_3DFBInfoBottom.POFF = 0; if (state.xfb_3d_info_bottom.CLRPOFF)
state.xfb_3d_info_bottom.POFF = 0;
})); }));
// MMIOs with unimplemented writes that trigger warnings. // MMIOs with unimplemented writes that trigger warnings.
mmio->Register( mmio->Register(
base | VI_VERTICAL_BEAM_POSITION, base | VI_VERTICAL_BEAM_POSITION, MMIO::ComplexRead<u16>([](u32) {
MMIO::ComplexRead<u16>([](u32) { return 1 + (s_half_line_count) / 2; }), auto& state = Core::System::GetInstance().GetVideoInterfaceState().GetData();
return 1 + (state.half_line_count) / 2;
}),
MMIO::ComplexWrite<u16>([](u32, u16 val) { MMIO::ComplexWrite<u16>([](u32, u16 val) {
WARN_LOG_FMT( WARN_LOG_FMT(
VIDEOINTERFACE, VIDEOINTERFACE,
@ -321,10 +342,11 @@ void RegisterMMIO(MMIO::Mapping* mmio, u32 base)
})); }));
mmio->Register( mmio->Register(
base | VI_HORIZONTAL_BEAM_POSITION, MMIO::ComplexRead<u16>([](u32) { base | VI_HORIZONTAL_BEAM_POSITION, MMIO::ComplexRead<u16>([](u32) {
u16 value = static_cast<u16>(1 + m_HTiming0.HLW * auto& state = Core::System::GetInstance().GetVideoInterfaceState().GetData();
(CoreTiming::GetTicks() - s_ticks_last_line_start) / u16 value = static_cast<u16>(
1 + state.h_timing_0.HLW * (CoreTiming::GetTicks() - state.ticks_last_line_start) /
(GetTicksPerHalfLine())); (GetTicksPerHalfLine()));
return std::clamp<u16>(value, 1, m_HTiming0.HLW * 2); return std::clamp<u16>(value, 1, state.h_timing_0.HLW * 2);
}), }),
MMIO::ComplexWrite<u16>([](u32, u16 val) { MMIO::ComplexWrite<u16>([](u32, u16 val) {
WARN_LOG_FMT( WARN_LOG_FMT(
@ -335,61 +357,75 @@ void RegisterMMIO(MMIO::Mapping* mmio, u32 base)
// The following MMIOs are interrupts related and update interrupt status // The following MMIOs are interrupts related and update interrupt status
// on writes. // on writes.
mmio->Register(base | VI_PRERETRACE_HI, MMIO::DirectRead<u16>(&m_InterruptRegister[0].Hi), mmio->Register(base | VI_PRERETRACE_HI, MMIO::DirectRead<u16>(&state.interrupt_register[0].Hi),
MMIO::ComplexWrite<u16>([](u32, u16 val) { MMIO::ComplexWrite<u16>([](u32, u16 val) {
m_InterruptRegister[0].Hi = val; auto& state = Core::System::GetInstance().GetVideoInterfaceState().GetData();
state.interrupt_register[0].Hi = val;
UpdateInterrupts(); UpdateInterrupts();
})); }));
mmio->Register(base | VI_POSTRETRACE_HI, MMIO::DirectRead<u16>(&m_InterruptRegister[1].Hi), mmio->Register(base | VI_POSTRETRACE_HI, MMIO::DirectRead<u16>(&state.interrupt_register[1].Hi),
MMIO::ComplexWrite<u16>([](u32, u16 val) { MMIO::ComplexWrite<u16>([](u32, u16 val) {
m_InterruptRegister[1].Hi = val; auto& state = Core::System::GetInstance().GetVideoInterfaceState().GetData();
state.interrupt_register[1].Hi = val;
UpdateInterrupts(); UpdateInterrupts();
})); }));
mmio->Register(base | VI_DISPLAY_INTERRUPT_2_HI, mmio->Register(base | VI_DISPLAY_INTERRUPT_2_HI,
MMIO::DirectRead<u16>(&m_InterruptRegister[2].Hi), MMIO::DirectRead<u16>(&state.interrupt_register[2].Hi),
MMIO::ComplexWrite<u16>([](u32, u16 val) { MMIO::ComplexWrite<u16>([](u32, u16 val) {
m_InterruptRegister[2].Hi = val; auto& state = Core::System::GetInstance().GetVideoInterfaceState().GetData();
state.interrupt_register[2].Hi = val;
UpdateInterrupts(); UpdateInterrupts();
})); }));
mmio->Register(base | VI_DISPLAY_INTERRUPT_3_HI, mmio->Register(base | VI_DISPLAY_INTERRUPT_3_HI,
MMIO::DirectRead<u16>(&m_InterruptRegister[3].Hi), MMIO::DirectRead<u16>(&state.interrupt_register[3].Hi),
MMIO::ComplexWrite<u16>([](u32, u16 val) { MMIO::ComplexWrite<u16>([](u32, u16 val) {
m_InterruptRegister[3].Hi = val; auto& state = Core::System::GetInstance().GetVideoInterfaceState().GetData();
state.interrupt_register[3].Hi = val;
UpdateInterrupts(); UpdateInterrupts();
})); }));
// Unknown anti-aliasing related MMIO register: puts a warning on log and // Unknown anti-aliasing related MMIO register: puts a warning on log and
// needs to shift/mask when reading/writing. // needs to shift/mask when reading/writing.
mmio->Register(base | VI_UNK_AA_REG_HI, mmio->Register(base | VI_UNK_AA_REG_HI, MMIO::ComplexRead<u16>([](u32) {
MMIO::ComplexRead<u16>([](u32) { return m_UnkAARegister >> 16; }), auto& state = Core::System::GetInstance().GetVideoInterfaceState().GetData();
return state.unknown_aa_register >> 16;
}),
MMIO::ComplexWrite<u16>([](u32, u16 val) { MMIO::ComplexWrite<u16>([](u32, u16 val) {
m_UnkAARegister = (m_UnkAARegister & 0x0000FFFF) | ((u32)val << 16); auto& state = Core::System::GetInstance().GetVideoInterfaceState().GetData();
state.unknown_aa_register =
(state.unknown_aa_register & 0x0000FFFF) | ((u32)val << 16);
WARN_LOG_FMT(VIDEOINTERFACE, "Writing to the unknown AA register (hi)"); WARN_LOG_FMT(VIDEOINTERFACE, "Writing to the unknown AA register (hi)");
})); }));
mmio->Register(base | VI_UNK_AA_REG_LO, mmio->Register(base | VI_UNK_AA_REG_LO, MMIO::ComplexRead<u16>([](u32) {
MMIO::ComplexRead<u16>([](u32) { return m_UnkAARegister & 0xFFFF; }), auto& state = Core::System::GetInstance().GetVideoInterfaceState().GetData();
return state.unknown_aa_register & 0xFFFF;
}),
MMIO::ComplexWrite<u16>([](u32, u16 val) { MMIO::ComplexWrite<u16>([](u32, u16 val) {
m_UnkAARegister = (m_UnkAARegister & 0xFFFF0000) | val; auto& state = Core::System::GetInstance().GetVideoInterfaceState().GetData();
state.unknown_aa_register = (state.unknown_aa_register & 0xFFFF0000) | val;
WARN_LOG_FMT(VIDEOINTERFACE, "Writing to the unknown AA register (lo)"); WARN_LOG_FMT(VIDEOINTERFACE, "Writing to the unknown AA register (lo)");
})); }));
// Control register writes only updates some select bits, and additional // Control register writes only updates some select bits, and additional
// processing needs to be done if a reset is requested. // processing needs to be done if a reset is requested.
mmio->Register(base | VI_CONTROL_REGISTER, MMIO::DirectRead<u16>(&m_DisplayControlRegister.Hex), mmio->Register(base | VI_CONTROL_REGISTER,
MMIO::DirectRead<u16>(&state.display_control_register.Hex),
MMIO::ComplexWrite<u16>([](u32, u16 val) { MMIO::ComplexWrite<u16>([](u32, u16 val) {
auto& state = Core::System::GetInstance().GetVideoInterfaceState().GetData();
UVIDisplayControlRegister tmpConfig(val); UVIDisplayControlRegister tmpConfig(val);
m_DisplayControlRegister.ENB = tmpConfig.ENB; state.display_control_register.ENB = tmpConfig.ENB;
m_DisplayControlRegister.NIN = tmpConfig.NIN; state.display_control_register.NIN = tmpConfig.NIN;
m_DisplayControlRegister.DLR = tmpConfig.DLR; state.display_control_register.DLR = tmpConfig.DLR;
m_DisplayControlRegister.LE0 = tmpConfig.LE0; state.display_control_register.LE0 = tmpConfig.LE0;
m_DisplayControlRegister.LE1 = tmpConfig.LE1; state.display_control_register.LE1 = tmpConfig.LE1;
m_DisplayControlRegister.FMT = tmpConfig.FMT; state.display_control_register.FMT = tmpConfig.FMT;
if (tmpConfig.RST) if (tmpConfig.RST)
{ {
// shuffle2 clear all data, reset to default vals, and enter idle mode // shuffle2 clear all data, reset to default vals, and enter idle mode
m_DisplayControlRegister.RST = 0; state.display_control_register.RST = 0;
m_InterruptRegister = {}; state.interrupt_register = {};
UpdateInterrupts(); UpdateInterrupts();
} }
@ -414,10 +450,11 @@ void RegisterMMIO(MMIO::Mapping* mmio, u32 base)
void UpdateInterrupts() void UpdateInterrupts()
{ {
if ((m_InterruptRegister[0].IR_INT && m_InterruptRegister[0].IR_MASK) || auto& state = Core::System::GetInstance().GetVideoInterfaceState().GetData();
(m_InterruptRegister[1].IR_INT && m_InterruptRegister[1].IR_MASK) || if ((state.interrupt_register[0].IR_INT && state.interrupt_register[0].IR_MASK) ||
(m_InterruptRegister[2].IR_INT && m_InterruptRegister[2].IR_MASK) || (state.interrupt_register[1].IR_INT && state.interrupt_register[1].IR_MASK) ||
(m_InterruptRegister[3].IR_INT && m_InterruptRegister[3].IR_MASK)) (state.interrupt_register[2].IR_INT && state.interrupt_register[2].IR_MASK) ||
(state.interrupt_register[3].IR_INT && state.interrupt_register[3].IR_MASK))
{ {
ProcessorInterface::SetInterrupt(ProcessorInterface::INT_CAUSE_VI, true); ProcessorInterface::SetInterrupt(ProcessorInterface::INT_CAUSE_VI, true);
} }
@ -429,31 +466,36 @@ void UpdateInterrupts()
u32 GetXFBAddressTop() u32 GetXFBAddressTop()
{ {
if (m_XFBInfoTop.POFF) auto& state = Core::System::GetInstance().GetVideoInterfaceState().GetData();
return m_XFBInfoTop.FBB << 5; if (state.xfb_info_top.POFF)
return state.xfb_info_top.FBB << 5;
else else
return m_XFBInfoTop.FBB; return state.xfb_info_top.FBB;
} }
u32 GetXFBAddressBottom() u32 GetXFBAddressBottom()
{ {
auto& state = Core::System::GetInstance().GetVideoInterfaceState().GetData();
// POFF for XFB bottom is connected to POFF for XFB top // POFF for XFB bottom is connected to POFF for XFB top
if (m_XFBInfoTop.POFF) if (state.xfb_info_top.POFF)
return m_XFBInfoBottom.FBB << 5; return state.xfb_info_bottom.FBB << 5;
else else
return m_XFBInfoBottom.FBB; return state.xfb_info_bottom.FBB;
} }
static u32 GetHalfLinesPerEvenField() static u32 GetHalfLinesPerEvenField()
{ {
return (3 * m_VerticalTimingRegister.EQU + m_VBlankTimingEven.PRB + auto& state = Core::System::GetInstance().GetVideoInterfaceState().GetData();
2 * m_VerticalTimingRegister.ACV + m_VBlankTimingEven.PSB); return (3 * state.vertical_timing_register.EQU + state.vblank_timing_even.PRB +
2 * state.vertical_timing_register.ACV + state.vblank_timing_even.PSB);
} }
static u32 GetHalfLinesPerOddField() static u32 GetHalfLinesPerOddField()
{ {
return (3 * m_VerticalTimingRegister.EQU + m_VBlankTimingOdd.PRB + auto& state = Core::System::GetInstance().GetVideoInterfaceState().GetData();
2 * m_VerticalTimingRegister.ACV + m_VBlankTimingOdd.PSB); return (3 * state.vertical_timing_register.EQU + state.vblank_timing_odd.PRB +
2 * state.vertical_timing_register.ACV + state.vblank_timing_odd.PSB);
} }
static u32 GetTicksPerEvenField() static u32 GetTicksPerEvenField()
@ -469,6 +511,8 @@ static u32 GetTicksPerOddField()
// Get the aspect ratio of VI's active area. // Get the aspect ratio of VI's active area.
float GetAspectRatio() float GetAspectRatio()
{ {
auto& state = Core::System::GetInstance().GetVideoInterfaceState().GetData();
// The picture of a PAL/NTSC TV signal is defined to have a 4:3 aspect ratio, // The picture of a PAL/NTSC TV signal is defined to have a 4:3 aspect ratio,
// but it's only 4:3 if the picture fill the entire active area. // but it's only 4:3 if the picture fill the entire active area.
// All games configure VideoInterface to add padding in both the horizontal and vertical // All games configure VideoInterface to add padding in both the horizontal and vertical
@ -483,8 +527,9 @@ float GetAspectRatio()
// multiply the result by 1.33333.. // multiply the result by 1.33333..
// 1. Get our active area in BT.601 samples (more or less pixels) // 1. Get our active area in BT.601 samples (more or less pixels)
int active_lines = m_VerticalTimingRegister.ACV; int active_lines = state.vertical_timing_register.ACV;
int active_width_samples = (m_HTiming0.HLW + m_HTiming1.HBS640 - m_HTiming1.HBE640); int active_width_samples =
(state.h_timing_0.HLW + state.h_timing_1.HBS640 - state.h_timing_1.HBE640);
// 2. TVs are analog and don't have pixels. So we convert to seconds. // 2. TVs are analog and don't have pixels. So we convert to seconds.
float tick_length = (1.0f / SystemTimers::GetTicksPerSecond()); float tick_length = (1.0f / SystemTimers::GetTicksPerSecond());
@ -509,7 +554,7 @@ float GetAspectRatio()
// NOTE: With the exception of selecting between PAL-M and NTSC color encoding on Brazilian // NOTE: With the exception of selecting between PAL-M and NTSC color encoding on Brazilian
// GameCubes, the FMT field doesn't actually do anything on real hardware. But // GameCubes, the FMT field doesn't actually do anything on real hardware. But
// Nintendo's SDK always sets it appropriately to match the number of lines. // Nintendo's SDK always sets it appropriately to match the number of lines.
if (m_DisplayControlRegister.FMT == 1) // 625 line TV (PAL) if (state.display_control_register.FMT == 1) // 625 line TV (PAL)
{ {
// PAL defines the horizontal active area as 52us of the 64us line. // PAL defines the horizontal active area as 52us of the 64us line.
// BT.470-6 defines the blanking period as 12.0us +0.0 -0.3 [table on page 5] // BT.470-6 defines the blanking period as 12.0us +0.0 -0.3 [table on page 5]
@ -594,13 +639,13 @@ float GetAspectRatio()
// //
// Uppercase is field 1, lowercase is field 2 // Uppercase is field 1, lowercase is field 2
// E,e = pre-equ/vert-sync/post-equ // E,e = pre-equ/vert-sync/post-equ
// = (m_VerticalTimingRegister.EQU*3) half-scanlines // = (vertical_timing_register.EQU*3) half-scanlines
// R,r = preblanking // R,r = preblanking
// = (m_VBlankTimingX.PRB) half-scanlines // = (vblank_timing_x.PRB) half-scanlines
// A,a = active lines // A,a = active lines
// = (m_VerticalTimingRegister.ACV*2) half-scanlines // = (vertical_timing_register.ACV*2) half-scanlines
// S,s = postblanking // S,s = postblanking
// = (m_VBlankTimingX.PSB) half-scanlines // = (vblank_timing_x.PSB) half-scanlines
// //
// NB: for double-strike modes, the second field // NB: for double-strike modes, the second field
// does not get offset by half a scanline // does not get offset by half a scanline
@ -690,43 +735,49 @@ float GetAspectRatio()
void UpdateParameters() void UpdateParameters()
{ {
u32 equ_hl = 3 * m_VerticalTimingRegister.EQU; auto& state = Core::System::GetInstance().GetVideoInterfaceState().GetData();
u32 acv_hl = 2 * m_VerticalTimingRegister.ACV; u32 equ_hl = 3 * state.vertical_timing_register.EQU;
s_odd_field_first_hl = equ_hl + m_VBlankTimingOdd.PRB; u32 acv_hl = 2 * state.vertical_timing_register.ACV;
s_odd_field_last_hl = s_odd_field_first_hl + acv_hl - 1; state.odd_field_first_hl = equ_hl + state.vblank_timing_odd.PRB;
state.odd_field_last_hl = state.odd_field_first_hl + acv_hl - 1;
s_even_field_first_hl = equ_hl + m_VBlankTimingEven.PRB + GetHalfLinesPerOddField(); state.even_field_first_hl = equ_hl + state.vblank_timing_even.PRB + GetHalfLinesPerOddField();
s_even_field_last_hl = s_even_field_first_hl + acv_hl - 1; state.even_field_last_hl = state.even_field_first_hl + acv_hl - 1;
s_target_refresh_rate_numerator = SystemTimers::GetTicksPerSecond() * 2; state.target_refresh_rate_numerator = SystemTimers::GetTicksPerSecond() * 2;
s_target_refresh_rate_denominator = GetTicksPerEvenField() + GetTicksPerOddField(); state.target_refresh_rate_denominator = GetTicksPerEvenField() + GetTicksPerOddField();
s_target_refresh_rate = state.target_refresh_rate = static_cast<double>(state.target_refresh_rate_numerator) /
static_cast<double>(s_target_refresh_rate_numerator) / s_target_refresh_rate_denominator; state.target_refresh_rate_denominator;
} }
double GetTargetRefreshRate() double GetTargetRefreshRate()
{ {
return s_target_refresh_rate; auto& state = Core::System::GetInstance().GetVideoInterfaceState().GetData();
return state.target_refresh_rate;
} }
u32 GetTargetRefreshRateNumerator() u32 GetTargetRefreshRateNumerator()
{ {
return s_target_refresh_rate_numerator; auto& state = Core::System::GetInstance().GetVideoInterfaceState().GetData();
return state.target_refresh_rate_numerator;
} }
u32 GetTargetRefreshRateDenominator() u32 GetTargetRefreshRateDenominator()
{ {
return s_target_refresh_rate_denominator; auto& state = Core::System::GetInstance().GetVideoInterfaceState().GetData();
return state.target_refresh_rate_denominator;
} }
u32 GetTicksPerSample() u32 GetTicksPerSample()
{ {
return 2 * SystemTimers::GetTicksPerSecond() / s_clock_freqs[m_Clock]; auto& state = Core::System::GetInstance().GetVideoInterfaceState().GetData();
return 2 * SystemTimers::GetTicksPerSecond() / CLOCK_FREQUENCIES[state.clock];
} }
u32 GetTicksPerHalfLine() u32 GetTicksPerHalfLine()
{ {
return GetTicksPerSample() * m_HTiming0.HLW; auto& state = Core::System::GetInstance().GetVideoInterfaceState().GetData();
return GetTicksPerSample() * state.h_timing_0.HLW;
} }
u32 GetTicksPerField() u32 GetTicksPerField()
@ -736,11 +787,13 @@ u32 GetTicksPerField()
static void LogField(FieldType field, u32 xfb_address) static void LogField(FieldType field, u32 xfb_address)
{ {
auto& state = Core::System::GetInstance().GetVideoInterfaceState().GetData();
static constexpr std::array<const char*, 2> field_type_names{{"Odd", "Even"}}; static constexpr std::array<const char*, 2> field_type_names{{"Odd", "Even"}};
static const std::array<const UVIVBlankTimingRegister*, 2> vert_timing{{ const std::array<const UVIVBlankTimingRegister*, 2> vert_timing{{
&m_VBlankTimingOdd, &state.vblank_timing_odd,
&m_VBlankTimingEven, &state.vblank_timing_even,
}}; }};
const auto field_index = static_cast<size_t>(field); const auto field_index = static_cast<size_t>(field);
@ -748,29 +801,31 @@ static void LogField(FieldType field, u32 xfb_address)
DEBUG_LOG_FMT(VIDEOINTERFACE, DEBUG_LOG_FMT(VIDEOINTERFACE,
"(VI->BeginField): Address: {:08X} | WPL {} | STD {} | EQ {} | PRB {} | " "(VI->BeginField): Address: {:08X} | WPL {} | STD {} | EQ {} | PRB {} | "
"ACV {} | PSB {} | Field {}", "ACV {} | PSB {} | Field {}",
xfb_address, m_PictureConfiguration.WPL, m_PictureConfiguration.STD, xfb_address, state.picture_configuration.WPL, state.picture_configuration.STD,
m_VerticalTimingRegister.EQU, vert_timing[field_index]->PRB, state.vertical_timing_register.EQU, vert_timing[field_index]->PRB,
m_VerticalTimingRegister.ACV, vert_timing[field_index]->PSB, state.vertical_timing_register.ACV, vert_timing[field_index]->PSB,
field_type_names[field_index]); field_type_names[field_index]);
DEBUG_LOG_FMT(VIDEOINTERFACE, "HorizScaling: {:04x} | fbwidth {} | {} | {}", DEBUG_LOG_FMT(VIDEOINTERFACE, "HorizScaling: {:04x} | fbwidth {} | {} | {}",
m_HorizontalScaling.Hex, m_FBWidth.Hex, GetTicksPerEvenField(), state.horizontal_scaling.Hex, state.fb_width.Hex, GetTicksPerEvenField(),
GetTicksPerOddField()); GetTicksPerOddField());
} }
static void OutputField(FieldType field, u64 ticks) static void OutputField(FieldType field, u64 ticks)
{ {
auto& state = Core::System::GetInstance().GetVideoInterfaceState().GetData();
// Could we fit a second line of data in the stride? // Could we fit a second line of data in the stride?
// (Datel's Wii FreeLoaders are the only titles known to set WPL to 0) // (Datel's Wii FreeLoaders are the only titles known to set WPL to 0)
bool potentially_interlaced_xfb = bool potentially_interlaced_xfb =
m_PictureConfiguration.WPL != 0 && state.picture_configuration.WPL != 0 &&
((m_PictureConfiguration.STD / m_PictureConfiguration.WPL) == 2); ((state.picture_configuration.STD / state.picture_configuration.WPL) == 2);
// Are there an odd number of half-lines per field (definition of interlaced video) // Are there an odd number of half-lines per field (definition of interlaced video)
bool interlaced_video_mode = (GetHalfLinesPerEvenField() & 1) == 1; bool interlaced_video_mode = (GetHalfLinesPerEvenField() & 1) == 1;
u32 fbStride = m_PictureConfiguration.STD * 16; u32 fbStride = state.picture_configuration.STD * 16;
u32 fbWidth = m_PictureConfiguration.WPL * 16; u32 fbWidth = state.picture_configuration.WPL * 16;
u32 fbHeight = m_VerticalTimingRegister.ACV; u32 fbHeight = state.vertical_timing_register.ACV;
u32 xfbAddr; u32 xfbAddr;
@ -803,12 +858,18 @@ static void OutputField(FieldType field, u64 ticks)
// has the first line. For the field with the second line, we // has the first line. For the field with the second line, we
// offset the xfb by (-stride_of_one_line) to get the start // offset the xfb by (-stride_of_one_line) to get the start
// address of the full xfb. // address of the full xfb.
if (field == FieldType::Odd && m_VBlankTimingOdd.PRB == m_VBlankTimingEven.PRB + 1 && xfbAddr) if (field == FieldType::Odd &&
state.vblank_timing_odd.PRB == state.vblank_timing_even.PRB + 1 && xfbAddr)
{
xfbAddr -= fbStride; xfbAddr -= fbStride;
}
if (field == FieldType::Even && m_VBlankTimingOdd.PRB == m_VBlankTimingEven.PRB - 1 && xfbAddr) if (field == FieldType::Even &&
state.vblank_timing_odd.PRB == state.vblank_timing_even.PRB - 1 && xfbAddr)
{
xfbAddr -= fbStride; xfbAddr -= fbStride;
} }
}
LogField(field, xfbAddr); LogField(field, xfbAddr);
@ -844,29 +905,31 @@ static void EndField(FieldType field, u64 ticks)
// Run when: When a frame is scanned (progressive/interlace) // Run when: When a frame is scanned (progressive/interlace)
void Update(u64 ticks) void Update(u64 ticks)
{ {
auto& state = Core::System::GetInstance().GetVideoInterfaceState().GetData();
// Movie's frame counter should be updated before actually rendering the frame, // Movie's frame counter should be updated before actually rendering the frame,
// in case frame counter display is enabled // in case frame counter display is enabled
if (s_half_line_count == 0 || s_half_line_count == GetHalfLinesPerEvenField()) if (state.half_line_count == 0 || state.half_line_count == GetHalfLinesPerEvenField())
Movie::FrameUpdate(); Movie::FrameUpdate();
// If this half-line is at some boundary of the "active video lines" in either field, we either // If this half-line is at some boundary of the "active video lines" in either field, we either
// need to (a) send a request to the GPU thread to actually render the XFB, or (b) increment // need to (a) send a request to the GPU thread to actually render the XFB, or (b) increment
// the number of frames we've actually drawn // the number of frames we've actually drawn
if (s_half_line_count == s_even_field_first_hl) if (state.half_line_count == state.even_field_first_hl)
{ {
BeginField(FieldType::Even, ticks); BeginField(FieldType::Even, ticks);
} }
else if (s_half_line_count == s_odd_field_first_hl) else if (state.half_line_count == state.odd_field_first_hl)
{ {
BeginField(FieldType::Odd, ticks); BeginField(FieldType::Odd, ticks);
} }
else if (s_half_line_count == s_even_field_last_hl) else if (state.half_line_count == state.even_field_last_hl)
{ {
EndField(FieldType::Even, ticks); EndField(FieldType::Even, ticks);
} }
else if (s_half_line_count == s_odd_field_last_hl) else if (state.half_line_count == state.odd_field_last_hl)
{ {
EndField(FieldType::Odd, ticks); EndField(FieldType::Odd, ticks);
} }
@ -874,52 +937,53 @@ void Update(u64 ticks)
// If this half-line is at a field boundary, deal with frame stepping before potentially // If this half-line is at a field boundary, deal with frame stepping before potentially
// dealing with SI polls, but after potentially sending a swap request to the GPU thread // dealing with SI polls, but after potentially sending a swap request to the GPU thread
if (s_half_line_count == 0 || s_half_line_count == GetHalfLinesPerEvenField()) if (state.half_line_count == 0 || state.half_line_count == GetHalfLinesPerEvenField())
Core::Callback_NewField(); Core::Callback_NewField();
// If an SI poll is scheduled to happen on this half-line, do it! // If an SI poll is scheduled to happen on this half-line, do it!
if (s_half_line_of_next_si_poll == s_half_line_count) if (state.half_line_of_next_si_poll == state.half_line_count)
{ {
Core::UpdateInputGate(!Config::Get(Config::MAIN_INPUT_BACKGROUND_INPUT), Core::UpdateInputGate(!Config::Get(Config::MAIN_INPUT_BACKGROUND_INPUT),
Config::Get(Config::MAIN_LOCK_CURSOR)); Config::Get(Config::MAIN_LOCK_CURSOR));
SerialInterface::UpdateDevices(); SerialInterface::UpdateDevices();
s_half_line_of_next_si_poll += 2 * SerialInterface::GetPollXLines(); state.half_line_of_next_si_poll += 2 * SerialInterface::GetPollXLines();
} }
// If this half-line is at the actual boundary of either field, schedule an SI poll to happen // If this half-line is at the actual boundary of either field, schedule an SI poll to happen
// some number of half-lines in the future // some number of half-lines in the future
if (s_half_line_count == 0) if (state.half_line_count == 0)
{ {
s_half_line_of_next_si_poll = num_half_lines_for_si_poll; // first results start at vsync state.half_line_of_next_si_poll = NUM_HALF_LINES_FOR_SI_POLL; // first results start at vsync
} }
if (s_half_line_count == GetHalfLinesPerEvenField()) if (state.half_line_count == GetHalfLinesPerEvenField())
{ {
s_half_line_of_next_si_poll = GetHalfLinesPerEvenField() + num_half_lines_for_si_poll; state.half_line_of_next_si_poll = GetHalfLinesPerEvenField() + NUM_HALF_LINES_FOR_SI_POLL;
} }
// Move to the next half-line and potentially roll-over the count to zero. If we've reached // Move to the next half-line and potentially roll-over the count to zero. If we've reached
// the beginning of a new full-line, update the timer // the beginning of a new full-line, update the timer
s_half_line_count++; state.half_line_count++;
if (s_half_line_count == GetHalfLinesPerEvenField() + GetHalfLinesPerOddField()) if (state.half_line_count == GetHalfLinesPerEvenField() + GetHalfLinesPerOddField())
{ {
s_half_line_count = 0; state.half_line_count = 0;
} }
if (!(s_half_line_count & 1)) if (!(state.half_line_count & 1))
{ {
s_ticks_last_line_start = CoreTiming::GetTicks(); state.ticks_last_line_start = CoreTiming::GetTicks();
} }
// Check if we need to assert IR_INT. Note that the granularity of our current horizontal // Check if we need to assert IR_INT. Note that the granularity of our current horizontal
// position is limited to half-lines. // position is limited to half-lines.
for (UVIInterruptRegister& reg : m_InterruptRegister) for (UVIInterruptRegister& reg : state.interrupt_register)
{ {
u32 target_halfline = (reg.HCT > m_HTiming0.HLW) ? 1 : 0; u32 target_halfline = (reg.HCT > state.h_timing_0.HLW) ? 1 : 0;
if ((1 + (s_half_line_count) / 2 == reg.VCT) && ((s_half_line_count & 1) == target_halfline)) if ((1 + (state.half_line_count) / 2 == reg.VCT) &&
((state.half_line_count & 1) == target_halfline))
{ {
reg.IR_INT = 1; reg.IR_INT = 1;
} }
@ -931,6 +995,8 @@ void Update(u64 ticks)
// Create a fake VI mode for a fifolog // Create a fake VI mode for a fifolog
void FakeVIUpdate(u32 xfb_address, u32 fb_width, u32 fb_stride, u32 fb_height) void FakeVIUpdate(u32 xfb_address, u32 fb_width, u32 fb_stride, u32 fb_height)
{ {
auto& state = Core::System::GetInstance().GetVideoInterfaceState().GetData();
bool interlaced = fb_height > 480 / 2; bool interlaced = fb_height > 480 / 2;
if (interlaced) if (interlaced)
{ {
@ -938,31 +1004,31 @@ void FakeVIUpdate(u32 xfb_address, u32 fb_width, u32 fb_stride, u32 fb_height)
fb_stride = fb_stride * 2; fb_stride = fb_stride * 2;
} }
m_XFBInfoTop.POFF = 1; state.xfb_info_top.POFF = 1;
m_XFBInfoBottom.POFF = 1; state.xfb_info_bottom.POFF = 1;
m_VerticalTimingRegister.ACV = fb_height; state.vertical_timing_register.ACV = fb_height;
m_VerticalTimingRegister.EQU = 6; state.vertical_timing_register.EQU = 6;
m_VBlankTimingOdd.PRB = 502 - fb_height * 2; state.vblank_timing_odd.PRB = 502 - fb_height * 2;
m_VBlankTimingOdd.PSB = 5; state.vblank_timing_odd.PSB = 5;
m_VBlankTimingEven.PRB = 503 - fb_height * 2; state.vblank_timing_even.PRB = 503 - fb_height * 2;
m_VBlankTimingEven.PSB = 4; state.vblank_timing_even.PSB = 4;
m_PictureConfiguration.WPL = fb_width / 16; state.picture_configuration.WPL = fb_width / 16;
m_PictureConfiguration.STD = (fb_stride / 2) / 16; state.picture_configuration.STD = (fb_stride / 2) / 16;
UpdateParameters(); UpdateParameters();
u32 total_halflines = GetHalfLinesPerEvenField() + GetHalfLinesPerOddField(); u32 total_halflines = GetHalfLinesPerEvenField() + GetHalfLinesPerOddField();
if ((s_half_line_count - s_even_field_first_hl) % total_halflines < if ((state.half_line_count - state.even_field_first_hl) % total_halflines <
(s_half_line_count - s_odd_field_first_hl) % total_halflines) (state.half_line_count - state.odd_field_first_hl) % total_halflines)
{ {
// Even/Bottom field is next. // Even/Bottom field is next.
m_XFBInfoBottom.FBB = interlaced ? (xfb_address + fb_width * 2) >> 5 : xfb_address >> 5; state.xfb_info_bottom.FBB = interlaced ? (xfb_address + fb_width * 2) >> 5 : xfb_address >> 5;
} }
else else
{ {
// Odd/Top field is next // Odd/Top field is next
m_XFBInfoTop.FBB = (xfb_address >> 5); state.xfb_info_top.FBB = (xfb_address >> 5);
} }
} }

View File

@ -3,6 +3,8 @@
#pragma once #pragma once
#include <memory>
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
class PointerWrap; class PointerWrap;
@ -13,6 +15,23 @@ class Mapping;
namespace VideoInterface namespace VideoInterface
{ {
class VideoInterfaceState
{
public:
VideoInterfaceState();
VideoInterfaceState(const VideoInterfaceState&) = delete;
VideoInterfaceState(VideoInterfaceState&&) = delete;
VideoInterfaceState& operator=(const VideoInterfaceState&) = delete;
VideoInterfaceState& operator=(VideoInterfaceState&&) = delete;
~VideoInterfaceState();
struct Data;
Data& GetData() { return *m_data; }
private:
std::unique_ptr<Data> m_data;
};
// VI Internal Hardware Addresses // VI Internal Hardware Addresses
enum enum
{ {

View File

@ -12,6 +12,7 @@
#include "Core/HW/DVD/DVDInterface.h" #include "Core/HW/DVD/DVDInterface.h"
#include "Core/HW/DVD/DVDThread.h" #include "Core/HW/DVD/DVDThread.h"
#include "Core/HW/Sram.h" #include "Core/HW/Sram.h"
#include "Core/HW/VideoInterface.h"
namespace Core namespace Core
{ {
@ -26,6 +27,7 @@ struct System::Impl
DVDInterface::DVDInterfaceState m_dvd_interface_state; DVDInterface::DVDInterfaceState m_dvd_interface_state;
DVDThread::DVDThreadState m_dvd_thread_state; DVDThread::DVDThreadState m_dvd_thread_state;
Sram m_sram; Sram m_sram;
VideoInterface::VideoInterfaceState m_video_interface_state;
}; };
System::System() : m_impl{std::make_unique<Impl>()} System::System() : m_impl{std::make_unique<Impl>()}
@ -95,4 +97,9 @@ Sram& System::GetSRAM() const
{ {
return m_impl->m_sram; return m_impl->m_sram;
} }
VideoInterface::VideoInterfaceState& System::GetVideoInterfaceState() const
{
return m_impl->m_video_interface_state;
}
} // namespace Core } // namespace Core

View File

@ -24,6 +24,10 @@ namespace DVDThread
{ {
class DVDThreadState; class DVDThreadState;
} }
namespace VideoInterface
{
class VideoInterfaceState;
};
namespace Core namespace Core
{ {
@ -64,6 +68,7 @@ public:
DVDInterface::DVDInterfaceState& GetDVDInterfaceState() const; DVDInterface::DVDInterfaceState& GetDVDInterfaceState() const;
DVDThread::DVDThreadState& GetDVDThreadState() const; DVDThread::DVDThreadState& GetDVDThreadState() const;
Sram& GetSRAM() const; Sram& GetSRAM() const;
VideoInterface::VideoInterfaceState& GetVideoInterfaceState() const;
private: private:
System(); System();