diff --git a/src/pse/cpu_core.cpp b/src/pse/cpu_core.cpp index 371153d4e..e4fddc5ff 100644 --- a/src/pse/cpu_core.cpp +++ b/src/pse/cpu_core.cpp @@ -24,6 +24,8 @@ bool Core::Initialize(Bus* bus) void Core::Reset() { + m_slice_ticks = std::numeric_limits::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() diff --git a/src/pse/cpu_core.h b/src/pse/cpu_core.h index 1e774e605..2b20d4f3e 100644 --- a/src/pse/cpu_core.h +++ b/src/pse/cpu_core.h @@ -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; diff --git a/src/pse/gpu.cpp b/src/pse/gpu.cpp index d54d93bb0..beaa8f627 100644 --- a/src/pse/gpu.cpp +++ b/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 dot_clock_dividers = {{8, 4, 10, 5, 7, 7, 7, 7}}; + static constexpr std::array horizontal_resolutions = {{256, 320, 512, 630, 368, 368, 368, 368}}; + static constexpr std::array 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 horizontal_resolution_1; + BitField vertical_resolution; + BitField pal_mode; + BitField display_area_color_depth; + BitField vertical_interlace; + BitField horizontal_resolution_2; + BitField 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; diff --git a/src/pse/gpu.h b/src/pse/gpu.h index 484faae2a..2930b516c 100644 --- a/src/pse/gpu.h +++ b/src/pse/gpu.h @@ -31,7 +31,7 @@ public: // gpu_hw_opengl.cpp static std::unique_ptr 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 texture_disable; BitField horizontal_resolution_2; BitField horizontal_resolution_1; - BitField vetical_resolution; + BitField vertical_resolution; BitField pal_mode; BitField display_area_color_depth_24; BitField 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 X; + BitField Y; + }; + union + { + u32 horizontal_display_range; + BitField X1; + BitField X2; + }; + + union + { + u32 vertical_display_range; + BitField Y1; + BitField 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 m_GP0_command; std::deque m_GPUREAD_buffer; }; diff --git a/src/pse/system.cpp b/src/pse/system.cpp index 8348eaa7a..1aa9e09a9 100644 --- a/src/pse/system.cpp +++ b/src/pse/system.cpp @@ -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); +} diff --git a/src/pse/system.h b/src/pse/system.h index 83365788b..44f71dd9d 100644 --- a/src/pse/system.h +++ b/src/pse/system.h @@ -36,6 +36,8 @@ public: bool LoadEXE(const char* filename); + void SetSliceTicks(TickCount downcount); + private: bool DoState(StateWrapper& sw); diff --git a/src/pse/types.h b/src/pse/types.h index 933218ecf..e7af0d643 100644 --- a/src/pse/types.h +++ b/src/pse/types.h @@ -15,4 +15,10 @@ enum class MemoryAccessSize : u32 Byte, HalfWord, Word -}; \ No newline at end of file +}; + +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; +