mirror of
https://gitlab.freedesktop.org/xorg/xserver.git
synced 2026-06-04 17:58:20 +02:00
Merge EXA updates up to commit cf5b29d75d.
This is EXA version 2.1, including the damagetrack work.
This commit is contained in:
parent
9fbb845e8b
commit
ed1778cfb2
10 changed files with 501 additions and 212 deletions
141
exa/exa.c
141
exa/exa.c
|
|
@ -32,6 +32,10 @@
|
|||
#include <dix-config.h>
|
||||
#endif
|
||||
|
||||
#ifdef MITSHM
|
||||
#include "shmint.h"
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "exa_priv.h"
|
||||
|
|
@ -117,18 +121,78 @@ exaGetDrawablePixmap(DrawablePtr pDrawable)
|
|||
return (PixmapPtr) pDrawable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the offsets to add to coordinates to make them address the same bits in
|
||||
* the backing drawable. These coordinates are nonzero only for redirected
|
||||
* windows.
|
||||
*/
|
||||
static void
|
||||
exaGetDrawableDeltas (DrawablePtr pDrawable, PixmapPtr pPixmap,
|
||||
int *xp, int *yp)
|
||||
{
|
||||
#ifdef COMPOSITE
|
||||
if (pDrawable->type == DRAWABLE_WINDOW) {
|
||||
*xp = -pPixmap->screen_x;
|
||||
*yp = -pPixmap->screen_y;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
*xp = 0;
|
||||
*yp = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* exaPixmapDirty() marks a pixmap as dirty, allowing for
|
||||
* optimizations in pixmap migration when no changes have occurred.
|
||||
*/
|
||||
void
|
||||
exaPixmapDirty (PixmapPtr pPix, int x1, int y1, int x2, int y2)
|
||||
{
|
||||
ExaPixmapPriv(pPix);
|
||||
BoxRec box;
|
||||
RegionPtr pDamageReg;
|
||||
RegionRec region;
|
||||
|
||||
if (!pExaPixmap)
|
||||
return;
|
||||
|
||||
box.x1 = max(x1, 0);
|
||||
box.y1 = max(y1, 0);
|
||||
box.x2 = min(x2, pPix->drawable.width);
|
||||
box.y2 = min(y2, pPix->drawable.height);
|
||||
|
||||
if (box.x1 >= box.x2 || box.y1 >= box.y2)
|
||||
return;
|
||||
|
||||
pDamageReg = DamageRegion(pExaPixmap->pDamage);
|
||||
|
||||
REGION_INIT(pScreen, ®ion, &box, 1);
|
||||
REGION_UNION(pScreen, pDamageReg, pDamageReg, ®ion);
|
||||
REGION_UNINIT(pScreen, ®ion);
|
||||
}
|
||||
|
||||
/**
|
||||
* exaDrawableDirty() marks a pixmap backing a drawable as dirty, allowing for
|
||||
* optimizations in pixmap migration when no changes have occurred.
|
||||
*/
|
||||
void
|
||||
exaDrawableDirty (DrawablePtr pDrawable)
|
||||
exaDrawableDirty (DrawablePtr pDrawable, int x1, int y1, int x2, int y2)
|
||||
{
|
||||
ExaPixmapPrivPtr pExaPixmap;
|
||||
PixmapPtr pPix = exaGetDrawablePixmap(pDrawable);
|
||||
int xoff, yoff;
|
||||
|
||||
pExaPixmap = ExaGetPixmapPriv(exaGetDrawablePixmap (pDrawable));
|
||||
if (pExaPixmap != NULL)
|
||||
pExaPixmap->dirty = TRUE;
|
||||
x1 = max(x1, pDrawable->x);
|
||||
y1 = max(y1, pDrawable->y);
|
||||
x2 = min(x2, pDrawable->x + pDrawable->width);
|
||||
y2 = min(y2, pDrawable->y + pDrawable->height);
|
||||
|
||||
if (x1 >= x2 || y1 >= y2)
|
||||
return;
|
||||
|
||||
exaGetDrawableDeltas(pDrawable, pPix, &xoff, &yoff);
|
||||
|
||||
exaPixmapDirty(pPix, x1 + xoff, y1 + yoff, x2 + xoff, y2 + yoff);
|
||||
}
|
||||
|
||||
static Bool
|
||||
|
|
@ -149,6 +213,7 @@ exaDestroyPixmap (PixmapPtr pPixmap)
|
|||
pPixmap->devPrivate.ptr = pExaPixmap->sys_ptr;
|
||||
pPixmap->devKind = pExaPixmap->sys_pitch;
|
||||
}
|
||||
REGION_UNINIT(pPixmap->drawable.pScreen, &pExaPixmap->validReg);
|
||||
}
|
||||
return fbDestroyPixmap (pPixmap);
|
||||
}
|
||||
|
|
@ -216,7 +281,20 @@ exaCreatePixmap(ScreenPtr pScreen, int w, int h, int depth)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
pExaPixmap->dirty = FALSE;
|
||||
/* Set up damage tracking */
|
||||
pExaPixmap->pDamage = DamageCreate (NULL, NULL, DamageReportNone, TRUE,
|
||||
pScreen, pPixmap);
|
||||
|
||||
if (pExaPixmap->pDamage == NULL) {
|
||||
fbDestroyPixmap (pPixmap);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DamageRegister (&pPixmap->drawable, pExaPixmap->pDamage);
|
||||
DamageSetReportAfterOp (pExaPixmap->pDamage, TRUE);
|
||||
|
||||
/* None of the pixmap bits are valid initially */
|
||||
REGION_NULL(pScreen, &pExaPixmap->validReg);
|
||||
|
||||
return pPixmap;
|
||||
}
|
||||
|
|
@ -261,32 +339,14 @@ exaDrawableIsOffscreen (DrawablePtr pDrawable)
|
|||
/**
|
||||
* Returns the pixmap which backs a drawable, and the offsets to add to
|
||||
* coordinates to make them address the same bits in the backing drawable.
|
||||
* These coordinates are nonzero only for redirected windows.
|
||||
*/
|
||||
PixmapPtr
|
||||
exaGetOffscreenPixmap (DrawablePtr pDrawable, int *xp, int *yp)
|
||||
{
|
||||
PixmapPtr pPixmap;
|
||||
int x, y;
|
||||
PixmapPtr pPixmap = exaGetDrawablePixmap (pDrawable);
|
||||
|
||||
exaGetDrawableDeltas (pDrawable, pPixmap, xp, yp);
|
||||
|
||||
if (pDrawable->type == DRAWABLE_WINDOW) {
|
||||
pPixmap = (*pDrawable->pScreen->GetWindowPixmap) ((WindowPtr) pDrawable);
|
||||
#ifdef COMPOSITE
|
||||
x = -pPixmap->screen_x;
|
||||
y = -pPixmap->screen_y;
|
||||
#else
|
||||
x = 0;
|
||||
y = 0;
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
pPixmap = (PixmapPtr) pDrawable;
|
||||
x = 0;
|
||||
y = 0;
|
||||
}
|
||||
*xp = x;
|
||||
*yp = y;
|
||||
if (exaPixmapIsOffscreen (pPixmap))
|
||||
return pPixmap;
|
||||
else
|
||||
|
|
@ -334,8 +394,7 @@ exaPrepareAccess(DrawablePtr pDrawable, int index)
|
|||
/**
|
||||
* exaFinishAccess() is EXA's wrapper for the driver's FinishAccess() handler.
|
||||
*
|
||||
* It deals with marking drawables as dirty, and calling the driver's
|
||||
* FinishAccess() only if necessary.
|
||||
* It deals with calling the driver's FinishAccess() only if necessary.
|
||||
*/
|
||||
void
|
||||
exaFinishAccess(DrawablePtr pDrawable, int index)
|
||||
|
|
@ -345,9 +404,6 @@ exaFinishAccess(DrawablePtr pDrawable, int index)
|
|||
PixmapPtr pPixmap;
|
||||
ExaPixmapPrivPtr pExaPixmap;
|
||||
|
||||
if (index == EXA_PREPARE_DEST)
|
||||
exaDrawableDirty (pDrawable);
|
||||
|
||||
pPixmap = exaGetDrawablePixmap (pDrawable);
|
||||
|
||||
pExaPixmap = ExaGetPixmapPriv(pPixmap);
|
||||
|
|
@ -373,7 +429,7 @@ exaFinishAccess(DrawablePtr pDrawable, int index)
|
|||
* accelerated or may sync the card and fall back to fb.
|
||||
*/
|
||||
static void
|
||||
exaValidateGC (GCPtr pGC, Mask changes, DrawablePtr pDrawable)
|
||||
exaValidateGC (GCPtr pGC, unsigned long changes, DrawablePtr pDrawable)
|
||||
{
|
||||
/* fbValidateGC will do direct access to pixmaps if the tiling has changed.
|
||||
* Preempt fbValidateGC by doing its work and masking the change out, so
|
||||
|
|
@ -404,6 +460,7 @@ exaValidateGC (GCPtr pGC, Mask changes, DrawablePtr pDrawable)
|
|||
exaPrepareAccess(&pOldTile->drawable, EXA_PREPARE_SRC);
|
||||
pNewTile = fb24_32ReformatTile (pOldTile,
|
||||
pDrawable->bitsPerPixel);
|
||||
exaPixmapDirty(pNewTile, 0, 0, pNewTile->drawable.width, pNewTile->drawable.height);
|
||||
exaFinishAccess(&pOldTile->drawable, EXA_PREPARE_SRC);
|
||||
}
|
||||
if (pNewTile)
|
||||
|
|
@ -419,9 +476,14 @@ exaValidateGC (GCPtr pGC, Mask changes, DrawablePtr pDrawable)
|
|||
if (!pGC->tileIsPixel && FbEvenTile (pGC->tile.pixmap->drawable.width *
|
||||
pDrawable->bitsPerPixel))
|
||||
{
|
||||
exaPrepareAccess(&pGC->tile.pixmap->drawable, EXA_PREPARE_SRC);
|
||||
/* XXX This fixes corruption with tiled pixmaps, but may just be a
|
||||
* workaround for broken drivers
|
||||
*/
|
||||
exaMoveOutPixmap(pGC->tile.pixmap);
|
||||
fbPadPixmap (pGC->tile.pixmap);
|
||||
exaFinishAccess(&pGC->tile.pixmap->drawable, EXA_PREPARE_SRC);
|
||||
exaPixmapDirty(pGC->tile.pixmap, 0, 0,
|
||||
pGC->tile.pixmap->drawable.width,
|
||||
pGC->tile.pixmap->drawable.height);
|
||||
}
|
||||
/* Mask out the GCTile change notification, now that we've done FB's
|
||||
* job for it.
|
||||
|
|
@ -560,7 +622,7 @@ exaDriverInit (ScreenPtr pScreen,
|
|||
|
||||
pScreen->devPrivates[exaScreenPrivateIndex].ptr = (pointer) pExaScr;
|
||||
|
||||
pExaScr->migration = ExaMigrationSmart;
|
||||
pExaScr->migration = ExaMigrationAlways;
|
||||
|
||||
exaDDXDriverInit(pScreen);
|
||||
|
||||
|
|
@ -610,6 +672,13 @@ exaDriverInit (ScreenPtr pScreen,
|
|||
miDisableCompositeWrapper(pScreen);
|
||||
#endif
|
||||
|
||||
#ifdef MITSHM
|
||||
/* Re-register with the MI funcs, which don't allow shared pixmaps.
|
||||
* Shared pixmaps are almost always a performance loss for us, but this
|
||||
* still allows for SHM PutImage.
|
||||
*/
|
||||
ShmRegisterFuncs(pScreen, NULL);
|
||||
#endif
|
||||
/*
|
||||
* Hookup offscreen pixmaps
|
||||
*/
|
||||
|
|
|
|||
12
exa/exa.h
12
exa/exa.h
|
|
@ -39,7 +39,7 @@
|
|||
#include "fb.h"
|
||||
|
||||
#define EXA_VERSION_MAJOR 2
|
||||
#define EXA_VERSION_MINOR 0
|
||||
#define EXA_VERSION_MINOR 1
|
||||
#define EXA_VERSION_RELEASE 0
|
||||
|
||||
typedef struct _ExaOffscreenArea ExaOffscreenArea;
|
||||
|
|
@ -73,8 +73,8 @@ struct _ExaOffscreenArea {
|
|||
typedef struct _ExaDriver {
|
||||
/**
|
||||
* exa_major and exa_minor should be set by the driver to the version of
|
||||
* EXA which the driver was compiled for (or configures itself at runtime to
|
||||
* support). This allows EXA to extend the structure for new features
|
||||
* EXA which the driver was compiled for (or configures itself at runtime
|
||||
* to support). This allows EXA to extend the structure for new features
|
||||
* without breaking ABI for drivers compiled against older versions.
|
||||
*/
|
||||
int exa_major, exa_minor;
|
||||
|
|
@ -716,6 +716,12 @@ exaGetPixmapSize(PixmapPtr pPix);
|
|||
void
|
||||
exaEnableDisableFBAccess (int index, Bool enable);
|
||||
|
||||
void
|
||||
exaMoveInPixmap (PixmapPtr pPixmap);
|
||||
|
||||
void
|
||||
exaMoveOutPixmap (PixmapPtr pPixmap);
|
||||
|
||||
/**
|
||||
* Returns TRUE if the given planemask covers all the significant bits in the
|
||||
* pixel values for pDrawable.
|
||||
|
|
|
|||
159
exa/exa_accel.c
159
exa/exa_accel.c
|
|
@ -20,6 +20,11 @@
|
|||
* DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||||
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*
|
||||
* Authors:
|
||||
* Eric Anholt <eric@anholt.net>
|
||||
* Michel Dänzer <michel@tungstengraphics.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#ifdef HAVE_DIX_CONFIG_H
|
||||
|
|
@ -49,12 +54,12 @@ exaFillSpans(DrawablePtr pDrawable, GCPtr pGC, int n,
|
|||
|
||||
pixmaps[0].as_dst = TRUE;
|
||||
pixmaps[0].as_src = FALSE;
|
||||
pixmaps[0].pPix = exaGetDrawablePixmap (pDrawable);
|
||||
pixmaps[0].pPix = pPixmap = exaGetDrawablePixmap (pDrawable);
|
||||
|
||||
if (pExaScr->swappedOut ||
|
||||
pGC->fillStyle != FillSolid ||
|
||||
pDrawable->width > pExaScr->info->maxX ||
|
||||
pDrawable->height > pExaScr->info->maxY)
|
||||
pPixmap->drawable.width > pExaScr->info->maxX ||
|
||||
pPixmap->drawable.height > pExaScr->info->maxY)
|
||||
{
|
||||
exaDoMigration (pixmaps, 1, FALSE);
|
||||
ExaCheckFillSpans (pDrawable, pGC, n, ppt, pwidth, fSorted);
|
||||
|
|
@ -104,6 +109,8 @@ exaFillSpans(DrawablePtr pDrawable, GCPtr pGC, int n,
|
|||
(*pExaScr->info->Solid) (pPixmap,
|
||||
fullX1 + off_x, fullY1 + off_y,
|
||||
fullX2 + off_x, fullY1 + 1 + off_y);
|
||||
exaPixmapDirty (pPixmap, fullX1 + off_x, fullY1 + off_y,
|
||||
fullX2 + off_x, fullY1 + 1 + off_y);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -118,17 +125,19 @@ exaFillSpans(DrawablePtr pDrawable, GCPtr pGC, int n,
|
|||
partX2 = pbox->x2;
|
||||
if (partX2 > fullX2)
|
||||
partX2 = fullX2;
|
||||
if (partX2 > partX1)
|
||||
if (partX2 > partX1) {
|
||||
(*pExaScr->info->Solid) (pPixmap,
|
||||
partX1 + off_x, fullY1 + off_y,
|
||||
partX2 + off_x, fullY1 + 1 + off_y);
|
||||
exaPixmapDirty (pPixmap, partX1 + off_x, fullY1 + off_y,
|
||||
partX2 + off_x, fullY1 + 1 + off_y);
|
||||
}
|
||||
}
|
||||
pbox++;
|
||||
}
|
||||
}
|
||||
}
|
||||
(*pExaScr->info->DoneSolid) (pPixmap);
|
||||
exaDrawableDirty (pDrawable);
|
||||
exaMarkSync(pScreen);
|
||||
}
|
||||
|
||||
|
|
@ -222,8 +231,8 @@ exaPutImage (DrawablePtr pDrawable, GCPtr pGC, int depth, int x, int y,
|
|||
|
||||
exaFinishAccess(pDrawable, EXA_PREPARE_DEST);
|
||||
}
|
||||
exaPixmapDirty(pPix, x1 + xoff, y1 + yoff, x2 + xoff, y2 + yoff);
|
||||
}
|
||||
exaDrawableDirty(pDrawable);
|
||||
|
||||
return;
|
||||
|
||||
|
|
@ -351,11 +360,12 @@ exaCopyNtoNTwoDir (DrawablePtr pSrcDrawable, DrawablePtr pDstDrawable,
|
|||
dst_off_y + pbox->y1 + i,
|
||||
pbox->x2 - pbox->x1, 1);
|
||||
}
|
||||
exaPixmapDirty(pDstPixmap, dst_off_x + pbox->x1, dst_off_y + pbox->y1,
|
||||
dst_off_x + pbox->x2, dst_off_y + pbox->y2);
|
||||
}
|
||||
if (dirsetup != 0)
|
||||
pExaScr->info->DoneCopy(pDstPixmap);
|
||||
exaMarkSync(pDstDrawable->pScreen);
|
||||
exaDrawableDirty(pDstDrawable);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
|
@ -380,19 +390,19 @@ exaCopyNtoN (DrawablePtr pSrcDrawable,
|
|||
|
||||
pixmaps[0].as_dst = TRUE;
|
||||
pixmaps[0].as_src = FALSE;
|
||||
pixmaps[0].pPix = exaGetDrawablePixmap (pDstDrawable);
|
||||
pixmaps[0].pPix = pDstPixmap = exaGetDrawablePixmap (pDstDrawable);
|
||||
pixmaps[1].as_dst = FALSE;
|
||||
pixmaps[1].as_src = TRUE;
|
||||
pixmaps[1].pPix = exaGetDrawablePixmap (pSrcDrawable);
|
||||
pixmaps[1].pPix = pSrcPixmap = exaGetDrawablePixmap (pSrcDrawable);
|
||||
|
||||
/* Respect maxX/maxY in a trivial way: don't set up drawing when we might
|
||||
* violate the limits. The proper solution would be a temporary pixmap
|
||||
* adjusted so that the drawing happened within limits.
|
||||
*/
|
||||
if (pSrcDrawable->width > pExaScr->info->maxX ||
|
||||
pSrcDrawable->height > pExaScr->info->maxY ||
|
||||
pDstDrawable->width > pExaScr->info->maxX ||
|
||||
pDstDrawable->height > pExaScr->info->maxY)
|
||||
if (pSrcPixmap->drawable.width > pExaScr->info->maxX ||
|
||||
pSrcPixmap->drawable.height > pExaScr->info->maxY ||
|
||||
pDstPixmap->drawable.width > pExaScr->info->maxX ||
|
||||
pDstPixmap->drawable.height > pExaScr->info->maxY)
|
||||
{
|
||||
exaDoMigration (pixmaps, 2, FALSE);
|
||||
goto fallback;
|
||||
|
|
@ -401,7 +411,8 @@ exaCopyNtoN (DrawablePtr pSrcDrawable,
|
|||
}
|
||||
|
||||
/* Mixed directions must be handled specially if the card is lame */
|
||||
if (pExaScr->info->flags & EXA_TWO_BITBLT_DIRECTIONS && (dx*dy) < 0) {
|
||||
if (pExaScr->info->flags & EXA_TWO_BITBLT_DIRECTIONS &&
|
||||
reverse != upsidedown) {
|
||||
if (!exaCopyNtoNTwoDir(pSrcDrawable, pDstDrawable, pGC, pbox, nbox,
|
||||
dx, dy))
|
||||
goto fallback;
|
||||
|
|
@ -411,7 +422,7 @@ exaCopyNtoN (DrawablePtr pSrcDrawable,
|
|||
if ((pSrcPixmap = exaGetOffscreenPixmap (pSrcDrawable, &src_off_x, &src_off_y)) &&
|
||||
(pDstPixmap = exaGetOffscreenPixmap (pDstDrawable, &dst_off_x, &dst_off_y)) &&
|
||||
(*pExaScr->info->PrepareCopy) (pSrcPixmap, pDstPixmap,
|
||||
dx, dy,
|
||||
reverse ? -1 : 1, upsidedown ? -1 : 1,
|
||||
pGC ? pGC->alu : GXcopy,
|
||||
pGC ? pGC->planemask : FB_ALLONES))
|
||||
{
|
||||
|
|
@ -423,11 +434,13 @@ exaCopyNtoN (DrawablePtr pSrcDrawable,
|
|||
pbox->x1 + dst_off_x, pbox->y1 + dst_off_y,
|
||||
pbox->x2 - pbox->x1,
|
||||
pbox->y2 - pbox->y1);
|
||||
exaPixmapDirty (pDstPixmap,
|
||||
pbox->x1 + dst_off_x, pbox->y1 + dst_off_y,
|
||||
pbox->x2 + dst_off_x, pbox->y2 + dst_off_y);
|
||||
pbox++;
|
||||
}
|
||||
(*pExaScr->info->DoneCopy) (pDstPixmap);
|
||||
exaMarkSync(pDstDrawable->pScreen);
|
||||
exaDrawableDirty (pDstDrawable);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -442,6 +455,11 @@ fallback:
|
|||
bitplane, closure);
|
||||
exaFinishAccess (pSrcDrawable, EXA_PREPARE_SRC);
|
||||
exaFinishAccess (pDstDrawable, EXA_PREPARE_DEST);
|
||||
while (nbox--)
|
||||
{
|
||||
exaDrawableDirty (pDstDrawable, pbox->x1, pbox->y1, pbox->x2, pbox->y2);
|
||||
pbox++;
|
||||
}
|
||||
}
|
||||
|
||||
RegionPtr
|
||||
|
|
@ -621,12 +639,12 @@ exaPolyFillRect(DrawablePtr pDrawable,
|
|||
|
||||
pixmaps[0].as_dst = TRUE;
|
||||
pixmaps[0].as_src = FALSE;
|
||||
pixmaps[0].pPix = exaGetDrawablePixmap (pDrawable);
|
||||
pixmaps[0].pPix = pPixmap = exaGetDrawablePixmap (pDrawable);
|
||||
|
||||
if (pExaScr->swappedOut ||
|
||||
pGC->fillStyle != FillSolid ||
|
||||
pDrawable->width > pExaScr->info->maxX ||
|
||||
pDrawable->height > pExaScr->info->maxY)
|
||||
pPixmap->drawable.width > pExaScr->info->maxX ||
|
||||
pPixmap->drawable.height > pExaScr->info->maxY)
|
||||
{
|
||||
exaDoMigration (pixmaps, 1, FALSE);
|
||||
ExaCheckPolyFillRect (pDrawable, pGC, nrect, prect);
|
||||
|
|
@ -681,6 +699,8 @@ exaPolyFillRect(DrawablePtr pDrawable,
|
|||
(*pExaScr->info->Solid) (pPixmap,
|
||||
fullX1 + xoff, fullY1 + yoff,
|
||||
fullX2 + xoff, fullY2 + yoff);
|
||||
exaPixmapDirty (pPixmap, fullX1 + xoff, fullY1 + yoff,
|
||||
fullX2 + xoff, fullY2 + yoff);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -706,15 +726,17 @@ exaPolyFillRect(DrawablePtr pDrawable,
|
|||
|
||||
pbox++;
|
||||
|
||||
if (partX1 < partX2 && partY1 < partY2)
|
||||
if (partX1 < partX2 && partY1 < partY2) {
|
||||
(*pExaScr->info->Solid) (pPixmap,
|
||||
partX1 + xoff, partY1 + yoff,
|
||||
partX2 + xoff, partY2 + yoff);
|
||||
exaPixmapDirty (pPixmap, partX1 + xoff, partY1 + yoff,
|
||||
partX2 + xoff, partY2 + yoff);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
(*pExaScr->info->DoneSolid) (pPixmap);
|
||||
exaDrawableDirty (pDrawable);
|
||||
exaMarkSync(pDrawable->pScreen);
|
||||
}
|
||||
|
||||
|
|
@ -735,14 +757,15 @@ exaSolidBoxClipped (DrawablePtr pDrawable,
|
|||
int xoff, yoff;
|
||||
int partX1, partX2, partY1, partY2;
|
||||
ExaMigrationRec pixmaps[1];
|
||||
Bool fallback = FALSE;
|
||||
|
||||
pixmaps[0].as_dst = TRUE;
|
||||
pixmaps[0].as_src = FALSE;
|
||||
pixmaps[0].pPix = exaGetDrawablePixmap (pDrawable);
|
||||
|
||||
pixmaps[0].pPix = pPixmap = exaGetDrawablePixmap (pDrawable);
|
||||
|
||||
if (pExaScr->swappedOut ||
|
||||
pDrawable->width > pExaScr->info->maxX ||
|
||||
pDrawable->height > pExaScr->info->maxY)
|
||||
pPixmap->drawable.width > pExaScr->info->maxX ||
|
||||
pPixmap->drawable.height > pExaScr->info->maxY)
|
||||
{
|
||||
exaDoMigration (pixmaps, 1, FALSE);
|
||||
goto fallback;
|
||||
|
|
@ -750,19 +773,21 @@ exaSolidBoxClipped (DrawablePtr pDrawable,
|
|||
exaDoMigration (pixmaps, 1, TRUE);
|
||||
}
|
||||
|
||||
if (!(pPixmap = exaGetOffscreenPixmap (pDrawable, &xoff, &yoff)) ||
|
||||
pPixmap = exaGetOffscreenPixmap (pDrawable, &xoff, &yoff);
|
||||
|
||||
if (!pPixmap ||
|
||||
!(*pExaScr->info->PrepareSolid) (pPixmap, GXcopy, pm, fg))
|
||||
{
|
||||
fallback:
|
||||
EXA_FALLBACK(("to %p (%c)\n", pDrawable,
|
||||
exaDrawableLocation(pDrawable)));
|
||||
fallback = TRUE;
|
||||
exaPrepareAccess (pDrawable, EXA_PREPARE_DEST);
|
||||
fg = fbReplicatePixel (fg, pDrawable->bitsPerPixel);
|
||||
fbSolidBoxClipped (pDrawable, pClip, x1, y1, x2, y2,
|
||||
fbAnd (GXcopy, fg, pm),
|
||||
fbXor (GXcopy, fg, pm));
|
||||
exaFinishAccess (pDrawable, EXA_PREPARE_DEST);
|
||||
return;
|
||||
}
|
||||
for (nbox = REGION_NUM_RECTS(pClip), pbox = REGION_RECTS(pClip);
|
||||
nbox--;
|
||||
|
|
@ -790,12 +815,20 @@ fallback:
|
|||
if (partY2 <= partY1)
|
||||
continue;
|
||||
|
||||
(*pExaScr->info->Solid) (pPixmap,
|
||||
partX1 + xoff, partY1 + yoff,
|
||||
partX2 + xoff, partY2 + yoff);
|
||||
if (!fallback) {
|
||||
(*pExaScr->info->Solid) (pPixmap,
|
||||
partX1 + xoff, partY1 + yoff,
|
||||
partX2 + xoff, partY2 + yoff);
|
||||
exaPixmapDirty (pPixmap, partX1 + xoff, partY1 + yoff,
|
||||
partX2 + xoff, partY2 + yoff);
|
||||
} else
|
||||
exaDrawableDirty (pDrawable, partX1, partY1, partX2, partY2);
|
||||
}
|
||||
|
||||
if (fallback)
|
||||
return;
|
||||
|
||||
(*pExaScr->info->DoneSolid) (pPixmap);
|
||||
exaDrawableDirty (pDrawable);
|
||||
exaMarkSync(pDrawable->pScreen);
|
||||
}
|
||||
|
||||
|
|
@ -908,12 +941,17 @@ exaImageGlyphBlt (DrawablePtr pDrawable,
|
|||
pPriv->fg,
|
||||
gx + dstXoff,
|
||||
gHeight);
|
||||
exaDrawableDirty (pDrawable, gx, gy, gx + gWidth, gy + gHeight);
|
||||
}
|
||||
else
|
||||
{
|
||||
RegionPtr pClip = fbGetCompositeClip(pGC);
|
||||
int nbox;
|
||||
BoxPtr pbox;
|
||||
|
||||
gStride = GLYPHWIDTHBYTESPADDED(pci) / sizeof (FbStip);
|
||||
fbPutXYImage (pDrawable,
|
||||
fbGetCompositeClip(pGC),
|
||||
pClip,
|
||||
pPriv->fg,
|
||||
pPriv->bg,
|
||||
pPriv->pm,
|
||||
|
|
@ -927,6 +965,18 @@ exaImageGlyphBlt (DrawablePtr pDrawable,
|
|||
(FbStip *) pglyph,
|
||||
gStride,
|
||||
0);
|
||||
|
||||
for (nbox = REGION_NUM_RECTS(pClip), pbox = REGION_RECTS(pClip);
|
||||
nbox--; pbox++) {
|
||||
int x1 = max(gx, pbox->x1), x2 = min(gx + gWidth, pbox->x2);
|
||||
int y1 = max(gy, pbox->y1), y2 = min(gy + gHeight, pbox->y2);
|
||||
|
||||
if (x1 >= x2 || y1 >= y2)
|
||||
continue;
|
||||
|
||||
exaDrawableDirty (pDrawable, gx, gy, gx + gWidth,
|
||||
gy + gHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
x += pci->metrics.characterWidth;
|
||||
|
|
@ -994,13 +1044,15 @@ exaFillRegionSolid (DrawablePtr pDrawable,
|
|||
PixmapPtr pPixmap;
|
||||
int xoff, yoff;
|
||||
ExaMigrationRec pixmaps[1];
|
||||
int nbox = REGION_NUM_RECTS (pRegion);
|
||||
BoxPtr pBox = REGION_RECTS (pRegion);
|
||||
|
||||
pixmaps[0].as_dst = TRUE;
|
||||
pixmaps[0].as_src = FALSE;
|
||||
pixmaps[0].pPix = exaGetDrawablePixmap (pDrawable);
|
||||
pixmaps[0].pPix = pPixmap = exaGetDrawablePixmap (pDrawable);
|
||||
|
||||
if (pDrawable->width > pExaScr->info->maxX ||
|
||||
pDrawable->height > pExaScr->info->maxY)
|
||||
if (pPixmap->drawable.width > pExaScr->info->maxX ||
|
||||
pPixmap->drawable.height > pExaScr->info->maxY)
|
||||
{
|
||||
exaDoMigration (pixmaps, 1, FALSE);
|
||||
goto fallback;
|
||||
|
|
@ -1011,19 +1063,17 @@ exaFillRegionSolid (DrawablePtr pDrawable,
|
|||
if ((pPixmap = exaGetOffscreenPixmap (pDrawable, &xoff, &yoff)) &&
|
||||
(*pExaScr->info->PrepareSolid) (pPixmap, GXcopy, FB_ALLONES, pixel))
|
||||
{
|
||||
int nbox = REGION_NUM_RECTS (pRegion);
|
||||
BoxPtr pBox = REGION_RECTS (pRegion);
|
||||
|
||||
while (nbox--)
|
||||
{
|
||||
(*pExaScr->info->Solid) (pPixmap,
|
||||
pBox->x1 + xoff, pBox->y1 + yoff,
|
||||
pBox->x2 + xoff, pBox->y2 + yoff);
|
||||
exaPixmapDirty (pPixmap, pBox->x1 + xoff, pBox->y1 + yoff,
|
||||
pBox->x2 + xoff, pBox->y2 + yoff);
|
||||
pBox++;
|
||||
}
|
||||
(*pExaScr->info->DoneSolid) (pPixmap);
|
||||
exaMarkSync(pDrawable->pScreen);
|
||||
exaDrawableDirty (pDrawable);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -1034,6 +1084,11 @@ fallback:
|
|||
fbFillRegionSolid (pDrawable, pRegion, 0,
|
||||
fbReplicatePixel (pixel, pDrawable->bitsPerPixel));
|
||||
exaFinishAccess (pDrawable, EXA_PREPARE_DEST);
|
||||
while (nbox--)
|
||||
{
|
||||
exaDrawableDirty (pDrawable, pBox->x1, pBox->y1, pBox->x2, pBox->y2);
|
||||
pBox++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1047,9 +1102,11 @@ exaFillRegionTiled (DrawablePtr pDrawable,
|
|||
{
|
||||
ExaScreenPriv(pDrawable->pScreen);
|
||||
PixmapPtr pPixmap;
|
||||
int xoff, yoff;
|
||||
int xoff, yoff, tileXoff, tileYoff;
|
||||
int tileWidth, tileHeight;
|
||||
ExaMigrationRec pixmaps[2];
|
||||
int nbox = REGION_NUM_RECTS (pRegion);
|
||||
BoxPtr pBox = REGION_RECTS (pRegion);
|
||||
|
||||
tileWidth = pTile->drawable.width;
|
||||
tileHeight = pTile->drawable.height;
|
||||
|
|
@ -1064,13 +1121,13 @@ exaFillRegionTiled (DrawablePtr pDrawable,
|
|||
|
||||
pixmaps[0].as_dst = TRUE;
|
||||
pixmaps[0].as_src = FALSE;
|
||||
pixmaps[0].pPix = exaGetDrawablePixmap (pDrawable);
|
||||
pixmaps[0].pPix = pPixmap = exaGetDrawablePixmap (pDrawable);
|
||||
pixmaps[1].as_dst = FALSE;
|
||||
pixmaps[1].as_src = TRUE;
|
||||
pixmaps[1].pPix = pTile;
|
||||
|
||||
if (pDrawable->width > pExaScr->info->maxX ||
|
||||
pDrawable->height > pExaScr->info->maxY ||
|
||||
if (pPixmap->drawable.width > pExaScr->info->maxX ||
|
||||
pPixmap->drawable.height > pExaScr->info->maxY ||
|
||||
tileWidth > pExaScr->info->maxX ||
|
||||
tileHeight > pExaScr->info->maxY)
|
||||
{
|
||||
|
|
@ -1081,18 +1138,16 @@ exaFillRegionTiled (DrawablePtr pDrawable,
|
|||
}
|
||||
|
||||
pPixmap = exaGetOffscreenPixmap (pDrawable, &xoff, &yoff);
|
||||
|
||||
if (!pPixmap)
|
||||
goto fallback;
|
||||
|
||||
if (!exaPixmapIsOffscreen(pTile))
|
||||
goto fallback;
|
||||
|
||||
if ((*pExaScr->info->PrepareCopy) (pTile, pPixmap, 0, 0, GXcopy,
|
||||
if ((*pExaScr->info->PrepareCopy) (exaGetOffscreenPixmap((DrawablePtr)pTile, &tileXoff, &tileYoff), pPixmap, 0, 0, GXcopy,
|
||||
FB_ALLONES))
|
||||
{
|
||||
int nbox = REGION_NUM_RECTS (pRegion);
|
||||
BoxPtr pBox = REGION_RECTS (pRegion);
|
||||
|
||||
while (nbox--)
|
||||
{
|
||||
int height = pBox->y2 - pBox->y1;
|
||||
|
|
@ -1118,7 +1173,7 @@ exaFillRegionTiled (DrawablePtr pDrawable,
|
|||
width -= w;
|
||||
|
||||
(*pExaScr->info->Copy) (pPixmap,
|
||||
tileX, tileY,
|
||||
tileX + tileXoff, tileY + tileYoff,
|
||||
dstX + xoff, dstY + yoff,
|
||||
w, h);
|
||||
dstX += w;
|
||||
|
|
@ -1127,11 +1182,12 @@ exaFillRegionTiled (DrawablePtr pDrawable,
|
|||
dstY += h;
|
||||
tileY = 0;
|
||||
}
|
||||
exaPixmapDirty (pPixmap, pBox->x1 + xoff, pBox->y1 + yoff,
|
||||
pBox->x2 + xoff, pBox->y2 + yoff);
|
||||
pBox++;
|
||||
}
|
||||
(*pExaScr->info->DoneCopy) (pPixmap);
|
||||
exaMarkSync(pDrawable->pScreen);
|
||||
exaDrawableDirty (pDrawable);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1144,6 +1200,11 @@ fallback:
|
|||
fbFillRegionTiled (pDrawable, pRegion, pTile);
|
||||
exaFinishAccess ((DrawablePtr)pTile, EXA_PREPARE_SRC);
|
||||
exaFinishAccess (pDrawable, EXA_PREPARE_DEST);
|
||||
while (nbox--)
|
||||
{
|
||||
exaDrawableDirty (pDrawable, pBox->x1, pBox->y1, pBox->x2, pBox->y2);
|
||||
pBox++;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
*
|
||||
* Authors:
|
||||
* Eric Anholt <eric@anholt.net>
|
||||
* Michel Dänzer <michel@tungstengraphics.com>
|
||||
*
|
||||
*/
|
||||
|
||||
|
|
@ -57,6 +58,27 @@ exaPixmapIsPinned (PixmapPtr pPix)
|
|||
return pExaPixmap == NULL || pExaPixmap->score == EXA_PIXMAP_SCORE_PINNED;
|
||||
}
|
||||
|
||||
/**
|
||||
* The fallback path for UTS/DFS failing is to just memcpy. exaCopyDirtyToSys
|
||||
* and exaCopyDirtyToFb both needed to do this loop.
|
||||
*/
|
||||
static void
|
||||
exaMemcpyBox (PixmapPtr pPixmap, BoxPtr pbox, CARD8 *src, int src_pitch,
|
||||
CARD8 *dst, int dst_pitch)
|
||||
{
|
||||
int i, cpp = pPixmap->drawable.bitsPerPixel / 8;
|
||||
int bytes = (pbox->x2 - pbox->x1) * cpp;
|
||||
|
||||
src += pbox->y1 * src_pitch + pbox->x1 * cpp;
|
||||
dst += pbox->y1 * dst_pitch + pbox->x1 * cpp;
|
||||
|
||||
for (i = pbox->y2 - pbox->y1; i; i--) {
|
||||
memcpy (dst, src, bytes);
|
||||
src += src_pitch;
|
||||
dst += dst_pitch;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns TRUE if the pixmap is dirty (has been modified in its current
|
||||
* location compared to the other), or lacks a private for tracking
|
||||
|
|
@ -67,7 +89,8 @@ exaPixmapIsDirty (PixmapPtr pPix)
|
|||
{
|
||||
ExaPixmapPriv (pPix);
|
||||
|
||||
return pExaPixmap == NULL || pExaPixmap->dirty == TRUE;
|
||||
return pExaPixmap == NULL ||
|
||||
REGION_NOTEMPTY (pScreen, DamageRegion(pExaPixmap->pDamage));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -98,54 +121,62 @@ exaCopyDirtyToSys (PixmapPtr pPixmap)
|
|||
{
|
||||
ExaScreenPriv (pPixmap->drawable.pScreen);
|
||||
ExaPixmapPriv (pPixmap);
|
||||
RegionPtr pRegion = DamageRegion (pExaPixmap->pDamage);
|
||||
CARD8 *save_ptr;
|
||||
int save_pitch;
|
||||
|
||||
if (!pExaPixmap->dirty)
|
||||
return;
|
||||
BoxPtr pBox = REGION_RECTS(pRegion);
|
||||
int nbox = REGION_NUM_RECTS(pRegion);
|
||||
Bool do_sync = FALSE;
|
||||
|
||||
save_ptr = pPixmap->devPrivate.ptr;
|
||||
save_pitch = pPixmap->devKind;
|
||||
pPixmap->devPrivate.ptr = pExaPixmap->fb_ptr;
|
||||
pPixmap->devKind = pExaPixmap->fb_pitch;
|
||||
|
||||
if (pExaScr->info->DownloadFromScreen == NULL ||
|
||||
!pExaScr->info->DownloadFromScreen (pPixmap,
|
||||
0,
|
||||
0,
|
||||
pPixmap->drawable.width,
|
||||
pPixmap->drawable.height,
|
||||
pExaPixmap->sys_ptr,
|
||||
pExaPixmap->sys_pitch))
|
||||
{
|
||||
char *src, *dst;
|
||||
int src_pitch, dst_pitch, i, bytes;
|
||||
while (nbox--) {
|
||||
pBox->x1 = max(pBox->x1, 0);
|
||||
pBox->y1 = max(pBox->y1, 0);
|
||||
pBox->x2 = min(pBox->x2, pPixmap->drawable.width);
|
||||
pBox->y2 = min(pBox->y2, pPixmap->drawable.height);
|
||||
|
||||
exaPrepareAccess(&pPixmap->drawable, EXA_PREPARE_SRC);
|
||||
if (pBox->x1 >= pBox->x2 || pBox->y1 >= pBox->y2)
|
||||
continue;
|
||||
|
||||
dst = pExaPixmap->sys_ptr;
|
||||
dst_pitch = pExaPixmap->sys_pitch;
|
||||
src = pExaPixmap->fb_ptr;
|
||||
src_pitch = pExaPixmap->fb_pitch;
|
||||
bytes = src_pitch < dst_pitch ? src_pitch : dst_pitch;
|
||||
|
||||
for (i = 0; i < pPixmap->drawable.height; i++) {
|
||||
memcpy (dst, src, bytes);
|
||||
dst += dst_pitch;
|
||||
src += src_pitch;
|
||||
if (pExaScr->info->DownloadFromScreen == NULL ||
|
||||
!pExaScr->info->DownloadFromScreen (pPixmap,
|
||||
pBox->x1, pBox->y1,
|
||||
pBox->x2 - pBox->x1,
|
||||
pBox->y2 - pBox->y1,
|
||||
pExaPixmap->sys_ptr
|
||||
+ pBox->y1 * pExaPixmap->sys_pitch
|
||||
+ pBox->x1 * pPixmap->drawable.bitsPerPixel / 8,
|
||||
pExaPixmap->sys_pitch))
|
||||
{
|
||||
exaPrepareAccess(&pPixmap->drawable, EXA_PREPARE_SRC);
|
||||
exaMemcpyBox (pPixmap, pBox,
|
||||
pExaPixmap->fb_ptr, pExaPixmap->fb_pitch,
|
||||
pExaPixmap->sys_ptr, pExaPixmap->sys_pitch);
|
||||
exaFinishAccess(&pPixmap->drawable, EXA_PREPARE_SRC);
|
||||
}
|
||||
exaFinishAccess(&pPixmap->drawable, EXA_PREPARE_SRC);
|
||||
else
|
||||
do_sync = TRUE;
|
||||
|
||||
pBox++;
|
||||
}
|
||||
|
||||
/* Make sure the bits have actually landed, since we don't necessarily sync
|
||||
* when accessing pixmaps in system memory.
|
||||
*/
|
||||
exaWaitSync (pPixmap->drawable.pScreen);
|
||||
if (do_sync)
|
||||
exaWaitSync (pPixmap->drawable.pScreen);
|
||||
|
||||
pPixmap->devPrivate.ptr = save_ptr;
|
||||
pPixmap->devKind = save_pitch;
|
||||
|
||||
pExaPixmap->dirty = FALSE;
|
||||
/* The previously damaged bits are now no longer damaged but valid */
|
||||
REGION_UNION(pPixmap->drawable.pScreen,
|
||||
&pExaPixmap->validReg, &pExaPixmap->validReg, pRegion);
|
||||
DamageEmpty (pExaPixmap->pDamage);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -158,49 +189,59 @@ exaCopyDirtyToFb (PixmapPtr pPixmap)
|
|||
{
|
||||
ExaScreenPriv (pPixmap->drawable.pScreen);
|
||||
ExaPixmapPriv (pPixmap);
|
||||
RegionPtr pRegion = DamageRegion (pExaPixmap->pDamage);
|
||||
CARD8 *save_ptr;
|
||||
int save_pitch;
|
||||
|
||||
if (!pExaPixmap->dirty)
|
||||
return;
|
||||
BoxPtr pBox = REGION_RECTS(pRegion);
|
||||
int nbox = REGION_NUM_RECTS(pRegion);
|
||||
Bool do_sync = FALSE;
|
||||
|
||||
save_ptr = pPixmap->devPrivate.ptr;
|
||||
save_pitch = pPixmap->devKind;
|
||||
pPixmap->devPrivate.ptr = pExaPixmap->fb_ptr;
|
||||
pPixmap->devKind = pExaPixmap->fb_pitch;
|
||||
|
||||
if (pExaScr->info->UploadToScreen == NULL ||
|
||||
!pExaScr->info->UploadToScreen (pPixmap,
|
||||
0,
|
||||
0,
|
||||
pPixmap->drawable.width,
|
||||
pPixmap->drawable.height,
|
||||
pExaPixmap->sys_ptr,
|
||||
pExaPixmap->sys_pitch))
|
||||
{
|
||||
char *src, *dst;
|
||||
int src_pitch, dst_pitch, i, bytes;
|
||||
while (nbox--) {
|
||||
pBox->x1 = max(pBox->x1, 0);
|
||||
pBox->y1 = max(pBox->y1, 0);
|
||||
pBox->x2 = min(pBox->x2, pPixmap->drawable.width);
|
||||
pBox->y2 = min(pBox->y2, pPixmap->drawable.height);
|
||||
|
||||
exaPrepareAccess(&pPixmap->drawable, EXA_PREPARE_DEST);
|
||||
if (pBox->x1 >= pBox->x2 || pBox->y1 >= pBox->y2)
|
||||
continue;
|
||||
|
||||
dst = pExaPixmap->fb_ptr;
|
||||
dst_pitch = pExaPixmap->fb_pitch;
|
||||
src = pExaPixmap->sys_ptr;
|
||||
src_pitch = pExaPixmap->sys_pitch;
|
||||
bytes = src_pitch < dst_pitch ? src_pitch : dst_pitch;
|
||||
|
||||
for (i = 0; i < pPixmap->drawable.height; i++) {
|
||||
memcpy (dst, src, bytes);
|
||||
dst += dst_pitch;
|
||||
src += src_pitch;
|
||||
if (pExaScr->info->UploadToScreen == NULL ||
|
||||
!pExaScr->info->UploadToScreen (pPixmap,
|
||||
pBox->x1, pBox->y1,
|
||||
pBox->x2 - pBox->x1,
|
||||
pBox->y2 - pBox->y1,
|
||||
pExaPixmap->sys_ptr
|
||||
+ pBox->y1 * pExaPixmap->sys_pitch
|
||||
+ pBox->x1 * pPixmap->drawable.bitsPerPixel / 8,
|
||||
pExaPixmap->sys_pitch))
|
||||
{
|
||||
exaPrepareAccess(&pPixmap->drawable, EXA_PREPARE_DEST);
|
||||
exaMemcpyBox (pPixmap, pBox,
|
||||
pExaPixmap->sys_ptr, pExaPixmap->sys_pitch,
|
||||
pExaPixmap->fb_ptr, pExaPixmap->fb_pitch);
|
||||
exaFinishAccess(&pPixmap->drawable, EXA_PREPARE_DEST);
|
||||
}
|
||||
exaFinishAccess(&pPixmap->drawable, EXA_PREPARE_DEST);
|
||||
else
|
||||
do_sync = TRUE;
|
||||
|
||||
pBox++;
|
||||
}
|
||||
|
||||
if (do_sync)
|
||||
exaMarkSync (pPixmap->drawable.pScreen);
|
||||
|
||||
pPixmap->devPrivate.ptr = save_ptr;
|
||||
pPixmap->devKind = save_pitch;
|
||||
|
||||
pExaPixmap->dirty = FALSE;
|
||||
/* The previously damaged bits are now no longer damaged but valid */
|
||||
REGION_UNION(pPixmap->drawable.pScreen,
|
||||
&pExaPixmap->validReg, &pExaPixmap->validReg, pRegion);
|
||||
DamageEmpty (pExaPixmap->pDamage);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -208,11 +249,12 @@ exaCopyDirtyToFb (PixmapPtr pPixmap)
|
|||
* Called when the memory manager decides it's time to kick the pixmap out of
|
||||
* framebuffer entirely.
|
||||
*/
|
||||
static void
|
||||
void
|
||||
exaPixmapSave (ScreenPtr pScreen, ExaOffscreenArea *area)
|
||||
{
|
||||
PixmapPtr pPixmap = area->privData;
|
||||
ExaPixmapPriv(pPixmap);
|
||||
RegionPtr pDamageReg = DamageRegion(pExaPixmap->pDamage);
|
||||
|
||||
DBG_MIGRATE (("Save %p (%p) (%dx%d) (%c)\n", pPixmap,
|
||||
(void*)(ExaGetPixmapPriv(pPixmap)->area ?
|
||||
|
|
@ -231,10 +273,9 @@ exaPixmapSave (ScreenPtr pScreen, ExaOffscreenArea *area)
|
|||
pExaPixmap->fb_ptr = NULL;
|
||||
pExaPixmap->area = NULL;
|
||||
|
||||
/* Mark it dirty now, to say that there is important data in the
|
||||
* system-memory copy.
|
||||
*/
|
||||
pExaPixmap->dirty = TRUE;
|
||||
/* Mark all valid bits as damaged, so they'll get copied to FB next time */
|
||||
REGION_UNION(pPixmap->drawable.pScreen, pDamageReg, pDamageReg,
|
||||
&pExaPixmap->validReg);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -413,32 +454,57 @@ exaMigrateTowardSys (PixmapPtr pPixmap)
|
|||
* If the pixmap has both a framebuffer and system memory copy, this function
|
||||
* asserts that both of them are the same.
|
||||
*/
|
||||
static void
|
||||
static Bool
|
||||
exaAssertNotDirty (PixmapPtr pPixmap)
|
||||
{
|
||||
ExaPixmapPriv (pPixmap);
|
||||
CARD8 *dst, *src;
|
||||
int dst_pitch, src_pitch, data_row_bytes, y;
|
||||
RegionPtr pValidReg = &pExaPixmap->validReg;
|
||||
int dst_pitch, src_pitch, cpp, y, nbox = REGION_NUM_RECTS(pValidReg);
|
||||
BoxPtr pBox = REGION_RECTS(pValidReg);
|
||||
Bool ret = TRUE;
|
||||
|
||||
if (pExaPixmap == NULL || pExaPixmap->fb_ptr == NULL)
|
||||
return;
|
||||
return ret;
|
||||
|
||||
dst = pExaPixmap->sys_ptr;
|
||||
dst_pitch = pExaPixmap->sys_pitch;
|
||||
src = pExaPixmap->fb_ptr;
|
||||
src_pitch = pExaPixmap->fb_pitch;
|
||||
data_row_bytes = pPixmap->drawable.width *
|
||||
pPixmap->drawable.bitsPerPixel / 8;
|
||||
cpp = pPixmap->drawable.bitsPerPixel / 8;
|
||||
|
||||
exaPrepareAccess(&pPixmap->drawable, EXA_PREPARE_SRC);
|
||||
for (y = 0; y < pPixmap->drawable.height; y++) {
|
||||
if (memcmp(dst, src, data_row_bytes) != 0) {
|
||||
abort();
|
||||
}
|
||||
dst += dst_pitch;
|
||||
src += src_pitch;
|
||||
while (nbox--) {
|
||||
int rowbytes;
|
||||
|
||||
pBox->x1 = max(pBox->x1, 0);
|
||||
pBox->y1 = max(pBox->y1, 0);
|
||||
pBox->x2 = min(pBox->x2, pPixmap->drawable.width);
|
||||
pBox->y2 = min(pBox->y2, pPixmap->drawable.height);
|
||||
|
||||
if (pBox->x1 >= pBox->x2 || pBox->y1 >= pBox->y2)
|
||||
continue;
|
||||
|
||||
rowbytes = (pBox->x2 - pBox->x1) * cpp;
|
||||
src += pBox->y1 * src_pitch + pBox->x1 * cpp;
|
||||
dst += pBox->y1 * dst_pitch + pBox->x1 * cpp;
|
||||
|
||||
for (y = pBox->y2 - pBox->y1; y; y--) {
|
||||
if (memcmp(dst + pBox->y1 * dst_pitch + pBox->x1 * cpp,
|
||||
src + pBox->y1 * src_pitch + pBox->x1 * cpp,
|
||||
(pBox->x2 - pBox->x1) * cpp) != 0) {
|
||||
ret = FALSE;
|
||||
break;
|
||||
}
|
||||
src += src_pitch;
|
||||
dst += dst_pitch;
|
||||
}
|
||||
src -= pBox->y1 * src_pitch + pBox->x1 * cpp;
|
||||
dst -= pBox->y1 * dst_pitch + pBox->x1 * cpp;
|
||||
}
|
||||
exaFinishAccess(&pPixmap->drawable, EXA_PREPARE_SRC);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -462,8 +528,9 @@ exaDoMigration (ExaMigrationPtr pixmaps, int npixmaps, Bool can_accel)
|
|||
*/
|
||||
if (pExaScr->checkDirtyCorrectness) {
|
||||
for (i = 0; i < npixmaps; i++) {
|
||||
if (!exaPixmapIsDirty (pixmaps[i].pPix))
|
||||
exaAssertNotDirty (pixmaps[i].pPix);
|
||||
if (!exaPixmapIsDirty (pixmaps[i].pPix) &&
|
||||
!exaAssertNotDirty (pixmaps[i].pPix))
|
||||
ErrorF("%s: Pixmap %d dirty but not marked as such!\n", __func__, i);
|
||||
}
|
||||
}
|
||||
/* If anything is pinned in system memory, we won't be able to
|
||||
|
|
|
|||
|
|
@ -81,15 +81,14 @@ ExaOffscreenKickOut (ScreenPtr pScreen, ExaOffscreenArea *area)
|
|||
* @param save callback for when the area is evicted from memory
|
||||
* @param privdata private data for the save callback.
|
||||
*
|
||||
* Allocates offscreen memory from the device associated with pScreen. size and
|
||||
* align deteremine where and how large the allocated area is, and locked will
|
||||
* mark whether it should be held in card memory. privdata may be any pointer
|
||||
* for the save callback when the area is removed.
|
||||
* Allocates offscreen memory from the device associated with pScreen. size
|
||||
* and align deteremine where and how large the allocated area is, and locked
|
||||
* will mark whether it should be held in card memory. privdata may be any
|
||||
* pointer for the save callback when the area is removed.
|
||||
*
|
||||
* Note that locked areas do get evicted on VT switch, because during that time
|
||||
* all offscreen memory becomes inaccessible. This may change in the future,
|
||||
* but drivers should be aware of this and provide a callback to mark that their
|
||||
* locked allocation was evicted, and then restore it if necessary on EnterVT.
|
||||
* Note that locked areas do get evicted on VT switch unless the driver
|
||||
* requested version 2.1 or newer behavior. In that case, the save callback is
|
||||
* still called.
|
||||
*/
|
||||
ExaOffscreenArea *
|
||||
exaOffscreenAlloc (ScreenPtr pScreen, int size, int align,
|
||||
|
|
@ -256,6 +255,9 @@ exaOffscreenAlloc (ScreenPtr pScreen, int size, int align,
|
|||
return area;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ejects all offscreen areas, and uninitializes the offscreen memory manager.
|
||||
*/
|
||||
void
|
||||
ExaOffscreenSwapOut (ScreenPtr pScreen)
|
||||
{
|
||||
|
|
@ -283,12 +285,56 @@ ExaOffscreenSwapOut (ScreenPtr pScreen)
|
|||
ExaOffscreenFini (pScreen);
|
||||
}
|
||||
|
||||
/** Ejects all pixmaps managed by EXA. */
|
||||
static void
|
||||
ExaOffscreenEjectPixmaps (ScreenPtr pScreen)
|
||||
{
|
||||
ExaScreenPriv (pScreen);
|
||||
|
||||
ExaOffscreenValidate (pScreen);
|
||||
/* loop until a single free area spans the space */
|
||||
for (;;)
|
||||
{
|
||||
ExaOffscreenArea *area;
|
||||
|
||||
for (area = pExaScr->info->offScreenAreas; area != NULL;
|
||||
area = area->next)
|
||||
{
|
||||
if (area->state == ExaOffscreenRemovable &&
|
||||
area->save == exaPixmapSave)
|
||||
{
|
||||
(void) ExaOffscreenKickOut (pScreen, area);
|
||||
ExaOffscreenValidate (pScreen);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (area == NULL)
|
||||
break;
|
||||
}
|
||||
ExaOffscreenValidate (pScreen);
|
||||
}
|
||||
|
||||
void
|
||||
ExaOffscreenSwapIn (ScreenPtr pScreen)
|
||||
{
|
||||
exaOffscreenInit (pScreen);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares EXA for disabling of FB access, or restoring it.
|
||||
*
|
||||
* In version 2.1, the disabling results in pixmaps being ejected, while other
|
||||
* allocations remain. With this plus the prevention of migration while
|
||||
* swappedOut is set, EXA by itself should not cause any access of the
|
||||
* framebuffer to occur while swapped out. Any remaining issues are the
|
||||
* responsibility of the driver.
|
||||
*
|
||||
* Prior to version 2.1, all allocations, including locked ones, are ejected
|
||||
* when access is disabled, and the allocator is torn down while swappedOut
|
||||
* is set. This is more drastic, and caused implementation difficulties for
|
||||
* many drivers that could otherwise handle the lack of FB access while
|
||||
* swapped out.
|
||||
*/
|
||||
void
|
||||
exaEnableDisableFBAccess (int index, Bool enable)
|
||||
{
|
||||
|
|
@ -296,10 +342,14 @@ exaEnableDisableFBAccess (int index, Bool enable)
|
|||
ExaScreenPriv (pScreen);
|
||||
|
||||
if (!enable) {
|
||||
ExaOffscreenSwapOut (pScreen);
|
||||
if (pExaScr->info->exa_minor < 1)
|
||||
ExaOffscreenSwapOut (pScreen);
|
||||
else
|
||||
ExaOffscreenEjectPixmaps (pScreen);
|
||||
pExaScr->swappedOut = TRUE;
|
||||
} else {
|
||||
ExaOffscreenSwapIn (pScreen);
|
||||
if (pExaScr->info->exa_minor < 1)
|
||||
ExaOffscreenSwapIn (pScreen);
|
||||
pExaScr->swappedOut = FALSE;
|
||||
}
|
||||
}
|
||||
|
|
@ -390,6 +440,7 @@ ExaOffscreenMarkUsed (PixmapPtr pPixmap)
|
|||
if (area->state == ExaOffscreenRemovable)
|
||||
area->score = (area->score * 7) / 8;
|
||||
}
|
||||
iter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@
|
|||
#ifdef RENDER
|
||||
#include "fbpict.h"
|
||||
#endif
|
||||
#include "damage.h"
|
||||
|
||||
#define DEBUG_TRACE_FALL 0
|
||||
#define DEBUG_MIGRATE 0
|
||||
|
|
@ -160,16 +161,16 @@ typedef struct {
|
|||
unsigned int fb_size; /**< size of pixmap in framebuffer memory */
|
||||
|
||||
/**
|
||||
* If area is NULL, then dirty == TRUE means that the pixmap has been
|
||||
* modified, so the contents are defined. Used to avoid uploads of
|
||||
* undefined data.
|
||||
*
|
||||
* If area is non-NULL, then dirty == TRUE means that the pixmap data at
|
||||
* pPixmap->devPrivate.ptr (either fb_ptr or sys_ptr) has been changed
|
||||
* compared to the copy in the other location. This is used to avoid
|
||||
* uploads/downloads of unmodified data.
|
||||
* The damage record contains the areas of the pixmap's current location
|
||||
* (framebuffer or system) that have been damaged compared to the other
|
||||
* location.
|
||||
*/
|
||||
Bool dirty;
|
||||
DamagePtr pDamage;
|
||||
/**
|
||||
* The valid region marks the valid bits of a drawable (at least, as it's
|
||||
* derived from damage, which may be overreported).
|
||||
*/
|
||||
RegionRec validReg;
|
||||
} ExaPixmapPrivRec, *ExaPixmapPrivPtr;
|
||||
|
||||
typedef struct _ExaMigrationRec {
|
||||
|
|
@ -315,7 +316,7 @@ ExaCheckComposite (CARD8 op,
|
|||
CARD16 height);
|
||||
#endif
|
||||
|
||||
/* exaoffscreen.c */
|
||||
/* exa_offscreen.c */
|
||||
void
|
||||
ExaOffscreenMarkUsed (PixmapPtr pPixmap);
|
||||
|
||||
|
|
@ -339,7 +340,10 @@ void
|
|||
exaFinishAccess(DrawablePtr pDrawable, int index);
|
||||
|
||||
void
|
||||
exaDrawableDirty(DrawablePtr pDrawable);
|
||||
exaPixmapDirty(PixmapPtr pPix, int x1, int y1, int x2, int y2);
|
||||
|
||||
void
|
||||
exaDrawableDirty(DrawablePtr pDrawable, int x1, int y1, int x2, int y2);
|
||||
|
||||
Bool
|
||||
exaDrawableIsOffscreen (DrawablePtr pDrawable);
|
||||
|
|
@ -409,9 +413,6 @@ void
|
|||
exaDoMigration (ExaMigrationPtr pixmaps, int npixmaps, Bool can_accel);
|
||||
|
||||
void
|
||||
exaMoveInPixmap (PixmapPtr pPixmap);
|
||||
|
||||
void
|
||||
exaMoveOutPixmap (PixmapPtr pPixmap);
|
||||
exaPixmapSave (ScreenPtr pScreen, ExaOffscreenArea *area);
|
||||
|
||||
#endif /* EXAPRIV_H */
|
||||
|
|
|
|||
|
|
@ -302,12 +302,12 @@ exaTryDriverSolidFill(PicturePtr pSrc,
|
|||
(*pExaScr->info->Solid) (pDstPix,
|
||||
pbox->x1 + dst_off_x, pbox->y1 + dst_off_y,
|
||||
pbox->x2 + dst_off_x, pbox->y2 + dst_off_y);
|
||||
exaPixmapDirty (pDstPix, pbox->x1 + dst_off_x, pbox->y1 + dst_off_y,
|
||||
pbox->x2 + dst_off_x, pbox->y2 + dst_off_y);
|
||||
pbox++;
|
||||
}
|
||||
|
||||
(*pExaScr->info->DoneSolid) (pDstPix);
|
||||
exaMarkSync(pDst->pDrawable->pScreen);
|
||||
exaDrawableDirty (pDst->pDrawable);
|
||||
|
||||
REGION_UNINIT(pDst->pDrawable->pScreen, ®ion);
|
||||
return 1;
|
||||
|
|
@ -336,16 +336,21 @@ exaTryDriverComposite(CARD8 op,
|
|||
struct _Pixmap scratch;
|
||||
ExaMigrationRec pixmaps[3];
|
||||
|
||||
pSrcPix = exaGetDrawablePixmap(pSrc->pDrawable);
|
||||
pDstPix = exaGetDrawablePixmap(pDst->pDrawable);
|
||||
if (pMask)
|
||||
pMaskPix = exaGetDrawablePixmap(pMask->pDrawable);
|
||||
|
||||
/* Bail if we might exceed coord limits by rendering from/to these. We
|
||||
* should really be making some scratch pixmaps with offsets and coords
|
||||
* adjusted to deal with this, but it hasn't been done yet.
|
||||
*/
|
||||
if (pSrc->pDrawable->width > pExaScr->info->maxX ||
|
||||
pSrc->pDrawable->height > pExaScr->info->maxY ||
|
||||
pDst->pDrawable->width > pExaScr->info->maxX ||
|
||||
pDst->pDrawable->height > pExaScr->info->maxY ||
|
||||
(pMask && (pMask->pDrawable->width > pExaScr->info->maxX ||
|
||||
pMask->pDrawable->height > pExaScr->info->maxY)))
|
||||
if (pSrcPix->drawable.width > pExaScr->info->maxX ||
|
||||
pSrcPix->drawable.height > pExaScr->info->maxY ||
|
||||
pDstPix->drawable.width > pExaScr->info->maxX ||
|
||||
pDstPix->drawable.height > pExaScr->info->maxY ||
|
||||
(pMask && (pMaskPix->drawable.width > pExaScr->info->maxX ||
|
||||
pMaskPix->drawable.height > pExaScr->info->maxY)))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
|
@ -441,12 +446,12 @@ exaTryDriverComposite(CARD8 op,
|
|||
pbox->y1 + dst_off_y,
|
||||
pbox->x2 - pbox->x1,
|
||||
pbox->y2 - pbox->y1);
|
||||
exaPixmapDirty (pDstPix, pbox->x1 + dst_off_x, pbox->y1 + dst_off_y,
|
||||
pbox->x2 + dst_off_x, pbox->y2 + dst_off_y);
|
||||
pbox++;
|
||||
}
|
||||
|
||||
(*pExaScr->info->DoneComposite) (pDstPix);
|
||||
exaMarkSync(pDst->pDrawable->pScreen);
|
||||
exaDrawableDirty (pDst->pDrawable);
|
||||
|
||||
REGION_UNINIT(pDst->pDrawable->pScreen, ®ion);
|
||||
return 1;
|
||||
|
|
@ -705,16 +710,19 @@ void
|
|||
exaRasterizeTrapezoid (PicturePtr pPicture, xTrapezoid *trap,
|
||||
int x_off, int y_off)
|
||||
{
|
||||
DrawablePtr pDraw = pPicture->pDrawable;
|
||||
ExaMigrationRec pixmaps[1];
|
||||
|
||||
pixmaps[0].as_dst = TRUE;
|
||||
pixmaps[0].as_src = TRUE;
|
||||
pixmaps[0].pPix = exaGetDrawablePixmap (pPicture->pDrawable);
|
||||
pixmaps[0].pPix = exaGetDrawablePixmap (pDraw);
|
||||
exaDoMigration(pixmaps, 1, FALSE);
|
||||
|
||||
exaPrepareAccess(pPicture->pDrawable, EXA_PREPARE_DEST);
|
||||
exaPrepareAccess(pDraw, EXA_PREPARE_DEST);
|
||||
fbRasterizeTrapezoid(pPicture, trap, x_off, y_off);
|
||||
exaFinishAccess(pPicture->pDrawable, EXA_PREPARE_DEST);
|
||||
exaDrawableDirty(pDraw, pDraw->x, pDraw->y,
|
||||
pDraw->x + pDraw->width, pDraw->y + pDraw->height);
|
||||
exaFinishAccess(pDraw, EXA_PREPARE_DEST);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -725,16 +733,19 @@ void
|
|||
exaAddTriangles (PicturePtr pPicture, INT16 x_off, INT16 y_off, int ntri,
|
||||
xTriangle *tris)
|
||||
{
|
||||
DrawablePtr pDraw = pPicture->pDrawable;
|
||||
ExaMigrationRec pixmaps[1];
|
||||
|
||||
pixmaps[0].as_dst = TRUE;
|
||||
pixmaps[0].as_src = TRUE;
|
||||
pixmaps[0].pPix = exaGetDrawablePixmap (pPicture->pDrawable);
|
||||
pixmaps[0].pPix = exaGetDrawablePixmap (pDraw);
|
||||
exaDoMigration(pixmaps, 1, FALSE);
|
||||
|
||||
exaPrepareAccess(pPicture->pDrawable, EXA_PREPARE_DEST);
|
||||
exaPrepareAccess(pDraw, EXA_PREPARE_DEST);
|
||||
fbAddTriangles(pPicture, x_off, y_off, ntri, tris);
|
||||
exaFinishAccess(pPicture->pDrawable, EXA_PREPARE_DEST);
|
||||
exaDrawableDirty(pDraw, pDraw->x, pDraw->y,
|
||||
pDraw->x + pDraw->width, pDraw->y + pDraw->height);
|
||||
exaFinishAccess(pDraw, EXA_PREPARE_DEST);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1023,10 +1034,11 @@ exaGlyphs (CARD8 op,
|
|||
|
||||
exaCopyArea (&pScratchPixmap->drawable, &pPixmap->drawable, pGC,
|
||||
0, 0, glyph->info.width, glyph->info.height, 0, 0);
|
||||
} else {
|
||||
exaDrawableDirty (&pPixmap->drawable);
|
||||
}
|
||||
|
||||
exaPixmapDirty (pPixmap, 0, 0,
|
||||
glyph->info.width, glyph->info.height);
|
||||
|
||||
if (maskFormat)
|
||||
{
|
||||
exaComposite (PictOpAdd, pPicture, NULL, pMask, 0, 0, 0, 0,
|
||||
|
|
|
|||
|
|
@ -200,11 +200,33 @@ ExaCheckPolyFillRect (DrawablePtr pDrawable, GCPtr pGC,
|
|||
int nrect, xRectangle *prect)
|
||||
{
|
||||
EXA_FALLBACK(("to %p (%c)\n", pDrawable, exaDrawableLocation(pDrawable)));
|
||||
exaPrepareAccess (pDrawable, EXA_PREPARE_DEST);
|
||||
exaPrepareAccessGC (pGC);
|
||||
fbPolyFillRect (pDrawable, pGC, nrect, prect);
|
||||
exaFinishAccessGC (pGC);
|
||||
exaFinishAccess (pDrawable, EXA_PREPARE_DEST);
|
||||
|
||||
if (nrect) {
|
||||
int x1 = max(prect->x, 0), y1 = max(prect->y, 0);
|
||||
int x2 = min(prect->x + prect->width, pDrawable->width);
|
||||
int y2 = min(prect->y + prect->height, pDrawable->height);
|
||||
|
||||
exaPrepareAccess (pDrawable, EXA_PREPARE_DEST);
|
||||
exaPrepareAccessGC (pGC);
|
||||
fbPolyFillRect (pDrawable, pGC, nrect, prect);
|
||||
exaFinishAccessGC (pGC);
|
||||
exaFinishAccess (pDrawable, EXA_PREPARE_DEST);
|
||||
|
||||
/* Only track bounding box of damage, as this path can degenerate to
|
||||
* zillions of damage boxes
|
||||
*/
|
||||
while (--nrect)
|
||||
{
|
||||
prect++;
|
||||
x1 = min(x1, prect->x);
|
||||
x2 = max(x2, prect->x + prect->width);
|
||||
y1 = min(y1, prect->y);
|
||||
y2 = max(y2, prect->y + prect->height);
|
||||
}
|
||||
|
||||
exaDrawableDirty (pDrawable, pDrawable->x + x1, pDrawable->y + y1,
|
||||
pDrawable->x + x2, pDrawable->y + y2);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ INCLUDES = \
|
|||
-I$(srcdir)/../../../exa \
|
||||
-I$(srcdir)/../../../miext/cw
|
||||
|
||||
AM_CFLAGS = $(XORG_CFLAGS)
|
||||
AM_CFLAGS = $(DIX_CFLAGS) $(XORG_CFLAGS)
|
||||
|
||||
libexa_la_SOURCES = \
|
||||
examodule.c
|
||||
|
|
|
|||
|
|
@ -35,6 +35,6 @@ Default: No.
|
|||
Chooses an alternate pixmap migration heuristic, for debugging purposes. The
|
||||
default is intended to be the best performing one for general use, though others
|
||||
may help with specific use cases. Available options include \*qalways\*q,
|
||||
\*qgreedy\*q, and \*qsmart\*q. Default: smart.
|
||||
\*qgreedy\*q, and \*qsmart\*q. Default: always.
|
||||
.SH AUTHORS
|
||||
Authors include: Keith Packard, Eric Anholt, Zack Rusin, and Michel Dänzer
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue