System: Basic timings for GPU scanout
This commit is contained in:
parent
9475c281bd
commit
f47688b61f
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
|
|
159
src/pse/gpu.cpp
159
src/pse/gpu.cpp
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -36,6 +36,8 @@ public:
|
|||
|
||||
bool LoadEXE(const char* filename);
|
||||
|
||||
void SetSliceTicks(TickCount downcount);
|
||||
|
||||
private:
|
||||
bool DoState(StateWrapper& sw);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in New Issue