/*------------------------------------------------------------------------- | SAE - Scripted Amiga Emulator | https://github.com/naTmeg/ScriptedAmigaEmulator | | Copyright (C) 2012-2016 Rupert Hausberger | | This program is free software; you can redistribute it and/or | modify it under the terms of the GNU General Public License | as published by the Free Software Foundation; either version 2 | of the License, or (at your option) any later version. | | This program is distributed in the hope that it will be useful, | but WITHOUT ANY WARRANTY; without even the implied warranty of | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | GNU General Public License for more details. | | Note: ported from WinUAE 3.2.x -------------------------------------------------------------------------*/ /* global constants */ const SAEC_Events_CYCLE_UNIT = 512; const SAEC_Events_CYCLE_UNIT_INV = 1.0 / SAEC_Events_CYCLE_UNIT; /* mul is always faster than div */ const SAEC_Events_CYCLE_MAX = 0x100000000 * SAEC_Events_CYCLE_UNIT; /*---------------------------------*/ const SAEC_Events_EV_CIA = 0; const SAEC_Events_EV_AUDIO = 1; const SAEC_Events_EV_HSYNC = 2; const SAEC_Events_EV2_BLITTER = 0; const SAEC_Events_EV2_DISK = 1; const SAEC_Events_syncbase = 1000000; /*---------------------------------*/ /*const SAEC_Events_cycle_line_REFRESH = 1; const SAEC_Events_cycle_line_STROBE = 2; const SAEC_Events_cycle_line_MISC = 3; const SAEC_Events_cycle_line_SPRITE = 4; const SAEC_Events_cycle_line_COPPER = 5; const SAEC_Events_cycle_line_BLITTER = 6; const SAEC_Events_cycle_line_CPU = 7; //const SAEC_Events_cycle_line_CPUNASTY = 8; const SAEC_Events_cycle_line_COPPER_SPECIAL = 0x10; const SAEC_Events_cycle_line_MASK = 0x0f;*/ /*---------------------------------*/ /* global references */ var SAER_Events_eventtab = null; //var SAER_Events_cycle_line = null; /*---------------------------------*/ /* global variables */ var SAEV_Events_currcycle = 0; var SAEV_Events_bogusframe = 0; var SAEV_Events_timeframes = 0; var SAEV_Events_hsync_counter = 0; var SAEV_Events_vsync_counter = 0; var SAEV_Events_frameskiptime = 0; var SAEV_Events_reflowtime = 0; //OWN /*---------------------------------*/ function SAEO_Events() { const EV_MISC = 3; const EV_MAX = 4; function Event() { this.active = false; this.evtime = 0; this.oldcycles = 0; this.handler = null; }; var eventtab = new Array(EV_MAX); for (i = 0; i < EV_MAX; i++) eventtab[i] = new Event(); SAER_Events_eventtab = eventtab; const EV2_MISC = 2; const EV2_MAX = 12; function Event2() { this.active = false; this.evtime = 0; this.data = null; this.handler = null; }; var eventtab2 = new Array(EV2_MAX); for (i = 0; i < EV2_MAX; i++) eventtab2[i] = new Event2(); //var currcycle = 0; -> SAEV_Events_currcycle var nextevent = 0; var is_syncline = 0, is_syncline_end = 0; //var vblank_found_chipset = false; //var sleeps_remaining = 0; var linecounter = 0; //var syncbase = 0; -> SAEC_Events_syncbase var vsyncmintime = 0, vsyncmaxtime = 0, vsyncwaittime = 0; var vsynctimebase = 0; //var rpt_did_reset = 0; //var frameskiptime = 0; -> SAEV_Events_frameskiptime var vsynctimeperline = 0; //global var dmal = 0, dmal_hpos = 0; //u16 //in framewait() const MAVG_VSYNC_SIZE = 128; var ma_frameskipt = new SAEO_MAvg(MAVG_VSYNC_SIZE); //var ma_adjust = new mavg_data(); //var ma_legacy = new mavg_data(); //var ma_skip = new mavg_data(); var ma_reflowt = new SAEO_MAvg(10); //OWN //var vsync_time = 0; //in MISC_handler() var dorecheck = false; var recursive = 0; /* Statistics */ const FPSCOUNTER_MAVG_SIZE = 10 var fps_mavg = new SAEO_MAvg(FPSCOUNTER_MAVG_SIZE); var idle_mavg = new SAEO_MAvg(FPSCOUNTER_MAVG_SIZE); //var bogusframe = 0; //-> SAEV_Events_bogusframe //var timeframes = 0; //-> SAEV_Events_timeframes var frametime = 0, lastframetime = 0; //global var idletime = 0; //global //var hsync_counter = 0; -> SAEV_Events_hsync_counter //var vsync_counter = 0; -> SAEV_Events_vsync_counter //event2_newevent_xx() var nextno = EV2_MISC; const PISSOFF_NOJIT_VALUE = 256 * SAEC_Events_CYCLE_UNIT; var pissoff = 0; //#define countdown pissoff //#ifdef CPUEMU_13 //var cycle_line = new Uint8Array(256 + 1); //SAER_Events_cycle_line = cycle_line; //#endif /*-----------------------------------------------------------------------*/ this.reset = function() { //init_eventtab() if (eventtab[SAEC_Events_EV_HSYNC].handler === null) { //OWN eventtab[SAEC_Events_EV_CIA].handler = function() { SAER.cia.handler(); }; eventtab[SAEC_Events_EV_HSYNC].handler = function() { SAER.playfield.hsync_handler(); }; eventtab[SAEC_Events_EV_AUDIO].handler = function() { SAER.audio.handler(); }; eventtab[EV_MISC].handler = MISC_handler; eventtab2[SAEC_Events_EV2_BLITTER].handler = function(data) { SAER.blitter.handler(data); }; eventtab2[SAEC_Events_EV2_DISK].handler = function(data) { SAER.disk.handler(data); }; } nextevent = 0; nextno = EV2_MISC; //OWN for (var i = 0; i < EV_MAX; i++) { eventtab[i].active = false; eventtab[i].oldcycles = SAEV_Events_currcycle; } eventtab[SAEC_Events_EV_HSYNC].evtime = SAEV_Events_currcycle + SAER.playfield.get_maxhpos() * SAEC_Events_CYCLE_UNIT; eventtab[SAEC_Events_EV_HSYNC].active = true; for (i = 0; i < EV2_MAX; i++) eventtab2[i].active = false; this.schedule(); //OWN dorecheck = false; recursive = 0; //nextno = EV2_MISC; fpscounter_reset(); ma_frameskipt.clr(); ma_reflowt.clr(); //reset_frame_rate_hack(); //sleeps_remaining = 0; linecounter = 0; } this.clr_dmal = function() { dmal = 0; } this.pauseResume = function(pause) { if (pause) { SAER.gui.data.fps = 0; SAER.gui.data.idle = 0; SAER.gui.fps(0, 0, 1); } else { dmal = 0; fpscounter_reset(); ma_frameskipt.clr(); } } /*-----------------------------------------------------------------------*/ this.reset_frame_rate_hack = function() { if (SAEV_config.cpu.speed < 0) { //rpt_did_reset = 1; is_syncline = 0; vsyncmintime = SAEF_now() + vsynctimebase; //SAEF_log("events.reset_frame_rate_hack() %d", vsyncmintime); } } this.calc_vsynctimebase = function(hz) { vsynctimebase = SAEC_Events_syncbase / hz >>> 0; SAEF_log("events.calc_vsynctimebase() %d us (%f)", vsynctimebase, hz); this.reset_frame_rate_hack(); return vsynctimebase; } this.schedule = function() { var mintime = SAEC_Events_CYCLE_MAX; for (var i = 0; i < EV_MAX; i++) { if (eventtab[i].active) { var eventtime = eventtab[i].evtime - SAEV_Events_currcycle; if (eventtime < mintime) mintime = eventtime; } } nextevent = SAEV_Events_currcycle + mintime; } /*this.get_cycles = function() { //OPT inline ok return SAEV_Events_currcycle; } this.set_cycles = function(x) { SAEV_Events_currcycle = x; eventtab[SAEC_Events_EV_HSYNC].oldcycles = x; } this.cycles_in_range = function(endcycles) { //OPT inline ok, used in disk.DSKBYTR() return endcycles - SAEV_Events_currcycle > 0; }*/ /*function current_hpos_safe() { //OPT inline ok return ((SAEV_Events_currcycle - eventtab[SAEC_Events_EV_HSYNC].oldcycles) * SAEC_Events_CYCLE_UNIT_INV) >>> 0; }*/ this.current_hpos = function() { //var hp = current_hpos_safe(); var hp = ((SAEV_Events_currcycle - eventtab[SAEC_Events_EV_HSYNC].oldcycles) * SAEC_Events_CYCLE_UNIT_INV) >>> 0; if (hp < 0 || hp > 256) { SAEF_error("events.current_hpos() hpos = %d !?", hp); hp = 0; } return hp; } /*-----------------------------------------------------------------------*/ this.do_cycles = function(cycles_to_add) { //do_cycles_slow() if ((pissoff -= cycles_to_add) >= 0) return; cycles_to_add = -pissoff; pissoff = 0; //if (cycles_to_add == 0) SAEF_warn("event.do_cycles() cycles_to_add == 0"); while ((nextevent - SAEV_Events_currcycle) <= cycles_to_add) { // Keep only CPU emulation running while waiting for sync point. if (is_syncline) { //if (!vblank_found_chipset) { if (is_syncline > 0) { var rpt = SAEF_now(); var v = rpt - vsyncmintime; var v2 = rpt - is_syncline_end; if (v > vsynctimebase || v < -vsynctimebase) v = 0; if (v < 0 && v2 < 0) { pissoff = PISSOFF_NOJIT_VALUE; return; } } else if (is_syncline < 0) { var rpt = SAEF_now(); var v = rpt - is_syncline_end; if (v < 0) { pissoff = PISSOFF_NOJIT_VALUE; return; } } //} is_syncline = 0; } cycles_to_add -= nextevent - SAEV_Events_currcycle; SAEV_Events_currcycle = nextevent; for (var i = 0; i < EV_MAX; i++) { if (eventtab[i].active && eventtab[i].evtime == SAEV_Events_currcycle) { /*if (eventtab[i].handler === null) { SAEF_error("events.eventtab[%d].handler is null!", i); eventtab[i].active = false; } else*/ eventtab[i].handler(); } } this.schedule(); } SAEV_Events_currcycle += cycles_to_add; } this.do_cycles_post = function(cycles, v) { this.do_cycles(cycles); } /*-----------------------------------------------------------------------*/ function MISC_handler() { var mintime = SAEC_Events_CYCLE_MAX; var ct = SAEV_Events_currcycle; if (recursive) { dorecheck = true; return; } recursive++; SAER_Events_eventtab[EV_MISC].active = false; var recheck = true; while (recheck) { recheck = false; mintime = SAEC_Events_CYCLE_MAX; for (var i = 0; i < EV2_MAX; i++) { if (eventtab2[i].active) { if (eventtab2[i].evtime == ct) { eventtab2[i].active = false; eventtab2[i].handler(eventtab2[i].data); if (dorecheck || eventtab2[i].active) { recheck = true; dorecheck = false; } } else { var eventtime = eventtab2[i].evtime - ct; if (eventtime < mintime) mintime = eventtime; } } } } if (mintime != SAEC_Events_CYCLE_MAX) { SAER_Events_eventtab[EV_MISC].active = true; SAER_Events_eventtab[EV_MISC].oldcycles = ct; SAER_Events_eventtab[EV_MISC].evtime = ct + mintime; SAER.events.schedule(); } recursive--; } this.event2_newevent_xx = function(no, t, data, func) { var et = SAEV_Events_currcycle + t; if (no < 0) { no = nextno; for (;;) { if (!eventtab2[no].active) break; if (eventtab2[no].evtime == et && eventtab2[no].data == data && eventtab2[no].handler === func) break; no++; if (no == EV2_MAX) no = EV2_MISC; if (no == nextno) { SAEF_error("events.event2_newevent_xx() out of events!"); return; } } nextno = no; } eventtab2[no].active = true; eventtab2[no].evtime = et; eventtab2[no].handler = func; eventtab2[no].data = data; MISC_handler(); } function event2_newevent_x(no, t, data, func) { if (t <= 0) { func(data); return; } SAER.events.event2_newevent_xx(no, t * SAEC_Events_CYCLE_UNIT, data, func); } this.event2_newevent = function(no, t, data) { event2_newevent_x(no, t, data, eventtab2[no].handler); } /*this.event2_newevent2 = function(t, data, func) { event2_newevent_x(-1, t, data, func); }*/ this.event2_remevent = function(no) { eventtab2[no].active = false; } /*-----------------------------------------------------------------------*/ /* events dmal */ function dmal_emu(v) { // Disk and Audio DMA bits are ignored by Agnus, Agnus only checks DMAL and master bit if (!(SAEV_Custom_dmacon & SAEC_Custom_DMAF_DMAEN)) return; var hpos = SAER.events.current_hpos(); if (v >= 6) { v -= 6; var nr = v >> 1; var pt = SAER.audio.getpt(nr, (v & 1) != 0); var dat = SAER_Memory_chipGet16_indirect(pt); SAEV_Custom_last_value = dat; SAER.audio.AUDxDAT(nr, dat); } else { var w = v & 1; var pt = SAER.disk.getpt(); // disk_fifostatus() needed in >100% disk speed modes if (w) { // write to disk if (SAER.disk.fifostatus() <= 0) { var dat = SAER_Memory_chipGet16_indirect(pt); SAEV_Custom_last_value = dat; SAER.disk.DSKDAT(dat); } } else { // read from disk if (SAER.disk.fifostatus() >= 0) { var dat = SAER.disk.DSKDATR(); SAER_Memory_chipPut16_indirect(pt, dat); } } } } this.events_dmal_hsync = function() { if (dmal) SAEF_error("events.events_dmal_hsync() DMAL error!? %04x", dmal); dmal = SAER.audio.dmal(); dmal = (dmal << 6) & 0xffff; dmal |= SAER.disk.dmal(); if (dmal) { dmal_hpos = 0; //SAER.events.event2_newevent2(7, 13, function(v) { SAER.events.event2_newevent_xx(-1, 7 * SAEC_Events_CYCLE_UNIT, 13, function(v) { while (dmal) { if (dmal & 3) dmal_emu(dmal_hpos + ((dmal & 2) ? 1 : 0)); dmal_hpos += 2; dmal >>>= 2; } }); } } /*-----------------------------------------------------------------------*/ /* fps counter */ function fpscounter_reset() { //global fps_mavg.clr(); idle_mavg.clr(); SAEV_Events_bogusframe = 2; SAEV_Events_timeframes = 0; lastframetime = SAEF_now(); idletime = 0; } this.fpscounter = function(frameok) { var now = SAEF_now(); var last = now - lastframetime; lastframetime = now; if (SAEV_Events_bogusframe || last < 0) return; fps_mavg.set(last); idle_mavg.set(idletime); idletime = 0; frametime += last; SAEV_Events_timeframes++; if ((SAEV_Events_timeframes & 7) == 0) { var avg = idle_mavg.get(); var idle = 100.0 - (avg == 0 ? 0 : avg * 100 / vsynctimebase); if (idle < 0) idle = 0.0; else if (idle > 100) idle = 100.0; avg = fps_mavg.get(); var fps = avg == 0 ? 0 : SAEC_Events_syncbase / avg; if (fps > 999) fps = 999.0; if (SAEV_Playfield_fake_vblank_hz > fps) idle *= SAEV_Playfield_fake_vblank_hz / fps; //if (currprefs.turbo_emulation && idle < 100) idle = 100.0; SAER.gui.data.fps = fps; //SAER.gui.data.idle = (int)idle; SAER.gui.data.idle = idle; SAER.gui.data.fps_color = frameok ? 0 : 1; if ((SAEV_Events_timeframes & 15) == 0) { //SAER.gui.fps(fps, (int)idle, frameok ? 0 : 1); SAER.gui.fps(fps, idle, frameok ? 0 : 1); } } } /*-----------------------------------------------------------------------*/ /* synchronization */ this.framewait2_maximum = function(ll) { //static int sleeps_remaining; //if (is_last_line()) { if (ll) { /*sleeps_remaining = (165 - currprefs.cpu_idle) / 6; if (sleeps_remaining < 0) sleeps_remaining = 0; // really last line, just run the cpu emulation until whole vsync time has been used if (SAER.m68k.stopped && currprefs.cpu_idle) { // CPU in STOP state: sleep if enough time left. var rpt = SAEF_now(); while (!vsync_isdone () && ~~vsyncmintime - ~~(rpt + vsynctimebase / 10) > 0 && ~~vsyncmintime - ~~rpt < vsynctimebase) { //if (!execute_other_cpu(rpt + vsynctimebase / 10)) SAEF_sleep(1); rpt = SAEF_now(); } } else*/ if (SAEV_config.cpu.speedThrottle) { vsyncmintime = SAEF_now(); // end of CPU emulation time is_syncline = 0; } else { vsyncmintime = vsyncmaxtime; // emulate if still time left is_syncline_end = SAEF_now() + vsynctimebase; // far enough in future, we never wait that long is_syncline = 2; } } else { //static int linecounter; // end of scanline, run cpu emulation as long as we still have time vsyncmintime += vsynctimeperline; linecounter++; is_syncline = 0; //if (!vsync_isdone() && !currprefs.turbo_emulation) { if (vsyncmaxtime - vsyncmintime > 0) { if (vsyncwaittime - vsyncmintime > 0) { var rpt = SAEF_now(); // Extra time left? Do some extra CPU emulation if (vsyncmintime - rpt > 0) { /*if (SAER.m68k.stopped && currprefs.cpu_idle && sleeps_remaining > 0) { // STOP STATE: sleep. SAEF_sleep(1); sleeps_remaining--; } else*/ { is_syncline = 1; // limit extra time is_syncline_end = rpt + vsynctimeperline; linecounter = 0; } } } if (!SAER_Playfield_isvsync()) { // extra cpu emulation time if previous 10 lines without extra time. if (!is_syncline && linecounter >= 10 && (!SAER.m68k.stopped)) { // || !currprefs.cpu_idle)) { is_syncline = -1; is_syncline_end = SAEF_now() + vsynctimeperline; linecounter = 0; } } } } } } this.framewait2_normal = function() { vsyncmintime += vsynctimeperline; //if (!vsync_isdone() && !currprefs.turbo_emulation) { var rpt = SAEF_now(); // sleep if more than 2ms "free" time //while (!vsync_isdone() && vsyncmintime - Math.floor(rpt + vsynctimebase / 10) > 0 && vsyncmintime - rpt < vsynctimebase) { while (vsyncmintime - Math.floor(rpt + vsynctimebase / 10) > 0 && vsyncmintime - rpt < vsynctimebase) { //if (!execute_other_cpu(rpt + vsynctimebase / 10)) SAEF_sleep(1); rpt = SAEF_now(); //SAEF_log("*"); } } } /*---------------------------------*/ function rpt_vsync(adjust) { var curr_time = SAEF_now(); var v = curr_time - vsyncwaittime + adjust; if (v > SAEC_Events_syncbase || v < -SAEC_Events_syncbase) { vsyncmintime = vsyncmaxtime = vsyncwaittime = curr_time; v = 0; } return v; } /*function rtg_vsync() { #ifdef PICASSO96 var start = SAEF_now(); picasso_handle_vsync(); var end = SAEF_now(); SAEV_Events_frameskiptime += end - start; #endif } function rtg_vsynccheck() { if (vblank_found_rtg) { vblank_found_rtg = false; rtg_vsync(); } }*/ this.framewait = function() { var curr_time; var start; var vs = SAER_Playfield_isvsync_chipset(); var status = 0; is_syncline = 0; //static struct mavg_data ma_frameskipt; var frameskipt_avg = ~~ma_frameskipt.set(SAEV_Events_frameskiptime); SAEV_Events_frameskiptime = 0; var reflowt_avg = ~~ma_reflowt.set(SAEV_Events_reflowtime); /*OWN stripped if (vs > 0) {} else if (vs < 0) {}*/ status = 1; var clockadjust = 0; var vstb = vsynctimebase; if (SAEV_config.cpu.speed < 0) { //max if (!SAEV_Playfield_frame_rendered && !SAEV_Playfield_picasso_on) SAEV_Playfield_frame_rendered = SAER.video.render_screen(false); if (SAEV_config.cpu.speedThrottle) { // this delay can safely overshoot frame time by 1-2 ms, following code will compensate for it. for (;;) { curr_time = SAEF_now(); if (vsyncwaittime - curr_time <= 0 || vsyncwaittime - curr_time > 2 * vsynctimebase) break; //rtg_vsynccheck(); SAEF_sleep(1); } } else curr_time = SAEF_now(); var adjust = 0, max; if (curr_time - vsyncwaittime > 0 && curr_time - vsyncwaittime < (vstb >> 1)) adjust += curr_time - vsyncwaittime; adjust += clockadjust; if (SAEV_config.cpu.speedThrottle) max = Math.truncate(vstb * (1000.0 + SAEV_config.cpu.speedThrottle) / 1000.0 - adjust); else max = vstb - adjust; vsyncwaittime = curr_time + vstb - adjust; vsyncmintime = curr_time; if (max < 0) { max = 0; vsynctimeperline = 1; } else vsynctimeperline = max / (SAER.playfield.get_maxvpos_display() + 1) >>> 0; vsyncmaxtime = curr_time + max; //SAEF_info("%06d:%06d/%06d", adjust, vsynctimeperline, vstb); } else { const syncbase1000inv = 1.0 / (SAEC_Events_syncbase / 1000); //OWN var t = reflowt_avg; //OWN if (!SAEV_Playfield_frame_rendered && !SAEV_Playfield_picasso_on) { start = SAEF_now(); SAEV_Playfield_frame_rendered = SAER.video.render_screen(false); t += SAEF_now() - start; } start = SAEF_now(); while (true) { //while (!currprefs.turbo_emulation) { var v = rpt_vsync(clockadjust) * syncbase1000inv; //double if (v >= -4) break; //rtg_vsynccheck(); SAEF_sleep(2); } while (rpt_vsync(clockadjust) < 0) { //rtg_vsynccheck(); } curr_time = SAEF_now(); idletime += curr_time - start; vsyncmintime = curr_time; vsyncmaxtime = vsyncwaittime = curr_time + vstb; if (SAEV_Playfield_frame_rendered) { SAER.video.show_screen(0); t += SAEF_now() - curr_time; } t += frameskipt_avg; vsynctimeperline = ~~((vstb - t) / 3); if (vsynctimeperline < 0) vsynctimeperline = 0; else if (vsynctimeperline > vstb / 3 >>> 0) vsynctimeperline = vstb / 3 >>> 0; SAEV_Playfield_frame_shown = true; } return status != 0; } /*-----------------------------------------------------------------------*/ /* exact cycling */ /*this.alloc_cycle = function(hpos, type) { //#ifdef CPUEMU_13 //#if 0 //if (cycle_line[hpos]) SAEF_log("events.alloc_cycle() hpos=%d, old=%d, new=%d", hpos, cycle_line[hpos], type); //if ((type == SAEC_Events_cycle_line_COPPER) && (hpos & 1) && hpos != SAER.playfield.get_maxhpos() - 2) SAEF_log("events.alloc_cycle() odd %d cycle %d", hpos); //if (!(hpos & 1) && (type == SAEC_Events_cycle_line_SPRITE || type == SAEC_Events_cycle_line_REFRESH || type == SAEC_Events_cycle_line_MISC)) SAEF_log("events.alloc_cycle() even %d cycle %d", type, hpos); //#endif cycle_line[hpos] = type; //#endif } this.alloc_cycle_maybe = function(hpos, type) { if ((cycle_line[hpos] & SAEC_Events_cycle_line_MASK) == 0) this.alloc_cycle(hpos, type); } this.alloc_cycle_blitter = function(hpos, ptr, chnum) { if (cycle_line[hpos] & SAEC_Events_cycle_line_COPPER_SPECIAL) { //static int warned = 100; var srcptr = SAER.copper.get_copxlc(); //cop_state.strobe == 1 ? cop1lc : cop2lc; //if (warned > 0) { SAEF_warn("events.alloc_cycle_blitter() buggy copper cycle conflict with blitter ch %d", chnum); //warned--; } //if ((currprefs.cs_hacks & 1) && SAEV_config.cpu.model == SAEC_Config_CPU_Model_68000) if (SAEV_config.cpu.model == SAEC_Config_CPU_Model_68000 && SAEV_config.chipset.blitter.cycle_exact) ptr.val = srcptr; } this.alloc_cycle(hpos, SAEC_Events_cycle_line_BLITTER); }*/ }