System: Basic timings for GPU scanout

This commit is contained in:
Connor McLaughlin 2019-09-17 14:25:25 +10:00
parent 9475c281bd
commit f47688b61f
7 changed files with 268 additions and 34 deletions

View file

@ -24,6 +24,8 @@ bool Core::Initialize(Bus* bus)
void Core::Reset()
{
m_slice_ticks = std::numeric_limits<decltype(m_slice_ticks)>::max();
m_regs = {};
m_cop0_regs.BPC = 0;
@ -40,6 +42,7 @@ void Core::Reset()
bool Core::DoState(StateWrapper& sw)
{
sw.Do(&m_slice_ticks);
sw.DoArray(m_regs.r, countof(m_regs.r));
sw.Do(&m_regs.pc);
sw.Do(&m_regs.hi);
@ -269,28 +272,38 @@ void Core::DisassembleAndPrint(u32 addr)
PrintInstruction(bits, addr);
}
void Core::Execute()
TickCount Core::Execute()
{
// now executing the instruction we previously fetched
const Instruction inst = m_next_instruction;
m_current_instruction_pc = m_regs.pc;
TickCount executed_ticks = 0;
while (executed_ticks < m_slice_ticks)
{
executed_ticks++;
// fetch the next instruction
if (!FetchInstruction())
return;
// now executing the instruction we previously fetched
const Instruction inst = m_next_instruction;
m_current_instruction_pc = m_regs.pc;
// handle branch delays - we are now in a delay slot if we just branched
m_in_branch_delay_slot = m_branched;
m_branched = false;
// fetch the next instruction
if (!FetchInstruction())
continue;
// execute the instruction we previously fetched
ExecuteInstruction(inst);
// handle branch delays - we are now in a delay slot if we just branched
m_in_branch_delay_slot = m_branched;
m_branched = false;
// next load delay
m_load_delay_reg = m_next_load_delay_reg;
m_next_load_delay_reg = Reg::count;
m_load_delay_old_value = m_next_load_delay_old_value;
m_next_load_delay_old_value = 0;
// execute the instruction we previously fetched
ExecuteInstruction(inst);
// next load delay
m_load_delay_reg = m_next_load_delay_reg;
m_next_load_delay_reg = Reg::count;
m_load_delay_old_value = m_next_load_delay_old_value;
m_next_load_delay_old_value = 0;
}
// reset slice ticks, it'll be updated when the components execute
m_slice_ticks = MAX_CPU_SLICE_SIZE;
return executed_ticks;
}
bool Core::FetchInstruction()

View file

@ -26,7 +26,12 @@ public:
void Reset();
bool DoState(StateWrapper& sw);
void Execute();
TickCount Execute();
void SetSliceTicks(TickCount downcount)
{
m_slice_ticks = (downcount < m_slice_ticks ? downcount : m_slice_ticks);
}
const Registers& GetRegs() const { return m_regs; }
Registers& GetRegs() { return m_regs; }
@ -91,6 +96,10 @@ private:
void WriteCacheControl(u32 value);
Bus* m_bus = nullptr;
// ticks of master/CPU clock until the next event
TickCount m_slice_ticks = 0;
Registers m_regs = {};
Instruction m_next_instruction = {};
bool m_in_branch_delay_slot = false;

View file

@ -26,7 +26,12 @@ void GPU::Reset()
void GPU::SoftReset()
{
m_GPUSTAT.bits = 0x14802000;
m_crtc_state = {};
m_crtc_state.regs.display_address_start = 0;
m_crtc_state.regs.horizontal_display_range = 0xC60260;
m_crtc_state.regs.vertical_display_range = 0x3FC10;
UpdateGPUSTAT();
UpdateCRTCConfig();
}
bool GPU::DoState(StateWrapper& sw)
@ -126,7 +131,12 @@ u32 GPU::ReadRegister(u32 offset)
return ReadGPUREAD();
case 0x04:
return m_GPUSTAT.bits;
{
// Bit 31 of GPUSTAT is always clear during vblank.
u32 bits = m_GPUSTAT.bits;
// bits &= (BoolToUInt32(!m_crtc_state.in_vblank) << 31);
return bits;
}
default:
Log_ErrorPrintf("Unhandled register read: %02X", offset);
@ -178,10 +188,101 @@ void GPU::DMAWrite(u32 value)
}
}
void GPU::Flush()
void GPU::UpdateCRTCConfig()
{
FlushRender();
UpdateDisplay();
static constexpr std::array<TickCount, 8> dot_clock_dividers = {{8, 4, 10, 5, 7, 7, 7, 7}};
static constexpr std::array<u32, 8> horizontal_resolutions = {{256, 320, 512, 630, 368, 368, 368, 368}};
static constexpr std::array<u32, 2> vertical_resolutions = {{240, 480}};
CRTCState& cs = m_crtc_state;
const u8 horizontal_resolution_index = m_GPUSTAT.horizontal_resolution_1 | (m_GPUSTAT.horizontal_resolution_2 << 2);
cs.dot_clock_divider = dot_clock_dividers[horizontal_resolution_index];
cs.horizontal_resolution = horizontal_resolutions[horizontal_resolution_index];
cs.vertical_resolution = vertical_resolutions[m_GPUSTAT.vertical_resolution];
// check for a change in resolution
const u32 old_horizontal_resolution = cs.visible_horizontal_resolution;
const u32 old_vertical_resolution = cs.visible_vertical_resolution;
cs.visible_horizontal_resolution = std::max((cs.regs.X2 - cs.regs.X1) / cs.dot_clock_divider, u32(1));
cs.visible_vertical_resolution = cs.regs.Y2 - cs.regs.Y1 + 1;
if (cs.visible_horizontal_resolution != old_horizontal_resolution ||
cs.visible_vertical_resolution != old_vertical_resolution)
{
Log_InfoPrintf("Visible resolution is now %ux%u", cs.visible_horizontal_resolution, cs.visible_vertical_resolution);
}
if (m_GPUSTAT.pal_mode)
{
cs.total_scanlines_per_frame = 314;
cs.ticks_per_scanline = 3406;
}
else
{
cs.total_scanlines_per_frame = 263;
cs.ticks_per_scanline = 3413;
}
UpdateSliceTicks();
}
void GPU::UpdateSliceTicks()
{
// the next event is at the end of the next scanline
// const TickCount ticks_until_next_event = m_crtc_state.ticks_per_scanline - m_crtc_state.current_tick_in_scanline;
// or at vblank. this will depend on the timer config..
const TickCount ticks_until_next_event =
((m_crtc_state.total_scanlines_per_frame - m_crtc_state.current_scanline) * m_crtc_state.ticks_per_scanline) -
m_crtc_state.current_tick_in_scanline;
// convert to master clock, rounding up as we want to overshoot not undershoot
const TickCount system_ticks = (ticks_until_next_event * 7 + 10) / 11;
m_system->SetSliceTicks(system_ticks);
}
void GPU::Execute(TickCount ticks)
{
// convert cpu/master clock to GPU ticks, accounting for partial cycles because of the non-integer divider
{
const TickCount temp = (ticks * 11) + m_crtc_state.fractional_ticks;
m_crtc_state.current_tick_in_scanline += temp / 7;
m_crtc_state.fractional_ticks = temp % 7;
}
while (m_crtc_state.current_tick_in_scanline >= m_crtc_state.ticks_per_scanline)
{
m_crtc_state.current_tick_in_scanline -= m_crtc_state.ticks_per_scanline;
m_crtc_state.current_scanline++;
const bool old_vblank = m_crtc_state.in_vblank;
m_crtc_state.in_vblank = m_crtc_state.current_scanline >= m_crtc_state.visible_vertical_resolution;
if (m_crtc_state.in_vblank && !old_vblank)
{
// TODO: trigger vblank interrupt
Log_WarningPrint("VBlank interrupt would go here");
}
// past the end of vblank?
if (m_crtc_state.current_scanline >= m_crtc_state.total_scanlines_per_frame)
{
// flush any pending draws and "scan out" the image
FlushRender();
UpdateDisplay();
// start the new frame
m_system->IncrementFrameNumber();
m_crtc_state.current_scanline = 0;
if (m_GPUSTAT.vertical_resolution)
m_GPUSTAT.drawing_even_line ^= true;
}
// alternating even line bit in 240-line mode
if (!m_crtc_state.vertical_resolution)
m_GPUSTAT.drawing_even_line = ConvertToBoolUnchecked(m_crtc_state.current_scanline & u32(1));
}
UpdateSliceTicks();
}
u32 GPU::ReadGPUREAD()
@ -336,9 +437,53 @@ void GPU::WriteGP1(u32 value)
case 0x05: // Set display start address
{
// TODO: Remove this later..
FlushRender();
UpdateDisplay();
m_crtc_state.regs.display_address_start = param & CRTCState::Regs::DISPLAY_ADDRESS_START_MASK;
Log_DebugPrintf("Display address start <- 0x%08X", m_crtc_state.regs.display_address_start);
}
break;
case 0x06: // Set horizontal display range
{
m_crtc_state.regs.horizontal_display_range = param & CRTCState::Regs::HORIZONTAL_DISPLAY_RANGE_MASK;
Log_DebugPrintf("Horizontal display range <- 0x%08X", m_crtc_state.regs.horizontal_display_range);
UpdateCRTCConfig();
}
break;
case 0x07: // Set display start address
{
m_crtc_state.regs.vertical_display_range = param & CRTCState::Regs::VERTICAL_DISPLAY_RANGE_MASK;
Log_DebugPrintf("Vertical display range <- 0x%08X", m_crtc_state.regs.vertical_display_range);
UpdateCRTCConfig();
}
break;
case 0x08: // Set display mode
{
union GP1_08h
{
u32 bits;
BitField<u32, u8, 0, 2> horizontal_resolution_1;
BitField<u32, u8, 2, 1> vertical_resolution;
BitField<u32, bool, 3, 1> pal_mode;
BitField<u32, bool, 4, 1> display_area_color_depth;
BitField<u32, bool, 5, 1> vertical_interlace;
BitField<u32, bool, 6, 1> horizontal_resolution_2;
BitField<u32, bool, 7, 1> reverse_flag;
};
const GP1_08h dm{param};
m_GPUSTAT.horizontal_resolution_1 = dm.horizontal_resolution_1;
m_GPUSTAT.vertical_resolution = dm.vertical_resolution;
m_GPUSTAT.pal_mode = dm.pal_mode;
m_GPUSTAT.display_area_color_depth_24 = dm.display_area_color_depth;
m_GPUSTAT.vertical_interlace = dm.vertical_interlace;
m_GPUSTAT.horizontal_resolution_2 = dm.horizontal_resolution_2;
m_GPUSTAT.reverse_flag = dm.reverse_flag;
Log_DebugPrintf("Set display mode <- 0x%08X", dm.bits);
UpdateCRTCConfig();
}
break;

View file

@ -31,7 +31,7 @@ public:
// gpu_hw_opengl.cpp
static std::unique_ptr<GPU> CreateHardwareOpenGLRenderer();
void Flush();
void Execute(TickCount ticks);
protected:
static constexpr u32 VRAM_WIDTH = 1024;
@ -113,6 +113,12 @@ protected:
void SoftReset();
// Sets dots per scanline
void UpdateCRTCConfig();
// Update ticks for this execution slice
void UpdateSliceTicks();
// Updates dynamic bits in GPUSTAT (ready to send VRAM/ready to receive DMA)
void UpdateGPUSTAT();
@ -154,7 +160,7 @@ protected:
BitField<u32, bool, 15, 1> texture_disable;
BitField<u32, u8, 16, 1> horizontal_resolution_2;
BitField<u32, u8, 17, 2> horizontal_resolution_1;
BitField<u32, u8, 19, 1> vetical_resolution;
BitField<u32, u8, 19, 1> vertical_resolution;
BitField<u32, bool, 20, 1> pal_mode;
BitField<u32, bool, 21, 1> display_area_color_depth_24;
BitField<u32, bool, 22, 1> vertical_interlace;
@ -217,6 +223,54 @@ protected:
s32 y;
} m_drawing_offset = {};
struct CRTCState
{
struct Regs
{
static constexpr u32 DISPLAY_ADDRESS_START_MASK = 0b111'11111111'11111111;
static constexpr u32 HORIZONTAL_DISPLAY_RANGE_MASK = 0b11111111'11111111'11111111;
static constexpr u32 VERTICAL_DISPLAY_RANGE_MASK = 0b1111'11111111'11111111;
union
{
u32 display_address_start;
BitField<u32, u32, 0, 10> X;
BitField<u32, u32, 10, 9> Y;
};
union
{
u32 horizontal_display_range;
BitField<u32, u32, 0, 12> X1;
BitField<u32, u32, 12, 12> X2;
};
union
{
u32 vertical_display_range;
BitField<u32, u32, 0, 10> Y1;
BitField<u32, u32, 10, 10> Y2;
};
} regs;
u32 horizontal_resolution;
u32 vertical_resolution;
TickCount dot_clock_divider;
u32 visible_horizontal_resolution;
u32 visible_vertical_resolution;
TickCount ticks_per_scanline;
TickCount visible_ticks_per_scanline;
u32 total_scanlines_per_frame;
TickCount fractional_ticks;
TickCount current_tick_in_scanline;
u32 current_scanline;
bool in_hblank;
bool in_vblank;
} m_crtc_state = {};
std::vector<u32> m_GP0_command;
std::deque<u32> m_GPUREAD_buffer;
};

View file

@ -53,6 +53,8 @@ bool System::DoState(StateWrapper& sw)
void System::Reset()
{
SetSliceTicks(1);
m_cpu->Reset();
m_bus->Reset();
m_dma->Reset();
@ -75,14 +77,13 @@ bool System::SaveState(ByteStream* state)
void System::RunFrame()
{
u32 current_frame_number = m_frame_number;
u32 ticks = 0;
while (current_frame_number == m_frame_number && ticks < (44100 * 300))
while (current_frame_number == m_frame_number)
{
m_cpu->Execute();
ticks++;
}
const TickCount pending_ticks = m_cpu->Execute();
m_gpu->Flush();
// run pending ticks from CPU for other components
m_gpu->Execute(pending_ticks);
}
}
bool System::LoadEXE(const char* filename)
@ -172,3 +173,7 @@ bool System::LoadEXE(const char* filename)
return true;
}
void System::SetSliceTicks(TickCount downcount)
{
m_cpu->SetSliceTicks(downcount);
}

View file

@ -36,6 +36,8 @@ public:
bool LoadEXE(const char* filename);
void SetSliceTicks(TickCount downcount);
private:
bool DoState(StateWrapper& sw);

View file

@ -15,4 +15,10 @@ enum class MemoryAccessSize : u32
Byte,
HalfWord,
Word
};
};
using TickCount = s32;
static constexpr TickCount MASTER_CLOCK = 44100 * 0x300; // 33868800Hz or 33.8688MHz, also used as CPU clock
static constexpr TickCount MAX_CPU_SLICE_SIZE = MASTER_CLOCK / 10;