Files
multirom_m86/framebuffer.c
2013-07-27 17:42:34 +02:00

825 lines
18 KiB
C

/*
* This file is part of MultiROM.
*
* MultiROM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MultiROM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with MultiROM. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <assert.h>
#include <linux/fb.h>
#include <linux/kd.h>
#include <cutils/memory.h>
#include <pthread.h>
#include "log.h"
#include "framebuffer.h"
#include "iso_font.h"
#include "util.h"
#define PIXEL_SIZE 4
static struct FB framebuffers[2];
static int active_fb = 0;
static int fb_frozen = 0;
static fb_items_t fb_items = { NULL, NULL, NULL };
static fb_items_t **inactive_ctx = NULL;
uint32_t fb_width = 0;
uint32_t fb_height = 0;
int fb_rotation = 0; // in degrees, clockwise
static uint8_t **fb_rot_helpers = NULL;
static pthread_mutex_t fb_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t fb_draw_req_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_t fb_draw_thread;
static volatile int fb_draw_requested = 0;
static volatile int fb_draw_run = 0;
static void *fb_draw_thread_work(void*);
struct FB *fb = &framebuffers[0];
void fb_destroy_item(void *item); // private!
inline void fb_cpy_fb_with_rotation(void *dst, void *src);
#if PIXEL_SIZE == 4
inline void fb_rotate_90deg_4b(uint32_t *dst, uint32_t *src);
inline void fb_rotate_270deg_4b(uint32_t *dst, uint32_t *src);
inline void fb_rotate_180deg_4b(uint32_t *dst, uint32_t *src);
#elif PIXEL_SIZE == 2
inline void fb_rotate_90deg_2b(uint16_t *dst, uint16_t *src);
inline void fb_rotate_270deg_2b(uint16_t *dst, uint16_t *src);
inline void fb_rotate_180deg_2b(uint16_t *dst, uint16_t *src);
#endif
int vt_set_mode(int graphics)
{
int fd, r;
mknod("/dev/tty0", (0600 | S_IFCHR), makedev(4, 0));
fd = open("/dev/tty0", O_RDWR | O_SYNC);
if (fd < 0)
return -1;
r = ioctl(fd, KDSETMODE, (void*) (graphics ? KD_GRAPHICS : KD_TEXT));
close(fd);
unlink("/dev/tty0");
return r;
}
struct FB *get_active_fb()
{
return &framebuffers[active_fb];
}
int fb_open(int rotation)
{
fb_rotation = rotation;
int fd = open("/dev/graphics/fb0", O_RDWR);
if (fd < 0)
return -1;
struct fb_fix_screeninfo fi;
struct fb_var_screeninfo vi;
if (ioctl(fd, FBIOGET_FSCREENINFO, &fi) < 0)
goto fail;
if (ioctl(fd, FBIOGET_VSCREENINFO, &vi) < 0)
goto fail;
uint32_t *bits = mmap(0, fi.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (bits == MAP_FAILED)
goto fail;
if(fb_rotation%180 == 0)
{
fb_width = vi.xres;
fb_height = vi.yres;
}
else
{
fb_width = vi.yres;
fb_height = vi.xres;
}
fb_frozen = 0;
active_fb = 0;
uint32_t *b_store = malloc(vi.xres*vi.yres*PIXEL_SIZE);
android_memset32(b_store, BLACK, vi.xres*vi.yres*PIXEL_SIZE);
fb = &framebuffers[0];
fb->fd = fd;
fb->size = vi.xres*vi.yres*PIXEL_SIZE;
fb->vi = vi;
fb->fi = fi;
fb->bits = b_store;
fb->mapped = bits;
++fb;
fb->fd = fd;
fb->size = vi.xres*vi.yres*PIXEL_SIZE;
fb->vi = vi;
fb->fi = fi;
fb->bits = b_store;
fb->mapped = (void*) (((unsigned) bits) + vi.yres * fi.line_length);
--fb;
fb_update();
fb_draw_run = 1;
pthread_create(&fb_draw_thread, NULL, fb_draw_thread_work, NULL);
return 0;
fail:
close(fd);
return -1;
}
void fb_close(void)
{
fb_draw_run = 0;
pthread_join(fb_draw_thread, NULL);
free(fb_rot_helpers);
munmap(fb->mapped, fb->fi.smem_len);
close(fb->fd);
free(fb->bits);
}
void fb_set_active_framebuffer(unsigned n)
{
if (n > 1) return;
fb->vi.yres_virtual = fb->vi.yres * PIXEL_SIZE;
fb->vi.yoffset = n * fb->vi.yres;
fb->vi.bits_per_pixel = PIXEL_SIZE * 8;
if (ioctl(fb->fd, FBIOPUT_VSCREENINFO, &fb->vi) < 0) {
ERROR("active fb swap failed");
}
}
void fb_update(void)
{
active_fb = !active_fb;
fb_cpy_fb_with_rotation(get_active_fb()->mapped, fb->bits);
fb_set_active_framebuffer(active_fb);
}
void fb_cpy_fb_with_rotation(void *dst, void *src)
{
switch(fb_rotation)
{
case 0:
memcpy(dst, src, fb->vi.xres_virtual * fb->vi.yres * PIXEL_SIZE);
break;
case 90:
{
#if PIXEL_SIZE == 4
fb_rotate_90deg_4b((uint32_t*)dst, (uint32_t*)src);
#elif PIXEL_SIZE == 2
fb_rotate_90deg_2b((uint16_t*)dst, (uint16_t*)src);
#endif
break;
}
case 180:
{
#if PIXEL_SIZE == 4
fb_rotate_180deg_4b((uint32_t*)dst, (uint32_t*)src);
#elif PIXEL_SIZE == 2
fb_rotate_180deg_2b((uint16_t*)dst, (uint16_t*)src);
#endif
break;
}
case 270:
{
#if PIXEL_SIZE == 4
fb_rotate_270deg_4b((uint32_t*)dst, (uint32_t*)src);
#elif PIXEL_SIZE == 2
fb_rotate_270deg_2b((uint16_t*)dst, (uint16_t*)src);
#endif
break;
}
}
}
#if PIXEL_SIZE == 4
void fb_rotate_90deg_4b(uint32_t *dst, uint32_t *src)
{
uint32_t i;
int32_t x;
if(!fb_rot_helpers)
fb_rot_helpers = malloc(fb_height*sizeof(uint32_t*));
uint32_t **helpers = (uint32_t**)fb_rot_helpers;
helpers[0] = src;
for(i = 1; i < fb_height; ++i)
helpers[i] = helpers[i-1] + fb_width;
const int padding = fb->vi.xres_virtual - fb->vi.xres;
for(i = 0; i < fb_width; ++i)
{
for(x = fb_height-1; x >= 0; --x)
*dst++ = *(helpers[x]++);
dst += padding;
}
}
void fb_rotate_270deg_4b(uint32_t *dst, uint32_t *src)
{
if(!fb_rot_helpers)
fb_rot_helpers = malloc(fb_height*sizeof(uint32_t*));
uint32_t i, x;
uint32_t **helpers = (uint32_t**)fb_rot_helpers;
helpers[0] = src + fb_width-1;
for(i = 1; i < fb_height; ++i)
helpers[i] = helpers[i-1] + fb_width;
const int padding = fb->vi.xres_virtual - fb->vi.xres;
for(i = 0; i < fb_width; ++i)
{
for(x = 0; x < fb_height; ++x)
*dst++ = *(helpers[x]--);
dst += padding;
}
}
void fb_rotate_180deg_4b(uint32_t *dst, uint32_t *src)
{
uint32_t i, x;
int len = fb->vi.xres * fb->vi.yres;
src += len;
const int padding = fb->vi.xres_virtual - fb->vi.xres;
for(i = 0; i < fb_height; ++i)
{
for(x = 0; x < fb_width; ++x)
*dst++ = *src--;
dst += padding;
}
}
#elif PIXEL_SIZE == 2
void fb_rotate_90deg_2b(uint16_t *dst, uint16_t *src)
{
uint32_t i;
int32_t x;
if(!fb_rot_helpers)
fb_rot_helpers = malloc(fb_height*sizeof(uint16_t*));
uint16_t **helpers = (uint16_t**)fb_rot_helpers;
helpers[0] = src;
for(i = 1; i < fb_height; ++i)
helpers[i] = helpers[i-1] + fb_width;
const int padding = fb->vi.xres_virtual - fb->vi.xres;
for(i = 0; i < fb_width; ++i)
{
for(x = fb_height-1; x >= 0; --x)
*dst++ = *(helpers[x]++);
dst += padding;
}
}
void fb_rotate_270deg_2b(uint16_t *dst, uint16_t *src)
{
if(!fb_rot_helpers)
fb_rot_helpers = malloc(fb_height*sizeof(uint16_t*));
uint32_t i, x;
uint16_t **helpers = (uint16_t**)fb_rot_helpers;
helpers[0] = src + fb_width-1;
for(i = 1; i < fb_height; ++i)
helpers[i] = helpers[i-1] + fb_width;
const int padding = fb->vi.xres_virtual - fb->vi.xres;
for(i = 0; i < fb_width; ++i)
{
for(x = 0; x < fb_height; ++x)
*dst++ = *(helpers[x]--);
dst += padding;
}
}
void fb_rotate_180deg_2b(uint16_t *dst, uint16_t *src)
{
uint32_t i, x;
int len = fb->vi.xres * fb->vi.yres;
src += len;
const int padding = fb->vi.xres_virtual - fb->vi.xres;
for(i = 0; i < fb_height; ++i)
{
for(x = 0; x < fb_width; ++x)
*dst++ = *src--;
dst += padding;
}
}
#endif // PIXEL_SIZE == 2
int fb_clone(char **buff)
{
int len = fb->size;
*buff = malloc(len);
pthread_mutex_lock(&fb_mutex);
memcpy(*buff, fb->bits, len);
pthread_mutex_unlock(&fb_mutex);
return len;
}
void fb_fill(uint32_t color)
{
android_memset32(fb->bits, color, fb->size);
}
void fb_draw_text(fb_text *t)
{
int c_width = ISO_CHAR_WIDTH * t->size;
int c_height = ISO_CHAR_HEIGHT * t->size;
int linelen = fb_width/(c_width);
int x = t->head.x;
int y = t->head.y;
int i;
for(i = 0; t->text[i] != 0; ++i)
{
switch(t->text[i])
{
case '\n':
y += c_height;
x = t->head.x;
continue;
case '\r':
x = t->head.x;
continue;
case '\f':
x = t->head.x;
y = t->head.y;
continue;
}
if(x < fb_width)
fb_draw_char(x, y, t->text[i], t->color, t->size);
x += c_width;
}
}
void fb_draw_char(int x, int y, char c, uint32_t color, int size)
{
int line = 0;
uint8_t bit = 0;
unsigned char *f = (unsigned char*)iso_font + (ISO_CHAR_HEIGHT*c);
for(; line < ISO_CHAR_HEIGHT; ++line)
{
for(bit = 0; bit < ISO_CHAR_WIDTH; ++bit)
{
if(*f & (1 << bit))
fb_draw_square(x+(bit*size), y, color, size);
}
y += size;
++f;
}
}
void fb_draw_square(int x, int y, uint32_t color, int size)
{
uint32_t *bits = fb->bits + (fb_width*y) + x;
int i;
for(i = 0; i < size; ++i)
{
android_memset32(bits, color, size*PIXEL_SIZE);
bits += fb_width;
}
}
void fb_remove_item(void *item)
{
switch(((fb_item_header*)item)->type)
{
case FB_TEXT:
fb_rm_text((fb_text*)item);
break;
case FB_RECT:
fb_rm_rect((fb_rect*)item);
break;
case FB_BOX:
// fb_destroy_msgbox must be used
assert(0);
break;
}
}
void fb_destroy_item(void *item)
{
switch(((fb_item_header*)item)->type)
{
case FB_TEXT:
free(((fb_text*)item)->text);
break;
case FB_RECT:
break;
case FB_BOX:
// fb_destroy_msgbox must be used
assert(0);
break;
}
free(item);
}
void fb_draw_rect(fb_rect *r)
{
uint32_t *bits = fb->bits + (fb_width*r->head.y) + r->head.x;
int i;
for(i = 0; i < r->h; ++i)
{
android_memset32(bits, r->color, r->w*PIXEL_SIZE);
bits += fb_width;
}
}
int fb_generate_item_id()
{
pthread_mutex_lock(&fb_mutex);
static int id = 0;
int res = id++;
pthread_mutex_unlock(&fb_mutex);
return res;
}
static fb_text *fb_create_text_item(int x, int y, uint32_t color, int size, const char *txt)
{
fb_text *t = malloc(sizeof(fb_text));
t->head.id = fb_generate_item_id();
t->head.type = FB_TEXT;
t->head.x = x;
t->head.y = y;
t->color = color;
t->size = size;
t->text = malloc(strlen(txt)+1);
strcpy(t->text, txt);
return t;
}
fb_text *fb_add_text(int x, int y, uint32_t color, int size, const char *fmt, ...)
{
char txt[512];
va_list ap;
va_start(ap, fmt);
vsnprintf(txt, sizeof(txt), fmt, ap);
va_end(ap);
return fb_add_text_long(x, y, color, size, txt);
}
fb_text *fb_add_text_long(int x, int y, uint32_t color, int size, char *text)
{
fb_text *t = fb_create_text_item(x, y, color, size, text);
pthread_mutex_lock(&fb_mutex);
list_add(t, &fb_items.texts);
pthread_mutex_unlock(&fb_mutex);
return t;
}
fb_rect *fb_add_rect(int x, int y, int w, int h, uint32_t color)
{
fb_rect *r = malloc(sizeof(fb_rect));
r->head.id = fb_generate_item_id();
r->head.type = FB_RECT;
r->head.x = x;
r->head.y = y;
r->w = w;
r->h = h;
r->color = color;
pthread_mutex_lock(&fb_mutex);
list_add(r, &fb_items.rects);
pthread_mutex_unlock(&fb_mutex);
return r;
}
void fb_rm_text(fb_text *t)
{
if(!t)
return;
pthread_mutex_lock(&fb_mutex);
list_rm_noreorder(t, &fb_items.texts, &fb_destroy_item);
pthread_mutex_unlock(&fb_mutex);
}
void fb_rm_rect(fb_rect *r)
{
if(!r)
return;
pthread_mutex_lock(&fb_mutex);
list_rm_noreorder(r, &fb_items.rects, &fb_destroy_item);
pthread_mutex_unlock(&fb_mutex);
}
#define BOX_BORDER 2
#define SHADOW 10
fb_msgbox *fb_create_msgbox(int w, int h, int bgcolor)
{
if(fb_items.msgbox)
return fb_items.msgbox;
fb_msgbox *box = mzalloc(sizeof(fb_msgbox));
int x = fb_width/2 - w/2;
int y = fb_height/2 - h/2;
box->head.id = fb_generate_item_id();
box->head.type = FB_BOX;
box->head.x = x;
box->head.y = y;
box->w = w;
box->h = h;
box->background[0] = fb_add_rect(x+SHADOW, y+SHADOW, w, h, GRAY); // shadow
box->background[1] = fb_add_rect(x, y, w, h, WHITE); // border
box->background[2] = fb_add_rect(x+BOX_BORDER, y+BOX_BORDER,
w-BOX_BORDER*2, h-BOX_BORDER*2, bgcolor);
pthread_mutex_lock(&fb_mutex);
fb_items.msgbox = box;
pthread_mutex_unlock(&fb_mutex);
return box;
}
fb_text *fb_msgbox_add_text(int x, int y, int size, char *fmt, ...)
{
char txt[512];
va_list ap;
va_start(ap, fmt);
vsnprintf(txt, sizeof(txt), fmt, ap);
va_end(ap);
fb_msgbox *box = fb_items.msgbox;
if(x == -1)
x = center_x(0, box->w, size, txt);
if(y == -1)
y = center_y(0, box->h, size);
x += box->head.x;
y += box->head.y;
fb_text *t = fb_create_text_item(x, y, WHITE, size, txt);
pthread_mutex_lock(&fb_mutex);
list_add(t, &box->texts);
pthread_mutex_unlock(&fb_mutex);
return t;
}
void fb_msgbox_rm_text(fb_text *text)
{
if(!text)
return;
pthread_mutex_lock(&fb_mutex);
if(fb_items.msgbox)
list_rm_noreorder(text, &fb_items.msgbox->texts, &fb_destroy_item);
pthread_mutex_unlock(&fb_mutex);
}
void fb_destroy_msgbox(void)
{
pthread_mutex_lock(&fb_mutex);
if(!fb_items.msgbox)
{
pthread_mutex_unlock(&fb_mutex);
return;
}
fb_msgbox *box = fb_items.msgbox;
fb_items.msgbox = NULL;
pthread_mutex_unlock(&fb_mutex);
list_clear(&box->texts, &fb_destroy_item);
uint32_t i;
for(i = 0; i < ARRAY_SIZE(box->background); ++i)
fb_rm_rect(box->background[i]);
free(box);
}
void fb_clear(void)
{
pthread_mutex_lock(&fb_mutex);
list_clear(&fb_items.texts, &fb_destroy_item);
list_clear(&fb_items.rects, &fb_destroy_item);
pthread_mutex_unlock(&fb_mutex);
fb_destroy_msgbox();
}
#define ALPHA 220
#define BLEND_CLR 0x1B
static inline int blend(int value1, int value2) {
int r = (0xFF-ALPHA)*value1 + ALPHA*value2;
return (r+1 + (r >> 8)) >> 8; // divide by 255
}
void fb_draw_overlay(void)
{
union clr_t
{
int i;
uint8_t c[4];
};
int i;
union clr_t *unions = (union clr_t *)fb->bits;
const int size = fb_width*fb_height;
for(i = 0; i < size; ++i)
{
unions->c[0] = blend(unions->c[0], BLEND_CLR);
unions->c[1] = blend(unions->c[1], BLEND_CLR);
unions->c[2] = blend(unions->c[2], BLEND_CLR);
++unions;
}
}
void fb_draw(void)
{
if(fb_frozen)
return;
uint32_t i;
pthread_mutex_lock(&fb_mutex);
fb_fill(BLACK);
// rectangles
for(i = 0; fb_items.rects && fb_items.rects[i]; ++i)
fb_draw_rect(fb_items.rects[i]);
// texts
for(i = 0; fb_items.texts && fb_items.texts[i]; ++i)
fb_draw_text(fb_items.texts[i]);
// msg box
if(fb_items.msgbox)
{
fb_draw_overlay();
fb_msgbox *box = fb_items.msgbox;
for(i = 0; i < ARRAY_SIZE(box->background); ++i)
fb_draw_rect(box->background[i]);
for(i = 0; box->texts && box->texts[i]; ++i)
fb_draw_text(box->texts[i]);
}
fb_update();
pthread_mutex_unlock(&fb_mutex);
}
void fb_freeze(int freeze)
{
if(freeze)
++fb_frozen;
else
--fb_frozen;
}
int center_x(int x, int width, int size, const char *text)
{
return x + (width/2 - (strlen(text)*ISO_CHAR_WIDTH*size)/2);
}
int center_y(int y, int height, int size)
{
return y + (height/2 - (ISO_CHAR_HEIGHT*size)/2);
}
void fb_push_context(void)
{
fb_items_t *ctx = mzalloc(sizeof(fb_items_t));
pthread_mutex_lock(&fb_mutex);
list_move(&fb_items.texts, &ctx->texts);
list_move(&fb_items.rects, &ctx->rects);
ctx->msgbox = fb_items.msgbox;
fb_items.msgbox = NULL;
pthread_mutex_unlock(&fb_mutex);
list_add(ctx, &inactive_ctx);
}
void fb_pop_context(void)
{
if(!inactive_ctx)
return;
fb_clear();
int idx = list_item_count(inactive_ctx)-1;
fb_items_t *ctx = inactive_ctx[idx];
pthread_mutex_lock(&fb_mutex);
list_move(&ctx->texts, &fb_items.texts);
list_move(&ctx->rects, &fb_items.rects);
fb_items.msgbox = ctx->msgbox;
pthread_mutex_unlock(&fb_mutex);
list_rm_noreorder(ctx, &inactive_ctx, &free);
fb_draw();
}
#define SLEEP_CONST 16
void *fb_draw_thread_work(void *cookie)
{
volatile int req = 0;
struct timespec last, curr;
uint32_t diff = 0, prevSleepTime = 0;
clock_gettime(CLOCK_MONOTONIC, &last);
while(fb_draw_run)
{
clock_gettime(CLOCK_MONOTONIC, &curr);
diff = timespec_diff(&last, &curr);
pthread_mutex_lock(&fb_draw_req_mutex);
req = fb_draw_requested;
fb_draw_requested = 0;
pthread_mutex_unlock(&fb_draw_req_mutex);
if(req)
fb_draw();
last = curr;
if(diff <= SLEEP_CONST+prevSleepTime)
{
prevSleepTime = SLEEP_CONST+prevSleepTime-diff;
usleep(prevSleepTime*1000);
}
else
prevSleepTime = 0;
}
return NULL;
}
void fb_request_draw(void)
{
pthread_mutex_lock(&fb_draw_req_mutex);
fb_draw_requested = 1;
pthread_mutex_unlock(&fb_draw_req_mutex);
}