39cf417e17
Recovery now draws directly to the framebuffer by rolling its own graphics code, rather than depending on libpixelflinger. The recovery UI is modified slightly to eliminate operations that are slow with the software implementation: when the text display / menu is turned on, it now appears on a black background instead of a dimmed version of the recovery icon. There's probably substantial room for optimization of the graphics operations. Bug: 12131110 Change-Id: Iab6520e0a7aaec39e2ce39377c10aef82ae0c595
546 lines
15 KiB
C
546 lines
15 KiB
C
/*
|
|
* Copyright (C) 2007 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
|
|
#include <fcntl.h>
|
|
#include <stdio.h>
|
|
|
|
#include <sys/ioctl.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/types.h>
|
|
|
|
#include <linux/fb.h>
|
|
#include <linux/kd.h>
|
|
|
|
#include <time.h>
|
|
|
|
#include "font_10x18.h"
|
|
#include "minui.h"
|
|
|
|
typedef struct {
|
|
GRSurface* texture;
|
|
int cwidth;
|
|
int cheight;
|
|
} GRFont;
|
|
|
|
static GRFont* gr_font = NULL;
|
|
|
|
static int overscan_percent = OVERSCAN_PERCENT;
|
|
static int overscan_offset_x = 0;
|
|
static int overscan_offset_y = 0;
|
|
|
|
static int gr_fb_fd = -1;
|
|
static int gr_vt_fd = -1;
|
|
|
|
static GRSurface gr_framebuffer[2];
|
|
static bool double_buffered;
|
|
static GRSurface* gr_draw = NULL;
|
|
static int displayed_buffer;
|
|
|
|
static unsigned char gr_current_r = 255;
|
|
static unsigned char gr_current_g = 255;
|
|
static unsigned char gr_current_b = 255;
|
|
static unsigned char gr_current_a = 255;
|
|
|
|
static struct fb_var_screeninfo vi;
|
|
|
|
static bool outside(int x, int y)
|
|
{
|
|
return x < 0 || x >= gr_draw->width || y < 0 || y >= gr_draw->height;
|
|
}
|
|
|
|
static void set_displayed_framebuffer(unsigned n)
|
|
{
|
|
if (n > 1 || !double_buffered) return;
|
|
|
|
vi.yres_virtual = gr_framebuffer[0].height * 2;
|
|
vi.yoffset = n * gr_framebuffer[0].height;
|
|
vi.bits_per_pixel = gr_framebuffer[0].pixel_bytes * 8;
|
|
if (ioctl(gr_fb_fd, FBIOPUT_VSCREENINFO, &vi) < 0) {
|
|
perror("active fb swap failed");
|
|
}
|
|
displayed_buffer = n;
|
|
}
|
|
|
|
static int get_framebuffer()
|
|
{
|
|
int fd;
|
|
void *bits;
|
|
|
|
struct fb_fix_screeninfo fi;
|
|
|
|
fd = open("/dev/graphics/fb0", O_RDWR);
|
|
if (fd < 0) {
|
|
perror("cannot open fb0");
|
|
return -1;
|
|
}
|
|
|
|
if (ioctl(fd, FBIOGET_FSCREENINFO, &fi) < 0) {
|
|
perror("failed to get fb0 info");
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
|
|
if (ioctl(fd, FBIOGET_VSCREENINFO, &vi) < 0) {
|
|
perror("failed to get fb0 info");
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
|
|
// We print this out for informational purposes only, but
|
|
// throughout we assume that the framebuffer device uses an RGBX
|
|
// pixel format. This is the case for every development device I
|
|
// have access to. For some of those devices (eg, hammerhead aka
|
|
// Nexus 5), FBIOGET_VSCREENINFO *reports* that it wants a
|
|
// different format (XBGR) but actually produces the correct
|
|
// results on the display when you write RGBX.
|
|
//
|
|
// If you have a device that actually *needs* another pixel format
|
|
// (ie, BGRX, or 565), patches welcome...
|
|
|
|
printf("fb0 reports (possibly inaccurate):\n"
|
|
" vi.bits_per_pixel = %d\n"
|
|
" vi.red.offset = %3d .length = %3d\n"
|
|
" vi.green.offset = %3d .length = %3d\n"
|
|
" vi.blue.offset = %3d .length = %3d\n",
|
|
vi.bits_per_pixel,
|
|
vi.red.offset, vi.red.length,
|
|
vi.green.offset, vi.green.length,
|
|
vi.blue.offset, vi.blue.length);
|
|
|
|
bits = mmap(0, fi.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
|
if (bits == MAP_FAILED) {
|
|
perror("failed to mmap framebuffer");
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
|
|
overscan_offset_x = vi.xres * overscan_percent / 100;
|
|
overscan_offset_y = vi.yres * overscan_percent / 100;
|
|
|
|
gr_framebuffer[0].width = vi.xres;
|
|
gr_framebuffer[0].height = vi.yres;
|
|
gr_framebuffer[0].row_bytes = fi.line_length;
|
|
gr_framebuffer[0].pixel_bytes = vi.bits_per_pixel / 8;
|
|
gr_framebuffer[0].data = bits;
|
|
memset(gr_framebuffer[0].data, 0, gr_framebuffer[0].height * gr_framebuffer[0].row_bytes);
|
|
|
|
/* check if we can use double buffering */
|
|
if (vi.yres * fi.line_length * 2 <= fi.smem_len) {
|
|
double_buffered = true;
|
|
|
|
memcpy(gr_framebuffer+1, gr_framebuffer, sizeof(GRSurface));
|
|
gr_framebuffer[1].data = gr_framebuffer[0].data +
|
|
gr_framebuffer[0].height * gr_framebuffer[0].row_bytes;
|
|
|
|
gr_draw = gr_framebuffer+1;
|
|
|
|
} else {
|
|
double_buffered = false;
|
|
|
|
// Without double-buffering, we allocate RAM for a buffer to
|
|
// draw in, and then "flipping" the buffer consists of a
|
|
// memcpy from the buffer we allocated to the framebuffer.
|
|
|
|
gr_draw = (GRSurface*) malloc(sizeof(GRSurface));
|
|
memcpy(gr_draw, gr_framebuffer, sizeof(GRSurface));
|
|
gr_draw->data = (unsigned char*) malloc(gr_draw->height * gr_draw->row_bytes);
|
|
if (!gr_draw->data) {
|
|
perror("failed to allocate in-memory surface");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
memset(gr_draw->data, 0, gr_draw->height * gr_draw->row_bytes);
|
|
gr_fb_fd = fd;
|
|
set_displayed_framebuffer(0);
|
|
|
|
return fd;
|
|
}
|
|
|
|
void gr_flip(void)
|
|
{
|
|
if (double_buffered) {
|
|
// Change gr_draw to point to the buffer currently displayed,
|
|
// then flip the driver so we're displaying the other buffer
|
|
// instead.
|
|
gr_draw = gr_framebuffer + displayed_buffer;
|
|
set_displayed_framebuffer(1-displayed_buffer);
|
|
} else {
|
|
// Copy from the in-memory surface to the framebuffer.
|
|
memcpy(gr_framebuffer[0].data, gr_draw->data,
|
|
gr_draw->height * gr_draw->row_bytes);
|
|
}
|
|
}
|
|
|
|
int gr_measure(const char *s)
|
|
{
|
|
return gr_font->cwidth * strlen(s);
|
|
}
|
|
|
|
void gr_font_size(int *x, int *y)
|
|
{
|
|
*x = gr_font->cwidth;
|
|
*y = gr_font->cheight;
|
|
}
|
|
|
|
static void text_blend(unsigned char* src_p, int src_row_bytes,
|
|
unsigned char* dst_p, int dst_row_bytes,
|
|
int width, int height)
|
|
{
|
|
int i, j;
|
|
for (j = 0; j < height; ++j) {
|
|
unsigned char* sx = src_p;
|
|
unsigned char* px = dst_p;
|
|
for (i = 0; i < width; ++i) {
|
|
unsigned char a = *sx++;
|
|
if (gr_current_a < 255) a = ((int)a * gr_current_a) / 255;
|
|
if (a == 255) {
|
|
*px++ = gr_current_r;
|
|
*px++ = gr_current_g;
|
|
*px++ = gr_current_b;
|
|
px++;
|
|
} else if (a > 0) {
|
|
*px = (*px * (255-a) + gr_current_r * a) / 255;
|
|
++px;
|
|
*px = (*px * (255-a) + gr_current_g * a) / 255;
|
|
++px;
|
|
*px = (*px * (255-a) + gr_current_b * a) / 255;
|
|
++px;
|
|
++px;
|
|
} else {
|
|
px += 4;
|
|
}
|
|
}
|
|
src_p += src_row_bytes;
|
|
dst_p += dst_row_bytes;
|
|
}
|
|
}
|
|
|
|
|
|
void gr_text(int x, int y, const char *s, int bold)
|
|
{
|
|
GRFont *font = gr_font;
|
|
unsigned off;
|
|
|
|
if (!font->texture) return;
|
|
if (gr_current_a == 0) return;
|
|
|
|
bold = bold && (font->texture->height != font->cheight);
|
|
|
|
x += overscan_offset_x;
|
|
y += overscan_offset_y;
|
|
|
|
while((off = *s++)) {
|
|
off -= 32;
|
|
if (outside(x, y) || outside(x+font->cwidth-1, y+font->cheight-1)) break;
|
|
if (off < 96) {
|
|
|
|
unsigned char* src_p = font->texture->data + (off * font->cwidth) +
|
|
(bold ? font->cheight * font->texture->row_bytes : 0);
|
|
unsigned char* dst_p = gr_draw->data + y*gr_draw->row_bytes + x*gr_draw->pixel_bytes;
|
|
|
|
text_blend(src_p, font->texture->row_bytes,
|
|
dst_p, gr_draw->row_bytes,
|
|
font->cwidth, font->cheight);
|
|
|
|
}
|
|
x += font->cwidth;
|
|
}
|
|
}
|
|
|
|
void gr_texticon(int x, int y, GRSurface* icon) {
|
|
if (icon == NULL) return;
|
|
|
|
if (icon->pixel_bytes != 1) {
|
|
printf("gr_texticon: source has wrong format\n");
|
|
return;
|
|
}
|
|
|
|
x += overscan_offset_x;
|
|
y += overscan_offset_y;
|
|
|
|
if (outside(x, y) || outside(x+icon->width-1, y+icon->height-1)) return;
|
|
|
|
unsigned char* src_p = icon->data;
|
|
unsigned char* dst_p = gr_draw->data + y*gr_draw->row_bytes + x*gr_draw->pixel_bytes;
|
|
|
|
text_blend(src_p, icon->row_bytes,
|
|
dst_p, gr_draw->row_bytes,
|
|
icon->width, icon->height);
|
|
}
|
|
|
|
void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a)
|
|
{
|
|
gr_current_r = r;
|
|
gr_current_g = g;
|
|
gr_current_b = b;
|
|
gr_current_a = a;
|
|
}
|
|
|
|
void gr_clear()
|
|
{
|
|
if (gr_current_r == gr_current_g &&
|
|
gr_current_r == gr_current_b) {
|
|
memset(gr_draw->data, gr_current_r, gr_draw->height * gr_draw->row_bytes);
|
|
} else {
|
|
int x, y;
|
|
unsigned char* px = gr_draw->data;
|
|
for (y = 0; y < gr_draw->height; ++y) {
|
|
for (x = 0; x < gr_draw->width; ++x) {
|
|
*px++ = gr_current_r;
|
|
*px++ = gr_current_g;
|
|
*px++ = gr_current_b;
|
|
px++;
|
|
}
|
|
px += gr_draw->row_bytes - (gr_draw->width * gr_draw->pixel_bytes);
|
|
}
|
|
}
|
|
}
|
|
|
|
void gr_fill(int x1, int y1, int x2, int y2)
|
|
{
|
|
x1 += overscan_offset_x;
|
|
y1 += overscan_offset_y;
|
|
|
|
x2 += overscan_offset_x;
|
|
y2 += overscan_offset_y;
|
|
|
|
if (outside(x1, y1) || outside(x2-1, y2-1)) return;
|
|
|
|
unsigned char* p = gr_draw->data + y1 * gr_draw->row_bytes + x1 * gr_draw->pixel_bytes;
|
|
if (gr_current_a == 255) {
|
|
int x, y;
|
|
for (y = y1; y < y2; ++y) {
|
|
unsigned char* px = p;
|
|
for (x = x1; x < x2; ++x) {
|
|
*px++ = gr_current_r;
|
|
*px++ = gr_current_g;
|
|
*px++ = gr_current_b;
|
|
px++;
|
|
}
|
|
p += gr_draw->row_bytes;
|
|
}
|
|
} else if (gr_current_a > 0) {
|
|
int x, y;
|
|
for (y = y1; y < y2; ++y) {
|
|
unsigned char* px = p;
|
|
for (x = x1; x < x2; ++x) {
|
|
*px = (*px * (255-gr_current_a) + gr_current_r * gr_current_a) / 255;
|
|
++px;
|
|
*px = (*px * (255-gr_current_a) + gr_current_g * gr_current_a) / 255;
|
|
++px;
|
|
*px = (*px * (255-gr_current_a) + gr_current_b * gr_current_a) / 255;
|
|
++px;
|
|
++px;
|
|
}
|
|
p += gr_draw->row_bytes;
|
|
}
|
|
}
|
|
}
|
|
|
|
void gr_blit(GRSurface* source, int sx, int sy, int w, int h, int dx, int dy) {
|
|
if (source == NULL) return;
|
|
|
|
if (gr_draw->pixel_bytes != source->pixel_bytes) {
|
|
printf("gr_blit: source has wrong format\n");
|
|
return;
|
|
}
|
|
|
|
dx += overscan_offset_x;
|
|
dy += overscan_offset_y;
|
|
|
|
if (outside(dx, dy) || outside(dx+w-1, dy+h-1)) return;
|
|
|
|
unsigned char* src_p = source->data + sy*source->row_bytes + sx*source->pixel_bytes;
|
|
unsigned char* dst_p = gr_draw->data + dy*gr_draw->row_bytes + dx*gr_draw->pixel_bytes;
|
|
|
|
int i;
|
|
for (i = 0; i < h; ++i) {
|
|
memcpy(dst_p, src_p, w * source->pixel_bytes);
|
|
src_p += source->row_bytes;
|
|
dst_p += gr_draw->row_bytes;
|
|
}
|
|
}
|
|
|
|
unsigned int gr_get_width(GRSurface* surface) {
|
|
if (surface == NULL) {
|
|
return 0;
|
|
}
|
|
return surface->width;
|
|
}
|
|
|
|
unsigned int gr_get_height(GRSurface* surface) {
|
|
if (surface == NULL) {
|
|
return 0;
|
|
}
|
|
return surface->height;
|
|
}
|
|
|
|
static void gr_init_font(void)
|
|
{
|
|
gr_font = calloc(sizeof(*gr_font), 1);
|
|
|
|
int res = res_create_surface("font", &(gr_font->texture));
|
|
if (res == 0) {
|
|
// The font image should be a 96x2 array of character images. The
|
|
// columns are the printable ASCII characters 0x20 - 0x7f. The
|
|
// top row is regular text; the bottom row is bold.
|
|
gr_font->cwidth = gr_font->texture->width / 96;
|
|
gr_font->cheight = gr_font->texture->height / 2;
|
|
} else {
|
|
printf("failed to read font: res=%d\n", res);
|
|
|
|
// fall back to the compiled-in font.
|
|
gr_font->texture = malloc(sizeof(*gr_font->texture));
|
|
gr_font->texture->width = font.width;
|
|
gr_font->texture->height = font.height;
|
|
gr_font->texture->row_bytes = font.width;
|
|
gr_font->texture->pixel_bytes = 1;
|
|
|
|
unsigned char* bits = malloc(font.width * font.height);
|
|
gr_font->texture->data = (void*) bits;
|
|
|
|
unsigned char data;
|
|
unsigned char* in = font.rundata;
|
|
while((data = *in++)) {
|
|
memset(bits, (data & 0x80) ? 255 : 0, data & 0x7f);
|
|
bits += (data & 0x7f);
|
|
}
|
|
|
|
gr_font->cwidth = font.cwidth;
|
|
gr_font->cheight = font.cheight;
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
// Exercises many of the gr_*() functions; useful for testing.
|
|
static void gr_test() {
|
|
GRSurface** images;
|
|
int frames;
|
|
int result = res_create_multi_surface("icon_installing", &frames, &images);
|
|
if (result < 0) {
|
|
printf("create surface %d\n", result);
|
|
gr_exit();
|
|
return;
|
|
}
|
|
|
|
time_t start = time(NULL);
|
|
int x;
|
|
for (x = 0; x <= 1200; ++x) {
|
|
if (x < 400) {
|
|
gr_color(0, 0, 0, 255);
|
|
} else {
|
|
gr_color(0, (x-400)%128, 0, 255);
|
|
}
|
|
gr_clear();
|
|
|
|
gr_color(255, 0, 0, 255);
|
|
gr_surface frame = images[x%frames];
|
|
gr_blit(frame, 0, 0, frame->width, frame->height, x, 0);
|
|
|
|
gr_color(255, 0, 0, 128);
|
|
gr_fill(400, 150, 600, 350);
|
|
|
|
gr_color(255, 255, 255, 255);
|
|
gr_text(500, 225, "hello, world!", 0);
|
|
gr_color(255, 255, 0, 128);
|
|
gr_text(300+x, 275, "pack my box with five dozen liquor jugs", 1);
|
|
|
|
gr_color(0, 0, 255, 128);
|
|
gr_fill(gr_draw->width - 200 - x, 300, gr_draw->width - x, 500);
|
|
|
|
gr_flip();
|
|
}
|
|
printf("getting end time\n");
|
|
time_t end = time(NULL);
|
|
printf("got end time\n");
|
|
printf("start %ld end %ld\n", (long)start, (long)end);
|
|
if (end > start) {
|
|
printf("%.2f fps\n", ((double)x) / (end-start));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
int gr_init(void)
|
|
{
|
|
gr_init_font();
|
|
|
|
gr_vt_fd = open("/dev/tty0", O_RDWR | O_SYNC);
|
|
if (gr_vt_fd < 0) {
|
|
// This is non-fatal; post-Cupcake kernels don't have tty0.
|
|
perror("can't open /dev/tty0");
|
|
} else if (ioctl(gr_vt_fd, KDSETMODE, (void*) KD_GRAPHICS)) {
|
|
// However, if we do open tty0, we expect the ioctl to work.
|
|
perror("failed KDSETMODE to KD_GRAPHICS on tty0");
|
|
gr_exit();
|
|
return -1;
|
|
}
|
|
|
|
if (get_framebuffer(&gr_framebuffer) < 0) {
|
|
gr_exit();
|
|
return -1;
|
|
}
|
|
|
|
printf("framebuffer: fd %d (%d x %d)\n",
|
|
gr_fb_fd, gr_draw->width, gr_draw->height);
|
|
|
|
gr_flip();
|
|
gr_flip();
|
|
|
|
gr_fb_blank(true);
|
|
gr_fb_blank(false);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void gr_exit(void)
|
|
{
|
|
close(gr_fb_fd);
|
|
gr_fb_fd = -1;
|
|
|
|
ioctl(gr_vt_fd, KDSETMODE, (void*) KD_TEXT);
|
|
close(gr_vt_fd);
|
|
gr_vt_fd = -1;
|
|
|
|
if (!double_buffered) {
|
|
if (gr_draw) free(gr_draw->data);
|
|
free(gr_draw);
|
|
}
|
|
}
|
|
|
|
int gr_fb_width(void)
|
|
{
|
|
return gr_draw->width - 2*overscan_offset_x;
|
|
}
|
|
|
|
int gr_fb_height(void)
|
|
{
|
|
return gr_draw->height - 2*overscan_offset_y;
|
|
}
|
|
|
|
void gr_fb_blank(bool blank)
|
|
{
|
|
int ret;
|
|
|
|
ret = ioctl(gr_fb_fd, FBIOBLANK, blank ? FB_BLANK_POWERDOWN : FB_BLANK_UNBLANK);
|
|
if (ret < 0)
|
|
perror("ioctl(): blank");
|
|
}
|