/************************************************************************** * SAE - Scripted Amiga Emulator * * 2012-2015 Rupert Hausberger * * https://github.com/naTmeg/ScriptedAmigaEmulator * **************************************************************************/ function Event1() { 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 Event1(); 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(); var dat, pt; if (v >= 6) { v -= 6; var nr = v >> 1; pt = AMIGA.audio.getpt(nr, (v & 1) != 0); //var dat = AMIGA.mem.load16_chip(pt); dat = AMIGA.mem.chip.data[pt >>> 1]; AMIGA.custom.last_value = dat; AMIGA.audio.AUDxDAT(nr, dat); } else { var w = v & 1; pt = AMIGA.disk.getpt(); if (w) { if (AMIGA.disk.fifostatus() <= 0) { //var dat = AMIGA.mem.load16_chip(pt); dat = AMIGA.mem.chip.data[pt >>> 1]; AMIGA.custom.last_value = dat; AMIGA.disk.DSKDAT(dat); } } else { if (AMIGA.disk.fifostatus() >= 0) { 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 curr_time; 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); curr_time = read_processor_time(); var adjust = 0; if (Math.floor(curr_time - vsyncwaittime) > 0 && Math.floor(curr_time - vsyncwaittime) < (vsynctimebase >> 1)) adjust += curr_time - vsyncwaittime; adjust += clockadjust; //console.log(adjust); vsyncwaittime = curr_time + vsynctimebase - adjust; vsyncmintime = curr_time; var max = Math.floor(vsynctimebase - 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; curr_time = read_processor_time(); vsyncmintime = curr_time; vsyncmaxtime = vsyncwaittime = curr_time + vsynctimebase; if (frame_rendered) { frame_shown = AMIGA.playfield.show_screen(); t += read_processor_time() - curr_time; } t += frameskipt_avg; vsynctimeperline = Math.floor((vsynctimebase - t) / 3); if (vsynctimeperline < 0) vsynctimeperline = 0; else if (vsynctimeperline > Math.floor(vsynctimebase / 3)) vsynctimeperline = Math.floor(vsynctimebase / 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(); 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); } }