/* * Copyright © 2013 Siarhei Siamashka * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include "fb_copyarea.h" /* * HACK: non-standard ioctl, which provides access to fb_copyarea accelerated * function in the kernel. It just accepts the standard fb_copyarea structure * defined in "linux/fb.h" header */ #define FBIOCOPYAREA _IOW('z', 0x21, struct fb_copyarea) /* * HACK: another non-standard ioctl, which is used to check whether the * fbdev kernel driver actually returns errors on unsupported ioctls. */ #define FBUNSUPPORTED _IOW('z', 0x22, struct fb_copyarea) /* Fallback to CPU when handling less than COPYAREA_BLT_SIZE_THRESHOLD pixels */ #define COPYAREA_BLT_SIZE_THRESHOLD 90 fb_copyarea_t *fb_copyarea_init(const char *device, void *xserver_fbmem) { fb_copyarea_t *ctx = calloc(sizeof(fb_copyarea_t), 1); struct fb_var_screeninfo fb_var; struct fb_fix_screeninfo fb_fix; struct fb_copyarea copyarea; int tmp, version; int gfx_layer_size; int ovl_layer_size; /* use /dev/fb0 by default */ if (!device) device = "/dev/fb0"; ctx->fd = open(device, O_RDWR); if (ctx->fd < 0) { close(ctx->fd); free(ctx); return NULL; } /* * Check if the unsupported dummy ioctl fails. If it does not, then the * kernel framebuffer driver is buggy and does not handle errors correctly. */ if (ioctl(ctx->fd, FBUNSUPPORTED, ©area) == 0) { close(ctx->fd); free(ctx); return NULL; } /* * Check whether the FBIOCOPYAREA ioctl is supported by requesting to do * a copy of 1x1 rectangle in the top left corner to itself */ copyarea.sx = 0; copyarea.sy = 0; copyarea.dx = 0; copyarea.dy = 0; copyarea.width = 1; copyarea.height = 1; if (ioctl(ctx->fd, FBIOCOPYAREA, ©area) != 0) { close(ctx->fd); free(ctx); return NULL; } if (ioctl(ctx->fd, FBIOGET_VSCREENINFO, &fb_var) < 0 || ioctl(ctx->fd, FBIOGET_FSCREENINFO, &fb_fix) < 0) { close(ctx->fd); free(ctx); return NULL; } if (fb_fix.line_length % 4) { close(ctx->fd); free(ctx); return NULL; } /* store the already existing mapping done by xserver */ ctx->xserver_fbmem = xserver_fbmem; ctx->xres = fb_var.xres; ctx->yres = fb_var.yres; ctx->bits_per_pixel = fb_var.bits_per_pixel; ctx->framebuffer_paddr = fb_fix.smem_start; ctx->framebuffer_size = fb_fix.smem_len; ctx->framebuffer_height = ctx->framebuffer_size / (ctx->xres * ctx->bits_per_pixel / 8); ctx->gfx_layer_size = ctx->xres * ctx->yres * fb_var.bits_per_pixel / 8; ctx->framebuffer_stride = fb_fix.line_length / 4; if (ctx->framebuffer_size < ctx->gfx_layer_size) { close(ctx->fd); free(ctx); return NULL; } if (ctx->xserver_fbmem) { /* use already existing mapping */ ctx->framebuffer_addr = ctx->xserver_fbmem; } else { /* mmap framebuffer memory */ ctx->framebuffer_addr = (uint8_t *)mmap(0, ctx->framebuffer_size, PROT_READ | PROT_WRITE, MAP_SHARED, ctx->fd, 0); if (ctx->framebuffer_addr == MAP_FAILED) { close(ctx->fd); free(ctx); return NULL; } } ctx->blt2d.self = ctx; ctx->blt2d.overlapped_blt = fb_copyarea_blt; return ctx; } void fb_copyarea_close(fb_copyarea_t *ctx) { close(ctx->fd); free(ctx); } static inline int try_fallback_blt(void *self, uint32_t *src_bits, uint32_t *dst_bits, int src_stride, int dst_stride, int src_bpp, int dst_bpp, int src_x, int src_y, int dst_x, int dst_y, int w, int h) { fb_copyarea_t *ctx = (fb_copyarea_t *)self; if (ctx->fallback_blt2d) return ctx->fallback_blt2d->overlapped_blt(ctx->fallback_blt2d->self, src_bits, dst_bits, src_stride, dst_stride, src_bpp, dst_bpp, src_x, src_y, dst_x, dst_y, w, h); return 0; } #define FALLBACK_BLT() try_fallback_blt(self, src_bits, \ dst_bits, src_stride, \ dst_stride, src_bpp, \ dst_bpp, src_x, src_y, \ dst_x, dst_y, w, h); int fb_copyarea_blt(void *self, uint32_t *src_bits, uint32_t *dst_bits, int src_stride, int dst_stride, int src_bpp, int dst_bpp, int src_x, int src_y, int dst_x, int dst_y, int w, int h) { fb_copyarea_t *ctx = (fb_copyarea_t *)self; struct fb_copyarea copyarea; uint32_t *framebuffer_addr = (uint32_t *)ctx->framebuffer_addr; /* Zero size blit, nothing to do */ if (w <= 0 || h <= 0) return 1; if (src_bpp != dst_bpp || src_bpp != ctx->bits_per_pixel || src_stride != dst_stride || src_stride != ctx->framebuffer_stride || src_bits != dst_bits || src_bits != framebuffer_addr) { return FALLBACK_BLT(); } if (w * h < COPYAREA_BLT_SIZE_THRESHOLD) return FALLBACK_BLT(); copyarea.sx = src_x; copyarea.sy = src_y; copyarea.dx = dst_x; copyarea.dy = dst_y; copyarea.width = w; copyarea.height = h; return ioctl(ctx->fd, FBIOCOPYAREA, ©area) == 0; }