www.pudn.com > ncdzsrc.rar > cpuintrf.c
/***************************************************************************
cpuintrf.c
Core multi-CPU execution engine.
***************************************************************************/
#include "neogeocd.h"
/*************************************
*
* Debug logging
*
*************************************/
#define VERBOSE 0
#if VERBOSE
#define LOG(x) logerror x
#else
#define LOG(x)
#endif
/*************************************
*
* Macros to help verify active CPU
*
*************************************/
#define VERIFY_ACTIVECPU(retval, name) \
if (activecpu < 0) \
{ \
logerror(#name "() called with no active cpu!\n"); \
return retval; \
}
#define VERIFY_ACTIVECPU_VOID(name) \
if (activecpu < 0) \
{ \
logerror(#name "() called with no active cpu!\n"); \
return; \
}
/*************************************
*
* Macros to help verify CPU index
*
*************************************/
#define VERIFY_CPUNUM(retval, name) \
if (cpunum < 0 || cpunum >= MAX_CPU) \
{ \
logerror(#name "() called for invalid cpu num!\n"); \
return retval; \
}
#define VERIFY_CPUNUM_VOID(name) \
if (cpunum < 0 || cpunum >= MAX_CPU) \
{ \
logerror(#name "() called for invalid cpu num!\n"); \
return; \
}
/*************************************
*
* Triggers for the timer system
*
*************************************/
enum
{
TRIGGER_TIMESLICE = -1000,
TRIGGER_INT = -2000,
TRIGGER_YIELDTIME = -3000,
TRIGGER_SUSPENDTIME = -4000
};
/*************************************
*
* Internal CPU info structure
*
*************************************/
struct cpuinfo
{
struct cpu_interface *intf; /* pointer to the interface functions */
int iloops; /* number of interrupts remaining this frame */
int totalcycles; /* total CPU cycles executed */
int vblankint_countdown; /* number of vblank callbacks left until we interrupt */
int vblankint_multiplier; /* number of vblank callbacks per interrupt */
void * vblankint_timer; /* reference to elapsed time counter */
double vblankint_period; /* timing period of the VBLANK interrupt */
};
/*************************************
*
* General CPU variables
*
*************************************/
static struct cpuinfo cpu[MAX_CPU];
static int activecpu;
static int old_activecpu;
static int cycles_running;
static INT32 watchdog_counter;
int cpu_reset_flag;
/*************************************
*
* CPU interrupt variables
*
*************************************/
static UINT8 interrupt_enable[MAX_CPU];
static INT32 interrupt_vector[MAX_CPU][MAX_IRQ_LINES];
static UINT8 irq_line_state[MAX_CPU][MAX_IRQ_LINES];
static INT32 irq_line_vector[MAX_CPU][MAX_IRQ_LINES];
/*************************************
*
* IRQ acknowledge callbacks
*
*************************************/
static int cpu_0_irq_callback(int irqline);
static int cpu_1_irq_callback(int irqline);
int (*cpu_irq_callbacks[MAX_CPU])(int) =
{
cpu_0_irq_callback,
cpu_1_irq_callback
};
/*************************************
*
* Timer variables
*
*************************************/
static void *vblank_timer;
static int vblank_countdown;
static int vblank_multiplier;
static double vblank_period;
static void *refresh_timer;
static double refresh_period;
static double refresh_period_inv;
static void *timeslice_timer;
static double timeslice_period;
static double scanline_period;
static double scanline_period_inv;
/*************************************
*
* Static prototypes
*
*************************************/
static void cpu_inittimers(void);
static void cpu_vblankreset(void);
static void cpu_vblankcallback(int param);
static void cpu_updatecallback(int param);
/*************************************
*
* The core list of CPU interfaces
*
*************************************/
struct cpu_interface cpuintf[] =
{
{
CPU_M68000,
12000000, /* clock */
neogeo_interrupt, /* vblank interrupt */
1, /* vblank interrupt per frame */
m68000_init,
m68000_reset,
m68000_exit,
m68000_execute,
NULL,
m68000_set_irq_line,
m68000_set_irq_callback,
8, /* irq nums */
-1, /* default vector */
&m68000_ICount, /* icount */
1.0, /* over clock */
-1 /* IRQ int */
},
{
CPU_Z80 | CPU_AUDIO_CPU,
4000000, /* clock */
NULL, /* vblank interrupt */
0, /* vblank interrupt per frame */
z80_init,
z80_reset,
z80_exit,
z80_execute,
z80_burn,
z80_set_irq_line,
z80_set_irq_callback,
1, /* irq nums */
255, /* default vector */
&z80_ICount, /* icount */
1.0, /* over clock */
-1000 /* IRQ int */
}
};
#if 0
#pragma mark -
#pragma mark INTERFACE ACCESSORS
#endif
#define cpu_gettotalcpu() (MAX_CPU)
#define cpu_getactivecpu() (activecpu)
#define cpu_set_activecpu(cpunum) { old_activecpu = activecpu; activecpu = cpunum; }
#define cpu_restore_activecpu() { activecpu = old_activecpu; }
/*************************************
*
* Interfaces to a specific CPU
*
*************************************/
#define cpunum_init(cpunum) (*cpu[cpunum].intf->init)()
#define cpunum_exit(cpunum) (*cpu[cpunum].intf->exit)()
#define cpunum_reset(cpunum, param) (*cpu[cpunum].intf->reset)(param)
#define cpunum_execute(cpunum, cycles) (*cpu[cpunum].intf->execute)(cycles)
#define cpunum_set_irq_line(cpunum, irqline, state) (*cpu[cpunum].intf->set_irq_line)(irqline, state)
#define cpunum_set_irq_callback(cpunum, irqack) (*cpu[cpunum].intf->set_irq_callback)(irqack)
#define cpunum_vblank_interrupt(cpunum) (*cpu[cpunum].intf->vblank_interrupt)()
#define cpunum_type(cpunum) cpu[cpunum].intf->type
#define cpunum_clock(cpunum) cpu[cpunum].intf->clock
#define cpunum_vblank_interrupts_per_frame(cpunum) cpu[cpunum].intf->vblank_interrupts_per_frame
#define cpunum_irq_nums(cpunum) cpu[cpunum].intf->irq_nums
#define cpunum_default_irq_line(cpunum) cpu[cpunum].intf->irq_int
#define cpunum_default_irq_vector(cpunum) cpu[cpunum].intf->default_vector
/*--------------------------
IRQ line setting
--------------------------*/
void activecpu_set_irq_line(int irqline, int state)
{
if (state != INTERNAL_CLEAR_LINE && state != INTERNAL_ASSERT_LINE)
{
logerror("activecpu_set_irq_line called when cpu_set_irq_line should have been used!\n");
return;
}
cpunum_set_irq_line(activecpu, irqline, state - INTERNAL_CLEAR_LINE);
}
#if 0
#pragma mark CORE CPU
#endif
/*************************************
*
* Initialize all the CPUs
*
*************************************/
void cpu_init(void)
{
int cpunum, irqline;
cpu_reset_flag = 0;
activecpu = -1;
/* zap the CPU data structure */
memset(cpu, 0, sizeof(cpu));
/* initialize the interfaces first */
for (cpunum = 0; cpunum < MAX_CPU; cpunum++)
{
/* set up the interface functions */
cpu[cpunum].intf = &cpuintf[cpunum];
/* initialize this CPU */
cpunum_init(cpunum);
/* reset the IRQ lines */
for (irqline = 0; irqline < MAX_IRQ_LINES; irqline++)
{
irq_line_state[cpunum][irqline] = CLEAR_LINE;
irq_line_vector[cpunum][irqline] = cpunum_default_irq_vector(cpunum);
}
cpu[cpunum].vblankint_timer = NULL;
}
timeslice_timer = NULL;
refresh_timer = NULL;
vblank_timer = NULL;
}
/*************************************
*
* Change driver settings
*
*************************************/
void cpu_change_driver(void)
{
switch (driver_type)
{
case NEOGEO:
case NEOGEO_FAST:
cpuintf[CPU_M68000].vblank_interrupt = neogeo_interrupt;
cpuintf[CPU_M68000].vblank_interrupts_per_frame = 1;
break;
case RASTER:
case RASTER_FAST:
cpuintf[CPU_M68000].vblank_interrupt = neogeo_raster_interrupt;
cpuintf[CPU_M68000].vblank_interrupts_per_frame = RASTER_LINES;
break;
case RASTER_BUSY:
case RBUSY_FAST:
cpuintf[CPU_M68000].vblank_interrupt = neogeo_raster_interrupt_busy;
cpuintf[CPU_M68000].vblank_interrupts_per_frame = RASTER_LINES;
break;
}
cpu_inittimers();
}
/*************************************
*
* Prepare the system for execution
*
*************************************/
static void cpu_pre_run(void)
{
int cpunum, irqline;
logerror("Machine reset.\n");
// ドライバ変更
cpu_change_driver();
/* initialize the various timers (suspends all CPUs at startup) */
watchdog_counter = -1;
/* reset sound chips */
sound_reset();
/* first pass over CPUs */
for (cpunum = 0; cpunum < MAX_CPU; cpunum++)
{
/* enable all CPUs */
if (!(cpu[cpunum].intf->cpu_type & CPU_AUDIO_CPU) || sample_rate != 0)
timer_suspendcpu(cpunum, 0, SUSPEND_ANY_REASON);
else
timer_suspendcpu(cpunum, 1, SUSPEND_REASON_DISABLE);
/* start with interrupts enabled, so the generic routine will work even if */
/* the machine doesn't have an interrupt enable port */
interrupt_enable[cpunum] = 1;
for (irqline = 0; irqline < MAX_IRQ_LINES; irqline++)
interrupt_vector[cpunum][irqline] = cpunum_default_irq_vector(cpunum);
/* reset the total number of cycles */
cpu[cpunum].totalcycles = 0;
}
/* now reset each CPU */
for (cpunum = 0; cpunum < MAX_CPU; cpunum++)
{
cpunum_reset(cpunum, cpu_reset_flag);
cpunum_set_irq_callback(cpunum, cpu_irq_callbacks[cpunum]);
}
neogeo_reset();
/* reset the globals */
cpu_vblankreset();
if (game_index >= 0)
{
// ゲーム起動後は 1 に
cpu_reset_flag = 1;
}
}
/*************************************
*
* Finish up execution
*
*************************************/
static void cpu_post_run(void)
{
}
/*************************************
*
* Execute until done
*
*************************************/
void cpu_run(void)
{
int cpunum;
while (osd_get_app_state() <= OSD_RESET)
{
osd_set_app_state(OSD_EXEC);
/* prepare everything to run */
cpu_pre_run();
/* loop until the user quits or resets */
while (osd_get_app_state() == OSD_EXEC)
{
/* ask the timer system to schedule */
if (timer_schedule_cpu(&cpunum, &cycles_running))
{
int ran;
/* run for the requested number of cycles */
cpu_set_activecpu(cpunum);
ran = cpunum_execute(cpunum, cycles_running);
cpu_restore_activecpu();
/* update based on how many cycles we really ran */
cpu[cpunum].totalcycles += ran;
/* update the timer with how long we actually ran */
timer_update_cpu(cpunum, ran);
}
}
/* finish up this iteration */
cpu_post_run();
}
}
/*************************************
*
* Deinitialize all the CPUs
*
*************************************/
void cpu_exit(void)
{
int cpunum;
/* shut down the CPU cores */
for (cpunum = 0; cpunum < MAX_CPU; cpunum++)
cpunum_exit(cpunum);
// フラグ初期化
cpu_reset_flag = 0;
}
/*************************************
*
* Force a reset at the end of this
* timeslice
*
*************************************/
void machine_reset(void)
{
if (cdrom_ipl_loading)
osd_set_app_state(OSD_REBOOT);
else
osd_set_app_state(OSD_RESET);
}
#if 0
#pragma mark -
#pragma mark WATCHDOG
#endif
/*************************************
*
* Watchdog routines
*
*************************************/
/*--------------------------------------------------------------
Use these functions to initialize, and later maintain, the
watchdog. For convenience, when the machine is reset, the
watchdog is disabled. If you call this function, the
watchdog is initialized, and from that point onwards, if you
don't call it at least once every 3 seconds, the machine
will be reset.
The 3 seconds delay is targeted at qzshowby, which otherwise
would reset at the start of a game.
--------------------------------------------------------------*/
static void watchdog_reset(void)
{
if (watchdog_counter == -1)
logerror("watchdog initalized.\n");
watchdog_counter = 3 * video_fps;
}
WRITE16_HANDLER( watchdog_reset_16_w )
{
watchdog_reset();
}
#if 0
#pragma mark -
#pragma mark HALT/RESET
#endif
/*************************************
*
* Handle halt line changes
*
*************************************/
static void halt_callback(int param)
{
int cpunum = param & 0xff;
int state = param >> 8;
/* if asserting, halt the CPU */
if (state == ASSERT_LINE)
timer_suspendcpu(cpunum, 1, SUSPEND_REASON_HALT);
/* if clearing, unhalt the CPU */
else if (state == CLEAR_LINE)
timer_suspendcpu(cpunum, 0, SUSPEND_REASON_HALT);
}
void cpu_set_halt_line(int cpunum, int state)
{
timer_set(TIME_NOW, (cpunum & 0xff) | (state << 8), halt_callback);
}
/*************************************
*
* Return suspended status of CPU
*
*************************************/
int cpu_getstatus(int cpunum)
{
if (cpunum < MAX_CPU)
return !timer_iscpususpended(cpunum, SUSPEND_REASON_HALT | SUSPEND_REASON_RESET | SUSPEND_REASON_DISABLE);
return 0;
}
#if 0
#pragma mark -
#pragma mark INTERRUPT HANDLING
#endif
/*************************************
*
* Internal IRQ callbacks
*
*************************************/
INLINE int cpu_irq_callback(int cpunum, int irqline)
{
int vector = irq_line_vector[cpunum][irqline];
LOG(("cpu_%d_irq_callback(%d) $%04xn", cpunum, irqline, vector));
/* if the IRQ state is HOLD_LINE, clear it */
if (irq_line_state[cpunum][irqline] == HOLD_LINE)
{
LOG(("->set_irq_line(%d,%d,%d)\n", cpunum, irqline, CLEAR_LINE));
activecpu_set_irq_line(irqline, INTERNAL_CLEAR_LINE);
irq_line_state[cpunum][irqline] = CLEAR_LINE;
}
/* otherwise, just return the current vector */
return vector;
}
static int cpu_0_irq_callback(int irqline) { return cpu_irq_callback(0, irqline); }
static int cpu_1_irq_callback(int irqline) { return cpu_irq_callback(1, irqline); }
/*************************************
*
* Generate a IRQ interrupt
*
*************************************/
static void cpu_manualirqcallback(int param)
{
int cpunum = param & 0x0f;
int state = (param >> 4) & 0x0f;
int irqline = (param >> 8) & 0x7f;
int set_vector = (param >> 15) & 0x01;
int vector = param >> 16;
LOG(("cpu_manualirqcallback %d,%d,%d\n",cpunum,irqline,state));
cpu_set_activecpu(cpunum);
/* set the IRQ line state and vector */
if (irqline >= 0 && irqline < MAX_IRQ_LINES)
{
irq_line_state[cpunum][irqline] = state;
if (set_vector)
irq_line_vector[cpunum][irqline] = vector;
}
/* switch off the requested state */
switch (state)
{
case PULSE_LINE:
activecpu_set_irq_line(irqline, INTERNAL_ASSERT_LINE);
activecpu_set_irq_line(irqline, INTERNAL_CLEAR_LINE);
break;
case HOLD_LINE:
case ASSERT_LINE:
activecpu_set_irq_line(irqline, INTERNAL_ASSERT_LINE);
break;
case CLEAR_LINE:
activecpu_set_irq_line(irqline, INTERNAL_CLEAR_LINE);
break;
default:
logerror("cpu_manualirqcallback cpu #%d, line %d, unknown state %d\n", cpunum, irqline, state);
}
cpu_restore_activecpu();
/* generate a trigger to unsuspend any CPUs waiting on the interrupt */
if (state != CLEAR_LINE)
cpu_triggerint(cpunum);
}
void cpu_set_irq_line(int cpunum, int irqline, int state)
{
int vector = 0xff;
int param;
/* don't trigger interrupts on suspended CPUs */
if (cpu_getstatus(cpunum) == 0)
return;
/* pick the vector */
if (irqline >= 0 && irqline < MAX_IRQ_LINES)
vector = interrupt_vector[cpunum][irqline];
LOG(("cpu_set_irq_line(%d,%d,%d,%02x)\n", cpunum, irqline, state, vector));
/* set a timer to go off */
param = (cpunum & 0x0f) | ((state & 0x0f) << 4) | ((irqline & 0x7f) << 8) | (1 << 15) | (vector << 16);
timer_set(TIME_NOW, param, cpu_manualirqcallback);
}
#if 0
#pragma mark -
#pragma mark VIDEO TIMING
#endif
/*************************************
*
* Creates the refresh timer
*
*************************************/
void cpu_init_refresh_timer(void)
{
/* allocate an infinite timer to track elapsed time since the last refresh */
refresh_period = TIME_IN_HZ(video_fps);
refresh_period_inv = 1.0 / refresh_period;
refresh_timer = timer_alloc(NULL);
/* while we're at it, compute the scanline times */
scanline_period = refresh_period / (double)RASTER_LINES;
scanline_period_inv = 1.0 / scanline_period;
}
#if 0
#pragma mark -
#pragma mark SYNCHRONIZATION
#endif
/*************************************
*
* Generate a specific trigger
*
*************************************/
void cpu_trigger(int trigger)
{
timer_trigger(trigger);
}
/*************************************
*
* Generate a trigger in the future
*
*************************************/
void cpu_triggertime(double duration, int trigger)
{
timer_set(duration, trigger, cpu_trigger);
}
/*************************************
*
* Generate a trigger for an int
*
*************************************/
void cpu_triggerint(int cpunum)
{
timer_trigger(TRIGGER_INT + cpunum);
}
/*************************************
*
* Burn/yield CPU cycles until a trigger
*
*************************************/
void cpu_spinuntil_trigger(int trigger)
{
VERIFY_ACTIVECPU_VOID(cpu_spinuntil_trigger);
timer_suspendcpu_trigger(activecpu, trigger);
}
/*************************************
*
* Burn/yield CPU cycles for a
* specific period of time
*
*************************************/
void cpu_spinuntil_time(double duration)
{
static int timetrig = 0;
cpu_spinuntil_trigger(TRIGGER_SUSPENDTIME + timetrig);
cpu_triggertime(duration, TRIGGER_SUSPENDTIME + timetrig);
timetrig = (timetrig + 1) & 255;
}
#if 0
#pragma mark -
#pragma mark CORE TIMING
#endif
/*************************************
*
* Returns the number of times the
* interrupt handler will be called
* before the end of the current
* video frame.
*
*************************************/
/*--------------------------------------------------------------
This can be useful to interrupt handlers to synchronize
their operation. If you call this from outside an interrupt
handler, add 1 to the result, i.e. if it returns 0, it means
that the interrupt handler will be called once.
--------------------------------------------------------------*/
int cpu_getiloops(void)
{
VERIFY_ACTIVECPU(0, cpu_getiloops);
return cpu[activecpu].iloops;
}
/*************************************
*
* Hook for updating things on the
* real VBLANK (once per frame)
*
*************************************/
static void cpu_vblankreset(void)
{
int cpunum;
/* reset the cycle counters */
for (cpunum = 0; cpunum < MAX_CPU; cpunum++)
{
if (!timer_iscpususpended(cpunum, SUSPEND_REASON_DISABLE))
cpu[cpunum].iloops = cpu[cpunum].intf->vblank_interrupts_per_frame - 1;
else
cpu[cpunum].iloops = -1;
}
}
/*************************************
*
* First-run callback for VBLANKs
*
*************************************/
static void cpu_firstvblankcallback(int param)
{
/* now that we're synced up, pulse from here on out */
timer_adjust(vblank_timer, vblank_period, param, vblank_period);
/* but we need to call the standard routine as well */
cpu_vblankcallback(param);
}
/*************************************
*
* VBLANK core handler
*
*************************************/
static void cpu_vblankcallback(int param)
{
int cpunum;
/* loop over CPUs */
for (cpunum = 0; cpunum < MAX_CPU; cpunum++)
{
/* if the interrupt multiplier is valid */
if (cpu[cpunum].vblankint_multiplier != -1)
{
/* decrement; if we hit zero, generate the interrupt and reset the countdown */
if (!--cpu[cpunum].vblankint_countdown)
{
/* a param of -1 means don't call any callbacks */
if (param != -1)
{
/* if the CPU has a VBLANK handler, call it */
if (cpu[cpunum].intf->vblank_interrupt && cpu_getstatus(cpunum))
{
cpu_set_activecpu(cpunum);
cpunum_vblank_interrupt(cpunum);
cpu_restore_activecpu();
}
/* update the counters */
cpu[cpunum].iloops--;
}
/* reset the countdown and timer */
cpu[cpunum].vblankint_countdown = cpu[cpunum].vblankint_multiplier;
timer_adjust(cpu[cpunum].vblankint_timer, TIME_NEVER, 0, 0);
}
}
/* else reset the VBLANK timer if this is going to be a real VBLANK */
else if (vblank_countdown == 1)
timer_adjust(cpu[cpunum].vblankint_timer, TIME_NEVER, 0, 0);
}
/* is it a real VBLANK? */
if (!--vblank_countdown)
{
/* set the timer to update the screen */
timer_set(TIME_IN_USEC(0), 0, cpu_updatecallback);
/* reset the globals */
cpu_vblankreset();
/* reset the counter */
vblank_countdown = vblank_multiplier;
}
}
/*************************************
*
* End-of-VBLANK callback
*
*************************************/
static void cpu_updatecallback(int param)
{
update_autofire_flag();
/* update input port */
update_input_port();
/* update the screen */
updatescreen();
/* check the watchdog */
if (watchdog_counter > 0)
{
if (--watchdog_counter == 0)
{
logerror("watchdog armed.\n");
machine_reset();
}
}
/* reset the refresh timer */
timer_adjust(refresh_timer, TIME_NEVER, 0, 0);
}
/*************************************
*
* Callback to force a timeslice
*
*************************************/
static void cpu_timeslicecallback(int param)
{
timer_trigger(TRIGGER_TIMESLICE);
}
/*************************************
*
* Setup all the core timers
*
*************************************/
static void cpu_inittimers(void)
{
double first_time;
int cpunum, max, ipf;
/* remove old timers */
if (timeslice_timer) timer_remove(timeslice_timer);
if (refresh_timer) timer_remove(refresh_timer);
if (vblank_timer) timer_remove(vblank_timer);
/* allocate a dummy timer at the minimum frequency to break things up */
timeslice_period = TIME_IN_HZ(video_fps);
timeslice_timer = timer_alloc(cpu_timeslicecallback);
timer_adjust(timeslice_timer, timeslice_period, 0, timeslice_period);
cpu_init_refresh_timer();
/*
* The following code finds all the CPUs that are interrupting in sync with the VBLANK
* and sets up the VBLANK timer to run at the minimum number of cycles per frame in
* order to service all the synced interrupts
*/
/* find the CPU with the maximum interrupts per frame */
max = 1;
for (cpunum = 0; cpunum < MAX_CPU; cpunum++)
{
ipf = cpu[cpunum].intf->vblank_interrupts_per_frame;
if (ipf > max)
max = ipf;
}
/* now find the LCD with the rest of the CPUs (brute force - these numbers aren't huge) */
vblank_multiplier = max;
while (1)
{
for (cpunum = 0; cpunum < MAX_CPU; cpunum++)
{
ipf = cpu[cpunum].intf->vblank_interrupts_per_frame;
if (ipf > 0 && (vblank_multiplier % ipf) != 0)
break;
}
if (cpunum == MAX_CPU)
break;
vblank_multiplier += max;
}
/* initialize the countdown timers and intervals */
for (cpunum = 0; cpunum < MAX_CPU; cpunum++)
{
ipf = cpu[cpunum].intf->vblank_interrupts_per_frame;
if (ipf > 0)
cpu[cpunum].vblankint_countdown = cpu[cpunum].vblankint_multiplier = vblank_multiplier / ipf;
else
cpu[cpunum].vblankint_countdown = cpu[cpunum].vblankint_multiplier = -1;
}
/* allocate a vblank timer at the frame rate * the LCD number of interrupts per frame */
vblank_period = TIME_IN_HZ(video_fps * vblank_multiplier);
vblank_timer = timer_alloc(cpu_vblankcallback);
vblank_countdown = vblank_multiplier;
/*
* The following code creates individual timers for each CPU whose interrupts are not
* synced to the VBLANK, and computes the typical number of cycles per interrupt
*/
/* start the CPU interrupt timers */
for (cpunum = 0; cpunum < MAX_CPU; cpunum++)
{
ipf = cpu[cpunum].intf->vblank_interrupts_per_frame;
/* remove old timers */
if (cpu[cpunum].vblankint_timer)
timer_remove(cpu[cpunum].vblankint_timer);
/* compute the average number of cycles per interrupt */
if (ipf <= 0)
ipf = 1;
cpu[cpunum].vblankint_period = TIME_IN_HZ(video_fps * ipf);
cpu[cpunum].vblankint_timer = timer_alloc(NULL);
}
/* note that since we start the first frame on the refresh, we can't pulse starting
immediately; instead, we back up one VBLANK period, and inch forward until we hit
positive time. That time will be the time of the first VBLANK timer callback */
first_time = -TIME_IN_USEC(0) + vblank_period;
while (first_time < 0)
{
cpu_vblankcallback(-1);
first_time += vblank_period;
}
timer_set(first_time, 0, cpu_firstvblankcallback);
}