Add timers and ability to set a target fps for the application (#36)

* working on windows

* minor fix

* working on macosx

* Oops! I reversed resolution and frequency on macos

* working on X11

* working on wayland

* update the readme

* format the readme

Co-authored-by: Carlos Aragones <>
This commit is contained in:
Carlos Aragonés
2020-04-22 13:00:15 +02:00
committed by GitHub
parent 4175cec89e
commit 35b8439b26
19 changed files with 734 additions and 239 deletions

View File

@ -6,6 +6,13 @@
#define kCall(func, ...) if(window_data && window_data->func) window_data->func((struct mfb_window *) window_data, __VA_ARGS__);
#define kUnused(var) (void) var;
typedef struct mfb_timer {
int64_t start_time;
int64_t delta_counter;
uint64_t time;
} mfb_timer;
#if defined(__cplusplus)
extern "C" {
#endif

32
src/MiniFB_linux.c Normal file
View File

@ -0,0 +1,32 @@
#include <time.h>
#include <MiniFB.h>
extern double g_timer_frequency;
extern double g_timer_resolution;
#define kClock CLOCK_MONOTONIC
//#define kClock CLOCK_REALTIME
uint64_t
mfb_timer_tick() {
struct timespec time;
if (clock_gettime(kClock, &time) != 0) {
return 0.0;
}
return time.tv_sec * 1e+9 + time.tv_nsec;
}
void
mfb_timer_init() {
struct timespec res;
if (clock_getres(kClock, &res) != 0) {
g_timer_frequency = 1e+9;
}
else {
g_timer_frequency = res.tv_sec + res.tv_nsec * 1e+9;
}
g_timer_resolution = 1.0 / g_timer_frequency;
}

102
src/MiniFB_timer.c Normal file
View File

@ -0,0 +1,102 @@
#include "MiniFB.h"
#include "MiniFB_internal.h"
#include <stdlib.h>
//-------------------------------------
double g_timer_frequency;
double g_timer_resolution;
double g_time_for_frame = 1.0 / 60.0;
//-------------------------------------
extern uint64_t mfb_timer_tick();
extern void mfb_timer_init();
//-------------------------------------
void
mfb_set_target_fps(uint32_t fps) {
if(fps == 0) {
g_time_for_frame = 0;
}
else {
g_time_for_frame = 1.0 / fps;
}
}
//-------------------------------------
struct mfb_timer *
mfb_timer_create() {
static int once = 1;
mfb_timer *tmr;
if(once) {
once = 0;
mfb_timer_init();
}
tmr = malloc(sizeof(mfb_timer));
mfb_timer_reset(tmr);
return tmr;
}
//-------------------------------------
void
mfb_timer_destroy(struct mfb_timer *tmr) {
if(tmr != 0x0) {
free(tmr);
}
}
//-------------------------------------
void
mfb_timer_reset(struct mfb_timer *tmr) {
if(tmr == 0x0)
return;
tmr->start_time = mfb_timer_tick();
tmr->delta_counter = tmr->start_time;
tmr->time = 0;
}
//-------------------------------------
double
mfb_timer_now(struct mfb_timer *tmr) {
uint64_t counter;
if(tmr == 0x0)
return 0.0;
counter = mfb_timer_tick();
tmr->time += (counter - tmr->start_time);
tmr->start_time = counter;
return tmr->time * g_timer_resolution;
}
//-------------------------------------
double
mfb_timer_delta(struct mfb_timer *tmr) {
int64_t counter;
uint64_t delta;
if(tmr == 0x0)
return 0.0;
counter = mfb_timer_tick();
delta = (counter - tmr->delta_counter);
tmr->delta_counter = counter;
return delta * g_timer_resolution;
}
//-------------------------------------
double
mfb_timer_get_frequency() {
return g_timer_frequency;
}
//-------------------------------------
double
mfb_timer_get_resolution() {
return g_timer_resolution;
}

View File

@ -10,6 +10,8 @@
#include <MetalKit/MetalKit.h>
#endif
#include <unistd.h>
#include <sched.h>
#include <mach/mach_time.h>
void init_keycodes();
@ -250,6 +252,7 @@ mfb_open_ex(const char *title, unsigned width, unsigned height, unsigned flags)
[window_data_osx->window setAcceptsMouseMovedEvents:YES];
[window_data_osx->window center];
window_data_osx->timer = mfb_timer_create();
[NSApp activateIgnoringOtherApps:YES];
@ -286,6 +289,8 @@ destroy_window_data(SWindowData *window_data)
[window removeWindowData];
[window performClose:nil];
mfb_timer_destroy(window_data_osx->timer);
memset(window_data_osx, 0, sizeof(SWindowData_OSX));
free(window_data_osx);
}
@ -303,14 +308,12 @@ update_events(SWindowData *window_data)
NSEvent* event;
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
do
{
do {
event = [NSApp nextEventMatchingMask:NSEventMaskAny untilDate:[NSDate distantPast] inMode:NSDefaultRunLoopMode dequeue:YES];
if (event) {
[NSApp sendEvent:event];
}
}
while ((window_data->close == false) && event);
} while ((window_data->close == false) && event);
[pool release];
}
@ -375,6 +378,59 @@ mfb_update_events(struct mfb_window *window)
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
extern double g_time_for_frame;
bool
mfb_wait_sync(struct mfb_window *window)
{
NSEvent* event;
if(window == 0x0) {
return STATE_INVALID_WINDOW;
}
SWindowData *window_data = (SWindowData *) window;
if(window_data->close) {
destroy_window_data(window_data);
return false;
}
//NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
SWindowData_OSX *window_data_osx = (SWindowData_OSX *) window_data->specific;
double current;
uint32_t millis = 1;
while(1) {
event = [NSApp nextEventMatchingMask:NSEventMaskAny untilDate:[NSDate distantPast] inMode:NSDefaultRunLoopMode dequeue:YES];
if (event) {
[NSApp sendEvent:event];
}
if(window_data->close) {
destroy_window_data(window_data);
return false;
}
current = mfb_timer_now(window_data_osx->timer);
if (current >= g_time_for_frame) {
mfb_timer_reset(window_data_osx->timer);
return true;
}
else if(current >= g_time_for_frame * 0.8) {
millis = 0;
}
usleep(millis * 1000);
//sched_yield();
}
//[pool release];
return true;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool
mfb_set_viewport(struct mfb_window *window, unsigned offset_x, unsigned offset_y, unsigned width, unsigned height)
{
@ -540,3 +596,35 @@ init_keycodes()
g_keycodes[0x43] = KB_KEY_KP_MULTIPLY;
g_keycodes[0x4E] = KB_KEY_KP_SUBTRACT;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
extern double g_timer_frequency;
extern double g_timer_resolution;
uint64_t
mfb_timer_tick() {
static mach_timebase_info_data_t timebase = { 0 };
if (timebase.denom == 0) {
(void) mach_timebase_info(&timebase);
}
uint64_t time = mach_absolute_time();
//return (time * s_timebase_info.numer) / s_timebase_info.denom;
// Perform the arithmetic at 128-bit precision to avoid the overflow!
uint64_t high = (time >> 32) * timebase.numer;
uint64_t highRem = ((high % timebase.denom) << 32) / timebase.denom;
uint64_t low = (time & 0xFFFFFFFFull) * timebase.numer / timebase.denom;
high /= timebase.denom;
return (high << 32) + highRem + low;
}
void
mfb_timer_init() {
g_timer_frequency = 1e+9;
g_timer_resolution = 1.0 / g_timer_frequency;
}

View File

@ -10,7 +10,9 @@
@class OSXWindow;
typedef struct {
OSXWindow *window;
OSXWindow *window;
struct mfb_timer *timer;
#if defined(USE_METAL_API)
struct {
id<MTLCommandQueue> command_queue;

View File

@ -27,6 +27,7 @@ destroy_window_data(SWindowData *window_data)
SWindowData_Way *window_data_way = (SWindowData_Way *) window_data->specific;
if(window_data_way != 0x0) {
mfb_timer_destroy(window_data_way->timer);
memset(window_data_way, 0, sizeof(SWindowData_Way));
free(window_data_way);
}
@ -596,8 +597,8 @@ mfb_open(const char *title, unsigned width, unsigned height)
init_keycodes();
if (wl_display_dispatch(window_data_way->display) == -1 || wl_display_roundtrip(window_data_way->display) == -1)
{
if (wl_display_dispatch(window_data_way->display) == -1 ||
wl_display_roundtrip(window_data_way->display) == -1) {
return 0x0;
}
@ -667,6 +668,8 @@ mfb_open(const char *title, unsigned width, unsigned height)
wl_surface_damage(window_data_way->surface, window_data->dst_offset_x, window_data->dst_offset_y, window_data->dst_width, window_data->dst_height);
wl_surface_commit(window_data_way->surface);
window_data_way->timer = mfb_timer_create();
mfb_set_keyboard_callback((struct mfb_window *) window_data, keyboard_default);
printf("Window created using Wayland API\n");
@ -762,7 +765,7 @@ mfb_update_events(struct mfb_window *window)
if (!window_data_way->display || wl_display_get_error(window_data_way->display) != 0)
return STATE_INTERNAL_ERROR;
if (wl_display_dispatch(window_data_way->display) == -1 || wl_display_roundtrip(window_data_way->display) == -1) {
if (wl_display_dispatch_pending(window_data_way->display) == -1) {
return STATE_INTERNAL_ERROR;
}
@ -771,6 +774,51 @@ mfb_update_events(struct mfb_window *window)
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
extern double g_time_for_frame;
bool
mfb_wait_sync(struct mfb_window *window) {
if(window == 0x0) {
return false;
}
SWindowData *window_data = (SWindowData *) window;
if(window_data->close) {
destroy(window_data);
return false;
}
SWindowData_Way *window_data_way = (SWindowData_Way *) window_data->specific;
double current;
uint32_t millis = 1;
while(1) {
if (wl_display_dispatch_pending(window_data_way->display) == -1) {
return false;
}
if(window_data->close) {
destroy_window_data(window_data);
return false;
}
current = mfb_timer_now(window_data_way->timer);
if (current >= g_time_for_frame) {
mfb_timer_reset(window_data_way->timer);
return true;
}
else if(current >= g_time_for_frame * 0.8) {
millis = 0;
}
usleep(millis * 1000);
//sched_yield();
}
return true;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
extern short int g_keycodes[512];
void

View File

@ -53,5 +53,6 @@ typedef struct
uint32_t buffer_stride;
uint32_t mod_keys;
struct mfb_timer *timer;
bool close;
} SWindowData_Way;

View File

@ -48,10 +48,8 @@ WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
StretchDIBits(window_data_win->hdc, window_data->dst_offset_x, window_data->dst_offset_y, window_data->dst_width, window_data->dst_height, 0, 0, window_data->buffer_width, window_data->buffer_height, window_data->draw_buffer,
window_data_win->bitmapInfo, DIB_RGB_COLORS, SRCCOPY);
ValidateRect(hWnd, 0x0);
}
ValidateRect(hWnd, 0x0);
break;
}
@ -359,6 +357,8 @@ mfb_open_ex(const char *title, unsigned width, unsigned height, unsigned flags)
window_data_win->hdc = GetDC(window_data_win->window);
window_data_win->timer = mfb_timer_create();
mfb_set_keyboard_callback((struct mfb_window *) window_data, keyboard_default);
return (struct mfb_window *) window_data;
@ -421,9 +421,8 @@ mfb_update_events(struct mfb_window *window) {
SWindowData_Win *window_data_win = (SWindowData_Win *) window_data->specific;
while (window_data->close == false && PeekMessage(&msg, window_data_win->window, 0, 0, PM_REMOVE)) {
if(msg.message == WM_PAINT)
return STATE_OK;
//if(msg.message == WM_PAINT)
// return STATE_OK;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
@ -433,6 +432,53 @@ mfb_update_events(struct mfb_window *window) {
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
extern double g_time_for_frame;
bool
mfb_wait_sync(struct mfb_window *window) {
MSG msg;
if (window == 0x0) {
return false;
}
SWindowData *window_data = (SWindowData *)window;
if (window_data->close) {
destroy_window_data(window_data);
return false;
}
SWindowData_Win *window_data_win = (SWindowData_Win *) window_data->specific;
double current;
uint32_t millis = 1;
while (1) {
if(PeekMessage(&msg, window_data_win->window, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
if (window_data->close) {
destroy_window_data(window_data);
return false;
}
current = mfb_timer_now(window_data_win->timer);;
if (current >= g_time_for_frame) {
mfb_timer_reset(window_data_win->timer);
return true;
}
else if(current >= g_time_for_frame * 0.8) {
millis = 0;
}
Sleep(millis);
}
return true;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void
destroy_window_data(SWindowData *window_data) {
if (window_data == 0x0)
@ -453,6 +499,8 @@ destroy_window_data(SWindowData *window_data) {
window_data_win->window = 0;
window_data_win->hdc = 0;
window_data_win->bitmapInfo = 0x0;
mfb_timer_destroy(window_data_win->timer);
window_data_win->timer = 0x0;
window_data->close = true;
}
@ -659,3 +707,27 @@ mfb_set_viewport(struct mfb_window *window, unsigned offset_x, unsigned offset_y
return true;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
extern double g_timer_frequency;
extern double g_timer_resolution;
uint64_t
mfb_timer_tick() {
int64_t counter;
QueryPerformanceCounter((LARGE_INTEGER *) &counter);
return counter;
}
void
mfb_timer_init() {
uint64_t frequency;
QueryPerformanceFrequency((LARGE_INTEGER *) &frequency);
g_timer_frequency = (double) ((int64_t) frequency);
g_timer_resolution = 1.0 / g_timer_frequency;
}

View File

@ -7,9 +7,10 @@
typedef struct {
HWND window;
WNDCLASS wc;
HDC hdc;
BITMAPINFO *bitmapInfo;
bool mouse_inside;
HWND window;
WNDCLASS wc;
HDC hdc;
BITMAPINFO *bitmapInfo;
struct mfb_timer *timer;
bool mouse_inside;
} SWindowData_Win;

View File

@ -6,15 +6,16 @@
typedef struct {
Window window;
Display *display;
int screen;
GC gc;
XImage *image;
void *image_buffer;
XImage *image_scaler;
uint32_t image_scaler_width;
uint32_t image_scaler_height;
Window window;
Display *display;
int screen;
GC gc;
XImage *image;
void *image_buffer;
XImage *image_scaler;
uint32_t image_scaler_width;
uint32_t image_scaler_height;
struct mfb_timer *timer;
} SWindowData_X11;

View File

@ -7,6 +7,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <MiniFB.h>
#include <MiniFB_internal.h>
#include "WindowData.h"
@ -185,6 +186,8 @@ mfb_open_ex(const char *title, unsigned width, unsigned height, unsigned flags)
window_data_x11->image = XCreateImage(window_data_x11->display, CopyFromParent, depth, ZPixmap, 0, 0x0, width, height, 32, width * 4);
window_data_x11->timer = mfb_timer_create();
mfb_set_keyboard_callback((struct mfb_window *) window_data, keyboard_default);
printf("Window created using X11 API\n");
@ -203,106 +206,111 @@ int translate_key(int scancode);
int translate_mod(int state);
int translate_mod_ex(int key, int state, int is_pressed);
static void
processEvent(SWindowData *window_data, XEvent *event) {
switch (event->type) {
case KeyPress:
case KeyRelease:
{
mfb_key key_code = (mfb_key) translate_key(event->xkey.keycode);
int is_pressed = (event->type == KeyPress);
window_data->mod_keys = translate_mod_ex(key_code, event->xkey.state, is_pressed);
window_data->key_status[key_code] = is_pressed;
kCall(keyboard_func, key_code, (mfb_key_mod) window_data->mod_keys, is_pressed);
}
break;
case ButtonPress:
case ButtonRelease:
{
mfb_mouse_button button = (mfb_mouse_button) event->xbutton.button;
int is_pressed = (event->type == ButtonPress);
window_data->mod_keys = translate_mod(event->xkey.state);
switch (button) {
case Button1:
case Button2:
case Button3:
kCall(mouse_btn_func, button, (mfb_key_mod) window_data->mod_keys, is_pressed);
break;
case Button4:
kCall(mouse_wheel_func, (mfb_key_mod) window_data->mod_keys, 0.0f, 1.0f);
break;
case Button5:
kCall(mouse_wheel_func, (mfb_key_mod) window_data->mod_keys, 0.0f, -1.0f);
break;
case 6:
kCall(mouse_wheel_func, (mfb_key_mod) window_data->mod_keys, 1.0f, 0.0f);
break;
case 7:
kCall(mouse_wheel_func, (mfb_key_mod) window_data->mod_keys, -1.0f, 0.0f);
break;
default:
kCall(mouse_btn_func, (mfb_mouse_button) (button - 4), (mfb_key_mod) window_data->mod_keys, is_pressed);
break;
}
}
break;
case MotionNotify:
window_data->mouse_pos_x = event->xmotion.x;
window_data->mouse_pos_y = event->xmotion.y;
kCall(mouse_move_func, event->xmotion.x, event->xmotion.y);
break;
case ConfigureNotify:
{
window_data->window_width = event->xconfigure.width;
window_data->window_height = event->xconfigure.height;
window_data->dst_offset_x = 0;
window_data->dst_offset_y = 0;
window_data->dst_width = window_data->window_width;
window_data->dst_height = window_data->window_height;
SWindowData_X11 *window_data_x11 = (SWindowData_X11 *) window_data->specific;
XClearWindow(window_data_x11->display, window_data_x11->window);
kCall(resize_func, window_data->window_width, window_data->window_height);
}
break;
case EnterNotify:
case LeaveNotify:
break;
case FocusIn:
window_data->is_active = true;
kCall(active_func, true);
break;
case FocusOut:
window_data->is_active = false;
kCall(active_func, false);
break;
case DestroyNotify:
window_data->close = true;
return;
break;
}
}
static void
processEvents(SWindowData *window_data) {
XEvent event;
SWindowData_X11 *window_data_x11 = (SWindowData_X11 *) window_data->specific;
SWindowData_X11 *window_data_x11 = (SWindowData_X11 *) window_data->specific;
while ((window_data->close == false) && XPending(window_data_x11->display)) {
XNextEvent(window_data_x11->display, &event);
switch (event.type) {
case KeyPress:
case KeyRelease:
{
mfb_key key_code = (mfb_key) translate_key(event.xkey.keycode);
int is_pressed = (event.type == KeyPress);
window_data->mod_keys = translate_mod_ex(key_code, event.xkey.state, is_pressed);
window_data->key_status[key_code] = is_pressed;
kCall(keyboard_func, key_code, (mfb_key_mod) window_data->mod_keys, is_pressed);
}
break;
case ButtonPress:
case ButtonRelease:
{
mfb_mouse_button button = (mfb_mouse_button) event.xbutton.button;
int is_pressed = (event.type == ButtonPress);
window_data->mod_keys = translate_mod(event.xkey.state);
switch (button) {
case Button1:
case Button2:
case Button3:
kCall(mouse_btn_func, button, (mfb_key_mod) window_data->mod_keys, is_pressed);
break;
case Button4:
kCall(mouse_wheel_func, (mfb_key_mod) window_data->mod_keys, 0.0f, 1.0f);
break;
case Button5:
kCall(mouse_wheel_func, (mfb_key_mod) window_data->mod_keys, 0.0f, -1.0f);
break;
case 6:
kCall(mouse_wheel_func, (mfb_key_mod) window_data->mod_keys, 1.0f, 0.0f);
break;
case 7:
kCall(mouse_wheel_func, (mfb_key_mod) window_data->mod_keys, -1.0f, 0.0f);
break;
default:
kCall(mouse_btn_func, (mfb_mouse_button) (button - 4), (mfb_key_mod) window_data->mod_keys, is_pressed);
break;
}
}
break;
case MotionNotify:
window_data->mouse_pos_x = event.xmotion.x;
window_data->mouse_pos_y = event.xmotion.y;
kCall(mouse_move_func, event.xmotion.x, event.xmotion.y);
break;
case ConfigureNotify:
{
window_data->window_width = event.xconfigure.width;
window_data->window_height = event.xconfigure.height;
window_data->dst_offset_x = 0;
window_data->dst_offset_y = 0;
window_data->dst_width = window_data->window_width;
window_data->dst_height = window_data->window_height;
XClearWindow(window_data_x11->display, window_data_x11->window);
kCall(resize_func, window_data->window_width, window_data->window_height);
}
break;
case EnterNotify:
case LeaveNotify:
break;
case FocusIn:
window_data->is_active = true;
kCall(active_func, true);
break;
case FocusOut:
window_data->is_active = false;
kCall(active_func, false);
break;
case DestroyNotify:
window_data->close = true;
return;
break;
}
processEvent(window_data, &event);
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void destroy(SWindowData *window_data);
void destroy_window_data(SWindowData *window_data);
mfb_update_state
mfb_update(struct mfb_window *window, void *buffer) {
@ -312,7 +320,7 @@ mfb_update(struct mfb_window *window, void *buffer) {
SWindowData *window_data = (SWindowData *) window;
if (window_data->close) {
destroy(window_data);
destroy_window_data(window_data);
return STATE_EXIT;
}
@ -368,7 +376,7 @@ mfb_update_events(struct mfb_window *window) {
SWindowData *window_data = (SWindowData *) window;
if (window_data->close) {
destroy(window_data);
destroy_window_data(window_data);
return STATE_EXIT;
}
@ -381,8 +389,56 @@ mfb_update_events(struct mfb_window *window) {
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
extern double g_time_for_frame;
bool
mfb_wait_sync(struct mfb_window *window) {
if (window == 0x0) {
return STATE_INVALID_WINDOW;
}
SWindowData *window_data = (SWindowData *) window;
if (window_data->close) {
destroy_window_data(window_data);
return false;
}
SWindowData_X11 *window_data_x11 = (SWindowData_X11 *) window_data->specific;
XFlush(window_data_x11->display);
XEvent event;
double current;
uint32_t millis = 1;
while(1) {
if(XEventsQueued(window_data_x11->display, QueuedAlready) > 0) {
XNextEvent(window_data_x11->display, &event);
processEvent(window_data, &event);
}
if(window_data->close) {
destroy_window_data(window_data);
return false;
}
current = mfb_timer_now(window_data_x11->timer);
if (current >= g_time_for_frame) {
mfb_timer_reset(window_data_x11->timer);
return true;
}
else if(current >= g_time_for_frame * 0.8) {
millis = 0;
}
usleep(millis * 1000);
//sched_yield();
}
return true;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void
destroy(SWindowData *window_data) {
destroy_window_data(SWindowData *window_data) {
if (window_data != 0x0) {
if (window_data->specific != 0x0) {
SWindowData_X11 *window_data_x11 = (SWindowData_X11 *) window_data->specific;
@ -392,6 +448,7 @@ destroy(SWindowData *window_data) {
XDestroyWindow(window_data_x11->display, window_data_x11->window);
XCloseDisplay(window_data_x11->display);
}
mfb_timer_destroy(window_data_x11->timer);
memset(window_data_x11, 0, sizeof(SWindowData_X11));
free(window_data_x11);
}