flycast/core/hw/pvr/spg.cpp

327 lines
7.6 KiB
C++
Executable File

#include <array>
#include "spg.h"
#include "hw/holly/holly_intc.h"
#include "hw/holly/sb.h"
#include "hw/sh4/sh4_sched.h"
#include "oslib/oslib.h"
#include "hw/maple/maple_if.h"
#include "serialize.h"
#include "network/ggpo.h"
#include "hw/pvr/Renderer_if.h"
#ifdef TEST_AUTOMATION
#include "input/gamepad_device.h"
#endif
//SPG emulation; Scanline/Raster beam registers & interrupts
static u32 clc_pvr_scanline;
static u32 pvr_numscanlines = 512;
static u32 prv_cur_scanline = -1;
static u32 vblk_cnt;
#if !defined(NDEBUG) || defined(DEBUGFAST)
static float last_fps;
#endif
// 27 mhz pixel clock
constexpr int PIXEL_CLOCK = 27 * 1000 * 1000;
static u32 Line_Cycles;
static u32 Frame_Cycles;
int render_end_schid;
int vblank_schid;
static std::array<double, 4> real_times;
static std::array<u64, 4> cpu_cycles;
static u32 cpu_time_idx;
bool SH4FastEnough;
u32 fskip;
static u32 lightgun_line = 0xffff;
static u32 lightgun_hpos;
static bool maple_int_pending;
void CalculateSync()
{
u32 pixel_clock = PIXEL_CLOCK / (FB_R_CTRL.vclk_div ? 1 : 2);
// We need to calculate the pixel clock
pvr_numscanlines = SPG_LOAD.vcount + 1;
Line_Cycles = (u32)((u64)SH4_MAIN_CLOCK * (u64)(SPG_LOAD.hcount + 1) / (u64)pixel_clock);
if (SPG_CONTROL.interlace)
Line_Cycles /= 2;
Frame_Cycles = pvr_numscanlines * Line_Cycles;
prv_cur_scanline = 0;
clc_pvr_scanline = 0;
sh4_sched_request(vblank_schid, Line_Cycles);
}
static int getNextSpgInterrupt()
{
if (SPG_HBLANK_INT.hblank_int_mode == 2)
return Line_Cycles;
u32 min_scanline = prv_cur_scanline + 1;
u32 min_active = pvr_numscanlines;
if (min_scanline <= SPG_VBLANK_INT.vblank_in_interrupt_line_number)
min_active = std::min(min_active, SPG_VBLANK_INT.vblank_in_interrupt_line_number);
if (min_scanline <= SPG_VBLANK_INT.vblank_out_interrupt_line_number)
min_active = std::min(min_active, SPG_VBLANK_INT.vblank_out_interrupt_line_number);
if (min_scanline <= SPG_VBLANK.vstart)
min_active = std::min(min_active, SPG_VBLANK.vstart);
if (min_scanline <= SPG_VBLANK.vbend)
min_active = std::min(min_active, SPG_VBLANK.vbend);
if (lightgun_line != 0xffff && min_scanline <= lightgun_line)
min_active = std::min(min_active, lightgun_line);
if (SPG_HBLANK_INT.hblank_int_mode == 0 && min_scanline <= SPG_HBLANK_INT.line_comp_val)
min_active = std::min(min_active, SPG_HBLANK_INT.line_comp_val);
min_active = std::max(min_active, min_scanline);
return (min_active - prv_cur_scanline) * Line_Cycles;
}
void rescheduleSPG()
{
sh4_sched_request(vblank_schid, getNextSpgInterrupt());
}
static int spg_line_sched(int tag, int cycles, int jitter)
{
clc_pvr_scanline += cycles + jitter;
while (clc_pvr_scanline >= Line_Cycles)
{
prv_cur_scanline = (prv_cur_scanline + 1) % pvr_numscanlines;
clc_pvr_scanline -= Line_Cycles;
if (SPG_VBLANK_INT.vblank_in_interrupt_line_number == prv_cur_scanline)
{
if (maple_int_pending)
{
maple_int_pending = false;
SB_MDST = 0;
}
asic_RaiseInterrupt(holly_SCANINT1);
}
if (SPG_VBLANK_INT.vblank_out_interrupt_line_number == prv_cur_scanline)
{
maple_vblank();
asic_RaiseInterrupt(holly_SCANINT2);
}
if (SPG_VBLANK.vstart == prv_cur_scanline)
SPG_STATUS.vsync = 1;
if (SPG_VBLANK.vbend == prv_cur_scanline)
SPG_STATUS.vsync = 0;
SPG_STATUS.scanline = prv_cur_scanline;
switch (SPG_HBLANK_INT.hblank_int_mode)
{
case 0:
if (prv_cur_scanline == SPG_HBLANK_INT.line_comp_val)
asic_RaiseInterrupt(holly_HBLank);
break;
case 2:
asic_RaiseInterrupt(holly_HBLank);
break;
default:
die("Unimplemented HBLANK INT mode");
break;
}
// Vblank
if (prv_cur_scanline == 0)
{
if (SPG_CONTROL.interlace)
SPG_STATUS.fieldnum = ~SPG_STATUS.fieldnum;
else
SPG_STATUS.fieldnum = 0;
rend_vblank();
double now = os_GetSeconds() * 1000000.0;
cpu_time_idx = (cpu_time_idx + 1) % cpu_cycles.size();
if (cpu_cycles[cpu_time_idx] != 0)
{
u32 cycle_span = (u32)(sh4_sched_now64() - cpu_cycles[cpu_time_idx]);
double time_span = now - real_times[cpu_time_idx];
double cpu_speed = ((double)cycle_span / time_span) / (SH4_MAIN_CLOCK / 100000000);
SH4FastEnough = cpu_speed >= 85.0;
}
else
SH4FastEnough = false;
cpu_cycles[cpu_time_idx] = sh4_sched_now64();
real_times[cpu_time_idx] = now;
#ifdef TEST_AUTOMATION
replay_input();
#endif
#if !defined(NDEBUG) || defined(DEBUGFAST)
vblk_cnt++;
if ((os_GetSeconds()-last_fps)>2)
{
static int Last_FC;
double ts=os_GetSeconds()-last_fps;
double spd_fps=(FrameCount-Last_FC)/ts;
double spd_vbs=vblk_cnt/ts;
double spd_cpu=spd_vbs*Frame_Cycles;
spd_cpu/=1000000; //mrhz kthx
double fullvbs=(spd_vbs/spd_cpu)*200;
Last_FC=FrameCount;
vblk_cnt=0;
const char* mode=0;
const char* res=0;
res=SPG_CONTROL.interlace?"480i":"240p";
if (SPG_CONTROL.NTSC==0 && SPG_CONTROL.PAL==1)
mode="PAL";
else if (SPG_CONTROL.NTSC==1 && SPG_CONTROL.PAL==0)
mode="NTSC";
else
{
res=SPG_CONTROL.interlace?"480i":"480p";
mode="VGA";
}
double frames_done=spd_cpu/2;
double mspdf=1/frames_done*1000;
double full_rps = spd_fps + fskip / ts;
INFO_LOG(COMMON, "%s/%c - %4.2f - %4.2f - V: %4.2f (%.2f, %s%s%4.2f) R: %4.2f+%4.2f",
VER_SHORTNAME,'n',mspdf,spd_cpu*100/200,spd_vbs,
spd_vbs/full_rps,mode,res,fullvbs,
spd_fps,fskip/ts);
fskip=0;
last_fps=os_GetSeconds();
}
#endif
}
if (lightgun_line != 0xffff && lightgun_line == prv_cur_scanline)
{
maple_int_pending = false;
SPG_TRIGGER_POS = ((lightgun_line & 0x3FF) << 16) | (lightgun_hpos & 0x3FF);
SB_MDST = 0;
lightgun_line = 0xffff;
}
}
return getNextSpgInterrupt();
}
void read_lightgun_position(int x, int y)
{
static u8 flip;
maple_int_pending = true;
if (y < 0 || y >= 480 || x < 0 || x >= 640)
{
// Off screen
lightgun_line = 0xffff;
}
else
{
lightgun_line = y / (SPG_CONTROL.interlace ? 2 : 1) + SPG_VBLANK_INT.vblank_out_interrupt_line_number;
// For some reason returning the same position twice makes it register off screen
lightgun_hpos = (x + 286) ^ flip;
flip ^= 1;
}
}
bool spg_Init()
{
render_end_schid = sh4_sched_register(0, &rend_end_render);
vblank_schid = sh4_sched_register(0, &spg_line_sched);
return true;
}
void spg_Term()
{
sh4_sched_unregister(render_end_schid);
render_end_schid = -1;
sh4_sched_unregister(vblank_schid);
vblank_schid = -1;
}
void spg_Reset(bool hard)
{
CalculateSync();
SH4FastEnough = false;
cpu_time_idx = 0;
cpu_cycles.fill(0);
real_times.fill(0.0);
}
void scheduleRenderDone(TA_context *cntx)
{
int cycles = 4096;
if (cntx != nullptr)
{
if (settings.platform.isNaomi2()) {
cycles = 1500000;
}
else
{
int size = 0;
for (TA_context *c = cntx; c != nullptr; c = c->nextContext)
size += c->tad.thd_data - c->tad.thd_root;
cycles = std::min(550000 + size * 100, 1500000);
}
}
sh4_sched_request(render_end_schid, cycles);
}
void spg_Serialize(Serializer& ser)
{
ser << clc_pvr_scanline;
ser << maple_int_pending;
ser << pvr_numscanlines;
ser << prv_cur_scanline;
ser << Line_Cycles;
ser << Frame_Cycles;
ser << lightgun_line;
ser << lightgun_hpos;
}
void spg_Deserialize(Deserializer& deser)
{
if (deser.version() < Deserializer::V30)
deser.skip<u32>(); // in_vblank
deser >> clc_pvr_scanline;
if (deser.version() >= Deserializer::V12)
{
deser >> maple_int_pending;
if (deser.version() >= Deserializer::V14)
{
deser >> pvr_numscanlines;
deser >> prv_cur_scanline;
deser >> Line_Cycles;
deser >> Frame_Cycles;
deser >> lightgun_line;
deser >> lightgun_hpos;
}
}
if (deser.version() < Deserializer::V14)
CalculateSync();
}