mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-07 08:36:02 -10:00
The flag let a host clock driving runloop_iterate() from a modal loop skip the frame limiter's sleep, the frame delay and the no-window wait. Its only setter was the Win32 size/move pump from91920289, removed in 7fbc868499; the replacement (6c0cedc945) never runs the run loop from the modal loop, so nothing has set pace_external since. The bit (1 << 5) is left vacant with a note; PACE_FACT_EXTERNAL goes with it and the three guarded sites lose their dead branch. Checked: host build of runloop.o, tools/mingw_syntax_check.sh, samples/runloop/pacing.
812 lines
31 KiB
C
812 lines
31 KiB
C
/* RetroArch - A frontend for libretro.
|
|
* Copyright (C) 2010-2014 - Hans-Kristian Arntzen
|
|
* Copyright (C) 2011-2021 - Daniel De Matteis
|
|
*
|
|
* RetroArch 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 Found-
|
|
* ation, either version 3 of the License, or (at your option) any later version.
|
|
*
|
|
* RetroArch 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.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with RetroArch.
|
|
* If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#ifndef __RUNLOOP_H
|
|
#define __RUNLOOP_H
|
|
|
|
#include <stdint.h>
|
|
#include <stddef.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <boolean.h>
|
|
#include <retro_inline.h>
|
|
#include <retro_common_api.h>
|
|
#include <libretro.h>
|
|
#include <dynamic/dylib.h>
|
|
#include <queues/message_queue.h>
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#ifdef HAVE_THREADS
|
|
#include <rthreads/rthreads.h>
|
|
#endif
|
|
|
|
#include "dynamic.h"
|
|
#include "configuration.h"
|
|
#include "core_option_manager.h"
|
|
#include "performance_counters.h"
|
|
#include "state_manager.h"
|
|
#ifdef HAVE_RUNAHEAD
|
|
#include "runahead.h"
|
|
#endif
|
|
#include "tasks/tasks_internal.h"
|
|
|
|
/* Arbitrary twenty subsystems limit */
|
|
#define SUBSYSTEM_MAX_SUBSYSTEMS 20
|
|
|
|
/* Arbitrary 10 roms for each subsystem limit */
|
|
#define SUBSYSTEM_MAX_SUBSYSTEM_ROMS 10
|
|
|
|
#ifdef HAVE_THREADS
|
|
#define RUNLOOP_MSG_QUEUE_LOCK(runloop_st) slock_lock((runloop_st)->msg_queue_lock)
|
|
#define RUNLOOP_MSG_QUEUE_UNLOCK(runloop_st) slock_unlock((runloop_st)->msg_queue_lock)
|
|
#else
|
|
#define RUNLOOP_MSG_QUEUE_LOCK(runloop_st) (void)(runloop_st)
|
|
#define RUNLOOP_MSG_QUEUE_UNLOCK(runloop_st) (void)(runloop_st)
|
|
#endif
|
|
|
|
#ifdef HAVE_BSV_MOVIE
|
|
#define BSV_MOVIE_IS_EOF() || (((input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_END) && (input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_EOF_EXIT)))
|
|
#else
|
|
#define BSV_MOVIE_IS_EOF()
|
|
#endif
|
|
|
|
/* Time to exit out of the main loop?
|
|
* Reasons for exiting:
|
|
* a) Shutdown environment callback was invoked.
|
|
* b) Quit key was pressed.
|
|
* c) Frame count exceeds or equals maximum amount of frames to run.
|
|
* d) Video driver no longer alive.
|
|
* e) End of BSV movie and BSV EOF exit is true. (TODO/FIXME - explain better)
|
|
*/
|
|
#define RUNLOOP_TIME_TO_EXIT(quit_key_pressed) ((runloop_state.flags & RUNLOOP_FLAG_SHUTDOWN_INITIATED) || quit_key_pressed || !is_alive BSV_MOVIE_IS_EOF() || ((runloop_state.max_frames != 0) && (frame_count >= runloop_state.max_frames)) || runloop_exec)
|
|
|
|
enum runloop_state_enum
|
|
{
|
|
RUNLOOP_STATE_ITERATE = 0,
|
|
RUNLOOP_STATE_POLLED_AND_SLEEP,
|
|
/* A one-shot menu action was performed and the next iteration should
|
|
* start clean. Not idle: returns to the caller immediately, without
|
|
* the idle sleep POLLED_AND_SLEEP takes. */
|
|
RUNLOOP_STATE_POLLED_AND_CONTINUE,
|
|
RUNLOOP_STATE_PAUSE,
|
|
RUNLOOP_STATE_MENU,
|
|
RUNLOOP_STATE_QUIT
|
|
};
|
|
|
|
enum poll_type_override_t
|
|
{
|
|
POLL_TYPE_OVERRIDE_DONTCARE = 0,
|
|
POLL_TYPE_OVERRIDE_EARLY,
|
|
POLL_TYPE_OVERRIDE_NORMAL,
|
|
POLL_TYPE_OVERRIDE_LATE
|
|
};
|
|
|
|
enum runloop_flags
|
|
{
|
|
RUNLOOP_FLAG_MAX_FRAMES_SCREENSHOT = (1 << 0),
|
|
RUNLOOP_FLAG_HAS_SET_CORE = (1 << 1),
|
|
RUNLOOP_FLAG_CORE_SET_SHARED_CONTEXT = (1 << 2),
|
|
RUNLOOP_FLAG_IGNORE_ENVIRONMENT_CB = (1 << 3),
|
|
RUNLOOP_FLAG_IS_SRAM_LOAD_DISABLED = (1 << 4),
|
|
RUNLOOP_FLAG_IS_SRAM_SAVE_DISABLED = (1 << 5),
|
|
RUNLOOP_FLAG_USE_SRAM = (1 << 6),
|
|
RUNLOOP_FLAG_PATCH_BLOCKED = (1 << 7),
|
|
RUNLOOP_FLAG_REQUEST_SPECIAL_SAVESTATE = (1 << 8),
|
|
RUNLOOP_FLAG_OVERRIDES_ACTIVE = (1 << 9),
|
|
RUNLOOP_FLAG_GAME_OPTIONS_ACTIVE = (1 << 10),
|
|
RUNLOOP_FLAG_FOLDER_OPTIONS_ACTIVE = (1 << 11),
|
|
RUNLOOP_FLAG_REMAPS_CORE_ACTIVE = (1 << 12),
|
|
RUNLOOP_FLAG_REMAPS_GAME_ACTIVE = (1 << 13),
|
|
RUNLOOP_FLAG_REMAPS_CONTENT_DIR_ACTIVE = (1 << 14),
|
|
RUNLOOP_FLAG_SHUTDOWN_INITIATED = (1 << 15),
|
|
RUNLOOP_FLAG_CORE_SHUTDOWN_INITIATED = (1 << 16),
|
|
RUNLOOP_FLAG_CORE_RUNNING = (1 << 17),
|
|
RUNLOOP_FLAG_AUTOSAVE = (1 << 18),
|
|
RUNLOOP_FLAG_HAS_VARIABLE_UPDATE = (1 << 19),
|
|
RUNLOOP_FLAG_INPUT_IS_DIRTY = (1 << 20),
|
|
RUNLOOP_FLAG_RUNAHEAD_SAVE_STATE_SIZE_KNOWN = (1 << 21),
|
|
RUNLOOP_FLAG_RUNAHEAD_AVAILABLE = (1 << 22),
|
|
RUNLOOP_FLAG_RUNAHEAD_SECONDARY_CORE_AVAILABLE = (1 << 23),
|
|
RUNLOOP_FLAG_RUNAHEAD_FORCE_INPUT_DIRTY = (1 << 24),
|
|
RUNLOOP_FLAG_SLOWMOTION = (1 << 25),
|
|
RUNLOOP_FLAG_FASTMOTION = (1 << 26),
|
|
RUNLOOP_FLAG_PAUSED = (1 << 27),
|
|
RUNLOOP_FLAG_IDLE = (1 << 28),
|
|
RUNLOOP_FLAG_FOCUSED = (1 << 29),
|
|
RUNLOOP_FLAG_FORCE_NONBLOCK = (1 << 30)
|
|
/* (1 << 31) was RUNLOOP_FLAG_IS_INITED. Moved out of this word into
|
|
* an atomic behind the runloop_is_inited_* accessors: it is the only
|
|
* bit here read off the main thread (the AppIntents entity query runs
|
|
* on a GCD worker), and the main thread's non-atomic read-modify-
|
|
* writes of this word would race that read. Bit left reserved. */
|
|
};
|
|
|
|
/* Whether retroarch_main_init() has completed. Written on the main
|
|
* thread, readable from any thread. */
|
|
void runloop_is_inited_set(void);
|
|
void runloop_is_inited_clear(void);
|
|
bool runloop_is_inited(void);
|
|
|
|
/* Contains the current retro_fastforwarding_override
|
|
* parameters along with any pending updates triggered
|
|
* by RETRO_ENVIRONMENT_SET_FASTFORWARDING_OVERRIDE */
|
|
typedef struct fastmotion_overrides
|
|
{
|
|
struct retro_fastforwarding_override current;
|
|
struct retro_fastforwarding_override next;
|
|
bool pending;
|
|
} fastmotion_overrides_t;
|
|
|
|
typedef struct
|
|
{
|
|
unsigned priority;
|
|
float duration;
|
|
char str[128];
|
|
bool set;
|
|
} runloop_core_status_msg_t;
|
|
|
|
/* Contains all callbacks associated with
|
|
* core options.
|
|
* > At present there is only a single
|
|
* callback, 'update_display' - but we
|
|
* may wish to add more in the future
|
|
* (e.g. for directly informing a core of
|
|
* core option value changes, or getting/
|
|
* setting extended/non-standard option
|
|
* value data types) */
|
|
typedef struct core_options_callbacks
|
|
{
|
|
retro_core_options_update_display_callback_t update_display;
|
|
} core_options_callbacks_t;
|
|
|
|
struct runloop
|
|
{
|
|
#if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL)
|
|
rarch_timer_t shader_delay_timer; /* int64_t alignment */
|
|
#endif
|
|
retro_time_t core_runtime_last;
|
|
retro_time_t core_runtime_usec;
|
|
retro_time_t core_run_time;
|
|
retro_time_t frame_limit_minimum_time;
|
|
retro_time_t frame_limit_last_time;
|
|
/* The same period and anchor in nanoseconds, for the gap limiter's
|
|
* schedule: a period rounded to whole microseconds is 21 ppm off
|
|
* at 59.94 Hz, which the schedule would carry into every frame. */
|
|
int64_t frame_limit_minimum_time_ns;
|
|
int64_t frame_limit_anchor_ns;
|
|
/* How early the gap limiter's sleep is asked to return, so the
|
|
* remainder can be spun to the deadline: the sleep's observed
|
|
* overshoot, tracked by runloop_pace_margin_update(). */
|
|
retro_time_t frame_limit_margin;
|
|
/* When the previous iteration reached the pacing block, and the
|
|
* smoothed interval between iterations. The pace bits say who
|
|
* claims to be holding the loop; this says how fast it is actually
|
|
* going, which is the other half of the same question - a source
|
|
* that claims the loop while the loop runs at three times the
|
|
* content rate is not holding anything. */
|
|
retro_time_t pace_iter_last;
|
|
retro_time_t pace_period_usec;
|
|
unsigned pace; /* enum runloop_pace_source bits */
|
|
retro_usec_t frame_time_last; /* int64_t alignment */
|
|
|
|
/* Per-frame scalar state. Kept adjacent to the timing block above so the
|
|
* whole set the frame loop evaluates every iteration lands in the first
|
|
* cache lines of the struct, rather than ~348 lines further in behind the
|
|
* content/system/subsystem blocks. Declaration order only; every access is
|
|
* by member name. */
|
|
fastmotion_overrides_t fastmotion_override; /* float alignment */
|
|
|
|
enum rarch_core_type current_core_type;
|
|
enum rarch_core_type explicit_current_core_type;
|
|
enum poll_type_override_t core_poll_type_override;
|
|
#if defined(HAVE_RUNAHEAD)
|
|
enum rarch_core_type last_core_type;
|
|
#endif
|
|
|
|
uint32_t flags;
|
|
int16_t entry_state_slot;
|
|
uint8_t pending_disk_control_insert;
|
|
int8_t run_frames_and_pause;
|
|
|
|
struct retro_core_t current_core; /* uint64_t alignment */
|
|
#if defined(HAVE_RUNAHEAD)
|
|
uint64_t runahead_last_frame_count; /* uint64_t alignment */
|
|
#if defined(HAVE_DYNAMIC) || defined(HAVE_DYLIB)
|
|
struct retro_core_t secondary_core; /* uint64_t alignment */
|
|
#endif
|
|
retro_ctx_load_content_info_t *load_content_info;
|
|
#if defined(HAVE_DYNAMIC) || defined(HAVE_DYLIB)
|
|
char *secondary_library_path;
|
|
#endif
|
|
/* Embedded directly: was previously a my_list wrapper holding a
|
|
* single retro_ctx_serialize_info_t* in data[0]. The list machinery
|
|
* was overkill for one pointer — two extra mallocs at init (the
|
|
* my_list struct + its 16-slot data array), one extra indirection
|
|
* per access, and a constructor/destructor function-pointer pair
|
|
* for what amounts to alloc/free of a data buffer. */
|
|
retro_ctx_serialize_info_t runahead_savestate_info;
|
|
my_list *input_state_list;
|
|
preempt_t *preempt_data;
|
|
#endif
|
|
|
|
#ifdef HAVE_REWIND
|
|
struct state_manager_rewind_state rewind_st;
|
|
#endif
|
|
struct retro_perf_counter *perf_counters_libretro[MAX_COUNTERS];
|
|
bool *load_no_content_hook;
|
|
struct string_list *subsystem_fullpaths;
|
|
struct retro_subsystem_info subsystem_data[SUBSYSTEM_MAX_SUBSYSTEMS];
|
|
struct retro_callbacks retro_ctx; /* ptr alignment */
|
|
msg_queue_t msg_queue; /* ptr alignment */
|
|
retro_input_poll_t input_poll_callback_original; /* ptr alignment */
|
|
retro_input_state_t input_state_callback_original; /* ptr alignment */
|
|
#ifdef HAVE_RUNAHEAD
|
|
function_t retro_reset_callback_original; /* ptr alignment */
|
|
function_t original_retro_deinit; /* ptr alignment */
|
|
function_t original_retro_unload; /* ptr alignment */
|
|
runahead_load_state_function
|
|
retro_unserialize_callback_original; /* ptr alignment */
|
|
#if defined(HAVE_DYNAMIC) || defined(HAVE_DYLIB)
|
|
struct retro_callbacks secondary_callbacks; /* ptr alignment */
|
|
#endif
|
|
#endif
|
|
#ifdef HAVE_THREADS
|
|
slock_t *msg_queue_lock;
|
|
#endif
|
|
|
|
content_state_t content_st; /* ptr alignment */
|
|
struct retro_subsystem_rom_info
|
|
subsystem_data_roms[SUBSYSTEM_MAX_SUBSYSTEMS]
|
|
[SUBSYSTEM_MAX_SUBSYSTEM_ROMS]; /* ptr alignment */
|
|
core_option_manager_t *core_options;
|
|
core_options_callbacks_t core_options_callback;/* ptr alignment */
|
|
|
|
retro_keyboard_event_t key_event; /* ptr alignment */
|
|
retro_keyboard_event_t frontend_key_event; /* ptr alignment */
|
|
|
|
rarch_system_info_t system; /* ptr alignment */
|
|
struct retro_frame_time_callback frame_time; /* ptr alignment */
|
|
struct retro_audio_buffer_status_callback audio_buffer_status; /* ptr alignment */
|
|
#ifdef HAVE_DYNAMIC
|
|
dylib_t lib_handle; /* ptr alignment */
|
|
#endif
|
|
#if defined(HAVE_RUNAHEAD)
|
|
#if defined(HAVE_DYNAMIC) || defined(HAVE_DYLIB)
|
|
dylib_t secondary_lib_handle; /* ptr alignment */
|
|
#endif
|
|
size_t runahead_save_state_size;
|
|
#endif
|
|
size_t msg_queue_size;
|
|
|
|
#if defined(HAVE_RUNAHEAD)
|
|
#if defined(HAVE_DYNAMIC) || defined(HAVE_DYLIB)
|
|
int port_map[MAX_USERS];
|
|
#endif
|
|
#endif
|
|
|
|
runloop_core_status_msg_t core_status_msg;
|
|
|
|
unsigned msg_queue_delay;
|
|
unsigned pending_windowed_scale;
|
|
unsigned max_frames;
|
|
unsigned audio_latency;
|
|
unsigned fastforward_after_frames;
|
|
unsigned perf_ptr_libretro;
|
|
unsigned subsystem_current_count;
|
|
unsigned video_swap_interval_auto;
|
|
retro_bits_t has_set_libretro_device; /* uint32_t alignment */
|
|
|
|
char runtime_content_path_basename[PATH_MAX_LENGTH];
|
|
#ifdef HAVE_SCREENSHOTS
|
|
char max_frames_screenshot_path[PATH_MAX_LENGTH];
|
|
#endif
|
|
#if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL)
|
|
char runtime_shader_preset_path[PATH_MAX_LENGTH];
|
|
#endif
|
|
char runtime_content_path[PATH_MAX_LENGTH];
|
|
char runtime_core_path[PATH_MAX_LENGTH];
|
|
char savefile_dir[DIR_MAX_LENGTH];
|
|
char savestate_dir[DIR_MAX_LENGTH];
|
|
char current_library_name[NAME_MAX_LENGTH];
|
|
char current_valid_extensions[256];
|
|
char subsystem_path[256];
|
|
char current_library_version[64];
|
|
|
|
struct
|
|
{
|
|
/* TODO/FIXME: Same type for all */
|
|
char *remapfile;
|
|
char label [PATH_MAX_LENGTH];
|
|
char savefile [PATH_MAX_LENGTH];
|
|
char savestate[PATH_MAX_LENGTH];
|
|
char replay [PATH_MAX_LENGTH];
|
|
char cheatfile[PATH_MAX_LENGTH];
|
|
char ups [PATH_MAX_LENGTH];
|
|
char bps [PATH_MAX_LENGTH];
|
|
char ips [PATH_MAX_LENGTH];
|
|
char xdelta [PATH_MAX_LENGTH];
|
|
} name;
|
|
|
|
bool perfcnt_enable;
|
|
bool paused_hotkey;
|
|
|
|
/* True from the moment closing content starts tearing the core
|
|
* down until the teardown is finished.
|
|
*
|
|
* Set and cleared around the existing synchronous teardown, so at
|
|
* present nothing can observe it as true: the main thread is
|
|
* inside that teardown for its whole duration and no frame runs.
|
|
* It is introduced separately, and deliberately inert, because
|
|
* the work that makes it observable - returning to the frame loop
|
|
* instead of blocking - is a lifecycle change, and this is the
|
|
* piece everything else will key off.
|
|
*
|
|
* A plain bool rather than a RUNLOOP_FLAG bit: bits 0-30 of that
|
|
* word are taken and bit 31 was deliberately vacated to avoid a
|
|
* cross-thread race, so reusing it would undo that reasoning for
|
|
* no gain. This is main-thread only. */
|
|
bool content_closing;
|
|
};
|
|
|
|
/* Frame pacing sources.
|
|
*
|
|
* The loop is held to a rate by whichever of these block on a given
|
|
* frame. They are not exclusive and they are not prioritised: VSync
|
|
* blocks in the video driver's present, audio backpressure blocks in
|
|
* audio_driver_write(), Scanline Sync waits in
|
|
* video_driver_scanline_after_frame(), and the frame-limit timer sleeps
|
|
* at the end of runloop_iterate(). Any combination can be live on the
|
|
* same frame, which is why VSync together with audio_sync stutters -
|
|
* two hardware clocks holding the same loop - and why the comment at
|
|
* the RUNLOOP_STATE_MENU throttle special-cases audio backpressure
|
|
* against the timer.
|
|
*
|
|
* runloop_state_t::pace records the composition for the current
|
|
* frame, computed once in runloop_pace_compute() from the same
|
|
* conditions each mechanism already tests. It does not yet choose
|
|
* between them: this is the composition made explicit, so that a
|
|
* priority can be decided in one place later rather than by adding a
|
|
* fourth special case in a third file. A bitmask rather than an enum
|
|
* for exactly that reason - a single value would assert a choice the
|
|
* loop does not currently make. */
|
|
enum runloop_pace_source
|
|
{
|
|
RUNLOOP_PACE_NONE = 0,
|
|
RUNLOOP_PACE_VSYNC = (1 << 0), /* display: present blocks */
|
|
RUNLOOP_PACE_AUDIO = (1 << 1), /* audio crystal: write blocks */
|
|
RUNLOOP_PACE_SCANLINE = (1 << 2), /* display: vblank-locked wait */
|
|
RUNLOOP_PACE_TIMER = (1 << 3), /* CPU counter: frame-limit sleep */
|
|
RUNLOOP_PACE_NOWINDOW = (1 << 4), /* nothing to present to: wait */
|
|
/* (1 << 5) was RUNLOOP_PACE_EXTERNAL, a host clock driving
|
|
* runloop_iterate() from a Win32 modal loop; that pump was removed
|
|
* in 7fbc868499 and nothing else ever set it. Left vacant. */
|
|
/* Threaded video's display pacing: the frame handover holds this
|
|
* thread until the core's next run is due, from the presenter's
|
|
* next vblank and the measured core and render times. It is the
|
|
* loop's pace whenever it is on, and it needs the gap limiter to
|
|
* stay out: a timer anchored on the last frame fires first, the
|
|
* core then runs on the timer's schedule, the handover measures a
|
|
* full period of "core time", reserves all of it, and holds for
|
|
* nothing - which is what happened, and read as "Pacing: Timer" with
|
|
* display pacing on. */
|
|
RUNLOOP_PACE_DISPLAY = (1 << 6)
|
|
};
|
|
|
|
/* The three pacing decisions the runloop makes every iteration, here
|
|
* rather than in runloop.c so samples/runloop/pacing can run the
|
|
* shipping versions instead of a copy that drifts from them. Each is
|
|
* pure: no state, no clock, nothing to mock. */
|
|
|
|
/* One frame of the content's own time, in microseconds. Bounded either
|
|
* way: a core is free to report a nonsense rate, and neither a busy
|
|
* loop nor a ten-second stall is a reasonable reading of one frame.
|
|
* A rate of zero means "unknown", which is taken as 60 Hz. */
|
|
static INLINE retro_time_t runloop_content_frame_time_us(float core_hz)
|
|
{
|
|
retro_time_t period = (core_hz > 0.0f)
|
|
? (retro_time_t)(1000000.0f / core_hz) : 16667;
|
|
if (period < 1000)
|
|
return 1000;
|
|
if (period > 100000)
|
|
return 100000;
|
|
return period;
|
|
}
|
|
|
|
/* Whether the frame limiter should hold the loop to the display rate
|
|
* because nothing else is: an empty pace record means no vsync, no
|
|
* audio, no scanline lock, and no fast-forward limit to fall back on.
|
|
* With audio rate control, whose resampler follows the loop so the
|
|
* loop can follow the display; or with Scanline Sync enabled, whose
|
|
* record bit clears while it recalibrates and which is aiming at the
|
|
* display rate anyway, so the timer bridges the recalibration rather
|
|
* than fighting it. Otherwise the content rate is what Sync to Exact
|
|
* Content Framerate is for, and the loop runs unlimited as
|
|
* configured. Never under fast-forward, where running unthrottled is
|
|
* the point. */
|
|
static INLINE bool runloop_pace_gap_engages(unsigned pace,
|
|
bool nonblocking, bool fastmotion, bool scanline_sync,
|
|
bool rate_control)
|
|
{
|
|
return (pace == RUNLOOP_PACE_NONE)
|
|
&& !nonblocking && !fastmotion && (scanline_sync || rate_control);
|
|
}
|
|
|
|
/* One frame of the content's own time, in nanoseconds, with the same
|
|
* bounds as runloop_content_frame_time_us(). */
|
|
static INLINE int64_t runloop_content_frame_time_ns(float core_hz)
|
|
{
|
|
int64_t period = (core_hz > 0.0f)
|
|
? (int64_t)(1000000000.0 / (double)core_hz) : 16666667;
|
|
if (period < 1000000)
|
|
return 1000000;
|
|
if (period > 100000000)
|
|
return 100000000;
|
|
return period;
|
|
}
|
|
|
|
/* The gap limiter's schedule, in nanoseconds so that a period that is
|
|
* not a whole number of microseconds - 16683.35 us at 59.94 Hz - is
|
|
* kept exactly over any span. @anchor_ns is where the last frame was
|
|
* due; the next is due a period after it. Returns the microseconds
|
|
* until then, rounded up, or 0 when it has passed, and moves
|
|
* @anchor_ns to the slot the frame is taken as filling: the due time
|
|
* when on time or late by less than a period, so the lateness is caught
|
|
* up on the next frame rather than kept; now when late by a period or
|
|
* more, a stall, from which the schedule restarts rather than chases.
|
|
* The caller spins to @anchor_ns / 1000 after the sleep. */
|
|
static INLINE retro_time_t runloop_pace_schedule(int64_t *anchor_ns,
|
|
int64_t period_ns, retro_time_t now_us)
|
|
{
|
|
int64_t now = (int64_t)now_us * 1000;
|
|
int64_t due = *anchor_ns + period_ns;
|
|
if (now < due)
|
|
{
|
|
*anchor_ns = due;
|
|
return (retro_time_t)((due - now + 999) / 1000);
|
|
}
|
|
if (now - due < period_ns)
|
|
{
|
|
*anchor_ns = due;
|
|
return 0;
|
|
}
|
|
*anchor_ns = now;
|
|
return 0;
|
|
}
|
|
|
|
/* The gap limiter's sleep margin: how early the sleep is asked to
|
|
* return, so the remainder can be spun to the deadline. It tracks the
|
|
* sleep's observed overshoot - up at once, since one late sleep is a
|
|
* frame late; down by a sixteenth a frame, so a single quiet sleep does
|
|
* not unwind it - and never past a quarter of the period, so the spin
|
|
* stays a fraction of the frame. */
|
|
static INLINE retro_time_t runloop_pace_margin_update(retro_time_t margin,
|
|
retro_time_t overshoot, retro_time_t period)
|
|
{
|
|
if (overshoot < 0)
|
|
overshoot = 0;
|
|
if (overshoot > margin)
|
|
margin = overshoot;
|
|
else
|
|
margin -= (margin - overshoot) / 16;
|
|
if (margin > period / 4)
|
|
margin = period / 4;
|
|
return margin;
|
|
}
|
|
|
|
/* Whether an interval between iterations is worth averaging into the
|
|
* measured loop rate. Anything past a quarter second is a stall - a
|
|
* state load, a shader rebuild - not pacing, and one of them drags an
|
|
* eight-sample average far enough to misreport for several frames.
|
|
* Nothing that genuinely holds the loop runs slower than 4 Hz. */
|
|
static INLINE bool runloop_pace_sample_usable(retro_time_t delta_us)
|
|
{
|
|
return delta_us > 0 && delta_us < 250000;
|
|
}
|
|
|
|
/* Everything the pace decision reads, gathered once per iteration into
|
|
* one word. Each bit is one fact about this iteration, named for what
|
|
* it means rather than where it lives: fast-forward is NONBLOCKING
|
|
* (the input flag the drivers follow) and FASTMOTION (the runloop's
|
|
* own flag) because the two differ for a few frames while fast-forward
|
|
* engages, and the sources test different ones. */
|
|
enum runloop_pace_fact
|
|
{
|
|
PACE_FACT_VSYNC = (1 << 0), /* the vsync setting */
|
|
PACE_FACT_NONBLOCKING = (1 << 1), /* INP_FLAG_NONBLOCKING */
|
|
PACE_FACT_FORCE_NONBLOCK = (1 << 2), /* RUNLOOP_FLAG_FORCE_NONBLOCK */
|
|
PACE_FACT_FASTMOTION = (1 << 3), /* RUNLOOP_FLAG_FASTMOTION */
|
|
PACE_FACT_PAUSED = (1 << 4), /* RUNLOOP_FLAG_PAUSED */
|
|
PACE_FACT_FOCUSED = (1 << 5), /* RUNLOOP_FLAG_FOCUSED */
|
|
PACE_FACT_MENU_ALIVE = (1 << 6),
|
|
PACE_FACT_MENU_EARLY_EXIT = (1 << 7), /* VRR on, menu throttle off: the
|
|
menu path returns before the
|
|
pace block */
|
|
PACE_FACT_VRR = (1 << 8), /* Sync to Exact Content Framerate */
|
|
PACE_FACT_WRAPPER = (1 << 9), /* threaded video wrapper installed */
|
|
PACE_FACT_DISPLAY_PACING = (1 << 10), /* Threaded Video Display Pacing */
|
|
PACE_FACT_AUDIO_HOLDING = (1 << 11), /* audio active, blocking, and
|
|
wrote this iteration */
|
|
PACE_FACT_SCANLINE_SYNC = (1 << 12), /* the setting */
|
|
PACE_FACT_SCANLINE_LOCKED = (1 << 13), /* a target scanline exists */
|
|
PACE_FACT_RATE_CONTROL = (1 << 14), /* audio rate control */
|
|
PACE_FACT_PRESENTABLE = (1 << 15), /* the context has a surface */
|
|
PACE_FACT_FRAME_LIMIT = (1 << 17) /* frame_limit_minimum_time != 0 */
|
|
};
|
|
|
|
typedef unsigned runloop_pace_facts_t;
|
|
|
|
/* The pace decision as one pure function of those facts: which sources
|
|
* hold the loop this iteration, as RUNLOOP_PACE_* bits. It reproduces,
|
|
* bit for bit, what runloop_iterate() decides across its paths - the
|
|
* menu path's early return included - so that the decision can be
|
|
* tested as a table (samples/runloop/pacing) and, once the paths defer
|
|
* to it, made in one place. Until then runloop_iterate() computes
|
|
* both and asserts they agree in debug builds. */
|
|
/* The sources: everything but the no-window wait and the gap limiter,
|
|
* which the caller applies after, because each has an effect beyond
|
|
* the bits - the wait ends the iteration, the gap limiter sets the
|
|
* period the timer sleeps to. */
|
|
static INLINE unsigned runloop_pace_sources(runloop_pace_facts_t f)
|
|
{
|
|
unsigned pace = RUNLOOP_PACE_NONE;
|
|
bool vsync_holds = (f & PACE_FACT_VSYNC)
|
|
&& !(f & PACE_FACT_NONBLOCKING)
|
|
&& !(f & PACE_FACT_FORCE_NONBLOCK);
|
|
|
|
/* The menu path's early return: vsync if it is blocking, nothing
|
|
* else, and no timer. */
|
|
if ((f & PACE_FACT_MENU_ALIVE) && (f & PACE_FACT_MENU_EARLY_EXIT))
|
|
return vsync_holds ? RUNLOOP_PACE_VSYNC : RUNLOOP_PACE_NONE;
|
|
|
|
if (vsync_holds)
|
|
pace |= RUNLOOP_PACE_VSYNC;
|
|
if ((f & PACE_FACT_WRAPPER) && (f & PACE_FACT_DISPLAY_PACING)
|
|
&& !(f & PACE_FACT_NONBLOCKING) && !(f & PACE_FACT_FASTMOTION))
|
|
pace |= RUNLOOP_PACE_DISPLAY;
|
|
if (f & PACE_FACT_AUDIO_HOLDING)
|
|
pace |= RUNLOOP_PACE_AUDIO;
|
|
if ( (f & PACE_FACT_SCANLINE_SYNC) && (f & PACE_FACT_SCANLINE_LOCKED)
|
|
&& !(f & PACE_FACT_NONBLOCKING))
|
|
pace |= RUNLOOP_PACE_SCANLINE;
|
|
{
|
|
bool display_paces = (pace & RUNLOOP_PACE_DISPLAY) != 0;
|
|
if ((f & PACE_FACT_FRAME_LIMIT)
|
|
&& ( (f & PACE_FACT_VRR)
|
|
|| (f & PACE_FACT_FASTMOTION)
|
|
|| (!display_paces && (f & PACE_FACT_MENU_ALIVE)
|
|
&& (!(f & PACE_FACT_VSYNC) || !(f & PACE_FACT_FOCUSED)))
|
|
|| (!display_paces && (f & PACE_FACT_PAUSED))))
|
|
pace |= RUNLOOP_PACE_TIMER;
|
|
}
|
|
return pace;
|
|
}
|
|
|
|
/* Whether nothing display-side can hold the loop because there is
|
|
* nothing to present to; the caller waits a frame and returns. */
|
|
static INLINE bool runloop_pace_no_window(unsigned sources,
|
|
runloop_pace_facts_t f)
|
|
{
|
|
return !(sources & (RUNLOOP_PACE_VSYNC | RUNLOOP_PACE_AUDIO
|
|
| RUNLOOP_PACE_SCANLINE | RUNLOOP_PACE_TIMER))
|
|
&& !(f & PACE_FACT_NONBLOCKING) && !(f & PACE_FACT_PRESENTABLE);
|
|
}
|
|
|
|
/* The whole decision, for the table and for anything that only needs
|
|
* the bits. */
|
|
static INLINE unsigned runloop_pace_decide(runloop_pace_facts_t f)
|
|
{
|
|
unsigned pace = runloop_pace_sources(f);
|
|
if ((f & PACE_FACT_MENU_ALIVE) && (f & PACE_FACT_MENU_EARLY_EXIT))
|
|
return pace;
|
|
if (runloop_pace_no_window(pace, f))
|
|
return pace | RUNLOOP_PACE_NOWINDOW;
|
|
if (runloop_pace_gap_engages(pace,
|
|
(f & PACE_FACT_NONBLOCKING) != 0,
|
|
(f & PACE_FACT_FASTMOTION) != 0,
|
|
(f & PACE_FACT_SCANLINE_SYNC) != 0,
|
|
(f & PACE_FACT_RATE_CONTROL) != 0))
|
|
pace |= RUNLOOP_PACE_TIMER;
|
|
return pace;
|
|
}
|
|
|
|
|
|
typedef struct runloop runloop_state_t;
|
|
|
|
RETRO_BEGIN_DECLS
|
|
|
|
void runloop_path_fill_names(void);
|
|
|
|
/**
|
|
* runloop_environment_cb:
|
|
* @cmd : Identifier of command.
|
|
* @data : Pointer to data.
|
|
*
|
|
* Environment callback function implementation.
|
|
*
|
|
* Returns: true (1) if environment callback command could
|
|
* be performed, otherwise false (0).
|
|
**/
|
|
bool runloop_environment_cb(unsigned cmd, void *data);
|
|
|
|
void runloop_msg_queue_push(const char *msg, size_t len,
|
|
unsigned prio, unsigned duration,
|
|
bool flush,
|
|
char *title,
|
|
enum message_queue_icon icon,
|
|
enum message_queue_category category);
|
|
|
|
void runloop_set_current_core_type(
|
|
enum rarch_core_type type, bool explicitly_set);
|
|
|
|
/**
|
|
* runloop_iterate:
|
|
*
|
|
* Run Libretro core in RetroArch for one frame.
|
|
*
|
|
* Returns: 0 on successful run,
|
|
* Returns 1 if we have to wait until button input in order
|
|
* to wake up the loop.
|
|
* Returns -1 if we forcibly quit out of the
|
|
* RetroArch iteration loop.
|
|
**/
|
|
int runloop_iterate(void);
|
|
|
|
void runloop_system_info_free(void);
|
|
|
|
/**
|
|
* libretro_get_system_info:
|
|
* @path : Path to libretro library.
|
|
* @info : Pointer to system info information.
|
|
* @load_no_content : If true, core should be able to auto-start
|
|
* without any content loaded.
|
|
*
|
|
* Gets system info from an arbitrary lib.
|
|
* The struct returned must be freed as strings are allocated dynamically.
|
|
*
|
|
* Returns: true (1) if successful, otherwise false (0).
|
|
**/
|
|
bool libretro_get_system_info(
|
|
const char *path,
|
|
struct retro_system_info *info,
|
|
bool *load_no_content);
|
|
|
|
void runloop_performance_counter_register(
|
|
struct retro_perf_counter *perf);
|
|
|
|
void runloop_runtime_log_deinit(
|
|
runloop_state_t *runloop_st,
|
|
bool content_runtime_log,
|
|
bool content_runtime_log_aggregate,
|
|
const char *dir_runtime_log,
|
|
const char *dir_playlist);
|
|
|
|
void runloop_event_deinit_core(void);
|
|
|
|
bool runloop_event_init_core(
|
|
settings_t *settings,
|
|
void *input_data,
|
|
enum rarch_core_type type,
|
|
const char *old_savefile_dir,
|
|
const char *old_savestate_dir
|
|
);
|
|
|
|
void runloop_pause_checks(void);
|
|
|
|
/* The sources that held the loop on the last iteration, as the
|
|
* statistics overlay names them - "VSync+Display", "Timer", "None" -
|
|
* and, when a rate has been measured, " (59.9 fps)" after it. One
|
|
* string for the overlay and for System Information, so the menu can
|
|
* say what paces it where the overlay does not draw. */
|
|
size_t runloop_pace_string(char *s, size_t len);
|
|
|
|
void runloop_set_frame_limit(
|
|
const struct retro_system_av_info *av_info,
|
|
float fastforward_ratio);
|
|
|
|
float runloop_get_fastforward_ratio(
|
|
settings_t *settings,
|
|
struct retro_fastforwarding_override *fastmotion_override);
|
|
|
|
void runloop_set_video_swap_interval(
|
|
settings_t *settings);
|
|
|
|
unsigned runloop_get_video_swap_interval(
|
|
unsigned swap_interval_config);
|
|
|
|
void runloop_task_msg_queue_push(
|
|
retro_task_t *task, const char *msg,
|
|
unsigned prio, unsigned duration,
|
|
bool flush);
|
|
|
|
/* Status of the asynchronous secondary-core binary copy. Only
|
|
* RUNAHEAD_COPY_READY means the secondary instance exists and its
|
|
* function pointers are safe to call; PENDING means the copy task
|
|
* is still running (callers should skip quietly and retry later,
|
|
* NOT tear the secondary state down). */
|
|
enum runahead_copy_status
|
|
{
|
|
RUNAHEAD_COPY_UNAVAILABLE = 0,
|
|
RUNAHEAD_COPY_PENDING,
|
|
RUNAHEAD_COPY_READY
|
|
};
|
|
|
|
enum runahead_copy_status secondary_core_ensure_exists(void *data,
|
|
settings_t *settings);
|
|
|
|
void runloop_log_counters(
|
|
struct retro_perf_counter **counters, unsigned num);
|
|
|
|
void runloop_msg_queue_deinit(void);
|
|
|
|
void runloop_msg_queue_init(void);
|
|
|
|
void runloop_path_set_basename(const char *path);
|
|
|
|
void runloop_path_set_names(void);
|
|
|
|
uint32_t runloop_get_flags(void);
|
|
|
|
bool runloop_get_entry_state_path(char *path, size_t len, int slot);
|
|
|
|
bool runloop_get_current_savestate_path(char *path, size_t len);
|
|
|
|
bool runloop_get_savestate_path(char *path, size_t len, int slot);
|
|
|
|
bool runloop_get_replay_path(char *path, size_t len, int slot);
|
|
|
|
void runloop_state_free(runloop_state_t *runloop_st);
|
|
|
|
void runloop_path_set_redirect(settings_t *settings, const char *a, const char *b);
|
|
|
|
void runloop_path_set_special(char **argv, unsigned num_content);
|
|
|
|
void runloop_path_deinit_subsystem(void);
|
|
|
|
/**
|
|
* init_libretro_symbols:
|
|
* @type : Type of core to be loaded.
|
|
* If CORE_TYPE_DUMMY, will
|
|
* load dummy symbols.
|
|
*
|
|
* Setup libretro callback symbols.
|
|
*
|
|
* @return true on success, or false if symbols could not be loaded.
|
|
**/
|
|
bool runloop_init_libretro_symbols(
|
|
void *data,
|
|
enum rarch_core_type type,
|
|
struct retro_core_t *current_core,
|
|
const char *lib_path,
|
|
void *_lib_handle_p);
|
|
|
|
runloop_state_t *runloop_state_get_ptr(void);
|
|
|
|
/**
|
|
* runloop_is_content_closing:
|
|
*
|
|
* True while content is being closed, i.e. while the core is being
|
|
* torn down. Currently only ever true inside the synchronous
|
|
* teardown, where nothing else runs to ask.
|
|
*/
|
|
bool runloop_is_content_closing(void);
|
|
|
|
RETRO_END_DECLS
|
|
|
|
#endif
|