27 #define VERSION "0.1.0"
29 #define DEFAULT_FB "/dev/fb0"
41 #include <sys/ioctl.h>
56 struct fb_var_screeninfo vinfo;
57 struct fb_fix_screeninfo finfo;
88 typedef enum fbgl_key {
102 fbgl_key_t current_key;
103 bool special_key_pressed;
110 static struct timespec previous_frame_time = { 0 };
111 static struct termios orig_termios;
121 char const *fbgl_name_info(
void);
122 char const *fbgl_version_info(
void);
123 float fbgl_get_fps(
void);
126 int fbgl_init(
const char *device,
fbgl_t *fb);
127 void fbgl_destroy(
fbgl_t *fb);
132 void fbgl_clear(uint32_t color);
133 void fbgl_put_pixel(
int x,
int y, uint32_t color,
fbgl_t *fb);
139 uint32_t *fb_get_data(
fbgl_t const *fb);
140 uint32_t fb_get_width(
fbgl_t const *fb);
141 uint32_t fb_get_height(
fbgl_t const *fb);
152 void fbgl_draw_circle_outline(
int x,
int y,
int radius, uint32_t color,
154 void fbgl_draw_circle_filled(
int x,
int y,
int radius, uint32_t color,
171 int x,
int y, uint32_t color);
175 int fbgl_keyboard_init(
void);
176 void fbgl_destroy_keyboard(
void);
177 fbgl_key_t fbgl_get_key(
void);
178 bool fbgl_is_key_pressed(fbgl_key_t key);
184 #define FBGL_RGB(r, g, b) ((uint32_t)(((r) << 16) | ((g) << 8) | (b)))
185 #define FBGL_RGBA(r, g, b, a) \
186 ((uint32_t)(((a) << 24) | ((r) << 16) | ((g) << 8) | (b)))
187 #define FBGL_F32RGB_TO_U32(r, g, b) \
188 ((uint32_t)(((uint8_t)(r * 255) << 16) | ((uint8_t)(g * 255) << 8) | \
190 #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 static void i_fbgl_die(
const char *s);
194 static void i_fbgl_disable_raw_mode();
195 static void i_fbgl_enable_raw_mode();
197 static void i_fbgl_die(
const char *s)
203 static void i_fbgl_enable_raw_mode()
205 if (tcgetattr(STDIN_FILENO, &orig_termios) == -1) {
206 i_fbgl_die(
"tcgetattr");
208 atexit(i_fbgl_disable_raw_mode);
209 struct termios raw = orig_termios;
210 raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
211 raw.c_oflag &= ~(OPOST);
212 raw.c_cflag |= (CS8);
213 raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
216 if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) {
217 i_fbgl_die(
"tcsetattr");
221 static void i_fbgl_disable_raw_mode()
223 if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios) == -1) {
224 i_fbgl_die(
"tcesetattr");
228 #ifdef FBGL_IMPLEMENTATION
230 char const *fbgl_name_info(
void)
235 char const *fbgl_version_info(
void)
240 int fbgl_init(
const char *device,
fbgl_t *fb)
243 fprintf(stderr,
"Error: fbgl_t pointer is NULL.");
247 fb->fd = device == NULL ? open(DEFAULT_FB, O_RDWR) :
248 open(device, O_RDWR);
250 perror(
"Error openning framebuffer device");
254 if (ioctl(fb->fd, FBIOGET_FSCREENINFO, &fb->finfo) == -1) {
255 perror(
"Error: Reading fixed information.");
259 if (ioctl(fb->fd, FBIOGET_VSCREENINFO, &fb->vinfo) == -1) {
260 perror(
"Error reading variable information");
265 fb->width = fb->vinfo.xres;
266 fb->height = fb->vinfo.yres;
267 fb->screen_size = fb->finfo.smem_len;
270 fb->pixels = (uint32_t *)mmap(NULL, fb->screen_size,
271 PROT_READ | PROT_WRITE, MAP_SHARED,
273 if (fb->pixels == MAP_FAILED) {
274 perror(
"Error mapping framebuffer device to memory");
282 void fbgl_destroy(
fbgl_t *fb)
284 if (!fb || fb->fd == -1) {
286 "Error: framebuffer not initialized or already destroyed.\n");
290 if (fb->pixels && fb->pixels != MAP_FAILED) {
291 munmap(fb->pixels, fb->screen_size);
298 void fbgl_set_bg(
fbgl_t *fb, uint32_t color)
301 if (!fb || fb->fd == -1) {
302 fprintf(stderr,
"Error: framebuffer not initialized.\n");
308 for (int32_t i = 0; i < fb->width * fb->height; i++) {
309 fb->pixels[i] = color;
313 void fbgl_put_pixel(
int x,
int y, uint32_t color,
fbgl_t *fb)
315 #ifdef FBGL_VALIDATE_PUT_PIXEL
316 if (!fb || !fb->pixels) {
317 fprintf(stderr,
"Error: framebuffer not initialized.\n");
321 if (x < 0 || x >= fb->width || y < 0 || y >= fb->height) {
326 const size_t index = y * fb->width + x;
327 fb->pixels[index] = color;
333 const int32_t dx = abs(y.x - x.x);
334 const int32_t dy = abs(y.y - x.y);
336 const int32_t sx = (x.x < y.x) ? 1 : -1;
337 const int32_t sy = (x.y < y.y) ? 1 : -1;
339 int32_t err = dx - dy;
343 fbgl_put_pixel(x.x, x.y, color, buffer);
346 if (x.x >= y.x && x.y >= y.y)
349 const int32_t e2 = 2 * err;
367 for (
int x = top_left.x; x < bottom_right.x; x++) {
368 fbgl_put_pixel(x, top_left.y, color, fb);
372 for (
int x = top_left.x; x < bottom_right.x; x++) {
373 fbgl_put_pixel(x, bottom_right.y - 1, color, fb);
377 for (
int y = top_left.y; y < bottom_right.y; y++) {
378 fbgl_put_pixel(top_left.x, y, color, fb);
382 for (
int y = top_left.y; y < bottom_right.y; y++) {
383 fbgl_put_pixel(bottom_right.x - 1, y, color, fb);
391 for (int32_t y = top_left.y; y < bottom_right.y; y++) {
393 for (int32_t x = top_left.x; x < bottom_right.x; x++) {
394 fbgl_put_pixel(x, y, color, fb);
399 void fbgl_draw_circle_outline(
int x,
int y,
int radius, uint32_t color,
404 int ddF_y = -2 * radius;
408 fbgl_put_pixel(x, y + radius, color, fb);
409 fbgl_put_pixel(x, y - radius, color, fb);
410 fbgl_put_pixel(x + radius, y, color, fb);
411 fbgl_put_pixel(x - radius, y, color, fb);
423 fbgl_put_pixel(x + xx, y + yy, color, fb);
424 fbgl_put_pixel(x - xx, y + yy, color, fb);
425 fbgl_put_pixel(x + xx, y - yy, color, fb);
426 fbgl_put_pixel(x - xx, y - yy, color, fb);
427 fbgl_put_pixel(x + yy, y + xx, color, fb);
428 fbgl_put_pixel(x - yy, y + xx, color, fb);
429 fbgl_put_pixel(x + yy, y - xx, color, fb);
430 fbgl_put_pixel(x - yy, y - xx, color, fb);
434 void fbgl_draw_circle_filled(
int x,
int y,
int radius, uint32_t color,
437 for (
int yy = -radius; yy <= radius; ++yy) {
438 int half_width = (int)sqrt(radius * radius - yy * yy);
440 int row_start = x - half_width;
441 int row_end = x + half_width;
443 if (y + yy < 0 || y + yy >= fb->height)
447 if (row_end >= fb->width)
448 row_end = fb->width - 1;
450 int pixel_offset = (y + yy) * fb->width + row_start;
451 int num_pixels = row_end - row_start + 1;
453 uint32_t *row_start_ptr = fb->pixels + pixel_offset;
454 for (
int i = 0; i < num_pixels; ++i) {
455 row_start_ptr[i] = color;
462 FILE *file = fopen(path,
"rb");
464 perror(
"Unable to open texture file");
470 if (fread(header, 1,
sizeof(header), file) !=
sizeof(header)) {
471 perror(
"Error reading TGA header");
480 perror(
"Failed to allocate texture structure");
486 texture->width = header[12] | (header[13] << 8);
487 texture->height = header[14] | (header[15] << 8);
488 uint8_t bits_per_pixel = header[16];
489 uint8_t image_descriptor = header[17];
492 if (bits_per_pixel != 24 && bits_per_pixel != 32) {
494 "Unsupported TGA bit depth: %d (only 24 and 32-bit supported)\n",
503 fseek(file, header[0], SEEK_CUR);
507 size_t pixel_count = texture->width * texture->height;
508 texture->data = (uint32_t *)malloc(pixel_count *
sizeof(uint32_t));
509 if (!texture->data) {
510 perror(
"Failed to allocate pixel data");
517 uint8_t *pixel_buffer = (uint8_t *)malloc(bits_per_pixel / 8);
519 perror(
"Failed to allocate pixel buffer");
527 bool bottom_up = !(image_descriptor & 0x20);
529 for (
size_t i = 0; i < pixel_count; i++) {
532 (texture->height - 1 - (i / texture->width)) *
534 (i % texture->width) :
537 if (fread(pixel_buffer, 1, bits_per_pixel / 8, file) !=
538 bits_per_pixel / 8) {
539 perror(
"Error reading pixel data");
548 uint32_t pixel = 0xFF000000;
549 pixel |= pixel_buffer[2] << 16;
550 pixel |= pixel_buffer[1] << 8;
551 pixel |= pixel_buffer[0];
552 if (bits_per_pixel == 32) {
553 pixel = (pixel & 0x00FFFFFF) |
554 (pixel_buffer[3] << 24);
557 texture->data[pixel_index] = pixel;
576 if (!fb || !texture || !texture->data) {
580 for (
int ty = 0; ty < texture->height; ty++) {
581 for (
int tx = 0; tx < texture->width; tx++) {
582 int screen_x = x + tx;
583 int screen_y = y + ty;
586 if (screen_x < 0 || screen_x >= fb->width ||
587 screen_y < 0 || screen_y >= fb->height) {
592 texture->data[ty * texture->width + tx];
594 if ((pixel & 0xFF000000) != 0) {
595 fbgl_put_pixel(screen_x, screen_y, pixel, fb);
601 uint32_t fb_get_width(
fbgl_t const *fb)
606 uint32_t fb_get_height(
fbgl_t const *fb)
611 uint32_t *fb_get_data(
fbgl_t const *fb)
616 float fbgl_get_fps(
void)
618 struct timespec current_time;
619 clock_gettime(CLOCK_MONOTONIC, ¤t_time);
621 if (previous_frame_time.tv_sec == 0 &&
622 previous_frame_time.tv_nsec == 0) {
623 previous_frame_time = current_time;
628 (current_time.tv_sec - previous_frame_time.tv_sec) +
629 (current_time.tv_nsec - previous_frame_time.tv_nsec) / 1e9;
631 previous_frame_time = current_time;
633 if (time_diff > 0.0) {
634 return 1.0 / time_diff;
642 FILE *file = fopen(path,
"rb");
644 perror(
"Failed to open font file");
651 perror(
"Failed to allocate memory for font");
658 if (fread(header, 1,
sizeof(header), file) !=
sizeof(header)) {
659 perror(
"Failed to read font header");
666 if (header[0] != 0x36 || header[1] != 0x04) {
667 fprintf(stderr,
"Invalid PSF1 magic number\n");
674 font->magic[0] = header[0];
675 font->magic[1] = header[1];
676 font->mode = header[2];
677 font->char_height = header[3];
678 font->glyph_count = (font->mode & 0x01) ? 512 :
680 font->char_width = 8;
683 size_t glyph_data_size = font->glyph_count * font->char_height;
684 font->glyphs = malloc(glyph_data_size);
686 perror(
"Failed to allocate memory for glyphs");
693 if (fread(font->glyphs, 1, glyph_data_size, file) != glyph_data_size) {
694 perror(
"Failed to read glyph data");
714 int x,
int y, uint32_t color)
716 if (!fb || !font || !text)
722 for (
const char *c = text; *c; c++) {
723 uint8_t glyph_index = (uint8_t)*c;
726 if (glyph_index >= font->glyph_count)
730 uint8_t *glyph = font->glyphs + glyph_index * font->char_height;
733 for (
int row = 0; row < font->char_height; row++) {
734 for (
int col = 0; col < font->char_width; col++) {
736 if (glyph[row] & (0x80 >> col)) {
737 fbgl_put_pixel(cursor_x + col,
738 cursor_y + row, color,
745 cursor_x += font->char_width;
749 int fbgl_keyboard_init(
void)
751 i_fbgl_enable_raw_mode();
754 g_keyboard_state.is_key_down =
false;
755 g_keyboard_state.current_key = FBGL_KEY_NONE;
756 g_keyboard_state.special_key_pressed =
false;
761 void fbgl_destroy_keyboard(
void)
763 i_fbgl_disable_raw_mode();
765 fbgl_key_t fbgl_get_key(
void)
768 ssize_t bytes_read = read(STDIN_FILENO, &c, 1);
770 if (bytes_read <= 0) {
771 return FBGL_KEY_NONE;
777 if (read(STDIN_FILENO, &seq[0], 1) != 1)
778 return FBGL_KEY_ESCAPE;
779 if (read(STDIN_FILENO, &seq[1], 1) != 1)
780 return FBGL_KEY_ESCAPE;
787 return FBGL_KEY_DOWN;
789 return FBGL_KEY_RIGHT;
791 return FBGL_KEY_LEFT;
795 return FBGL_KEY_NONE;
801 return FBGL_KEY_ENTER;
803 return FBGL_KEY_SPACE;
809 return FBGL_KEY_DOWN;
812 return FBGL_KEY_LEFT;
815 return FBGL_KEY_RIGHT;
817 return FBGL_KEY_ESCAPE;
820 return FBGL_KEY_NONE;
823 bool fbgl_is_key_pressed(fbgl_key_t key)
827 struct timeval timeout;
830 FD_SET(STDIN_FILENO, &read_fds);
837 if (select(STDIN_FILENO + 1, &read_fds, NULL, NULL, &timeout) > 0) {
838 fbgl_key_t pressed_key = fbgl_get_key();
839 return pressed_key == key;