28#define VERSION "0.1.0"
30#define DEFAULT_FB "/dev/fb0"
57 struct fb_var_screeninfo vinfo;
58 struct fb_fix_screeninfo finfo;
89typedef enum fbgl_key {
103 fbgl_key_t current_key;
104 bool special_key_pressed;
111static struct timespec previous_frame_time = { 0 };
112static struct termios orig_termios;
122char const *fbgl_name_info(
void);
123char const *fbgl_version_info(
void);
124float fbgl_get_fps(
void);
127int fbgl_init(
const char *device,
fbgl_t *fb);
128void fbgl_destroy(
fbgl_t *fb);
133void fbgl_clear(uint32_t color);
134void fbgl_put_pixel(
int x,
int y, uint32_t color,
fbgl_t *fb);
140uint32_t *fb_get_data(
fbgl_t const *fb);
141uint32_t fb_get_width(
fbgl_t const *fb);
142uint32_t fb_get_height(
fbgl_t const *fb);
153void fbgl_draw_circle_outline(
int x,
int y,
int radius, uint32_t color,
155void fbgl_draw_circle_filled(
int x,
int y,
int radius, uint32_t color,
172 int x,
int y, uint32_t color);
176int fbgl_keyboard_init(
void);
177void fbgl_destroy_keyboard(
void);
178fbgl_key_t fbgl_get_key(
void);
179bool fbgl_is_key_pressed(fbgl_key_t key);
185#define FBGL_RGB(r, g, b) ((uint32_t)(((r) << 16) | ((g) << 8) | (b)))
186#define FBGL_RGBA(r, g, b, a) \
187 ((uint32_t)(((a) << 24) | ((r) << 16) | ((g) << 8) | (b)))
188#define FBGL_F32RGB_TO_U32(r, g, b) \
189 ((uint32_t)(((uint8_t)(r * 255) << 16) | ((uint8_t)(g * 255) << 8) | \
191#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))
193#define FBGL_INLINE static inline
196static void i_fbgl_die(
const char *s);
197static void i_fbgl_disable_raw_mode();
198static void i_fbgl_enable_raw_mode();
199FBGL_INLINE i_fbgl_abs_int(
int x);
201FBGL_INLINE i_fbgl_sqrt_int(
int x);
203FBGL_INLINE i_fbgl_abs_int(
int x)
205 return x < 0 ? -x : x;
208FBGL_INLINE i_fbgl_sqrt_int(
int x)
213static void i_fbgl_die(
const char *s)
219static void i_fbgl_enable_raw_mode()
221 if (tcgetattr(STDIN_FILENO, &orig_termios) == -1) {
222 i_fbgl_die(
"tcgetattr");
224 atexit(i_fbgl_disable_raw_mode);
225 struct termios raw = orig_termios;
226 raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
227 raw.c_oflag &= ~(OPOST);
228 raw.c_cflag |= (CS8);
229 raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
232 if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) {
233 i_fbgl_die(
"tcsetattr");
237static void i_fbgl_disable_raw_mode()
239 if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios) == -1) {
240 i_fbgl_die(
"tcesetattr");
244#ifdef FBGL_IMPLEMENTATION
246char const *fbgl_name_info(
void)
251char const *fbgl_version_info(
void)
256int fbgl_init(
const char *device,
fbgl_t *fb)
259 fprintf(stderr,
"Error: fbgl_t pointer is NULL.");
263 fb->fd = device == NULL ? open(DEFAULT_FB, O_RDWR) :
264 open(device, O_RDWR);
266 perror(
"Error openning framebuffer device");
270 if (ioctl(fb->fd, FBIOGET_FSCREENINFO, &fb->finfo) == -1) {
271 perror(
"Error: Reading fixed information.");
275 if (ioctl(fb->fd, FBIOGET_VSCREENINFO, &fb->vinfo) == -1) {
276 perror(
"Error reading variable information");
281 fb->width = fb->vinfo.xres;
282 fb->height = fb->vinfo.yres;
283 fb->screen_size = fb->finfo.smem_len;
286 fb->pixels = (uint32_t *)mmap(NULL, fb->screen_size,
287 PROT_READ | PROT_WRITE, MAP_SHARED,
289 if (fb->pixels == MAP_FAILED) {
290 perror(
"Error mapping framebuffer device to memory");
298void fbgl_destroy(
fbgl_t *fb)
300 if (!fb || fb->fd == -1) {
302 "Error: framebuffer not initialized or already destroyed.\n");
306 if (fb->pixels && fb->pixels != MAP_FAILED) {
307 munmap(fb->pixels, fb->screen_size);
314void fbgl_set_bg(
fbgl_t *fb, uint32_t color)
317 if (!fb || fb->fd == -1) {
318 fprintf(stderr,
"Error: framebuffer not initialized.\n");
324 for (int32_t i = 0; i < fb->width * fb->height; i++) {
325 fb->pixels[i] = color;
329void fbgl_put_pixel(
int x,
int y, uint32_t color,
fbgl_t *fb)
331#ifdef FBGL_VALIDATE_PUT_PIXEL
332 if (!fb || !fb->pixels) {
333 fprintf(stderr,
"Error: framebuffer not initialized.\n");
337 if (x < 0 || x >= fb->width || y < 0 || y >= fb->height) {
342 const size_t index = y * fb->width + x;
343 fb->pixels[index] = color;
349 const int32_t dx = i_fbgl_abs_int(y.x - x.x);
350 const int32_t dy = i_fbgl_abs_int(y.y - x.y);
352 const int32_t sx = (x.x < y.x) ? 1 : -1;
353 const int32_t sy = (x.y < y.y) ? 1 : -1;
355 int32_t err = dx - dy;
359 fbgl_put_pixel(x.x, x.y, color, buffer);
362 if (x.x >= y.x && x.y >= y.y)
365 const int32_t e2 = 2 * err;
383 for (
int x = top_left.x; x < bottom_right.x; x++) {
384 fbgl_put_pixel(x, top_left.y, color, fb);
388 for (
int x = top_left.x; x < bottom_right.x; x++) {
389 fbgl_put_pixel(x, bottom_right.y - 1, color, fb);
393 for (
int y = top_left.y; y < bottom_right.y; y++) {
394 fbgl_put_pixel(top_left.x, y, color, fb);
398 for (
int y = top_left.y; y < bottom_right.y; y++) {
399 fbgl_put_pixel(bottom_right.x - 1, y, color, fb);
407 for (int32_t y = top_left.y; y < bottom_right.y; y++) {
409 for (int32_t x = top_left.x; x < bottom_right.x; x++) {
410 fbgl_put_pixel(x, y, color, fb);
415void fbgl_draw_circle_outline(
int x,
int y,
int radius, uint32_t color,
420 int ddF_y = -2 * radius;
424 fbgl_put_pixel(x, y + radius, color, fb);
425 fbgl_put_pixel(x, y - radius, color, fb);
426 fbgl_put_pixel(x + radius, y, color, fb);
427 fbgl_put_pixel(x - radius, y, color, fb);
439 fbgl_put_pixel(x + xx, y + yy, color, fb);
440 fbgl_put_pixel(x - xx, y + yy, color, fb);
441 fbgl_put_pixel(x + xx, y - yy, color, fb);
442 fbgl_put_pixel(x - xx, y - yy, color, fb);
443 fbgl_put_pixel(x + yy, y + xx, color, fb);
444 fbgl_put_pixel(x - yy, y + xx, color, fb);
445 fbgl_put_pixel(x + yy, y - xx, color, fb);
446 fbgl_put_pixel(x - yy, y - xx, color, fb);
450void fbgl_draw_circle_filled(
int x,
int y,
int radius, uint32_t color,
453 for (
int yy = -radius; yy <= radius; ++yy) {
455 (int)i_fbgl_sqrt_int(radius * radius - yy * yy);
457 int row_start = x - half_width;
458 int row_end = x + half_width;
460 if (y + yy < 0 || y + yy >= fb->height)
464 if (row_end >= fb->width)
465 row_end = fb->width - 1;
467 int pixel_offset = (y + yy) * fb->width + row_start;
468 int num_pixels = row_end - row_start + 1;
470 uint32_t *row_start_ptr = fb->pixels + pixel_offset;
471 for (
int i = 0; i < num_pixels; ++i) {
472 row_start_ptr[i] = color;
479 FILE *file = fopen(path,
"rb");
481 perror(
"Unable to open texture file");
487 if (fread(header, 1,
sizeof(header), file) !=
sizeof(header)) {
488 perror(
"Error reading TGA header");
497 perror(
"Failed to allocate texture structure");
503 texture->width = header[12] | (header[13] << 8);
504 texture->height = header[14] | (header[15] << 8);
505 uint8_t bits_per_pixel = header[16];
506 uint8_t image_descriptor = header[17];
509 if (bits_per_pixel != 24 && bits_per_pixel != 32) {
511 "Unsupported TGA bit depth: %d (only 24 and 32-bit supported)\n",
520 fseek(file, header[0], SEEK_CUR);
524 size_t pixel_count = texture->width * texture->height;
525 texture->data = (uint32_t *)malloc(pixel_count *
sizeof(uint32_t));
526 if (!texture->data) {
527 perror(
"Failed to allocate pixel data");
534 uint8_t *pixel_buffer = (uint8_t *)malloc(bits_per_pixel / 8);
536 perror(
"Failed to allocate pixel buffer");
544 bool bottom_up = !(image_descriptor & 0x20);
546 for (
size_t i = 0; i < pixel_count; i++) {
549 (texture->height - 1 - (i / texture->width)) *
551 (i % texture->width) :
554 if (fread(pixel_buffer, 1, bits_per_pixel / 8, file) !=
555 bits_per_pixel / 8) {
556 perror(
"Error reading pixel data");
565 uint32_t pixel = 0xFF000000;
566 pixel |= pixel_buffer[2] << 16;
567 pixel |= pixel_buffer[1] << 8;
568 pixel |= pixel_buffer[0];
569 if (bits_per_pixel == 32) {
570 pixel = (pixel & 0x00FFFFFF) |
571 (pixel_buffer[3] << 24);
574 texture->data[pixel_index] = pixel;
593 if (!fb || !texture || !texture->data) {
597 for (
int ty = 0; ty < texture->height; ty++) {
598 for (
int tx = 0; tx < texture->width; tx++) {
599 int screen_x = x + tx;
600 int screen_y = y + ty;
603 if (screen_x < 0 || screen_x >= fb->width ||
604 screen_y < 0 || screen_y >= fb->height) {
609 texture->data[ty * texture->width + tx];
611 if ((pixel & 0xFF000000) != 0) {
612 fbgl_put_pixel(screen_x, screen_y, pixel, fb);
618uint32_t fb_get_width(
fbgl_t const *fb)
623uint32_t fb_get_height(
fbgl_t const *fb)
628uint32_t *fb_get_data(
fbgl_t const *fb)
633float fbgl_get_fps(
void)
635 struct timespec current_time;
636 clock_gettime(CLOCK_MONOTONIC, ¤t_time);
638 if (previous_frame_time.tv_sec == 0 &&
639 previous_frame_time.tv_nsec == 0) {
640 previous_frame_time = current_time;
645 (current_time.tv_sec - previous_frame_time.tv_sec) +
646 (current_time.tv_nsec - previous_frame_time.tv_nsec) / 1e9;
648 previous_frame_time = current_time;
650 if (time_diff > 0.0) {
651 return 1.0 / time_diff;
659 FILE *file = fopen(path,
"rb");
661 perror(
"Failed to open font file");
668 perror(
"Failed to allocate memory for font");
675 if (fread(header, 1,
sizeof(header), file) !=
sizeof(header)) {
676 perror(
"Failed to read font header");
683 if (header[0] != 0x36 || header[1] != 0x04) {
684 fprintf(stderr,
"Invalid PSF1 magic number\n");
691 font->magic[0] = header[0];
692 font->magic[1] = header[1];
693 font->mode = header[2];
694 font->char_height = header[3];
695 font->glyph_count = (font->mode & 0x01) ? 512 :
697 font->char_width = 8;
700 size_t glyph_data_size = font->glyph_count * font->char_height;
701 font->glyphs = malloc(glyph_data_size);
703 perror(
"Failed to allocate memory for glyphs");
710 if (fread(font->glyphs, 1, glyph_data_size, file) != glyph_data_size) {
711 perror(
"Failed to read glyph data");
731 int x,
int y, uint32_t color)
733 if (!fb || !font || !text)
739 for (
const char *c = text; *c; c++) {
740 uint8_t glyph_index = (uint8_t)*c;
743 if (glyph_index >= font->glyph_count)
747 uint8_t *glyph = font->glyphs + glyph_index * font->char_height;
750 for (
int row = 0; row < font->char_height; row++) {
751 for (
int col = 0; col < font->char_width; col++) {
753 if (glyph[row] & (0x80 >> col)) {
754 fbgl_put_pixel(cursor_x + col,
755 cursor_y + row, color,
762 cursor_x += font->char_width;
766int fbgl_keyboard_init(
void)
768 i_fbgl_enable_raw_mode();
771 g_keyboard_state.is_key_down =
false;
772 g_keyboard_state.current_key = FBGL_KEY_NONE;
773 g_keyboard_state.special_key_pressed =
false;
778void fbgl_destroy_keyboard(
void)
780 i_fbgl_disable_raw_mode();
782fbgl_key_t fbgl_get_key(
void)
785 ssize_t bytes_read = read(STDIN_FILENO, &c, 1);
787 if (bytes_read <= 0) {
788 return FBGL_KEY_NONE;
794 if (read(STDIN_FILENO, &seq[0], 1) != 1)
795 return FBGL_KEY_ESCAPE;
796 if (read(STDIN_FILENO, &seq[1], 1) != 1)
797 return FBGL_KEY_ESCAPE;
804 return FBGL_KEY_DOWN;
806 return FBGL_KEY_RIGHT;
808 return FBGL_KEY_LEFT;
812 return FBGL_KEY_NONE;
818 return FBGL_KEY_ENTER;
820 return FBGL_KEY_SPACE;
826 return FBGL_KEY_DOWN;
829 return FBGL_KEY_LEFT;
832 return FBGL_KEY_RIGHT;
834 return FBGL_KEY_ESCAPE;
837 return FBGL_KEY_NONE;
840bool fbgl_is_key_pressed(fbgl_key_t key)
844 struct timeval timeout;
847 FD_SET(STDIN_FILENO, &read_fds);
854 if (select(STDIN_FILENO + 1, &read_fds, NULL, NULL, &timeout) > 0) {
855 fbgl_key_t pressed_key = fbgl_get_key();
856 return pressed_key == key;