input: autofire overhaul (#1091)
* frame-synchronized autofire, per-button rates, custom rates via mister.ini * frame timer using core frame counter, fallback to timerfd * improved button reference counting (previously limited to 2)
This commit is contained in:
@@ -341,6 +341,10 @@ vrr_vesa_framerate=0
|
||||
; disable autofire if for some reason it's not required and accidentally triggered
|
||||
disable_autofire=0
|
||||
|
||||
; custom autofire rates (in hertz). up to five allowed.
|
||||
; can also use literal bit patterns for on/off cycles, i.e. 0b00111. each bit is one frame.
|
||||
;autofire_rates=10,0b00111,20,30
|
||||
|
||||
; Specify a default video processing preset that will be applied to cores.
|
||||
; Path is relative to the presets/ directory and can optionally include the .ini extension
|
||||
;preset_default=General Hardware/Console - 3rdGen
|
||||
|
||||
342
autofire.cpp
Normal file
342
autofire.cpp
Normal file
@@ -0,0 +1,342 @@
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <stdint.h>
|
||||
#include "input.h"
|
||||
#include "autofire.h"
|
||||
#include "cfg.h"
|
||||
|
||||
/*
|
||||
New autofire system.
|
||||
We take the desired autofire rate in hertz and convert that to a bitmask
|
||||
0 == button released
|
||||
1 == button pressed
|
||||
We advance through a single bit of this mask every frame on the assumption that
|
||||
most games read their inputs every vsync (or is it vrefresh? once per frame.)
|
||||
|
||||
We display autofire rates to the user as if the game is running at 60hz, but internally
|
||||
the rate is scaled to match the game's actual display refresh rate.
|
||||
|
||||
e.g. A PAL game (running at 50hz) with 30hz autofire will internally be toggling the button at 25hz
|
||||
actual_refresh / 60 * autofire_rate_hz == real_autofire_rate_hz
|
||||
*/
|
||||
|
||||
#define MAX_AF_CODES 16
|
||||
#define MAX_AF_RATES 6
|
||||
#define AF_NAME_LEN 32
|
||||
|
||||
// global autofire cycle data.
|
||||
struct AutofireData {
|
||||
char name[AF_NAME_LEN]; // display name
|
||||
uint64_t cycle_mask; // bitmask representing the autofire cycle
|
||||
int cycle_length; // length of the cycle in frames
|
||||
int bit; // current bit in the cycle
|
||||
int frame_count; // current frame in the cycle
|
||||
};
|
||||
|
||||
// per-player autofire table; index 0 means disabled.
|
||||
struct AutofireTable {
|
||||
int count; // number of entries
|
||||
int index[MAX_AF_CODES]; // index of matching autofire rate in autofiredata[]
|
||||
uint32_t mask[MAX_AF_CODES]; // bitmask representing which buttons this code represents
|
||||
uint32_t autofirecodes[MAX_AF_CODES]; // codes that have autofire set (or codes that had it set, then disabled)
|
||||
};
|
||||
|
||||
static struct AutofireTable autofiretable[NUMPLAYERS]; // tracks autofire state per key per player
|
||||
static struct AutofireData autofiredata[MAX_AF_RATES]; // global autofire rates/masks/etc.
|
||||
static struct AutofireData autofiredata_default[MAX_AF_RATES]; // hardcoded fallback rates/masks/etc.
|
||||
static int num_af_rates_default = 0;
|
||||
static int num_af_rates = 0;
|
||||
|
||||
static void set_autofire_name(struct AutofireData *data, const char *base_name) {
|
||||
float hz = data->cycle_length > 0 ? (60.0f / data->cycle_length) : 0.0f;
|
||||
if (base_name && base_name[0]) {
|
||||
snprintf(data->name, sizeof(data->name), "%s (%.1fhz)", base_name, hz);
|
||||
} else {
|
||||
snprintf(data->name, sizeof(data->name), "%.1fhz", hz);
|
||||
}
|
||||
}
|
||||
|
||||
// returns rate index for code, or 0 if autofire is disabled/unset.
|
||||
int get_autofire_code_idx(int player, uint32_t code) {
|
||||
for (int i = 0; i != autofiretable[player].count; i++)
|
||||
{
|
||||
if (autofiretable[player].autofirecodes[i] == code)
|
||||
return autofiretable[player].index[i];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// autofire structs are private to this unit, so we offer a helper to clear them
|
||||
void clear_autofire(int player) {
|
||||
memset(&autofiretable[player], 0, sizeof(AutofireTable));
|
||||
}
|
||||
|
||||
// set autofire index for a code: >0 enable, 0 disable.
|
||||
void set_autofire_code(int player, uint32_t code, uint32_t mask, int index) {
|
||||
for (int i = 0; i != autofiretable[player].count; i++) {
|
||||
if (autofiretable[player].autofirecodes[i] == code) {
|
||||
autofiretable[player].index[i] = index;
|
||||
autofiretable[player].mask[i] = mask;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// TODO implement compactification if we run out of slots.
|
||||
// presently if user enables/disables too many codes we just stop adding new ones.
|
||||
// represented by MAX_AF_CODES.
|
||||
if (autofiretable[player].count < MAX_AF_CODES) {
|
||||
int idx = autofiretable[player].count++;
|
||||
autofiretable[player].autofirecodes[idx] = code;
|
||||
autofiretable[player].index[idx] = index;
|
||||
autofiretable[player].mask[idx] = mask;
|
||||
}
|
||||
}
|
||||
|
||||
// step autofire rate; wrap to disabled at max.
|
||||
void inc_autofire_code(int player, uint32_t code, uint32_t mask) {
|
||||
int index = get_autofire_code_idx(player, code) + 1;
|
||||
if (index <= 0) index = 1;
|
||||
if (index >= num_af_rates || index < 0) index = 0;
|
||||
set_autofire_code(player, code, mask, index);
|
||||
}
|
||||
|
||||
// advance all autofire patterns (run once per frame)
|
||||
void autofire_tick() {
|
||||
for (int i = 1; i < num_af_rates; i++)
|
||||
{
|
||||
autofiredata[i].bit = (autofiredata[i].cycle_mask >> autofiredata[i].frame_count) & 1u;
|
||||
if (++(autofiredata[i].frame_count) >= autofiredata[i].cycle_length)
|
||||
autofiredata[i].frame_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// returns whether the buttons for this code should be held or released this frame.
|
||||
// (updated every time we call autofire_tick)
|
||||
bool get_autofire_bit(int player, uint32_t code) {
|
||||
int rate_idx = get_autofire_code_idx(player, code);
|
||||
if (rate_idx > 0) {
|
||||
return autofiredata[rate_idx].bit;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// display-only rate lookup for ui.
|
||||
const char *get_autofire_rate_hz(int player, uint32_t code) {
|
||||
int rate_idx = get_autofire_code_idx(player, code);
|
||||
if (rate_idx <= 0) {
|
||||
return "disabled";
|
||||
}
|
||||
if (autofiredata[rate_idx].name[0]) {
|
||||
return autofiredata[rate_idx].name;
|
||||
}
|
||||
return "disabled";
|
||||
}
|
||||
|
||||
bool is_autofire_enabled(int player, uint32_t code) {
|
||||
return get_autofire_code_idx(player, code) > 0;
|
||||
}
|
||||
|
||||
/* autofire configuration parsing/loading */
|
||||
|
||||
// we accept strings as input so of course this code is about
|
||||
// 500x longer and more complicated than the actual autofire code
|
||||
|
||||
// autofire config parsing lives here to keep cfg.cpp simple.
|
||||
// accepts comma-separated float rates, or 0b patterns for custom cycles.
|
||||
// example: "5.0,10.0,0b11001100,15.0".
|
||||
// extremely long custom patterns could be used to simulate hold/release
|
||||
|
||||
// some arcade shooters have pretty odd optimal autofire patterns to manage rank
|
||||
// let's hide them here for my shmup buddies
|
||||
static const struct AutofireData autofire_patterns[] = {
|
||||
{ "GUNFRONTIER", 0b111100000ULL, 9, 0, 0 },
|
||||
{ "GAREGGA", 0b1110000ULL, 7, 0, 0 },
|
||||
};
|
||||
|
||||
// helper for formatting binary literal patterns.
|
||||
static inline const char *bits_to_str(uint64_t value, int cycle_length) {
|
||||
static char buf[65]; // 64 bits + null
|
||||
int pos = 0;
|
||||
int max_len = cycle_length;
|
||||
if (max_len < 0) max_len = 0;
|
||||
if (max_len > 64) max_len = 64;
|
||||
|
||||
for (int i = max_len - 1; i >= 0; i--)
|
||||
buf[pos++] = (value & (1ULL << i)) ? '1' : '0';
|
||||
|
||||
buf[pos] = '\0';
|
||||
return buf;
|
||||
}
|
||||
|
||||
// slot 0 is always "autofire disabled" initialize defensively
|
||||
static void init_autofire_entry(struct AutofireData *data, uint64_t mask, int length,
|
||||
const char *name) {
|
||||
if (name) {
|
||||
snprintf(data->name, sizeof(data->name), "%s", name);
|
||||
} else {
|
||||
data->name[0] = '\0';
|
||||
}
|
||||
data->cycle_length = length;
|
||||
data->cycle_mask = mask;
|
||||
if (data->cycle_length > 64) data->cycle_length = 64;
|
||||
if (data->cycle_length < 1) data->cycle_length = 1;
|
||||
data->frame_count = 0;
|
||||
data->bit = (data->cycle_mask & 1ULL) ? 1 : 0;
|
||||
}
|
||||
|
||||
static inline struct AutofireData mask_from_hertz(double hz_target)
|
||||
{
|
||||
struct AutofireData p = {{0}, 0, 0, 0, 0};
|
||||
|
||||
if (hz_target <= 0.0) return p;
|
||||
if (hz_target > 30.0) hz_target = 30.0;
|
||||
|
||||
int P = (int)ceil(60.0 / hz_target);
|
||||
if (P < 2) P = 2;
|
||||
if (P > 64) P = 64;
|
||||
|
||||
p.cycle_length = P;
|
||||
|
||||
int W = P / 2;
|
||||
if (W < 1) W = 1;
|
||||
if (W >= P) W = P - 1;
|
||||
|
||||
if (W == 64)
|
||||
p.cycle_mask = ~0ull;
|
||||
else
|
||||
p.cycle_mask = (1ull << W) - 1ull;
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
static void init_default_autofire_data() {
|
||||
const float default_rates[] = { 10.0f, 15.0f, 30.0f };
|
||||
int count = 0;
|
||||
|
||||
init_autofire_entry(&autofiredata_default[count++], 1, 1, NULL);
|
||||
|
||||
for (size_t i = 0; i < (sizeof(default_rates) / sizeof(default_rates[0])); i++) {
|
||||
if (count >= MAX_AF_RATES) break;
|
||||
struct AutofireData p = mask_from_hertz(default_rates[i]);
|
||||
init_autofire_entry(&autofiredata_default[count], p.cycle_mask,
|
||||
p.cycle_length ? p.cycle_length : 1, NULL);
|
||||
set_autofire_name(&autofiredata_default[count], NULL);
|
||||
count++;
|
||||
}
|
||||
num_af_rates_default = count;
|
||||
}
|
||||
|
||||
// parse a 0b... binary pattern; every digit is a frame, up to 64
|
||||
// some interesting stuff could be done with longer patterns (hold/release)
|
||||
static bool parse_autofire_literal(const char *token, uint64_t *mask_out, int *len_out) {
|
||||
if (!token || token[0] != '0' || !token[1]) return false;
|
||||
|
||||
const char *p = token + 2;
|
||||
uint64_t mask = 0;
|
||||
int len = 0;
|
||||
|
||||
if (token[1] != 'b' && token[1] != 'B') return false;
|
||||
if (!*p) return false;
|
||||
|
||||
for (const char *c = p; *c; c++) {
|
||||
if (*c != '0' && *c != '1') return false;
|
||||
if (len >= 64) return false;
|
||||
mask = (mask << 1) | (uint64_t)(*c - '0');
|
||||
len++;
|
||||
}
|
||||
|
||||
*mask_out = mask;
|
||||
*len_out = len;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void build_autofire_data(const char *rates, struct AutofireData *out, int *out_count) {
|
||||
char *token;
|
||||
int count = 0;
|
||||
char cfg_string[256] = {};
|
||||
|
||||
init_autofire_entry(&out[count++], 1, 1, NULL);
|
||||
|
||||
snprintf(cfg_string, sizeof(cfg_string), "%s", rates);
|
||||
|
||||
token = strtok(cfg_string, ",");
|
||||
while (token && count < MAX_AF_RATES) {
|
||||
bool handled = false;
|
||||
for (size_t i = 0; i < (sizeof(autofire_patterns) / sizeof(autofire_patterns[0])); i++) {
|
||||
if (!strcasecmp(token, autofire_patterns[i].name)) {
|
||||
init_autofire_entry(&out[count], autofire_patterns[i].cycle_mask,
|
||||
autofire_patterns[i].cycle_length, autofire_patterns[i].name);
|
||||
set_autofire_name(&out[count], autofire_patterns[i].name);
|
||||
count++;
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (handled) {
|
||||
token = strtok(NULL, ",");
|
||||
continue;
|
||||
}
|
||||
uint64_t literal_mask = 0;
|
||||
int literal_len = 0;
|
||||
if (parse_autofire_literal(token, &literal_mask, &literal_len)) {
|
||||
// use literal directly (allows arbitrary press/release cycles)
|
||||
init_autofire_entry(&out[count], literal_mask, literal_len, "custom");
|
||||
set_autofire_name(&out[count], "custom");
|
||||
count++;
|
||||
token = strtok(NULL, ",");
|
||||
continue;
|
||||
}
|
||||
// pass through to float processing
|
||||
char *endptr = NULL;
|
||||
float f = strtof(token, &endptr);
|
||||
|
||||
// reject 0.0 and values > 30.0
|
||||
if (endptr && endptr != token && *endptr == '\0' && f > 0.0f && f <= 30.0f) {
|
||||
struct AutofireData p = mask_from_hertz(f);
|
||||
init_autofire_entry(&out[count], p.cycle_mask,
|
||||
p.cycle_length ? p.cycle_length : 1, NULL);
|
||||
set_autofire_name(&out[count], NULL);
|
||||
count++;
|
||||
}
|
||||
token = strtok(NULL, ",");
|
||||
}
|
||||
*out_count = count;
|
||||
}
|
||||
|
||||
// parse config; fall back to defaults if no valid rates remain.
|
||||
// always returns true to indicate some rate set is loaded.
|
||||
bool parse_autofire_cfg() {
|
||||
printf("[AUTOFIRE INITIALIZATION]\n");
|
||||
static bool default_ready = false;
|
||||
if (!default_ready) {
|
||||
init_default_autofire_data();
|
||||
default_ready = true;
|
||||
}
|
||||
|
||||
struct AutofireData parsed[MAX_AF_RATES] = {};
|
||||
int valid_count = 0;
|
||||
build_autofire_data(cfg.autofire_rates, parsed, &valid_count);
|
||||
|
||||
if (valid_count > 1) {
|
||||
memcpy(autofiredata, parsed, sizeof(parsed));
|
||||
num_af_rates = valid_count;
|
||||
} else {
|
||||
memcpy(autofiredata, autofiredata_default, sizeof(autofiredata_default));
|
||||
num_af_rates = num_af_rates_default;
|
||||
}
|
||||
|
||||
if (valid_count <= 1) {
|
||||
printf("Autofire configuration in .ini invalid, using default rates:\n");
|
||||
}
|
||||
|
||||
printf("Number of autofire rates found: %d\n", num_af_rates - 1);
|
||||
for (int i = 1; i != num_af_rates; i++) {
|
||||
printf("%s, bitmask %s, cycle length %u\n",
|
||||
autofiredata[i].name[0] ? autofiredata[i].name : "custom",
|
||||
bits_to_str(autofiredata[i].cycle_mask, autofiredata[i].cycle_length),
|
||||
autofiredata[i].cycle_length);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
14
autofire.h
Normal file
14
autofire.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#ifndef AUTOFIRE_H
|
||||
#define AUTOFIRE_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
const char *get_autofire_rate_hz(int player, uint32_t code);
|
||||
bool is_autofire_enabled(int player, uint32_t code);
|
||||
void clear_autofire(int player);
|
||||
void inc_autofire_code(int player, uint32_t code, uint32_t mask);
|
||||
void autofire_tick();
|
||||
bool parse_autofire_cfg();
|
||||
bool get_autofire_bit(int player, uint32_t code);
|
||||
|
||||
#endif
|
||||
3
cfg.cpp
3
cfg.cpp
@@ -133,6 +133,8 @@ static const ini_var_t ini_vars[] =
|
||||
{ "LOOKAHEAD", (void *)(&(cfg.lookahead)), UINT8, 0, 3 },
|
||||
{ "MAIN", (void*)(&(cfg.main)), STRING, 0, sizeof(cfg.main) - 1 },
|
||||
{"VFILTER_INTERLACE_DEFAULT", (void*)(&(cfg.vfilter_interlace_default)), STRING, 0, sizeof(cfg.vfilter_interlace_default) - 1 },
|
||||
{ "AUTOFIRE_RATES", (void *)(&(cfg.autofire_rates)), STRING, 0, sizeof(cfg.autofire_rates) - 1 },
|
||||
|
||||
};
|
||||
|
||||
static const int nvars = (int)(sizeof(ini_vars) / sizeof(ini_var_t));
|
||||
@@ -595,6 +597,7 @@ void cfg_parse()
|
||||
has_video_sections = false;
|
||||
using_video_section = false;
|
||||
cfg_error_count = 0;
|
||||
strcpy(cfg.autofire_rates, "10,15,30");
|
||||
ini_parse(altcfg(), video_get_core_mode_name(1));
|
||||
if (has_video_sections && !using_video_section)
|
||||
{
|
||||
|
||||
2
cfg.h
2
cfg.h
@@ -101,6 +101,8 @@ typedef struct {
|
||||
uint8_t lookahead;
|
||||
char main[1024];
|
||||
char vfilter_interlace_default[1023];
|
||||
char autofire_rates[256];
|
||||
|
||||
} cfg_t;
|
||||
|
||||
extern cfg_t cfg;
|
||||
|
||||
139
frame_timer.cpp
Normal file
139
frame_timer.cpp
Normal file
@@ -0,0 +1,139 @@
|
||||
#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
|
||||
|
||||
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() {
|
||||
// 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++;
|
||||
}
|
||||
}
|
||||
18
frame_timer.h
Normal file
18
frame_timer.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#ifndef FRAME_TIMER_H
|
||||
#define FRAME_TIMER_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// macro to tell if a new vertical refresh has happened since the last time we checked
|
||||
// requires a local uint64_t to track frame counter
|
||||
#define FRAME_TICK(last) \
|
||||
((global_frame_counter != (last)) ? ((last) = global_frame_counter, 1) : 0)
|
||||
|
||||
void frame_timer();
|
||||
|
||||
// global
|
||||
extern uint64_t global_frame_counter; // used by FRAME_TICK()
|
||||
extern bool fpga_vsync_timer; // does this core expose the frame counter directly?
|
||||
// exposed globally in case timerfd isn't accurate enough for some use cases
|
||||
|
||||
#endif
|
||||
355
input.cpp
355
input.cpp
@@ -9,16 +9,17 @@
|
||||
#include <sys/sysinfo.h>
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/uinput.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <time.h>
|
||||
#include <stdarg.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "input.h"
|
||||
#include "autofire.h"
|
||||
#include "user_io.h"
|
||||
#include "menu.h"
|
||||
#include "hardware.h"
|
||||
@@ -32,9 +33,9 @@
|
||||
#include "profiling.h"
|
||||
#include "gamecontroller_db.h"
|
||||
#include "str_util.h"
|
||||
#include "frame_timer.h"
|
||||
|
||||
#define NUMDEV 30
|
||||
#define NUMPLAYERS 6
|
||||
#define UINPUT_NAME "MiSTer virtual input"
|
||||
|
||||
char joy_bnames[NUMBUTTONS][32] = {};
|
||||
@@ -1645,11 +1646,6 @@ static int keyrah_trans(int key, int press)
|
||||
static void input_cb(struct input_event *ev, struct input_absinfo *absinfo, int dev);
|
||||
|
||||
static int kbd_toggle = 0;
|
||||
static uint64_t joy[NUMPLAYERS] = {}; // 0-31 primary mappings, 32-64 alternate
|
||||
static uint64_t autofire[NUMPLAYERS] = {}; // 0-31 primary mappings, 32-64 alternate
|
||||
static uint32_t autofirecodes[NUMPLAYERS][BTN_NUM] = {};
|
||||
static int af_delay[NUMPLAYERS] = {};
|
||||
|
||||
static uint32_t crtgun_timeout[NUMDEV] = {};
|
||||
|
||||
static unsigned char mouse_btn = 0; //emulated mouse
|
||||
@@ -1669,10 +1665,6 @@ static uint32_t mouse_timer = 0;
|
||||
#define BTN_TGL 100
|
||||
#define BTN_OSD 101
|
||||
|
||||
#define AF_MIN 16
|
||||
#define AF_MAX 512
|
||||
#define AF_STEP 8
|
||||
|
||||
static int uinp_fd = -1;
|
||||
static int input_uinp_setup()
|
||||
{
|
||||
@@ -1841,98 +1833,158 @@ static void joy_apply_deadzone(int* x, int* y, const devInput* dev, const int st
|
||||
joy_clamp(y, min_range, INT8_MAX);
|
||||
}
|
||||
|
||||
static uint32_t osdbtn = 0;
|
||||
static void joy_digital(int jnum, uint64_t mask, uint32_t code, char press, int bnum, int dont_save = 0)
|
||||
static bool osd_autofire_consumed[NUMPLAYERS] = {};
|
||||
|
||||
// returns true if autofire was toggled which also means input was consumed.
|
||||
static bool handle_autofire_toggle(int num, uint32_t mask, uint32_t code, char press, int bnum, int dont_save)
|
||||
{
|
||||
static char str[128];
|
||||
static uint32_t lastcode[NUMPLAYERS];
|
||||
static uint64_t lastmask[NUMPLAYERS];
|
||||
int num = jnum - 1;
|
||||
if (num < NUMPLAYERS)
|
||||
static uint32_t lastmask[NUMPLAYERS];
|
||||
static char str[512];
|
||||
|
||||
// if the button is not OSD or BTN_TGL we save it into lastmask/lastcode
|
||||
if (bnum != BTN_OSD && bnum != BTN_TGL)
|
||||
{
|
||||
if (jnum)
|
||||
if (!dont_save)
|
||||
{
|
||||
if (bnum != BTN_OSD && bnum != BTN_TGL)
|
||||
if (press)
|
||||
{
|
||||
if (!dont_save)
|
||||
{
|
||||
if (press)
|
||||
{
|
||||
lastcode[num] = code;
|
||||
lastmask[num] = mask;
|
||||
}
|
||||
else
|
||||
{
|
||||
lastcode[num] = 0;
|
||||
lastmask[num] = 0;
|
||||
}
|
||||
if (lastcode[num] == code) { // build up mask if multiple buttons map to same code
|
||||
lastmask[num] |= mask; // this can't happen at present but if it ever does it will work correctly
|
||||
|
||||
} else {
|
||||
lastcode[num] = code;
|
||||
lastmask[num] = mask;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!user_io_osd_is_visible() && press && !cfg.disable_autofire)
|
||||
{
|
||||
if (lastcode[num] && lastmask[num])
|
||||
{
|
||||
int found = 0;
|
||||
int zero = -1;
|
||||
for (uint i = 0; i < BTN_NUM; i++)
|
||||
{
|
||||
if (!autofirecodes[num][i]) zero = i;
|
||||
if (autofirecodes[num][i] == lastcode[num])
|
||||
{
|
||||
found = 1;
|
||||
autofirecodes[num][i] = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found && zero >= 0) autofirecodes[num][zero] = lastcode[num];
|
||||
|
||||
autofire[num] = !found ? autofire[num] | lastmask[num] : autofire[num] & ~lastmask[num];
|
||||
|
||||
if (hasAPI1_5())
|
||||
{
|
||||
if (!found) sprintf(str, "Auto fire: %dms (%uhz)", af_delay[num] * 2, 1000 / (af_delay[num] * 2));
|
||||
else sprintf(str, "Auto fire: OFF");
|
||||
Info(str);
|
||||
}
|
||||
else InfoMessage((!found) ? "\n\n Auto fire\n ON" :
|
||||
"\n\n Auto fire\n OFF");
|
||||
|
||||
return;
|
||||
}
|
||||
else if (lastmask[num] & 0xF)
|
||||
{
|
||||
if (lastmask[num] & 9)
|
||||
{
|
||||
af_delay[num] += AF_STEP << ((lastmask[num] & 1) ? 1 : 0);
|
||||
if (af_delay[num] > AF_MAX) af_delay[num] = AF_MAX;
|
||||
}
|
||||
else
|
||||
{
|
||||
af_delay[num] -= AF_STEP << ((lastmask[num] & 2) ? 1 : 0);
|
||||
if (af_delay[num] < AF_MIN) af_delay[num] = AF_MIN;
|
||||
}
|
||||
|
||||
static char str[256];
|
||||
|
||||
if (hasAPI1_5())
|
||||
{
|
||||
sprintf(str, "Auto fire period: %dms (%uhz)", af_delay[num] * 2, 1000 / (af_delay[num] * 2));
|
||||
Info(str);
|
||||
}
|
||||
else
|
||||
{
|
||||
sprintf(str, "\n\n Auto fire period\n %dms(%uhz)", af_delay[num] * 2, 1000 / (af_delay[num] * 2));
|
||||
InfoMessage(str);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
lastcode[num] = 0;
|
||||
lastmask[num] = 0;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// we can only get here if the OSD or BTN_TGL keys were pressed
|
||||
// in that event we see if lastmask/lastcode tells us we're holding a button
|
||||
// and if we are, we toggle autofire for that button
|
||||
if (!user_io_osd_is_visible() && press && !cfg.disable_autofire)
|
||||
{
|
||||
if ((lastcode[num] && lastmask[num] && (lastmask[num] & 0xF) == 0)) // don't allow enabling autofire on directions
|
||||
{
|
||||
char *strat = str;
|
||||
inc_autofire_code(num, lastcode[num], lastmask[num]);
|
||||
|
||||
// display autofire status for each button in the mask
|
||||
FOR_EACH_SET_BIT(lastmask[num], btn) {
|
||||
strat += sprintf(strat, "%s\n", joy_bnames[btn-4]);
|
||||
}
|
||||
|
||||
const char *rate = get_autofire_rate_hz(num, lastcode[num]);
|
||||
|
||||
if (!strcmp(rate, "disabled")) {
|
||||
strat += sprintf(strat, "Autofire disabled");
|
||||
} else {
|
||||
strat += sprintf(strat, "Autofire: %s", rate);
|
||||
}
|
||||
|
||||
if (hasAPI1_5())
|
||||
Info(str);
|
||||
else
|
||||
InfoMessage(str);
|
||||
if (bnum == BTN_OSD && press) osd_autofire_consumed[num] = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static uint32_t osdbtn = 0;
|
||||
|
||||
struct KeyStates {
|
||||
uint32_t key[256];
|
||||
uint32_t mask[256];
|
||||
int count;
|
||||
};
|
||||
|
||||
#define MAX_KEY_STATES 64
|
||||
|
||||
KeyStates key_states[NUMPLAYERS] = {};
|
||||
|
||||
static void set_key_state(int player, uint32_t key, bool press, uint32_t mask)
|
||||
{
|
||||
for (int i = 0; i < key_states[player].count; i++)
|
||||
{
|
||||
if (key_states[player].key[i] == key)
|
||||
{
|
||||
if (press)
|
||||
{
|
||||
key_states[player].mask[i] |= mask;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
key_states[player].mask[i] &= ~mask;
|
||||
// remove an entry if no bits are set.
|
||||
if (!key_states[player].mask[i]) {
|
||||
int last = key_states[player].count - 1;
|
||||
if (i != last) {
|
||||
key_states[player].key[i] = key_states[player].key[last];
|
||||
key_states[player].mask[i] = key_states[player].mask[last];
|
||||
}
|
||||
key_states[player].key[last] = 0;
|
||||
key_states[player].mask[last] = 0u;
|
||||
key_states[player].count--;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (key_states[player].count < MAX_KEY_STATES)
|
||||
{
|
||||
if (press)
|
||||
{
|
||||
int idx = key_states[player].count++;
|
||||
key_states[player].key[idx] = key;
|
||||
key_states[player].mask[idx] = mask;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t build_joy_mask(int player)
|
||||
{
|
||||
uint32_t mask = 0u;
|
||||
for (int i = 0; i < key_states[player].count; i++)
|
||||
{
|
||||
if (!is_autofire_enabled(player, key_states[player].key[i]))
|
||||
mask |= key_states[player].mask[i];
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
uint32_t build_autofire_mask(int player)
|
||||
{
|
||||
uint32_t mask = 0u;
|
||||
for (int i = 0; i < key_states[player].count; i++)
|
||||
{
|
||||
if (is_autofire_enabled(player, key_states[player].key[i]))
|
||||
if (get_autofire_bit(player, key_states[player].key[i]))
|
||||
mask |= key_states[player].mask[i];
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
static void joy_digital(int jnum, uint32_t mask, uint32_t code, char press, int bnum, int dont_save = 0)
|
||||
{
|
||||
int num = jnum - 1;
|
||||
if (num < NUMPLAYERS)
|
||||
{
|
||||
// autofire handler moved to helper function for clarity
|
||||
if (handle_autofire_toggle(num, mask, code, press, bnum, dont_save)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (bnum == BTN_TGL)
|
||||
{
|
||||
@@ -1960,6 +2012,13 @@ static void joy_digital(int jnum, uint64_t mask, uint32_t code, char press, int
|
||||
return;
|
||||
}
|
||||
|
||||
// toggling autofire via OSD button consumes the "press" event of OSD but not the release event
|
||||
// this suppresses that so toggling autofire on a button doesn't immediately disable the button
|
||||
if (bnum == BTN_OSD && !press && osd_autofire_consumed[num]) {
|
||||
osd_autofire_consumed[num] = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// clear OSD button state if not in the OSD. this avoids problems where buttons are still held
|
||||
// on OSD exit and causes combinations to match when partial buttons are pressed.
|
||||
if (!user_io_osd_is_visible()) osdbtn = 0;
|
||||
@@ -1998,13 +2057,13 @@ static void joy_digital(int jnum, uint64_t mask, uint32_t code, char press, int
|
||||
if((mask & JOY_BTN2) && !(old_osdbtn & JOY_BTN2)) mask = 0;
|
||||
}
|
||||
|
||||
memset(joy, 0, sizeof(joy));
|
||||
memset(key_states, 0, sizeof(key_states));
|
||||
struct input_event ev;
|
||||
ev.type = EV_KEY;
|
||||
ev.value = press;
|
||||
|
||||
int cfg_switch = menu_allow_cfg_switch() && (osdbtn & JOY_BTN2) && press;
|
||||
|
||||
|
||||
switch (mask)
|
||||
{
|
||||
case JOY_RIGHT:
|
||||
@@ -2128,17 +2187,7 @@ static void joy_digital(int jnum, uint64_t mask, uint32_t code, char press, int
|
||||
}
|
||||
else if(jnum)
|
||||
{
|
||||
if (press) joy[num] |= mask;
|
||||
else joy[num] &= ~mask;
|
||||
|
||||
//user_io_digital_joystick(num, joy[num]);
|
||||
|
||||
if (code)
|
||||
{
|
||||
int found = 0;
|
||||
for (uint i = 0; i < BTN_NUM; i++) if (autofirecodes[num][i] == code) found = 1;
|
||||
if (found) autofire[num] = press ? autofire[num] | mask : autofire[num] & ~mask;
|
||||
}
|
||||
set_key_state(num, code, press, mask);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2342,6 +2391,11 @@ void reset_players()
|
||||
input[i].map_shown = 0;
|
||||
update_num_hw(i, 0);
|
||||
}
|
||||
|
||||
memset(key_states, 0, sizeof(key_states));
|
||||
for (int i = 0; i < NUMPLAYERS; i++) {
|
||||
clear_autofire(i);
|
||||
}
|
||||
memset(player_pad, 0, sizeof(player_pad));
|
||||
memset(player_pdsp, 0, sizeof(player_pdsp));
|
||||
}
|
||||
@@ -3301,13 +3355,8 @@ static void input_cb(struct input_event *ev, struct input_absinfo *absinfo, int
|
||||
|
||||
for (uint i = 0; i < BTN_NUM; i++)
|
||||
{
|
||||
uint64_t mask = 0;
|
||||
if (ev->code == (input[dev].map[i] & 0xFFFF)) mask = (uint64_t)1 << i;
|
||||
else if (ev->code == (input[dev].map[i] >> 16)) mask = (uint64_t)1 << (i + 32); // 1 is uint32_t. i spent hours realizing this.
|
||||
if (mask) {
|
||||
if (i <= 3 && origcode == ev->code) origcode = 0; // prevent autofire for original dpad
|
||||
if (ev->value <=1) joy_digital(input[dev].num, mask, origcode, ev->value, i, (ev->code == input[dev].mmap[SYS_BTN_OSD_KTGL + 1] || ev->code == input[dev].mmap[SYS_BTN_OSD_KTGL + 2]));
|
||||
// support 2 simultaneous functions for 1 button if defined in 2 sets. No return.
|
||||
if (ev->code == (input[dev].map[i] & 0xFFFF) || ev->code == (input[dev].map[i] >> 16)) {
|
||||
if (ev->value <= 1) joy_digital(input[dev].num, 1 << i, origcode, ev->value, i, (ev->code == input[dev].mmap[SYS_BTN_OSD_KTGL + 1] || ev->code == input[dev].mmap[SYS_BTN_OSD_KTGL + 2]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3359,11 +3408,15 @@ static void input_cb(struct input_event *ev, struct input_absinfo *absinfo, int
|
||||
{
|
||||
if (ev->code == (uint16_t)input[dev].map[i])
|
||||
{
|
||||
if (i <= 3 && origcode == ev->code) origcode = 0; // prevent autofire for original dpad
|
||||
if (ev->value <= 1) joy_digital((user_io_get_kbdemu() == EMU_JOY0) ? 1 : 2, 1 << i, origcode, ev->value, i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// if we move the return above to here, it should change behavior so that joy_digital() is called once for every
|
||||
// button that is mapped to a given code. each call would have the same ev->code but a different mask and bnum.
|
||||
// no time to test now though so leaving as note to anybody else who wants to try it. current system can't even
|
||||
// generate an ev->code with multiple buttons
|
||||
// return;
|
||||
}
|
||||
|
||||
if (ev->code == input[dev].mmap[SYS_BTN_OSD_KTGL])
|
||||
@@ -4699,6 +4752,12 @@ int input_test(int getchar)
|
||||
pool[i].events = 0;
|
||||
}
|
||||
|
||||
// clear button reference counts and key states
|
||||
memset(key_states, 0, sizeof(key_states));
|
||||
for (int i = 0; i < NUMPLAYERS; i++) {
|
||||
clear_autofire(i);
|
||||
}
|
||||
|
||||
memset(input, 0, sizeof(input));
|
||||
|
||||
int n = 0;
|
||||
@@ -5787,10 +5846,15 @@ int input_test(int getchar)
|
||||
int input_poll(int getchar)
|
||||
{
|
||||
PROFILE_FUNCTION();
|
||||
|
||||
static int af[NUMPLAYERS] = {};
|
||||
static uint32_t time[NUMPLAYERS] = {};
|
||||
static uint64_t joy_prev[NUMPLAYERS] = {};
|
||||
static bool autofire_cfg_parsed = false;
|
||||
if (!autofire_cfg_parsed) autofire_cfg_parsed = parse_autofire_cfg();
|
||||
static uint32_t joy_mask_prev[NUMPLAYERS] = {};
|
||||
|
||||
// FRAME_TICK compares against frame_timer's counter (updated elsewhere) and fires once per frame.
|
||||
static uint32_t last_frame_count = 0;
|
||||
if (FRAME_TICK(last_frame_count)) {
|
||||
autofire_tick(); // advance all autofire patterns by 1
|
||||
}
|
||||
|
||||
int ret = input_test(getchar);
|
||||
if (getchar) return ret;
|
||||
@@ -5824,41 +5888,23 @@ int input_poll(int getchar)
|
||||
|
||||
if (!mouse_emu_x && !mouse_emu_y) mouse_timer = 0;
|
||||
|
||||
uint32_t joy_mask[NUMPLAYERS];
|
||||
uint32_t autofire_mask[NUMPLAYERS];
|
||||
|
||||
for (int i = 0; i < NUMPLAYERS; i++) {
|
||||
joy_mask[i] = build_joy_mask(i);
|
||||
autofire_mask[i] = build_autofire_mask(i);
|
||||
}
|
||||
|
||||
if (grabbed)
|
||||
{
|
||||
for (int i = 0; i < NUMPLAYERS; i++)
|
||||
{
|
||||
int send = 0;
|
||||
if (af_delay[i] < AF_MIN) af_delay[i] = AF_MIN;
|
||||
|
||||
/* Autofire handler */
|
||||
if (joy[i] & autofire[i])
|
||||
for (int i = 0; i < NUMPLAYERS; i++) {
|
||||
joy_mask[i] = joy_mask[i] | autofire_mask[i];
|
||||
int newdir = (joy_mask[i] & 0xF) | (joy_mask_prev[i] & 0xF);
|
||||
if (joy_mask[i] != joy_mask_prev[i])
|
||||
{
|
||||
if (!time[i]) time[i] = GetTimer(af_delay[i]);
|
||||
else if ((joy[i] ^ joy_prev[i]) & autofire[i])
|
||||
{
|
||||
time[i] = GetTimer(af_delay[i]);
|
||||
af[i] = 0;
|
||||
}
|
||||
else if (CheckTimer(time[i]))
|
||||
{
|
||||
time[i] = GetTimer(af_delay[i]);
|
||||
af[i] = !af[i];
|
||||
send = 1;
|
||||
}
|
||||
}
|
||||
|
||||
int newdir = ((((uint32_t)(joy[i]) | (uint32_t)(joy[i] >> 32)) & 0xF) != (((uint32_t)(joy_prev[i]) | (uint32_t)(joy_prev[i] >> 32)) & 0xF));
|
||||
|
||||
if (joy[i] != joy_prev[i])
|
||||
{
|
||||
joy_prev[i] = joy[i];
|
||||
send = 1;
|
||||
}
|
||||
|
||||
if (send)
|
||||
{
|
||||
user_io_digital_joystick(i, af[i] ? joy[i] & ~autofire[i] : joy[i], newdir);
|
||||
joy_mask_prev[i] = joy_mask[i];
|
||||
user_io_digital_joystick(i, joy_mask[i], newdir);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5867,12 +5913,9 @@ int input_poll(int getchar)
|
||||
{
|
||||
for (int i = 0; i < NUMPLAYERS; i++)
|
||||
{
|
||||
if(joy[i]) user_io_digital_joystick(i, 0, 1);
|
||||
|
||||
joy[i] = 0;
|
||||
af[i] = 0;
|
||||
autofire[i] = 0;
|
||||
if(joy_mask[i]) user_io_digital_joystick(i, 0, 1);
|
||||
}
|
||||
memset(key_states, 0, sizeof(key_states));
|
||||
}
|
||||
|
||||
if (mouse_req)
|
||||
@@ -5895,7 +5938,7 @@ int input_poll(int getchar)
|
||||
|
||||
int is_key_pressed(int key)
|
||||
{
|
||||
unsigned char bits[(KEY_MAX + 7) / 8];
|
||||
unsigned char bits[(KEY_CNT + 7) / 8];
|
||||
for (int i = 0; i < NUMDEV; i++)
|
||||
{
|
||||
if (pool[i].fd > 0)
|
||||
|
||||
6
input.h
6
input.h
@@ -29,6 +29,8 @@
|
||||
|
||||
#define UPSTROKE 0x400000
|
||||
|
||||
#define NUMPLAYERS 6
|
||||
|
||||
#define NUMBUTTONS 32
|
||||
#define BUTTON_DPAD_COUNT 12 // dpad + 8 buttons
|
||||
|
||||
@@ -114,4 +116,8 @@ void parse_buttons();
|
||||
char *get_buttons(int type = 0);
|
||||
void set_ovr_buttons(char *s, int type);
|
||||
|
||||
#define FOR_EACH_SET_BIT(mask, bit) \
|
||||
for (uint32_t _m = (mask); _m; _m &= (_m - 1)) \
|
||||
for (int bit = __builtin_ctz(_m), _once = 1; _once; _once = 0)
|
||||
|
||||
#endif
|
||||
|
||||
2
main.cpp
2
main.cpp
@@ -29,6 +29,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#include "menu.h"
|
||||
#include "user_io.h"
|
||||
#include "input.h"
|
||||
#include "frame_timer.h"
|
||||
#include "fpga_io.h"
|
||||
#include "scheduler.h"
|
||||
#include "osd.h"
|
||||
@@ -83,6 +84,7 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
|
||||
user_io_poll();
|
||||
frame_timer();
|
||||
input_poll(0);
|
||||
HandleUI();
|
||||
OsdUpdate();
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "menu.h"
|
||||
#include "user_io.h"
|
||||
#include "input.h"
|
||||
#include "frame_timer.h"
|
||||
#include "fpga_io.h"
|
||||
#include "osd.h"
|
||||
#include "profiling.h"
|
||||
@@ -30,6 +31,7 @@ static void scheduler_co_poll(void)
|
||||
{
|
||||
SPIKE_SCOPE("co_poll", 1000);
|
||||
user_io_poll();
|
||||
frame_timer();
|
||||
input_poll(0);
|
||||
}
|
||||
|
||||
|
||||
16
user_io.cpp
16
user_io.cpp
@@ -1735,23 +1735,21 @@ void user_io_r_analog_joystick(unsigned char joystick, char valueX, char valueY)
|
||||
}
|
||||
}
|
||||
|
||||
void user_io_digital_joystick(unsigned char joystick, uint64_t map, int newdir)
|
||||
void user_io_digital_joystick(unsigned char joystick, uint32_t map, int newdir)
|
||||
{
|
||||
uint8_t joy = (joystick>1 || !joyswap) ? joystick : joystick ^ 1;
|
||||
|
||||
static int use32 = 0;
|
||||
// primary button mappings are in 31:0, alternate mappings are in 64:32.
|
||||
// take the logical OR to ensure a held button isn't overriden
|
||||
// by other mapping being pressed
|
||||
uint32_t bitmask = (uint32_t)(map) | (uint32_t)(map >> 32);
|
||||
use32 |= bitmask >> 16;
|
||||
use32 |= map >> 16;
|
||||
|
||||
spi_uio_cmd_cont((joy < 2) ? (UIO_JOYSTICK0 + joy) : (UIO_JOYSTICK2 + joy - 2));
|
||||
spi_w(bitmask);
|
||||
if(use32) spi_w(bitmask >> 16);
|
||||
spi_w(map);
|
||||
if(use32) spi_w(map >> 16);
|
||||
DisableIO();
|
||||
|
||||
if (!is_minimig() && joy_transl == 1 && newdir)
|
||||
{
|
||||
user_io_l_analog_joystick(joystick, (bitmask & 2) ? 128 : (bitmask & 1) ? 127 : 0, (bitmask & 8) ? 128 : (bitmask & 4) ? 127 : 0);
|
||||
user_io_l_analog_joystick(joystick, (map & 2) ? 128 : (map & 1) ? 127 : 0, (map & 8) ? 128 : (map & 4) ? 127 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -206,7 +206,7 @@ void user_io_mouse(unsigned char b, int16_t x, int16_t y, int16_t w);
|
||||
void user_io_kbd(uint16_t key, int press);
|
||||
char* user_io_create_config_name(int with_ver = 0);
|
||||
int user_io_get_joy_transl();
|
||||
void user_io_digital_joystick(unsigned char, uint64_t, int);
|
||||
void user_io_digital_joystick(unsigned char, uint32_t, int);
|
||||
void user_io_l_analog_joystick(unsigned char, char, char);
|
||||
void user_io_r_analog_joystick(unsigned char, char, char);
|
||||
void user_io_set_joyswap(int swap);
|
||||
|
||||
@@ -75,7 +75,7 @@ static int brd_y = 0;
|
||||
static int menu_bg = 0;
|
||||
static int menu_bgn = 0;
|
||||
|
||||
static VideoInfo current_video_info;
|
||||
VideoInfo current_video_info;
|
||||
|
||||
static int support_FHD = 0;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user