29#define VERSION "1.1.1"
31#define DEFAULT_FB "/dev/fb0"
58 struct fb_var_screeninfo vinfo;
59 struct fb_fix_screeninfo finfo;
90typedef enum fbgl_key {
104 fbgl_key_t current_key;
105 bool special_key_pressed;
112static struct timespec previous_frame_time = { 0 };
113static struct termios orig_termios;
123char const *fbgl_name_info(
void);
124char const *fbgl_version_info(
void);
125float fbgl_get_fps(
void);
128int fbgl_init(
const char *device,
fbgl_t *fb);
129void fbgl_destroy(
fbgl_t *fb);
134void fbgl_clear(uint32_t color);
135void fbgl_put_pixel(
int x,
int y, uint32_t color,
fbgl_t *fb);
141uint32_t *fb_get_data(
fbgl_t const *fb);
142uint32_t fb_get_width(
fbgl_t const *fb);
143uint32_t fb_get_height(
fbgl_t const *fb);
154void fbgl_draw_circle_outline(
int x,
int y,
int radius, uint32_t color,
156void fbgl_draw_circle_filled(
int x,
int y,
int radius, uint32_t color,
173 int x,
int y, uint32_t color);
177int fbgl_keyboard_init(
void);
178void fbgl_destroy_keyboard(
void);
179fbgl_key_t fbgl_get_key(
void);
180bool fbgl_is_key_pressed(fbgl_key_t key);
186#define FBGL_RGB(r, g, b) ((uint32_t)(((r) << 16) | ((g) << 8) | (b)))
187#define FBGL_RGBA(r, g, b, a) \
188 ((uint32_t)(((a) << 24) | ((r) << 16) | ((g) << 8) | (b)))
189#define FBGL_F32RGB_TO_U32(r, g, b) \
190 ((uint32_t)(((uint8_t)(r * 255) << 16) | ((uint8_t)(g * 255) << 8) | \
192#define FBGL_F32RGBA_TO_U32(r, g, b, a) ((uint32_t)(((uint8_t)(a * 255) << 24) | ((uint8_t)(r * 255) << 16) | ((uint8_t)(g * 255) << 8) | (uint8_t)(b * 255))
195#if defined(__GNUC__) || defined(__clang__)
196#define FBGL_INLINE static inline __attribute__((always_inline))
198#define FBGL_INLINE static inline
203static void i_fbgl_die(
const char *s);
204static void i_fbgl_disable_raw_mode();
205static void i_fbgl_enable_raw_mode();
206FBGL_INLINE
int i_fbgl_abs_int(
int x);
208FBGL_INLINE
int i_fbgl_sqrt_int(
int x);
210FBGL_INLINE
int i_fbgl_abs_int(
int x)
212 return x < 0 ? -x : x;
215FBGL_INLINE
int i_fbgl_sqrt_int(
int x)
220static void i_fbgl_die(
const char *s)
226static void i_fbgl_enable_raw_mode()
228 if (tcgetattr(STDIN_FILENO, &orig_termios) == -1) {
229 i_fbgl_die(
"tcgetattr");
231 atexit(i_fbgl_disable_raw_mode);
232 struct termios raw = orig_termios;
233 raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
234 raw.c_oflag &= ~(OPOST);
235 raw.c_cflag |= (CS8);
236 raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
239 if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) {
240 i_fbgl_die(
"tcsetattr");
244static void i_fbgl_disable_raw_mode()
246 if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios) == -1) {
247 i_fbgl_die(
"tcesetattr");
251#ifdef FBGL_IMPLEMENTATION
253char const *fbgl_name_info(
void)
258char const *fbgl_version_info(
void)
263int fbgl_init(
const char *device,
fbgl_t *fb)
266 fprintf(stderr,
"Error: fbgl_t pointer is NULL.");
270 fb->fd = device == NULL ? open(DEFAULT_FB, O_RDWR) :
271 open(device, O_RDWR);
273 perror(
"Error openning framebuffer device");
277 if (ioctl(fb->fd, FBIOGET_FSCREENINFO, &fb->finfo) == -1) {
278 perror(
"Error: Reading fixed information.");
282 if (ioctl(fb->fd, FBIOGET_VSCREENINFO, &fb->vinfo) == -1) {
283 perror(
"Error reading variable information");
288 fb->width = fb->vinfo.xres;
289 fb->height = fb->vinfo.yres;
290 fb->screen_size = fb->finfo.smem_len;
293 fb->pixels = (uint32_t *)mmap(NULL, fb->screen_size,
294 PROT_READ | PROT_WRITE, MAP_SHARED,
296 if (fb->pixels == MAP_FAILED) {
297 perror(
"Error mapping framebuffer device to memory");
305void fbgl_destroy(
fbgl_t *fb)
307 if (!fb || fb->fd == -1) {
309 "Error: framebuffer not initialized or already destroyed.\n");
313 if (fb->pixels && fb->pixels != MAP_FAILED) {
314 munmap(fb->pixels, fb->screen_size);
321void fbgl_set_bg(
fbgl_t *fb, uint32_t color)
324 if (!fb || fb->fd == -1) {
325 fprintf(stderr,
"Error: framebuffer not initialized.\n");
331 for (int32_t i = 0; i < fb->width * fb->height; i++) {
332 fb->pixels[i] = color;
336void fbgl_put_pixel(
int x,
int y, uint32_t color,
fbgl_t *fb)
338#ifdef FBGL_VALIDATE_PUT_PIXEL
339 if (!fb || !fb->pixels) {
340 fprintf(stderr,
"Error: framebuffer not initialized.\n");
344 if (x < 0 || x >= fb->width || y < 0 || y >= fb->height) {
349 const size_t index = y * fb->width + x;
350 fb->pixels[index] = color;
356 const int32_t dx = i_fbgl_abs_int(y.x - x.x);
357 const int32_t dy = i_fbgl_abs_int(y.y - x.y);
359 const int32_t sx = (x.x < y.x) ? 1 : -1;
360 const int32_t sy = (x.y < y.y) ? 1 : -1;
362 int32_t err = dx - dy;
366 fbgl_put_pixel(x.x, x.y, color, buffer);
369 if (x.x >= y.x && x.y >= y.y)
372 const int32_t e2 = 2 * err;
390 for (
int x = top_left.x; x < bottom_right.x; x++) {
391 fbgl_put_pixel(x, top_left.y, color, fb);
395 for (
int x = top_left.x; x < bottom_right.x; x++) {
396 fbgl_put_pixel(x, bottom_right.y - 1, color, fb);
400 for (
int y = top_left.y; y < bottom_right.y; y++) {
401 fbgl_put_pixel(top_left.x, y, color, fb);
405 for (
int y = top_left.y; y < bottom_right.y; y++) {
406 fbgl_put_pixel(bottom_right.x - 1, y, color, fb);
414 for (int32_t y = top_left.y; y < bottom_right.y; y++) {
416 for (int32_t x = top_left.x; x < bottom_right.x; x++) {
417 fbgl_put_pixel(x, y, color, fb);
422void fbgl_draw_circle_outline(
int x,
int y,
int radius, uint32_t color,
427 int ddF_y = -2 * radius;
431 fbgl_put_pixel(x, y + radius, color, fb);
432 fbgl_put_pixel(x, y - radius, color, fb);
433 fbgl_put_pixel(x + radius, y, color, fb);
434 fbgl_put_pixel(x - radius, y, color, fb);
446 fbgl_put_pixel(x + xx, y + yy, color, fb);
447 fbgl_put_pixel(x - xx, y + yy, color, fb);
448 fbgl_put_pixel(x + xx, y - yy, color, fb);
449 fbgl_put_pixel(x - xx, y - yy, color, fb);
450 fbgl_put_pixel(x + yy, y + xx, color, fb);
451 fbgl_put_pixel(x - yy, y + xx, color, fb);
452 fbgl_put_pixel(x + yy, y - xx, color, fb);
453 fbgl_put_pixel(x - yy, y - xx, color, fb);
457void fbgl_draw_circle_filled(
int x,
int y,
int radius, uint32_t color,
460 for (
int yy = -radius; yy <= radius; ++yy) {
462 (int)i_fbgl_sqrt_int(radius * radius - yy * yy);
464 int row_start = x - half_width;
465 int row_end = x + half_width;
467 if (y + yy < 0 || y + yy >= fb->height)
471 if (row_end >= fb->width)
472 row_end = fb->width - 1;
474 int pixel_offset = (y + yy) * fb->width + row_start;
475 int num_pixels = row_end - row_start + 1;
477 uint32_t *row_start_ptr = fb->pixels + pixel_offset;
478 for (
int i = 0; i < num_pixels; ++i) {
479 row_start_ptr[i] = color;
486 FILE *file = fopen(path,
"rb");
488 perror(
"Unable to open texture file");
494 if (fread(header, 1,
sizeof(header), file) !=
sizeof(header)) {
495 perror(
"Error reading TGA header");
504 perror(
"Failed to allocate texture structure");
510 texture->width = header[12] | (header[13] << 8);
511 texture->height = header[14] | (header[15] << 8);
512 uint8_t bits_per_pixel = header[16];
513 uint8_t image_descriptor = header[17];
516 if (bits_per_pixel != 24 && bits_per_pixel != 32) {
518 "Unsupported TGA bit depth: %d (only 24 and 32-bit supported)\n",
527 fseek(file, header[0], SEEK_CUR);
531 size_t pixel_count = texture->width * texture->height;
532 texture->data = (uint32_t *)malloc(pixel_count *
sizeof(uint32_t));
533 if (!texture->data) {
534 perror(
"Failed to allocate pixel data");
541 uint8_t *pixel_buffer = (uint8_t *)malloc(bits_per_pixel / 8);
543 perror(
"Failed to allocate pixel buffer");
551 bool bottom_up = !(image_descriptor & 0x20);
553 for (
size_t i = 0; i < pixel_count; i++) {
556 (texture->height - 1 - (i / texture->width)) *
558 (i % texture->width) :
561 if (fread(pixel_buffer, 1, bits_per_pixel / 8, file) !=
562 bits_per_pixel / 8) {
563 perror(
"Error reading pixel data");
572 uint32_t pixel = 0xFF000000;
573 pixel |= pixel_buffer[2] << 16;
574 pixel |= pixel_buffer[1] << 8;
575 pixel |= pixel_buffer[0];
576 if (bits_per_pixel == 32) {
577 pixel = (pixel & 0x00FFFFFF) |
578 (pixel_buffer[3] << 24);
581 texture->data[pixel_index] = pixel;
600 if (!fb || !texture || !texture->data) {
604 for (
int ty = 0; ty < texture->height; ty++) {
605 for (
int tx = 0; tx < texture->width; tx++) {
606 int screen_x = x + tx;
607 int screen_y = y + ty;
610 if (screen_x < 0 || screen_x >= fb->width ||
611 screen_y < 0 || screen_y >= fb->height) {
616 texture->data[ty * texture->width + tx];
618 if ((pixel & 0xFF000000) != 0) {
619 fbgl_put_pixel(screen_x, screen_y, pixel, fb);
625uint32_t fb_get_width(
fbgl_t const *fb)
630uint32_t fb_get_height(
fbgl_t const *fb)
635uint32_t *fb_get_data(
fbgl_t const *fb)
640float fbgl_get_fps(
void)
642 struct timespec current_time;
643 clock_gettime(CLOCK_MONOTONIC, ¤t_time);
645 if (previous_frame_time.tv_sec == 0 &&
646 previous_frame_time.tv_nsec == 0) {
647 previous_frame_time = current_time;
652 (current_time.tv_sec - previous_frame_time.tv_sec) +
653 (current_time.tv_nsec - previous_frame_time.tv_nsec) / 1e9;
655 previous_frame_time = current_time;
657 if (time_diff > 0.0) {
658 return 1.0 / time_diff;
666 FILE *file = fopen(path,
"rb");
668 perror(
"Failed to open font file");
675 perror(
"Failed to allocate memory for font");
682 if (fread(header, 1,
sizeof(header), file) !=
sizeof(header)) {
683 perror(
"Failed to read font header");
690 if (header[0] != 0x36 || header[1] != 0x04) {
691 fprintf(stderr,
"Invalid PSF1 magic number\n");
698 font->magic[0] = header[0];
699 font->magic[1] = header[1];
700 font->mode = header[2];
701 font->char_height = header[3];
702 font->glyph_count = (font->mode & 0x01) ? 512 :
704 font->char_width = 8;
707 size_t glyph_data_size = font->glyph_count * font->char_height;
708 font->glyphs = malloc(glyph_data_size);
710 perror(
"Failed to allocate memory for glyphs");
717 if (fread(font->glyphs, 1, glyph_data_size, file) != glyph_data_size) {
718 perror(
"Failed to read glyph data");
738 int x,
int y, uint32_t color)
740 if (!fb || !font || !text)
746 for (
const char *c = text; *c; c++) {
747 uint8_t glyph_index = (uint8_t)*c;
750 if (glyph_index >= font->glyph_count)
754 uint8_t *glyph = font->glyphs + glyph_index * font->char_height;
757 for (
int row = 0; row < font->char_height; row++) {
758 for (
int col = 0; col < font->char_width; col++) {
760 if (glyph[row] & (0x80 >> col)) {
761 fbgl_put_pixel(cursor_x + col,
762 cursor_y + row, color,
769 cursor_x += font->char_width;
773int fbgl_keyboard_init(
void)
775 i_fbgl_enable_raw_mode();
778 g_keyboard_state.is_key_down =
false;
779 g_keyboard_state.current_key = FBGL_KEY_NONE;
780 g_keyboard_state.special_key_pressed =
false;
785void fbgl_destroy_keyboard(
void)
787 i_fbgl_disable_raw_mode();
789fbgl_key_t fbgl_get_key(
void)
792 ssize_t bytes_read = read(STDIN_FILENO, &c, 1);
794 if (bytes_read <= 0) {
795 return FBGL_KEY_NONE;
801 if (read(STDIN_FILENO, &seq[0], 1) != 1)
802 return FBGL_KEY_ESCAPE;
803 if (read(STDIN_FILENO, &seq[1], 1) != 1)
804 return FBGL_KEY_ESCAPE;
811 return FBGL_KEY_DOWN;
813 return FBGL_KEY_RIGHT;
815 return FBGL_KEY_LEFT;
819 return FBGL_KEY_NONE;
825 return FBGL_KEY_ENTER;
827 return FBGL_KEY_SPACE;
833 return FBGL_KEY_DOWN;
836 return FBGL_KEY_LEFT;
839 return FBGL_KEY_RIGHT;
841 return FBGL_KEY_ESCAPE;
844 return FBGL_KEY_NONE;
847bool fbgl_is_key_pressed(fbgl_key_t key)
851 struct timeval timeout;
854 FD_SET(STDIN_FILENO, &read_fds);
861 if (select(STDIN_FILENO + 1, &read_fds, NULL, NULL, &timeout) > 0) {
862 fbgl_key_t pressed_key = fbgl_get_key();
863 return pressed_key == key;