/* $XFree86: xc/programs/Xserver/Xext/xvmc.c,v 1.8 2003/07/16 01:38:31 dawes Exp $ */

#define NEED_REPLIES
#define NEED_EVENTS
#include "X.h"
#include "Xproto.h"
#include "misc.h"
#include "os.h"
#include "dixstruct.h"
#include "resource.h"
#include "scrnintstr.h"
#include "extnsionst.h"
#include "servermd.h"
#include "Xfuncproto.h"
#include "xvdix.h"
#include "XvMC.h"
#include "Xvproto.h"
#include "XvMCproto.h"
#include "xvmcext.h"

int XvMCScreenIndex = -1;

unsigned long XvMCGeneration = 0;

int XvMCReqCode;
int XvMCEventBase;
int XvMCErrorBase;

unsigned long XvMCRTContext;
unsigned long XvMCRTSurface;
unsigned long XvMCRTSubpicture;

typedef struct {
   int num_adaptors;
   XvMCAdaptorPtr adaptors;
   CloseScreenProcPtr	CloseScreen;
} XvMCScreenRec, *XvMCScreenPtr; 

#define XVMC_GET_PRIVATE(pScreen) \
   (XvMCScreenPtr)((pScreen)->devPrivates[XvMCScreenIndex].ptr)


static int
XvMCDestroyContextRes(pointer data, XID id)
{
   XvMCContextPtr pContext = (XvMCContextPtr)data;
   
   pContext->refcnt--;

   if(!pContext->refcnt) {
  	 XvMCScreenPtr pScreenPriv = XVMC_GET_PRIVATE(pContext->pScreen);
	(*pScreenPriv->adaptors[pContext->adapt_num].DestroyContext)(pContext);
	xfree(pContext);
   }	   

   return Success;
}

static int
XvMCDestroySurfaceRes(pointer data, XID id)
{
   XvMCSurfacePtr pSurface = (XvMCSurfacePtr)data;
   XvMCContextPtr pContext = pSurface->context;
   XvMCScreenPtr pScreenPriv = XVMC_GET_PRIVATE(pContext->pScreen);

   (*pScreenPriv->adaptors[pContext->adapt_num].DestroySurface)(pSurface); 
   xfree(pSurface);

   XvMCDestroyContextRes((pointer)pContext, pContext->context_id);

   return Success;
}


static int
XvMCDestroySubpictureRes(pointer data, XID id)
{
   XvMCSubpicturePtr pSubpict = (XvMCSubpicturePtr)data;
   XvMCContextPtr pContext = pSubpict->context;
   XvMCScreenPtr pScreenPriv = XVMC_GET_PRIVATE(pContext->pScreen);

   (*pScreenPriv->adaptors[pContext->adapt_num].DestroySubpicture)(pSubpict); 
   xfree(pSubpict);

   XvMCDestroyContextRes((pointer)pContext, pContext->context_id);

   return Success;
}

static void
XvMCResetProc (ExtensionEntry *extEntry)
{
}


static int 
ProcXvMCQueryVersion(ClientPtr client)
{
    xvmcQueryVersionReply rep;
    /* REQUEST(xvmcQueryVersionReq); */
    REQUEST_SIZE_MATCH(xvmcQueryVersionReq);
    rep.type = X_Reply;
    rep.sequenceNumber = client->sequence;
    rep.length = 0;
    rep.major = XvMCVersion;
    rep.minor = XvMCRevision;
    WriteToClient(client, sizeof(xvmcQueryVersionReply), (char*)&rep);
    return Success;
}


static int 
ProcXvMCListSurfaceTypes(ClientPtr client)
{
    XvPortPtr pPort;
    int i;
    XvMCScreenPtr pScreenPriv;
    xvmcListSurfaceTypesReply rep;
    xvmcSurfaceInfo info;
    XvMCAdaptorPtr adaptor = NULL;
    XvMCSurfaceInfoPtr surface;
    REQUEST(xvmcListSurfaceTypesReq);
    REQUEST_SIZE_MATCH(xvmcListSurfaceTypesReq);

    if(!(pPort = LOOKUP_PORT(stuff->port, client))) {
        client->errorValue = stuff->port;
        return _XvBadPort;
    }

    if(XvMCScreenIndex >= 0) { /* any adaptors at all */
       ScreenPtr pScreen = pPort->pAdaptor->pScreen;
       if((pScreenPriv = XVMC_GET_PRIVATE(pScreen))) {  /* any this screen */
          for(i = 0; i < pScreenPriv->num_adaptors; i++) {
             if(pPort->pAdaptor == pScreenPriv->adaptors[i].xv_adaptor) {
               adaptor = &(pScreenPriv->adaptors[i]);
               break;
             }
          }
       }
    }

    rep.type = X_Reply;
    rep.sequenceNumber = client->sequence;
    rep.num = (adaptor) ? adaptor->num_surfaces : 0;
    rep.length = rep.num * sizeof(xvmcSurfaceInfo) >> 2;
 
    WriteToClient(client, sizeof(xvmcListSurfaceTypesReply), (char*)&rep);

    for(i = 0; i < rep.num; i++) {
	surface = adaptor->surfaces[i];
	info.surface_type_id = surface->surface_type_id;
	info.chroma_format = surface->chroma_format;
	info.max_width = surface->max_width;
	info.max_height = surface->max_height;
	info.subpicture_max_width = surface->subpicture_max_width;
	info.subpicture_max_height = surface->subpicture_max_height;
	info.mc_type = surface->mc_type;
	info.flags = surface->flags;
	WriteToClient(client, sizeof(xvmcSurfaceInfo), (char*)&info);
    }

    return Success;
}

static int 
ProcXvMCCreateContext(ClientPtr client)
{
    XvPortPtr pPort;
    CARD32 *data = NULL;
    int dwords = 0; 
    int i, result, adapt_num = -1;
    ScreenPtr pScreen;
    XvMCContextPtr pContext;
    XvMCScreenPtr pScreenPriv;
    XvMCAdaptorPtr adaptor = NULL;
    XvMCSurfaceInfoPtr surface = NULL;
    xvmcCreateContextReply rep;
    REQUEST(xvmcCreateContextReq);
    REQUEST_SIZE_MATCH(xvmcCreateContextReq);

    if(!(pPort = LOOKUP_PORT(stuff->port, client))) {
	client->errorValue = stuff->port;
	return _XvBadPort;
    }

    pScreen = pPort->pAdaptor->pScreen;

    if(XvMCScreenIndex < 0) /* no XvMC adaptors */
       return BadMatch;
 
    if(!(pScreenPriv = XVMC_GET_PRIVATE(pScreen))) /* none this screen */
       return BadMatch;

    for(i = 0; i < pScreenPriv->num_adaptors; i++) {
	if(pPort->pAdaptor == pScreenPriv->adaptors[i].xv_adaptor) {
	    adaptor = &(pScreenPriv->adaptors[i]);
	    adapt_num = i; 
	    break;
	}
    }

    if(adapt_num < 0) /* none this port */
	return BadMatch;	

    for(i = 0; i < adaptor->num_surfaces; i++) {
        if(adaptor->surfaces[i]->surface_type_id == stuff->surface_type_id) {
            surface = adaptor->surfaces[i];
            break;
        }
    }

    /* adaptor doesn't support this suface_type_id */
    if(!surface) return BadMatch;


    if((stuff->width > surface->max_width) ||
       (stuff->height > surface->max_height))
        return BadValue;

    if(!(pContext = xalloc(sizeof(XvMCContextRec)))) {
	return BadAlloc;
    }


    pContext->pScreen = pScreen;
    pContext->adapt_num = adapt_num;
    pContext->context_id = stuff->context_id;
    pContext->surface_type_id = stuff->surface_type_id;
    pContext->width = stuff->width;
    pContext->height = stuff->height;
    pContext->flags = stuff->flags;
    pContext->refcnt = 1;

    result = (*adaptor->CreateContext)(pPort, pContext, &dwords, &data);

    if(result != Success) {
	xfree(pContext);
	return result;
    }

    rep.type = X_Reply;
    rep.sequenceNumber = client->sequence;
    rep.width_actual = pContext->width; 
    rep.height_actual = pContext->height; 
    rep.flags_return = pContext->flags; 
    rep.length = dwords;

    WriteToClient(client, sizeof(xvmcCreateContextReply), (char*)&rep);
    if(dwords)
      WriteToClient(client, dwords << 2, (char*)data); 
    AddResource(pContext->context_id, XvMCRTContext, pContext);

    if(data)
	xfree(data);

    return Success;
}

static int 
ProcXvMCDestroyContext(ClientPtr client)
{
    REQUEST(xvmcDestroyContextReq);
    REQUEST_SIZE_MATCH(xvmcDestroyContextReq);

    if(!LookupIDByType(stuff->context_id, XvMCRTContext))
	return (XvMCBadContext + XvMCErrorBase);

    FreeResource(stuff->context_id, RT_NONE); 

    return Success;
}

static int 
ProcXvMCCreateSurface(ClientPtr client)
{
    CARD32 *data = NULL;
    int dwords = 0;
    int result;
    XvMCContextPtr pContext;
    XvMCSurfacePtr pSurface;
    XvMCScreenPtr pScreenPriv;
    xvmcCreateSurfaceReply rep;
    REQUEST(xvmcCreateSurfaceReq);
    REQUEST_SIZE_MATCH(xvmcCreateSurfaceReq);

    if(!(pContext = LookupIDByType(stuff->context_id, XvMCRTContext)))
        return (XvMCBadContext + XvMCErrorBase);

    pScreenPriv = XVMC_GET_PRIVATE(pContext->pScreen);

    if(!(pSurface = xalloc(sizeof(XvMCSurfaceRec)))) 
        return BadAlloc;

    pSurface->surface_id = stuff->surface_id;
    pSurface->surface_type_id = pContext->surface_type_id;
    pSurface->context = pContext;

    result = (*pScreenPriv->adaptors[pContext->adapt_num].CreateSurface)(
                pSurface, &dwords, &data);

    if(result != Success) {
        xfree(pSurface);
        return result;
    }

    rep.type = X_Reply;
    rep.sequenceNumber = client->sequence;
    rep.length = dwords;

    WriteToClient(client, sizeof(xvmcCreateSurfaceReply), (char*)&rep);
    if(dwords)
      WriteToClient(client, dwords << 2, (char*)data);
    AddResource(pSurface->surface_id, XvMCRTSurface, pSurface);

    if(data)
        xfree(data);

    pContext->refcnt++;

    return Success;
}

static int 
ProcXvMCDestroySurface(ClientPtr client)
{
    REQUEST(xvmcDestroySurfaceReq);
    REQUEST_SIZE_MATCH(xvmcDestroySurfaceReq);

    if(!LookupIDByType(stuff->surface_id, XvMCRTSurface))
        return (XvMCBadSurface + XvMCErrorBase);

    FreeResource(stuff->surface_id, RT_NONE);

    return Success;
}

static int 
ProcXvMCCreateSubpicture(ClientPtr client)
{
    Bool image_supported = FALSE;
    CARD32 *data = NULL;
    int i, result, dwords = 0;
    XvMCContextPtr pContext;
    XvMCSubpicturePtr pSubpicture;
    XvMCScreenPtr pScreenPriv;
    xvmcCreateSubpictureReply rep;
    XvMCAdaptorPtr adaptor;
    XvMCSurfaceInfoPtr surface = NULL;
    REQUEST(xvmcCreateSubpictureReq);
    REQUEST_SIZE_MATCH(xvmcCreateSubpictureReq);

    if(!(pContext = LookupIDByType(stuff->context_id, XvMCRTContext)))
        return (XvMCBadContext + XvMCErrorBase);

    pScreenPriv = XVMC_GET_PRIVATE(pContext->pScreen);

    adaptor = &(pScreenPriv->adaptors[pContext->adapt_num]); 

    /* find which surface this context supports */
    for(i = 0; i < adaptor->num_surfaces; i++) {
	if(adaptor->surfaces[i]->surface_type_id == pContext->surface_type_id){
	   surface = adaptor->surfaces[i];
	   break;
	}
    } 

    if(!surface) return BadMatch;

    /* make sure this surface supports that xvimage format */
    if(!surface->compatible_subpictures) return BadMatch;

    for(i = 0; i < surface->compatible_subpictures->num_xvimages; i++) {
      if(surface->compatible_subpictures->xvimage_ids[i] == stuff->xvimage_id) {
	   image_supported = TRUE;
	   break;
      }
    }

    if(!image_supported) return BadMatch;

    /* make sure the size is OK */
    if((stuff->width > surface->subpicture_max_width) ||
       (stuff->height > surface->subpicture_max_height))
	return BadValue;

    if(!(pSubpicture = xalloc(sizeof(XvMCSubpictureRec))))
        return BadAlloc;

    pSubpicture->subpicture_id = stuff->subpicture_id;
    pSubpicture->xvimage_id = stuff->xvimage_id;
    pSubpicture->width = stuff->width;
    pSubpicture->height = stuff->height;
    pSubpicture->num_palette_entries = 0; /* overwritten by DDX */
    pSubpicture->entry_bytes = 0;         /* overwritten by DDX */
    pSubpicture->component_order[0] = 0;  /* overwritten by DDX */
    pSubpicture->component_order[1] = 0;
    pSubpicture->component_order[2] = 0;
    pSubpicture->component_order[3] = 0;
    pSubpicture->context = pContext;
   
    result = (*pScreenPriv->adaptors[pContext->adapt_num].CreateSubpicture)(
                pSubpicture, &dwords, &data);

    if(result != Success) {
        xfree(pSubpicture);
        return result;
    }

    rep.type = X_Reply;
    rep.sequenceNumber = client->sequence;
    rep.width_actual = pSubpicture->width;
    rep.height_actual = pSubpicture->height;
    rep.num_palette_entries = pSubpicture->num_palette_entries;
    rep.entry_bytes = pSubpicture->entry_bytes;
    rep.component_order[0] = pSubpicture->component_order[0];
    rep.component_order[1] = pSubpicture->component_order[1];
    rep.component_order[2] = pSubpicture->component_order[2];
    rep.component_order[3] = pSubpicture->component_order[3];
    rep.length = dwords;

    WriteToClient(client, sizeof(xvmcCreateSubpictureReply), (char*)&rep);
    if(dwords)
      WriteToClient(client, dwords << 2, (char*)data);
    AddResource(pSubpicture->subpicture_id, XvMCRTSubpicture, pSubpicture);

    if(data)
        xfree(data);

    pContext->refcnt++;

    return Success;
}

static int 
ProcXvMCDestroySubpicture(ClientPtr client)
{
    REQUEST(xvmcDestroySubpictureReq);
    REQUEST_SIZE_MATCH(xvmcDestroySubpictureReq);

    if(!LookupIDByType(stuff->subpicture_id, XvMCRTSubpicture))
        return (XvMCBadSubpicture + XvMCErrorBase);

    FreeResource(stuff->subpicture_id, RT_NONE);

    return Success;
}


static int
ProcXvMCListSubpictureTypes(ClientPtr client)
{
    XvPortPtr pPort;
    xvmcListSubpictureTypesReply rep;
    XvMCScreenPtr pScreenPriv;
    ScreenPtr pScreen;
    XvMCAdaptorPtr adaptor = NULL;
    XvMCSurfaceInfoPtr surface = NULL;
    xvImageFormatInfo info;
    XvImagePtr pImage;
    int i, j;
    REQUEST(xvmcListSubpictureTypesReq);
    REQUEST_SIZE_MATCH(xvmcListSubpictureTypesReq);

    if(!(pPort = LOOKUP_PORT(stuff->port, client))) {
        client->errorValue = stuff->port;
        return _XvBadPort;
    }

    pScreen = pPort->pAdaptor->pScreen;

    if(XvMCScreenIndex < 0) /* No XvMC adaptors */
        return BadMatch;

    if(!(pScreenPriv = XVMC_GET_PRIVATE(pScreen)))
        return BadMatch;   /* None this screen */

    for(i = 0; i < pScreenPriv->num_adaptors; i++) {
        if(pPort->pAdaptor == pScreenPriv->adaptors[i].xv_adaptor) {
            adaptor = &(pScreenPriv->adaptors[i]);
            break;
        }
    }

    if(!adaptor) return BadMatch;

    for(i = 0; i < adaptor->num_surfaces; i++) {
        if(adaptor->surfaces[i]->surface_type_id == stuff->surface_type_id) {
            surface = adaptor->surfaces[i];
            break;
        }
    }

    if(!surface) return BadMatch;

    rep.type = X_Reply;
    rep.sequenceNumber = client->sequence;
    rep.num = 0;
    if(surface->compatible_subpictures) 
	rep.num = surface->compatible_subpictures->num_xvimages;

    rep.length = rep.num * sizeof(xvImageFormatInfo) >> 2;

    WriteToClient(client, sizeof(xvmcListSubpictureTypesReply), (char*)&rep);

    for(i = 0; i < rep.num; i++) {
	pImage = NULL;
	for(j = 0; j < adaptor->num_subpictures; j++) {
	    if(surface->compatible_subpictures->xvimage_ids[i] ==
	       adaptor->subpictures[j]->id)
	    {
		pImage = adaptor->subpictures[j];
	        break;
	    }
	}
	if(!pImage) return BadImplementation;

        info.id = pImage->id;      
	info.type = pImage->type;  
        info.byte_order = pImage->byte_order; 
        memcpy(&info.guid, pImage->guid, 16);      
        info.bpp = pImage->bits_per_pixel;         
        info.num_planes = pImage->num_planes;      
        info.depth = pImage->depth;        
        info.red_mask = pImage->red_mask;  
        info.green_mask = pImage->green_mask;      
        info.blue_mask = pImage->blue_mask;        
        info.format = pImage->format;      
        info.y_sample_bits = pImage->y_sample_bits;        
        info.u_sample_bits = pImage->u_sample_bits;        
        info.v_sample_bits = pImage->v_sample_bits;        
        info.horz_y_period = pImage->horz_y_period;        
        info.horz_u_period = pImage->horz_u_period;        
        info.horz_v_period = pImage->horz_v_period;        
        info.vert_y_period = pImage->vert_y_period;        
        info.vert_u_period = pImage->vert_u_period;        
        info.vert_v_period = pImage->vert_v_period;        
        memcpy(&info.comp_order, pImage->component_order, 32);     
        info.scanline_order = pImage->scanline_order;
	WriteToClient(client, sizeof(xvImageFormatInfo), (char*)&info);
    }

    return Success;
}



int (*ProcXvMCVector[xvmcNumRequest])(ClientPtr) = {
  ProcXvMCQueryVersion,
  ProcXvMCListSurfaceTypes,
  ProcXvMCCreateContext,
  ProcXvMCDestroyContext,
  ProcXvMCCreateSurface,
  ProcXvMCDestroySurface,
  ProcXvMCCreateSubpicture,
  ProcXvMCDestroySubpicture,
  ProcXvMCListSubpictureTypes
};

static int
ProcXvMCDispatch (ClientPtr client)
{
    REQUEST(xReq);
    
    if(stuff->data < xvmcNumRequest)
	return (*ProcXvMCVector[stuff->data])(client);
    else
	return BadRequest;
}

static int
SProcXvMCDispatch (ClientPtr client)
{
    /* We only support local */
    return BadImplementation;
}

void 
XvMCExtensionInit()
{
   ExtensionEntry *extEntry;

   if(XvMCScreenIndex < 0) /* nobody supports it */
	return; 

   if(!(XvMCRTContext = CreateNewResourceType(XvMCDestroyContextRes)))
	return;

   if(!(XvMCRTSurface = CreateNewResourceType(XvMCDestroySurfaceRes)))
	return;

   if(!(XvMCRTSubpicture = CreateNewResourceType(XvMCDestroySubpictureRes)))
	return;

   extEntry = AddExtension(XvMCName, XvMCNumEvents, XvMCNumErrors, 
                              ProcXvMCDispatch, SProcXvMCDispatch,
                              XvMCResetProc, StandardMinorOpcode);

   if(!extEntry) return;
  
   XvMCReqCode = extEntry->base;
   XvMCEventBase = extEntry->eventBase;
   XvMCErrorBase = extEntry->errorBase;
}

static Bool
XvMCCloseScreen (int i, ScreenPtr pScreen)
{
    XvMCScreenPtr pScreenPriv = XVMC_GET_PRIVATE(pScreen);

    pScreen->CloseScreen = pScreenPriv->CloseScreen;

    xfree(pScreenPriv);

    return (*pScreen->CloseScreen)(i, pScreen);
}


int
XvMCScreenInit(ScreenPtr pScreen, int num, XvMCAdaptorPtr pAdapt)
{
   XvMCScreenPtr pScreenPriv;

   if(XvMCGeneration != serverGeneration) {
	if((XvMCScreenIndex = AllocateScreenPrivateIndex()) < 0)
	   return BadAlloc;

	XvMCGeneration = serverGeneration;
   }

   if(!(pScreenPriv = (XvMCScreenPtr)xalloc(sizeof(XvMCScreenRec))))
	return BadAlloc;

   pScreen->devPrivates[XvMCScreenIndex].ptr = (pointer)pScreenPriv;

   pScreenPriv->CloseScreen = pScreen->CloseScreen;
   pScreen->CloseScreen = XvMCCloseScreen;

   pScreenPriv->num_adaptors = num;
   pScreenPriv->adaptors = pAdapt;

   return Success;
}

XvImagePtr XvMCFindXvImage(XvPortPtr pPort, CARD32 id)
{
    XvImagePtr pImage = NULL;
    ScreenPtr pScreen = pPort->pAdaptor->pScreen;
    XvMCScreenPtr pScreenPriv;
    XvMCAdaptorPtr adaptor = NULL;
    int i;

    if(XvMCScreenIndex < 0) return NULL;

    if(!(pScreenPriv = XVMC_GET_PRIVATE(pScreen))) 
        return NULL;

    for(i = 0; i < pScreenPriv->num_adaptors; i++) {
       if(pPort->pAdaptor == pScreenPriv->adaptors[i].xv_adaptor) {
          adaptor = &(pScreenPriv->adaptors[i]);
          break;
       }
    }

    if(!adaptor) return NULL;

    for(i = 0; i < adaptor->num_subpictures; i++) {
        if(adaptor->subpictures[i]->id == id) {
            pImage = adaptor->subpictures[i];
            break;
        }
    }

    return pImage;
}