/*
 * Copyright © 2005 Novell, Inc.
 *
 * Permission to use, copy, modify, distribute, and sell this software
 * and its documentation for any purpose is hereby granted without
 * fee, provided that the above copyright notice appear in all copies
 * and that both that copyright notice and this permission notice
 * appear in supporting documentation, and that the name of
 * Novell, Inc. not be used in advertising or publicity pertaining to
 * distribution of the software without specific, written prior permission.
 * Novell, Inc. makes no representations about the suitability of this
 * software for any purpose. It is provided "as is" without express or
 * implied warranty.
 *
 * NOVELL, INC. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
 * NO EVENT SHALL NOVELL, INC. BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
 * OF USE, 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.
 *
 * Author: David Reveman <davidr@novell.com>
 */

#include "xgl.h"

#ifdef RENDER
#include "gcstruct.h"
#include "picturestr.h"

#define BITMAP_CACHE_SIZE      256000
#define BITMAP_CACHE_MAX_LEVEL ~0
#define BITMAP_CACHE_MAX_SIZE  512

#define TEXTURE_CACHE_SIZE	 512
#define TEXTURE_CACHE_MAX_LEVEL	 64
#define TEXTURE_CACHE_MAX_HEIGHT 72
#define TEXTURE_CACHE_MAX_WIDTH  72

#define NEXT_GLYPH_SERIAL_NUMBER ((++glyphSerialNumber) > MAX_SERIAL_NUM ? \
	    (glyphSerialNumber = 1): glyphSerialNumber)

#define GLYPH_GET_AREA_PRIV(pArea)		\
    ((xglGlyphAreaPtr) (pArea)->devPrivate.ptr)

#define GLYPH_AREA_PRIV(pArea)				    \
    xglGlyphAreaPtr pAreaPriv = GLYPH_GET_AREA_PRIV (pArea)

#define NEEDS_COMPONENT(f) (PICT_FORMAT_A (f) != 0 && PICT_FORMAT_RGB (f) != 0)

#define WRITE_VEC2(ptr, _x, _y) \
    *(ptr)++ = (_x);		\
    *(ptr)++ = (_y)

#define WRITE_BOX(ptr, _vx1, _vy1, _vx2, _vy2, box) \
    WRITE_VEC2 (ptr, _vx1, _vy1);		    \
    WRITE_VEC2 (ptr, (box).x1, (box).y2);	    \
    WRITE_VEC2 (ptr, _vx2, _vy1);		    \
    WRITE_VEC2 (ptr, (box).x2, (box).y2);	    \
    WRITE_VEC2 (ptr, _vx2, _vy2);		    \
    WRITE_VEC2 (ptr, (box).x2, (box).y1);	    \
    WRITE_VEC2 (ptr, _vx1, _vy2);		    \
    WRITE_VEC2 (ptr, (box).x1, (box).y1)

typedef union _xglGlyphList {
    glitz_short_t *s;
    glitz_float_t *f;
} xglGlyphListRec, *xglGlyphListPtr;

typedef struct _xglGlyphArray {
    int	lastX, lastY;
} xglGlyphArrayRec, *xglGlyphArrayPtr;

typedef union _xglGlyphVertexData {
    xglGlyphArrayRec array;
    xglGlyphListRec  list;
} xglGlyphVertexDataRec, *xglGlyphVertexDataPtr;

typedef struct _xglGlyphOp {
    GlyphListPtr pLists;
    int		 listLen;
    GlyphPtr	 *ppGlyphs;
    int		 nGlyphs;
    int		 xOff;
    int		 yOff;
    Bool	 noCache;
} xglGlyphOpRec, *xglGlyphOpPtr;

unsigned long glyphSerialNumber = 0;

xglAreaRec zeroSizeArea = {
    0, 0,
    0, 0,
    0, 0,
    { NULL, NULL, NULL, NULL }, NULL,
    (pointer) 0,
    { 0 }
};

static Bool
xglGlyphCreate (xglAreaPtr pArea)
{
    return TRUE;
}

static Bool
xglGlyphMoveIn (xglAreaPtr pArea,
		pointer    closure)
{
    xglGlyphCachePtr pCache = (xglGlyphCachePtr) pArea->pRoot->closure;
    GlyphPtr	     pGlyph = (GlyphPtr) closure;

    XGL_GLYPH_PRIV (pCache->pScreen, pGlyph);

    pGlyphPriv->pArea = pArea;

    return TRUE;
}

static void
xglGlyphMoveOut (xglAreaPtr pArea,
		 pointer    closure)
{
    xglGlyphCachePtr pCache = (xglGlyphCachePtr) pArea->pRoot->closure;
    GlyphPtr	     pGlyph = (GlyphPtr) closure;

    XGL_GLYPH_PRIV (pCache->pScreen, pGlyph);

    pGlyphPriv->pArea = NULL;
}

static int
xglGlyphCompareScore (xglAreaPtr pArea,
		      pointer	 closure1,
		      pointer	 closure2)
{
    GLYPH_AREA_PRIV (pArea);

    if (pAreaPriv->serial == glyphSerialNumber)
	return 1;

    return -1;
}

static const xglAreaFuncsRec xglGlyphAreaFuncs = {
    xglGlyphCreate,
    xglGlyphMoveIn,
    xglGlyphMoveOut,
    xglGlyphCompareScore
};

Bool
xglRealizeGlyph (ScreenPtr pScreen,
		 GlyphPtr  pGlyph)
{
    PictureScreenPtr pPictureScreen = GetPictureScreen (pScreen);
    Bool	     ret;

    XGL_SCREEN_PRIV (pScreen);
    XGL_GLYPH_PRIV (pScreen, pGlyph);

    XGL_PICTURE_SCREEN_UNWRAP (RealizeGlyph);
    ret = (*pPictureScreen->RealizeGlyph) (pScreen, pGlyph);
    XGL_PICTURE_SCREEN_WRAP (RealizeGlyph, xglRealizeGlyph);

    pGlyphPriv->pArea = NULL;

    return ret;
}

void
xglUnrealizeGlyph (ScreenPtr pScreen,
		   GlyphPtr  pGlyph)
{
    PictureScreenPtr pPictureScreen = GetPictureScreen (pScreen);

    XGL_SCREEN_PRIV (pScreen);
    XGL_GLYPH_PRIV (pScreen, pGlyph);

    XGL_PICTURE_SCREEN_UNWRAP (UnrealizeGlyph);
    (*pPictureScreen->UnrealizeGlyph) (pScreen, pGlyph);
    XGL_PICTURE_SCREEN_WRAP (UnrealizeGlyph, xglUnrealizeGlyph);

    if (pGlyphPriv->pArea && pGlyphPriv->pArea->width)
	xglWithdrawArea (pGlyphPriv->pArea);
}

Bool
xglInitGlyphCache (xglGlyphCachePtr pCache,
		   ScreenPtr	    pScreen,
		   PictFormatPtr    format)
{
    XGL_SCREEN_PRIV (pScreen);

    pCache->depth = format->depth;

    if (!pScreenPriv->pSolidAlpha)
    {
	xglCreateSolidAlphaPicture (pScreen);
	if (!pScreenPriv->pSolidAlpha)
	    return FALSE;
    }

    if (pCache->depth == 1)
    {
	int stride;

	GEOMETRY_INIT (pScreen, &pCache->u.geometry,
		       GLITZ_GEOMETRY_TYPE_VERTEX,
		       GEOMETRY_USAGE_STATIC, BITMAP_CACHE_SIZE);
	GEOMETRY_SET_VERTEX_DATA_TYPE (&pCache->u.geometry,
				       pScreenPriv->geometryDataType);

	stride = pCache->u.geometry.f.vertex.bytes_per_vertex;
	if (!xglRootAreaInit (&pCache->rootArea,
			      BITMAP_CACHE_MAX_LEVEL,
			      BITMAP_CACHE_SIZE / (stride * 4),
			      0, sizeof (xglGlyphAreaRec),
			      (xglAreaFuncsPtr) &xglGlyphAreaFuncs,
			      (pointer) pCache))
	{
	    GEOMETRY_UNINIT (&pCache->u.geometry);
	    return FALSE;
	}
    }
    else
    {

	xglGlyphTexturePtr	   pTexture = &pCache->u.texture;
	glitz_surface_t		   *mask;
	glitz_surface_attributes_t attr;
	glitz_vertex_format_t	   *vertex;
	xglVisualPtr		   pVisual;

	pVisual = xglFindVisualWithDepth (pScreen, format->depth);
	if (!pVisual)
	    return FALSE;

	if (!xglRootAreaInit (&pCache->rootArea,
			      TEXTURE_CACHE_MAX_LEVEL,
			      TEXTURE_CACHE_SIZE, TEXTURE_CACHE_SIZE,
			      sizeof (xglGlyphAreaRec),
			      (xglAreaFuncsPtr) &xglGlyphAreaFuncs,
			      (pointer) pCache))
	    return FALSE;

	if (pScreenPriv->geometryDataType == GEOMETRY_DATA_TYPE_SHORT)
	{
	    attr.unnormalized = 1;
	    mask = glitz_surface_create (pScreenPriv->drawable,
					 pVisual->format.surface,
					 TEXTURE_CACHE_SIZE,
					 TEXTURE_CACHE_SIZE,
					 GLITZ_SURFACE_UNNORMALIZED_MASK,
					 &attr);
	}
	else
	    mask = NULL;

	if (!mask)
	{
	    mask = glitz_surface_create (pScreenPriv->drawable,
					 pVisual->format.surface,
					 TEXTURE_CACHE_SIZE,
					 TEXTURE_CACHE_SIZE,
					 0, NULL);
	    if (!mask)
		return FALSE;

	    pTexture->geometryDataType = GEOMETRY_DATA_TYPE_FLOAT;
	}
	else
	    pTexture->geometryDataType = GEOMETRY_DATA_TYPE_SHORT;

	if (NEEDS_COMPONENT (format->format))
	    glitz_surface_set_component_alpha (mask, 1);

	pTexture->pMask = xglCreateDevicePicture (mask);
	if (!pTexture->pMask)
	    return FALSE;

	vertex = &pCache->u.texture.format.vertex;
	vertex->primitive  = GLITZ_PRIMITIVE_QUADS;
	vertex->mask.size  = GLITZ_COORDINATE_SIZE_XY;
	vertex->attributes = GLITZ_VERTEX_ATTRIBUTE_MASK_COORD_MASK;

	if (pTexture->geometryDataType == GEOMETRY_DATA_TYPE_FLOAT)
	{
	    vertex->type	     = GLITZ_DATA_TYPE_FLOAT;
	    vertex->bytes_per_vertex = sizeof (glitz_float_t) * 4;
	    vertex->mask.offset	     = sizeof (glitz_float_t) * 2;
	    vertex->mask.type	     = GLITZ_DATA_TYPE_FLOAT;
	}
	else
	{
	    vertex->type	     = GLITZ_DATA_TYPE_SHORT;
	    vertex->bytes_per_vertex = sizeof (glitz_short_t) * 4;
	    vertex->mask.offset	     = sizeof (glitz_short_t) * 2;
	    vertex->mask.type	     = GLITZ_DATA_TYPE_SHORT;
	}

	pTexture->pixel.fourcc	       = GLITZ_FOURCC_RGB;
	pTexture->pixel.masks	       = pVisual->pPixel->masks;
	pTexture->pixel.scanline_order = GLITZ_PIXEL_SCANLINE_ORDER_BOTTOM_UP;
	pTexture->pixel.bytes_per_line = 0;
	pTexture->pixel.xoffset	       = 0;
	pTexture->pixel.skip_lines     = 0;
    }

    pCache->pScreen = pScreen;

    return TRUE;
}

void
xglFiniGlyphCache (xglGlyphCachePtr pCache)
{
    if (pCache->pScreen)
    {
	xglRootAreaFini (&pCache->rootArea);

	if (pCache->depth == 1)
	{
	    GEOMETRY_UNINIT (&pCache->u.geometry);
	}
	else
	{
	    if (pCache->u.texture.pMask)
		FreePicture ((pointer) pCache->u.texture.pMask, 0);
	}

	pCache->pScreen = NULL;
    }
}

static xglAreaPtr
xglCacheGlyph (xglGlyphCachePtr pCache,
	       GlyphPtr		pGlyph)
{
    ScreenPtr pScreen = pCache->pScreen;

    XGL_GLYPH_PRIV (pScreen, pGlyph);

    if (pCache->depth == 1)
    {
	PixmapPtr pPixmap;
	RegionPtr pRegion;
	int	  nBox;

	pPixmap = GetScratchPixmapHeader (pScreen,
					  pGlyph->info.width,
					  pGlyph->info.height,
					  pCache->depth, pCache->depth, 0,
					  (pointer) (pGlyph + 1));
	if (!pPixmap)
	    return NULL;

	(*pScreen->ModifyPixmapHeader) (pPixmap,
					pGlyph->info.width,
					pGlyph->info.height,
					0, 0, -1, (pointer) (pGlyph + 1));

	pRegion = (*pScreen->BitmapToRegion) (pPixmap);
	FreeScratchPixmapHeader (pPixmap);

	if (!pRegion)
	    return NULL;

	nBox = REGION_NUM_RECTS (pRegion);
	if (nBox > BITMAP_CACHE_MAX_SIZE)
	{
	    REGION_DESTROY (pScreen, pRegion);
	    return NULL;
	}

	if (nBox > 0)
	{
	    /* Find available area */
	    if (!xglFindArea (pCache->rootArea.pArea, nBox, 0,
			      FALSE, (pointer) pGlyph))
	    {
		/* Kicking out area with lower score */
		xglFindArea (pCache->rootArea.pArea, nBox, 0,
			     TRUE, (pointer) pGlyph);
	    }

	    if (pGlyphPriv->pArea)
	    {
		int stride;

		GLYPH_AREA_PRIV (pGlyphPriv->pArea);

		pAreaPriv->serial = glyphSerialNumber;
		pAreaPriv->u.range.first = pGlyphPriv->pArea->x * 4;
		pAreaPriv->u.range.count = nBox * 4;

		stride = pCache->u.geometry.f.vertex.bytes_per_vertex;
		GEOMETRY_ADD_REGION_AT (pScreen, &pCache->u.geometry, pRegion,
					pGlyphPriv->pArea->x * stride * 4);
	    }
	} else
	    pGlyphPriv->pArea = &zeroSizeArea;

	REGION_DESTROY (pScreen, pRegion);
    }
    else
    {
	xglGlyphTexturePtr pTexture = &pCache->u.texture;

	if (pGlyph->info.width  > TEXTURE_CACHE_MAX_WIDTH ||
	    pGlyph->info.height > TEXTURE_CACHE_MAX_HEIGHT)
	    return NULL;

	if (pGlyph->info.width > 0 && pGlyph->info.height > 0)
	{
	    glitz_buffer_t *buffer;

	    buffer = glitz_buffer_create_for_data (pGlyph + 1);
	    if (!buffer)
		return NULL;

	    /* Find available area */
	    if (!xglFindArea (pCache->rootArea.pArea,
			      pGlyph->info.width, pGlyph->info.height,
			      FALSE, (pointer) pGlyph))
	    {
		/* Kicking out area with lower score */
		xglFindArea (pCache->rootArea.pArea,
			     pGlyph->info.width, pGlyph->info.height,
			     TRUE, (pointer) pGlyph);
	    }

	    if (pGlyphPriv->pArea)
	    {
		glitz_surface_t	     *surface;
		glitz_point_fixed_t  p1, p2;
		glitz_pixel_format_t pixel;

		GLYPH_AREA_PRIV (pGlyphPriv->pArea);

		pixel = pTexture->pixel;
		pixel.bytes_per_line =
		    PixmapBytePad (pGlyph->info.width, pCache->depth);

		surface = pTexture->pMask->pSourcePict->source.devPrivate.ptr;

		glitz_set_pixels (surface,
				  pGlyphPriv->pArea->x,
				  pGlyphPriv->pArea->y,
				  pGlyph->info.width,
				  pGlyph->info.height,
				  &pixel,
				  buffer);

		p1.x = pGlyphPriv->pArea->x << 16;
		p1.y = pGlyphPriv->pArea->y << 16;
		p2.x = (pGlyphPriv->pArea->x + pGlyph->info.width)  << 16;
		p2.y = (pGlyphPriv->pArea->y + pGlyph->info.height) << 16;

		glitz_surface_translate_point (surface, &p1, &p1);
		glitz_surface_translate_point (surface, &p2, &p2);

		pAreaPriv->serial = glyphSerialNumber;
		if (pTexture->geometryDataType)
		{
		    pAreaPriv->u.box.fBox.x1 = FIXED_TO_FLOAT (p1.x);
		    pAreaPriv->u.box.fBox.y1 = FIXED_TO_FLOAT (p1.y);
		    pAreaPriv->u.box.fBox.x2 = FIXED_TO_FLOAT (p2.x);
		    pAreaPriv->u.box.fBox.y2 = FIXED_TO_FLOAT (p2.y);
		}
		else
		{
		    pAreaPriv->u.box.sBox.x1 = p1.x >> 16;
		    pAreaPriv->u.box.sBox.y1 = p1.y >> 16;
		    pAreaPriv->u.box.sBox.x2 = p2.x >> 16;
		    pAreaPriv->u.box.sBox.y2 = p2.y >> 16;
		}
	    }
	    glitz_buffer_destroy (buffer);
	} else
	    pGlyphPriv->pArea = &zeroSizeArea;
    }

    return pGlyphPriv->pArea;
}

static void
xglUncachedGlyphs (CARD8	 op,
		   PicturePtr    pSrc,
		   PicturePtr    pDst,
		   INT16	 xSrc,
		   INT16	 ySrc,
		   xglGlyphOpPtr pOp)
{
    ScreenPtr	     pScreen = pDst->pDrawable->pScreen;
    PicturePtr	     pPicture = NULL;
    PixmapPtr	     pPixmap = NULL;
    xglGlyphCachePtr pCache;
    int		     depth = pOp->pLists->format->depth;
    GlyphPtr	     glyph;
    INT16	     xOff, yOff;
    xglGlyphPtr	     pGlyphPriv;
    xglAreaPtr	     pArea;
    Bool	     usingCache = !pOp->noCache;

    XGL_SCREEN_PRIV (pScreen);

    pCache = &pScreenPriv->glyphCache[depth];
    if (usingCache)
    {
	if (!pCache->pScreen)
	{
	    if (!xglInitGlyphCache (pCache, pScreen, pOp->pLists->format))
		usingCache = FALSE;
	}
    }

    while (pOp->nGlyphs)
    {
	glyph = *pOp->ppGlyphs;

	if (!pOp->listLen)
	{
	    pOp->pLists++;
	    pOp->listLen = pOp->pLists->len;
	    pOp->xOff   += pOp->pLists->xOff;
	    pOp->yOff   += pOp->pLists->yOff;
	}

	xOff = pOp->xOff;
	yOff = pOp->yOff;

	if (usingCache)
	{
	    pGlyphPriv = XGL_GET_GLYPH_PRIV (pScreen, glyph);
	    pArea = pGlyphPriv->pArea;
	    if (pSrc)
	    {
		if (!pArea)
		    pArea = xglCacheGlyph (pCache, glyph);

		if (pArea)
		    break;
	    }
	} else
	    pArea = NULL;

	pOp->listLen--;
	pOp->nGlyphs--;
	pOp->ppGlyphs++;

	pOp->xOff += glyph->info.xOff;
	pOp->yOff += glyph->info.yOff;

	if (pArea)
	    continue;

	if (!pPicture)
	{
	    XID componentAlpha;
	    int	error;

	    pPixmap = GetScratchPixmapHeader (pScreen,
					      glyph->info.width,
					      glyph->info.height,
					      depth, depth,
					      0, (pointer) (glyph + 1));
	    if (!pPixmap)
		return;

	    componentAlpha = NEEDS_COMPONENT (pOp->pLists->format->format);
	    pPicture = CreatePicture (0, &pPixmap->drawable,
				      pOp->pLists->format,
				      CPComponentAlpha, &componentAlpha,
				      serverClient, &error);
	    if (!pPicture)
	    {
		FreeScratchPixmapHeader (pPixmap);
		return;
	    }
	}

	(*pScreen->ModifyPixmapHeader) (pPixmap,
					glyph->info.width, glyph->info.height,
					0, 0, -1, (pointer) (glyph + 1));
	pPixmap->drawable.serialNumber = NEXT_SERIAL_NUMBER;

	if (pSrc)
	    CompositePicture (op,
			      pSrc,
			      pPicture,
			      pDst,
			      xSrc + (xOff - glyph->info.x),
			      ySrc + (yOff - glyph->info.y),
			      0, 0,
			      xOff - glyph->info.x,
			      yOff - glyph->info.y,
			      glyph->info.width,
			      glyph->info.height);
	else
	    CompositePicture (PictOpAdd,
			      pPicture,
			      NULL,
			      pDst,
			      0, 0,
			      0, 0,
			      xOff - glyph->info.x,
			      yOff - glyph->info.y,
			      glyph->info.width,
			      glyph->info.height);
    }

    if (pPicture)
    {
	FreeScratchPixmapHeader (pPixmap);
	FreePicture ((pointer) pPicture, 0);
    }
}

static Bool
xglCachedGlyphs (CARD8	       op,
		 PicturePtr    pSrc,
		 PicturePtr    pDst,
		 INT16	       xSrc,
		 INT16	       ySrc,
		 xglGlyphOpPtr pOp)
{
    ScreenPtr		  pScreen = pDst->pDrawable->pScreen;
    xglGlyphOpRec	  opSave = *pOp;
    xglGlyphCachePtr	  pCache;
    xglGlyphVertexDataRec vData;
    xglGeometryPtr	  pGeometry;
    GlyphPtr		  glyph;
    xglGlyphPtr		  pGlyphPriv;
    xglAreaPtr		  pArea;
    xglGlyphAreaPtr	  pGlyphArea;
    BoxRec		  extents;
    INT16		  xOff, yOff, x1, x2, y1, y2;
    int			  depth = pOp->pLists->format->depth;
    int			  i, remaining = pOp->nGlyphs;
    int			  nGlyph = 0;
    PicturePtr		  pMaskPicture = NULL;

    XGL_SCREEN_PRIV (pScreen);

    pCache = &pScreenPriv->glyphCache[depth];
    if (!pCache->pScreen)
    {
	if (!xglInitGlyphCache (pCache, pScreen, pOp->pLists->format))
	{
	    pOp->noCache = TRUE;
	    return 1;
	}
    }

    /* update serial number for all glyphs already in cache so that
       we don't accidentally replace one. */
    for (i = 0; i < pOp->nGlyphs; i++)
    {
	pGlyphPriv = XGL_GET_GLYPH_PRIV (pScreen, pOp->ppGlyphs[i]);
	pArea = pGlyphPriv->pArea;
	if (pArea && pArea->width)
	    GLYPH_GET_AREA_PRIV (pArea)->serial = glyphSerialNumber;
    }

    for (i = 0; i < pOp->nGlyphs; i++)
    {
	pGlyphPriv = XGL_GET_GLYPH_PRIV (pScreen, pOp->ppGlyphs[i]);
	pArea = pGlyphPriv->pArea;
	if (!pArea)
	    pArea = xglCacheGlyph (pCache, pOp->ppGlyphs[i]);

	if (pArea)
	{
	    if (pArea->width)
		nGlyph++;
	}
	else if (pSrc)
	    break;
    }

    if (nGlyph)
    {
	if (depth == 1)
	{
	    glitz_multi_array_t *multiArray;

	    pGeometry = &pCache->u.geometry;
	    pGeometry->xOff = pGeometry->yOff = 0;

	    multiArray = glitz_multi_array_create (nGlyph);
	    if (!multiArray)
		return 1;

	    GEOMETRY_SET_MULTI_ARRAY (pGeometry, multiArray);
	    glitz_multi_array_destroy (multiArray);

	    vData.array.lastX = 0;
	    vData.array.lastY = 0;
	}
	else
	{
	    i = 4 * pCache->u.texture.format.vertex.bytes_per_vertex * nGlyph;
	    pGeometry = xglGetScratchGeometryWithSize (pScreen, i);

	    pGeometry->f = pCache->u.texture.format;
	    pGeometry->type = GLITZ_GEOMETRY_TYPE_VERTEX;
	    pMaskPicture = pCache->u.texture.pMask;

	    vData.list.s = glitz_buffer_map (pGeometry->buffer,
					     GLITZ_BUFFER_ACCESS_WRITE_ONLY);
	}
    } else
	pGeometry = NULL;

    extents.x1 = MAXSHORT;
    extents.y1 = MAXSHORT;
    extents.x2 = MINSHORT;
    extents.y2 = MINSHORT;

    while (pOp->nGlyphs)
    {
	glyph = *pOp->ppGlyphs;

	if (!pOp->listLen)
	{
	    pOp->pLists++;
	    pOp->listLen = pOp->pLists->len;
	    pOp->xOff   += pOp->pLists->xOff;
	    pOp->yOff   += pOp->pLists->yOff;
	}

	xOff = pOp->xOff;
	yOff = pOp->yOff;

	pGlyphPriv = XGL_GET_GLYPH_PRIV (pScreen, glyph);
	pArea = pGlyphPriv->pArea;
	if (!pArea && pSrc)
	    break;

	pOp->listLen--;
	pOp->nGlyphs--;
	pOp->ppGlyphs++;

	pOp->xOff += glyph->info.xOff;
	pOp->yOff += glyph->info.yOff;

	if (!pArea)
	    continue;

	x1 = xOff - glyph->info.x;
	x2 = x1 + glyph->info.width;
	if (x1 < extents.x1)
	    extents.x1 = x1;
	if (x2 > extents.x2)
	    extents.x2 = x2;

	y1 = yOff - glyph->info.y;
	y2 = y1 + glyph->info.height;
	if (y1 < extents.y1)
	    extents.y1 = y1;
	if (y2 > extents.y2)
	    extents.y2 = y2;

	if (pArea->width)
	{
	    pGlyphArea = GLYPH_GET_AREA_PRIV (pArea);
	    if (depth == 1)
	    {
		glitz_multi_array_add (pGeometry->array,
				       pGlyphArea->u.range.first, 2,
				       pGlyphArea->u.range.count,
				       (x1 - vData.array.lastX) << 16,
				       (y1 - vData.array.lastY) << 16);
		vData.array.lastX = x1;
		vData.array.lastY = y1;
	    }
	    else
	    {
		if (pCache->u.texture.geometryDataType)
		{
		    WRITE_BOX (vData.list.f, x1, y1, x2, y2,
			       pGlyphArea->u.box.fBox);
		}
		else
		{
		    WRITE_BOX (vData.list.s, x1, y1, x2, y2,
			       pGlyphArea->u.box.sBox);
		}
	    }
	}
	remaining--;
    }

    NEXT_GLYPH_SERIAL_NUMBER;

    if (nGlyph)
    {
	if (depth != 1)
	{
	    glitz_buffer_unmap (pGeometry->buffer);
	    pGeometry->count = nGlyph * 4;
	}

	xSrc += extents.x1;
	ySrc += extents.y1;

	if (!pSrc)
	{
	    op = PictOpAdd;
	    pSrc = pScreenPriv->pSolidAlpha;

	    if (remaining)
		*pOp = opSave;
	}

	GEOMETRY_TRANSLATE (pGeometry,
			    pDst->pDrawable->x,
			    pDst->pDrawable->y);

	if (xglCompositeGeneral (op,
				 pSrc,
				 pMaskPicture,
				 pDst,
				 pGeometry,
				 xSrc, ySrc,
				 0, 0,
				 pDst->pDrawable->x + extents.x1,
				 pDst->pDrawable->y + extents.y1,
				 extents.x2 - extents.x1,
				 extents.y2 - extents.y1))
	{
	    xglAddCurrentBitDamage (pDst->pDrawable);
	    return remaining;
	}

	remaining = ~0;
	*pOp = opSave;
	pOp->noCache = TRUE;
    }
    else
    {
	if (remaining)
	{
	    *pOp = opSave;
	    pOp->noCache = TRUE;
	}
    }

    return remaining;
}

static Bool
xglGlyphExtents (PicturePtr   pDst,
		 int	      nlist,
		 GlyphListPtr list,
		 GlyphPtr     *glyphs,
		 BoxPtr	      extents)
{
    GlyphPtr glyph;
    BoxRec   line;
    int	     x1, x2, y1, y2;
    int	     n;
    int	     x;
    int	     y;
    Bool     overlap = FALSE;

    x = 0;
    y = 0;

    extents->x1 = MAXSHORT;
    extents->x2 = MINSHORT;
    extents->y1 = MAXSHORT;
    extents->y2 = MINSHORT;

    while (!list->len)
    {
	if (--nlist)
	{
	    x += list->xOff;
	    y += list->yOff;
	    list++;
	}
	else
	{
	    return FALSE;
	}
    }

    glyph = *glyphs;
    x1 = (x + list->xOff) - glyph->info.x;
    if (x1 < MINSHORT)
	x1 = MINSHORT;
    y1 = (y  + list->yOff) - glyph->info.y;
    if (y1 < MINSHORT)
	y1 = MINSHORT;

    line.x1 = x1;
    line.x2 = x1;
    line.y1 = y1;
    line.y2 = y1;

    while (nlist--)
    {
	x += list->xOff;
	y += list->yOff;
	n = list->len;
	list++;

	while (n--)
	{
	    glyph = *glyphs++;
	    x1 = x - glyph->info.x;
	    if (x1 < MINSHORT)
		x1 = MINSHORT;
	    y1 = y - glyph->info.y;
	    if (y1 < MINSHORT)
		y1 = MINSHORT;
	    x2 = x1 + glyph->info.width;
	    if (x2 > MAXSHORT)
		x2 = MAXSHORT;
	    y2 = y1 + glyph->info.height;
	    if (y2 > MAXSHORT)
		y2 = MAXSHORT;

	    if (x1 >= line.x2)
	    {
		line.x2 = x2;
		if (y1 < line.y1)
		    line.y1 = y1;
		if (y2 > line.y2)
		    line.y2 = y2;
	    }
	    else if (x2 <= line.x1)
	    {
		line.x1 = x1;
		if (y1 < line.y1)
		    line.y1 = y1;
		if (y2 > line.y2)
		    line.y2 = y2;
	    }
	    else
	    {
		if (line.y1 >= extents->y2)
		{
		    extents->y2 = line.y2;
		    if (line.y1 < extents->y1)
			extents->y1 = line.y1;
		}
		else if (line.y2 <= extents->y1)
		{
		    extents->y1 = line.y1;
		    if (line.y2 > extents->y2)
			extents->y2 = line.y2;
		}
		else
		{
		    if (line.y1 < extents->y1)
			extents->y1 = line.y1;
		    if (line.y2 > extents->y2)
			extents->y2 = line.y2;

		    overlap = TRUE;
		}

		if (line.x1 < extents->x1)
		    extents->x1 = line.x1;
		if (line.x2 > extents->x2)
		    extents->x2 = line.x2;

		line.x1 = x1;
		line.y1 = y1;
		line.x2 = x2;
		line.y2 = y2;
	    }

	    x += glyph->info.xOff;
	    y += glyph->info.yOff;
	}
    }

    if (line.y1 >= extents->y2)
    {
	extents->y2 = line.y2;
	if (line.y1 < extents->y1)
	    extents->y1 = line.y1;
    }
    else if (line.y2 <= extents->y1)
    {
	extents->y1 = line.y1;
	if (line.y2 > extents->y2)
	    extents->y2 = line.y2;
    }
    else
    {
	if (line.y1 < extents->y1)
	    extents->y1 = line.y1;
	if (line.y2 > extents->y2)
	    extents->y2 = line.y2;

	overlap = TRUE;
    }

    if (line.x1 < extents->x1)
	extents->x1 = line.x1;
    if (line.x2 > extents->x2)
	extents->x2 = line.x2;

    xglPictureClipExtents (pDst, extents);

    return overlap;
}

/* returns 0 if all glyph lists don't have the same format */
static CARD32
xglGlyphListFormatId (GlyphListPtr list,
		      int	   nlist)
{
    CARD32 id = list->format->id;

    nlist--;
    list++;

    while (nlist--)
    {
	if (list->format->id != id)
	    return 0;

	list++;
    }

    return id;
}

void
xglGlyphs (CARD8	 op,
	   PicturePtr	 pSrc,
	   PicturePtr	 pDst,
	   PictFormatPtr maskFormat,
	   INT16	 xSrc,
	   INT16	 ySrc,
	   int		 nlist,
	   GlyphListPtr	 list,
	   GlyphPtr	 *glyphs)
{
    ScreenPtr	  pScreen = pDst->pDrawable->pScreen;
    PicturePtr	  pMask = NULL, pSrcPicture, pDstPicture;
    BoxRec	  extents;
    xglGlyphOpRec glyphOp;
    int		  xDst = list->xOff, yDst = list->yOff;
    int		  overlap;
    int		  target;

    overlap = xglGlyphExtents (pDst, nlist, list, glyphs, &extents);
    if (extents.x2 <= extents.x1 || extents.y2 <= extents.y1)
	return;

    target = xglPrepareTarget (pDst->pDrawable);

    if (op != PictOpAdd && maskFormat &&
	(!target || overlap || op != PictOpOver ||
	 xglGlyphListFormatId (list, nlist) != maskFormat->id))
    {
	PixmapPtr  pPixmap;
	XID	   componentAlpha;
	GCPtr	   pGC;
	xRectangle rect;
	int	   error;

	rect.x = 0;
	rect.y = 0;
	rect.width = extents.x2 - extents.x1;
	rect.height = extents.y2 - extents.y1;

	pPixmap = (*pScreen->CreatePixmap) (pScreen,
					    rect.width, rect.height,
					    maskFormat->depth);
	if (!pPixmap)
	    return;

	componentAlpha = NEEDS_COMPONENT (maskFormat->format);
	pMask = CreatePicture (0, &pPixmap->drawable,
			       maskFormat, CPComponentAlpha, &componentAlpha,
			       serverClient, &error);
	if (!pMask)
	{
	    (*pScreen->DestroyPixmap) (pPixmap);
	    return;
	}

	if (!target)
	{
	    /* make sure we don't do accelerated drawing to mask */
	    xglSetPixmapVisual (pPixmap, NULL);
	}

	ValidatePicture (pMask);
	pGC = GetScratchGC (pPixmap->drawable.depth, pScreen);
	ValidateGC (&pPixmap->drawable, pGC);
	(*pGC->ops->PolyFillRect) (&pPixmap->drawable, pGC, 1, &rect);
	FreeScratchGC (pGC);

	(*pScreen->DestroyPixmap) (pPixmap);

	target = xglPrepareTarget (pMask->pDrawable);

	glyphOp.xOff = -extents.x1;
	glyphOp.yOff = -extents.y1;
	pSrcPicture = NULL;
	pDstPicture = pMask;
    }
    else
    {
	glyphOp.xOff = 0;
	glyphOp.yOff = 0;
	pSrcPicture = pSrc;
	pDstPicture = pDst;
    }

    glyphOp.ppGlyphs = glyphs;
    glyphOp.noCache  = !target;

    while (nlist--)
    {
	glyphOp.xOff   += list->xOff;
	glyphOp.yOff   += list->yOff;
	glyphOp.listLen = list->len;
	glyphOp.nGlyphs = list->len;
	glyphOp.pLists  = list++;

	for (; nlist; nlist--, list++)
	{
	    if (list->format->id != glyphOp.pLists->format->id)
		break;

	    glyphOp.nGlyphs += list->len;
	}

	while (glyphOp.nGlyphs)
	{
	    if (glyphOp.noCache || xglCachedGlyphs (op,
						    pSrcPicture,
						    pDstPicture,
						    xSrc - xDst, ySrc - yDst,
						    &glyphOp))
		xglUncachedGlyphs (op,
				   pSrcPicture,
				   pDstPicture,
				   xSrc - xDst, ySrc - yDst,
				   &glyphOp);
	}
    }

    if (pMask)
    {
	CompositePicture (op, pSrc, pMask, pDst,
			  xSrc + extents.x1 - xDst,
			  ySrc + extents.y1 - yDst,
			  0, 0,
			  extents.x1, extents.y1,
			  extents.x2 - extents.x1,
			  extents.y2 - extents.y1);

	FreePicture ((pointer) pMask, (XID) 0);
    }
}

#endif
