diff --git a/src/emu/machine.cpp b/src/emu/machine.cpp index bf451940..234fd873 100644 --- a/src/emu/machine.cpp +++ b/src/emu/machine.cpp @@ -1397,6 +1397,7 @@ void system_time::full_time::set(struct tm &t) #if defined(__EMSCRIPTEN__) running_machine * running_machine::emscripten_running_machine; +static double berzerker_game_speed = 1.0; // set by berzerker_speed() void running_machine::emscripten_main_loop() { @@ -1410,10 +1411,23 @@ void running_machine::emscripten_main_loop() device_scheduler * scheduler; scheduler = &(machine->scheduler()); - // Emscripten will call this function at 60Hz, so step the simulation - // forward for the amount of time that has passed since the last frame - const attotime frametime(0,HZ_TO_ATTOSECONDS(60)); - const attotime stoptime(scheduler->time() + frametime); + // Step the simulation forward by the real time since the last call. The browser calls this once per + // display frame, which isn't always 60 Hz (slow frames, 120/144 Hz monitors); stepping a fixed 1/60 s + // made the game run slow or fast with the page. Capped so a stall (hidden tab) doesn't fast-forward. + // The target is kept across calls, so a slice that runs past it is paid back next call instead of + // adding up (that ran 9% fast at 120 Hz). + static double s_last_ms = 0.0; + static attotime s_target; + const double now_ms = emscripten_get_now(); + double step = s_last_ms > 0.0 ? (now_ms - s_last_ms) / 1000.0 : 1.0 / 60.0; + s_last_ms = now_ms; + if (step > 0.1) + step = 0.1; + step *= berzerker_game_speed; // editor: Double speed + if (s_target < scheduler->time() - attotime::from_double(0.1) || s_target > scheduler->time() + attotime::from_double(0.1)) + s_target = scheduler->time(); // first call, reset or long stall: start from now + s_target += attotime::from_double(step); + const attotime stoptime(s_target); while (!machine->m_paused && !machine->scheduled_event_pending() && scheduler->time() < stoptime) { @@ -1477,4 +1491,87 @@ void running_machine::emscripten_load(const char *name) { emscripten_running_machine->schedule_load(name); } +// Berzerker page bridge: MAME's own software rasterizer draws the UI target (game + menus) into a buffer the +// page blits to a 2D canvas, so the picture never depends on SDL's browser renderers. Plus byte access to the +// main CPU's address space for the editor. +#include "rendersw.hxx" + +extern "C" { + +EMSCRIPTEN_KEEPALIVE uint8_t *berzerker_frame(int width, int height) +{ + static std::vector s_frame; + running_machine *machine = running_machine::emscripten_get_running_machine(); + if (!machine || width <= 0 || height <= 0) + return nullptr; + render_target &target = machine->render().ui_target(); + s_frame.assign(size_t(width) * height, 0xff000000); + target.set_bounds(width, height); + render_primitive_list &prims = target.get_primitives(); + prims.acquire_lock(); + // RGBA byte order in memory, as ImageData wants it (alpha is set by the page) + software_renderer::draw_primitives(prims, s_frame.data(), width, height, width); + prims.release_lock(); + return reinterpret_cast(s_frame.data()); +} + +static address_space *berzerker_space() +{ + running_machine *machine = running_machine::emscripten_get_running_machine(); + device_t *cpu = machine ? machine->root_device().subdevice("maincpu") : nullptr; + device_memory_interface *mem = nullptr; + return (cpu && cpu->interface(mem)) ? &mem->space(AS_PROGRAM) : nullptr; +} + +// Emulated seconds since start: the page compares it with wall time to show the real speed. +EMSCRIPTEN_KEEPALIVE double berzerker_time() +{ + running_machine *machine = running_machine::emscripten_get_running_machine(); + return machine ? machine->time().as_double() : 0.0; +} + +// Whole-game speed (emulated seconds per real second), for the editor's Double speed. +EMSCRIPTEN_KEEPALIVE void berzerker_speed(double speed) +{ + if (speed >= 0.25 && speed <= 4.0) + berzerker_game_speed = speed; +} + +// Main CPU speed multiplier (MAME's "Overclock CPU" slider). Berzerk's Z80 falls behind in crowded rooms +// and the whole game slows, as on the cabinet; a faster CPU keeps every room at full pace. +EMSCRIPTEN_KEEPALIVE void berzerker_cpu_scale(double scale) +{ + running_machine *machine = running_machine::emscripten_get_running_machine(); + device_t *cpu = machine ? machine->root_device().subdevice("maincpu") : nullptr; + if (cpu && scale >= 0.5 && scale <= 8.0) + cpu->set_clock_scale(scale); +} + +// Direct access to a ROM region ("maincpu", "speech"): the editor patches code, phrases and speech data in +// place after boot, so the loaded ROM files stay the verified originals (no bad-checksum warning). +EMSCRIPTEN_KEEPALIVE int berzerker_rom(const char *region, int offset, int value) +{ + running_machine *machine = running_machine::emscripten_get_running_machine(); + memory_region *r = machine ? machine->root_device().memregion(region) : nullptr; + if (!r || offset < 0 || u32(offset) >= r->bytes()) + return -1; + if (value >= 0) + r->base()[offset] = value & 0xff; + return r->base()[offset]; +} + +EMSCRIPTEN_KEEPALIVE int berzerker_peek(int address) +{ + address_space *space = berzerker_space(); + return space ? space->read_byte(address & 0xffff) : -1; +} + +EMSCRIPTEN_KEEPALIVE void berzerker_poke(int address, int value) +{ + if (address_space *space = berzerker_space()) + space->write_byte(address & 0xffff, value & 0xff); +} + +} // extern "C" + #endif /* defined(__EMSCRIPTEN__) */