Files
multirom_m86/lib/framebuffer.c
KINGbabasula 55600f4acc Use a macro for unused variables
Change-Id: I491bea842274029feb965b71c3a62b5953c2ce5c
2015-03-23 14:57:20 +01:00

1207 lines
31 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 <png.h>
#include <math.h>
#include "log.h"
#include "framebuffer.h"
#include "util.h"
#include "containers.h"
#include "animation.h"
#include "listview.h"
#include "atomics.h"
#include "mrom_data.h"
#if PIXEL_SIZE == 4
#define fb_memset(dst, what, len) android_memset32(dst, what, len)
#else
#define fb_memset(dst, what, len) android_memset16(dst, what, len)
#endif
uint32_t fb_width = 0;
uint32_t fb_height = 0;
int fb_rotation = 0; // in degrees, clockwise
fb_item_pos DEFAULT_FB_PARENT = {
.x = 0,
.y = 0,
};
static struct framebuffer fb;
static int fb_frozen = 0;
static int fb_force_generic = 0;
static fb_context_t fb_ctx = {
.first_item = NULL,
.batch_started = 0,
.background_color = BLACK,
.mutex = PTHREAD_MUTEX_INITIALIZER
};
static fb_context_t **inactive_ctx = NULL;
static uint8_t **fb_rot_helpers = NULL;
static pthread_t fb_draw_thread;
static pthread_mutex_t fb_update_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t fb_draw_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t fb_draw_cond = PTHREAD_COND_INITIALIZER;
static atomic_int fb_draw_requested = ATOMIC_VAR_INIT(0);
static volatile int fb_draw_run = 0;
static void *fb_draw_thread_work(void*);
static void fb_destroy_item(void *item); // private!
static inline void fb_cpy_fb_with_rotation(px_type *dst, px_type *src);
static inline void fb_rotate_90deg(px_type *dst, px_type *src);
static inline void fb_rotate_270deg(px_type *dst, px_type *src);
static inline void fb_rotate_180deg(px_type *dst, px_type *src);
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 | O_CLOEXEC);
if (fd < 0)
return -1;
r = ioctl(fd, KDSETMODE, (void*) (graphics ? KD_GRAPHICS : KD_TEXT));
close(fd);
unlink("/dev/tty0");
return r;
}
int fb_open_impl(void)
{
struct fb_impl **itr;
struct fb_impl *impls[FB_IMPL_CNT];
#define ADD_IMPL(ID, N) \
extern struct fb_impl fb_impl_ ## N; \
impls[ID] = &fb_impl_ ## N;
ADD_IMPL(FB_IMPL_GENERIC, generic);
#ifdef MR_USE_QCOM_OVERLAY
ADD_IMPL(FB_IMPL_QCOM_OVERLAY, qcom_overlay);
#endif
if(fb_force_generic)
itr = &impls[FB_IMPL_GENERIC];
else
itr = impls;
for(; *itr; ++itr)
{
if((*itr)->open(&fb) >= 0)
{
INFO("Framebuffer implementation: %s\n", (*itr)->name);
fb.impl = *itr;
return 0;
}
}
ERROR("All framebuffer implementations have failed to open!\n");
return -1;
}
int fb_open(int rotation)
{
memset(&fb, 0, sizeof(struct framebuffer));
fb.fd = open("/dev/graphics/fb0", O_RDWR | O_CLOEXEC);
if (fb.fd < 0)
return -1;
if(ioctl(fb.fd, FBIOGET_VSCREENINFO, &fb.vi) < 0)
goto fail;
if(ioctl(fb.fd, FBIOGET_FSCREENINFO, &fb.fi) < 0)
goto fail;
/*
* No FBIOPUT_VSCREENINFO ioctl must be called here. Flo's display drivers
* contain a hack to set backlight while in recovery, which is triggered by
* this ioctl (and probably other things). The hack turns *something* on
* and it causes insane battery drain while in android (it eats at least
* five times more energy). The device enters deep sleep just fine, the dmesg
* says it was suspended, but it drains more energy. Qualcomm ION overlay
* framebuffer implementation works around this hack, because it doesn't
* require that ioctl (but we can't set pixel format without it, must use
* framebuffer's default format in "TARGET_RECOVERY_PIXEL_FORMAT" config
* value). This bug does not manifest when MultiROM isn't installed,
* because nothing sets FBIOPUT_VSCREENINFO during Android's boot,
* and well, you're not really supposed to stay long in recovery nor does
* it have "suspend" state.
*
* This ioctl call was moved to framebuffer_generic implementation.
*/
if(fb_open_impl() < 0)
goto fail;
fb_frozen = 0;
fb_rotation = rotation;
if(fb_rotation%180 == 0)
{
fb_width = fb.vi.xres;
fb_height = fb.vi.yres;
}
else
{
fb_width = fb.vi.yres;
fb_height = fb.vi.xres;
}
#ifdef RECOVERY_GRAPHICS_USE_LINELENGTH
fb.vi.xres_virtual = fb.fi.line_length / PIXEL_SIZE;
#endif
fb.stride = (fb_rotation%180 == 0) ? fb.vi.xres_virtual : fb.vi.yres;
fb.size = fb.vi.xres_virtual*fb.vi.yres*PIXEL_SIZE;
fb.buffer = malloc(fb.size);
fb_memset(fb.buffer, fb_convert_color(BLACK), fb.size);
#if 0
fb_dump_info();
#endif
DEFAULT_FB_PARENT.w = fb_width;
DEFAULT_FB_PARENT.h = fb_height;
fb_set_brightness(MULTIROM_DEFAULT_BRIGHTNESS);
fb_update();
fb_draw_run = 1;
pthread_create(&fb_draw_thread, NULL, fb_draw_thread_work, NULL);
return 0;
fail:
close(fb.fd);
return -1;
}
void fb_close(void)
{
fb_draw_run = 0;
pthread_join(fb_draw_thread, NULL);
free(fb_rot_helpers);
fb_rot_helpers = NULL;
fb.impl->close(&fb);
fb.impl = NULL;
close(fb.fd);
free(fb.buffer);
fb.buffer = NULL;
}
void fb_dump_info(void)
{
ERROR("Framebuffer:\n");
ERROR("fi.smem_len: %u\n", fb.fi.smem_len);
ERROR("fi.type: %u\n", fb.fi.type);
ERROR("fi.type_aux: %u\n", fb.fi.type_aux);
ERROR("fi.visual: %u\n", fb.fi.visual);
ERROR("fi.xpanstep: %u\n", fb.fi.xpanstep);
ERROR("fi.ypanstep: %u\n", fb.fi.ypanstep);
ERROR("fi.ywrapstep: %u\n", fb.fi.ywrapstep);
ERROR("fi.line_length: %u\n", fb.fi.line_length);
ERROR("fi.mmio_start: %p\n", (void*)fb.fi.mmio_start);
ERROR("fi.mmio_len: %u\n", fb.fi.mmio_len);
ERROR("fi.accel: %u\n", fb.fi.accel);
ERROR("vi.xres: %u\n", fb.vi.xres);
ERROR("vi.yres: %u\n", fb.vi.yres);
ERROR("vi.xres_virtual: %u\n", fb.vi.xres_virtual);
ERROR("vi.yres_virtual: %u\n", fb.vi.yres_virtual);
ERROR("vi.xoffset: %u\n", fb.vi.xoffset);
ERROR("vi.yoffset: %u\n", fb.vi.yoffset);
ERROR("vi.bits_per_pixel: %u\n", fb.vi.bits_per_pixel);
ERROR("vi.grayscale: %u\n", fb.vi.grayscale);
ERROR("vi.red: offset: %u len: %u msb_right: %u\n", fb.vi.red.offset, fb.vi.red.length, fb.vi.red.msb_right);
ERROR("vi.green: offset: %u len: %u msb_right: %u\n", fb.vi.green.offset, fb.vi.green.length, fb.vi.green.msb_right);
ERROR("vi.blue: offset: %u len: %u msb_right: %u\n", fb.vi.blue.offset, fb.vi.blue.length, fb.vi.blue.msb_right);
ERROR("vi.transp: offset: %u len: %u msb_right: %u\n", fb.vi.transp.offset, fb.vi.transp.length, fb.vi.transp.msb_right);
ERROR("vi.nonstd: %u\n", fb.vi.nonstd);
ERROR("vi.activate: %u\n", fb.vi.activate);
ERROR("vi.height: %u\n", fb.vi.height);
ERROR("vi.width: %u\n", fb.vi.width);
ERROR("vi.accel_flags: %u\n", fb.vi.accel_flags);
}
void fb_set_brightness(int val)
{
#ifdef TW_BRIGHTNESS_PATH
FILE *f = fopen(TW_BRIGHTNESS_PATH, "we");
if(!f)
{
ERROR("Failed to set brightness: %s!\n", strerror(errno));
return;
}
fprintf(f, "%d", val);
fclose(f);
#endif
}
int fb_get_vi_xres(void)
{
return fb.vi.xres;
}
int fb_get_vi_yres(void)
{
return fb.vi.yres;
}
void fb_force_generic_impl(int force)
{
fb_force_generic = force;
}
void fb_update(void)
{
fb_cpy_fb_with_rotation(fb.impl->get_frame_dest(&fb), fb.buffer);
fb.impl->update(&fb);
}
void fb_cpy_fb_with_rotation(px_type *dst, px_type *src)
{
switch(fb_rotation)
{
case 0:
memcpy(dst, src, fb.vi.xres_virtual * fb.vi.yres * PIXEL_SIZE);
break;
case 90:
fb_rotate_90deg(dst, src);
break;
case 180:
fb_rotate_180deg(dst, src);
break;
case 270:
fb_rotate_270deg(dst, src);
break;
}
}
void fb_rotate_90deg(px_type *dst, px_type *src)
{
uint32_t i;
int32_t x;
if(!fb_rot_helpers)
fb_rot_helpers = malloc(fb_height*sizeof(px_type*));
px_type **helpers = (px_type**)fb_rot_helpers;
helpers[0] = src;
for(i = 1; i < fb_height; ++i)
helpers[i] = helpers[i-1] + fb.stride;
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(px_type *dst, px_type *src)
{
if(!fb_rot_helpers)
fb_rot_helpers = malloc(fb_height*sizeof(px_type*));
uint32_t i, x;
px_type **helpers = (px_type**)fb_rot_helpers;
helpers[0] = src + fb_width-1;
for(i = 1; i < fb_height; ++i)
helpers[i] = helpers[i-1] + fb.stride;
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(px_type *dst, px_type *src)
{
uint32_t i, x;
int len = fb.vi.xres_virtual * fb.vi.yres;
src += len;
const int padding = fb.vi.xres_virtual - fb.vi.xres;
for(i = 0; i < fb_height; ++i)
{
src -= padding;
for(x = 0; x < fb_width; ++x)
*dst++ = *(--src);
dst += padding;
}
}
int fb_clone(char **buff)
{
int len = fb.size;
*buff = malloc(len);
pthread_mutex_lock(&fb_update_mutex);
memcpy(*buff, fb.buffer, len);
pthread_mutex_unlock(&fb_update_mutex);
return len;
}
void fb_fill(uint32_t color)
{
fb_memset(fb.buffer, fb_convert_color(color), fb.size);
}
px_type fb_convert_color(uint32_t c)
{
#ifdef RECOVERY_BGRA
return c;
#elif defined(RECOVERY_RGBX)
// A B G R
return (c & 0xFF000000) | ((c & 0xFF) << 16) | (c & 0xFF00) | ((c & 0xFF0000) >> 16);
#elif defined(RECOVERY_RGB_565)
const uint8_t alpha_pct = (((c >> 24) & 0xFF)*100) / 0xFF;
// R G B
return (((c & 0xFF0000) >> 19) << 11) | (((c & 0xFF00) >> 10) << 5) | ((c & 0xFF) >> 3);
#else
#error "Unknown pixel format"
#endif
}
uint32_t fb_convert_color_img(uint32_t clr)
{
uint32_t c = fb_convert_color(clr);
#if PIXEL_SIZE == 2
const uint8_t alpha_pct = (((clr >> 24) & 0xFF)*100) / 0xFF;
// Alpha - RB Alpha - G
c |= (((alpha_pct*31)/100) << 16) | (((alpha_pct*63)/100) << 24);
#endif
return c;
}
void fb_set_background(uint32_t color)
{
fb_ctx.background_color = color;
}
void fb_batch_start(void)
{
pthread_mutex_lock(&fb_ctx.mutex);
fb_ctx.batch_thread = pthread_self();
fb_ctx.batch_started = 1;
}
void fb_batch_end(void)
{
fb_ctx.batch_started = 0;
pthread_mutex_unlock(&fb_ctx.mutex);
}
void fb_items_lock(void)
{
if(!fb_ctx.batch_started || !pthread_equal(fb_ctx.batch_thread, pthread_self()))
pthread_mutex_lock(&fb_ctx.mutex);
}
void fb_items_unlock(void)
{
if(!fb_ctx.batch_started || !pthread_equal(fb_ctx.batch_thread, pthread_self()))
pthread_mutex_unlock(&fb_ctx.mutex);
}
static void fb_ctx_put_it_before(fb_item_header *new_it, fb_item_header *next_it)
{
if(next_it->prev)
{
next_it->prev->next = new_it;
new_it->prev = next_it->prev;
}
new_it->next = next_it;
next_it->prev = new_it;
}
static void fb_ctx_put_it_after(fb_item_header *new_it, fb_item_header *prev_it)
{
if(prev_it->next)
{
prev_it->next->prev = new_it;
new_it->next = prev_it->next;
}
new_it->prev = prev_it;
prev_it->next = new_it;
}
void fb_ctx_add_item(void *item)
{
fb_item_header *h = item;
fb_items_lock();
if(!fb_ctx.first_item)
fb_ctx.first_item = item;
else
{
fb_item_header *itr = fb_ctx.first_item;
while(1)
{
if(itr->level > h->level)
{
if(itr == fb_ctx.first_item)
fb_ctx.first_item = h;
fb_ctx_put_it_before(h, itr);
itr = NULL;
break;
}
if(itr->next)
itr = itr->next;
else
break;
}
if(itr)
fb_ctx_put_it_after(h, itr);
}
fb_items_unlock();
}
void fb_ctx_rm_item(void *item)
{
fb_item_header *h = item;
fb_items_lock();
if(!h->prev)
fb_ctx.first_item = h->next;
else
h->prev->next = h->next;
if(h->next)
h->next->prev = h->prev;
fb_items_unlock();
}
void fb_remove_item(void *item)
{
switch(((fb_item_header*)item)->type)
{
case FB_IT_RECT:
fb_rm_rect((fb_rect*)item);
break;
case FB_IT_IMG:
fb_rm_img((fb_img*)item);
break;
case FB_IT_LISTVIEW:
listview_destroy((listview*)item);
break;
case FB_IT_LINE:
fb_rm_line((fb_line*)item);
break;
}
}
void fb_destroy_item(void *item)
{
anim_cancel_for(item, 0);
switch(((fb_item_header*)item)->type)
{
case FB_IT_RECT:
case FB_IT_LINE:
break;
case FB_IT_IMG:
{
fb_img *i = (fb_img*)item;
switch(i->img_type)
{
case FB_IMG_TYPE_PNG:
fb_png_release(i->data);
break;
case FB_IMG_TYPE_GENERIC:
free(i->data);
break;
case FB_IMG_TYPE_TEXT:
fb_text_destroy(i);
break;
default:
ERROR("fb_destroy_item(): unknown fb_img type %d\n", i->img_type);
assert(0);
break;
}
break;
}
}
free(item);
}
static inline void clamp_to_parent(void *it, int *min_x, int *max_x, int *min_y, int *max_y)
{
fb_item_header *h = it;
int parent_x = h->parent->x;
int parent_y = h->parent->y;
int parent_w = h->parent->w;
int parent_h = h->parent->h;
if(h->parent != &DEFAULT_FB_PARENT)
{
if(parent_x < 0)
{
parent_w += parent_x;
parent_x = 0;
}
if(parent_y < 0)
{
parent_h += parent_y;
parent_y = 0;
}
parent_w = imin(parent_x + parent_w, fb_width) - parent_x;
parent_h = imin(parent_y + parent_h, fb_height) - parent_y;
}
*min_x = h->x >= parent_x ? 0 : parent_x - h->x;
*min_y = h->y >= parent_y ? 0 : parent_y - h->y;
*max_x = imin(h->w, parent_x + parent_w - h->x);
*max_y = imin(h->h, parent_y + parent_h - h->y);
}
void fb_draw_rect(fb_rect *r)
{
const uint8_t alpha = (r->color >> 24) & 0xFF;
const uint8_t inv_alpha = 0xFF - ((r->color >> 24) & 0xFF);
const px_type color = fb_convert_color(r->color);
if(alpha == 0)
return;
#ifdef RECOVERY_RGBX
const uint32_t premult_color_rb = ((color & 0xFF00FF) * (alpha)) >> 8;
const uint32_t premult_color_g = ((color & 0x00FF00) * (alpha)) >> 8;
#elif defined(RECOVERY_BGRA)
const uint32_t premult_color_rb = (((color >> 8) & 0xFF00FF) * (alpha)) >> 8;
const uint32_t premult_color_g = (((color >> 8) & 0x00FF00) * (alpha)) >> 8;
#elif defined(RECOVERY_RGB_565)
const uint8_t alpha5b = (alpha >> 3) + 1;
const uint8_t alpha6b = (alpha >> 2) + 1;
const uint8_t inv_alpha5b = 32 - alpha5b;
const uint8_t inv_alpha6b = 64 - alpha6b;
const uint16_t premult_color_rb = ((color & 0xF81F) * alpha5b) >> 5;
const uint16_t premult_color_g = ((color & 0x7E0) * alpha6b) >> 6;
#endif
int min_x, max_x, min_y, max_y;
clamp_to_parent(r, &min_x, &max_x, &min_y, &max_y);
const int rendered_w = max_x - min_x;
if(rendered_w <= 0)
return;
const int w = rendered_w*PIXEL_SIZE;
px_type *bits = fb.buffer + (fb.stride*(r->y + min_y)) + r->x + min_x;
int i, x;
uint8_t *comps_bits;
const uint8_t *comps_clr = (uint8_t*)&color;
for(i = min_y; i < max_y; ++i)
{
if(alpha == 0xFF)
{
fb_memset(bits, color, w);
bits += fb.stride;
}
// Do the blending
else
{
#ifdef MR_DISABLE_ALPHA
fb_memset(bits, color, w);
bits += fb.stride;
#else
for(x = 0; x < rendered_w; ++x)
{
#ifdef RECOVERY_RGBX
const uint32_t rb = (premult_color_rb & 0xFF00FF) + ((inv_alpha * (*bits & 0xFF00FF)) >> 8);
const uint32_t g = (premult_color_g & 0x00FF00) + ((inv_alpha * (*bits & 0x00FF00)) >> 8);
*bits = 0xFF000000 | (rb & 0xFF00FF) | (g & 0x00FF00);
#elif defined(RECOVERY_BGRA)
const uint32_t rb = (premult_color_rb & 0xFF00FF) + ((inv_alpha * ((*bits >> 8) & 0xFF00FF)) >> 8);
const uint32_t g = (premult_color_g & 0x00FF00) + ((inv_alpha * ((*bits >> 8) & 0x00FF00)) >> 8);
*bits = 0xFF000000 | (rb & 0xFF00FF) | (g & 0x00FF00);
#elif defined(RECOVERY_RGB_565)
const uint16_t rb = (premult_color_rb & 0xF81F) + ((inv_alpha5b * (*bits & 0xF81F)) >> 5);
const uint16_t g = (premult_color_g & 0x7E0) + ((inv_alpha6b * (*bits & 0x7E0)) >> 6);
*bits = (rb & 0xF81F) | (g & 0x7E0);
#else
#error "No alpha blending implementation for this format!"
#endif
++bits;
}
bits += fb.stride - rendered_w;
#endif // MR_DISABLE_ALPHA
}
}
}
static inline int blend_png(int value1, int value2, int alpha) {
int r = (0xFF-alpha)*value1 + alpha*value2;
return (r+1 + (r >> 8)) >> 8; // divide by 255
}
void fb_draw_img(fb_img *i)
{
int y, x;
const int w = i->w*PIXEL_SIZE;
uint8_t alpha;
uint8_t *comps_img, *comps_bits;
#if PIXEL_SIZE == 4
const uint8_t max_alpha = 0xFF;
#elif PIXEL_SIZE == 2
const uint8_t max_alpha = 31;
#endif
int min_x, max_x, min_y, max_y;
clamp_to_parent(i, &min_x, &max_x, &min_y, &max_y);
const int rendered_w = max_x - min_x;
if(rendered_w <= 0)
return;
px_type *bits = fb.buffer + (fb.stride*(i->y + min_y)) + i->x + min_x;
px_type *img = (px_type*)(((uint32_t*)i->data) + (min_y * i->w) + min_x);
for(y = min_y; y < max_y; ++y)
{
for(x = min_x; x < max_x; ++x)
{
// Colors, 0xAABBGGRR
#if PIXEL_SIZE == 4
alpha = PX_GET_A(*img);
#elif PIXEL_SIZE == 2
alpha = ((uint8_t*)img)[2];
#endif
// fully opaque
if(alpha == max_alpha)
{
*bits = *img;
}
// do the blending
else if(alpha != 0x00)
{
#ifdef MR_DISABLE_ALPHA
*bits = *img;
#else
#if PIXEL_SIZE == 4
comps_bits = (uint8_t*)bits;
comps_img = (uint8_t*)img;
comps_bits[PX_IDX_R] = blend_png(comps_bits[PX_IDX_R], comps_img[PX_IDX_R], comps_img[PX_IDX_A]);
comps_bits[PX_IDX_G] = blend_png(comps_bits[PX_IDX_G], comps_img[PX_IDX_G], comps_img[PX_IDX_A]);
comps_bits[PX_IDX_B] = blend_png(comps_bits[PX_IDX_B], comps_img[PX_IDX_B], comps_img[PX_IDX_A]);
comps_bits[PX_IDX_A] = 0xFF;
#else
const uint8_t alpha5b = alpha;
const uint8_t alpha6b = ((uint8_t*)img)[3];
*bits = (((31-alpha5b)*(*bits & 0x1F) + (alpha5b*(*img & 0x1F))) / 31) |
((((63-alpha6b)*((*bits & 0x7E0) >> 5) + (alpha6b*((*img & 0x7E0) >> 5))) / 63) << 5) |
((((31-alpha5b)*((*bits & 0xF800) >> 11) + (alpha5b*((*img & 0xF800) >> 11))) / 31) << 11);
#endif // PIXEL_SIZE
#endif // MR_DISABLE_ALPHA
}
++bits;
#if PIXEL_SIZE == 4
++img;
#elif PIXEL_SIZE == 2
img += 2;
#endif
}
bits += fb.stride - rendered_w;
img = (px_type*)(((uint32_t*)img) + (i->w - rendered_w));
}
}
// from http://members.chello.at/~easyfilter/bresenham.html
void fb_draw_line(fb_line *l)
{
const px_type px = fb_convert_color(l->color);
int x0 = imin(imax(l->x, l->parent->x), l->parent->x + l->parent->w);
int x1 = imin(imax(l->x2, l->parent->x), l->parent->x + l->parent->w);
int y0 = imin(imax(l->y, l->parent->y), l->parent->y + l->parent->h);
int y1 = imin(imax(l->y2, l->parent->y), l->parent->y + l->parent->h);
int dx = abs(x1-x0);
int dy = abs(y1-y0);
const int sx = x0 < x1 ? 1 : -1;
const int sy = y0 < y1 ? 1 : -1;
int err;
double e2 = sqrt((double)(dx*dx) + (double)(dy*dy));
if(e2 == 0.0)
return;
dx *= 255/e2;
dy *= 255/e2;
double th = 255*((double)(l->thickness - 1));
if(dx < dy)
{
x1 = (e2+th/2) / dy;
err = x1*dy - th/2;
for(x0 -= x1*sx; ; y0 += sy)
{
x1 = x0;
for(e2 = dy-err-th; e2+dy < 255; e2 += dy)
{
x1 += sx;
*(fb.buffer + fb.stride*y0 + x1) = px;
}
if(y0 == y1)
break;
err += dx;
if(err > 255)
{
err -= dy;
x0 += sx;
}
}
}
else
{
y1 = (e2 + th/2) / dx;
err = y1*dx - th/2;
for(y0 -= y1*sy; ; x0 += sx)
{
y1 = y0;
for(e2 = dx - err - th; e2+dx < 255; e2 += dx)
{
y1 += sy;
*(fb.buffer + fb.stride*y1 + x0) = px;
}
if(x0 == x1)
break;
err += dy;
if(err > 255)
{
err -= dx;
y0 += sy;
}
}
}
}
int fb_generate_item_id(void)
{
fb_items_lock();
static int id = 0;
int res = id++;
fb_items_unlock();
return res;
}
fb_rect *fb_add_rect_lvl(int level, int x, int y, int w, int h, uint32_t color)
{
fb_rect *r = mzalloc(sizeof(fb_rect));
r->id = fb_generate_item_id();
r->type = FB_IT_RECT;
r->parent = &DEFAULT_FB_PARENT;
r->level = level;
r->x = x;
r->y = y;
r->w = w;
r->h = h;
r->color = color;
fb_ctx_add_item(r);
return r;
}
void fb_add_rect_notfilled(int level, int x, int y, int w, int h, uint32_t color, int thickness, fb_rect ***list)
{
fb_rect *r;
// top
r = fb_add_rect_lvl(level, x, y, w, thickness, color);
list_add(list, r);
// right
r = fb_add_rect_lvl(level, x + w - thickness, y, thickness, h, color);
list_add(list, r);
// bottom
r = fb_add_rect_lvl(level, x, y + h - thickness, w, thickness, color);
list_add(list, r);
// left
r = fb_add_rect_lvl(level, x, y, thickness, h, color);
list_add(list, r);
}
fb_img *fb_add_img(int level, int x, int y, int w, int h, int img_type, px_type *data)
{
fb_img *result = mzalloc(sizeof(fb_img));
result->id = fb_generate_item_id();
result->type = FB_IT_IMG;
result->parent = &DEFAULT_FB_PARENT;
result->level = level;
result->x = x;
result->y = y;
result->img_type = img_type;
result->data = data;
result->w = w;
result->h = h;
fb_ctx_add_item(result);
return result;
}
fb_img* fb_add_png_img_lvl(int level, int x, int y, int w, int h, const char *path)
{
px_type *data = NULL;
if(strncmp(path, ":/", 2) == 0)
{
const int full_path_len = strlen(path) + strlen(mrom_dir()) + 4;
char *full_path = malloc(full_path_len);
snprintf(full_path, full_path_len, "%s/res%s", mrom_dir(), path+1);
data = fb_png_get(full_path, w, h);
free(full_path);
}
else
data = fb_png_get(path, w, h);
if(!data)
return NULL;
return fb_add_img(level, x, y, w, h, FB_IMG_TYPE_PNG, data);
}
fb_circle *fb_add_circle_lvl(int level, int x, int y, int radius, uint32_t color)
{
const int diameter = radius*2 + 1;
uint32_t *data = mzalloc(diameter * diameter * 4);
uint32_t px = fb_convert_color_img(color);
int rx, ry;
const int radius_check = radius*radius + radius*0.8;
for(ry = -radius; ry <= radius; ++ry)
for(rx = -radius; rx <= radius; ++rx)
if(rx*rx+ry*ry <= radius_check)
*(data + diameter*(radius + ry) + (radius+rx)) = px;
return fb_add_img(level, x, y, diameter, diameter, FB_IMG_TYPE_GENERIC, data);
}
fb_line *fb_add_line_lvl(int level, int x1, int y1, int x2, int y2, int thickness, uint32_t color)
{
fb_line *res = mzalloc(sizeof(fb_line));
res->id = fb_generate_item_id();
res->type = FB_IT_LINE;
res->parent = &DEFAULT_FB_PARENT;
res->level = level;
res->x = x1;
res->y = y1;
res->x2 = x2;
res->y2 = y2;
res->thickness = thickness;
res->color = color;
fb_ctx_add_item(res);
return res;
}
void fb_rm_rect(fb_rect *r)
{
if(!r)
return;
fb_ctx_rm_item(r);
fb_destroy_item(r);
}
void fb_rm_text(fb_img *i)
{
fb_rm_img(i);
}
void fb_rm_img(fb_img *i)
{
if(!i)
return;
fb_ctx_rm_item(i);
fb_destroy_item(i);
}
void fb_rm_circle(fb_circle *c)
{
fb_rm_img(c);
}
void fb_rm_line(fb_line *l)
{
if(!l)
return;
fb_ctx_rm_item(l);
fb_destroy_item(l);
}
void fb_clear(void)
{
pthread_mutex_lock(&fb_ctx.mutex);
fb_item_header *it, *next;
for(it = fb_ctx.first_item; it; it = next)
{
next = it->next;
fb_destroy_item(it);
}
fb_ctx.first_item = NULL;
pthread_mutex_unlock(&fb_ctx.mutex);
fb_png_drop_unused();
fb_text_drop_cache_unused();
}
static void fb_draw(void)
{
uint32_t i;
fb_item_header *it;
fb_fill(fb_ctx.background_color);
fb_batch_start();
for(it = fb_ctx.first_item; it; it = it->next)
{
switch(it->type)
{
case FB_IT_RECT:
fb_draw_rect((fb_rect*)it);
break;
case FB_IT_IMG:
fb_draw_img((fb_img*)it);
break;
case FB_IT_LISTVIEW:
listview_update_ui_args((listview*)it, 1, 1);
break;
case FB_IT_LINE:
fb_draw_line((fb_line*)it);
break;
}
}
fb_batch_end();
pthread_mutex_lock(&fb_update_mutex);
fb_update();
pthread_mutex_unlock(&fb_update_mutex);
}
void fb_freeze(int freeze)
{
if(freeze)
++fb_frozen;
else
--fb_frozen;
// wait for last draw to finish or prevent new draw
if(fb_frozen == 1)
{
atomic_int expected = ATOMIC_VAR_INIT(1);
pthread_mutex_lock(&fb_draw_mutex);
int res = atomic_compare_exchange_strong(&fb_draw_requested, &expected, 0);
pthread_mutex_unlock(&fb_draw_mutex);
}
}
void fb_push_context(void)
{
fb_context_t *ctx = mzalloc(sizeof(fb_context_t));
pthread_mutex_lock(&fb_ctx.mutex);
ctx->first_item = fb_ctx.first_item;
ctx->background_color = fb_ctx.background_color;
fb_ctx.first_item = NULL;
pthread_mutex_unlock(&fb_ctx.mutex);
list_add(&inactive_ctx, ctx);
}
void fb_pop_context(void)
{
if(!inactive_ctx)
return;
fb_clear();
int idx = list_item_count(inactive_ctx)-1;
fb_context_t *ctx = inactive_ctx[idx];
pthread_mutex_lock(&fb_ctx.mutex);
fb_ctx.first_item = ctx->first_item;
fb_ctx.background_color = ctx->background_color;
pthread_mutex_unlock(&fb_ctx.mutex);
list_rm_noreorder(&inactive_ctx, ctx, &free);
fb_request_draw();
}
#define SLEEP_CONST 16
void *fb_draw_thread_work(UNUSED void *cookie)
{
struct timespec last, curr;
uint32_t diff = 0, prevSleepTime = 0;
clock_gettime(CLOCK_MONOTONIC, &last);
atomic_int expected = ATOMIC_VAR_INIT(1);
while(fb_draw_run)
{
clock_gettime(CLOCK_MONOTONIC, &curr);
diff = timespec_diff(&last, &curr);
expected.__val = 1; // might be reseted by atomic_compare_exchange_strong
pthread_mutex_lock(&fb_draw_mutex);
if(atomic_compare_exchange_strong(&fb_draw_requested, &expected, 0))
{
fb_draw();
pthread_cond_broadcast(&fb_draw_cond);
pthread_mutex_unlock(&fb_draw_mutex);
}
else
{
pthread_mutex_unlock(&fb_draw_mutex);
#ifdef MR_CONTINUOUS_FB_UPDATE
pthread_mutex_lock(&fb_update_mutex);
fb_update();
pthread_mutex_unlock(&fb_update_mutex);
#endif
}
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)
{
if(!fb_frozen)
{
atomic_int expected = ATOMIC_VAR_INIT(0);
atomic_compare_exchange_strong(&fb_draw_requested, &expected, 1);
}
}
void fb_force_draw(void)
{
atomic_int expected = ATOMIC_VAR_INIT(0);
pthread_mutex_lock(&fb_draw_mutex);
atomic_compare_exchange_strong(&fb_draw_requested, &expected, 1);
pthread_cond_wait(&fb_draw_cond, &fb_draw_mutex);
pthread_mutex_unlock(&fb_draw_mutex);
}
int fb_save_screenshot(void)
{
char *r;
int c, media_rw_id;
char dir[256];
char path[256];
strcpy(dir, mrom_dir());
r = strrchr(dir, '/');
if(!r)
{
ERROR("Failed to determine path to save a screenshot!\n");
return -1;
}
*r = 0;
strcat(dir, "/Pictures/Screenshots");
mkdir_recursive_with_perms(path, 0775, "media_rw", "media_rw");
for(c = 0; c < 999; ++c)
{
snprintf(path, sizeof(path), "%s/mrom_screenshot_%03d.png", dir, c);
if(access(path, F_OK) < 0)
break;
}
pthread_mutex_lock(&fb_draw_mutex);
if(fb_png_save_img(path, fb_width, fb_height, fb.stride, fb.buffer) >= 0)
{
media_rw_id = decode_uid("media_rw");
if(media_rw_id != -1)
chown(path, (uid_t)media_rw_id, (gid_t)media_rw_id);
chmod(path, 0664);
INFO("Screenshot saved to %s\n", path);
fb_fill(WHITE);
pthread_mutex_lock(&fb_update_mutex);
fb_update();
usleep(100000);
pthread_mutex_unlock(&fb_update_mutex);
pthread_mutex_unlock(&fb_draw_mutex);
fb_request_draw();
return 0;
}
else
{
pthread_mutex_unlock(&fb_draw_mutex);
ERROR("Failed to take screenshot!\n");
return -1;
}
}