diff --git a/backend/fbdev.c b/backend/fbdev.c index e19b093..9f3e362 100644 --- a/backend/fbdev.c +++ b/backend/fbdev.c @@ -40,24 +40,41 @@ 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) -{ - twin_screen_t *screen = SCREEN(closure); - twin_fbdev_t *tx = PRIV(closure); - - if (tx->fb_base == MAP_FAILED) - return; +/* color conversion */ +#define ARGB32_TO_RGB565_PERLINE(dest, pixels, width) \ + do { \ + for (int i = 0; i < width; i++) \ + dest[i] = ((pixels[i] & 0x00f80000) >> 8) | \ + ((pixels[i] & 0x0000fc00) >> 5) | \ + ((pixels[i] & 0x000000f8) >> 3); \ + } while (0) + +/* Requires validation in 24-bit per pixel environments */ +#define ARGB32_TO_RGB888_PERLINE(dest, pixels, width) \ + do { \ + for (int i = 0; i < width; i++) \ + dest[i] = 0xff000000 | pixels[i]; \ + } while (0) + +#define ARGB32_TO_ARGB32_PERLINE(dest, pixels, width) \ + memcpy(dest, pixels, width * sizeof(*dest)) + +#define FBDEV_PUT_SPAN_IMPL(bpp, op) \ + static void _twin_fbdev_put_span##bpp( \ + twin_coord_t left, twin_coord_t top, twin_coord_t right, \ + twin_argb32_t *pixels, void *closure) \ + { \ + uint32_t *dest; \ + twin_fbdev_t *tx = PRIV(closure); \ + off_t off = sizeof(*dest) * left + top * tx->fb_fix.line_length; \ + dest = (uint32_t *) ((uintptr_t) tx->fb_base + off); \ + twin_coord_t width = right - left; \ + op(dest, pixels, width); \ + } - twin_coord_t width = right - left; - uint32_t *dest; - off_t off = sizeof(*dest) * left + top * tx->fb_fix.line_length; - dest = (uint32_t *) ((uintptr_t) tx->fb_base + off); - memcpy(dest, pixels, width * sizeof(*dest)); -} +FBDEV_PUT_SPAN_IMPL(16, ARGB32_TO_RGB565_PERLINE) +FBDEV_PUT_SPAN_IMPL(24, ARGB32_TO_RGB888_PERLINE) +FBDEV_PUT_SPAN_IMPL(32, ARGB32_TO_ARGB32_PERLINE) static void twin_fbdev_get_screen_size(twin_fbdev_t *tx, int *width, @@ -85,6 +102,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 */ @@ -96,7 +134,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; @@ -108,10 +145,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 */ @@ -172,9 +228,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);