created an new EGL extension called ANDROID_swap_rectangle

ANDROID_swap_rectangle allows to specify the rectangle affected by eglSwapBuffers(), anything outside of this rectangle is unchanged. in particular EGL_BUFFER_DESTROYED only applies to that rectangle. This extension as well as EGL_BUFFER_PRESERVED allow major optimizations on surfaceflinger, which can redraw only the dirty area during compositing.

However, ANDROID_swap_rectangle allows further optimizations in EGL by reducing the amount of copy-back needed. ANDROID_swap_rectangle is particularily important for software implementations.
This commit is contained in:
Mathias Agopian
2009-05-04 19:29:25 -07:00
parent 9bd5da4db9
commit 2e20bffbab
7 changed files with 298 additions and 36 deletions

View File

@@ -195,17 +195,19 @@ void DisplayHardware::init(uint32_t dpy)
* Create our main surface
*/
surface = eglCreateWindowSurface(display, config, mNativeWindow.get(), NULL);
checkEGLErrors("eglCreateDisplaySurfaceANDROID");
if (eglQuerySurface(display, surface, EGL_SWAP_BEHAVIOR, &dummy) == EGL_TRUE) {
if (dummy == EGL_BUFFER_PRESERVED) {
mFlags |= BUFFER_PRESERVED;
}
}
if (strstr(egl_extensions, "ANDROID_swap_rectangle")) {
mFlags |= SWAP_RECTANGLE;
}
mDpiX = mNativeWindow->xdpi;
mDpiX = mNativeWindow->ydpi;
mRefreshRate = mNativeWindow->getDevice()->fps;
@@ -304,11 +306,12 @@ void DisplayHardware::flip(const Region& dirty) const
EGLDisplay dpy = mDisplay;
EGLSurface surface = mSurface;
if (mFlags & BUFFER_PRESERVED) {
if (mFlags & SWAP_RECTANGLE) {
Region newDirty(dirty);
newDirty.andSelf(Rect(mWidth, mHeight));
const Rect& b(newDirty.bounds());
//mNativeWindow->setSwapRectangle(b);
eglSetSwapRectangleANDROID(dpy, surface,
b.left, b.top, b.width(), b.height());
}
mPageFlipCount++;

View File

@@ -50,6 +50,7 @@ public:
BUFFER_PRESERVED = 0x00010000,
UPDATE_ON_DEMAND = 0x00020000, // video driver feature
SLOW_CONFIG = 0x00040000, // software
SWAP_RECTANGLE = 0x00080000,
};
DisplayHardware(

View File

@@ -803,12 +803,14 @@ void SurfaceFlinger::handleRepaint()
mInvalidRegion.orSelf(mDirtyRegion);
uint32_t flags = hw.getFlags();
if (flags & DisplayHardware::BUFFER_PRESERVED) {
// here we assume DisplayHardware::flip()'s implementation
// performs the copy-back optimization.
if ((flags & DisplayHardware::SWAP_RECTANGLE) ||
(flags & DisplayHardware::BUFFER_PRESERVED))
{
// we can redraw only what's dirty
} else {
if (flags & DisplayHardware::UPDATE_ON_DEMAND) {
// we need to fully redraw the part that will be updated
// we need to redraw the rectangle that will be updated
// (pushed to the framebuffer).
mDirtyRegion.set(mInvalidRegion.bounds());
} else {
// we need to redraw everything
@@ -890,7 +892,9 @@ void SurfaceFlinger::debugFlashRegions()
{
const DisplayHardware& hw(graphicPlane(0).displayHardware());
const uint32_t flags = hw.getFlags();
if (!(flags & DisplayHardware::BUFFER_PRESERVED)) {
if (!((flags & DisplayHardware::SWAP_RECTANGLE) ||
(flags & DisplayHardware::BUFFER_PRESERVED))) {
const Region repaint((flags & DisplayHardware::UPDATE_ON_DEMAND) ?
mDirtyRegion.bounds() : hw.bounds());
composeSurfaces(repaint);

View File

@@ -131,6 +131,18 @@ typedef EGLBoolean (EGLAPIENTRYP PFNEGLDESTROYIMAGEKHRPROC) (EGLDisplay dpy, EGL
/* Interfaces defined by EGL_KHR_image above */
#endif
#ifndef EGL_ANDROID_swap_rectangle
#define EGL_ANDROID_swap_rectangle 1
#ifdef EGL_EGLEXT_PROTOTYPES
EGLAPI EGLBoolean EGLAPIENTRY eglSetSwapRectangleANDROID (EGLDisplay dpy, EGLSurface draw, EGLint left, EGLint top, EGLint width, EGLint height);
#endif /* EGL_EGLEXT_PROTOTYPES */
typedef EGLBoolean (EGLAPIENTRYP PFNEGLSETSWAPRECTANGLEANDROIDPROC) (EGLDisplay dpy, EGLSurface draw, EGLint left, EGLint top, EGLint width, EGLint height);
#endif
#ifdef __cplusplus
}
#endif

View File

@@ -157,6 +157,7 @@ struct egl_surface_t
virtual EGLint getRefreshRate() const;
virtual EGLint getSwapBehavior() const;
virtual EGLBoolean swapBuffers();
virtual EGLBoolean setSwapRectangle(EGLint l, EGLint t, EGLint w, EGLint h);
protected:
GGLSurface depth;
};
@@ -190,6 +191,11 @@ EGLint egl_surface_t::getRefreshRate() const {
EGLint egl_surface_t::getSwapBehavior() const {
return EGL_BUFFER_PRESERVED;
}
EGLBoolean egl_surface_t::setSwapRectangle(
EGLint l, EGLint t, EGLint w, EGLint h)
{
return EGL_FALSE;
}
// ----------------------------------------------------------------------------
@@ -214,16 +220,104 @@ struct egl_window_surface_v2_t : public egl_surface_t
virtual EGLint getVerticalResolution() const;
virtual EGLint getRefreshRate() const;
virtual EGLint getSwapBehavior() const;
virtual EGLBoolean setSwapRectangle(EGLint l, EGLint t, EGLint w, EGLint h);
private:
status_t lock(android_native_buffer_t* buf, int usage, void** vaddr);
status_t unlock(android_native_buffer_t* buf);
android_native_window_t* nativeWindow;
android_native_buffer_t* buffer;
android_native_buffer_t* previousBuffer;
gralloc_module_t const* module;
int width;
int height;
void* bits;
GGLFormat const* pixelFormatTable;
struct Rect {
inline Rect() { };
inline Rect(int32_t w, int32_t h)
: left(0), top(0), right(w), bottom(h) { }
inline Rect(int32_t l, int32_t t, int32_t r, int32_t b)
: left(l), top(t), right(r), bottom(b) { }
Rect& andSelf(const Rect& r) {
left = max(left, r.left);
top = max(top, r.top);
right = min(right, r.right);
bottom = min(bottom, r.bottom);
return *this;
}
bool isEmpty() const {
return (left>=right || top>=bottom);
}
void dump(char const* what) {
LOGD("%s { %5d, %5d, w=%5d, h=%5d }",
what, left, top, right-left, bottom-top);
}
int32_t left;
int32_t top;
int32_t right;
int32_t bottom;
};
struct Region {
inline Region() : count(0) { }
static Region subtract(const Rect& lhs, const Rect& rhs) {
Region reg;
Rect* storage = reg.storage;
if (!lhs.isEmpty()) {
if (lhs.top < rhs.top) { // top rect
storage->left = lhs.left;
storage->top = lhs.top;
storage->right = lhs.right;
storage->bottom = max(lhs.top, rhs.top);
storage++;
}
if (lhs.left < rhs.left) { // left-side rect
storage->left = lhs.left;
storage->top = max(lhs.top, rhs.top);
storage->right = max(lhs.left, rhs.left);
storage->bottom = min(lhs.bottom, rhs.bottom);
storage++;
}
if (lhs.right > rhs.right) { // right-side rect
storage->left = min(lhs.right, rhs.right);
storage->top = max(lhs.top, rhs.top);
storage->right = lhs.right;
storage->bottom = min(lhs.bottom, rhs.bottom);
storage++;
}
if (lhs.bottom > rhs.bottom) { // bottom rect
storage->left = lhs.left;
storage->top = min(lhs.bottom, rhs.bottom);
storage->right = lhs.right;
storage->bottom = lhs.bottom;
storage++;
}
reg.count = storage - reg.storage;
}
return reg;
}
bool isEmpty() const {
return count<=0;
}
ssize_t getRects(Rect const* * rects) const {
*rects = storage;
return count;
}
private:
Rect storage[4];
ssize_t count;
};
void copyBlt(
android_native_buffer_t* dst, void* dst_vaddr,
android_native_buffer_t* src, void const* src_vaddr,
const Rect* reg, ssize_t count);
Rect dirtyRegion;
Rect oldDirtyRegion;
};
egl_window_surface_v2_t::egl_window_surface_v2_t(EGLDisplay dpy,
@@ -231,16 +325,22 @@ egl_window_surface_v2_t::egl_window_surface_v2_t(EGLDisplay dpy,
int32_t depthFormat,
android_native_window_t* window)
: egl_surface_t(dpy, config, depthFormat),
nativeWindow(window), buffer(0), module(0), bits(NULL)
nativeWindow(window), buffer(0), previousBuffer(0), module(0),
bits(NULL)
{
hw_module_t const* pModule;
hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &pModule);
module = reinterpret_cast<gralloc_module_t const*>(pModule);
pixelFormatTable = gglGetPixelFormatTable();
// keep a reference on the window
nativeWindow->common.incRef(&nativeWindow->common);
// dequeue a buffer
nativeWindow->dequeueBuffer(nativeWindow, &buffer);
// allocate a corresponding depth-buffer
width = buffer->width;
height = buffer->height;
if (depthFormat) {
@@ -254,14 +354,32 @@ egl_window_surface_v2_t::egl_window_surface_v2_t(EGLDisplay dpy,
}
}
// keep a reference on the buffer
buffer->common.incRef(&buffer->common);
}
egl_window_surface_v2_t::~egl_window_surface_v2_t() {
if (buffer) {
buffer->common.decRef(&buffer->common);
}
if (previousBuffer) {
previousBuffer->common.decRef(&previousBuffer->common);
}
nativeWindow->common.decRef(&nativeWindow->common);
}
void egl_window_surface_v2_t::connect()
{
// Lock the buffer
nativeWindow->lockBuffer(nativeWindow, buffer);
lock(buffer, GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN, &bits);
// pin the buffer down
if (lock(buffer, GRALLOC_USAGE_SW_READ_OFTEN |
GRALLOC_USAGE_SW_WRITE_OFTEN, &bits) != NO_ERROR) {
LOGE("eglSwapBuffers() failed to lock buffer %p (%ux%u)",
buffer, buffer->width, buffer->height);
setError(EGL_BAD_ACCESS, EGL_NO_SURFACE);
// FIXME: we should make sure we're not accessing the buffer anymore
}
}
void egl_window_surface_v2_t::disconnect()
@@ -286,38 +404,86 @@ status_t egl_window_surface_v2_t::unlock(android_native_buffer_t* buf)
return err;
}
void egl_window_surface_v2_t::copyBlt(
android_native_buffer_t* dst, void* dst_vaddr,
android_native_buffer_t* src, void const* src_vaddr,
const Rect* reg, ssize_t count)
{
// FIXME: use copybit if possible
// NOTE: dst and src must be the same format
Rect r;
const size_t bpp = pixelFormatTable[src->format].size;
const size_t dbpr = dst->stride * bpp;
const size_t sbpr = src->stride * bpp;
egl_window_surface_v2_t::~egl_window_surface_v2_t() {
if (buffer) {
buffer->common.decRef(&buffer->common);
uint8_t const * const src_bits = (uint8_t const *)src_vaddr;
uint8_t * const dst_bits = (uint8_t *)dst_vaddr;
for (int i= 0 ; i<count ; i++) {
const Rect& r(reg[i]);
ssize_t w = r.right - r.left;
ssize_t h = r.bottom - r.top;
if (w <= 0 || h<=0) continue;
size_t size = w * bpp;
uint8_t const * s = src_bits + (r.left + src->stride * r.top) * bpp;
uint8_t * d = dst_bits + (r.left + dst->stride * r.top) * bpp;
if (dbpr==sbpr && size==sbpr) {
size *= h;
h = 1;
}
do {
memcpy(d, s, size);
d += dbpr;
s += sbpr;
} while (--h > 0);
}
nativeWindow->common.decRef(&nativeWindow->common);
}
EGLBoolean egl_window_surface_v2_t::swapBuffers()
{
// TODO: this is roughly the code needed for preserving the back buffer
// efficiently. dirty is the area that has been modified.
//Region newDirty(dirty);
//newDirty.andSelf(Rect(nativeWindow->width, nativeWindow->height));
//mDirty = newDirty;
//const Region copyback(mDirty.subtract(newDirty));
//mDisplaySurface->copyFrontToBack(copyback);
/*
* Handle eglSetSwapRectangleANDROID()
* We copyback from the front buffer
*/
if (!dirtyRegion.isEmpty()) {
dirtyRegion.andSelf(Rect(buffer->width, buffer->height));
if (previousBuffer) {
const Region copyBack(Region::subtract(oldDirtyRegion, dirtyRegion));
if (!copyBack.isEmpty()) {
Rect const* list;
ssize_t count = copyBack.getRects(&list);
// copy from previousBuffer to buffer
void* prevBits;
if (lock(previousBuffer,
GRALLOC_USAGE_SW_READ_OFTEN, &prevBits) == NO_ERROR)
{
copyBlt(buffer, bits, previousBuffer, prevBits, list, count);
unlock(previousBuffer);
}
}
}
oldDirtyRegion = dirtyRegion;
}
if (previousBuffer) {
previousBuffer->common.decRef(&previousBuffer->common);
previousBuffer = 0;
}
unlock(buffer);
previousBuffer = buffer;
nativeWindow->queueBuffer(nativeWindow, buffer);
buffer->common.decRef(&buffer->common); buffer = 0;
buffer = 0;
// dequeue a new buffer
nativeWindow->dequeueBuffer(nativeWindow, &buffer);
buffer->common.incRef(&buffer->common);
// TODO: lockBuffer should rather be executed when the very first
// direct rendering occurs.
void* vaddr;
nativeWindow->lockBuffer(nativeWindow, buffer);
lock(buffer, GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN, &bits);
// reallocate the depth-buffer if needed
if ((width != buffer->width) || (height != buffer->height)) {
// TODO: we probably should reset the swap rect here
// if the window size has changed
@@ -330,11 +496,31 @@ EGLBoolean egl_window_surface_v2_t::swapBuffers()
depth.stride = buffer->stride;
depth.data = (GGLubyte*)malloc(depth.stride*depth.height*2);
if (depth.data == 0) {
setError(EGL_BAD_ALLOC, EGL_NO_SURFACE);
setError(EGL_BAD_ALLOC, EGL_FALSE);
return EGL_FALSE;
}
}
}
// keep a reference on the buffer
buffer->common.incRef(&buffer->common);
// finally pin the buffer down
if (lock(buffer, GRALLOC_USAGE_SW_READ_OFTEN |
GRALLOC_USAGE_SW_WRITE_OFTEN, &bits) != NO_ERROR) {
LOGE("eglSwapBuffers() failed to lock buffer %p (%ux%u)",
buffer, buffer->width, buffer->height);
setError(EGL_BAD_ACCESS, EGL_NO_SURFACE);
// FIXME: we should make sure we're not accessing the buffer anymore
}
return EGL_TRUE;
}
EGLBoolean egl_window_surface_v2_t::setSwapRectangle(
EGLint l, EGLint t, EGLint w, EGLint h)
{
dirtyRegion = Rect(l, t, l+w, t+h);
return EGL_TRUE;
}
@@ -402,12 +588,22 @@ EGLint egl_window_surface_v2_t::getVerticalResolution() const {
EGLint egl_window_surface_v2_t::getRefreshRate() const {
return (60 * EGL_DISPLAY_SCALING); // FIXME
}
EGLint egl_window_surface_v2_t::getSwapBehavior() const {
//uint32_t flags = nativeWindow->flags;
//if (flags & SURFACE_FLAG_PRESERVE_CONTENT)
// return EGL_BUFFER_PRESERVED;
// This is now a feature of EGL, currently we don't preserve
// the content of the buffers.
EGLint egl_window_surface_v2_t::getSwapBehavior() const
{
/*
* EGL_BUFFER_PRESERVED means that eglSwapBuffers() completely preserves
* the content of the swapped buffer.
*
* EGL_BUFFER_DESTROYED means that the content of the buffer is lost.
*
* However when ANDROID_swap_retcangle is supported, EGL_BUFFER_DESTROYED
* only applies to the area specified by eglSetSwapRectangleANDROID(), that
* is, everything outside of this area is preserved.
*
* This implementation of EGL assumes the later case.
*
*/
return EGL_BUFFER_DESTROYED;
}
@@ -581,6 +777,7 @@ static char const * const gExtensionsString =
"KHR_image_base "
// "KHR_image_pixmap "
"EGL_ANDROID_image_native_buffer "
"EGL_ANDROID_swap_rectangle "
;
// ----------------------------------------------------------------------------
@@ -1765,3 +1962,23 @@ EGLBoolean eglDestroyImageKHR(EGLDisplay dpy, EGLImageKHR img)
return EGL_TRUE;
}
// ----------------------------------------------------------------------------
// ANDROID extensions
// ----------------------------------------------------------------------------
EGLBoolean eglSetSwapRectangleANDROID(EGLDisplay dpy, EGLSurface draw,
EGLint left, EGLint top, EGLint width, EGLint height)
{
if (egl_display_t::is_valid(dpy) == EGL_FALSE)
return setError(EGL_BAD_DISPLAY, EGL_FALSE);
egl_surface_t* d = static_cast<egl_surface_t*>(draw);
if (d->dpy != dpy)
return setError(EGL_BAD_DISPLAY, EGL_FALSE);
// post the surface
d->setSwapRectangle(left, top, width, height);
return EGL_TRUE;
}

View File

@@ -60,6 +60,7 @@ static char const * const gExtensionString =
"KHR_image_base "
"KHR_image_pixmap "
"EGL_ANDROID_image_native_buffer "
"EGL_ANDROID_swap_rectangle "
;
// ----------------------------------------------------------------------------
@@ -1568,3 +1569,23 @@ EGLBoolean eglDestroyImageKHR(EGLDisplay dpy, EGLImageKHR img)
return EGL_FALSE;
}
// ----------------------------------------------------------------------------
// ANDROID extensions
// ----------------------------------------------------------------------------
EGLBoolean eglSetSwapRectangleANDROID(EGLDisplay dpy, EGLSurface draw,
EGLint left, EGLint top, EGLint width, EGLint height)
{
if (!validate_display_surface(dpy, draw))
return EGL_FALSE;
egl_display_t const * const dp = get_display(dpy);
egl_surface_t const * const s = get_surface(draw);
if (s->cnx->hooks->egl.eglSetSwapRectangleANDROID) {
return s->cnx->hooks->egl.eglSetSwapRectangleANDROID(dp->dpys[s->impl],
s->surface, left, top, width, height);
}
return EGL_FALSE;
}

View File

@@ -50,3 +50,7 @@ EGL_ENTRY(EGLBoolean, eglLockSurfaceKHR, EGLDisplay, EGLSurface, const EGLint
EGL_ENTRY(EGLBoolean, eglUnlockSurfaceKHR, EGLDisplay, EGLSurface)
EGL_ENTRY(EGLImageKHR, eglCreateImageKHR, EGLDisplay, EGLContext, EGLenum, EGLClientBuffer, const EGLint *)
EGL_ENTRY(EGLBoolean, eglDestroyImageKHR, EGLDisplay, EGLImageKHR)
/* ANDROID extensions */
EGL_ENTRY(EGLBoolean, eglSetSwapRectangleANDROID, EGLDisplay, EGLSurface, EGLint, EGLint, EGLint, EGLint)