-
-
Notifications
You must be signed in to change notification settings - Fork 574
[libretro] Reverting back to the use of clock() #2844
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
clock() was previously replaced by frame time callback (1c8eb84 nesbox#2589) It introduced a few bugs that I tried to fix (nesbox#2829) But there is a [benchmark cartridge](https://tic80.com/play?cart=153) that calls time() to mesure itra-frame timing. This is how Gemini explains it: The situation involves two different timing requirements: Inter-Frame Timing (Game Speed): This is about ensuring the game logic advances correctly from one frame to the next. If 1/60th of a second has passed in the real world, the game should also advance its state by 1/60th of a second. The original fix (`state->frameTime += usec;`) solved this by correctly accumulating the time between frames. Intra-Frame Timing (Benchmark Measurement): This is what the benchmark cartridge needs. It uses TIC-80's `time()` function to measure how long an operation takes within a single frame. It records the start time, does a lot of drawing, records the end time, and calculates the difference. The original fix failed the benchmark test because the `state->frameTime` value is only updated once per frame. For the entire duration of the `MAINTIC()` function, the value returned by `time()` would be constant. Therefore, `time() - stime` would always be zero. -- The clock() solution is superior because it provides a high-resolution, continuously updating timer directly from the system's C library. clock(): This standard function returns the amount of processor time used by the program. Crucially, this value increases during the execution of a single frame. CLOCKS_PER_SEC: This constant provides the frequency of the clock() timer. When the benchmark calls time() (`clock() / CLOCKS_PER_SEC`), it gets a precise timestamp. After it finishes its work and calls time() again, the value from clock() will have increased, yielding a correct, non-zero `runningTime`. This solves the intra-frame timing problem. Simultaneously, because clock() is a consistently increasing counter, the TIC-80 engine can use it to perfectly measure the duration between frames, solving the inter-frame timing problem as well.
|
@RobLoach, you might want to look at this. As this is reverting one of your commits. |
-DCMAKE_POLICY_VERSION_MINIMUM=3.5
|
Frame time callback is usually better since the front-end tries to provide the best time it can. Clock() may not be available in some systems. Also allows the frontend to manipulate the frames as desired. The front-end may even use clock() to get that frame time value. You can find some doc's around it over at https://github.com/libretro/RetroArch/blob/master/libretro-common/include/libretro.h
Haven't looked at this in a while, where is runTime used? Which frontend are you using? |
|
|
|
From the tick documentation: So if I see the advantages of callback would allow to speedup/slowdown, I will have to spend more time to understand how to implement it correctly. |
// tic80_libretro.c
static u64 tic80_libretro_counter()
{
if (state == NULL) {
return 0;
}
return (u64)state->frameTime;
}
// Set up the frame time callback.
struct retro_frame_time_callback frame_time = {
.callback = tic80_libretro_frame_time,
.reference = TIC80_FREQUENCY / TIC80_FRAMERATE,
};
environ_cb(RETRO_ENVIRONMENT_SET_FRAME_TIME_CALLBACK, &frame_time)
tic80_tick(game, state->input, tic80_libretro_counter, tic80_libretro_frequency);// core.c
// time() // can be called multiple times in the same frame
double tic_api_time(tic_mem* memory)
{
tic_core* core = (tic_core*)memory;
return (double)(core->data->counter(core->data->data) - core->data->start) * 1000.0 / core->data->freq(core->data->data);
}
// tic.c
TIC80_API void tic80_tick(tic80* tic, tic80_input input, CounterCallback counter, FreqCallback freq)
{
tic_mem* mem = (tic_mem*)tic;
mem->ram->input = input;
tic_tick_data tickData = (tic_tick_data)
{
.error = onError,
.trace = onTrace,
.exit = onExit,
.data = tic,
.start = 0,
.counter = counter,
.freq = freq
};
// ... |
I'm using the regular Retroarch, testing both on a desktop Linux and Android. I found an option we can use: /**
* Returns an interface that the core can use for profiling code
* and to access performance-related information.
*
* This callback supports performance counters, a high-resolution timer,
* and listing available CPU features (mostly SIMD instructions).
*
* @param[out] data <tt>struct retro_perf_callback *</tt>.
* Pointer to the callback interface.
* Behavior is undefined if \c NULL.
* @returns \c true if the environment call is available.
* @see retro_perf_callback
*/
#define RETRO_ENVIRONMENT_GET_PERF_INTERFACE 28
struct retro_perf_callback
{
/** @copydoc retro_perf_get_time_usec_t */
retro_perf_get_time_usec_t get_time_usec;
/** @copydoc retro_perf_get_counter_t */
retro_perf_get_counter_t get_perf_counter;
// ....
};
/**
* @returns The current system time in microseconds.
* @note Accuracy may vary by platform.
* The frontend should use the most accurate timer possible.
* @see RETRO_ENVIRONMENT_GET_PERF_INTERFACE
*/
typedef retro_time_t (RETRO_CALLCONV *retro_perf_get_time_usec_t)(void);
/**
* @returns The number of ticks since some unspecified epoch.
* The exact meaning of a "tick" depends on the platform,
* but it usually refers to nanoseconds or CPU cycles.
* @see RETRO_ENVIRONMENT_GET_PERF_INTERFACE
*/
typedef retro_perf_tick_t (RETRO_CALLCONV *retro_perf_get_counter_t)(void);I tested both |
|
It's not final yet, but I think I found a path. This is best tested on the edge case games listed here: #2820 (comment), those games are quite sensitive to the changes in the counter. |
|
api_time_debug.zip |
|
Wow that seems so much better. Nice find. Definitely is tricky to get it all right. |




clock()was previously replaced by frame time callback (1c8eb84 #2589)It introduced a few bugs that I tried to fix (#2829)
But there is a benchmark cartridge ( git:demos/benchmark.lua | tic80.com/play ) that calls time() to measure itra-frame timing.
It was broken using the current libretro core, it's
runTimewas stuck in zero.Reverting to the use of
clock()fixes this issue.Bellow is Gemini explanation:
The situation involves two different timing requirements:
Inter-Frame Timing (Game Speed): This is about ensuring the game logic advances correctly from one frame to the next. If 1/60th of a second has passed in the real world, the game should also advance its state by 1/60th of a second. The original fix (
state->frameTime += usec;) solved this by correctly accumulating the time between frames.Intra-Frame Timing (Benchmark Measurement): This is what the benchmark cartridge needs. It uses TIC-80's
time()function to measure how long an operation takes within a single frame. It records the start time, does a lot of drawing, records the end time, and calculates the difference.The original fix failed the benchmark test because the
state->frameTimevalue is only updated once per frame. For the entire duration of theMAINTIC()function, the value returned bytime()would be constant. Therefore,time() - stimewould always be zero.--
The
clock()solution is superior because it provides a high-resolution, continuously updating timer directly from the system's C library.clock(): This standard function returns the amount of processor time used by the program. Crucially, this value increases during the execution of a single frame.CLOCKS_PER_SEC: This constant provides the frequency of the clock() timer.When the benchmark calls time() (
clock() / CLOCKS_PER_SEC), it gets a precise timestamp. After it finishes its work and calls time() again, the value from clock() will have increased, yielding a correct, non-zerorunningTime. This solves the intra-frame timing problem.Simultaneously, because
clock()is a consistently increasing counter, the TIC-80 engine can use it to perfectly measure the duration between frames, solving the inter-frame timing problem as well.