From bed915dfb634125984d568d77e1a15bfcbf6752f Mon Sep 17 00:00:00 2001 From: David Herrmann Date: Sat, 11 Aug 2012 13:48:44 +0200 Subject: [PATCH] uterm: video: fbdev: add support for non xrgb32 devices This adds blitting/blending/filling support for devices which are not the classic xrgb32 device. bpp=24 is not supported as it is still unclear how 3-byte integers look like in mixed/big endian. This uses a very basic dithering technique to check for errors between the real and computed values which is then distributed horizontally only. Signed-off-by: David Herrmann --- src/uterm_internal.h | 14 ++- src/uterm_video_fbdev.c | 248 +++++++++++++++++++++++++++++++++------- 2 files changed, 219 insertions(+), 43 deletions(-) diff --git a/src/uterm_internal.h b/src/uterm_internal.h index 6fe933d..43e1095 100644 --- a/src/uterm_internal.h +++ b/src/uterm_internal.h @@ -249,7 +249,18 @@ struct fbdev_display { size_t len; uint8_t *map; unsigned int stride; - unsigned int bpp; + + bool xrgb32; + unsigned int Bpp; + unsigned int off_r; + unsigned int off_g; + unsigned int off_b; + unsigned int len_r; + unsigned int len_g; + unsigned int len_b; + int_fast32_t dither_r; + int_fast32_t dither_g; + int_fast32_t dither_b; }; struct fbdev_video { @@ -312,6 +323,7 @@ int mode_new(struct uterm_mode **out, const struct mode_ops *ops); #define DISPLAY_AVAILABLE 0x04 #define DISPLAY_OPEN 0x08 #define DISPLAY_DBUF 0x10 +#define DISPLAY_DITHERING 0x20 struct uterm_display { unsigned long ref; diff --git a/src/uterm_video_fbdev.c b/src/uterm_video_fbdev.c index d784330..bb37b06 100644 --- a/src/uterm_video_fbdev.c +++ b/src/uterm_video_fbdev.c @@ -81,7 +81,9 @@ static int display_activate_force(struct uterm_display *disp, struct uterm_mode *mode, bool force) { - static const char depths[] = { 32, 24, 16, 0 }; + /* TODO: Add support for 24-bpp. However, we need to check how 3-bytes + * integers are assembled in big/little/mixed endian systems. */ + static const char depths[] = { 32, 16, 0 }; struct fb_var_screeninfo *vinfo; struct fb_fix_screeninfo *finfo; int ret, i; @@ -112,10 +114,6 @@ static int display_activate_force(struct uterm_display *disp, vinfo->activate = FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE; vinfo->xres_virtual = vinfo->xres; vinfo->yres_virtual = vinfo->yres * 2; - vinfo->bits_per_pixel = 32; - - log_info("activating display %s to %ux%u %u bpp", disp->fbdev.node, - vinfo->xres, vinfo->yres, vinfo->bits_per_pixel); disp->flags |= DISPLAY_DBUF; /* udlfb is broken as it reports the sizes of the virtual framebuffer @@ -153,7 +151,8 @@ static int display_activate_force(struct uterm_display *disp, * modes like pseudocolor or direct-color do not provide this. As I have * never seen a device that does not support TRUECOLOR, I think we can * ignore them here. */ - if (finfo->visual != FB_VISUAL_TRUECOLOR) { + if (finfo->visual != FB_VISUAL_TRUECOLOR || + vinfo->bits_per_pixel != 32) { for (i = 0; depths[i]; ++i) { vinfo->bits_per_pixel = depths[i]; vinfo->activate = FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE; @@ -176,37 +175,35 @@ static int display_activate_force(struct uterm_display *disp, (disp->flags & DISPLAY_DBUF && vinfo->yres_virtual < vinfo->yres * 2) || vinfo->yres_virtual < vinfo->yres) { - log_error("device %s does not support out buffer sizes", + log_error("device %s has weird buffer sizes", disp->fbdev.node); return -EFAULT; } - if (vinfo->bits_per_pixel % 8) { - log_error("device %s uses no power of 8 bpp: %u", + if (vinfo->bits_per_pixel != 32 && + vinfo->bits_per_pixel != 16) { + log_error("device %s does not support 16/32 bpp but: %u", disp->fbdev.node, vinfo->bits_per_pixel); return -EFAULT; } - if (finfo->visual != FB_VISUAL_TRUECOLOR || - vinfo->bits_per_pixel < 16) { - log_error("device %s does not support true-color bpp >= 16", + if (finfo->visual != FB_VISUAL_TRUECOLOR) { + log_error("device %s does not support true-color", disp->fbdev.node); return -EFAULT; } - /* TODO: remove this check and correctly provide conversions for the - * blitting functions. In fact, the for-loop above is totally useless - * while using this restriction here but lets be optimistic and say that - * this will be replaced soon. */ - if (vinfo->red.offset != 16 || vinfo->red.length != 8 || - vinfo->green.offset != 8 || vinfo->green.length != 8 || - vinfo->blue.offset != 0 || vinfo->green.length != 8 || - vinfo->bits_per_pixel != 32) { - log_error("device %s does not support xrgb32", + if (vinfo->red.length > 8 || + vinfo->green.length > 8 || + vinfo->blue.length > 8) { + log_error("device %s uses unusual color-ranges", disp->fbdev.node); return -EFAULT; } + log_info("activating display %s to %ux%u %u bpp", disp->fbdev.node, + vinfo->xres, vinfo->yres, vinfo->bits_per_pixel); + /* calculate monitor rate, default is 60 Hz */ quot = (vinfo->upper_margin + vinfo->lower_margin + vinfo->yres); quot *= (vinfo->left_margin + vinfo->right_margin + vinfo->xres); @@ -232,9 +229,30 @@ static int display_activate_force(struct uterm_display *disp, disp->fbdev.xres = vinfo->xres; disp->fbdev.yres = vinfo->yres; disp->fbdev.len = len; - disp->fbdev.bpp = vinfo->bits_per_pixel / 8; disp->fbdev.stride = finfo->line_length; disp->fbdev.bufid = 0; + disp->fbdev.Bpp = vinfo->bits_per_pixel / 8; + disp->fbdev.off_r = vinfo->red.offset; + disp->fbdev.len_r = vinfo->red.length; + disp->fbdev.off_g = vinfo->green.offset; + disp->fbdev.len_g = vinfo->green.length; + disp->fbdev.off_b = vinfo->blue.offset; + disp->fbdev.len_b = vinfo->blue.length; + disp->fbdev.dither_r = 0; + disp->fbdev.dither_g = 0; + disp->fbdev.dither_b = 0; + disp->fbdev.xrgb32 = false; + if (disp->fbdev.len_r == 8 && + disp->fbdev.len_g == 8 && + disp->fbdev.len_b == 8 && + disp->fbdev.off_r == 16 && + disp->fbdev.off_g == 8 && + disp->fbdev.off_b == 0 && + disp->fbdev.Bpp == 4) + disp->fbdev.xrgb32 = true; + + /* TODO: make dithering configurable */ + disp->flags |= DISPLAY_DITHERING; ret = mode_new(&disp->modes, &fbdev_mode_ops); if (ret) { @@ -344,13 +362,75 @@ static int display_swap(struct uterm_display *disp) return 0; } +static int clamp_value(int val, int low, int up) +{ + if (val < low) + return low; + else if (val > up) + return up; + else + return val; +} + +static uint_fast32_t xrgb32_to_device(struct uterm_display *disp, + uint32_t pixel) +{ + uint8_t r, g, b, nr, ng, nb; + int i; + uint_fast32_t res; + + r = (pixel >> 16) & 0xff; + g = (pixel >> 8) & 0xff; + b = (pixel >> 0) & 0xff; + + if (disp->flags & DISPLAY_DITHERING) { + /* This is some very basic dithering which simply does small + * rotations in the lower pixel bits. TODO: Let's take a look + * at Floyd-Steinberg dithering which should give much better + * results. It is slightly slower, though. + * Or even better would be some Sierra filter like the Sierra + * LITE. */ + disp->fbdev.dither_r = r - disp->fbdev.dither_r; + disp->fbdev.dither_g = g - disp->fbdev.dither_g; + disp->fbdev.dither_b = b - disp->fbdev.dither_b; + r = clamp_value(disp->fbdev.dither_r, 0, 255) >> (8 - disp->fbdev.len_r); + g = clamp_value(disp->fbdev.dither_g, 0, 255) >> (8 - disp->fbdev.len_g); + b = clamp_value(disp->fbdev.dither_b, 0, 255) >> (8 - disp->fbdev.len_b); + nr = r << (8 - disp->fbdev.len_r); + ng = g << (8 - disp->fbdev.len_g); + nb = b << (8 - disp->fbdev.len_b); + + for (i = disp->fbdev.len_r; i < 8; i <<= 1) + nr |= nr >> i; + for (i = disp->fbdev.len_g; i < 8; i <<= 1) + ng |= ng >> i; + for (i = disp->fbdev.len_b; i < 8; i <<= 1) + nb |= nb >> i; + + disp->fbdev.dither_r = nr - disp->fbdev.dither_r; + disp->fbdev.dither_g = ng - disp->fbdev.dither_g; + disp->fbdev.dither_b = nb - disp->fbdev.dither_b; + + res = r << disp->fbdev.off_r; + res |= g << disp->fbdev.off_g; + res |= b << disp->fbdev.off_b; + } else { + res = (r >> (8 - disp->fbdev.len_r)) << disp->fbdev.off_r; + res |= (g >> (8 - disp->fbdev.len_g)) << disp->fbdev.off_g; + res |= (b >> (8 - disp->fbdev.len_b)) << disp->fbdev.off_b; + } + + return res; +} + static int display_blit(struct uterm_display *disp, const struct uterm_video_buffer *buf, unsigned int x, unsigned int y) { unsigned int tmp; uint8_t *dst, *src; - unsigned int width, height; + unsigned int width, height, i; + uint32_t val; if (!disp->video || !(disp->flags & DISPLAY_ONLINE)) return -EINVAL; @@ -379,13 +459,35 @@ static int display_blit(struct uterm_display *disp, dst = disp->fbdev.map; else dst = &disp->fbdev.map[disp->fbdev.yres * disp->fbdev.stride]; - dst = &dst[y * disp->fbdev.stride + x * disp->fbdev.bpp]; + dst = &dst[y * disp->fbdev.stride + x * disp->fbdev.Bpp]; src = buf->data; - while (height--) { - memcpy(dst, src, 4 * width); - dst += disp->fbdev.stride; - src += buf->stride; + if (disp->fbdev.xrgb32) { + while (height--) { + memcpy(dst, src, 4 * width); + dst += disp->fbdev.stride; + src += buf->stride; + } + } else if (disp->fbdev.Bpp == 2) { + while (height--) { + for (i = 0; i < width; ++i) { + val = ((uint32_t*)src)[i]; + ((uint16_t*)dst)[i] = xrgb32_to_device(disp, val); + } + dst += disp->fbdev.stride; + src += buf->stride; + } + } else if (disp->fbdev.Bpp == 4) { + while (height--) { + for (i = 0; i < width; ++i) { + val = ((uint32_t*)src)[i]; + ((uint32_t*)dst)[i] = xrgb32_to_device(disp, val); + } + dst += disp->fbdev.stride; + src += buf->stride; + } + } else { + log_debug("invalid Bpp"); } return 0; @@ -401,11 +503,14 @@ static int display_blend(struct uterm_display *disp, uint8_t *dst, *src; unsigned int width, height, i; unsigned int r, g, b; + uint32_t val; if (!disp->video || !(disp->flags & DISPLAY_ONLINE)) return -EINVAL; if (!buf || !video_is_awake(disp->video)) return -EINVAL; + if (buf->format != UTERM_FORMAT_GREY) + return -EINVAL; tmp = x + buf->width; if (tmp < x || x >= disp->fbdev.xres) @@ -427,10 +532,10 @@ static int display_blend(struct uterm_display *disp, dst = disp->fbdev.map; else dst = &disp->fbdev.map[disp->fbdev.yres * disp->fbdev.stride]; - dst = &dst[y * disp->fbdev.stride + x * disp->fbdev.bpp]; + dst = &dst[y * disp->fbdev.stride + x * disp->fbdev.Bpp]; src = buf->data; - if (buf->format == UTERM_FORMAT_GREY) { + if (disp->fbdev.xrgb32) { while (height--) { for (i = 0; i < width; ++i) { r = (fr & 0xff) * src[i] / 255 + @@ -439,16 +544,50 @@ static int display_blend(struct uterm_display *disp, (bg & 0xff) * (255 - src[i]) / 255; b = (fb & 0xff) * src[i] / 255 + (bb & 0xff) * (255 - src[i]) / 255; - ((uint32_t*)dst)[i] = - ((r & 0xff) << 16) | - ((g & 0xff) << 8) | - (b & 0xff); + val = (r & 0xff) << 16; + val |= (g & 0xff) << 8; + val |= (b & 0xff) << 0; + ((uint32_t*)dst)[i] = val; + } + dst += disp->fbdev.stride; + src += buf->stride; + } + } else if (disp->fbdev.Bpp == 2) { + while (height--) { + for (i = 0; i < width; ++i) { + r = (fr & 0xff) * src[i] / 255 + + (br & 0xff) * (255 - src[i]) / 255; + g = (fg & 0xff) * src[i] / 255 + + (bg & 0xff) * (255 - src[i]) / 255; + b = (fb & 0xff) * src[i] / 255 + + (bb & 0xff) * (255 - src[i]) / 255; + val = (r & 0xff) << 16; + val |= (g & 0xff) << 8; + val |= (b & 0xff) << 0; + ((uint16_t*)dst)[i] = xrgb32_to_device(disp, val); + } + dst += disp->fbdev.stride; + src += buf->stride; + } + } else if (disp->fbdev.Bpp == 4) { + while (height--) { + for (i = 0; i < width; ++i) { + r = (fr & 0xff) * src[i] / 255 + + (br & 0xff) * (255 - src[i]) / 255; + g = (fg & 0xff) * src[i] / 255 + + (bg & 0xff) * (255 - src[i]) / 255; + b = (fb & 0xff) * src[i] / 255 + + (bb & 0xff) * (255 - src[i]) / 255; + val = (r & 0xff) << 16; + val |= (g & 0xff) << 8; + val |= (b & 0xff) << 0; + ((uint32_t*)dst)[i] = xrgb32_to_device(disp, val); } dst += disp->fbdev.stride; src += buf->stride; } } else { - log_warning("using unsupported buffer format for blending"); + log_warning("invalid Bpp"); } return 0; @@ -461,6 +600,7 @@ static int display_fill(struct uterm_display *disp, { unsigned int tmp, i; uint8_t *dst; + uint32_t full_val, rgb32; if (!disp->video || !(disp->flags & DISPLAY_ONLINE)) return -EINVAL; @@ -482,15 +622,39 @@ static int display_fill(struct uterm_display *disp, dst = disp->fbdev.map; else dst = &disp->fbdev.map[disp->fbdev.yres * disp->fbdev.stride]; - dst = &dst[y * disp->fbdev.stride + x * disp->fbdev.bpp]; + dst = &dst[y * disp->fbdev.stride + x * disp->fbdev.Bpp]; - while (height--) { - for (i = 0; i < width; ++i) { - ((uint32_t*)dst)[i] = ((r & 0xff) << 16) | - ((g & 0xff) << 8) | - (b & 0xff); + full_val = ((r & 0xff) >> (8 - disp->fbdev.len_r)) << disp->fbdev.off_r; + full_val |= ((g & 0xff) >> (8 - disp->fbdev.len_g)) << disp->fbdev.off_g; + full_val |= ((b & 0xff) >> (8 - disp->fbdev.len_b)) << disp->fbdev.off_b; + + if (disp->fbdev.Bpp == 2) { + if (disp->flags & DISPLAY_DITHERING) { + rgb32 = (r & 0xff) << 16; + rgb32 |= (g & 0xff) << 8; + rgb32 |= (b & 0xff) << 0; + while (height--) { + for (i = 0; i < width; ++i) + ((uint16_t*)dst)[i] = xrgb32_to_device(disp, rgb32); + dst += disp->fbdev.stride; + } + } else { + full_val &= 0xffff; + while (height--) { + for (i = 0; i < width; ++i) + ((uint16_t*)dst)[i] = full_val; + dst += disp->fbdev.stride; + } } - dst += disp->fbdev.stride; + } else if (disp->fbdev.Bpp == 4) { + while (height--) { + for (i = 0; i < width; ++i) + ((uint32_t*)dst)[i] = full_val; + dst += disp->fbdev.stride; + } + } else { + log_error("invalid Bpp"); + return -EFAULT; } return 0;