MiniFB is a small cross platform library to create a frame buffer that you can draw pixels in
MiniFB is a small cross platform library to create a frame buffer that you can draw pixels in
MiniFB (Mini FrameBuffer) is a small cross-platform C library. You give it a buffer of 32-bit pixels, it copies that buffer to a window, and it reports keyboard and mouse events back to you. Your code draws everything: MiniFB never draws for you. It only needs the system libraries of each platform.
The shortest useful program looks like this:
int main() {
struct mfb_window *window = mfb_open_ex("my display", 800, 600, MFB_WF_RESIZABLE);
if (window == NULL)
return 0;
uint32_t *buffer = malloc(800 * 600 * 4);
mfb_update_state state;
do {
// TODO: add some fancy rendering to the buffer of size 800 * 600
state = mfb_update_ex(window, buffer, 800, 600);
if (state != MFB_STATE_OK)
break;
} while(mfb_wait_sync(window));
free(buffer);
buffer = NULL;
window = NULL;
return 0;
}mfb_open() or mfb_open_ex().width * height pixels. MiniFB never allocates or frees it.mfb_update() or mfb_update_ex() to copy it to the window.mfb_wait_sync() to wait until it is time to draw the next frame.MFB_STATE_OK means the frame was accepted. MFB_STATE_EXIT means the window was closed and destroyed internally: leave the loop and do not use the handle again. The other values report an error for that frame (invalid window, invalid buffer, backend failure) and leave the window alive.
Note: By default, releasing the ESC key closes the window, so the next update returns MFB_STATE_EXIT. Install your own keyboard callback if you do not want that.
See examples/noise.c for a complete example.
| Platform | Backends | Status |
|---|---|---|
| Windows | GDI, OpenGL | Full API |
| macOS | Cocoa, Metal | Full API |
| Linux/Unix | X11, Wayland | Full API |
| iOS | Metal | Reduced API: single window, touch input |
| Android | Native | Reduced API: single window, touch input |
| Web | WASM | Partial: some calls are accepted but not applied |
| DOS | DJGPP | Reduced API: single window, partial keyboard |
Build instructions and platform notes: Windows, X11, Wayland, macOS, iOS, Android, Web, MS-DOS. Feature support by platform lists what each backend implements.
MiniFB has been tested on Windows, macOS, Linux, iOS, Android, browsers and DOSBox-x. Results depend on your setup. The library does not convert pixel data: if the system cannot provide a proper 32-bit display, MiniFB does not work around it.
…mfb_set_viewport() returns false if:
width == 0 or height == 0mfb_open() and mfb_open_ex() return NULL if:
width == 0 or height == 0width * 4 would overflow the internal framebuffer strideIf title is NULL or empty, MiniFB uses "minifb" as the effective window/canvas title.
If both MFB_WF_FULLSCREEN and MFB_WF_FULLSCREEN_DESKTOP are provided, MFB_WF_FULLSCREEN takes precedence.
mfb_update_ex() returns MFB_STATE_INVALID_BUFFER if:
buffer == NULLwidth == 0 or height == 0width * 4 would overflow internal stride calculationsmfb_update_ex() behaves differently on two backends:
mfb_update_ex() (can block). A minimized or hidden window may stop receiving that callback. Each call then waits briefly and returns MFB_STATE_OK without presenting.MFB_STATE_OK without presenting when ANativeWindow is temporarily unavailable during lifecycle transitions.What is ready when mfb_open_ex() returns also depends on the backend:
mfb_open_ex().ANativeWindow is ready (rendering starts once the native window becomes available).mfb_open_ex() flag support by backend:
| Backend | RESIZABLE | BORDERLESS | ALWAYS_ON_TOP | FULLSCREEN | FULLSCREEN_DESKTOP |
|---|---|---|---|---|---|
| Windows | Yes | Yes | Yes | Yes | Yes |
| X11 | Yes | Yes* | Yes* | Yes* | Yes* |
| Wayland | Yes | Yes | No (ignored, warning) | Yes | Yes (maximized) |
| macOS | Yes | Yes | Yes | Yes | Yes (zoom/maximize) |
| Web | No (ignored, warning) | No (ignored, warning) | No (ignored, warning) | Yes** | Yes** |
| DOS | No (ignored, warning) | No (ignored, warning) | No (ignored, warning) | No (ignored, warning) | No (ignored, warning) |
| Android | No (ignored, warning) | No (ignored, warning) | No (ignored, warning) | No (ignored, warning) | No (ignored, warning) |
| iOS | No (ignored, warning) | No (ignored, warning) | No (ignored, warning) | No (ignored, warning) | No (ignored, warning) |
* Best effort via window-manager hints/properties; behavior depends on compositor/WM support.
** Browser-managed fullscreen; typically requires a user gesture before entering fullscreen.
Register callbacks to handle window events:
……In C++ you can also point a callback at a class method or a lambda:
// Using object and pointer to member
mfb_set_active_callback(window, &myObject, &MyClass::onActive);
// Using std::bind
mfb_set_active_callback(std::bind(&MyClass::onActive, &myObject, _1, _2), window);
// Using lambda
mfb_set_active_callback([](struct mfb_window *window, bool is_active) {
// Handle event
}, window);Read window and input state directly, instead of using callbacks:
…On Android and iOS, touch positions carry the pointer id in their upper bits:
mfb_get_mouse_x(), mfb_get_mouse_y() and the x/y given to mfb_mouse_move_func are packed values. Decode them with mfb_decode_touch(), or one at a time with mfb_decode_touch_pos() / mfb_decode_touch_id(). On desktop, Web and DOS the id is always 0.button argument of mfb_mouse_button_func (MFB_MOUSE_BTN_0..MFB_MOUSE_BTN_7).HOVER_MOVE events from external devices use the same packing. When Android reports no valid pointer id, MiniFB uses 15.mfb_get_mouse_scroll_x() and mfb_get_mouse_scroll_y() only hold the delta of the last event pump: MiniFB sets them to 0.0f before pumping events, then writes the delta if a scroll event arrives during that pump.
Attach and retrieve custom data per window:
void mfb_set_user_data(struct mfb_window *window, void *user_data);
void * mfb_get_user_data(struct mfb_window *window);Create and manage timers independently:
struct mfb_timer * mfb_timer_create(void);
void mfb_timer_destroy(struct mfb_timer *tmr);
void mfb_timer_reset(struct mfb_timer *tmr);
void mfb_timer_compensated_reset(struct mfb_timer *tmr);
double mfb_timer_now(struct mfb_timer *tmr);
double mfb_timer_delta(struct mfb_timer *tmr);
double mfb_timer_get_frequency(void);
double mfb_timer_get_resolution(void);Control target FPS and frame synchronization:
void mfb_set_target_fps(uint32_t fps); // Default: 60 fps
unsigned mfb_get_target_fps(void);
bool mfb_wait_sync(struct mfb_window *window); // Frame sync pointNote: Where MiniFB renders through OpenGL or Metal, mfb_wait_sync() relies on vertical sync. The other backends pace frames in software.
MiniFB ships with a simple logger that you can redirect or disable:
// Set a custom logger; pass NULL to restore the built-in logger
void mfb_set_logger(mfb_log_func user_logger);
// Control verbosity threshold (inclusive)
void mfb_set_log_level(mfb_log_level level);MFB_LOG_TRACE, MFB_LOG_DEBUG, MFB_LOG_INFO, MFB_LOG_WARNING, MFB_LOG_ERROR._DEBUG builds the threshold is MFB_LOG_DEBUG; in release builds MFB_LOG_INFO.level + message).TRACE/DEBUG/INFO to stdout and WARNING/ERROR to stderr as [MiniFB (LEVEL)] message.MINIFB_LOG_LEVEL sets the threshold without touching the code:
MINIFB_LOG_LEVEL=trace ./my_programUse a level name, not a number: trace, debug, info, warning or error. Case does not matter. Names keep working if the mfb_log_level enum is ever reordered, which numbers would not.
The variable wins over mfb_set_log_level(). This is on purpose. The point of the variable is to get more output from a program you cannot rebuild, and a hardcoded call in that program would otherwise block you. If you need the program to stay in control, do not set the variable.
An unknown value is reported as an error and then ignored, so the threshold stays where the program left it. Nothing changes without a message.
Since the built-in logger writes low levels to stdout and high levels to stderr, the two streams can interleave out of order when you pipe them into one file. Redirect them separately, or send both to the same place with 2>&1 and accept the ordering.
void mfb_show_cursor(struct mfb_window *window, bool show);Note: Cursor hiding is supported on Windows, macOS, X11, and Wayland only.
void mfb_get_monitor_scale(struct mfb_window *window, float *scale_x, float *scale_y);
void mfb_get_monitor_dpi(struct mfb_window *window, float *dpi_x, float *dpi_y); // [Deprecated]mfb_get_monitor_scale():
1.0 = 100%).window == NULL, outputs still receive a safe fallback (1.0) when their pointers are non-NULL.1.0.If your layout is defined in logical units, convert it to drawable coordinates before calling mfb_set_viewport():
float sx = 1.0f, sy = 1.0f;
mfb_get_monitor_scale(window, &sx, &sy);
unsigned margin_x_viewport = (unsigned) lroundf(margin_logical_x * sx);
unsigned margin_y_viewport = (unsigned) lroundf(margin_logical_y * sy);Two functions report the display insets from C:
// Physical cutout/notch area only.
bool mfb_get_display_cutout_insets(struct mfb_window *window,
int *left, int *top, int *right, int *bottom);
// Full safe area: cutout + system UI reserved regions.
bool mfb_get_display_safe_insets(struct mfb_window *window,
int *left, int *top, int *right, int *bottom);Insets are edge margins in pixels, not a rectangle:
left, top, right, bottom are distances from each window edge.0, 0, 0, 0.No open issues yet, or sync has not completed.