diff --git a/backend/fbdev.c b/backend/fbdev.c index d7fb08e..ac93328 100644 --- a/backend/fbdev.c +++ b/backend/fbdev.c @@ -22,6 +22,20 @@ #define FBDEV_DEFAULT "/dev/fb0" #define SCREEN(x) ((twin_context_t *) x)->screen #define PRIV(x) ((twin_fbdev_t *) ((twin_context_t *) x)->priv) +#define ARGB32_TO_RGB565(pixel) \ + (((pixel & 0x00f80000) >> 8) | ((pixel & 0x0000fc00) >> 5) | \ + ((pixel & 0x000000f8) >> 3)) + +/* Requires validation in 24-bit per pixel environments. */ +#define ARGB32_TO_RGB888(pixel) (0xff000000 | (pixel)) +#define GET_TWIN_FBDEV(left, top, closure, dest) \ + do { \ + twin_screen_t *screen = SCREEN(closure); \ + twin_fbdev_t *tx = PRIV(closure); \ + off_t off = (top) * (screen->width) + (left); \ + *(dest) = \ + (uint32_t *) ((uintptr_t) tx->fb_base + (off * sizeof(uint32_t))); \ + } while (0) typedef struct { twin_screen_t *screen; @@ -43,22 +57,43 @@ typedef struct { size_t fb_len; } twin_fbdev_t; -static void _twin_fbdev_put_span(twin_coord_t left, - twin_coord_t top, - twin_coord_t right, - twin_argb32_t *pixels, - void *closure) +static void _twin_fbdev_put_span16(twin_coord_t left, + twin_coord_t top, + twin_coord_t right, + twin_argb32_t *pixels, + void *closure) { - twin_screen_t *screen = SCREEN(closure); - twin_fbdev_t *tx = PRIV(closure); + uint32_t *dest; + GET_TWIN_FBDEV(left, top, closure, &dest); + twin_coord_t width = right - left; + for (int i = 0; i < width; i++) { + dest[i] = ARGB32_TO_RGB565(pixels[i]); + } +} - if (tx->fb_base == MAP_FAILED) - return; +static void _twin_fbdev_put_span24(twin_coord_t left, + twin_coord_t top, + twin_coord_t right, + twin_argb32_t *pixels, + void *closure) +{ + uint32_t *dest; + GET_TWIN_FBDEV(left, top, closure, &dest); + twin_coord_t width = right - left; + for (int i = 0; i < width; i++) { + dest[i] = ARGB32_TO_RGB888(pixels[i]); + } +} +static void _twin_fbdev_put_span32(twin_coord_t left, + twin_coord_t top, + twin_coord_t right, + twin_argb32_t *pixels, + void *closure) +{ + uint32_t *dest; + GET_TWIN_FBDEV(left, top, closure, &dest); twin_coord_t width = right - left; - off_t off = top * screen->width + left; - uint32_t *dest = - (uint32_t *) ((uintptr_t) tx->fb_base + (off * sizeof(*dest))); memcpy(dest, pixels, width * sizeof(*dest)); } @@ -88,6 +123,27 @@ static bool twin_fbdev_work(void *closure) return true; } +static inline bool twin_fbdev_is_rgb565(twin_fbdev_t *tx) +{ + return tx->fb_var.red.offset == 11 && tx->fb_var.red.length == 5 && + tx->fb_var.green.offset == 5 && tx->fb_var.green.length == 6 && + tx->fb_var.blue.offset == 0 && tx->fb_var.blue.length == 5; +} + +static inline bool twin_fbdev_is_rgb888(twin_fbdev_t *tx) +{ + return tx->fb_var.red.offset == 16 && tx->fb_var.red.length == 8 && + tx->fb_var.green.offset == 8 && tx->fb_var.green.length == 8 && + tx->fb_var.blue.offset == 0 && tx->fb_var.blue.length == 8; +} + +static inline bool twin_fbdev_is_argb32(twin_fbdev_t *tx) +{ + return tx->fb_var.red.offset == 16 && tx->fb_var.red.length == 8 && + tx->fb_var.green.offset == 8 && tx->fb_var.green.length == 8 && + tx->fb_var.blue.offset == 0 && tx->fb_var.blue.length == 8; +} + static bool twin_fbdev_apply_config(twin_fbdev_t *tx) { /* Read changable information of the framebuffer */ @@ -99,7 +155,6 @@ static bool twin_fbdev_apply_config(twin_fbdev_t *tx) /* Set the virtual screen size to be the same as the physical screen */ tx->fb_var.xres_virtual = tx->fb_var.xres; tx->fb_var.yres_virtual = tx->fb_var.yres; - tx->fb_var.bits_per_pixel = 32; if (ioctl(tx->fb_fd, FBIOPUT_VSCREENINFO, &tx->fb_var) < 0) { log_error("Failed to set framebuffer mode"); return false; @@ -111,10 +166,29 @@ static bool twin_fbdev_apply_config(twin_fbdev_t *tx) return false; } - /* Check bits per pixel */ - if (tx->fb_var.bits_per_pixel != 32) { - log_error("Failed to set framebuffer bpp to 32"); - return false; + /* Examine the framebuffer format */ + switch (tx->fb_var.bits_per_pixel) { + case 16: /* RGB565 */ + if (!twin_fbdev_is_rgb565(tx)) { + log_error("Invalid framebuffer format for 16 bpp"); + return false; + } + break; + case 24: /* RGB888 */ + if (!twin_fbdev_is_rgb888(tx)) { + log_error("Invalid framebuffer format for 24 bpp"); + return false; + } + break; + case 32: /* ARGB32 */ + if (!twin_fbdev_is_argb32(tx)) { + log_error("Invalid framebuffer format for 32 bpp"); + return false; + } + break; + default: + log_error("Unsupported bits per pixel: %d", tx->fb_var.bits_per_pixel); + break; } /* Read unchangable information of the framebuffer */ @@ -220,9 +294,21 @@ twin_context_t *twin_fbdev_init(int width, int height) goto bail_vt_fd; } + /* Examine if framebuffer mapping is valid */ + if (tx->fb_base == MAP_FAILED) { + log_error("Failed to map framebuffer memory"); + return; + } + + const twin_put_span_t fbdev_put_spans[] = { + _twin_fbdev_put_span16, + _twin_fbdev_put_span24, + _twin_fbdev_put_span32, + }; /* Create TWIN screen */ - ctx->screen = - twin_screen_create(width, height, NULL, _twin_fbdev_put_span, ctx); + ctx->screen = twin_screen_create( + width, height, NULL, fbdev_put_spans[tx->fb_var.bits_per_pixel / 8 - 2], + ctx); /* Create Linux input system object */ tx->input = twin_linux_input_create(ctx->screen);