evas-drm: Add Evas Drm Engine (software only currently)

@feature: Add working Evas_Drm Engine (software only currently)

Signed-off-by: Chris Michael <cp.michael@samsung.com>
This commit is contained in:
Chris Michael 2014-02-11 07:09:39 +00:00
parent b36adec952
commit b42af265be
5 changed files with 1550 additions and 30 deletions

View File

@ -11,8 +11,19 @@ struct _Evas_Engine_Info_Drm
struct
{
Evas *evas;
unsigned int rotation, depth;
Eina_Bool destination_alpha : 1;
int fd;
Eina_Bool own_fd : 1;
int tty;
Eina_Bool own_tty : 1;
int output;
int plane;
} info;
/* non-blocking or blocking mode */

View File

@ -0,0 +1,630 @@
#include "evas_engine.h"
#include <linux/vt.h>
#include <linux/kd.h>
#include <sys/mman.h>
/* NB: REALLY hate to store this here, but sigaction signal handlers cannot
* pass any 'user data' to the signal handlers :( */
static Evas_Engine_Info_Drm *siginfo;
static int
_evas_drm_card_open(int card)
{
char dev[32];
int fd = -1;
uint64_t dumb;
sprintf(dev, DRM_DEV_NAME, DRM_DIR_NAME, card);
if ((fd = open(dev, (O_RDWR | O_CLOEXEC))) < 0)
{
CRI("Could not open drm device %s: %m", dev);
return -1;
}
drmVersionPtr ver;
if ((ver = drmGetVersion(fd)))
{
DBG("Drm Driver Name: %s", ver->name);
drmFreeVersion(ver);
}
/* check for dumb buffer support
*
* NB: This checks that we can at least support software rendering */
if ((drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &dumb) < 0) || (!dumb))
{
CRI("Drm Device %s does not support software rendering", dev);
/* close the card */
close(fd);
/* return failure */
return -1;
}
DBG("Opened Drm Card %s: %d", dev, fd);
/* return opened card */
return fd;
}
static int
_evas_drm_tty_open(Evas_Engine_Info_Drm *info)
{
int tty = STDIN_FILENO;
/* check if the current stdin is a valid tty */
if (!isatty(tty))
{
/* if not, try to open the curren tty */
if ((tty = open("/dev/tty", (O_RDWR | O_CLOEXEC))) < 0)
{
int tty0 = -1, num = -1;
char name[16];
/* if that fails, try tty0 */
if ((tty0 = open("/dev/tty0", (O_WRONLY | O_CLOEXEC))) < 0)
{
CRI("Could not open tty0: %m");
return -1;
}
/* try to find a non-opened tty */
if ((ioctl(tty0, VT_OPENQRY, &num) < 0) || (num < 0))
{
CRI("Could not find a non-opened tty");
close(tty0);
return -1;
}
snprintf(name, sizeof(name), "/dev/tty%d", num);
/* try to open this tty */
if ((tty = open(name, (O_RDWR | O_CLOEXEC))) < 0)
{
CRI("Could not open tty: %s", name);
close(tty0);
return -1;
}
/* set flag that evas should close this tty */
info->info.own_tty = EINA_TRUE;
/* close /dev/tty0 */
close(tty0);
}
}
else
{
/* set flag that evas should close this tty */
info->info.own_tty = EINA_TRUE;
}
DBG("Opened Tty %d", tty);
return tty;
}
static int
_evas_drm_crtc_find(int fd, drmModeRes *res, drmModeConnector *conn)
{
int crtc = -1;
drmModeEncoder *enc = NULL;
/* if this connector already has an encoder, get it */
if (conn->encoder_id) enc = drmModeGetEncoder(fd, conn->encoder_id);
/* if this encoder already has a crtc, lets try to use that */
if ((enc) && (enc->crtc_id)) crtc = enc->crtc_id;
if (crtc < 0)
{
int i = 0, c = 0;
/* if this connector has no encoder, we need to find one */
for (; i < conn->count_encoders; ++i)
{
/* try to get this encoder */
if (!(enc = drmModeGetEncoder(fd, conn->encoders[i])))
continue;
/* loop global crtcs */
for (; c < res->count_crtcs; ++c)
{
/* does this crtc work with this encoder ? */
if (!(enc->possible_crtcs & (1 << c))) continue;
/* FIXME: We could be more proactive here and check that
* nobody else is using this crtc */
/* if it works, let's use it */
crtc = res->crtcs[c];
break;
}
if (crtc >= 0) break;
}
}
/* free the encoder */
if (enc) drmModeFreeEncoder(enc);
return crtc;
}
static void
_evas_drm_tty_sigusr1(int x EINA_UNUSED, siginfo_t *info, void *data EINA_UNUSED)
{
Evas_Engine_Info_Drm *einfo;
DBG("Caught SIGUSR1");
if (!(einfo = siginfo)) return;
/* TODO: set canvas to not render */
DBG("\tDrop Master & Release VT");
/* drop drm master */
if (einfo->info.own_fd)
{
if (drmDropMaster(einfo->info.fd) != 0)
WRN("Could not drop drm master: %m");
}
/* release vt */
if (einfo->info.own_tty)
{
if (ioctl(einfo->info.tty, VT_RELDISP, 1) < 0)
WRN("Could not release vt: %m");
}
}
static void
_evas_drm_tty_sigusr2(int x EINA_UNUSED, siginfo_t *info, void *data EINA_UNUSED)
{
Evas_Engine_Info_Drm *einfo;
DBG("Caught SIGUSR2");
if (!(einfo = siginfo)) return;
/* TODO: set canvas to render again */
DBG("\tAcquire VT & Set Master");
/* acquire vt */
if (einfo->info.own_tty)
{
if (ioctl(einfo->info.tty, VT_RELDISP, VT_ACKACQ) < 0)
WRN("Could not acquire vt: %m");
}
/* set master */
if (einfo->info.own_fd)
{
if (drmSetMaster(einfo->info.fd) != 0)
WRN("Could not set drm master: %m");
}
}
static Eina_Bool
_evas_drm_tty_setup(Evas_Engine_Info_Drm *info)
{
struct vt_mode vtmode = { 0 };
struct sigaction sig;
/* check for valid tty */
if (info->info.tty < 0) return EINA_FALSE;
#if 0
/* set vt to graphics mode */
if (ioctl(info->info.tty, KDSETMODE, KD_GRAPHICS) < 0)
{
CRI("Could not set tty to graphics mode: %m");
return EINA_FALSE;
}
#endif
/* setup tty rel/acq signals */
vtmode.mode = VT_PROCESS;
vtmode.waitv = 0;
vtmode.relsig = SIGUSR1;
vtmode.acqsig = SIGUSR2;
if (ioctl(info->info.tty, VT_SETMODE, &vtmode) < 0)
{
CRI("Could not set tty mode: %m");
return EINA_FALSE;
}
/* store info struct
*
* NB: REALLY hate to store this here, but sigaction signal handlers cannot
* pass any 'user data' to the signal handlers :(
*/
siginfo = info;
/* setup signal handlers for above signals */
sig.sa_sigaction = _evas_drm_tty_sigusr1;
sig.sa_flags = (SA_NODEFER | SA_SIGINFO | SA_RESTART);
sigemptyset(&sig.sa_mask);
sigaction(SIGUSR1, &sig, NULL);
sig.sa_sigaction = _evas_drm_tty_sigusr2;
sig.sa_flags = (SA_NODEFER | SA_SIGINFO | SA_RESTART);
sigemptyset(&sig.sa_mask);
sigaction(SIGUSR2, &sig, NULL);
return EINA_TRUE;
}
static void
_evas_drm_outbuf_page_flip(int fd EINA_UNUSED, unsigned int seq EINA_UNUSED, unsigned int tv_sec EINA_UNUSED, unsigned int tv_usec EINA_UNUSED, void *data)
{
Outbuf *ob;
/* get the output buffer from data */
if (!(ob = data)) return;
/* DBG("Page Flip Event"); */
ob->priv.pending_flip = EINA_FALSE;
ob->priv.curr = (ob->priv.curr + 1) % ob->priv.num;
}
static void
_evas_drm_outbuf_vblank(int fd EINA_UNUSED, unsigned int frame EINA_UNUSED, unsigned int sec EINA_UNUSED, unsigned int usec EINA_UNUSED, void *data)
{
Outbuf *ob;
/* get the output buffer from data */
if (!(ob = data)) return;
DBG("VBlank Event");
}
Eina_Bool
evas_drm_init(Evas_Engine_Info_Drm *info, int card)
{
/* check for valid engine info */
if (!info) return EINA_FALSE;
setvbuf(stdout, NULL, _IONBF, 0);
/* check if we already opened the card */
if (info->info.fd < 0)
{
/* try to open the drm card */
if ((info->info.fd = _evas_drm_card_open(card)) < 0)
return EINA_FALSE;
/* set flag to indicate that evas opened the card and we should
* be the one to close it */
info->info.own_fd = EINA_TRUE;
}
/* check if we already opened the tty */
if (info->info.tty < 0)
{
/* try to open the current tty */
if ((info->info.tty = _evas_drm_tty_open(info)) < 0)
{
/* check if we already opened the card. if so, close it */
if ((info->info.fd >= 0) && (info->info.own_fd))
{
close(info->info.fd);
info->info.fd = -1;
}
return EINA_FALSE;
}
}
/* with the tty opened, we need to set it up */
if (!_evas_drm_tty_setup(info))
{
/* setup of tty failed, close it */
if ((info->info.tty >= 0) && (info->info.own_tty))
close(info->info.tty);
/* FIXME: Close card also ?? */
return EINA_FALSE;
}
return EINA_TRUE;
}
Eina_Bool
evas_drm_shutdown(Evas_Engine_Info_Drm *info)
{
/* check for valid engine info */
if (!info) return EINA_TRUE;
/* check if we already opened the tty. if so, close it */
if ((info->info.tty >= 0) && (info->info.own_tty))
{
close(info->info.tty);
info->info.tty = -1;
}
/* check if we already opened the card. if so, close it */
if ((info->info.fd >= 0) && (info->info.own_fd))
{
close(info->info.fd);
info->info.fd = -1;
}
return EINA_TRUE;
}
Eina_Bool
evas_drm_outbuf_setup(Outbuf *ob)
{
drmModeRes *res;
drmModeConnector *conn;
int i = 0;
/* check for valid Output buffer */
if ((!ob) || (ob->priv.fd < 0)) return EINA_FALSE;
/* setup drmHandleEvent context */
memset(&ob->priv.ctx, 0, sizeof(ob->priv.ctx));
ob->priv.ctx.version = DRM_EVENT_CONTEXT_VERSION;
ob->priv.ctx.page_flip_handler = _evas_drm_outbuf_page_flip;
ob->priv.ctx.vblank_handler = _evas_drm_outbuf_vblank;
/* try to get drm resources */
if (!(res = drmModeGetResources(ob->priv.fd)))
{
CRI("Could not get drm resources: %m");
return EINA_FALSE;
}
/* loop the connectors */
for (; i < res->count_connectors; ++i)
{
int crtc = -1;
int m = 0;
/* try to get this connector */
if (!(conn = drmModeGetConnector(ob->priv.fd, res->connectors[i])))
{
WRN("Could not get drm connector %d: %m", i);
continue;
}
/* make sure this connector is actually connected */
if (conn->connection != DRM_MODE_CONNECTED)
{
/* free connector resources */
drmModeFreeConnector(conn);
continue;
}
/* make sure it has modes */
if (conn->count_modes == 0)
{
/* free connector resources */
drmModeFreeConnector(conn);
continue;
}
/* try to find a crtc for this connector */
if ((crtc = _evas_drm_crtc_find(ob->priv.fd, res, conn)) < 0)
{
/* free connector resources */
drmModeFreeConnector(conn);
continue;
}
/* record the connector id */
ob->priv.conn = conn->connector_id;
/* record the crtc id */
ob->priv.crtc = crtc;
/* spew out connector properties for testing */
/* drmModePropertyPtr props; */
/* for (m = 0; m < conn->count_props; m++) */
/* { */
/* props = drmModeGetProperty(ob->priv.fd, conn->props[m]); */
/* if (!props) continue; */
/* DBG("Property Name: %s", props->name); */
/* } */
/* record the current mode */
memcpy(&ob->priv.mode, &conn->modes[0], sizeof(ob->priv.mode));
DBG("Output Current Mode: %d: %d %d", ob->priv.conn,
conn->modes[0].hdisplay, conn->modes[0].vdisplay);
for (m = 0; m < conn->count_modes; m++)
{
DBG("Output Available Mode: %d: %d %d %d", ob->priv.conn,
conn->modes[m].hdisplay, conn->modes[m].vdisplay,
conn->modes[m].vrefresh);
/* try to find a mode which matches the requested size */
if ((conn->modes[m].hdisplay == ob->w) &&
(conn->modes[m].vdisplay == ob->h) &&
(conn->modes[m].vrefresh == 60))
{
memcpy(&ob->priv.mode, &conn->modes[m],
sizeof(ob->priv.mode));
}
}
/* free connector resources */
drmModeFreeConnector(conn);
break;
}
/* free drm resources */
drmModeFreeResources(res);
return EINA_TRUE;
}
void
evas_drm_outbuf_framebuffer_set(Outbuf *ob, Buffer *buffer)
{
int ret;
/* validate params */
if ((!ob) || (!buffer)) return;
ret = drmModeSetCrtc(ob->priv.fd, ob->priv.crtc, buffer->fb, 0, 0,
&ob->priv.conn, 1, &ob->priv.mode);
if (ret) ERR("Failed to set crtc: %m");
else buffer->valid = EINA_TRUE;
}
Eina_Bool
evas_drm_framebuffer_create(int fd, Buffer *buffer, int depth)
{
struct drm_mode_create_dumb carg;
struct drm_mode_destroy_dumb darg;
struct drm_mode_map_dumb marg;
/* check for valid info */
if (fd < 0) return EINA_FALSE;
/* try to create a dumb buffer */
memset(&carg, 0, sizeof(carg));
carg.width = buffer->w;
carg.height = buffer->h;
carg.bpp = depth;
if (drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &carg) < 0)
{
ERR("Could not create dumb buffer: %m");
return EINA_FALSE;
}
buffer->stride = carg.pitch;
buffer->size = carg.size;
buffer->handle = carg.handle;
/* try to create a framebuffer object */
/* FIXME: Hardcoded bpp */
if (drmModeAddFB(fd, buffer->w, buffer->h, 24, depth, buffer->stride,
buffer->handle, &buffer->fb))
{
ERR("Could not create framebuffer object: %m");
goto add_err;
}
DBG("Creating dumb buffer: %d %d %d %d", buffer->fb,
buffer->w, buffer->h, depth);
/* try to mmap the buffer */
memset(&marg, 0, sizeof(marg));
marg.handle = buffer->handle;
if (drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &marg))
{
ERR("Could not map dumb buffer: %m");
goto map_err;
}
/* do actual mmap of memory */
buffer->data =
mmap(0, buffer->size, (PROT_WRITE),
// mmap(0, buffer->size, (PROT_READ | PROT_WRITE),
MAP_SHARED, fd, marg.offset);
if (buffer->data == MAP_FAILED)
{
ERR("Could not mmap dumb buffer: %m");
goto map_err;
}
/* clear memory */
memset(buffer->data, 0, buffer->size);
return EINA_TRUE;
map_err:
/* remove the framebuffer */
drmModeRmFB(fd, buffer->fb);
add_err:
/* destroy buffer */
memset(&darg, 0, sizeof(darg));
darg.handle = buffer->handle;
drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &darg);
return EINA_FALSE;
}
void
evas_drm_framebuffer_destroy(int fd, Buffer *buffer)
{
struct drm_mode_destroy_dumb darg;
/* check for valid info */
if (fd < 0) return;
/* unmap the buffer data */
if (buffer->data) munmap(buffer->data, buffer->size);
/* remove the framebuffer */
drmModeRmFB(fd, buffer->fb);
/* destroy buffer */
memset(&darg, 0, sizeof(darg));
darg.handle = buffer->handle;
drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &darg);
}
Eina_Bool
evas_drm_framebuffer_send(Outbuf *ob, Buffer *buffer, Eina_Rectangle *rects, unsigned int count)
{
int ret;
/* check for valid Output buffer */
if ((!ob) || (ob->priv.fd < 0)) return EINA_FALSE;
/* check for valid buffer */
if ((!buffer) || (!buffer->valid)) return EINA_FALSE;
#ifdef DRM_MODE_FEATURE_DIRTYFB
drmModeClip *clip;
unsigned int i = 0;
/* WRN("drmModeDirtyFB is experimental"); */
/* NB: alloca automatically frees memory */
clip = alloca(count * sizeof(drmModeClip));
for (i = 0; i < count; i++)
{
clip[i].x1 = rects[i].x;
clip[i].y1 = rects[i].y;
clip[i].x2 = rects[i].w;
clip[i].y2 = rects[i].h;
}
ret = drmModeDirtyFB(ob->priv.fd, buffer->fb, clip, count);
if (ret)
{
if (ret == -EINVAL)
ERR("Could not set FB Dirty: %m");
}
#endif
ret = drmModePageFlip(ob->priv.fd, ob->priv.crtc, buffer->fb,
DRM_MODE_PAGE_FLIP_EVENT, ob);
if (ret)
{
ERR("Cannot flip crtc for connector %u: %m", ob->priv.conn);
return EINA_FALSE;
}
ob->priv.sent = buffer;
ob->priv.pending_flip = EINA_TRUE;
// while (ob->priv.pending_flip)
drmHandleEvent(ob->priv.fd, &ob->priv.ctx);
return EINA_TRUE;
}

View File

@ -1,6 +1,3 @@
#include "evas_common_private.h"
#include "evas_private.h"
#include "Evas_Engine_Drm.h"
#include "evas_engine.h"
/* local structures */
@ -25,7 +22,7 @@ struct _Render_Engine
};
/* local function prototypes */
static void *_output_setup(int w, int h, unsigned int rotation, unsigned int depth, Eina_Bool alpha, int swap);
static void *_output_setup(Evas_Engine_Info_Drm *info, int w, int h, int swap);
/* function tables - filled in later (func and parent func) */
static Evas_Func func, pfunc;
@ -35,7 +32,7 @@ int _evas_engine_drm_log_dom;
/* local functions */
static void *
_output_setup(int w, int h, unsigned int rotation, unsigned int depth, Eina_Bool alpha, int swap)
_output_setup(Evas_Engine_Info_Drm *info, int w, int h, int swap)
{
Render_Engine *re;
@ -53,15 +50,31 @@ _output_setup(int w, int h, unsigned int rotation, unsigned int depth, Eina_Bool
/* set tilesize */
evas_common_tilebuf_set_tile_size(re->tb, TILESIZE, TILESIZE);
/* if we have no drm device, get one */
if (info->info.fd < 0)
{
/* try to init drm (this includes openening the card & tty) */
if (!evas_drm_init(info, 0))
{
if (re->tb) evas_common_tilebuf_free(re->tb);
free(re);
return NULL;
}
}
if (swap)
{
/* free any existing outbuf */
if (re->ob) evas_outbuf_free(re->ob);
/* try to create new outbuf */
if (!(re->ob = evas_outbuf_setup(w, h, rotation, depth, alpha)))
if (!(re->ob = evas_outbuf_setup(info, w, h)))
{
if (re->tb) evas_common_tilebuf_free(re->tb);
/* shutdown drm card & tty */
evas_drm_shutdown(info);
free(re);
return NULL;
}
@ -111,6 +124,12 @@ eng_setup(Evas *evas, void *einfo)
/* try to get the evas public data */
if (!(epd = eo_data_scope_get(evas, EVAS_CLASS))) return 0;
/* set canvas reference
*
* NB: We do this here so that on a vt switch, we can disable
* rendering (or re-enable) for this canvas */
info->info.evas = evas;
/* check for valid engine output */
if (!(re = epd->engine.data.output))
{
@ -138,9 +157,7 @@ eng_setup(Evas *evas, void *einfo)
}
/* try to create a new render_engine */
if (!(re = _output_setup(epd->output.w, epd->output.h,
info->info.rotation, info->info.depth,
info->info.destination_alpha, swap)))
if (!(re = _output_setup(info, epd->output.w, epd->output.h, swap)))
return 0;
}
else
@ -149,10 +166,7 @@ eng_setup(Evas *evas, void *einfo)
if (re->ob) evas_outbuf_free(re->ob);
/* try to create a new outbuf */
if (!(re->ob =
evas_outbuf_setup(epd->output.w, epd->output.h,
info->info.rotation, info->info.depth,
info->info.destination_alpha)))
if (!(re->ob = evas_outbuf_setup(info, epd->output.w, epd->output.h)))
return 0;
}
@ -190,6 +204,9 @@ eng_output_free(void *data)
evas_common_tilebuf_free_render_rects(re->prev_rects[1]);
if (re->prev_rects[2])
evas_common_tilebuf_free_render_rects(re->prev_rects[2]);
evas_drm_shutdown(re->info);
free(re);
}
@ -197,6 +214,266 @@ eng_output_free(void *data)
evas_common_image_shutdown();
}
static void
eng_output_resize(void *data, int w, int h)
{
Render_Engine *re;
/* try to get the render_engine */
if (!(re = (Render_Engine *)data)) return;
evas_outbuf_reconfigure(re->info, re->ob, w, h);
if (re->tb) evas_common_tilebuf_free(re->tb);
if ((re->tb = evas_common_tilebuf_new(w, h)))
evas_common_tilebuf_set_tile_size(re->tb, TILESIZE, TILESIZE);
}
static void
eng_output_tile_size_set(void *data, int w, int h)
{
Render_Engine *re;
/* try to get the render_engine */
if (!(re = (Render_Engine *)data)) return;
if (re->tb) evas_common_tilebuf_set_tile_size(re->tb, w, h);
}
static void
eng_output_redraws_rect_add(void *data, int x, int y, int w, int h)
{
Render_Engine *re;
/* try to get the render_engine */
if (!(re = (Render_Engine *)data)) return;
if (re->tb) evas_common_tilebuf_add_redraw(re->tb, x, y, w, h);
}
static void
eng_output_redraws_rect_del(void *data, int x, int y, int w, int h)
{
Render_Engine *re;
/* try to get the render_engine */
if (!(re = (Render_Engine *)data)) return;
if (re->tb) evas_common_tilebuf_del_redraw(re->tb, x, y, w, h);
}
static void
eng_output_redraws_clear(void *data)
{
Render_Engine *re;
/* try to get the render_engine */
if (!(re = (Render_Engine *)data)) return;
if (re->tb) evas_common_tilebuf_clear(re->tb);
}
static Tilebuf_Rect *
_merge_rects(Tilebuf *tb, Tilebuf_Rect *r1, Tilebuf_Rect *r2, Tilebuf_Rect *r3)
{
Tilebuf_Rect *r, *rects;
if (r1)
{
EINA_INLIST_FOREACH(EINA_INLIST_GET(r1), r)
evas_common_tilebuf_add_redraw(tb, r->x, r->y, r->w, r->h);
}
if (r2)
{
EINA_INLIST_FOREACH(EINA_INLIST_GET(r2), r)
evas_common_tilebuf_add_redraw(tb, r->x, r->y, r->w, r->h);
}
if (r3)
{
EINA_INLIST_FOREACH(EINA_INLIST_GET(r3), r)
evas_common_tilebuf_add_redraw(tb, r->x, r->y, r->w, r->h);
}
rects = evas_common_tilebuf_get_render_rects(tb);
/*
// bounding box -> make a bounding box single region update of all regions.
// yes we could try and be smart and figure out size of regions, how far
// apart etc. etc. to try and figure out an optimal "set". this is a tradeoff
// between multiple update regions to render and total pixels to render.
if (rects)
{
px1 = rects->x; py1 = rects->y;
px2 = rects->x + rects->w; py2 = rects->y + rects->h;
EINA_INLIST_FOREACH(EINA_INLIST_GET(rects), r)
{
if (r->x < x1) px1 = r->x;
if (r->y < y1) py1 = r->y;
if ((r->x + r->w) > x2) px2 = r->x + r->w;
if ((r->y + r->h) > y2) py2 = r->y + r->h;
}
evas_common_tilebuf_free_render_rects(rects);
rects = calloc(1, sizeof(Tilebuf_Rect));
if (rects)
{
rects->x = px1;
rects->y = py1;
rects->w = px2 - px1;
rects->h = py2 - py1;
}
}
*/
evas_common_tilebuf_clear(tb);
return rects;
}
static void *
eng_output_redraws_next_update_get(void *data, int *x, int *y, int *w, int *h, int *cx, int *cy, int *cw, int *ch)
{
Render_Engine *re;
RGBA_Image *img;
Tilebuf_Rect *rect;
/* try to get the render_engine */
if (!(re = (Render_Engine *)data)) return NULL;
#define CLEAR_PREV_RECTS(x) \
do { \
if (re->prev_rects[x]) \
evas_common_tilebuf_free_render_rects(re->prev_rects[x]); \
re->prev_rects[x] = NULL; \
} while (0)
if (re->end)
{
re->end = EINA_FALSE;
return NULL;
}
if (!re->rects)
{
re->mode = evas_outbuf_buffer_state_get(re->ob);
if ((re->rects = evas_common_tilebuf_get_render_rects(re->tb)))
{
if ((re->lost_back) || (re->mode == MODE_FULL))
{
re->lost_back = EINA_FALSE;
evas_common_tilebuf_add_redraw(re->tb,
0, 0, re->ob->w, re->ob->h);
evas_common_tilebuf_free_render_rects(re->rects);
re->rects = evas_common_tilebuf_get_render_rects(re->tb);
}
evas_common_tilebuf_clear(re->tb);
CLEAR_PREV_RECTS(2);
re->prev_rects[2] = re->prev_rects[1];
re->prev_rects[1] = re->prev_rects[0];
re->prev_rects[0] = re->rects;
re->rects = NULL;
switch (re->mode)
{
case MODE_FULL:
case MODE_COPY:
re->rects =
_merge_rects(re->tb, re->prev_rects[0], NULL, NULL);
break;
case MODE_DOUBLE:
re->rects =
_merge_rects(re->tb, re->prev_rects[0], re->prev_rects[1], NULL);
break;
case MODE_TRIPLE:
re->rects =
_merge_rects(re->tb, re->prev_rects[0], re->prev_rects[1], re->prev_rects[2]);
break;
default:
break;
}
}
/* NB: Not sure this is entirely needed here as it's already done
* inside _merge_rects */
/* evas_common_tilebuf_clear(re->tb); */
re->cur_rect = EINA_INLIST_GET(re->rects);
}
if (!re->cur_rect) return NULL;
rect = (Tilebuf_Rect *)re->cur_rect;
if (re->rects)
{
switch (re->mode)
{
case MODE_COPY:
case MODE_DOUBLE:
case MODE_TRIPLE:
rect = (Tilebuf_Rect *)re->cur_rect;
*x = rect->x;
*y = rect->y;
*w = rect->w;
*h = rect->h;
*cx = rect->x;
*cy = rect->y;
*cw = rect->w;
*ch = rect->h;
re->cur_rect = re->cur_rect->next;
break;
case MODE_FULL:
re->cur_rect = NULL;
if (x) *x = 0;
if (y) *y = 0;
if (w) *w = re->ob->w;
if (h) *h = re->ob->h;
if (cx) *cx = 0;
if (cy) *cy = 0;
if (cw) *cw = re->ob->w;
if (ch) *ch = re->ob->h;
break;
default:
break;
}
img =
evas_outbuf_update_region_new(re->ob, *x, *y, *w, *h,
cx, cy, cw, ch);
if (!re->cur_rect)
{
evas_common_tilebuf_free_render_rects(re->rects);
re->rects = NULL;
re->end = EINA_TRUE;
}
return img;
}
return NULL;
}
static void
eng_output_redraws_next_update_push(void *data, void *img, int x, int y, int w, int h, Evas_Render_Mode render_mode)
{
Render_Engine *re;
if (render_mode == EVAS_RENDER_MODE_ASYNC_INIT) return;
if (!(re = (Render_Engine *)data)) return;
#if defined(BUILD_PIPE_RENDER)
evas_common_pipe_map_begin(img);
#endif
evas_outbuf_update_region_push(re->ob, img, x, y, w, h);
/* NB: No reason to free region here. That is done on flush anyway */
/* re->outbuf_update_region_free(re->ob, img); */
evas_common_cpu_end_opt();
}
static void
eng_output_flush(void *data, Evas_Render_Mode render_mode)
{
Render_Engine *re;
if (render_mode == EVAS_RENDER_MODE_ASYNC_INIT) return;
if (!(re = (Render_Engine *)data)) return;
evas_outbuf_flush(re->ob);
if (re->rects) evas_common_tilebuf_free_render_rects(re->rects);
re->rects = NULL;
}
/* module api functions */
static int
module_open(Evas_Module *em)
@ -226,6 +503,15 @@ module_open(Evas_Module *em)
EVAS_API_OVERRIDE(info_free, &func, eng_);
EVAS_API_OVERRIDE(setup, &func, eng_);
EVAS_API_OVERRIDE(output_free, &func, eng_);
EVAS_API_OVERRIDE(output_resize, &func, eng_);
EVAS_API_OVERRIDE(output_tile_size_set, &func, eng_);
EVAS_API_OVERRIDE(output_redraws_rect_add, &func, eng_);
EVAS_API_OVERRIDE(output_redraws_rect_del, &func, eng_);
EVAS_API_OVERRIDE(output_redraws_clear, &func, eng_);
EVAS_API_OVERRIDE(output_redraws_next_update_get, &func, eng_);
EVAS_API_OVERRIDE(output_redraws_next_update_push, &func, eng_);
EVAS_API_OVERRIDE(output_flush, &func, eng_);
/* EVAS_API_OVERRIDE(output_idle_flush, &func, eng_); */
/* advertise our engine functions */
em->functions = (void *)(&func);

View File

@ -1,6 +1,22 @@
#ifndef EVAS_ENGINE_H
# define EVAS_ENGINE_H
#include "evas_common_private.h"
#include "evas_macros.h"
#include "evas_private.h"
#include "Evas.h"
#include "Evas_Engine_Drm.h"
#include <xf86drm.h>
#include <xf86drmMode.h>
#include <drm_fourcc.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
extern int _evas_engine_drm_log_dom;
# ifdef ERR
@ -28,6 +44,10 @@ extern int _evas_engine_drm_log_dom;
# endif
# define CRI(...) EINA_LOG_DOM_CRIT(_evas_engine_drm_log_dom, __VA_ARGS__)
/* define a maximum number of 'buffers' (double-buff, triple-buff, etc) */
# define NUM_BUFFERS 2
typedef struct _Buffer Buffer;
typedef struct _Outbuf Outbuf;
enum
@ -38,15 +58,65 @@ enum
MODE_TRIPLE
};
enum
{
OUTBUF_DEPTH_NONE,
OUTBUF_DEPTH_ARGB_32BPP_8888_8888,
OUTBUF_DEPTH_RGB_32BPP_8888_8888,
OUTBUF_DEPTH_LAST
};
struct _Buffer
{
int w, h;
int stride, size;
int handle;
unsigned int fb;
void *data;
Eina_Bool valid : 1;
};
struct _Outbuf
{
int w, h;
unsigned int rotation, depth;
Eina_Bool destination_alpha : 1;
struct
{
RGBA_Image *onebuf;
Eina_Array onebuf_regions;
Buffer buffer[NUM_BUFFERS], *sent;
int curr, num;
int fd;
unsigned int conn, crtc;
drmModeModeInfo mode;
drmEventContext ctx;
Eina_Bool pending_flip : 1;
Eina_List *pending_writes;
Eina_List *prev_pending_writes;
} priv;
};
Outbuf *evas_outbuf_setup(int w, int h, unsigned int rotation, unsigned int depth, Eina_Bool alpha);
Outbuf *evas_outbuf_setup(Evas_Engine_Info_Drm *info, int w, int h);
void evas_outbuf_free(Outbuf *ob);
void evas_outbuf_reconfigure(Outbuf *ob, int w, int h, unsigned int rotation, unsigned int depth, Eina_Bool alpha);
void evas_outbuf_reconfigure(Evas_Engine_Info_Drm *info, Outbuf *ob, int w, int h);
int evas_outbuf_buffer_state_get(Outbuf *ob);
RGBA_Image *evas_outbuf_update_region_new(Outbuf *ob, int x, int y, int w, int h, int *cx, int *cy, int *cw, int *ch);
void evas_outbuf_update_region_push(Outbuf *ob, RGBA_Image *update, int x, int y, int w, int h);
void evas_outbuf_flush(Outbuf *ob);
Eina_Bool evas_drm_init(Evas_Engine_Info_Drm *info, int card);
Eina_Bool evas_drm_shutdown(Evas_Engine_Info_Drm *info);
Eina_Bool evas_drm_outbuf_setup(Outbuf *ob);
void evas_drm_outbuf_framebuffer_set(Outbuf *ob, Buffer *buffer);
Eina_Bool evas_drm_framebuffer_create(int fd, Buffer *buffer, int depth);
void evas_drm_framebuffer_destroy(int fd, Buffer *buffer);
Eina_Bool evas_drm_framebuffer_send(Outbuf *ob, Buffer *buffer, Eina_Rectangle *rects, unsigned int count);
#endif

View File

@ -1,22 +1,135 @@
#include "evas_common_private.h"
#include "evas_private.h"
#include "evas_engine.h"
#ifdef EVAS_CSERVE2
# include "evas_cs2_private.h"
#endif
/* FIXME: We NEED to get the color map from the VT and use that for the mask */
#define RED_MASK 0x00ff0000
#define GREEN_MASK 0x0000ff00
#define BLUE_MASK 0x000000ff
static Eina_Bool
_evas_outbuf_buffer_new(Outbuf *ob, Buffer *buffer)
{
/* check for valid outbuf */
if (!ob) return EINA_FALSE;
buffer->w = ob->w;
buffer->h = ob->h;
/* sadly we cannot create buffers to be JUST the size of the canvas.
* drmModeSetCrtc will barf with ENOSPC if we try that :( so we have to
* allocate framebuffer objects to be the whole size of the display mode */
/* if (ob->priv.mode.hdisplay > buffer->w) */
/* buffer->w = ob->priv.mode.hdisplay; */
/* if (ob->priv.mode.vdisplay > buffer->h) */
/* buffer->h = ob->priv.mode.vdisplay; */
/* create a drm fb for this buffer */
if (!evas_drm_framebuffer_create(ob->priv.fd, buffer, ob->depth))
{
CRI("Could not create drm framebuffer");
return EINA_FALSE;
}
/* evas_drm_outbuf_framebuffer_set(ob, buffer); */
return EINA_TRUE;
}
static void *
_evas_outbuf_buffer_map(Outbuf *ob, int *w, int *h)
{
if (w) *w = ob->w;
if (h) *h = ob->h;
return ob->priv.buffer[ob->priv.curr].data;
}
static void
_evas_outbuf_buffer_put(Outbuf *ob, Buffer *buffer, Eina_Rectangle *rects, unsigned int count)
{
/* validate input params */
if ((!ob) || (!buffer)) return;
if (!buffer->data)
{
CRI("Current Buffer Has No Data !!");
/* TODO: call function to mmap buffer data */
/* if (!_evas_outbuf_buffer_new(ob, buffer)); */
return;
}
if (ob->priv.sent != buffer)
{
if (!buffer->valid) evas_drm_outbuf_framebuffer_set(ob, buffer);
if (!evas_drm_framebuffer_send(ob, buffer, rects, count))
ERR("Could not send buffer");
}
}
static void
_evas_outbuf_buffer_swap(Outbuf *ob, Eina_Rectangle *rects, unsigned int count)
{
/* check for valid output buffer */
if (!ob) return;
_evas_outbuf_buffer_put(ob, &(ob->priv.buffer[ob->priv.curr]),
rects, count);
}
Outbuf *
evas_outbuf_setup(int w, int h, unsigned int rotation, unsigned int depth, Eina_Bool alpha)
evas_outbuf_setup(Evas_Engine_Info_Drm *info, int w, int h)
{
Outbuf *ob;
char *num;
int i = 0;
/* try to allocate space for out outbuf structure */
/* try to allocate space for our outbuf structure */
if (!(ob = calloc(1, sizeof(Outbuf))))
return NULL;
/* set some default outbuf properties */
ob->w = w;
ob->h = h;
ob->rotation = rotation;
ob->depth = depth;
ob->destination_alpha = alpha;
ob->rotation = info->info.rotation;
ob->depth = info->info.depth;
ob->destination_alpha = info->info.destination_alpha;
/* set drm file descriptor */
ob->priv.fd = info->info.fd;
/* try to setup the drm card for this outbuf */
if (!evas_drm_outbuf_setup(ob))
{
free(ob);
return NULL;
}
ob->priv.num = NUM_BUFFERS;
/* check for buffer override */
if ((num = getenv("EVAS_DRM_BUFFERS")))
{
ob->priv.num = atoi(num);
/* cap maximum # of buffers */
if (ob->priv.num <= 0) ob->priv.num = 1;
else if (ob->priv.num > 3) ob->priv.num = 3;
}
/* with the connector and crtc set, we can now create buffers !! :) */
for (; i < ob->priv.num; i++)
{
if (!_evas_outbuf_buffer_new(ob, &(ob->priv.buffer[i])))
{
CRI("Failed to create buffer");
break;
}
}
/* set array step size for regions */
eina_array_step_set(&ob->priv.onebuf_regions, sizeof(Eina_Array), 8);
/* return the allocated outbuf structure */
return ob;
@ -25,21 +138,431 @@ evas_outbuf_setup(int w, int h, unsigned int rotation, unsigned int depth, Eina_
void
evas_outbuf_free(Outbuf *ob)
{
int i = 0;
/* destroy the buffers */
for (; i < ob->priv.num; i++)
evas_drm_framebuffer_destroy(ob->priv.fd, &(ob->priv.buffer[i]));
eina_array_flush(&ob->priv.onebuf_regions);
/* free the allocated outbuf structure */
free(ob);
}
void
evas_outbuf_reconfigure(Outbuf *ob, int w, int h, unsigned int rotation, unsigned int depth, Eina_Bool alpha)
evas_outbuf_reconfigure(Evas_Engine_Info_Drm *info, Outbuf *ob, int w, int h)
{
int i = 0;
/* check for changes */
if ((ob->w == w) && (ob->h == h) && (ob->destination_alpha == alpha) &&
(ob->rotation == rotation) && (ob->depth == depth)) return;
if ((ob->w == w) && (ob->h == h) &&
(ob->destination_alpha == info->info.destination_alpha) &&
(ob->rotation == info->info.rotation) &&
(ob->depth == info->info.depth))
return;
/* set new outbuf properties */
ob->w = w;
ob->h = h;
ob->rotation = rotation;
ob->depth = depth;
ob->destination_alpha = alpha;
ob->rotation =info->info. rotation;
ob->depth = info->info.depth;
ob->destination_alpha = info->info.destination_alpha;
/* handle rotation */
if ((ob->rotation == 0) || (ob->rotation == 180))
{
ob->w = w;
ob->h = h;
}
else
{
ob->w = h;
ob->h = w;
}
/* destroy the old buffers */
for (; i < ob->priv.num; i++)
evas_drm_framebuffer_destroy(ob->priv.fd, &(ob->priv.buffer[i]));
for (i = 0; i < ob->priv.num; i++)
{
if (!_evas_outbuf_buffer_new(ob, &(ob->priv.buffer[i])))
{
CRI("Failed to create buffer");
break;
}
}
}
int
evas_outbuf_buffer_state_get(Outbuf *ob)
{
int i = 0, n = 0, count = 0;
/* check for valid output buffer */
if (!ob) return MODE_FULL;
for (; i < ob->priv.num; i++)
{
n = (ob->priv.num + ob->priv.curr - i) % ob->priv.num;
if (ob->priv.buffer[n].valid) count++;
else break;
}
if (count == ob->priv.num)
{
if (count == 1) return MODE_COPY;
else if (count == 2) return MODE_DOUBLE;
else if (count == 3) return MODE_TRIPLE;
}
return MODE_FULL;
}
RGBA_Image *
evas_outbuf_update_region_new(Outbuf *ob, int x, int y, int w, int h, int *cx, int *cy, int *cw, int *ch)
{
RGBA_Image *img;
Eina_Rectangle *rect;
RECTS_CLIP_TO_RECT(x, y, w, h, 0, 0, ob->w, ob->h);
if ((w <= 0) || (h <= 0)) return NULL;
if (ob->rotation == 0)
{
if (!(img = ob->priv.onebuf))
{
int bpl = 0;
int bw = 0, bh = 0;
void *data;
data = _evas_outbuf_buffer_map(ob, &bw, &bh);
bpl = (bw * sizeof(int));
#ifdef EVAS_CSERVE2
if (evas_cserve2_use_get())
img = (RGBA_Image *)evas_cache2_image_data(evas_common_image_cache2_get(),
bpl / sizeof(int), bh,
data,
ob->destination_alpha,
EVAS_COLORSPACE_ARGB8888);
else
#endif
img = (RGBA_Image *)evas_cache_image_data(evas_common_image_cache_get(),
bpl / sizeof(int), bh,
data,
ob->destination_alpha,
EVAS_COLORSPACE_ARGB8888);
ob->priv.onebuf = img;
if (!img) return NULL;
}
if (!(rect = eina_rectangle_new(x, y, w, h)))
return NULL;
if (!eina_array_push(&ob->priv.onebuf_regions, rect))
{
#ifdef EVAS_CSERVE2
if (evas_cserve2_use_get())
evas_cache2_image_close(&img->cache_entry);
else
#endif
evas_cache_image_drop(&img->cache_entry);
eina_rectangle_free(rect);
return NULL;
}
/* clip the region to the onebuf region */
if (cx) *cx = x;
if (cy) *cy = y;
if (cw) *cw = w;
if (ch) *ch = h;
return img;
}
else
{
if (!(rect = eina_rectangle_new(x, y, w, h)))
return NULL;
#ifdef EVAS_CSERVE2
if (evas_cserve2_use_get())
img = (RGBA_Image *)evas_cache2_image_empty(evas_common_image_cache2_get());
else
#endif
img = (RGBA_Image *)evas_cache_image_empty(evas_common_image_cache_get());
if (!img)
{
eina_rectangle_free(rect);
return NULL;
}
img->cache_entry.flags.alpha |= ob->destination_alpha;
#ifdef EVAS_CSERVE2
if (evas_cserve2_use_get())
evas_cache2_image_surface_alloc(&img->cache_entry, w, h);
else
#endif
evas_cache_image_surface_alloc(&img->cache_entry, w, h);
img->extended_info = rect;
ob->priv.pending_writes =
eina_list_append(ob->priv.pending_writes, img);
if (cx) *cx = 0;
if (cy) *cy = 0;
if (cw) *cw = w;
if (ch) *ch = h;
return img;
}
return NULL;
}
void
evas_outbuf_update_region_push(Outbuf *ob, RGBA_Image *update, int x, int y, int w, int h)
{
Gfx_Func_Convert func = NULL;
Eina_Rectangle rect = {0, 0, 0, 0}, pr;
DATA32 *src;
DATA8 *dst;
int depth = 32, bpp = 0, bpl = 0, wid = 0;
int ww = 0, hh = 0;
int rx = 0, ry = 0;
/* check for valid output buffer */
if (!ob) return;
/* check for pending writes */
if (!ob->priv.pending_writes) return;
if ((ob->rotation == 0) || (ob->rotation == 180))
{
func =
evas_common_convert_func_get(0, w, h, depth,
RED_MASK, GREEN_MASK, BLUE_MASK,
PAL_MODE_NONE, ob->rotation);
}
else if ((ob->rotation == 90) || (ob->rotation == 270))
{
func =
evas_common_convert_func_get(0, h, w, depth,
RED_MASK, GREEN_MASK, BLUE_MASK,
PAL_MODE_NONE, ob->rotation);
}
/* make sure we have a valid convert function */
if (!func) return;
/* based on rotation, set rectangle position */
if (ob->rotation == 0)
{
rect.x = x;
rect.y = y;
}
else if (ob->rotation == 90)
{
rect.x = y;
rect.y = (ob->w - x - w);
}
else if (ob->rotation == 180)
{
rect.x = (ob->w - x - w);
rect.y = (ob->h - y - h);
}
else if (ob->rotation == 270)
{
rect.x = (ob->h - y - h);
rect.y = x;
}
/* based on rotation, set rectangle size */
if ((ob->rotation == 0) || (ob->rotation == 180))
{
rect.w = w;
rect.h = h;
}
else if ((ob->rotation == 90) || (ob->rotation == 270))
{
rect.w = h;
rect.h = w;
}
/* check for valid update image data */
if (!(src = update->image.data)) return;
bpp = depth / 8;
if (bpp <= 0) return;
/* check for valid desination data */
if (!(dst = _evas_outbuf_buffer_map(ob, &ww, &hh))) return;
bpl = (ww * sizeof(int));
if (ob->rotation == 0)
{
RECTS_CLIP_TO_RECT(rect.x, rect.y, rect.w, rect.h, 0, 0, ww, hh);
dst += (bpl * rect.y) + (rect.x + bpp);
w -= rx;
}
else if (ob->rotation == 180)
{
pr = rect;
RECTS_CLIP_TO_RECT(rect.x, rect.y, rect.w, rect.h, 0, 0, ww, hh);
rx = pr.w - rect.w;
ry = pr.h - rect.h;
src += (update->cache_entry.w * ry) + rx;
w -= rx;
}
else if (ob->rotation == 90)
{
pr = rect;
RECTS_CLIP_TO_RECT(rect.x, rect.y, rect.w, rect.h, 0, 0, ww, hh);
rx = pr.w - rect.w; ry = pr.h - rect.h;
src += ry;
w -= ry;
}
else if (ob->rotation == 270)
{
pr = rect;
RECTS_CLIP_TO_RECT(rect.x, rect.y, rect.w, rect.h, 0, 0, ww, hh);
rx = pr.w - rect.w; ry = pr.h - rect.h;
src += (update->cache_entry.w * rx);
w -= ry;
}
if ((rect.w <= 0) || (rect.h <= 0)) return;
wid = bpl / bpp;
dst += (bpl * rect.y) + (rect.x * bpp);
func(src, dst, (update->cache_entry.w - w), (wid - rect.w),
rect.w, rect.h, x + rx, y + ry, NULL);
}
void
evas_outbuf_flush(Outbuf *ob)
{
Eina_Rectangle *rects;
RGBA_Image *img;
unsigned int n = 0, i = 0;
/* check for valid output buffer */
if (!ob) return;
/* check for pending writes */
if (!ob->priv.pending_writes)
{
Eina_Rectangle *rect;
Eina_Array_Iterator it;
/* get number of buffer regions */
n = eina_array_count_get(&ob->priv.onebuf_regions);
if (n == 0) return;
/* allocate rectangles */
if (!(rects = alloca(n * sizeof(Eina_Rectangle)))) return;
/* loop the buffer regions and assign to rects */
EINA_ARRAY_ITER_NEXT(&ob->priv.onebuf_regions, i, rect, it)
rects[i] = *rect;
/* TODO: unmap the buffer ?? */
/* evas_swapper_buffer_unmap(ob->priv.swapper); */
/* force a buffer swap */
_evas_outbuf_buffer_swap(ob, rects, n);
/* clean array */
eina_array_clean(&ob->priv.onebuf_regions);
img = ob->priv.onebuf;
ob->priv.onebuf = NULL;
if (img)
{
#ifdef EVAS_CSERVE2
if (evas_cserve2_use_get())
evas_cache2_image_close(&img->cache_entry);
else
#endif
evas_cache_image_drop(&img->cache_entry);
}
}
else
{
/* get number of pending writes */
n = eina_list_count(ob->priv.pending_writes);
if (n == 0) return;
/* allocate rectangles */
if (!(rects = alloca(n * sizeof(Eina_Rectangle)))) return;
/* loop the pending writes */
EINA_LIST_FREE(ob->priv.pending_writes, img)
{
Eina_Rectangle *rect;
int x = 0, y = 0, w = 0, h = 0;
if (!(rect = img->extended_info)) continue;
x = rect->x; y = rect->y; w = rect->w; h = rect->h;
/* based on rotation, set rectangle position */
if (ob->rotation == 0)
{
rects[i].x = x;
rects[i].y = y;
}
else if (ob->rotation == 90)
{
rects[i].x = y;
rects[i].y = (ob->w - x - w);
}
else if (ob->rotation == 180)
{
rects[i].x = (ob->w - x - w);
rects[i].y = (ob->h - y - h);
}
else if (ob->rotation == 270)
{
rects[i].x = (ob->h - y - h);
rects[i].y = x;
}
/* based on rotation, set rectangle size */
if ((ob->rotation == 0) || (ob->rotation == 180))
{
rects[i].w = w;
rects[i].h = h;
}
else if ((ob->rotation == 90) || (ob->rotation == 270))
{
rects[i].w = h;
rects[i].h = w;
}
eina_rectangle_free(rect);
#ifdef EVAS_CSERVE2
if (evas_cserve2_use_get())
evas_cache2_image_close(&img->cache_entry);
else
#endif
evas_cache_image_drop(&img->cache_entry);
i++;
}
/* TODO: unmap the buffer ?? */
/* evas_swapper_buffer_unmap(ob->priv.swapper); */
/* force a buffer swap */
_evas_outbuf_buffer_swap(ob, rects, n);
}
}