diff options
author | David 'Digit' Turner <digit@android.com> | 2011-01-18 09:32:03 -0800 |
---|---|---|
committer | Android Code Review <code-review@android.com> | 2011-01-18 09:32:03 -0800 |
commit | 65357f3dd1f11d13ac64833d42d7e05e551d1f66 (patch) | |
tree | c7a134719117f333a7fdc54da0366665bc4b9be5 | |
parent | f19ee7707cfb10c2084aea1206650f40ed12feb7 (diff) | |
parent | d8a3d5ce2c54380ac5f4bc411a2e021df34c1fc5 (diff) | |
download | external_qemu-65357f3dd1f11d13ac64833d42d7e05e551d1f66.zip external_qemu-65357f3dd1f11d13ac64833d42d7e05e551d1f66.tar.gz external_qemu-65357f3dd1f11d13ac64833d42d7e05e551d1f66.tar.bz2 |
Merge "hw: goldfish_fb: Minimize the size of the update rectangle."
-rw-r--r-- | hw/goldfish_fb.c | 415 |
1 files changed, 323 insertions, 92 deletions
diff --git a/hw/goldfish_fb.c b/hw/goldfish_fb.c index 8300e7c..cc57f6a 100644 --- a/hw/goldfish_fb.c +++ b/hw/goldfish_fb.c @@ -14,6 +14,19 @@ #include "goldfish_device.h" #include "console.h" +/* These values *must* match the platform definitions found under + * hardware/libhardware/include/hardware/hardware.h + */ +enum { + HAL_PIXEL_FORMAT_RGBA_8888 = 1, + HAL_PIXEL_FORMAT_RGBX_8888 = 2, + HAL_PIXEL_FORMAT_RGB_888 = 3, + HAL_PIXEL_FORMAT_RGB_565 = 4, + HAL_PIXEL_FORMAT_BGRA_8888 = 5, + HAL_PIXEL_FORMAT_RGBA_5551 = 6, + HAL_PIXEL_FORMAT_RGBA_4444 = 7, +}; + enum { FB_GET_WIDTH = 0x00, FB_GET_HEIGHT = 0x04, @@ -32,6 +45,8 @@ enum { struct goldfish_fb_state { struct goldfish_device dev; DisplayState* ds; + int pixel_format; + int bytes_per_pixel; uint32_t fb_base; uint32_t base_valid : 1; uint32_t need_update : 1; @@ -114,6 +129,82 @@ Exit: return ret; } +/* Type used to record a mapping from display surface pixel format to + * HAL pixel format */ +typedef struct { + int pixel_format; /* HAL pixel format */ + uint8_t bits; + uint8_t bytes; + uint32_t rmask, gmask, bmask, amask; +} FbConfig; + + +/* Return the pixel format of the current framebuffer, based on + * the current display surface's pixel format. + * + * Note that you should not call this function from the device initialization + * function, because the display surface will change format before the kernel + * start. + */ +static int goldfish_fb_get_pixel_format(struct goldfish_fb_state *s) +{ + if (s->pixel_format >= 0) { + return s->pixel_format; + } + static const FbConfig fb_configs[] = { + { HAL_PIXEL_FORMAT_RGB_565, 16, 2, 0xf800, 0x7e0, 0x1f, 0x0 }, + { -1, } + }; + + /* Determine HAL pixel format value based on s->ds */ + struct PixelFormat* pf = &s->ds->surface->pf; +#if 0 + printf("%s:%d: display surface,pixel format:\n", __FUNCTION__, __LINE__); + printf(" bits/pixel: %d\n", pf->bits_per_pixel); + printf(" bytes/pixel: %d\n", pf->bytes_per_pixel); + printf(" depth: %d\n", pf->depth); + printf(" red: bits=%d mask=0x%x shift=%d max=0x%x\n", + pf->rbits, pf->rmask, pf->rshift, pf->rmax); + printf(" green: bits=%d mask=0x%x shift=%d max=0x%x\n", + pf->gbits, pf->gmask, pf->gshift, pf->gmax); + printf(" blue: bits=%d mask=0x%x shift=%d max=0x%x\n", + pf->bbits, pf->bmask, pf->bshift, pf->bmax); + printf(" alpha: bits=%d mask=0x%x shift=%d max=0x%x\n", + pf->abits, pf->amask, pf->ashift, pf->amax); + +#endif + s->bytes_per_pixel = pf->bytes_per_pixel; + int nn; + for (nn = 0; fb_configs[nn].pixel_format >= 0; nn++) { + const FbConfig* fbc = &fb_configs[nn]; + if (pf->bits_per_pixel == fbc->bits && + pf->bytes_per_pixel == fbc->bytes && + pf->rmask == fbc->rmask && + pf->gmask == fbc->gmask && + pf->bmask == fbc->bmask && + pf->amask == fbc->amask) { + /* We found it */ + s->pixel_format = fbc->pixel_format; + return s->pixel_format; + } + } + fprintf(stderr, "%s:%d: Unsupported display pixel format (depth=%d, bytespp=%d, bitspp=%d)\n", + __FUNCTION__, __LINE__, + pf->depth, + pf->bytes_per_pixel, + pf->bits_per_pixel); + exit(1); + return -1; +} + +static int goldfish_fb_get_bytes_per_pixel(struct goldfish_fb_state *s) +{ + if (s->pixel_format < 0) { + (void) goldfish_fb_get_pixel_format(s); + } + return s->bytes_per_pixel; +} + static int pixels_to_mm(int pixels, int dpi) { @@ -135,17 +226,210 @@ static int stats_full_updates; static long stats_total_full_updates; #endif +/* This structure is used to hold the inputs for + * compute_fb_update_rect_linear below. + * This corresponds to the source framebuffer and destination + * surface pixel buffers. + */ +typedef struct { + int width; + int height; + int bytes_per_pixel; + const uint8_t* src_pixels; + int src_pitch; + uint8_t* dst_pixels; + int dst_pitch; +} FbUpdateState; + +/* This structure is used to hold the outputs for + * compute_fb_update_rect_linear below. + * This corresponds to the smalled bounding rectangle of the + * latest framebuffer update. + */ +typedef struct { + int xmin, ymin, xmax, ymax; +} FbUpdateRect; + +/* Determine the smallest bounding rectangle of pixels which changed + * between the source (framebuffer) and destination (surface) pixel + * buffers. + * + * Return 0 if there was no change, otherwise, populate '*rect' + * and return 1. + * + * If 'dirty_base' is not 0, it is a physical address that will be + * used to speed-up the check using the VGA dirty bits. In practice + * this is only used if your kernel driver does not implement. + * + * This function assumes that the framebuffers are in linear memory. + * This may change later when we want to support larger framebuffers + * that exceed the max DMA aperture size though. + */ +static int +compute_fb_update_rect_linear(FbUpdateState* fbs, + uint32_t dirty_base, + FbUpdateRect* rect) +{ + int yy; + int width = fbs->width; + const uint8_t* src_line = fbs->src_pixels; + uint8_t* dst_line = fbs->dst_pixels; + uint32_t dirty_addr = dirty_base; + rect->xmin = rect->ymin = INT_MAX; + rect->xmax = rect->ymax = INT_MIN; + for (yy = 0; yy < fbs->height; yy++) { + int xx1, xx2; + /* If dirty_addr is != 0, then use it as a physical address to + * use the VGA dirty bits table to speed up the detection of + * changed pixels. + */ + if (dirty_addr != 0) { + int dirty = 0; + int len = fbs->src_pitch; + + while (len > 0) { + int len2 = TARGET_PAGE_SIZE - (dirty_addr & (TARGET_PAGE_SIZE-1)); + + if (len2 > len) + len2 = len; + + dirty |= cpu_physical_memory_get_dirty(dirty_addr, VGA_DIRTY_FLAG); + dirty_addr += len2; + len -= len2; + } + + if (!dirty) { /* this line was not modified, skip to next one */ + goto NEXT_LINE; + } + } + + /* Then compute actual bounds of the changed pixels, while + * copying them from 'src' to 'dst'. This depends on the pixel depth. + */ + switch (fbs->bytes_per_pixel) { + case 2: + { + const uint16_t* src = (const uint16_t*) src_line; + uint16_t* dst = (uint16_t*) dst_line; + + for (xx1 = 0; xx1 < width; xx1++) { + if (src[xx1] != dst[xx1]) { + break; + } + } + if (xx1 == width) { + break; + } + for (xx2 = width-1; xx2 > xx1; xx2--) { + if (src[xx2] != dst[xx2]) { + break; + } + } +#if HOST_WORDS_BIGENDIAN + /* Convert the guest little-endian pixels into big-endian ones */ + int xx = xx1; + for ( ; xx <= xx2; xx++ ) { + unsigned spix = src[xx]; + dst[xx] = (uint16_t)((spix << 8) | (spix >> 8)); + } +#else + memcpy( dst+xx1, src+xx1, (xx2-xx1+1)*2 ); +#endif + break; + } + + case 3: + { + for (xx1 = 0; xx1 < width; xx1 += 1) { + int xx = xx1*3; + if (src_line[xx+0] != dst_line[xx+0] || + src_line[xx+1] != dst_line[xx+1] || + src_line[xx+2] != dst_line[xx+2]) { + break; + } + } + if (xx1 == width) { + break; + } + for (xx2 = width-1; xx2 > xx1; xx2--) { + int xx = xx2*3; + if (src_line[xx+0] != dst_line[xx+0] || + src_line[xx+1] != dst_line[xx+1] || + src_line[xx+2] != dst_line[xx+2]) { + break; + } + } + memcpy( dst_line+xx1*3, src_line+xx1*3, (xx2-xx1+1)*3 ); + break; + } + + case 4: + { + const uint32_t* src = (const uint32_t*) src_line; + uint32_t* dst = (uint32_t*) dst_line; + + for (xx1 = 0; xx1 < width; xx1++) { + if (src[xx1] != dst[xx1]) { + break; + } + } + if (xx1 == width) { + break; + } + for (xx2 = width-1; xx2 > xx1; xx2--) { + if (src[xx2] != dst[xx2]) { + break; + } + } +#if HOST_WORDS_BIGENDIAN + /* Convert the guest little-endian pixels into big-endian ones */ + int xx = xx1; + for ( ; xx <= xx2; xx++ ) { + uint32_t spix = src[xx]; + spix = (spix << 16) | (spix >> 16); + spix = ((spix << 8) & 0xff00ff00) | ((spix >> 8) & 0x00ff00ff); + dst[xx] = spix; + } +#else + memcpy( dst+xx1, src+xx1, (xx2-xx1+1)*4 ); +#endif + break; + } + default: + return 0; + } + /* Update bounds if pixels on this line were modified */ + if (xx1 < width) { + if (xx1 < rect->xmin) rect->xmin = xx1; + if (xx2 > rect->xmax) rect->xmax = xx2; + if (yy < rect->ymin) rect->ymin = yy; + if (yy > rect->ymax) rect->ymax = yy; + } + NEXT_LINE: + src_line += fbs->src_pitch; + dst_line += fbs->dst_pitch; + } + + if (rect->ymin > rect->ymax) { /* nothing changed */ + return 0; + } + + /* Always clear the dirty VGA bits */ + cpu_physical_memory_reset_dirty(dirty_base + rect->ymin * fbs->src_pitch, + dirty_base + (rect->ymax+1)* fbs->src_pitch, + VGA_DIRTY_FLAG); + return 1; +} + + static void goldfish_fb_update_display(void *opaque) { struct goldfish_fb_state *s = (struct goldfish_fb_state *)opaque; - uint32_t addr; uint32_t base; - uint8_t* dst_line; uint8_t* src_line; - int y_first, y_last = 0; int full_update = 0; - int width, height, pitch; + int width, height, pitch; base = s->fb_base; if(base == 0) @@ -156,8 +440,6 @@ static void goldfish_fb_update_display(void *opaque) goldfish_device_set_irq(&s->dev, 0, 1); } - y_first = -1; - addr = base; if(s->need_update) { full_update = 1; if(s->need_int) { @@ -176,6 +458,19 @@ static void goldfish_fb_update_display(void *opaque) width = s->ds->surface->width; height = s->ds->surface->height; + FbUpdateState fbs; + FbUpdateRect rect; + + fbs.width = width; + fbs.height = height; + fbs.dst_pixels = dst_line; + fbs.dst_pitch = pitch; + fbs.bytes_per_pixel = goldfish_fb_get_bytes_per_pixel(s); + + fbs.src_pixels = src_line; + fbs.src_pitch = width*s->ds->surface->pf.bytes_per_pixel; + + #if STATS if (full_update) stats_full_updates += 1; @@ -195,99 +490,29 @@ static void goldfish_fb_update_display(void *opaque) if (s->blank) { memset( dst_line, 0, height*pitch ); - y_first = 0; - y_last = height-1; + rect.xmin = 0; + rect.ymin = 0; + rect.xmax = width-1; + rect.ymax = height-1; } - else if (full_update) + else { - int yy; - - for (yy = 0; yy < height; yy++, dst_line += pitch, src_line += width*2) - { - uint16_t* src = (uint16_t*) src_line; - uint16_t* dst = (uint16_t*) dst_line; - int nn; - - for (nn = 0; nn < width; nn++) { - unsigned spix = src[nn]; - unsigned dpix = dst[nn]; -#if HOST_WORDS_BIGENDIAN - spix = ((spix << 8) | (spix >> 8)) & 0xffff; -#else - if (spix != dpix) - break; -#endif - } - - if (nn == width) - continue; - -#if HOST_WORDS_BIGENDIAN - for ( ; nn < width; nn++ ) { - unsigned spix = src[nn]; - dst[nn] = (uint16_t)((spix << 8) | (spix >> 8)); - } -#else - memcpy( dst+nn, src+nn, (width-nn)*2 ); -#endif - - y_first = (y_first < 0) ? yy : y_first; - y_last = yy; + if (full_update) { /* don't use dirty-bits optimization */ + base = 0; } - } - else /* not a full update, should not happen very often with Android */ - { - int yy; - - for (yy = 0; yy < height; yy++, dst_line += pitch, src_line += width*2) - { - uint16_t* src = (uint16_t*) src_line; - uint16_t* dst = (uint16_t*) dst_line; - int len = width*2; -#if HOST_WORDS_BIGENDIAN - int nn; -#endif - int dirty = 0; - - while (len > 0) { - int len2 = TARGET_PAGE_SIZE - (addr & (TARGET_PAGE_SIZE-1)); - - if (len2 > len) - len2 = len; - - dirty |= cpu_physical_memory_get_dirty(addr, VGA_DIRTY_FLAG); - addr += len2; - len -= len2; - } - - if (!dirty) - continue; - -#if HOST_WORDS_BIGENDIAN - for (nn = 0; nn < width; nn++ ) { - unsigned spix = src[nn]; - dst[nn] = (uint16_t)((spix << 8) | (spix >> 8)); - } -#else - memcpy( dst, src, width*2 ); -#endif - - y_first = (y_first < 0) ? yy : y_first; - y_last = yy; + if (compute_fb_update_rect_linear(&fbs, base, &rect) == 0) { + return; } } - if (y_first < 0) - return; - - y_last += 1; - //printf("goldfish_fb_update_display %d %d, base %x\n", first, last, base); - - cpu_physical_memory_reset_dirty(base + y_first * width * 2, - base + y_last * width * 2, - VGA_DIRTY_FLAG); + rect.xmax += 1; + rect.ymax += 1; +#if 0 + printf("goldfish_fb_update_display (y:%d,h:%d,x=%d,w=%d)\n", + rect.ymin, rect.ymax-rect.ymin, rect.xmin, rect.xmax-rect.xmin); +#endif - dpy_update(s->ds, 0, y_first, width, y_last-y_first); + dpy_update(s->ds, rect.xmin, rect.ymin, rect.xmax-rect.xmin, rect.ymax-rect.ymin); } static void goldfish_fb_invalidate_display(void * opaque) @@ -408,6 +633,12 @@ void goldfish_fb_init(int id) s->dpi = 165; /* XXX: Find better way to get actual value ! */ + /* IMPORTANT: DO NOT COMPUTE s->pixel_format and s->bytes_per_pixel + * here because the display surface is going to change later. + */ + s->bytes_per_pixel = 0; + s->pixel_format = -1; + goldfish_device_add(&s->dev, goldfish_fb_readfn, goldfish_fb_writefn, s); register_savevm( "goldfish_fb", 0, GOLDFISH_FB_SAVE_VERSION, |