mirror of
https://github.com/MiSTer-devel/Main_MiSTer.git
synced 2026-04-12 03:04:02 +00:00
* increase allowed autofire rates from 5 to 30. add autofire_on_directions option to mister.ini (defaults to 0/off) * simd rewrite of scaler memcopy * optimize screenshots / scaler copies * optimize screenshots; simd memcopy for scaler_read, add frame timer callback handler * tweaks * restore .png save functionality. * screenshot optimizations * fix double free, guard against NULL * fixing merge * add mister.ini for screenshot_image_format * correct file extension handling. * slightly tweak bmp routine * consolide screenshot code back into scaler.cpp, remove custom resize/bmp code.
167 lines
5.0 KiB
C++
167 lines
5.0 KiB
C++
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <time.h>
|
|
#include <sys/timerfd.h>
|
|
#include <sys/poll.h>
|
|
#include <unistd.h>
|
|
|
|
#include "user_io.h"
|
|
#include "frame_timer.h"
|
|
#include "video.h"
|
|
|
|
// frame timer used by autofire; call frame_timer() periodically and use FRAME_TICK().
|
|
// prefers the core's frame counter, otherwise falls back to timerfd.
|
|
|
|
#define SIXTYHERTZ 1668335 // fallback if vtime doesn't work; actually 59.94hz
|
|
#define FIFTYHERTZ 2000000 // lowest refresh rate we consider valid 50hz
|
|
#define SEVENTYFIVEHERTZ 1326260 // highest refresh rate we consider valid 75.4hz
|
|
|
|
// frame timer callbacks
|
|
static frame_callback_t frame_callbacks[MAX_FRAME_CALLBACKS];
|
|
static int frame_callback_count = 0;
|
|
|
|
uint64_t global_frame_counter = 0;
|
|
|
|
extern VideoInfo current_video_info; // from video.cpp
|
|
static bool timer_started = false;
|
|
static int vtimerfd = -1;
|
|
|
|
bool fpga_vsync_timer = false; // flag to indicate core provides frame counter.
|
|
|
|
// clamp vrefresh to 50-75Hz; otherwise use 60Hz.
|
|
static inline uint32_t get_vtime() {
|
|
uint32_t current_vtime = current_video_info.vtime;
|
|
// not a bug - a higher refresh rate in hz means a *smaller* vtime value in 10ns units.
|
|
if (current_vtime <= FIFTYHERTZ && current_vtime >= SEVENTYFIVEHERTZ) {
|
|
return current_vtime;
|
|
}
|
|
else {
|
|
return SIXTYHERTZ;
|
|
}
|
|
}
|
|
|
|
// return true if core vtime changes (resolution or user refresh adjustment).
|
|
static inline bool vtime_changed()
|
|
{
|
|
static uint32_t prev_vtime;
|
|
uint32_t current_vtime = get_vtime();
|
|
if (prev_vtime != current_vtime) {
|
|
if (vtimerfd >= 0) {
|
|
close(vtimerfd); // recycle timerfd
|
|
vtimerfd = -1;
|
|
}
|
|
printf("frame_timer(): vtime change detected, restarting timer.\n");
|
|
prev_vtime = current_vtime;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// initialize timerfd-based timer; returns 1 on failure.
|
|
int start_vtimer(uint64_t interval_ns) {
|
|
vtimerfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
|
|
if (vtimerfd < 0) {
|
|
perror("timerfd_create");
|
|
return 1;
|
|
};
|
|
|
|
// start timer at absolute CLOCK_MONOTONIC time.
|
|
// not to be confused with absolute batman.
|
|
struct timespec now_ts = {};
|
|
struct itimerspec its = {};
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &now_ts);
|
|
|
|
uint64_t now_ns = (uint64_t)now_ts.tv_sec * 1000000000ULL + now_ts.tv_nsec;
|
|
uint64_t first_expiration_ns = now_ns + interval_ns;
|
|
|
|
its.it_value.tv_sec = first_expiration_ns / 1000000000ULL;
|
|
its.it_value.tv_nsec = first_expiration_ns % 1000000000ULL;
|
|
|
|
its.it_interval.tv_sec = interval_ns / 1000000000ULL;
|
|
its.it_interval.tv_nsec = interval_ns % 1000000000ULL;
|
|
|
|
if (timerfd_settime(vtimerfd, TFD_TIMER_ABSTIME, &its, NULL) < 0) {
|
|
close(vtimerfd);
|
|
vtimerfd = -1;
|
|
printf("frame_timer(): timerfd setup failed, will retry.\n");
|
|
return 1;
|
|
}
|
|
float hz = 1e9f / interval_ns;
|
|
printf("frame_timer(): core does not offer framecounter. using timerfd.\n");
|
|
printf("%.2fhz timer started.\n", hz);
|
|
return 0;
|
|
}
|
|
|
|
// attempt timerfd setup; returns false on failure or zero interval.
|
|
static inline bool init_frame_timer(uint32_t interval)
|
|
{
|
|
uint64_t interval_ns = interval * 10ull;
|
|
if (!timer_started && interval_ns) {
|
|
if (start_vtimer(interval_ns) == 0)
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// return true if timer has expired, false otherwise.
|
|
static bool check_vtimer() {
|
|
uint64_t expirations = 0;
|
|
struct pollfd pfd = { vtimerfd, POLLIN, 0 };
|
|
|
|
if (poll(&pfd, 1, 0) <= 0)
|
|
return false; // timer not ready
|
|
|
|
ssize_t n = read(vtimerfd, &expirations, sizeof(expirations));
|
|
if (n != (ssize_t)sizeof(expirations) || expirations == 0)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
// prefer core framecounter; fallback to timerfd with minor long-term drift risk.
|
|
// call periodically (e.g., start of input_poll()).
|
|
void frame_timer() {
|
|
static uint32_t last_frame_count = 0;
|
|
|
|
// if core offers its own framecounter skip all the timerfd nonsense
|
|
uint32_t frcnt = spi_uio_cmd(UIO_GET_FR_CNT);
|
|
if (frcnt & 0x100) {
|
|
if (!fpga_vsync_timer) printf("frame_timer(): core offers framecounter.\n");
|
|
fpga_vsync_timer = true;
|
|
global_frame_counter = frcnt & 0xFF;
|
|
}
|
|
else { // fallback to timerfd
|
|
if (vtime_changed())
|
|
timer_started = false; // restart timers if vtime has changed;
|
|
uint32_t vtime = get_vtime();
|
|
if (!timer_started)
|
|
timer_started = init_frame_timer(vtime);
|
|
if (timer_started && check_vtimer())
|
|
global_frame_counter++;
|
|
}
|
|
|
|
// call frame callbacks if frame has advanced
|
|
if (global_frame_counter != last_frame_count) {
|
|
last_frame_count = global_frame_counter;
|
|
for (int i = 0; i < frame_callback_count; i++) {
|
|
frame_callbacks[i]();
|
|
}
|
|
}
|
|
}
|
|
|
|
// frametimer callbacks. deprecating FRAME_TICK() macro in favor of these.
|
|
int callback_already_registered(frame_callback_t cb) {
|
|
for (int i = 0; i < frame_callback_count; ++i) {
|
|
if (frame_callbacks[i] == cb) return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void add_frame_callback(frame_callback_t cb) {
|
|
if (!callback_already_registered(cb) && frame_callback_count < MAX_FRAME_CALLBACKS) {
|
|
frame_callbacks[frame_callback_count++] = cb;
|
|
}
|
|
} |