/*****************************************************************************
 * VIA Unichrome XvMC extension client lib.
 *
 * Copyright (c) 2004-2005 Thomas Hellström. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHOR(S) OR COPYRIGHT HOLDER(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */


/*
 *Author: Thomas Hellström, 2004.
 *Bugfixes by among others Pascal Brisset and Terry Barnaby.
 *DRI protocol support by Thomas Hellström, 2005.
 */

#undef WAITPAUSE

#include "viaXvMCPriv.h"
#include "viaLowLevel.h"
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <time.h>
#include <fourcc.h>
#include <X11/extensions/Xv.h>
#include <xf86drm.h>
#include <pthread.h>
#include <X11/extensions/vldXvMC.h>
#include "xf86dri.h"
#include "driDrawable.h"
    
#define SAREAPTR(ctx) ((ViaXvMCSAreaPriv *)			\
		       (((CARD8 *)(ctx)->sAreaAddress) +	\
			(ctx)->sAreaPrivOffset))



static int error_base;
static int event_base;
static unsigned numContexts = 0;
static int globalFD;
static drmAddress sAreaAddress;
static drmAddress fbAddress;
static drmAddress mmioAddress;


#define FOURCC_XVMC (('C' << 24) + ('M' << 16) + ('V' << 8) + 'X')

#define ppthread_mutex_lock(arg)		\
  {						\
    pthread_mutex_lock(arg);			\
  }						\

#define ppthread_mutex_unlock(arg)		\
  {						\
    pthread_mutex_unlock(arg);			\
  }						\

static unsigned yOffs (ViaXvMCSurface *srf) 
{
    return srf->offsets[0];
}

static unsigned vOffs (ViaXvMCSurface *srf) 
{
    return srf->offsets[0] + srf->yStride * srf->height;
}

static unsigned uOffs (ViaXvMCSurface *srf) 
{
    return srf->offsets[0] + ( srf->yStride * srf->height) + 
	(srf->yStride >> 1) * (srf->height >> 1);
}


static void defaultQMatrices(ViaXvMCContext *ctx) 
{
    int i;

    static const char intra[64] = {
	8, 16, 19, 22, 26, 27, 29, 34, 16, 16, 22, 24, 27, 29, 34, 37,
        19, 22, 26, 27, 29, 34, 34, 38, 22, 22, 26, 27, 29, 34, 37, 40,
        22, 26, 27, 29, 32, 35, 40, 48, 26, 27, 29, 32, 35, 40, 48, 58,
        26, 27, 29, 34, 38, 46, 56, 69, 27, 29, 35, 38, 46, 56, 69, 83
    };
    
    for( i=0; i<64; ++i) {
	ctx->intra_quantiser_matrix[i] = intra[i];
	ctx->non_intra_quantiser_matrix[i] = 16;
    }
    ctx->intraLoaded = 0;
    ctx->nonIntraLoaded = 0;
}


static void releaseDecoder(ViaXvMCContext *ctx,int clearCtx) 
{
    volatile ViaXvMCSAreaPriv *sAPriv;

    sAPriv = SAREAPTR(ctx);
    UNICHROME_UNLOCK(ctx->fd, UNICHROME_LOCK_DECODER1, sAPriv, ctx->drmcontext);
}


static int grabDecoder(	ViaXvMCContext *ctx, int *hadLastLock) 
{
    volatile ViaXvMCSAreaPriv *sAPriv =  SAREAPTR(ctx);
    int retFtx, lc;

    /*
     * Try to grab the decoder. If it is not available we will sleep until
     * it becomes available or for a maximum of 20 ms. 
     * Then try to grab it again, unless a timeout occured. If the decoder is
     * available, the lock should be reasonably fast.
     */

    if (ctx->haveDecoder) {
        flushXvMCLowLevel(ctx->xl);  /* Ignore errors here. */
	
	/*fprintf(stderr,"ViaXvMC: ERROR: Trying to re-lock decoder.\n"); */
	*hadLastLock = 1;
        return 0;
    }
    UNICHROME_LOCK(ctx->fd, UNICHROME_LOCK_DECODER1, sAPriv, ctx->drmcontext, lc,
		   retFtx);
    *hadLastLock = (ctx->drmcontext == lc);

    return retFtx;
}
	    
static void setupAttribDesc(Display *display, XvPortID port,
			    const ViaXvMCAttrHolder *attrib,
			    XvAttribute attribDesc[]) 
{
    XvAttribute *XvAttribs,*curAD;
    int num;
    unsigned i,j;

    XLockDisplay(display);
    XvAttribs = XvQueryPortAttributes(display, port, &num);
    for(i=0; i<attrib->numAttr; ++i) {
	curAD = attribDesc + i;
	curAD->flags = 0;
	curAD->min_value = 0;
	curAD->max_value = 0;
	curAD->name = NULL;
	for(j=0; j<num; ++j) {
	    if (attrib->attributes[i].attribute == 
		XInternAtom(display,XvAttribs[j].name,TRUE)) {
		*curAD = XvAttribs[j];
		curAD->name = strdup(XvAttribs[j].name);
		break;
	    }
	}
    }
    if (XvAttribs) XFree(XvAttribs);
    XUnlockDisplay(display);

}

static void releaseAttribDesc(int numAttr, XvAttribute attribDesc[]) 
{
    int i;

    for (i=0; i<numAttr; ++i) {
	if (attribDesc[i].name)
	    free(attribDesc[i].name);
    }
}
    
static Status releaseContextResources(Display *display, XvMCContext *context,
				      int freePrivate, Status errType)
{
    ViaXvMCContext *pViaXvMC = (ViaXvMCContext *) context->privData;

    switch(pViaXvMC->resources) {
    case context_drawHash:
        driDestroyHashContents( pViaXvMC->drawHash );
	drmHashDestroy( pViaXvMC->drawHash );
    case context_lowLevel:
	closeXvMCLowLevel(pViaXvMC->xl);
    case context_mutex:
	pthread_mutex_destroy(&pViaXvMC->ctxMutex);
    case context_drmContext:
	XLockDisplay(display);
	uniDRIDestroyContext(display, pViaXvMC->screen, pViaXvMC->id);
	XUnlockDisplay(display);
    case context_sAreaMap:
	numContexts--;
	if (numContexts == 0)
	    drmUnmap(pViaXvMC->sAreaAddress,pViaXvMC->sAreaSize);
    case context_fbMap:
	if (numContexts == 0)
	    drmUnmap(pViaXvMC->fbAddress,pViaXvMC->fbSize);
    case context_mmioMap:
	if (numContexts == 0)
	    drmUnmap(pViaXvMC->mmioAddress,pViaXvMC->mmioSize);
    case context_fd:
	if (numContexts == 0) {
	    if (pViaXvMC->fd >= 0)
		drmClose(pViaXvMC->fd);
	}
	pViaXvMC->fd = -1;
    case context_driConnection:
	if (numContexts == 0) {
	    XLockDisplay(display);
	    uniDRICloseConnection(display, pViaXvMC->screen);
	    XUnlockDisplay(display);
	}
    case context_context:
	XLockDisplay(display);
	_xvmc_destroy_context(display, context);
	XUnlockDisplay(display);
	if (!freePrivate) break;
    default:
	free(pViaXvMC);
    }
    return errType;
}

Status XvMCCreateContext(Display *display, XvPortID port,
			 int surface_type_id, int width, int height, int flags,
			 XvMCContext *context) 
{  
    ViaXvMCContext *pViaXvMC;
    int priv_count;
    uint *priv_data;
    uint magic;
    unsigned i;
    Status ret;
    int major, minor;
    ViaXvMCCreateContextRec *tmpComm;
    drmVersionPtr drmVer;
    char *curBusID;
    int isCapable;

    /* 
     * Verify Obvious things first 
     */

    if(context == NULL) {
	return XvMCBadContext;
    }

    if(!(flags & XVMC_DIRECT)) {
	fprintf(stderr,"Indirect Rendering not supported! Using Direct.\n");
    }

    /* 
     *FIXME: Check $DISPLAY for legal values here 
     */

    context->surface_type_id = surface_type_id;
    context->width = (unsigned short)((width + 15) & ~15);
    context->height = (unsigned short)((height + 15) & ~15);
    context->flags = flags;
    context->port = port;

    /* 
     *  Width, Height, and flags are checked against surface_type_id
     *  and port for validity inside the X server, no need to check
     *  here.
     */

    /* Allocate private Context data */
    context->privData = (void *)malloc(sizeof(ViaXvMCContext));
    if(!context->privData) {
	fprintf(stderr,"Unable to allocate resources for XvMC context.\n");
	return BadAlloc;
    }
  
    pViaXvMC = (ViaXvMCContext *)context->privData;
    pViaXvMC->resources = context_none;

    /* Verify the XvMC extension exists */

    XLockDisplay(display);
    if(! XvMCQueryExtension(display, &event_base,
			    &error_base)) {
	fprintf(stderr,"XvMC Extension is not available!\n");
	free(pViaXvMC);
	XUnlockDisplay(display);
	return BadAlloc;
    }

    /* Verify XvMC version */
    ret = XvMCQueryVersion(display, &major, &minor);
    if(ret) {
	fprintf(stderr,"XvMCQuery Version Failed, unable to determine "
		"protocol version!\n");
    }
    XUnlockDisplay(display);

    /* FIXME: Check Major and Minor here */

    XLockDisplay(display);
    if((ret = _xvmc_create_context(display, context, &priv_count, 
				       &priv_data))) {
      XUnlockDisplay(display);
      fprintf(stderr,"Unable to create XvMC Context.\n");
      return releaseContextResources(display, context, 1, BadAlloc);
    }
    XUnlockDisplay(display);

    /*
     * Check size and version of returned data.
     */

    tmpComm = (  ViaXvMCCreateContextRec *) priv_data;
    if(priv_count != (sizeof(ViaXvMCCreateContextRec) >> 2)) {
	fprintf(stderr,"_xvmc_create_context() returned incorrect "
		"data size!\n");
	fprintf(stderr,"\tExpected %d, got %d\n",
		(int) (sizeof(ViaXvMCCreateContextRec) >> 2),
		(int) priv_count);
	XFree(priv_data);
	return releaseContextResources(display, context, 1, BadAlloc);
    }
    pViaXvMC->resources = context_context;

    if ((tmpComm->major != VIAXVMC_MAJOR) ||
	(tmpComm->minor != VIAXVMC_MINOR)) {
	fprintf(stderr,"Version mismatch between the X via driver\n"
		"and the XvMC library. Cannot continue!\n");
	XFree(priv_data);
	return releaseContextResources(display, context, 1, BadAlloc);
    }

    pViaXvMC->ctxNo = tmpComm->ctxNo;
    pViaXvMC->fbOffset = tmpComm->fbOffset;
    pViaXvMC->fbSize = tmpComm->fbSize;
    pViaXvMC->mmioOffset = tmpComm->mmioOffset;
    pViaXvMC->mmioSize = tmpComm->mmioSize;
    pViaXvMC->sAreaSize = tmpComm->sAreaSize;
    pViaXvMC->sAreaPrivOffset = tmpComm->sAreaPrivOffset;
    pViaXvMC->decoderOn = 0;
    pViaXvMC->xvMCPort = tmpComm->xvmc_port;
    pViaXvMC->useAGP = tmpComm->useAGP;
    pViaXvMC->attrib = tmpComm->initAttrs;
    pViaXvMC->screen = tmpComm->screen;
    pViaXvMC->depth = tmpComm->depth;
    pViaXvMC->stride = tmpComm->stride;
    pViaXvMC->chipId = tmpComm->chipId;

    /* 
     * Must free the private data we were passed from X 
     */

    XFree(priv_data);

    /*
     * Check for direct rendering capable, establish DRI and DRM connections,
     * map framebuffer, DRI shared area and read-only register areas. 
     * Initial checking for drm has already been done by the server. 
     * Only do this for the first context we create.
     */ 

    if (numContexts == 0) {
	XLockDisplay(display);
	ret = uniDRIQueryDirectRenderingCapable(display, pViaXvMC->screen, &isCapable);
	if (!ret || !isCapable) {
	    XUnlockDisplay(display);
	    fprintf(stderr,"Direct Rendering is not available on this system!\n");
	    return releaseContextResources(display, context, 1, BadAlloc);
	}

	if (!uniDRIOpenConnection(display, pViaXvMC->screen, &pViaXvMC->sAreaOffset,
				   &curBusID)) {
	    XUnlockDisplay(display);
	    fprintf(stderr,"Could not open DRI connection to X server!\n");
	    return releaseContextResources(display, context, 1, BadAlloc);
	}	
	XUnlockDisplay(display);

	strncpy(pViaXvMC->busIdString,curBusID,20);
	pViaXvMC->busIdString[20] = '\0';    
	XFree(curBusID);

	pViaXvMC->resources = context_driConnection;    

	if((pViaXvMC->fd = drmOpen("via",pViaXvMC->busIdString)) < 0) {
	    fprintf(stderr,"DRM Device for via could not be opened.\n");
	    return releaseContextResources(display, context, 1, BadAlloc);
	}
	globalFD = pViaXvMC->fd;
	pViaXvMC->resources = context_fd;

	if (NULL == (drmVer = drmGetVersion(pViaXvMC->fd))) {
	    fprintf(stderr, 
		    "viaXvMC: Could not get drm version.");
	    return releaseContextResources(display, context, 1, BadAlloc);
	}
	if (((drmVer->version_major != 2 ) || (drmVer->version_minor < 0))) {
	    fprintf(stderr, 
		    "viaXvMC: Kernel drm is not compatible with XvMC.\n"); 
	    fprintf(stderr, 
		    "viaXvMC: Kernel drm version: %d.%d.%d "
		    "and I need at least version 2.0.0.\n"
		    "Please update.\n",
		    drmVer->version_major,drmVer->version_minor,
		    drmVer->version_patchlevel); 
	    drmFreeVersion(drmVer);
	    return releaseContextResources(display, context, 1, BadAlloc);
	} 
	drmFreeVersion(drmVer);
	drmGetMagic(pViaXvMC->fd,&magic);
	
	XLockDisplay(display);
	if (!uniDRIAuthConnection(display, pViaXvMC->screen, magic)) {
	    XUnlockDisplay(display);
	    fprintf(stderr, "viaXvMC: X server did not allow DRI. Check permissions.\n");
	    XFree(priv_data);
	    return releaseContextResources(display, context, 1, BadAlloc);
	}
	XUnlockDisplay(display);

	/* 
	 * Map the register memory 
	 */

	if(drmMap(pViaXvMC->fd,pViaXvMC->mmioOffset,
		  pViaXvMC->mmioSize,&mmioAddress) < 0) {
	    fprintf(stderr,"Unable to map the display chip mmio registers.\n");
	    return releaseContextResources(display, context, 1, BadAlloc);
	}   
	pViaXvMC->mmioAddress = mmioAddress;
	pViaXvMC->resources = context_mmioMap;

	/* 
	 * Map Framebuffer memory 
	 */

	if(drmMap(pViaXvMC->fd,pViaXvMC->fbOffset,
		  pViaXvMC->fbSize,&fbAddress) < 0) {
	    fprintf(stderr,"Unable to map XvMC Framebuffer.\n");
	    return releaseContextResources(display, context, 1, BadAlloc);
	} 
	pViaXvMC->fbAddress = fbAddress;
	pViaXvMC->resources = context_fbMap;


	/*
	 * Map DRI Sarea.
	 */

	if(drmMap(pViaXvMC->fd,pViaXvMC->sAreaOffset,
		  pViaXvMC->sAreaSize,&sAreaAddress) < 0) {
	    fprintf(stderr,"Unable to map DRI SAREA.\n");
	    return releaseContextResources(display, context, 1, BadAlloc);
	} 
    } else {
	pViaXvMC->fd = globalFD;
	pViaXvMC->mmioAddress = mmioAddress;
	pViaXvMC->fbAddress = fbAddress;
    }

    pViaXvMC->sAreaAddress = sAreaAddress;
    pViaXvMC->resources = context_sAreaMap;
    numContexts++;

    /*
     * Find a matching visual. Important only for direct drawing to the visible
     * frame-buffer.
     */

    XLockDisplay(display);
    ret = XMatchVisualInfo(display, pViaXvMC->screen, 
			   (pViaXvMC->depth == 32) ? 24 : pViaXvMC->depth, TrueColor, 
			   &pViaXvMC->visualInfo);
    XUnlockDisplay(display);
    if (!ret) {
	fprintf(stderr, "viaXvMC: Could not find a matching TrueColor visual.\n");
	return releaseContextResources(display, context, 1, BadAlloc);
    }	

    if (!uniDRICreateContext(display, pViaXvMC->screen, pViaXvMC->visualInfo.visual, 
			      &pViaXvMC->id, &pViaXvMC->drmcontext)) {
	
	fprintf(stderr, "viaXvMC: Could not create DRI context.\n");
	return releaseContextResources(display, context, 1, BadAlloc);
    }

    pViaXvMC->resources = context_drmContext;

    for (i=0; i<VIA_MAX_RENDSURF; ++i) {
	pViaXvMC->rendSurf[i] = 0;
    }
    pViaXvMC->lastSrfDisplaying = ~0;
    setupAttribDesc(display, port, &pViaXvMC->attrib, pViaXvMC->attribDesc);

    pViaXvMC->hwLock = (drmLockPtr) pViaXvMC->sAreaAddress;
    defaultQMatrices(pViaXvMC);
    pViaXvMC->chromaIntraLoaded = 1;
    pViaXvMC->chromaNonIntraLoaded = 1;
    pViaXvMC->yStride = (width + 31) & ~31;
    pViaXvMC->haveDecoder = 0;
    pViaXvMC->attribChanged = 1;
    pViaXvMC->haveXv = 0;
    pViaXvMC->port = context->port;
    pthread_mutex_init(&pViaXvMC->ctxMutex,NULL);
    pViaXvMC->resources = context_mutex;
    pViaXvMC->timeStamp = 0;
    setRegion(0,0,-1,-1,pViaXvMC->sRegion);
    setRegion(0,0,-1,-1,pViaXvMC->dRegion);

    if (NULL == (pViaXvMC->xl = 
		 initXvMCLowLevel(pViaXvMC->fd, &pViaXvMC->drmcontext,
				  pViaXvMC->hwLock, pViaXvMC->mmioAddress, 
				  pViaXvMC->fbAddress, pViaXvMC->stride, pViaXvMC->depth,
				  context->width, context->height,
				  pViaXvMC->useAGP, pViaXvMC->chipId))) {

	fprintf(stderr,"ViaXvMC: Could not allocate timestamp blit area.\n");
	return releaseContextResources(display, context, 1, BadAlloc);
    }       
    pViaXvMC->resources = context_lowLevel;
    setAGPSyncLowLevel(pViaXvMC->xl, 1, 0);
    
    if (NULL == (pViaXvMC->drawHash = drmHashCreate())) {
	fprintf(stderr,"ViaXvMC: Could not allocate drawable hash table.\n");
	return releaseContextResources(display, context, 1, BadAlloc);
    }   
    pViaXvMC->resources = context_drawHash;


    if (numContexts == 1) {
	hwlLock(pViaXvMC->xl,1); 
	setLowLevelLocking(pViaXvMC->xl,0);
	viaVideoSubPictureOffLocked(pViaXvMC->xl);
	flushXvMCLowLevel(pViaXvMC->xl);  
	setLowLevelLocking(pViaXvMC->xl,1);
	hwlUnlock(pViaXvMC->xl,1);
    }

    return Success;
}


Status XvMCDestroyContext(Display *display, XvMCContext *context) 
{
    ViaXvMCContext *pViaXvMC;


    if(context == NULL) {
	return (error_base + XvMCBadContext);
    }
    if(NULL == (pViaXvMC = context->privData)) {
	return (error_base + XvMCBadContext);
    }

    /*
     * Release decoder if we have it. In case of crash or termination
     * before XvMCDestroyContext, the X server will take care of this.
     */

    releaseAttribDesc(pViaXvMC->attrib.numAttr,pViaXvMC->attribDesc);
    releaseDecoder(pViaXvMC,1);
    return releaseContextResources(display, context, 1, Success);
}

Status XvMCCreateSurface( Display *display, XvMCContext *context,
			  XvMCSurface *surface) 
{
    ViaXvMCContext *pViaXvMC;
    ViaXvMCSurface *pViaSurface;
    int priv_count;
    unsigned *priv_data;
    unsigned i;
    Status ret;

    if((surface == NULL) || (context == NULL) || (display == NULL)){
	return BadValue;
    }
  
    pViaXvMC = (ViaXvMCContext *)context->privData;
    ppthread_mutex_lock( &pViaXvMC->ctxMutex );

    if(pViaXvMC == NULL) {
        ppthread_mutex_unlock( &pViaXvMC->ctxMutex );
	return (error_base + XvMCBadContext);
    }

    pViaSurface = surface->privData = (ViaXvMCSurface *)malloc(sizeof(ViaXvMCSurface));
    if(!surface->privData) {
        ppthread_mutex_unlock( &pViaXvMC->ctxMutex );
	return BadAlloc;
    }
    XLockDisplay(display);
    if((ret = _xvmc_create_surface(display, context, surface,
				   &priv_count, &priv_data))) {
	XUnlockDisplay(display);
	free(pViaSurface);
	fprintf(stderr,"Unable to create XvMC Surface.\n");
	ppthread_mutex_unlock( &pViaXvMC->ctxMutex );
	return ret;
    }
    XUnlockDisplay(display);

    pViaSurface->srfNo = priv_data[0];

    /*
     * Store framebuffer offsets to the buffers allocated for this surface.
     * For some chipset revisions, surfaces may be double-buffered.
     */

    pViaSurface->numBuffers = priv_data[1];
    for (i=0; i < pViaSurface->numBuffers; ++i) {
	pViaSurface->offsets[i] = priv_data[i+2];
    }
    pViaSurface->curBuf = 0;


    /* Free data returned from xvmc_create_surface */

    XFree(priv_data);

    pViaSurface->width = context->width;
    pViaSurface->height = context->height;
    pViaSurface->yStride = pViaXvMC->yStride;
    pViaSurface->privContext = pViaXvMC;
    pViaSurface->privSubPic = NULL;
    pViaSurface->needsSync = 0;
    ppthread_mutex_unlock( &pViaXvMC->ctxMutex );
    return Success;
}

Status XvMCDestroySurface(Display *display, XvMCSurface *surface) 
{
    ViaXvMCSurface *pViaSurface;

    if((display == NULL) || (surface == NULL)) {
	return BadValue;
    }
    if(surface->privData == NULL) {
	return (error_base + XvMCBadSurface);
    }

    pViaSurface = (ViaXvMCSurface *)surface->privData;

    XLockDisplay(display);
    _xvmc_destroy_surface(display,surface);
    XUnlockDisplay(display);

    free(pViaSurface);
    surface->privData = NULL;
    return Success;
}

Status XvMCPutSlice2(Display *display,XvMCContext *context, char *slice,
		     int nBytes, int sliceCode) 
{
    ViaXvMCContext *pViaXvMC;
    CARD32 sCode = 0x00010000 | (sliceCode & 0xFF) << 24;

    if((display == NULL) || (context == NULL)) {
	return BadValue;
    }
    if(NULL == (pViaXvMC = context->privData)) {
	return (error_base + XvMCBadContext);
    }
    ppthread_mutex_lock( &pViaXvMC->ctxMutex );
    if (!pViaXvMC->haveDecoder) {
	fprintf(stderr,"XvMCPutSlice: This context does not own decoder!\n");
	ppthread_mutex_unlock( &pViaXvMC->ctxMutex );
	return BadAlloc;
    }

    viaMpegWriteSlice(pViaXvMC->xl, (CARD8 *)slice, nBytes, sCode);

    flushPCIXvMCLowLevel(pViaXvMC->xl); 
    ppthread_mutex_unlock( &pViaXvMC->ctxMutex );
    return Success;
}

Status XvMCPutSlice(Display *display,XvMCContext *context, char *slice,
		    int nBytes) 
{
    ViaXvMCContext *pViaXvMC;

    if((display == NULL) || (context == NULL)) {
	return BadValue;
    }
    if(NULL == (pViaXvMC = context->privData)) {
	return (error_base + XvMCBadContext);
    }
    ppthread_mutex_lock( &pViaXvMC->ctxMutex );

    if (!pViaXvMC->haveDecoder) {
	fprintf(stderr,"XvMCPutSlice: This context does not own decoder!\n");
	ppthread_mutex_unlock( &pViaXvMC->ctxMutex );
	return BadAlloc;
    }

    viaMpegWriteSlice(pViaXvMC->xl, (CARD8 *)slice, nBytes, 0);
    flushPCIXvMCLowLevel(pViaXvMC->xl);
    ppthread_mutex_unlock( &pViaXvMC->ctxMutex );
    return Success;
}


static Status updateXVOverlay(Display *display,ViaXvMCContext *pViaXvMC, 
			      ViaXvMCSurface *pViaSurface, Drawable draw,
			      short srcx, short srcy, unsigned short srcw, 
			      unsigned short srch,short destx,short desty,
			      unsigned short destw,unsigned short desth) 
{
    ViaXvMCCommandBuffer buf;
    ViaXvMCSubPicture *pViaSubPic;
    Status ret;

    if (!pViaXvMC->haveXv) {
	pViaXvMC->xvImage = 
	    XvCreateImage(display,pViaXvMC->port,FOURCC_XVMC,
			  (char *)&buf,pViaSurface->width,
			  pViaSurface->height);
	pViaXvMC->gc = XCreateGC(display,draw,0,0);
	pViaXvMC->haveXv = 1;
    }    
    pViaXvMC->draw = draw;
    pViaXvMC->xvImage->data = (char *)&buf;
    
    buf.command = (pViaXvMC->attribChanged) ? 
	VIA_XVMC_COMMAND_FDISPLAY : VIA_XVMC_COMMAND_DISPLAY;
    buf.ctxNo = pViaXvMC->ctxNo | VIA_XVMC_VALID;
    buf.srfNo = pViaSurface->srfNo | VIA_XVMC_VALID;
    pViaSubPic = pViaSurface->privSubPic;
    buf.subPicNo = ((NULL == pViaSubPic) ? 0 : pViaSubPic->srfNo  ) 
	| VIA_XVMC_VALID;
    buf.attrib = pViaXvMC->attrib;
    
    XLockDisplay(display);

    if ((ret = XvPutImage(display,pViaXvMC->port,draw,pViaXvMC->gc,
			  pViaXvMC->xvImage,srcx,srcy,srcw,srch,
			  destx,desty,destw,desth))) {
	XUnlockDisplay(display);
	return ret;
    }
    XSync(display, 0);
    XUnlockDisplay(display);
    pViaXvMC->attribChanged = 0;
    return Success;
}


Status XvMCPutSurface(Display *display,XvMCSurface *surface,Drawable draw,
		      short srcx, short srcy, unsigned short srcw, 
		      unsigned short srch,short destx,short desty,
		      unsigned short destw,unsigned short desth, int flags) 
{
    /*
     * This function contains some hairy locking logic. What we really want to
     * do is to flip the picture ASAP, to get a low latency and smooth playback.
     * However, if somebody else used the overlay since we used it last or if it is
     * our first time, we'll have to call X to update the overlay first. Otherwise 
     * we'll do the overlay update once we've flipped. Since we release the hardware
     * lock when we call X, X needs to verify using the SAREA that nobody else flipped
     * in a picture between the lock release and the X server control. Similarly
     * when the overlay update returns, we have to make sure that we still own the
     * overlay.
     */

    ViaXvMCSurface *pViaSurface;
    ViaXvMCContext *pViaXvMC;
    ViaXvMCSubPicture *pViaSubPic;
    volatile ViaXvMCSAreaPriv *sAPriv;
    Status ret;
    unsigned dispSurface, lastSurface;
    int overlayUpdated;
    drawableInfo *drawInfo;
    XvMCRegion sReg, dReg;
    Bool forceUpdate = FALSE;

    if((display == NULL) || (surface == NULL)) {
	return BadValue;
    }
    if(NULL == (pViaSurface = surface->privData )) {
	return (error_base + XvMCBadSurface);
    }
    if (NULL == (pViaXvMC = pViaSurface->privContext)) {
	return (error_base + XvMCBadContext);
    }

    ppthread_mutex_lock( &pViaXvMC->ctxMutex );
    pViaSubPic = pViaSurface->privSubPic;
    sAPriv = SAREAPTR( pViaXvMC );

    setRegion(srcx, srcy, srcw, srch, sReg);
    setRegion(destx, desty, destw, desth, dReg);
    

    if ((!regionEqual(sReg, pViaXvMC->sRegion)) ||
	(!regionEqual(dReg, pViaXvMC->dRegion))) {
    
	/*
	 * Force update of the video overlay to match the new format.
	 */

	pViaXvMC->sRegion = sReg;
	pViaXvMC->dRegion = dReg;
	forceUpdate = TRUE;
    }
    

    hwlLock(pViaXvMC->xl,1);
    
    if (getDRIDrawableInfoLocked(pViaXvMC->drawHash, display, pViaXvMC->screen, draw, 0,
				 pViaXvMC->fd, pViaXvMC->drmcontext, pViaXvMC->sAreaAddress, 
				 FALSE, &drawInfo, sizeof(*drawInfo))) {

	hwlUnlock(pViaXvMC->xl,1);
	ppthread_mutex_unlock( &pViaXvMC->ctxMutex );
	return BadAccess;
    }

    setLowLevelLocking(pViaXvMC->xl,0);
	

    /*
     * Put a surface ID in the SAREA to "authenticate" to the 
     * X server.
     */

    dispSurface = sAPriv->XvMCDisplaying[pViaXvMC->xvMCPort];
    lastSurface = pViaXvMC->lastSrfDisplaying;
    sAPriv->XvMCDisplaying[pViaXvMC->xvMCPort] = 
	pViaXvMC->lastSrfDisplaying = pViaSurface->srfNo | VIA_XVMC_VALID;
    overlayUpdated = 0;

    viaVideoSetSWFLipLocked(pViaXvMC->xl, yOffs(pViaSurface), uOffs(pViaSurface), 
			    vOffs(pViaSurface), pViaSurface->yStride, pViaSurface->yStride >> 1);

    while ((lastSurface != dispSurface) || forceUpdate) {

	forceUpdate = FALSE;
	flushPCIXvMCLowLevel(pViaXvMC->xl);
	setLowLevelLocking(pViaXvMC->xl,1);
	hwlUnlock(pViaXvMC->xl,1);

	/*
	 * We weren't the last to display. Update the overlay before flipping.
	 */

	ret = updateXVOverlay(display,pViaXvMC,pViaSurface,draw,srcx,srcy,srcw, 
			      srch,destx,desty,destw,desth);
	if (ret) {
	    ppthread_mutex_unlock( &pViaXvMC->ctxMutex );
	    return ret;
	}

	hwlLock(pViaXvMC->xl,1);

	if (getDRIDrawableInfoLocked(pViaXvMC->drawHash, display, pViaXvMC->screen, draw, 0,
				     pViaXvMC->fd, pViaXvMC->drmcontext, pViaXvMC->sAreaAddress, 
				     FALSE, &drawInfo, sizeof(*drawInfo))) {
	    
	    hwlUnlock(pViaXvMC->xl,1);
	    ppthread_mutex_unlock( &pViaXvMC->ctxMutex );
	    return BadAccess;
	}
	
	setLowLevelLocking(pViaXvMC->xl,0);
	lastSurface = pViaSurface->srfNo | VIA_XVMC_VALID;
	dispSurface = sAPriv->XvMCDisplaying[pViaXvMC->xvMCPort];
	overlayUpdated = 1;
    } 


    /*
     * Subpictures
     */

    if (NULL != pViaSubPic) {
	if (sAPriv->XvMCSubPicOn[pViaXvMC->xvMCPort] 
	    != (pViaSubPic->srfNo | VIA_XVMC_VALID)) {
	    sAPriv->XvMCSubPicOn[pViaXvMC->xvMCPort] = 
		pViaSubPic->srfNo | VIA_XVMC_VALID; 
	    viaVideoSubPictureLocked(pViaXvMC->xl, pViaSubPic);
	}  
    } else {
	if (sAPriv->XvMCSubPicOn[pViaXvMC->xvMCPort] & VIA_XVMC_VALID) {
	    viaVideoSubPictureOffLocked(pViaXvMC->xl);
	    sAPriv->XvMCSubPicOn[pViaXvMC->xvMCPort] &= ~VIA_XVMC_VALID;
	}  
    }

    /*
     * Flip
     */

    viaVideoSWFlipLocked(pViaXvMC->xl, flags, pViaSurface->progressiveSequence);
    flushXvMCLowLevel(pViaXvMC->xl);  

    setLowLevelLocking(pViaXvMC->xl,1);
    hwlUnlock(pViaXvMC->xl,1);

    if (overlayUpdated || !drawInfo->touched ) {
        ppthread_mutex_unlock( &pViaXvMC->ctxMutex );
	return Success;
    }
    
    /*
     * Update overlay
     */

    ret = updateXVOverlay(display,pViaXvMC,pViaSurface,draw,srcx,srcy,srcw, 
			  srch,destx,desty,destw,desth);
    ppthread_mutex_unlock( &pViaXvMC->ctxMutex );
    return ret;

}

Status XvMCBeginSurface(Display *display,
			XvMCContext *context,
			XvMCSurface *target_surface,
			XvMCSurface *past_surface,
			XvMCSurface *future_surface,
			const XvMCMpegControl *control) 
{
    ViaXvMCSurface *targS,*futS,*pastS;
    ViaXvMCContext *pViaXvMC;
    int hadDecoderLast;
    CARD32 timeStamp;

    if((display == NULL) || (context == NULL) || (target_surface == NULL)) {
	return BadValue;
    }
     
    pViaXvMC = context->privData;     

    ppthread_mutex_lock( &pViaXvMC->ctxMutex );
    if (grabDecoder(pViaXvMC, &hadDecoderLast)) {
      ppthread_mutex_unlock( &pViaXvMC->ctxMutex );
      return BadAlloc;
    }
    pViaXvMC->haveDecoder = 1;
 
    /*
     * We need to wait for decoder idle at next flush, since hardware doesn't queue 
     * beginsurface requests until the decoder is idle. This is  
     * done by waiting on the last previous timestamp, or if there was another context
     * having the decoder before us, by emitting a new one.
     */

    if (pViaXvMC->useAGP) {
        if (!hadDecoderLast || pViaXvMC->timeStamp == 0) {
	    timeStamp = viaDMATimeStampLowLevel(pViaXvMC->xl);
	    if (flushXvMCLowLevel(pViaXvMC->xl)) {
	        releaseDecoder(pViaXvMC, 0);
		ppthread_mutex_unlock( &pViaXvMC->ctxMutex );
		return BadAlloc;
	    } 	      
	    pViaXvMC->timeStamp = timeStamp;
	} else {
	    timeStamp = pViaXvMC->timeStamp;
        }
	setAGPSyncLowLevel(pViaXvMC->xl, 1, timeStamp);
    }

    if (!hadDecoderLast || !pViaXvMC->decoderOn) {
	pViaXvMC->intraLoaded = 0;
	pViaXvMC->nonIntraLoaded = 0;
    }

    viaMpegReset(pViaXvMC->xl);

    targS = (ViaXvMCSurface *)target_surface->privData;
    futS = NULL;
    pastS = NULL;

 
    pViaXvMC->rendSurf[0] = targS->srfNo | VIA_XVMC_VALID;
    if (future_surface) {
	futS = (ViaXvMCSurface *)future_surface->privData;
	futS->needsSync = 0;
    }
    if (past_surface) {	
	pastS = (ViaXvMCSurface *)past_surface->privData;
	pastS->needsSync = 0;
    }


    targS->progressiveSequence = (control->flags & XVMC_PROGRESSIVE_SEQUENCE);
    targS->topFieldFirst = (control->flags & XVMC_TOP_FIELD_FIRST);
    targS->privSubPic = NULL;
    
    viaMpegSetSurfaceStride(pViaXvMC->xl,pViaXvMC);

    viaMpegSetFB(pViaXvMC->xl,0,yOffs(targS),uOffs(targS),vOffs(targS));
    if (past_surface) {
	viaMpegSetFB(pViaXvMC->xl,1,yOffs(pastS),uOffs(pastS),vOffs(pastS));
    } else {
	viaMpegSetFB(pViaXvMC->xl,1,0,0,0);
    }
    
    if (future_surface) {
	viaMpegSetFB(pViaXvMC->xl,2,yOffs(futS),uOffs(futS),vOffs(futS));
    } else {
	viaMpegSetFB(pViaXvMC->xl,2,0,0,0);
    }

    viaMpegBeginPicture(pViaXvMC->xl,pViaXvMC,context->width,context->height,control);
    flushPCIXvMCLowLevel(pViaXvMC->xl);
    targS->needsSync = 1;
    targS->syncMode = LL_MODE_DECODER_IDLE;
    pViaXvMC->decoderOn = 1;
    ppthread_mutex_unlock( &pViaXvMC->ctxMutex );
    return Success;
}
    
  
Status XvMCSyncSurface(Display *display,XvMCSurface *surface)
{
    ViaXvMCSurface *pViaSurface;
    ViaXvMCContext *pViaXvMC;
    unsigned i;

    if((display == NULL) || (surface == NULL)) {
	return BadValue;
    }
    if(surface->privData == NULL) {
	return (error_base + XvMCBadSurface);
    }
    
    pViaSurface = (ViaXvMCSurface *)surface->privData;
    pViaXvMC = pViaSurface->privContext;

    if(pViaXvMC == NULL) {
	return (error_base + XvMCBadSurface);
    }

    ppthread_mutex_lock( &pViaXvMC->ctxMutex );

    if (pViaSurface->needsSync) {
        CARD32 timeStamp = pViaSurface->timeStamp;
	int syncMode = pViaSurface->syncMode;

	if (pViaXvMC->useAGP) {

	    syncMode = (pViaSurface->syncMode == LL_MODE_2D || 
			pViaSurface->timeStamp < pViaXvMC->timeStamp) ? 
		LL_MODE_2D : LL_MODE_DECODER_IDLE;
	    if (pViaSurface->syncMode != LL_MODE_2D) 
	        timeStamp = pViaXvMC->timeStamp;

	} else if (syncMode != LL_MODE_2D && 
		   pViaXvMC->rendSurf[0] != (pViaSurface->srfNo | VIA_XVMC_VALID)) {

	  pViaSurface->needsSync = 0;
	  ppthread_mutex_unlock( &pViaXvMC->ctxMutex );
	  return Success;
	}

	if (syncXvMCLowLevel(pViaXvMC->xl, syncMode, 1, 
			     pViaSurface->timeStamp)) {
	    ppthread_mutex_unlock( &pViaXvMC->ctxMutex );
	    return BadValue;
	}
	pViaSurface->needsSync = 0;
    }

    if (pViaXvMC->rendSurf[0] == (pViaSurface->srfNo | VIA_XVMC_VALID)) {
	pViaSurface->needsSync = 0;
	for (i=0; i<VIA_MAX_RENDSURF; ++i) {
	    pViaXvMC->rendSurf[i] = 0;
	}
    }

    ppthread_mutex_unlock( &pViaXvMC->ctxMutex );
    return Success;
}

Status XvMCLoadQMatrix(Display *display, XvMCContext *context,
		       const XvMCQMatrix *qmx) 
{	
    ViaXvMCContext
	*pViaXvMC;

    if((display == NULL) || (context == NULL)) {
	return BadValue;
    }
    if(NULL == (pViaXvMC = context->privData)) {
	return (error_base + XvMCBadContext);
    }

    ppthread_mutex_lock( &pViaXvMC->ctxMutex );
    if (qmx->load_intra_quantiser_matrix) {
	memcpy(pViaXvMC->intra_quantiser_matrix,
	       qmx->intra_quantiser_matrix,
	       sizeof(qmx->intra_quantiser_matrix));
	pViaXvMC->intraLoaded = 0;
    }

    if (qmx->load_non_intra_quantiser_matrix) {
	memcpy(pViaXvMC->non_intra_quantiser_matrix,
	       qmx->non_intra_quantiser_matrix,
	       sizeof(qmx->non_intra_quantiser_matrix));
	pViaXvMC->nonIntraLoaded = 0;
    }

    if (qmx->load_chroma_intra_quantiser_matrix) {
	memcpy(pViaXvMC->chroma_intra_quantiser_matrix,
	       qmx->chroma_intra_quantiser_matrix,
	       sizeof(qmx->chroma_intra_quantiser_matrix));
	pViaXvMC->chromaIntraLoaded = 0;
    }

    if (qmx->load_chroma_non_intra_quantiser_matrix) {
	memcpy(pViaXvMC->chroma_non_intra_quantiser_matrix,
	       qmx->chroma_non_intra_quantiser_matrix,
	       sizeof(qmx->chroma_non_intra_quantiser_matrix));
	pViaXvMC->chromaNonIntraLoaded = 0;
    }
    ppthread_mutex_unlock( &pViaXvMC->ctxMutex );

    return Success;
}    

/*
 * Below, we provide functions unusable for this implementation, but for
 * standard completeness.
 */

  
Status XvMCRenderSurface
(
    Display *display,
    XvMCContext *context,
    unsigned int picture_structure,
    XvMCSurface *target_surface,
    XvMCSurface *past_surface,
    XvMCSurface *future_surface,
    unsigned int flags,
    unsigned int num_macroblocks,
    unsigned int first_macroblock,
    XvMCMacroBlockArray *macroblock_array,
    XvMCBlockArray *blocks
    ) 
{
    return (error_base + XvMCBadContext);
}

Status XvMCCreateBlocks 
(
    Display *display, 
    XvMCContext *context,
    unsigned int num_blocks,
    XvMCBlockArray * block
    )
{
    return (error_base + XvMCBadContext);
}

Status XvMCDestroyBlocks (Display *display, XvMCBlockArray * block) 
{
    return Success;
}

Status XvMCCreateMacroBlocks 
(
    Display *display, 
    XvMCContext *context,
    unsigned int num_blocks,
    XvMCMacroBlockArray * blocks
    )
{
    return (error_base + XvMCBadContext);
}

Status XvMCDestroyMacroBlocks(Display *display, XvMCMacroBlockArray * block)
{
    return (error_base + XvMCBadContext);
}

Status XvMCCreateSubpicture( Display *display, 
			     XvMCContext *context,
			     XvMCSubpicture *subpicture,
			     unsigned short width,
			     unsigned short height,
			     int xvimage_id) 
{
    ViaXvMCContext *pViaXvMC;
    ViaXvMCSubPicture *pViaSubPic;
    int priv_count;
    unsigned *priv_data;
    Status ret;

    if((subpicture == NULL) || (context == NULL) || (display == NULL)){
	return BadValue;
    }
  
    pViaXvMC = (ViaXvMCContext *)context->privData;
    if(pViaXvMC == NULL) {
	return (error_base + XvMCBadContext);
    }

    subpicture->privData = (ViaXvMCSubPicture *)
	malloc(sizeof(ViaXvMCSubPicture));
    if(!subpicture->privData) {
	return BadAlloc;
    }

    ppthread_mutex_lock( &pViaXvMC->ctxMutex );
    subpicture->width = context->width;
    subpicture->height = context->height;
    subpicture->xvimage_id = xvimage_id;
    pViaSubPic = (ViaXvMCSubPicture *)subpicture->privData;

    XLockDisplay(display);
    if((ret = _xvmc_create_subpicture(display, context, subpicture,
				      &priv_count, &priv_data))) {
	XUnlockDisplay(display);
	free(pViaSubPic);
	fprintf(stderr,"Unable to create XvMC Subpicture.\n");
	ppthread_mutex_unlock( &pViaXvMC->ctxMutex );
	return ret;
    }
    XUnlockDisplay(display);


    subpicture->num_palette_entries = VIA_SUBPIC_PALETTE_SIZE;
    subpicture->entry_bytes = 3;
    strncpy(subpicture->component_order,"YUV",4); 
    pViaSubPic->srfNo = priv_data[0];
    pViaSubPic->offset = priv_data[1];
    pViaSubPic->stride = (subpicture->width + 31) & ~31;
    pViaSubPic->privContext = pViaXvMC;
    pViaSubPic->ia44 = (xvimage_id == FOURCC_IA44);
    pViaSubPic->needsSync = 0;

    /* Free data returned from _xvmc_create_subpicture */

    XFree(priv_data);
    ppthread_mutex_unlock( &pViaXvMC->ctxMutex );
    return Success;
}

Status
XvMCSetSubpicturePalette (Display *display, XvMCSubpicture *subpicture, 
			  unsigned char *palette) 
{    
    ViaXvMCSubPicture *pViaSubPic;
    ViaXvMCContext *pViaXvMC;
    volatile ViaXvMCSAreaPriv *sAPriv;
    unsigned i;
    CARD32 tmp;

    if((subpicture == NULL) || (display == NULL)){
	return BadValue;
    }
    if(subpicture->privData == NULL) {
	return (error_base + XvMCBadSubpicture);
    }
    pViaSubPic = (ViaXvMCSubPicture *) subpicture->privData;
    for (i=0; i < VIA_SUBPIC_PALETTE_SIZE; ++i) {
	tmp = *palette++ << 8;
	tmp |= *palette++ << 16;
	tmp |= *palette++ << 24;
	tmp |= ((i & 0x0f) << 4) | 0x07;
	pViaSubPic->palette[i] = tmp;
    }

    pViaXvMC = pViaSubPic->privContext;
    ppthread_mutex_lock( &pViaXvMC->ctxMutex );
    sAPriv = SAREAPTR( pViaXvMC );
    hwlLock(pViaXvMC->xl,1);
    setLowLevelLocking(pViaXvMC->xl,0);

    /*
     * If the subpicture is displaying, Immeadiately update it with the
     * new palette.
     */

    if (sAPriv->XvMCSubPicOn[pViaXvMC->xvMCPort] == 
	(pViaSubPic->srfNo | VIA_XVMC_VALID)) {
        viaVideoSubPictureLocked(pViaXvMC->xl,pViaSubPic);
    }
    flushPCIXvMCLowLevel(pViaXvMC->xl);
    setLowLevelLocking(pViaXvMC->xl,1);
    hwlUnlock(pViaXvMC->xl,1);
    ppthread_mutex_unlock( &pViaXvMC->ctxMutex );
    return Success;
}


static int findOverlap(unsigned width,unsigned height,
		       short *dstX, short *dstY,
		       short *srcX, short *srcY,
		       unsigned short *areaW, unsigned short *areaH) 
{
    int
	w,h;
    unsigned
	mWidth,mHeight;
    
    w = *areaW;
    h = *areaH;

    if ((*dstX >= width) || (*dstY >= height)) 
	return 1;
    if (*dstX < 0) {
	w += *dstX;
	*srcX -= *dstX;
	*dstX = 0;
    }
    if (*dstY < 0) {
	h += *dstY;
	*srcY -= *dstY;
	*dstY = 0;
    }
    if ((w <= 0) || ((h <= 0))) 
	return 1;
    mWidth = width - *dstX;
    mHeight = height - *dstY;
    *areaW = (w <= mWidth) ? w : mWidth;
    *areaH = (h <= mHeight) ? h : mHeight; 
    return 0;
}
    


Status XvMCClearSubpicture (
    Display *display,
    XvMCSubpicture *subpicture,
    short x,
    short y,
    unsigned short width,
    unsigned short height,
    unsigned int color
    ) 
{

    ViaXvMCContext *pViaXvMC;
    ViaXvMCSubPicture *pViaSubPic;
    short dummyX,dummyY;
    unsigned long bOffs;

    if((subpicture == NULL) || (display == NULL)) {
	return BadValue;
    }
    if(subpicture->privData == NULL) {
	return (error_base + XvMCBadSubpicture);
    }
    pViaSubPic = (ViaXvMCSubPicture *) subpicture->privData;
    pViaXvMC = pViaSubPic->privContext;
    ppthread_mutex_lock( &pViaXvMC->ctxMutex );

    /* Clip clearing area so that it fits inside subpicture. */

    if (findOverlap(subpicture->width, subpicture->height, &x, &y,
		    &dummyX, &dummyY, &width, &height)) {
	ppthread_mutex_unlock( &pViaXvMC->ctxMutex );
	return Success;
    }
		    
    bOffs = pViaSubPic->offset + y*pViaSubPic->stride + x;
    viaBlit(pViaXvMC->xl, 8, 0, pViaSubPic->stride, bOffs, pViaSubPic->stride,
	    width, height, 1, 1, VIABLIT_FILL, color);
    pViaSubPic->needsSync = 1;
    pViaSubPic->timeStamp = viaDMATimeStampLowLevel(pViaXvMC->xl);
    if (flushXvMCLowLevel(pViaXvMC->xl)) {
	ppthread_mutex_unlock( &pViaXvMC->ctxMutex );
	return BadValue;
    }
    ppthread_mutex_unlock( &pViaXvMC->ctxMutex );
    return Success;
}

Status
XvMCCompositeSubpicture (
    Display *display,
    XvMCSubpicture *subpicture,
    XvImage *image,
    short srcx,
    short srcy,
    unsigned short width,
    unsigned short height,
    short dstx,
    short dsty
    ) 
{
    
    unsigned i;
    ViaXvMCContext *pViaXvMC;
    ViaXvMCSubPicture *pViaSubPic;
    CARD8 *dAddr, *sAddr;

    if((subpicture == NULL) || (display == NULL) || (image == NULL)){
	return BadValue;
    }
    if(NULL == (pViaSubPic = (ViaXvMCSubPicture *)subpicture->privData)) {
	return (error_base + XvMCBadSubpicture);
    }

    pViaXvMC = pViaSubPic->privContext;


    if (image->id != subpicture->xvimage_id) 
	return BadMatch;

    ppthread_mutex_lock( &pViaXvMC->ctxMutex );
    

    /*
     * Clip copy area so that it fits inside subpicture and image.
     */

    if (findOverlap(subpicture->width, subpicture->height, 
		    &dstx, &dsty, &srcx, &srcy, &width, &height)) {
	ppthread_mutex_unlock( &pViaXvMC->ctxMutex );
	return Success;
    }
    if (findOverlap(image->width, image->height, 
		    &srcx, &srcy, &dstx, &dsty, &width, &height)) {
	ppthread_mutex_unlock( &pViaXvMC->ctxMutex );
	return Success;
    }

    if (pViaSubPic->needsSync) {
	if (syncXvMCLowLevel(pViaXvMC->xl, LL_MODE_2D, 0, pViaSubPic->timeStamp)) {
	    ppthread_mutex_unlock( &pViaXvMC->ctxMutex );
	    return BadValue;
	}
	pViaSubPic->needsSync = 0;
    }

    for(i=0; i<height; ++i) {
        dAddr = (((CARD8 *)pViaXvMC->fbAddress) + 
	         (pViaSubPic->offset + (dsty+i)*pViaSubPic->stride + dstx));
        sAddr = (((CARD8 *)image->data) + 
		 (image->offsets[0] + (srcy+i)*image->pitches[0] + srcx));
	memcpy(dAddr,sAddr,width);
    }

    ppthread_mutex_unlock( &pViaXvMC->ctxMutex );
    return Success;
}



Status
XvMCBlendSubpicture (
    Display *display,
    XvMCSurface *target_surface,
    XvMCSubpicture *subpicture,
    short subx,
    short suby,
    unsigned short subw,
    unsigned short subh,
    short surfx,
    short surfy,
    unsigned short surfw,
    unsigned short surfh
    ) 
{    
    ViaXvMCSurface *pViaSurface;
    ViaXvMCSubPicture *pViaSubPic;

    if((display == NULL) || target_surface == NULL){
	return BadValue;
    }
    
    if (subx || suby || surfx || surfy ||
	(subw != surfw) || (subh != surfh))  {
	fprintf(stderr,"ViaXvMC: Only completely overlapping subpicture "
		"supported.\n");
	return BadValue;
    }

    if(NULL == (pViaSurface = target_surface->privData)) {
	return (error_base + XvMCBadSurface);
    }

    if (subpicture) {

	if(NULL == (pViaSubPic = subpicture->privData)) {
	    return (error_base + XvMCBadSubpicture);
	}
	
	pViaSurface->privSubPic = pViaSubPic;
    } else {
	pViaSurface->privSubPic = NULL;
    }
    return Success;
}

Status
XvMCBlendSubpicture2 (
    Display *display,
    XvMCSurface *source_surface,
    XvMCSurface *target_surface,
    XvMCSubpicture *subpicture,
    short subx,
    short suby,
    unsigned short subw,
    unsigned short subh,
    short surfx,
    short surfy,
    unsigned short surfw,
    unsigned short surfh
    ) 
{    
    ViaXvMCSurface *pViaSurface,*pViaSSurface;
    ViaXvMCSubPicture *pViaSubPic;
    ViaXvMCContext *pViaXvMC;

    unsigned width,height;

    if((display == NULL) || target_surface == NULL || source_surface == NULL){
	return BadValue;
    }
    
    if (subx || suby || surfx || surfy ||
	(subw != surfw) || (subh != surfh))  {
	fprintf(stderr,"ViaXvMC: Only completely overlapping subpicture "
		"supported.\n");
	return BadMatch;
    }

    if(NULL == (pViaSurface = target_surface->privData)) {
	return (error_base + XvMCBadSurface);
    }

    if(NULL == (pViaSSurface = source_surface->privData)) {
	return (error_base + XvMCBadSurface);
    }
    pViaXvMC = pViaSurface->privContext;
    width = pViaSSurface->width;
    height = pViaSSurface->height;
    if (width != pViaSurface->width || height != pViaSSurface->height) {
	return BadMatch;
    }

    if (XvMCSyncSurface(display,source_surface)) {
        return BadValue;
    }

    ppthread_mutex_lock( &pViaXvMC->ctxMutex );
    viaBlit(pViaXvMC->xl, 8, yOffs(pViaSSurface), pViaSSurface->yStride,
	    yOffs(pViaSurface), pViaSurface->yStride,
	    width, height, 1, 1, VIABLIT_COPY, 0);
    flushPCIXvMCLowLevel(pViaXvMC->xl);
    if (pViaXvMC->chipId != PCI_CHIP_VT3259) {

	/*
	 * YV12 Chroma blit.
	 */
	
	viaBlit(pViaXvMC->xl, 8, uOffs(pViaSSurface), pViaSSurface->yStride >> 1,
		uOffs(pViaSurface), pViaSurface->yStride >> 1,
		width >> 1, height >> 1, 1, 1, VIABLIT_COPY, 0);
	flushPCIXvMCLowLevel(pViaXvMC->xl);
	viaBlit(pViaXvMC->xl, 8, vOffs(pViaSSurface), pViaSSurface->yStride >> 1,
		vOffs(pViaSurface), pViaSurface->yStride >> 1,
		width >> 1, height >> 1, 1, 1, VIABLIT_COPY, 0);
    } else {
	
	/*
	 * NV12 Chroma blit.
	 */

	viaBlit(pViaXvMC->xl, 8, vOffs(pViaSSurface), pViaSSurface->yStride,
		vOffs(pViaSurface), pViaSurface->yStride,
		width, height >> 1, 1, 1, VIABLIT_COPY, 0);
    }
    pViaSurface->needsSync = 1;
    pViaSurface->syncMode = LL_MODE_2D;
    pViaSurface->timeStamp = viaDMATimeStampLowLevel(pViaXvMC->xl);
    if (flushXvMCLowLevel(pViaXvMC->xl)) {
	ppthread_mutex_unlock( &pViaXvMC->ctxMutex );
	return BadValue;
    }
    if (subpicture) {

	if(NULL == (pViaSubPic = subpicture->privData)) {
	    ppthread_mutex_unlock( &pViaXvMC->ctxMutex );
	    return (error_base + XvMCBadSubpicture);
	}
	
	pViaSurface->privSubPic = pViaSubPic;
    } else {
	pViaSurface->privSubPic = NULL;
    }
    ppthread_mutex_unlock( &pViaXvMC->ctxMutex );
    return Success;
}


Status
XvMCSyncSubpicture (Display *display, XvMCSubpicture *subpicture) 
{
    ViaXvMCSubPicture *pViaSubPic;
    ViaXvMCContext *pViaXvMC;
    Status retVal=0;

    if((display == NULL) || subpicture == NULL){
	return BadValue;
    }
    if(NULL == (pViaSubPic = subpicture->privData)) {
	return (error_base + XvMCBadSubpicture);
    }

    pViaXvMC = pViaSubPic->privContext;
    ppthread_mutex_lock( &pViaXvMC->ctxMutex );
    if (pViaSubPic->needsSync) {
	if (syncXvMCLowLevel(pViaXvMC->xl, LL_MODE_2D, 
			     0, pViaSubPic->timeStamp)) {
	    retVal = BadValue;
	}
	pViaSubPic->needsSync = 0;
    }
    ppthread_mutex_unlock( &pViaXvMC->ctxMutex );
    return retVal;
}

Status
XvMCFlushSubpicture (Display *display, XvMCSubpicture *subpicture) 
{
    ViaXvMCSubPicture *pViaSubPic;

    if((display == NULL) || subpicture == NULL){
	return BadValue;
    }
    if(NULL == (pViaSubPic = subpicture->privData)) {
	return (error_base + XvMCBadSubpicture);
    }
    
    return Success;
}

Status
XvMCDestroySubpicture (Display *display, XvMCSubpicture *subpicture) 
{
    ViaXvMCSubPicture *pViaSubPic;
    ViaXvMCContext *pViaXvMC;
    volatile ViaXvMCSAreaPriv *sAPriv;

    if((display == NULL) || subpicture == NULL){
	return BadValue;
    }
    if(NULL == (pViaSubPic = subpicture->privData)) {
	return (error_base + XvMCBadSubpicture);
    }
    pViaXvMC = pViaSubPic->privContext;
    ppthread_mutex_lock( &pViaXvMC->ctxMutex );

    
    sAPriv = SAREAPTR(pViaXvMC);
    hwlLock(pViaXvMC->xl,1); 
    setLowLevelLocking(pViaXvMC->xl,0);
    if (sAPriv->XvMCSubPicOn[pViaXvMC->xvMCPort] == 
	( pViaSubPic->srfNo | VIA_XVMC_VALID )) {
        viaVideoSubPictureOffLocked(pViaXvMC->xl);
	sAPriv->XvMCSubPicOn[pViaXvMC->xvMCPort] = 0;
    }
    flushPCIXvMCLowLevel(pViaXvMC->xl);
    setLowLevelLocking(pViaXvMC->xl,1);
    hwlUnlock(pViaXvMC->xl,1);

    XLockDisplay(display);
    _xvmc_destroy_subpicture(display,subpicture);
    XUnlockDisplay(display);

    free(pViaSubPic);
    subpicture->privData = NULL;
    ppthread_mutex_unlock( &pViaXvMC->ctxMutex );

    return Success;
}

Status
XvMCGetSubpictureStatus (Display *display, XvMCSubpicture *subpic, int *stat) 
{
    ViaXvMCSubPicture *pViaSubPic;
    ViaXvMCContext *pViaXvMC;
    volatile ViaXvMCSAreaPriv *sAPriv;
   

    if((display == NULL) || subpic == NULL){
	return BadValue;
    }
    if(NULL == (pViaSubPic = subpic->privData)) {
	return (error_base + XvMCBadSubpicture);
    }
    if (stat) {
	*stat = 0;
	pViaXvMC = pViaSubPic->privContext;
	sAPriv = SAREAPTR( pViaXvMC );
	if (sAPriv->XvMCSubPicOn[pViaXvMC->xvMCPort] == 
	    (pViaSubPic->srfNo | VIA_XVMC_VALID)) 
	    *stat |= XVMC_DISPLAYING;	    
    }
    return Success;
}

Status
XvMCFlushSurface (Display *display, XvMCSurface *surface)
{
    ViaXvMCSurface *pViaSurface;
    ViaXvMCContext *pViaXvMC;
    Status ret;

    if((display == NULL) || surface == NULL){
	return BadValue;
    }
    if(NULL == (pViaSurface = surface->privData)) {
	return (error_base + XvMCBadSurface);
    }

    pViaXvMC = pViaSurface->privContext;
    ppthread_mutex_lock( &pViaXvMC->ctxMutex );
    if (pViaSurface->needsSync) 
	pViaSurface->timeStamp = pViaXvMC->timeStamp = 
	  viaDMATimeStampLowLevel(pViaXvMC->xl);
    ret = (flushXvMCLowLevel(pViaXvMC->xl)) ? BadValue : Success;
    if (pViaXvMC->rendSurf[0] == (pViaSurface->srfNo | VIA_XVMC_VALID)) {
	hwlLock(pViaXvMC->xl,0);
	pViaXvMC->haveDecoder = 0;
	releaseDecoder(pViaXvMC, 0);
	hwlUnlock(pViaXvMC->xl,0);
    }
    ppthread_mutex_unlock( &pViaXvMC->ctxMutex );
    return ret;
}

Status
XvMCGetSurfaceStatus (Display *display, XvMCSurface *surface, int *stat) 
{
    ViaXvMCSurface *pViaSurface;
    ViaXvMCContext *pViaXvMC;
    volatile ViaXvMCSAreaPriv *sAPriv;
    unsigned i;
    int ret = 0;

    if((display == NULL) || surface == NULL){
	return BadValue;
    }
    if(NULL == (pViaSurface = surface->privData)) {
	return (error_base + XvMCBadSurface);
    }
    if (stat) {
	*stat = 0;
	pViaXvMC = pViaSurface->privContext;
	ppthread_mutex_lock( &pViaXvMC->ctxMutex ); 
	sAPriv = SAREAPTR( pViaXvMC );
	if (sAPriv->XvMCDisplaying[pViaXvMC->xvMCPort] 
	    == (pViaSurface->srfNo | VIA_XVMC_VALID)) 
	    *stat |= XVMC_DISPLAYING;
	for (i=0; i<VIA_MAX_RENDSURF; ++i) {
	    if(pViaXvMC->rendSurf[i] == 
	       (pViaSurface->srfNo | VIA_XVMC_VALID)) {
		*stat |= XVMC_RENDERING;
		break;
	    }
	}
	ppthread_mutex_unlock( &pViaXvMC->ctxMutex ); 
    }
    return ret;
}

XvAttribute *
XvMCQueryAttributes (
    Display *display,
    XvMCContext *context,
    int *number
    )
{
    ViaXvMCContext *pViaXvMC;
    XvAttribute *ret;
    unsigned long siz;

    *number = 0;
    if ((display == NULL) || (context == NULL)) {
	return NULL;
    }
    
    if (NULL == (pViaXvMC = context->privData)) {
	return NULL;
    }

    ppthread_mutex_lock( &pViaXvMC->ctxMutex );
    if (NULL != (ret = (XvAttribute *)
		 malloc(siz = VIA_NUM_XVMC_ATTRIBUTES*sizeof(XvAttribute)))) {
	memcpy(ret,pViaXvMC->attribDesc,siz);
	*number =  VIA_NUM_XVMC_ATTRIBUTES;
    }
    ppthread_mutex_unlock( &pViaXvMC->ctxMutex );

    return ret;    
}

Status
XvMCSetAttribute (
    Display *display,
    XvMCContext *context, 
    Atom attribute, 
    int value
    )
{
    int found;
    unsigned i;
    ViaXvMCContext *pViaXvMC;
    ViaXvMCCommandBuffer buf;
    
    if ((display == NULL) || (context == NULL)) {
	return (error_base + XvMCBadContext);
    }
    
    if (NULL == (pViaXvMC = context->privData)) {
	return (error_base + XvMCBadContext);
    }

    ppthread_mutex_lock( &pViaXvMC->ctxMutex );

    found = 0;
    for (i=0; i < pViaXvMC->attrib.numAttr; ++i) {
	if (attribute == pViaXvMC->attrib.attributes[i].attribute) {
	    if ((!(pViaXvMC->attribDesc[i].flags & XvSettable)) ||
		value < pViaXvMC->attribDesc[i].min_value ||
		value > pViaXvMC->attribDesc[i].max_value) 
		return BadValue;
	    pViaXvMC->attrib.attributes[i].value = value;
	    found = 1;
	    pViaXvMC->attribChanged = 1;
	    break;
	}
    }
    if (!found) {
	ppthread_mutex_unlock( &pViaXvMC->ctxMutex );
	return BadMatch;
    }
    if (pViaXvMC->haveXv) {
	buf.command = VIA_XVMC_COMMAND_ATTRIBUTES;
	pViaXvMC->xvImage->data = (char *)&buf;
	buf.ctxNo = pViaXvMC->ctxNo | VIA_XVMC_VALID;
	buf.attrib = pViaXvMC->attrib;
	XLockDisplay(display);
	pViaXvMC->attribChanged = 
	    XvPutImage(display,pViaXvMC->port,pViaXvMC->draw,
		       pViaXvMC->gc,
		       pViaXvMC->xvImage,0,0,1,1,0,0,1,1);
	XUnlockDisplay(display);
    }
    ppthread_mutex_unlock( &pViaXvMC->ctxMutex ); 
    return Success;
}
    

Status
XvMCGetAttribute (
    Display *display,
    XvMCContext *context, 
    Atom attribute, 
    int *value
    )
{
    int found;
    unsigned i;
    ViaXvMCContext *pViaXvMC;
    
    if ((display == NULL) || (context == NULL)) {
	return (error_base + XvMCBadContext);
    }
    
    if (NULL == (pViaXvMC = context->privData)) {
	return (error_base + XvMCBadContext);
    }

    ppthread_mutex_lock( &pViaXvMC->ctxMutex ); 
    found = 0;
    for (i=0; i < pViaXvMC->attrib.numAttr; ++i) {
	if (attribute == pViaXvMC->attrib.attributes[i].attribute) {
	    if (pViaXvMC->attribDesc[i].flags & XvGettable) {
		*value = pViaXvMC->attrib.attributes[i].value;
		found = 1;
		break;
	    }
	}
    }
    ppthread_mutex_unlock( &pViaXvMC->ctxMutex ); 

    if (!found) 
	return BadMatch;
    return Success;
}
    

Status XvMCHideSurface(Display *display,XvMCSurface *surface)
{

    ViaXvMCSurface *pViaSurface;
    ViaXvMCContext *pViaXvMC;
    ViaXvMCSubPicture *pViaSubPic;
    volatile ViaXvMCSAreaPriv *sAPriv;
    ViaXvMCCommandBuffer buf;
    Status ret;

    if ((display == NULL) || (surface == NULL)) {
	return BadValue;
    }
    if (NULL == (pViaSurface = surface->privData )) {
	return (error_base + XvMCBadSurface);
    }
    if (NULL == (pViaXvMC = pViaSurface->privContext)) {
	return (error_base + XvMCBadContext);
    }

    ppthread_mutex_lock( &pViaXvMC->ctxMutex ); 
    if (!pViaXvMC->haveXv) {
	ppthread_mutex_unlock( &pViaXvMC->ctxMutex ); 
	return Success;
    }
    
    sAPriv = SAREAPTR( pViaXvMC );
    hwlLock(pViaXvMC->xl,1); 

    if (sAPriv->XvMCDisplaying[pViaXvMC->xvMCPort] != 
	(pViaSurface->srfNo | VIA_XVMC_VALID)) {
	hwlUnlock(pViaXvMC->xl,1);
	ppthread_mutex_unlock( &pViaXvMC->ctxMutex ); 
	return Success;
    }
    setLowLevelLocking(pViaXvMC->xl,0);
    if (NULL != (pViaSubPic = pViaSurface->privSubPic)) {
	if (sAPriv->XvMCSubPicOn[pViaXvMC->xvMCPort] ==
	    (pViaSubPic->srfNo | VIA_XVMC_VALID)) {
	    sAPriv->XvMCSubPicOn[pViaXvMC->xvMCPort] &= ~VIA_XVMC_VALID;
	    viaVideoSubPictureOffLocked(pViaXvMC->xl); 
	}
    }
    flushPCIXvMCLowLevel(pViaXvMC->xl);
    setLowLevelLocking(pViaXvMC->xl,1);
    hwlUnlock(pViaXvMC->xl,1);
	    
    buf.command = VIA_XVMC_COMMAND_UNDISPLAY;
    buf.ctxNo = pViaXvMC->ctxNo | VIA_XVMC_VALID;
    buf.srfNo = pViaSurface->srfNo | VIA_XVMC_VALID;
    pViaXvMC->xvImage->data = (char *)&buf;
    if ((ret = XvPutImage(display,pViaXvMC->port,pViaXvMC->draw,
			  pViaXvMC->gc,
			  pViaXvMC->xvImage,0,0,1,1,0,0,1,1))) {
	fprintf(stderr,"XvMCPutSurface: Hiding overlay failed.\n");
	ppthread_mutex_unlock( &pViaXvMC->ctxMutex ); 
	return ret;
    }
    ppthread_mutex_unlock( &pViaXvMC->ctxMutex ); 
    return Success;
}
