public void adjust(attotime start_delay, int param, attotime period) //void adjust(attotime start_delay, s32 param = 0, const attotime &periodicity = attotime::never); { // if this is the callback timer, mark it modified device_scheduler scheduler = machine().scheduler(); if (scheduler.callback_timer() == this) { scheduler.callback_timer_modified_set(true); } // compute the time of the next firing and insert into the list m_param = param; m_enabled = true; // clamp negative times to 0 if (start_delay.seconds() < 0) { start_delay = attotime.zero; } // set the start and expire times m_start = scheduler.time(); m_expire = m_start + start_delay; m_period = period; // remove and re-insert the timer in its new order scheduler.timer_list_remove(this); scheduler.timer_list_insert(this); // if this was inserted as the head, abort the current timeslice and resync if (this == scheduler.first_timer()) { scheduler.abort_timeslice(); } }
//------------------------------------------------- // update - force a stream to update to // the current emulated time //------------------------------------------------- public void update() { if (m_attoseconds_per_sample == 0) { return; } // determine the number of samples since the start of this second attotime time = m_device.machine().time(); int update_sampindex = (int)(time.attoseconds() / m_attoseconds_per_sample); // if we're ahead of the last update, then adjust upwards attotime last_update = m_device.machine().sound().last_update(); if (time.seconds() > last_update.seconds()) { assert(time.seconds() == last_update.seconds() + 1); update_sampindex += (int)m_sample_rate; } // if we're behind the last update, then adjust downwards if (time.seconds() < last_update.seconds()) { assert(time.seconds() == last_update.seconds() - 1); update_sampindex -= (int)m_sample_rate; } if (update_sampindex <= m_output_sampindex) { return; } // generate samples to get us up to the appropriate time profiler_global.g_profiler.start(profile_type.PROFILER_SOUND); //throw new emu_unimplemented(); #if false osdcomm_global.assert(m_output_sampindex - m_output_base_sampindex >= 0); osdcomm_global.assert(update_sampindex - m_output_base_sampindex <= m_output_bufalloc); #endif generate_samples(update_sampindex - m_output_sampindex); profiler_global.g_profiler.stop(); // remember this info for next time m_output_sampindex = update_sampindex; }
//void trigger(int trigid, const attotime &after = attotime::zero); //------------------------------------------------- // boost_interleave - temporarily boosts the // interleave factor //------------------------------------------------- public void boost_interleave(attotime timeslice_time, attotime boost_duration) { // ignore timeslices > 1 second if (timeslice_time.seconds() > 0) { return; } add_scheduling_quantum(timeslice_time, boost_duration); }
// internal helpers //------------------------------------------------- // video_exit - close down the video system //------------------------------------------------- void exit(running_machine machine_) { // stop recording any movie m_movie_recordings.clear(); // free the snapshot target machine().render().target_free(m_snap_target); m_snap_bitmap.reset(); // print a final result if we have at least 2 seconds' worth of data if (!emulator_info.standalone() && m_overall_emutime.seconds() >= 1) { osd_ticks_t tps = m_osdcore.osd_ticks_per_second(); double final_real_time = (double)m_overall_real_seconds + (double)m_overall_real_ticks / (double)tps; double final_emu_time = m_overall_emutime.as_double(); osd_printf_info("Average speed: {0}%% ({1} seconds)\n", 100 * final_emu_time / final_real_time, (m_overall_emutime + new attotime(0, ATTOSECONDS_PER_SECOND / 2)).seconds()); // %.2f%% (%d seconds)\n } }
//------------------------------------------------- // add_scheduling_quantum - add a scheduling // quantum; the smallest active one is the one // that is in use //------------------------------------------------- void add_scheduling_quantum(attotime quantum, attotime duration) { assert(quantum.seconds() == 0); attotime curtime = time(); attotime expire = curtime + duration; attoseconds_t quantum_attos = quantum.attoseconds(); // figure out where to insert ourselves, expiring any quanta that are out-of-date quantum_slot insert_after = null; quantum_slot next; for (quantum_slot quant = m_quantum_list.first(); quant != null; quant = next) { // if this quantum is expired, nuke it next = quant.next(); if (curtime >= quant.expire()) { m_quantum_allocator.reclaim(m_quantum_list.detach(quant)); } // if this quantum is shorter than us, we need to be inserted afterwards else if (quant.requested() <= quantum_attos) { insert_after = quant; } } // if we found an exact match, just take the maximum expiry time if (insert_after != null && insert_after.requested() == quantum_attos) { insert_after.expire_set(std.max(insert_after.expire(), expire)); } // otherwise, allocate a new quantum and insert it after the one we picked else { quantum_slot quant = m_quantum_allocator.alloc(); quant.requested_set(quantum_attos); quant.actual_set(std.max(quantum_attos, m_quantum_minimum)); quant.expire_set(expire); m_quantum_list.insert_after(quant, insert_after); } }
//------------------------------------------------- // update - mix everything down to its final form // and send it to the OSD layer //------------------------------------------------- void update(object o = null, s32 param = 0) // (void *ptr = nullptr, s32 param = 0); { sound_global.VPRINTF("sound_update\n"); profiler_global.g_profiler.start(profile_type.PROFILER_SOUND); // force all the speaker streams to generate the proper number of samples int samples_this_update = 0; foreach (speaker_device speaker in new speaker_device_iterator(machine().root_device())) { speaker.mix(m_leftmix, m_rightmix, ref samples_this_update, (m_muted & MUTE_REASON_SYSTEM) != 0); } // now downmix the final result u32 finalmix_step = (UInt32)machine().video().speed_factor(); u32 finalmix_offset = 0; ListPointer <s16> finalmix = new ListPointer <s16>(m_finalmix); //s16 *finalmix = &m_finalmix[0]; int sample; for (sample = (int)m_finalmix_leftover; sample < samples_this_update * 1000; sample += (int)finalmix_step) { int sampindex = sample / 1000; // clamp the left side int samp = m_leftmix[sampindex]; if (samp < -32768) { samp = -32768; } else if (samp > 32767) { samp = 32767; } finalmix[finalmix_offset++] = (Int16)samp; // clamp the right side samp = m_rightmix[sampindex]; if (samp < -32768) { samp = -32768; } else if (samp > 32767) { samp = 32767; } finalmix[finalmix_offset++] = (Int16)samp; } m_finalmix_leftover = (UInt32)(sample - samples_this_update * 1000); // play the result if (finalmix_offset > 0) { if (m_nosound_mode == 0) { machine().osd().update_audio_stream(finalmix, (int)(finalmix_offset / 2)); } machine().osd().add_audio_to_recording(finalmix, (int)finalmix_offset / 2); machine().video().add_sound_to_recording(finalmix, (int)(finalmix_offset / 2)); if (m_wavfile != null) { wavwrite_global.wav_add_data_16(m_wavfile, finalmix, (int)finalmix_offset); } } // see if we ticked over to the next second attotime curtime = machine().time(); bool second_tick = false; if (curtime.seconds() != m_last_update.seconds()) { assert(curtime.seconds() == m_last_update.seconds() + 1); second_tick = true; } // iterate over all the streams and update them foreach (var stream in m_stream_list) { stream.update_with_accounting(second_tick); } // remember the update time m_last_update = curtime; // update sample rates if they have changed foreach (var stream in m_stream_list) { stream.apply_sample_rate_changes(); } profiler_global.g_profiler.stop(); }
//------------------------------------------------- // recompute_speed - recompute the current // overall speed; we assume this is called only // if we did not skip a frame //------------------------------------------------- void recompute_speed(attotime emutime) { // if we don't have a starting time yet, or if we're paused, reset our starting point if (m_speed_last_realtime == 0 || machine().paused()) { m_speed_last_realtime = m_osdcore.osd_ticks(); m_speed_last_emutime = emutime; } // if it has been more than the update interval, update the time attotime delta_emutime = emutime - m_speed_last_emutime; if (delta_emutime > new attotime(0, ATTOSECONDS_PER_SPEED_UPDATE)) { // convert from ticks to attoseconds osd_ticks_t realtime = m_osdcore.osd_ticks(); osd_ticks_t delta_realtime = realtime - m_speed_last_realtime; osd_ticks_t tps = m_osdcore.osd_ticks_per_second(); m_speed_percent = delta_emutime.as_double() * (double)tps / (double)delta_realtime; // remember the last times m_speed_last_realtime = realtime; m_speed_last_emutime = emutime; // if we're throttled, this time period counts for overall speed; otherwise, we reset the counter if (!m_fastforward) { m_overall_valid_counter++; } else { m_overall_valid_counter = 0; } // if we've had at least 4 consecutive valid periods, accumulate stats if (m_overall_valid_counter >= 4) { m_overall_real_ticks += delta_realtime; while (m_overall_real_ticks >= tps) { m_overall_real_ticks -= tps; m_overall_real_seconds++; } m_overall_emutime += delta_emutime; } } // if we're past the "time-to-execute" requested, signal an exit if (m_seconds_to_run != 0 && emutime.seconds() >= m_seconds_to_run) { // create a final screenshot emu_file file = new emu_file(machine().options().snapshot_directory(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); std.error_condition filerr = open_next(file, "png"); if (!filerr) { save_snapshot(null, file); } file.close(); //printf("Scheduled exit at %f\n", emutime.as_double()); // schedule our demise machine().schedule_exit(); } }
// execution //------------------------------------------------- // timeslice - execute all devices for a single // timeslice //------------------------------------------------- public void timeslice() { bool call_debugger = (machine().debug_flags & DEBUG_FLAG_ENABLED) != 0; // build the execution list if we don't have one yet //if (UNEXPECTED(m_execute_list == null)) if (m_execute_list == null) { rebuild_execute_list(); } // if the current quantum has expired, find a new one while (m_basetime >= m_quantum_list.first().expire()) { m_quantum_allocator.reclaim(m_quantum_list.detach_head()); } // loop until we hit the next timer while (m_basetime < m_timer_list.expire()) { // by default, assume our target is the end of the next quantum attotime target = m_basetime + new attotime(0, m_quantum_list.first().actual()); // however, if the next timer is going to fire before then, override if (m_timer_list.expire() < target) { target = m_timer_list.expire(); } if (machine().video().frame_update_count() % 1000 == 0) { //LOG(("------------------\n")); LOG("device_scheduler.timeslice() - cpu_timeslice: target = {0}, m_timer_list.expire: {1}\n", target.as_string(), m_timer_list.expire().as_string()); } // do we have pending suspension changes? if (m_suspend_changes_pending) { apply_suspend_changes(); } // loop over all CPUs for (device_execute_interface exec = m_execute_list; exec != null; exec = exec.m_nextexec) { // only process if this CPU is executing or truly halted (not yielding) // and if our target is later than the CPU's current time (coarse check) if ((exec.m_suspend == 0 || exec.m_eatcycles > 0) && target.seconds() >= exec.m_localtime.seconds()) //if (EXPECTED((exec->m_suspend == 0 || exec->m_eatcycles) && target.seconds() >= exec->m_localtime.seconds())) { // compute how many attoseconds to execute this CPU attoseconds_t delta = target.attoseconds() - exec.m_localtime.attoseconds(); if (delta < 0 && target.seconds() > exec.m_localtime.seconds()) { delta += ATTOSECONDS_PER_SECOND; } assert(delta == (target - exec.m_localtime).as_attoseconds()); if (exec.m_attoseconds_per_cycle == 0) { exec.m_localtime = target; } // if we have enough for at least 1 cycle, do the math else if (delta >= exec.m_attoseconds_per_cycle) { // compute how many cycles we want to execute int ran = exec.m_cycles_running = (int)divu_64x32((u64)delta >> exec.m_divshift, (u32)exec.m_divisor); if (machine().video().frame_update_count() % 1000 == 0) { LOG("device_scheduler.timeslice() - cpu '{0}': {1} ({2} cycles)\n", exec.device().tag(), delta, exec.m_cycles_running); } // if we're not suspended, actually execute if (exec.m_suspend == 0) { g_profiler.start(exec.m_profiler); // note that this global variable cycles_stolen can be modified // via the call to cpu_execute exec.m_cycles_stolen = 0; m_executing_device = exec; exec.m_icountptr.i = exec.m_cycles_running; // *exec->m_icountptr = exec->m_cycles_running; if (!call_debugger) { exec.run(); } else { exec.debugger_start_cpu_hook(target); exec.run(); exec.debugger_stop_cpu_hook(); } // adjust for any cycles we took back //throw new emu_unimplemented(); #if false assert(ran >= *exec->m_icountptr); #endif ran -= exec.m_icountptr.i; //ran -= *exec->m_icountptr; //throw new emu_unimplemented(); #if false assert(ran >= exec->m_cycles_stolen); #endif ran -= exec.m_cycles_stolen; g_profiler.stop(); } // account for these cycles exec.m_totalcycles += (u64)ran; // update the local time for this CPU attotime deltatime; if (ran < exec.m_cycles_per_second) { deltatime = new attotime(0, exec.m_attoseconds_per_cycle * ran); } else { u32 remainder; s32 secs = (s32)divu_64x32_rem((u64)ran, exec.m_cycles_per_second, out remainder); deltatime = new attotime(secs, remainder * exec.m_attoseconds_per_cycle); } assert(deltatime >= attotime.zero); exec.m_localtime += deltatime; if (machine().video().frame_update_count() % 100 == 0) { LOG("device_scheduler.timeslice() - {0} ran, {1} total, time = {2}\n", ran, exec.m_totalcycles, exec.m_localtime.as_string()); } // if the new local CPU time is less than our target, move the target up, but not before the base if (exec.m_localtime < target) { target = std.max(exec.m_localtime, m_basetime); if (machine().video().frame_update_count() % 1000 == 0) { LOG("device_scheduler.timeslice() - (new target)\n"); } } } } } m_executing_device = null; // update the base time m_basetime = target; } // execute timers execute_timers(); }