/************************************************************************** * SAE - Scripted Amiga Emulator * * https://github.com/naTmeg/ScriptedAmigaEmulator * * ©2012 Rupert Hausberger * Commercial use is prohibited. * **************************************************************************/ function Event() { this.active = false; this.evtime = 0; this.oldcycles = 0; this.handler = function(v) {}; } function Event2() { this.active = false; this.evtime = 0; this.handler = function(v) {}; this.data = null; } function Events() { const SYNCBASE = 1000; this.eventtab = null; this.eventtab2 = null; this.currcycle = 0; var nextevent = 0; var nextevent2 = 0; var dmal = 0 var dmal_hpos = 0; var vsynctimebase = 0; var vsyncmintime = 0; var vsyncmaxtime = 0; var vsyncwaittime = 0; var vsynctimeperline = 0; var is_syncline = 0; var is_syncline_end = 0; //var hsync_counter = 0; //var vsync_counter = 0; const MAVG_VSYNC_SIZE = 128; var ma_frameskipt = new mavg(MAVG_VSYNC_SIZE); const FPSCOUNTER_MAVG_SIZE = 10; var fps_mavg = new mavg(FPSCOUNTER_MAVG_SIZE); var idle_mavg = new mavg(FPSCOUNTER_MAVG_SIZE); var timeframes = 0; var lastframetime = 0; var idletime = 0; var frametime = 0; var frameskiptime = 0; var linecounter = 0; var vsync_rendered = false; var frame_rendered = false; var frame_shown = false; var vsyncresume = false; /*---------------------------------*/ this.setup = function() { if (this.eventtab === null) { this.eventtab = new Array(EV_MAX); for (var i = 0; i < EV_MAX; i++) this.eventtab[i] = new Event(); this.eventtab[EV_CIA].handler = function() { AMIGA.cia.handler(); } this.eventtab[EV_AUDIO].handler = function() { AMIGA.audio.handler(); } this.eventtab[EV_MISC].handler = function() { AMIGA.events.misc_handler(); } this.eventtab[EV_HSYNC].handler = function() { AMIGA.events.hsync_handler(); } } if (this.eventtab2 === null) { this.eventtab2 = new Array(EV2_MAX); for (var i = 0; i < EV2_MAX; i++) this.eventtab2[i] = new Event2(); this.eventtab2[EV2_BLITTER].handler = function(data) { AMIGA.blitter.handler(data); } this.eventtab2[EV2_DISK].handler = function(data) { AMIGA.disk.handler(data); } this.eventtab2[EV2_DMAL].handler = function(data) { AMIGA.events.dmal_handler(data); } } this.calc_vsynctimebase(AMIGA.config.video.ntsc ? 60 : 50); } this.reset = function() { dmal = 0 dmal_hpos = 0; this.currcycle = 0; nextevent = CYCLE_MAX; nextevent2 = EV2_MISC; vsynctimebase = 0; vsyncmintime = 0; vsyncmaxtime = 0; vsyncwaittime = 0; vsynctimeperline = 0; is_syncline = 0; is_syncline_end = 0; this.fpscounter_reset(); for (var i = 0; i < EV_MAX; i++) { this.eventtab[i].active = false; this.eventtab[i].evtime = 0; this.eventtab[i].oldcycles = 0; } for (var i = 0; i < EV2_MAX; i++) { this.eventtab2[i].active = false; this.eventtab2[i].evtime = 0; } this.eventtab[EV_HSYNC].evtime = 227 * CYCLE_UNIT; /* 0xe3 */ this.eventtab[EV_HSYNC].active = true; this.schedule(); } this.calc_vsynctimebase = function(hz) { vsynctimebase = Math.floor(SYNCBASE / hz); //vsynctimebase >>= 1;//(AMIGA.config.video.framerate > 1 ? 1 : 0); } /*---------------------------------*/ this.hpos = function() { return Math.floor((this.currcycle - this.eventtab[EV_HSYNC].oldcycles) * CYCLE_UNIT_INV); } this.cycles_in_range = function(endcycles) { return (endcycles - this.currcycle > 0); } /*---------------------------------*/ this.schedule = function() { var mintime = CYCLE_MAX; for (var i = 0; i < EV_MAX; i++) { if (this.eventtab[i].active) { var evtime = this.eventtab[i].evtime - this.currcycle; if (evtime < mintime) mintime = evtime; } } nextevent = this.currcycle + mintime; } this.cycle = function(cycles) { if (vsyncresume) { vsyncresume = false; this.hsync_handler_post(1); } while ((nextevent - this.currcycle) <= cycles) { if (is_syncline) { var rpt = read_processor_time(); if (is_syncline > 0) { var v = rpt - vsyncmintime; var v2 = rpt - is_syncline_end; if (v > vsynctimebase || v < -vsynctimebase) v = 0; if (v < 0 && v2 < 0) return; } else if (is_syncline < 0) { var v = rpt - is_syncline_end; if (v < 0) return; } is_syncline = 0; } cycles -= nextevent - this.currcycle; this.currcycle = nextevent; for (var i = 0; i < EV_MAX; i++) { if (this.eventtab[i].active && this.eventtab[i].evtime == this.currcycle) this.eventtab[i].handler(this.eventtab[i].data); } this.schedule(); } this.currcycle += cycles; } /*---------------------------------*/ var stack = { recursive:0, dorecheck:false }; this.misc_handler = function() { //if (stack.recursive > 1) BUG.info('misc_handler() recursive %d', stack.recursive); var mintime; var ct = this.currcycle; if (stack.recursive) { stack.dorecheck = true; return; } stack.recursive++; this.eventtab[EV_MISC].active = false; var recheck = true; while (recheck) { recheck = false; mintime = CYCLE_MAX; for (var i = 0; i < EV2_MAX; i++) { if (this.eventtab2[i].active) { if (this.eventtab2[i].evtime == ct) { this.eventtab2[i].active = false; this.eventtab2[i].handler(this.eventtab2[i].data); if (stack.dorecheck || this.eventtab2[i].active) { recheck = true; stack.dorecheck = false; } } else { var eventtime = this.eventtab2[i].evtime - ct; if (eventtime < mintime) mintime = eventtime; } } } } if (mintime != CYCLE_MAX) { this.eventtab[EV_MISC].active = true; this.eventtab[EV_MISC].oldcycles = ct; this.eventtab[EV_MISC].evtime = ct + mintime; this.schedule(); } stack.recursive--; } this.newevent2_x = function(t, data, func) { var et = this.currcycle + t; var no = nextevent2; for (;;) { if (!this.eventtab2[no].active) break; no++; if (no == EV2_MAX) no = EV2_MISC; if (no == nextevent2) { BUG.info('newevent2_x() out of events!'); return; } } nextevent2 = no; this.eventtab2[no].active = true; this.eventtab2[no].evtime = et; this.eventtab2[no].handler = func; this.eventtab2[no].data = data; this.misc_handler(); } this.newevent2 = function(t, data, func) { if (t <= 0) func(data); else this.newevent2_x(t * CYCLE_UNIT, data, func); } this.newevent = function(id, t, data) { this.eventtab2[id].active = true; this.eventtab2[id].evtime = this.currcycle + t * CYCLE_UNIT; this.eventtab2[id].data = data; this.misc_handler(); } this.remevent = function(no) { if (this.eventtab2[no].active) { this.eventtab2[no].active = false; //BUG.info('remevent() %d', no); } } /*---------------------------------*/ this.dmal_emu = function(v) { if (!(AMIGA.dmacon & DMAF_DMAEN)) return; var hpos = this.hpos(); if (v >= 6) { v -= 6; var nr = v >> 1; var pt = AMIGA.audio.getpt(nr, (v & 1) != 0); //var dat = AMIGA.mem.load16_chip(pt); var dat = AMIGA.mem.chip.data[pt >>> 1]; AMIGA.custom.last_value = dat; AMIGA.audio.AUDxDAT(nr, dat); } else { var w = v & 1; var pt = AMIGA.disk.getpt(); if (w) { if (AMIGA.disk.fifostatus() <= 0) { //var dat = AMIGA.mem.load16_chip(pt); var dat = AMIGA.mem.chip.data[pt >>> 1]; AMIGA.custom.last_value = dat; AMIGA.disk.DSKDAT(dat); } } else { if (AMIGA.disk.fifostatus() >= 0) { var dat = AMIGA.disk.DSKDATR(); //AMIGA.mem.store16_chip(pt, dat); AMIGA.mem.chip.data[pt >>> 1] = dat; } } } } this.dmal_handler = function(v) { while (dmal) { if (dmal & 3) this.dmal_emu(dmal_hpos + ((dmal & 2) ? 1 : 0)); dmal_hpos += 2; dmal >>>= 2; } this.remevent(EV2_DMAL); } this.dmal_hsync = function() { if (dmal) BUG.info('dmal_hsync() DMAL error!? %04x', dmal); dmal = AMIGA.audio.dmal(); dmal <<= 6; dmal |= AMIGA.disk.dmal(); if (dmal) { dmal_hpos = 0; this.newevent(EV2_DMAL, 7, 13); } } /*---------------------------------*/ function sleep(ms) { var start = new Date().getTime(); while ((new Date().getTime() - start) < ms); } function read_processor_time() { return (new Date().getTime()); //return window.performance.now(); //return window.performance.webkitNow(); } function rpt_vsync(adjust) { var curr_time = read_processor_time(); var v = curr_time - vsyncwaittime + adjust; if (v > SYNCBASE || v < -SYNCBASE) { vsyncmintime = vsyncmaxtime = vsyncwaittime = curr_time; v = 0; } return v; } this.framewait = function() { var clockadjust = 0; var vstb = vsynctimebase; var frameskipt_avg = ma_frameskipt.set(frameskiptime); frameskiptime = 0; is_syncline = 0; if (AMIGA.config.cpu.speed < 0) { if (!frame_rendered) frame_rendered = AMIGA.playfield.render_screen(false); var curr_time = read_processor_time(); var adjust = 0; if (Math.floor(curr_time - vsyncwaittime) > 0 && Math.floor(curr_time - vsyncwaittime) < (vstb >> 1)) adjust += curr_time - vsyncwaittime; adjust += clockadjust; console.log(adjust); vsyncwaittime = curr_time + vstb - adjust; vsyncmintime = curr_time; var max = Math.floor(vstb - adjust); if (max < 0) { max = 0; vsynctimeperline = 1; } else vsynctimeperline = Math.floor(max / (AMIGA.playfield.maxvpos_nom + 1)); vsyncmaxtime = curr_time + max; } else { var start; var t = 0; if (!frame_rendered) { start = read_processor_time(); frame_rendered = AMIGA.playfield.render_screen(false); t = read_processor_time() - start; } while (rpt_vsync(clockadjust) < -4)// / (SYNCBASE / 1000.0); sleep(2); start = read_processor_time(); while (rpt_vsync(clockadjust) < 0); idletime += read_processor_time() - start; var curr_time = read_processor_time(); vsyncmintime = curr_time; vsyncmaxtime = vsyncwaittime = curr_time + vstb; if (frame_rendered) { frame_shown = AMIGA.playfield.show_screen(); t += read_processor_time() - curr_time; } t += frameskipt_avg; vsynctimeperline = Math.floor((vstb - t) / 3); if (vsynctimeperline < 0) vsynctimeperline = 0; else if (vsynctimeperline > Math.floor(vstb / 3)) vsynctimeperline = Math.floor(vstb / 3); } } this.framewait2 = function() { if (AMIGA.config.cpu.speed < 0) { if (AMIGA.playfield.is_last_line()) { /* really last line, just run the cpu emulation until whole vsync time has been used */ vsyncmintime = vsyncmaxtime; /* emulate if still time left */ is_syncline_end = read_processor_time() + vsynctimebase; /* far enough in future, we never wait that long */ is_syncline = 1; } else { /* end of scanline, run cpu emulation as long as we still have time */ vsyncmintime += vsynctimeperline; linecounter++; is_syncline = 0; if (Math.floor(vsyncmaxtime - vsyncmintime) > 0) { if (Math.floor(vsyncwaittime - vsyncmintime) > 0) { var rpt = read_processor_time(); /* Extra time left? Do some extra CPU emulation */ if (Math.floor(vsyncmintime - rpt) > 0) { is_syncline = 1; /* limit extra time */ is_syncline_end = rpt + vsynctimeperline; linecounter = 0; } } // extra cpu emulation time if previous 10 lines without extra time. if (!is_syncline && linecounter >= 10) { is_syncline = -1; is_syncline_end = read_processor_time() + vsynctimeperline; linecounter = 0; } } } } else { if (AMIGA.playfield.vpos + 1 < AMIGA.playfield.maxvpos + AMIGA.playfield.lof_store && (AMIGA.playfield.vpos == Math.floor(AMIGA.playfield.maxvpos_nom / 3) || AMIGA.playfield.vpos == Math.floor(AMIGA.playfield.maxvpos_nom * 2 / 3))) { vsyncmintime += vsynctimeperline; var rpt = read_processor_time(); // sleep if more than 2ms "free" time while (Math.floor(vsyncmintime) - Math.floor(rpt + vsynctimebase / 10) > 0 && Math.floor(vsyncmintime - rpt) < vsynctimebase) { sleep(1); rpt = read_processor_time(); //console.log('*'); } } } } this.fpscounter_reset = function() { timeframes = 0; fps_mavg.clr(); idle_mavg.clr(); lastframetime = read_processor_time(); idletime = 0; } this.fpscounter = function() { var hz = AMIGA.playfield.vblank_hz; var now = read_processor_time(); var last = now - lastframetime; lastframetime = now; if (AMIGA.config.video.framerate > 1) { last <<= 1; hz /= 2; } fps_mavg.set(last / 10); idle_mavg.set(idletime / 10); idletime = 0; frametime += last; timeframes++; if ((timeframes & 7) == 0) { var idle = 1000 - (idle_mavg.average == 0 ? 0.0 : idle_mavg.average * 1000.0 / vsynctimebase); var fps = fps_mavg.average == 0 ? 0 : SYNCBASE * 10 / fps_mavg.average; if (fps > 9999) fps = 9999; if (idle < 0) idle = 0; if (idle > 100 * 10) idle = 100 * 10; if (hz * 10 > fps) idle *= (hz * 10 / fps); if ((timeframes & 15) == 0) { AMIGA.config.hooks.fps(Math.round(fps * 0.1)); AMIGA.config.hooks.cpu(Math.round(idle * 0.1)); } } } /*---------------------------------*/ this.hsync_handler_pre = function(onvsync) { var hpos = this.hpos(); AMIGA.copper.sync_copper_with_cpu(AMIGA.playfield.maxhpos, 0); AMIGA.playfield.hsync_handler_pre(); AMIGA.disk.hsync(); if (AMIGA.config.audio.enabled) AMIGA.audio.hsync(); //AMIGA.cia.hsync_prehandler(); //empty //hsync_counter++; AMIGA.playfield.hsync_handler_pre_next_vpos(onvsync); this.eventtab[EV_HSYNC].evtime = this.currcycle + AMIGA.playfield.maxhpos * CYCLE_UNIT; this.eventtab[EV_HSYNC].oldcycles = this.currcycle; } this.vsync_handler_pre = function() { //AMIGA.audio.vsync(); //empty AMIGA.cia.vsync_prehandler(); if (!vsync_rendered) { var start = read_processor_time(); AMIGA.playfield.vsync_handle_redraw(); frameskiptime += read_processor_time() - start; vsync_rendered = true; } this.framewait(); if (!frame_rendered) frame_rendered = AMIGA.playfield.render_screen(false); if (frame_rendered && !frame_shown) frame_shown = AMIGA.playfield.show_screen(); this.fpscounter(); vsync_rendered = false; frame_shown = false; frame_rendered = false; AMIGA.playfield.checklacecount(null); } var cia_hsync = 256; this.hsync_handler_post = function(onvsync) { AMIGA.copper.last_copper_hpos = 0; var ciasyncs = !(AMIGA.playfield.bplcon0 & 2) || ((AMIGA.playfield.bplcon0 & 2) && AMIGA.config.chipset.genlock); AMIGA.cia.hsync_posthandler(ciasyncs); if (AMIGA.config.cia.tod > 0) { cia_hsync -= 256; if (cia_hsync <= 0) { AMIGA.cia.vsync_posthandler(1); cia_hsync += Math.floor((MAXVPOS_PAL * MAXHPOS_PAL * 50 * 256) / (AMIGA.playfield.maxhpos * (AMIGA.config.cia.tod == 2 ? 60 : 50))); } } else if (AMIGA.config.cia.tod == 0 && onvsync) AMIGA.cia.vsync_posthandler(ciasyncs); AMIGA.playfield.hsync_handler_post(); AMIGA.custom.last_value = 0xffff; if (!AMIGA.config.blitter.immediate && AMIGA.blitter.getState() != BLT_done && AMIGA.dmaen(DMAF_BPLEN) && AMIGA.playfield.getDiwstate() == DIW_WAITING_STOP) AMIGA.blitter.slowdown(); if (onvsync) { // vpos_count >= MAXVPOS just to not crash if VPOSW writes prevent vsync completely /*if ((AMIGA.playfield.bplcon0 & 8) && !lightpen_triggered) { vpos_lpen = AMIGA.playfield.vpos - 1; hpos_lpen = AMIGA.playfield.maxhpos; lightpen_triggered = 1; }*/ AMIGA.playfield.vpos = 0; this.vsync_handler_post(); AMIGA.playfield.vpos_count = 0; } if (AMIGA.config.chipset.agnus_dip) { if (AMIGA.playfield.vpos == 1) AMIGA.INTREQ_0(INT_VERTB); } else { if (AMIGA.playfield.vpos == 0) AMIGA.INTREQ_0(INT_VERTB); } this.dmal_hsync(); this.framewait2(); AMIGA.playfield.hsync_handler_post_nextline_how(); AMIGA.copper.reset2(); if (CUSTOM_SIMPLE) AMIGA.playfield.do_sprites(0); //AMIGA.copper.check(2); AMIGA.playfield.hsync_handler_post_diw_change(); } this.vsync_handler_post = function() { //if ((AMIGA.intreq & 0x0020) && (AMIGA.intena & 0x0020)) BUG.info('vblank interrupt not cleared'); AMIGA.disk.vsync(); AMIGA.playfield.vsync_handler_post(); } this.hsync_handler = function() { var vs = AMIGA.playfield.is_custom_vsync(); this.hsync_handler_pre(vs); if (vs) { this.vsync_handler_pre(); if (AMIGA.config.video.framerate > 1) { if (AMIGA.playfield.framecnt == AMIGA.config.video.framerate - 1) { //vsyncresume = true; //throw new VSync(0, 'vsync'); AMIGA.state = ST_IDLE; } } else { //vsyncresume = true; //throw new VSync(0, 'vsync'); AMIGA.state = ST_IDLE; } } this.hsync_handler_post(vs); } }