/*
 * Copyright (C) 2000-2004 the xine project
 * Copyright (C) 2004 the Unichrome project
 *
 * This file is part of xine, a free video player.
 *
 * xine is free software; you can redistribute it and/or modify it 
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * xine is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
 *
 * $Id: video_out_xxmc.c,v 1.19 2005/09/25 00:44:04 miguelfreitas Exp $
 *
 * video_out_xxmc.c, X11 decoding accelerated video extension interface for xine
 *
 * based on mpeg2dec code from
 * Aaron Holtzman <aholtzma@ess.engr.uvic.ca>
 *
 * Xv image support by Gerd Knorr <kraxel@goldbach.in-berlin.de>
 *
 * xine-specific code by Guenter Bartsch <bartscgr@studbox.uni-stuttgart.de>
 *
 * overlay support by James Courtier-Dutton <James@superbug.demon.co.uk> - July 2001
 * X11 unscaled overlay support by Miguel Freitas - Nov 2003
 * XvMC VLD implementation by Thomas Hellström - 2004, 2005.
 * XvMC merge by Thomas Hellström - Sep 2004
 *
 */



#include "xxmc.h"
#include <unistd.h>


static int gX11Fail;
static void xxmc_frame_updates(xxmc_driver_t *driver, xxmc_frame_t *frame, 
			       int init_macroblocks);
static void dispose_ximage (xxmc_driver_t *this, XShmSegmentInfo *shminfo,
			    XvImage *myimage);


/*
 * Acceleration level priority. Static for now. It may well turn out that IDCT
 * is more efficient than VLD.
 */

static unsigned accel_priority[] = {
#ifdef HAVE_VLDXVMC
				    XINE_XVMC_ACCEL_VLD,
#endif
				    XINE_XVMC_ACCEL_IDCT,
				    XINE_XVMC_ACCEL_MOCOMP};
#define NUM_ACCEL_PRIORITY (sizeof(accel_priority)/sizeof(accel_priority[0]))

#ifndef XVMC_VLD
  #define XVMC_VLD 0
#endif

/*
 * Additional thread safety, since the plugin may decide to destroy a context
 * while it's surfaces are still active in the video-out loop.
 * When / If XvMC libs are reasonably thread-safe, the locks can be made
 * more efficient by allowing multiple threads in that do not destroy 
 * the context or surfaces that may be active in other threads.
 */

static void init_context_lock(context_lock_t *c)
{
  pthread_cond_init(&c->cond,NULL);
  pthread_mutex_init(&c->mutex,NULL);
  c->num_readers = 0;
}

static void free_context_lock(context_lock_t *c)
{
  pthread_mutex_destroy(&c->mutex);
  pthread_cond_destroy(&c->cond);
}

void xvmc_context_reader_lock(context_lock_t *c)
{
  pthread_mutex_lock(&c->mutex);
#ifdef XVMC_THREAD_SAFE
  c->num_readers++;
  pthread_mutex_unlock(&c->mutex);
#endif 
}

void xvmc_context_reader_unlock(context_lock_t *c)
{
#ifdef XVMC_THREAD_SAFE
  pthread_mutex_lock(&c->mutex);
  if (c->num_readers > 0) {
    if (--(c->num_readers) == 0) {
      pthread_cond_broadcast(&c->cond);
    }
  } 
#endif
  pthread_mutex_unlock(&c->mutex); 
}

static void xvmc_context_writer_lock(context_lock_t *c)
{
  pthread_mutex_lock(&c->mutex);
#ifdef XVMC_THREAD_SAFE
  while(c->num_readers) {
    pthread_cond_wait(&c->cond, &c->mutex);
  }
#endif 
}

static void xvmc_context_writer_unlock(context_lock_t *c)
{
  pthread_mutex_unlock(&c->mutex);
}

/*
 * A number of simple surface allocator functions that implements the
 * notion that a surface may be invalid if it is asynchronously
 * destroyed. Both surfaces and subpictures are handled this way.
 */



static void xxmc_xvmc_dump_surfaces(xxmc_driver_t *this ) 
{
  int
    i;
  xvmc_surface_handler_t *handler = &this->xvmc_surf_handler;

  for (i=0; i<XVMC_MAX_SURFACES; ++i) {
    xprintf(this->xine, XINE_VERBOSITY_DEBUG, "%d %d;",handler->surfInUse[i],  
	    handler->surfValid[i]);
  }
  xprintf(this->xine, XINE_VERBOSITY_DEBUG, "\n"); 
}

static void xxmc_xvmc_dump_subpictures(xxmc_driver_t *this) 
{
  int
    i;
  xvmc_surface_handler_t *handler = &this->xvmc_surf_handler;

  for (i=0; i<XVMC_MAX_SUBPICTURES; ++i) {
    xprintf(this->xine, XINE_VERBOSITY_DEBUG, "%d %d;",handler->subInUse[i],  
	    handler->subValid[i]);
  }
  xprintf(this->xine, XINE_VERBOSITY_DEBUG, "\n"); 
}


static void xxmc_xvmc_surface_handler_construct(xxmc_driver_t *this) 
{
  int i;
  xvmc_surface_handler_t *handler = &this->xvmc_surf_handler;

  pthread_mutex_init(&handler->mutex,NULL);
  for (i=0; i<XVMC_MAX_SURFACES; ++i) {
    handler->surfInUse[i] = 0;
    handler->surfValid[i] = 0;
  }
  for (i=0; i<XVMC_MAX_SUBPICTURES; ++i) {
    handler->subInUse[i] = 0;
    handler->subValid[i] = 0;
  }
}

static void xxmc_xvmc_destroy_surfaces(xxmc_driver_t *this)
{
  int i;
  xvmc_surface_handler_t *handler = &this->xvmc_surf_handler;

  pthread_mutex_lock(&handler->mutex);
  for (i=0; i < XVMC_MAX_SURFACES; ++i) {
    XVMCLOCKDISPLAY( this->display );
    if (handler->surfValid[i]) {
      XvMCFlushSurface( this->display , handler->surfaces+i);
      XvMCSyncSurface( this->display, handler->surfaces+i );
      XvMCHideSurface( this->display, handler->surfaces+i );
      XvMCDestroySurface( this->display, handler->surfaces+i );
    }
    XVMCUNLOCKDISPLAY( this->display );
    handler->surfValid[i] = 0;
  }
  pthread_mutex_unlock(&handler->mutex);

}

static void xxmc_xvmc_destroy_subpictures(xxmc_driver_t *this)
{
  int i;
  xvmc_surface_handler_t *handler = &this->xvmc_surf_handler;

  pthread_mutex_lock(&handler->mutex);
  for (i=0; i < XVMC_MAX_SUBPICTURES; ++i) {
    XVMCLOCKDISPLAY( this->display );
    if (handler->subValid[i]) {
      XvMCFlushSubpicture( this->display , handler->subpictures+i);
      XvMCSyncSubpicture( this->display, handler->subpictures+i );
      XvMCDestroySubpicture( this->display, handler->subpictures+i );
    }
    XVMCUNLOCKDISPLAY( this->display );
    handler->subValid[i] = 0;
  }
  pthread_mutex_unlock(&handler->mutex);
}
    
static XvMCSurface *xxmc_xvmc_alloc_surface(xxmc_driver_t *this, 
					    XvMCContext *context)
{
  xvmc_surface_handler_t *handler = &this->xvmc_surf_handler;  
  int i;

  pthread_mutex_lock(&handler->mutex);
  xxmc_xvmc_dump_surfaces(this);
  for (i=0; i<XVMC_MAX_SURFACES; ++i) {
    if (handler->surfValid[i] && !handler->surfInUse[i]) {
      handler->surfInUse[i] = 1;
      xxmc_xvmc_dump_surfaces(this);
      pthread_mutex_unlock(&handler->mutex);
      return handler->surfaces + i;
    }
  }
  for (i=0; i<XVMC_MAX_SURFACES; ++i) {
    if (!handler->surfInUse[i]) {
      XVMCLOCKDISPLAY( this->display );
      if (Success != XvMCCreateSurface( this->display, context, 
					handler->surfaces + i)) {
	XVMCUNLOCKDISPLAY( this->display );
	pthread_mutex_unlock(&handler->mutex);
	return NULL;
      }
      XVMCUNLOCKDISPLAY( this->display );
      xprintf (this->xine, XINE_VERBOSITY_DEBUG,
	       "video_out_xxmc: Created surface %d\n",i);
      handler->surfInUse[i] = 1;
      handler->surfValid[i] = 1;
      pthread_mutex_unlock(&handler->mutex);
      return handler->surfaces + i;
    }
  }
  pthread_mutex_unlock(&handler->mutex);
  return NULL;
}

static void xxmc_xvmc_free_surface(xxmc_driver_t *this, XvMCSurface *surf) 
{
  xvmc_surface_handler_t *handler = &this->xvmc_surf_handler;
  unsigned
    index = surf - handler->surfaces;

  if (index >= XVMC_MAX_SURFACES) return; 
  pthread_mutex_lock(&handler->mutex);
  xprintf (this->xine, XINE_VERBOSITY_DEBUG,
	   "video_out_xxmc: Disposing of surface %d\n",index);
  handler->surfInUse[index]--;
  xxmc_xvmc_dump_surfaces(this);
  pthread_mutex_unlock(&handler->mutex);
}

int xxmc_xvmc_surface_valid(xxmc_driver_t *this, XvMCSurface *surf)
{
  xvmc_surface_handler_t *handler = &this->xvmc_surf_handler;
  unsigned
    index = surf - handler->surfaces;
  int ret;

  if (index >= XVMC_MAX_SURFACES) return 0;
  pthread_mutex_lock(&handler->mutex);
  ret = handler->surfValid[index];
  pthread_mutex_unlock(&handler->mutex);
  return ret;
}

static XvMCSubpicture *xxmc_xvmc_alloc_subpicture
                         (xxmc_driver_t *this, 
			  XvMCContext *context, unsigned short width,
			  unsigned short height, int xvimage_id)
{
  int i;
  xvmc_surface_handler_t *handler = &this->xvmc_surf_handler;
  int status;

  pthread_mutex_lock(&handler->mutex);
  xxmc_xvmc_dump_subpictures(this);
  for (i=0; i<XVMC_MAX_SUBPICTURES; ++i) {
    if (handler->subValid[i] && !handler->subInUse[i]) {
      XVMCLOCKDISPLAY( this->display );
      if (XvMCGetSubpictureStatus( this->display, handler->subpictures + i, 
				   &status)) {
	XVMCUNLOCKDISPLAY( this->display );
	continue;
      }
      XVMCUNLOCKDISPLAY( this->display );
      if (status & XVMC_DISPLAYING)
	continue;
      handler->subInUse[i] = 1;
      xxmc_xvmc_dump_subpictures(this);
      pthread_mutex_unlock(&handler->mutex);
      return handler->subpictures + i;
    }
  }
  for (i=0; i<XVMC_MAX_SUBPICTURES; ++i) {
    if (!handler->subInUse[i]) {
      XVMCLOCKDISPLAY( this->display );
      if (Success != XvMCCreateSubpicture( this->display, context, 
					   handler->subpictures + i,
					   width, height, xvimage_id)) {
	XVMCUNLOCKDISPLAY( this->display );
	pthread_mutex_unlock(&handler->mutex);
	return NULL;
      }
      XVMCUNLOCKDISPLAY( this->display );
      xprintf (this->xine, XINE_VERBOSITY_DEBUG,
	       "video_out_xxmc: Created subpicture %d\n",i);
      handler->subInUse[i] = 1;
      handler->subValid[i] = 1;
      pthread_mutex_unlock(&handler->mutex);
      return handler->subpictures + i;
    }
  }
  pthread_mutex_unlock(&handler->mutex);
  return NULL;
}

static void xxmc_xvmc_free_subpicture(xxmc_driver_t *this, XvMCSubpicture *sub) 
{

  xvmc_surface_handler_t *handler = &this->xvmc_surf_handler;
  unsigned
    index = sub - handler->subpictures;

  if (index >= XVMC_MAX_SUBPICTURES) return; 
  pthread_mutex_lock(&handler->mutex);
  xprintf (this->xine, XINE_VERBOSITY_DEBUG,
	   "video_out_xxmc: Disposing of subpicture %d\n",index);
  handler->subInUse[index] = 0;
  xxmc_xvmc_dump_subpictures(this);
  pthread_mutex_unlock(&handler->mutex);

}

/*
 * Callback used by decoder to check that surfaces are still valid,
 * and to lock the context so that it won't get destroyed during 
 * decoding.
 */


static int xxmc_lock_and_validate_surfaces(vo_frame_t *cur_frame,
					   vo_frame_t *fw_frame,
					   vo_frame_t *bw_frame,
					   unsigned pc_type)
{
  xxmc_driver_t 
    *driver = (xxmc_driver_t *) cur_frame->driver;
  xxmc_frame_t 
    *frame;

  xvmc_context_reader_lock( &driver->xvmc_lock );

  switch(pc_type) {
  case XINE_PICT_B_TYPE:
    frame = (xxmc_frame_t *) bw_frame;
    if (!xxmc_xvmc_surface_valid( driver, frame->xvmc_surf)) break;
    /* fall through */
  case XINE_PICT_P_TYPE:
    frame = (xxmc_frame_t *) fw_frame;
    if (!xxmc_xvmc_surface_valid( driver, frame->xvmc_surf)) break;
    /* fall through */
  default:
    frame = (xxmc_frame_t *) cur_frame;
    if (!xxmc_xvmc_surface_valid( driver, frame->xvmc_surf)) break;
    return 0;
  }
  
  xvmc_context_reader_unlock( &driver->xvmc_lock );
  return -1;
}

/*
 * Callback for decoder. Decoding temporarily halted. Release the context.
 */

static void xxmc_unlock_surfaces(vo_driver_t *this_gen)
{
  xxmc_driver_t 
    *driver = (xxmc_driver_t *) this_gen;
  
  xvmc_context_reader_unlock( &driver->xvmc_lock );
}

/*
 * Callback for decoder. 
 * Check that the surface is vaid and 
 * flush outstanding rendering requests on this surface.
 */

static void xvmc_flush(vo_frame_t *this_gen) 
{

  xxmc_frame_t
    *frame = (xxmc_frame_t *) this_gen;
  xxmc_driver_t
    *driver = (xxmc_driver_t *) this_gen->driver;

  xvmc_context_reader_lock( &driver->xvmc_lock );

  if ( ! xxmc_xvmc_surface_valid( driver, frame->xvmc_surf)) {
    frame->xxmc_data.result = 128;
    xvmc_context_reader_unlock( &driver->xvmc_lock );    
    return;
  }

  XVMCLOCKDISPLAY( driver->display );
  frame->xxmc_data.result = XvMCFlushSurface( driver->display, frame->xvmc_surf );
  XVMCUNLOCKDISPLAY( driver->display );

  xvmc_context_reader_unlock( &driver->xvmc_lock );

}


/*
 * Callback function for the VO-loop to duplicate frame data.
 * YV12 and YUY2 formats are taken care of in the xine-engine.
 * This one only deals with hardware surfaces and duplicates them
 * using a call to XvMCBlendSubpicture2 with a blank subpicture.
 */

static void xxmc_duplicate_frame_data(vo_frame_t *this_gen, 
				      vo_frame_t *original) 
{
  xxmc_frame_t *this = (xxmc_frame_t *) this_gen, 
    *orig = (xxmc_frame_t *) original;
  xxmc_driver_t *driver = (xxmc_driver_t *) this_gen->driver;
  xine_t *xine = driver->xine;
  xine_xxmc_t *xxmc;
  XvMCSubpicture *tmp;
  int need_dummy;

  if (original->format != XINE_IMGFMT_XXMC)
    return;
  xxmc = &orig->xxmc_data;
  xvmc_context_writer_lock( &driver->xvmc_lock);
  if (!xxmc_xvmc_surface_valid(driver,orig->xvmc_surf)) {
    xvmc_context_writer_unlock( &driver->xvmc_lock );
    return;
  }		
  this->xxmc_data = *xxmc;
  this->width = original->width;
  this->height = original->height;
  this->format = original->format;
  this->ratio = original->ratio;
  
  xxmc_frame_updates(driver,this,0); 

  /*
   * Allocate a dummy subpicture and copy using 
   * XvMCBlendsubpicture2. VLD implementations can do blending with a 
   * NULL subpicture. Use that if possible.
   */

  need_dummy = (xxmc->acceleration != XINE_XVMC_ACCEL_VLD);
  tmp = NULL;
  if (need_dummy) {
    tmp = xxmc_xvmc_alloc_subpicture( driver, &driver->context, 
				      this->width, this->height,
				      driver->xvmc_cap
				      [driver->xvmc_cur_cap].subPicType.id);
  }
  if (tmp || !need_dummy) {
    XVMCLOCKDISPLAY( driver->display );
    if (tmp) XvMCClearSubpicture(driver->display, tmp , 0,0, this->width,
				 this->height, 0);
    if (Success == XvMCBlendSubpicture2( driver->display, orig->xvmc_surf, 
					 this->xvmc_surf, tmp,
					 0,0,this->width, this->height, 
					 0,0,this->width, this->height)) {
      this->xxmc_data.decoded = 1;
    }
    XVMCUNLOCKDISPLAY( driver->display );
    if (tmp) xxmc_xvmc_free_subpicture( driver, tmp);
  }
  
  xvmc_context_writer_unlock( &driver->xvmc_lock );
  xprintf(xine, XINE_VERBOSITY_DEBUG, "Duplicated XvMC frame %d %d.\n",
	  this->width,this->height);
}   

static uint32_t xxmc_get_capabilities (vo_driver_t *this_gen) {
  xxmc_driver_t *this = (xxmc_driver_t *) this_gen;

  return this->capabilities;
}


static void xxmc_frame_field (vo_frame_t *vo_img, int which_field) 
{
  lprintf ("xvmc_frame_field\n");
}

static void xxmc_frame_dispose (vo_frame_t *vo_img) {
  xxmc_frame_t  *frame = (xxmc_frame_t *) vo_img ;
  xxmc_driver_t *this  = (xxmc_driver_t *) vo_img->driver;

  xprintf (this->xine, XINE_VERBOSITY_DEBUG, "Disposing of frame\n");

  xvmc_context_writer_lock( &this->xvmc_lock );
  if (this->xvmc_cap && frame->xvmc_surf) {
    xxmc_xvmc_free_surface( this, frame->xvmc_surf );
    frame->xvmc_surf = 0;
  }
  xvmc_context_writer_unlock( &this->xvmc_lock );

  if (frame->image) {

    if (this->use_shm) {
      XLockDisplay (this->display);
      XShmDetach (this->display, &frame->shminfo);
      XFree (frame->image);
      XUnlockDisplay (this->display);

      shmdt (frame->shminfo.shmaddr);
      shmctl (frame->shminfo.shmid, IPC_RMID, NULL);
    }
    else {
      if (frame->image->data) free(frame->image->data);
      XLockDisplay (this->display);
      XFree (frame->image);
      XUnlockDisplay (this->display);
    }
  }
  free (frame);
}

/*
 * Note that this one does NOT allocate the XvMC surface. That is
 * done by an additional decoder callback.
 */

static vo_frame_t *xxmc_alloc_frame (vo_driver_t *this_gen) {
  xxmc_driver_t  *this = (xxmc_driver_t *) this_gen; 
  xxmc_frame_t   *frame ;

  frame = (xxmc_frame_t *) xine_xmalloc (sizeof (xxmc_frame_t));
  if (!frame)
    return NULL;
  
  pthread_mutex_init (&frame->vo_frame.mutex, NULL);
  frame->xvmc_surf = NULL;

  /*
   * supply required functions
   */

  frame->vo_frame.proc_slice = NULL;
  frame->vo_frame.proc_frame = NULL;
  frame->vo_frame.proc_duplicate_frame_data = NULL;
  frame->vo_frame.field      = xxmc_frame_field;
  frame->vo_frame.dispose    = xxmc_frame_dispose;
  frame->vo_frame.driver     = this_gen;
  frame->last_sw_format      = 0;
  frame->vo_frame.accel_data = &frame->xxmc_data;
  frame->image               = NULL;

  xprintf (this->xine, XINE_VERBOSITY_DEBUG, "Allocating frame\n");

  return (vo_frame_t *) frame;
}

static int HandleXError (Display *display, XErrorEvent *xevent) {
  char str [1024];
  
  XGetErrorText (display, xevent->error_code, str, 1024);
  printf ("received X error event: %s\n", str);
  gX11Fail = 1;

  return 0;
}

/* called xlocked */
static void x11_InstallXErrorHandler (xxmc_driver_t *this) {
  this->x11_old_error_handler = XSetErrorHandler (HandleXError);
  XSync(this->display, False);
}

/* called xlocked */
static void x11_DeInstallXErrorHandler (xxmc_driver_t *this) {
  XSetErrorHandler (this->x11_old_error_handler);
  XSync(this->display, False);
  this->x11_old_error_handler = NULL;
}

/* called xlocked */
static XvImage *create_ximage (xxmc_driver_t *this, XShmSegmentInfo *shminfo,
			       int width, int height, int format) {
  unsigned int  xv_format;
  XvImage      *image = NULL;

  if (this->use_pitch_alignment) {
    width = (width + 7) & ~0x7;
  }

  switch (format) {
  case XINE_IMGFMT_YV12: 
    xv_format = this->xv_format_yv12;
    break;
  case XINE_IMGFMT_YUY2:
    xv_format = this->xv_format_yuy2;
    break;
  case FOURCC_IA44:
  case FOURCC_AI44:
    xv_format = format;
    break;
  default:
    xprintf (this->xine, XINE_VERBOSITY_DEBUG, "create_ximage: unknown format %08x\n",format);
    _x_abort();
  }

  if (this->use_shm) {

    /*
     * try shm
     */

    gX11Fail = 0;
    x11_InstallXErrorHandler (this);

    image = XvShmCreateImage(this->display, this->xv_port, xv_format, 0,
			     width, height, shminfo);

    if (image == NULL )  {
      xprintf(this->xine, XINE_VERBOSITY_LOG,
	      _("video_out_xxmc: XvShmCreateImage failed\n"
		"video_out_xxmc: => not using MIT Shared Memory extension.\n"));
      this->use_shm = 0;
      goto finishShmTesting;
    }

    shminfo->shmid = shmget(IPC_PRIVATE, image->data_size, IPC_CREAT | 0777);

    if (image->data_size==0) {
      xprintf(this->xine, XINE_VERBOSITY_LOG,
	      _("video_out_xxmc: XvShmCreateImage returned a zero size\n"
		"video_out_xxmc: => not using MIT Shared Memory extension.\n"));
      this->use_shm = 0;
      goto finishShmTesting;
    }

    if (shminfo->shmid < 0 ) {
      xprintf(this->xine, XINE_VERBOSITY_LOG,
	      _("video_out_xxmc: shared memory error in shmget: %s\n"
		"video_out_xxmc: => not using MIT Shared Memory extension.\n"), strerror(errno));
      this->use_shm = 0;
      goto finishShmTesting;
    }

    shminfo->shmaddr  = (char *) shmat(shminfo->shmid, 0, 0);

    if (shminfo->shmaddr == NULL) {
      xprintf(this->xine, XINE_VERBOSITY_DEBUG,
	      "video_out_xxmc: shared memory error (address error NULL)\n");
      this->use_shm = 0;
      goto finishShmTesting;
    }

    if (shminfo->shmaddr == ((char *) -1)) {
      xprintf(this->xine, XINE_VERBOSITY_DEBUG,
	      "video_out_xxmc: shared memory error (address error)\n");
      this->use_shm = 0;
      goto finishShmTesting;
    }

    shminfo->readOnly = False;
    image->data       = shminfo->shmaddr;

    XShmAttach(this->display, shminfo);

    XSync(this->display, False);
    shmctl(shminfo->shmid, IPC_RMID, 0);

    if (gX11Fail) {
      xprintf(this->xine, XINE_VERBOSITY_LOG,
	      _("video_out_xxmc: x11 error during shared memory XImage creation\n"
		"video_out_xxmc: => not using MIT Shared Memory extension.\n"));
      shmdt (shminfo->shmaddr);
      shmctl (shminfo->shmid, IPC_RMID, 0);
      shminfo->shmid = -1;
      this->use_shm  = 0;
      goto finishShmTesting;
    }

    /*
     * Now that the Xserver has learned about and attached to the
     * shared memory segment,  delete it.  It's actually deleted by
     * the kernel when all users of that segment have detached from
     * it.  Gives an automatic shared memory cleanup in case we crash.
     */
    shmctl (shminfo->shmid, IPC_RMID, 0);
    shminfo->shmid = -1;

  finishShmTesting:
    x11_DeInstallXErrorHandler(this);
  }


  /*
   * fall back to plain Xv if necessary
   */

  if (!this->use_shm) {
    char *data;

    switch (format) {
    case XINE_IMGFMT_YV12:
      data = malloc (width * height * 3/2);
      break;
    case XINE_IMGFMT_YUY2:
      data = malloc (width * height * 2);
      break;
    case FOURCC_IA44:
    case FOURCC_AI44:
      data = malloc (width * height);
      break;
    default:
      xprintf (this->xine, XINE_VERBOSITY_DEBUG, "create_ximage: unknown format %08x\n",format);
      _x_abort();
    }

    image = XvCreateImage (this->display, this->xv_port,
			   xv_format, data, width, height);
  }
  return image;
}


/*
 * Utility functions for the main update surface callback.
 */


static void xxmc_dispose_context(xxmc_driver_t *driver)
{
  if (driver->contextActive) {
    if (driver->xvmc_accel & (XINE_XVMC_ACCEL_MOCOMP | XINE_XVMC_ACCEL_IDCT)) {
      xvmc_macroblocks_t *macroblocks = &driver->macroblocks;

      XvMCDestroyMacroBlocks( driver->display, &macroblocks->macro_blocks );
      XvMCDestroyBlocks( driver->display , &macroblocks->blocks );
    }
    
    xprintf(driver->xine, XINE_VERBOSITY_LOG,
	    "video_out_xxmc: Freeing up XvMC Surfaces and subpictures.\n");
    if (driver->xvmc_palette) free(driver->xvmc_palette);
    _x_dispose_xx44_palette( &driver->palette );
    xxmc_xvmc_destroy_subpictures( driver );
    xxmc_xvmc_destroy_surfaces( driver );
    xprintf(driver->xine, XINE_VERBOSITY_LOG,
	    "video_out_xxmc: Freeing up XvMC Context.\n");
    XLockDisplay (driver->display);
    if (driver->subImage) 
      dispose_ximage(driver, &driver->subShmInfo, driver->subImage); 
    driver->subImage = NULL;
    XUnlockDisplay (driver->display);
    XVMCLOCKDISPLAY( driver->display );
    XvMCDestroyContext( driver->display, &driver->context);
    XVMCUNLOCKDISPLAY( driver->display );
    driver->contextActive = 0;
    driver->hwSubpictures = 0; 
    driver->xvmc_accel = 0;
  }
}

/*
 * Find a suitable XvMC Context according to the acceleration request
 * passed to us in the xxmc variable, and to the acceleration type
 * priority set up in this plugin. Result is returned in 
 * driver->xvmc_cur_cap.
 */

static int xxmc_find_context(xxmc_driver_t *driver, xine_xxmc_t *xxmc,
			     unsigned width, unsigned height)
{
  int i,k,found;
  xvmc_capabilities_t *curCap;
  unsigned request_mpeg_flags, request_accel_flags;

  request_mpeg_flags = xxmc->mpeg;
  found = 0;
  curCap = NULL;
  
  for (k = 0; k < NUM_ACCEL_PRIORITY; ++k) {
    request_accel_flags = xxmc->acceleration & accel_priority[k];
    if (!request_accel_flags) continue;

    curCap = driver->xvmc_cap;
    for (i =0; i < driver->xvmc_num_cap; ++i) {
      xprintf(driver->xine, XINE_VERBOSITY_LOG,
	      "video_out_xxmc: Surface type %d. Capabilities 0x%8x 0x%8x\n",i,
	      curCap->mpeg_flags,curCap->accel_flags);
      xprintf(driver->xine, XINE_VERBOSITY_LOG,
	      "video_out_xxmc:   Requests: 0x%8x 0x%8x\n",
	      request_mpeg_flags,request_accel_flags);
      if (((curCap->mpeg_flags & request_mpeg_flags) == request_mpeg_flags) &&
	  ((curCap->accel_flags & request_accel_flags)) &&
	  (width <= curCap->max_width) &&
	  (height <= curCap->max_height)) {
	found = 1;
	break;
      }
      curCap++;
    }
    if ( found ) {
      driver->xvmc_cur_cap = i;
      break;
    }
  }
  if ( found ) {
    driver->xvmc_accel = request_accel_flags;
    driver->unsigned_intra = curCap->flags & XVMC_INTRA_UNSIGNED;
    return 1;
  }
  driver->xvmc_accel = 0;
  return 0;
}   

static int xxmc_create_context(xxmc_driver_t *driver, unsigned width, unsigned height)
{
  xvmc_capabilities_t *curCap;

  curCap = driver->xvmc_cap + driver->xvmc_cur_cap;
  xprintf(driver->xine, XINE_VERBOSITY_LOG,
	  "video_out_xxmc: Creating new XvMC Context %d\n",curCap->type_id);
  XVMCLOCKDISPLAY( driver->display );
  if (Success == XvMCCreateContext( driver->display, driver->xv_port, 
				    curCap->type_id, width,
				    height, driver->context_flags, 
				    &driver->context)) {
    driver->xvmc_mpeg = curCap->mpeg_flags;
    driver->xvmc_width = width;
    driver->xvmc_height = height;
    driver->contextActive = 1;
  }
  XVMCUNLOCKDISPLAY( driver->display );
  return driver->contextActive;
}

static void xxmc_setup_subpictures(xxmc_driver_t *driver, unsigned width, unsigned height)
{
  xvmc_capabilities_t *curCap;
  XvMCSubpicture *sp;

  if (driver->contextActive) {

    /*
     * Determine if we can use hardware subpictures, and in that case, set up an
     * XvImage that we can use for blending.
     */    
    curCap = driver->xvmc_cap + driver->xvmc_cur_cap;

    if ((width > curCap->sub_max_width) || 
	(height > curCap->sub_max_height)) return;

    if ((driver->xvmc_backend_subpic = (curCap->flags & XVMC_BACKEND_SUBPICTURE))) 
      xprintf(driver->xine, XINE_VERBOSITY_LOG,
	      "video_out_xxmc: Using Backend subpictures.\n");
    
    if (!driver->subImage) {
      /*
       * Note: If other image formats than xx44 are to be used here, they must be
       * translated to XINE_IMGFMT_XXX, since that is what create_ximage 
       * expects.
       */

      XLockDisplay (driver->display);

      driver->subImage = 
	create_ximage(driver, &driver->subShmInfo, width, height, curCap->subPicType.id);
      XUnlockDisplay (driver->display);
      if (NULL == driver->subImage) {
	xprintf(driver->xine, XINE_VERBOSITY_LOG,
		"video_out_xxmc: Failed allocating XvImage for supbictures.\n");
	return;
      }
    }

    sp = xxmc_xvmc_alloc_subpicture( driver, &driver->context, width, 
				     height, curCap->subPicType.id);
    if (sp == NULL) return;

    _x_init_xx44_palette( &driver->palette, sp->num_palette_entries);
    driver->xvmc_palette = (char *) xine_xmalloc(sp->num_palette_entries 
						 * sp->entry_bytes);
    xxmc_xvmc_free_subpicture( driver, sp);
    if (driver->xvmc_palette == NULL) return; 
    driver->hwSubpictures = 1;
  }
}

static int xxmc_mocomp_create_macroblocks(xxmc_driver_t *driver,
					  xxmc_frame_t *frame,
					  int slices)
{
    Status ret;
    xvmc_macroblocks_t *macroblocks = &driver->macroblocks;
    xine_xxmc_t *xxmc = (xine_xxmc_t *) frame->vo_frame.accel_data;

    slices = (slices * driver->xvmc_width) / 16;
    ret = XvMCCreateMacroBlocks(driver->display, &driver->context, slices,
				&macroblocks->macro_blocks);
    if (ret) return 0;
    ret = XvMCCreateBlocks(driver->display, &driver->context, slices*6,
			   &macroblocks->blocks);
    if (ret) return 0;

    macroblocks->xine_mc.blockbaseptr = macroblocks->blocks.blocks;
    macroblocks->xine_mc.blockptr = macroblocks->xine_mc.blockbaseptr;
    macroblocks->num_blocks = 0;
    macroblocks->macroblockbaseptr = macroblocks->macro_blocks.macro_blocks;
    macroblocks->macroblockptr = macroblocks->macroblockbaseptr;
    macroblocks->slices = slices;
    xxmc->xvmc.macroblocks = (xine_macroblocks_t *)macroblocks;

    return 1;
}					 
 
static void xvmc_check_colorkey_properties(xxmc_driver_t *driver)
{
  int num,i;
  XvAttribute *xvmc_attributes;
  Atom ap;

  /*
   * Determine if the context is of "Overlay" type. If so,
   * check whether we can autopaint.
   */

  driver->have_xvmc_autopaint = 0;
  if (driver->context_flags & XVMC_OVERLAID_SURFACE) {
    XVMCLOCKDISPLAY( driver->display );
    xvmc_attributes = XvMCQueryAttributes( driver->display, 
					   &driver->context,
					   &num);
    if (xvmc_attributes) {
      for (i=0; i<num; ++i) {
	if (strcmp("XV_AUTOPAINT_COLORKEY", xvmc_attributes[i].name) == 0) {
	  ap = XInternAtom (driver->display, "XV_AUTOPAINT_COLORKEY", False);
	  XvMCSetAttribute(driver->display, &driver->context,ap,
			   driver->props[VO_PROP_AUTOPAINT_COLORKEY].value);
	  driver->have_xvmc_autopaint = 1;
	}			 
      }
    }
    XFree(xvmc_attributes);
    XVMCUNLOCKDISPLAY( driver->display );
    driver->xvmc_xoverlay_type = X11OSD_COLORKEY;
  } else {
    driver->xvmc_xoverlay_type = X11OSD_SHAPED;
  }
}


static int xxmc_xvmc_update_context(xxmc_driver_t *driver, xxmc_frame_t *frame,
				    uint32_t width, uint32_t height) 
{
  xine_xxmc_t *xxmc = &frame->xxmc_data;

  /*
   * Are we at all capable of doing XvMC ?
   */ 

  
  if (driver->xvmc_cap == 0) 
    return 0;

  xprintf(driver->xine, XINE_VERBOSITY_LOG,
	  "video_out_xxmc: New format. Need to change XvMC Context.\n"
	  "width: %d height: %d mpeg: %d acceleration: %d\n", width, height,
	    xxmc->mpeg, xxmc->acceleration);
  
  if (frame->xvmc_surf)
    xxmc_xvmc_free_surface( driver , frame->xvmc_surf);
  frame->xvmc_surf = NULL;

  xxmc_dispose_context( driver );
  
  if (xxmc_find_context( driver, xxmc, width, height )) {
    xxmc_create_context( driver, width, height);
    xvmc_check_colorkey_properties( driver );
    xxmc_setup_subpictures(driver, width, height);
    if ((driver->xvmc_accel & 
	 (XINE_XVMC_ACCEL_MOCOMP | XINE_XVMC_ACCEL_IDCT))) {
      if (!xxmc_mocomp_create_macroblocks(driver, frame, 1)) {
	lprintf("video_out_xxmc: ERROR: Macroblock allocation failed\n");
	xxmc_dispose_context( driver );
      }
    }
  }

  if (!driver->contextActive) {
    printf("video_out_xxmc: Using software decoding for this stream.\n");
    driver->xvmc_accel = 0;
  } else {
    printf("video_out_xxmc: Using hardware decoding for this stream.\n");
  }
    
  driver->xvmc_mpeg = xxmc->mpeg;
  driver->xvmc_width = width;
  driver->xvmc_height = height;
  return driver->contextActive;
}

static void xxmc_frame_updates(xxmc_driver_t *driver, 
			       xxmc_frame_t *frame, 
			       int init_macroblocks)
{
  xine_xxmc_t *xxmc = &frame->xxmc_data;

  /*
   * If we have changed context since the surface was updated, xvmc_surf
   * is either NULL or invalid. If it is invalid. Set it to NULL.
   * Also if there are other users of this surface, deregister our use of
   * it and later try to allocate a new, fresh one.
   */

  if (frame->xvmc_surf) {
    if (! xxmc_xvmc_surface_valid( driver, frame->xvmc_surf )) {
      xxmc_xvmc_free_surface(driver , frame->xvmc_surf);
      frame->xvmc_surf = NULL;
    }
  }

  /*
   * If it is NULL create a new surface.
   */ 

  if (frame->xvmc_surf == NULL) {
    if (NULL == (frame->xvmc_surf = 
		 xxmc_xvmc_alloc_surface( driver, &driver->context))) {
      fprintf(stderr, "video_out_xxmc: ERROR: Accelerated surface allocation failed.\n"
	      "video_out_xxmc: You are probably out of framebuffer memory.\n"
	      "video_out_xxmc: Falling back to software decoding.\n");
      driver->xvmc_accel = 0;
      xxmc_dispose_context( driver );
      return;
    }        
    xxmc->xvmc.macroblocks = (xine_macroblocks_t *) &driver->macroblocks;
    xxmc->xvmc.macroblocks->xvmc_accel = (driver->unsigned_intra) ?
      0 : XINE_VO_SIGNED_INTRA;
    switch(driver->xvmc_accel) {
    case XINE_XVMC_ACCEL_IDCT:
      xxmc->xvmc.macroblocks->xvmc_accel |= XINE_VO_IDCT_ACCEL;
      break;
    case XINE_XVMC_ACCEL_MOCOMP:
      xxmc->xvmc.macroblocks->xvmc_accel |= XINE_VO_MOTION_ACCEL;
      break;
    default:
      xxmc->xvmc.macroblocks->xvmc_accel = 0;
    }


    xxmc->proc_xxmc_flush = xvmc_flush;
    xxmc->proc_xxmc_lock_valid = xxmc_lock_and_validate_surfaces;
    xxmc->proc_xxmc_unlock = xxmc_unlock_surfaces;
    
    xxmc->xvmc.proc_macro_block = xxmc_xvmc_proc_macro_block;
    frame->vo_frame.proc_duplicate_frame_data = xxmc_duplicate_frame_data;
#ifdef HAVE_VLDXVMC
    xxmc->proc_xxmc_begin = xvmc_vld_frame;
    xxmc->proc_xxmc_slice = xvmc_vld_slice;
#endif
  }

  if (init_macroblocks) {
    driver->macroblocks.num_blocks = 0;
    driver->macroblocks.macroblockptr = driver->macroblocks.macroblockbaseptr;
    driver->macroblocks.xine_mc.blockptr = 
      driver->macroblocks.xine_mc.blockbaseptr;
  }
  xxmc->acceleration = driver->xvmc_accel;
}


/* called xlocked */
static void dispose_ximage (xxmc_driver_t *this,
			    XShmSegmentInfo *shminfo,
			    XvImage *myimage) {

  if (this->use_shm) {

    XShmDetach (this->display, shminfo);
    XFree (myimage);
    shmdt (shminfo->shmaddr);
    if (shminfo->shmid >= 0) {
      shmctl (shminfo->shmid, IPC_RMID, 0);
      shminfo->shmid = -1;
    }

  } 
  else {
    if (myimage->data) free(myimage->data);
    XFree (myimage);
  }

}


static void xxmc_do_update_frame_xv(vo_driver_t *this_gen,
				    vo_frame_t *frame_gen,
				    uint32_t width, uint32_t height,
				    double ratio, int format, int flags)
{
  xxmc_driver_t  *this  = (xxmc_driver_t *) this_gen;
  xxmc_frame_t   *frame = (xxmc_frame_t *) frame_gen;

  if (this->use_pitch_alignment) {
    width = (width + 7) & ~0x7;
  }
  
  if ((frame->width != width)
      || (frame->height != height)
      || (frame->last_sw_format != format)) {
    
    frame->last_sw_format = format;
    XLockDisplay (this->display);
    
    /*
     * (re-) allocate xvimage
     */
    
    if (frame->image) {
      dispose_ximage (this, &frame->shminfo, frame->image);
      frame->image = NULL;
    }
    
    frame->image = create_ximage (this, &frame->shminfo, width, height, format);
    
    if(format == XINE_IMGFMT_YUY2) {
      frame->vo_frame.pitches[0] = frame->image->pitches[0];
      frame->vo_frame.base[0] = frame->image->data + frame->image->offsets[0];
    } 
    else {
      frame->vo_frame.pitches[0] = frame->image->pitches[0];
      frame->vo_frame.pitches[1] = frame->image->pitches[2];
      frame->vo_frame.pitches[2] = frame->image->pitches[1];
      frame->vo_frame.base[0] = frame->image->data + frame->image->offsets[0];
      frame->vo_frame.base[1] = frame->image->data + frame->image->offsets[2];
      frame->vo_frame.base[2] = frame->image->data + frame->image->offsets[1];
    }
    
    XUnlockDisplay (this->display);
  }

  frame->ratio = ratio;
  frame->width  = width;
  frame->height = height;
  frame->format = format;   
  frame->vo_frame.format = frame->format;
}

/*
 * Check if we need to change XvMC context due to an
 * acceleration request change.
 */

static int xxmc_accel_update(xxmc_driver_t *driver,
			     uint32_t last_request,
			     uint32_t new_request)
{
  int k;

  /*
   * Same acceleration request. No need to change.
   */ 

  if (last_request == new_request) return 0;

  /*
   * Current acceleration not valid. Change.
   */

  if ((driver->xvmc_accel & new_request) == 0) return 1;

  /*
   * Test for possible use of a higher acceleration level.
   */

  for (k = 0; k < NUM_ACCEL_PRIORITY; ++k) {
    if (last_request & accel_priority[k]) return 0;
    if (new_request & accel_priority[k]) return 1;
  }

  /* 
   * Should never get here.
   */

  return 0;
}


static void xxmc_do_update_frame(vo_driver_t *this_gen,
				 vo_frame_t *frame_gen,
				 uint32_t width, uint32_t height,
				 double ratio, int format, int flags) {

  xxmc_driver_t  *this  = (xxmc_driver_t *) this_gen;
  xxmc_frame_t   *frame = (xxmc_frame_t *) frame_gen;

  if ( XINE_IMGFMT_XXMC == format ) {
    xine_xxmc_t *xxmc = &frame->xxmc_data;

    xvmc_context_writer_lock( &this->xvmc_lock);
    if (xxmc_accel_update(this, this->last_accel_request, xxmc->acceleration) ||
	(this->xvmc_mpeg != xxmc->mpeg) ||
	(this->xvmc_width != width) ||
	(this->xvmc_height != height)) {
      this->last_accel_request = xxmc->acceleration;
      xxmc_xvmc_update_context(this, frame, width, height);
    } else {
      this->last_accel_request = xxmc->acceleration;
    }

    if (this->contextActive) 
      xxmc_frame_updates(this, frame, 1);

    xxmc_do_update_frame_xv(this_gen, frame_gen, width, height, ratio, 
			    xxmc->fallback_format, flags);

    if (!this->contextActive) {
      xxmc->acceleration = 0;
      xxmc->xvmc.macroblocks = 0;
      frame->vo_frame.proc_duplicate_frame_data = NULL;
    } else {
      frame->format = format;
      frame->vo_frame.format = format;
    }

    xvmc_context_writer_unlock( &this->xvmc_lock);
    
  } else {
    frame->vo_frame.proc_duplicate_frame_data = NULL;
    xxmc_do_update_frame_xv(this_gen, frame_gen, width, height, ratio, 
			    format, flags);
  }
}

static void xxmc_update_frame_format(vo_driver_t *this_gen,
				     vo_frame_t *frame_gen,
				     uint32_t width, uint32_t height,
				     double ratio, int format, int flags) 
{

  if (format != XINE_IMGFMT_XXMC) {
    xxmc_do_update_frame(this_gen, frame_gen, width, height,
			 ratio, format, flags);
  } else {

    /*
     * More parameters are needed to xxmc_do_update_frame().
     * Register the function as a callback and return.
     * The decoder needs to call the callback with more parameters
     * in the xine_xxmc_t structure.
     */

    xine_xxmc_t *xxmc = (xine_xxmc_t *)frame_gen->accel_data;
    xxmc->decoded = 0;
    xxmc->proc_xxmc_update_frame = xxmc_do_update_frame;
    frame_gen->proc_duplicate_frame_data = xxmc_duplicate_frame_data; 
  }
}

/*
 * From Xv.
 */

static int xxmc_clean_output_area (xxmc_driver_t *this, int xvmc_active) {
  int i, autopainting, ret;

  XLockDisplay (this->display);

  XSetForeground (this->display, this->gc, this->black.pixel);

  for( i = 0; i < 4; i++ ) {
    if( this->sc.border[i].w && this->sc.border[i].h ) {
      XFillRectangle(this->display, this->drawable, this->gc,
		     this->sc.border[i].x, this->sc.border[i].y,
		     this->sc.border[i].w, this->sc.border[i].h);
    }
  }
  
  /*
   * XvMC does not support autopainting regardless of whether there's an
   * Xv attribute for it. However, if there is an XvMC attribute for 
   * autopainting, we should be able to assume it is supported. 
   * That support is checked whenever a context is changed.
   */

  autopainting = (this->props[VO_PROP_AUTOPAINT_COLORKEY].value == 1); 
  if ((xvmc_active && 
       (this->context_flags & XVMC_OVERLAID_SURFACE) &&
       (! this->have_xvmc_autopaint ||
	! autopainting)) ||
      (! xvmc_active && !autopainting)) {
    XSetForeground (this->display, this->gc, this->colorkey);
    XFillRectangle (this->display, this->drawable, this->gc,
		    this->sc.output_xoffset, this->sc.output_yoffset,
		    this->sc.output_width, this->sc.output_height);
    ret = 1;
  } else {
    ret = 0;
  }
  
  if (this->xoverlay) {
    x11osd_resize (this->xoverlay, this->sc.gui_width, this->sc.gui_height);
    this->ovl_changed = 1;
  }
  
  XUnlockDisplay (this->display);
  return ret;
}

/*
 * convert delivered height/width to ideal width/height
 * taking into account aspect ratio and zoom factor
 */

static void xxmc_compute_ideal_size (xxmc_driver_t *this) {
  _x_vo_scale_compute_ideal_size( &this->sc );
}


/*
 * make ideal width/height "fit" into the gui
 */

static void xxmc_compute_output_size (xxmc_driver_t *this) {

  _x_vo_scale_compute_output_size( &this->sc );

}


static void xxmc_check_xoverlay_type(xxmc_driver_t *driver, xxmc_frame_t *frame) 

{
  int
    new_overlay_type = (frame->format == XINE_IMGFMT_XXMC) ? 
    driver->xvmc_xoverlay_type : driver->xv_xoverlay_type;
  if (driver->xoverlay_type != new_overlay_type) {
    printf("Warning! Changing xoverlay\n");
    x11osd_destroy( driver->xoverlay );
    driver->xoverlay = x11osd_create( driver->xine, driver->display,
				      driver->screen, driver->drawable,
				      new_overlay_type);
    driver->xoverlay_type = new_overlay_type;
  }
}


static void xxmc_overlay_begin (vo_driver_t *this_gen, 
				vo_frame_t *frame_gen, int changed) {
  xxmc_driver_t  *this = (xxmc_driver_t *) this_gen;
  xxmc_frame_t *frame = (xxmc_frame_t *) frame_gen;


  this->ovl_changed += changed;
  
  xvmc_context_reader_lock( &this->xvmc_lock );
  if ((frame->format == XINE_IMGFMT_XXMC) && 
      !xxmc_xvmc_surface_valid(this, frame->xvmc_surf)) {
    xvmc_context_reader_unlock( &this->xvmc_lock );
    return;
  }

  if( this->ovl_changed && this->xoverlay ) {
    
    XLockDisplay (this->display);
    xxmc_check_xoverlay_type(this, frame);
    x11osd_clear(this->xoverlay); 
    XUnlockDisplay (this->display);
  } 
  if (this->ovl_changed && (frame->format == XINE_IMGFMT_XXMC) && 
      this->hwSubpictures ) {

    this->new_subpic = xxmc_xvmc_alloc_subpicture
      ( this, &this->context, this->xvmc_width, 
	this->xvmc_height, 
	this->xvmc_cap[this->xvmc_cur_cap].subPicType.id);

    if (this->new_subpic) {
      this->first_overlay = 1;
      XVMCLOCKDISPLAY( this->display );
      XvMCClearSubpicture(this->display, this->new_subpic, 0,0, 
			  this->xvmc_width,
			  this->xvmc_height, 0x00);
      XVMCUNLOCKDISPLAY( this->display );
      _x_clear_xx44_palette(&this->palette);
    }
  }
  xvmc_context_reader_unlock( &this->xvmc_lock );
  
  this->alphablend_extra_data.offset_x = frame_gen->overlay_offset_x;
  this->alphablend_extra_data.offset_y = frame_gen->overlay_offset_y;
}

static void xxmc_overlay_end (vo_driver_t *this_gen, vo_frame_t *vo_img) 
{
  xxmc_driver_t  *this = (xxmc_driver_t *) this_gen;
  xxmc_frame_t   *frame = (xxmc_frame_t *) vo_img;

  
  if( this->ovl_changed && this->xoverlay ) {
    XLockDisplay (this->display);
    x11osd_expose(this->xoverlay);
    XUnlockDisplay (this->display);
  } 
  if ((frame->format == XINE_IMGFMT_XXMC) && this->hwSubpictures) {
    LOCK_AND_SURFACE_VALID( this, frame->xvmc_surf );
    if (this->ovl_changed) {
      if (this->old_subpic) {
	xxmc_xvmc_free_subpicture(this, this->old_subpic);
	this->old_subpic = NULL;
      }
      if (this->new_subpic) {
	this->old_subpic = this->new_subpic;
	this->new_subpic = NULL;
	_x_xx44_to_xvmc_palette( &this->palette, this->xvmc_palette,
			      0, this->old_subpic->num_palette_entries,
			      this->old_subpic->entry_bytes,
			      this->reverse_nvidia_palette ? "YVU" :
			      this->old_subpic->component_order);
	XVMCLOCKDISPLAY( this->display );
	XvMCSetSubpicturePalette( this->display, this->old_subpic, 
				  this->xvmc_palette);
	XvMCFlushSubpicture( this->display , this->old_subpic);
	XvMCSyncSubpicture( this->display, this->old_subpic );
	XVMCUNLOCKDISPLAY( this->display );
      }
    }
    if (this->old_subpic && (! this->first_overlay)) {
      XVMCLOCKDISPLAY( this->display );
      if (this->xvmc_backend_subpic ) {
	XvMCBlendSubpicture( this->display, frame->xvmc_surf,
			     this->old_subpic,0,0,this->xvmc_width, 
			     this->xvmc_height, 0, 0,
			     this->xvmc_width, this->xvmc_height );
      } else {
	XvMCBlendSubpicture2( this->display, frame->xvmc_surf, 
			      frame->xvmc_surf,
			      this->old_subpic, 0,0,this->xvmc_width, 
			      this->xvmc_height,0,0,this->xvmc_width, 
			      this->xvmc_height);
      }
      XVMCUNLOCKDISPLAY( this->display );
    }
    xvmc_context_reader_unlock(&this->xvmc_lock );
  }
  this->ovl_changed = 0;
}


static void xxmc_overlay_blend (vo_driver_t *this_gen, vo_frame_t *frame_gen, 
				vo_overlay_t *overlay) 
{
  xxmc_driver_t  *this = (xxmc_driver_t *) this_gen;
  xxmc_frame_t   *frame = (xxmc_frame_t *) frame_gen;

  if (overlay->rle) {
    if( overlay->unscaled ) {
      if( this->ovl_changed && this->xoverlay ) {
        XLockDisplay (this->display);
        x11osd_blend(this->xoverlay, overlay); 
        XUnlockDisplay (this->display);
      }
    } else if (frame->format == XINE_IMGFMT_XXMC) {
      if (this->ovl_changed && this->hwSubpictures) {
	if (this->new_subpic) {
	  LOCK_AND_SURFACE_VALID( this, frame->xvmc_surf );
	  if (this->first_overlay) {
	    memset(this->subImage->data,0,this->subImage->width*
		   this->subImage->height);
	    this->first_overlay = 0;
	  }
	  _x_blend_xx44(this->subImage->data, overlay, this->subImage->width, 
		     this->subImage->height, this->subImage->width,
                     &this->alphablend_extra_data,
		     &this->palette, (this->subImage->id == FOURCC_IA44));
	  XVMCLOCKDISPLAY( this->display );
	  XvMCCompositeSubpicture( this->display, this->new_subpic, 
				   this->subImage, 
				   overlay->x, overlay->y,overlay->width,
				   overlay->height,
				   overlay->x, overlay->y);
	  XVMCUNLOCKDISPLAY( this->display );
	  xvmc_context_reader_unlock( &this->xvmc_lock ); 
        }
      }
    } else {      
      if (frame->format == XINE_IMGFMT_YV12) {
        _x_blend_yuv(frame->vo_frame.base, overlay, 
		  frame->width, frame->height, frame->vo_frame.pitches,
                  &this->alphablend_extra_data);
      } else {
	_x_blend_yuy2(frame->vo_frame.base[0], overlay, 
		   frame->width, frame->height, frame->vo_frame.pitches[0],
                   &this->alphablend_extra_data);
      }
    }
  }
}

static void xxmc_add_recent_frame (xxmc_driver_t *this, xxmc_frame_t *frame) 
{
  int i;
  i = VO_NUM_RECENT_FRAMES-1;
  if( this->recent_frames[i] ) {
    this->recent_frames[i]->vo_frame.free
      (&this->recent_frames[i]->vo_frame);
  }
  for( ; i ; i-- )
    this->recent_frames[i] = this->recent_frames[i-1];

  this->recent_frames[0] = frame;
}

static int xxmc_redraw_needed (vo_driver_t *this_gen) 
{
  xxmc_driver_t  *this = (xxmc_driver_t *) this_gen;
  int           ret  = 0;
  
  if( this->cur_frame ) {

    this->sc.delivered_height = this->cur_frame->height;
    this->sc.delivered_width  = this->cur_frame->width;
    this->sc.delivered_ratio  = this->cur_frame->ratio;

    this->sc.crop_left        = this->cur_frame->vo_frame.crop_left;
    this->sc.crop_right       = this->cur_frame->vo_frame.crop_right;
    this->sc.crop_top         = this->cur_frame->vo_frame.crop_top;
    this->sc.crop_bottom      = this->cur_frame->vo_frame.crop_bottom;

    xxmc_compute_ideal_size(this);

    if( _x_vo_scale_redraw_needed( &this->sc ) ) {

      xxmc_compute_output_size (this);
      
      xxmc_clean_output_area 
	(this, (this->cur_frame->format == XINE_IMGFMT_XXMC));

      ret = 1;
    }
  }
  else
    ret = 1;

  return ret;
}

static void xxmc_display_frame (vo_driver_t *this_gen, vo_frame_t *frame_gen) 
{
  xxmc_driver_t  *this  = (xxmc_driver_t *) this_gen;
  xxmc_frame_t   *frame = (xxmc_frame_t *) frame_gen;
  xine_xxmc_t *xxmc = &frame->xxmc_data;
  int first_field;

  /*
   * queue frames (deinterlacing)
   * free old frames
   */

  xvmc_context_reader_lock( &this->xvmc_lock );

  xxmc_add_recent_frame (this, frame); /* deinterlacing */

  if ((frame->format == XINE_IMGFMT_XXMC) &&
      (!xxmc->decoded || !xxmc_xvmc_surface_valid(this, frame->xvmc_surf))) {
    xvmc_context_reader_unlock( &this->xvmc_lock );
    return;
  }

  this->cur_frame = frame;


  /*
   * let's see if this frame is different in size / aspect
   * ratio from the previous one
   */
  if ( (frame->width != this->sc.delivered_width)
       || (frame->height != this->sc.delivered_height)
       || (frame->ratio != this->sc.delivered_ratio) ) {
    lprintf("frame format changed\n");
    this->sc.force_redraw = 1;    /* trigger re-calc of output size */
  }

  /*
   * tell gui that we are about to display a frame,
   * ask for offset and output size
   */

  first_field = (frame->vo_frame.top_field_first) ? XVMC_TOP_FIELD : XVMC_BOTTOM_FIELD;
  first_field = (this->bob) ? first_field : XVMC_TOP_FIELD;
  this->cur_field = (this->deinterlace_enabled) ? first_field : XVMC_FRAME_PICTURE;

  xxmc_redraw_needed (this_gen);
  if (frame->format == XINE_IMGFMT_XXMC) {
    XVMCLOCKDISPLAY( this->display );
    XvMCSyncSurface( this->display, frame->xvmc_surf );
    XvMCPutSurface( this->display, frame->xvmc_surf , this->drawable,
		    this->sc.displayed_xoffset, this->sc.displayed_yoffset,
		    this->sc.displayed_width, this->sc.displayed_height,
		    this->sc.output_xoffset, this->sc.output_yoffset,
		    this->sc.output_width, this->sc.output_height, 
		    this->cur_field);
    XVMCUNLOCKDISPLAY( this->display );
    if (this->deinterlace_enabled && this->bob) {
      unsigned 
	ms_per_field = 500 * frame->vo_frame.duration / 90000 - 2;
      
      usleep(ms_per_field*1000);
      this->cur_field = (frame->vo_frame.top_field_first) ? XVMC_BOTTOM_FIELD : XVMC_TOP_FIELD;

      XVMCLOCKDISPLAY( this->display );
      XvMCPutSurface( this->display, frame->xvmc_surf , this->drawable,
		      this->sc.displayed_xoffset, this->sc.displayed_yoffset,
		      this->sc.displayed_width, this->sc.displayed_height,
		      this->sc.output_xoffset, this->sc.output_yoffset,
		      this->sc.output_width, this->sc.output_height, 
		      this->cur_field);
      XVMCUNLOCKDISPLAY( this->display );
    }      
  } else {
    XLockDisplay (this->display);
    if (this->use_shm) {
      XvShmPutImage(this->display, this->xv_port,
		    this->drawable, this->gc, frame->image,
		    this->sc.displayed_xoffset, this->sc.displayed_yoffset,
		    this->sc.displayed_width, this->sc.displayed_height,
		    this->sc.output_xoffset, this->sc.output_yoffset,
		    this->sc.output_width, this->sc.output_height, True);

    } else {
      XvPutImage(this->display, this->xv_port,
		 this->drawable, this->gc, frame->image,
		 this->sc.displayed_xoffset, this->sc.displayed_yoffset,
		 this->sc.displayed_width, this->sc.displayed_height,
		 this->sc.output_xoffset, this->sc.output_yoffset,
		 this->sc.output_width, this->sc.output_height);
    }
    XSync(this->display, False);
    XUnlockDisplay (this->display);
  }
  xvmc_context_reader_unlock( &this->xvmc_lock );
}

static int xxmc_get_property (vo_driver_t *this_gen, int property) {
  xxmc_driver_t *this = (xxmc_driver_t *) this_gen;

  switch (property) {
  case VO_PROP_WINDOW_WIDTH:
    this->props[property].value = this->sc.gui_width;
    break;
  case VO_PROP_WINDOW_HEIGHT:
    this->props[property].value = this->sc.gui_height;
    break;
  }

  lprintf("video_out_xxmc: property #%d = %d\n", property, this->props[property].value);

  return this->props[property].value;
}

static void xxmc_property_callback (void *property_gen, xine_cfg_entry_t *entry) {
  xxmc_property_t *property = (xxmc_property_t *) property_gen;
  xxmc_driver_t   *this = property->this;

  xvmc_context_reader_lock( &this->xvmc_lock );
  XLockDisplay (this->display);
  XvSetPortAttribute (this->display, this->xv_port,
		      property->atom,
		      entry->num_value);
  XUnlockDisplay (this->display);
  if (this->contextActive) {
    XVMCLOCKDISPLAY( this->display );
    XvMCSetAttribute(this->display, &this->context,
		     property->atom, 
		     entry->num_value);
    XVMCUNLOCKDISPLAY( this->display );
  }
  xvmc_context_reader_unlock( &this->xvmc_lock );
}

static int xxmc_set_property (vo_driver_t *this_gen,
			      int property, int value) {
  xxmc_driver_t *this = (xxmc_driver_t *) this_gen;

  if (this->props[property].atom != None) {

    /* value is out of bound */
    if((value < this->props[property].min) || (value > this->props[property].max))
      value = (this->props[property].min + this->props[property].max) >> 1;
    xvmc_context_reader_lock( &this->xvmc_lock );
    if (this->contextActive) {
      XVMCLOCKDISPLAY( this->display );
      XvMCSetAttribute(this->display, &this->context,
		       this->props[property].atom, 
		       value);
      XVMCUNLOCKDISPLAY( this->display );
    }
    xvmc_context_reader_unlock( &this->xvmc_lock );

    XLockDisplay (this->display);
    XvSetPortAttribute (this->display, this->xv_port,
			this->props[property].atom, value);
    XvGetPortAttribute (this->display, this->xv_port,
			this->props[property].atom,
			&this->props[property].value);
    XUnlockDisplay (this->display);

    if (this->props[property].entry)
      this->props[property].entry->num_value = this->props[property].value;

    return this->props[property].value;
  } 
  else {
    switch (property) {

    case VO_PROP_INTERLACED:
      this->props[property].value = value;
      xprintf(this->xine, XINE_VERBOSITY_LOG,
	      "video_out_xxmc: VO_PROP_INTERLACED(%d)\n", this->props[property].value);
      this->deinterlace_enabled = value;
      break;

    case VO_PROP_ASPECT_RATIO:
      if (value>=XINE_VO_ASPECT_NUM_RATIOS)
	value = XINE_VO_ASPECT_AUTO;

      this->props[property].value = value;
      xprintf(this->xine, XINE_VERBOSITY_LOG, 
	      "video_out_xxmc: VO_PROP_ASPECT_RATIO(%d)\n", this->props[property].value);
      this->sc.user_ratio = value;

      xxmc_compute_ideal_size (this);

      this->sc.force_redraw = 1;    /* trigger re-calc of output size */
      break;

    case VO_PROP_ZOOM_X:
      if ((value >= XINE_VO_ZOOM_MIN) && (value <= XINE_VO_ZOOM_MAX)) {
        this->props[property].value = value;
	xprintf(this->xine, XINE_VERBOSITY_LOG,
		"video_out_xxmc: VO_PROP_ZOOM_X = %d\n", this->props[property].value);
	
	this->sc.zoom_factor_x = (double)value / (double)XINE_VO_ZOOM_STEP;

	xxmc_compute_ideal_size (this);

	this->sc.force_redraw = 1;    /* trigger re-calc of output size */
      }
      break;

    case VO_PROP_ZOOM_Y:
      if ((value >= XINE_VO_ZOOM_MIN) && (value <= XINE_VO_ZOOM_MAX)) {
        this->props[property].value = value;
	xprintf(this->xine, XINE_VERBOSITY_LOG,
		"video_out_xxmc: VO_PROP_ZOOM_Y = %d\n", this->props[property].value);

	this->sc.zoom_factor_y = (double)value / (double)XINE_VO_ZOOM_STEP;

	xxmc_compute_ideal_size (this);

	this->sc.force_redraw = 1;    /* trigger re-calc of output size */
      }
      break;
    }
  }
  return value;
}

static void xxmc_get_property_min_max (vo_driver_t *this_gen,
				       int property, int *min, int *max) {
  xxmc_driver_t *this = (xxmc_driver_t *) this_gen;

  *min = this->props[property].min;
  *max = this->props[property].max;
}

static int xxmc_gui_data_exchange (vo_driver_t *this_gen,
				   int data_type, void *data) {

  xxmc_driver_t     *this = (xxmc_driver_t *) this_gen;

  switch (data_type) {
#ifndef XINE_DISABLE_DEPRECATED_FEATURES
  case XINE_GUI_SEND_COMPLETION_EVENT:
    break;
#endif
    
  case XINE_GUI_SEND_EXPOSE_EVENT: {
    /* XExposeEvent * xev = (XExposeEvent *) data; */

    if (this->cur_frame) {
      xxmc_frame_t *frame = this->cur_frame;
      xine_xxmc_t *xxmc = &frame->xxmc_data;

      xvmc_context_reader_lock( &this->xvmc_lock );
      if ((frame->format == XINE_IMGFMT_XXMC) &&
	  (!xxmc->decoded || !xxmc_xvmc_surface_valid(this, frame->xvmc_surf))) {
	xvmc_context_reader_unlock( &this->xvmc_lock );
	if (! xxmc_redraw_needed (this_gen)) 
	  xxmc_clean_output_area(this, (frame->format == XINE_IMGFMT_XXMC));
	break;
      }

      if (!xxmc_redraw_needed (this_gen) && !this->xoverlay)
	xxmc_clean_output_area(this,(frame->format == XINE_IMGFMT_XXMC));
      if (frame->format == XINE_IMGFMT_XXMC) {
	XVMCLOCKDISPLAY( this->display );
        XvMCSyncSurface( this->display, frame->xvmc_surf );
	XvMCPutSurface( this->display, frame->xvmc_surf, this->drawable,
			this->sc.displayed_xoffset, this->sc.displayed_yoffset,
			this->sc.displayed_width, this->sc.displayed_height,
			this->sc.output_xoffset, this->sc.output_yoffset,
			this->sc.output_width, this->sc.output_height, 
			this->cur_field); 
	XVMCUNLOCKDISPLAY( this->display );
      } else {
	XLockDisplay (this->display);
	if (this->use_shm) {
	  XvShmPutImage(this->display, this->xv_port,
			this->drawable, this->gc, frame->image,
			this->sc.displayed_xoffset, this->sc.displayed_yoffset,
			this->sc.displayed_width, this->sc.displayed_height,
			this->sc.output_xoffset, this->sc.output_yoffset,
			this->sc.output_width, this->sc.output_height, True);
	} else {
	  XvPutImage(this->display, this->xv_port,
		     this->drawable, this->gc, frame->image,
		     this->sc.displayed_xoffset, this->sc.displayed_yoffset,
		     this->sc.displayed_width, this->sc.displayed_height,
		     this->sc.output_xoffset, this->sc.output_yoffset,
		     this->sc.output_width, this->sc.output_height);
	}
	XSync(this->display, False);
	XUnlockDisplay (this->display);
      }
      xvmc_context_reader_unlock( &this->xvmc_lock );
    }
    if(this->xoverlay)
      x11osd_expose(this->xoverlay);	
      
  }
  
    break;

  case XINE_GUI_SEND_DRAWABLE_CHANGED:
    XLockDisplay (this->display);
    this->drawable = (Drawable) data;
    XFreeGC(this->display, this->gc);
    this->gc = XCreateGC (this->display, this->drawable, 0, NULL);
    if(this->xoverlay)
      x11osd_drawable_changed(this->xoverlay, this->drawable);
    this->ovl_changed = 1;
    XUnlockDisplay (this->display);
    this->sc.force_redraw = 1;
    break;

  case XINE_GUI_SEND_TRANSLATE_GUI_TO_VIDEO:
    {
      int x1, y1, x2, y2;
      x11_rectangle_t *rect = data;

      _x_vo_scale_translate_gui2video(&this->sc, rect->x, rect->y,
				      &x1, &y1);
      _x_vo_scale_translate_gui2video(&this->sc, rect->x + rect->w, rect->y + rect->h,
				      &x2, &y2);
      rect->x = x1;
      rect->y = y1;
      rect->w = x2-x1;
      rect->h = y2-y1;

    }
    break;

  default:
    return -1;
  }

  return 0;
}

static void xxmc_dispose (vo_driver_t *this_gen) {
  xxmc_driver_t *this = (xxmc_driver_t *) this_gen;
  int          i;

  
  if (this->xvmc_cap) {
    xvmc_context_writer_lock( &this->xvmc_lock );
    xxmc_dispose_context( this );
    if (this->old_subpic) {
      xxmc_xvmc_free_subpicture(this, this->old_subpic);
      this->old_subpic = NULL;
    }
    if (this->new_subpic) {
      xxmc_xvmc_free_subpicture(this, this->new_subpic);
      this->new_subpic = NULL;
    }
    xvmc_context_writer_unlock( &this->xvmc_lock );
  }

  XLockDisplay (this->display);
  if(XvUngrabPort (this->display, this->xv_port, CurrentTime) != Success) {
    xprintf (this->xine, XINE_VERBOSITY_DEBUG, "video_out_xxmc: xxmc_exit: XvUngrabPort() failed.\n");
  }
  XFreeGC(this->display, this->gc);
  XUnlockDisplay (this->display);

  for( i=0; i < VO_NUM_RECENT_FRAMES; i++ ) {
    if( this->recent_frames[i] )
      this->recent_frames[i]->vo_frame.dispose
	(&this->recent_frames[i]->vo_frame);
    this->recent_frames[i] = NULL;
  }

  if( this->xoverlay ) {
    XLockDisplay (this->display);
    x11osd_destroy (this->xoverlay);
    XUnlockDisplay (this->display);
  }
  free_context_lock(&this->xvmc_lock);

  _x_alphablend_free(&this->alphablend_extra_data);
  
  free (this);
}

/* called xlocked */
static int xxmc_check_yv12 (Display *display, XvPortID port) {
  XvImageFormatValues *formatValues;
  int                  formats;
  int                  i;

  formatValues = XvListImageFormats (display, port, &formats);
  
  for (i = 0; i < formats; i++)
    if ((formatValues[i].id == XINE_IMGFMT_YV12) &&
	(! (strcmp (formatValues[i].guid, "YV12")))) {
      XFree (formatValues);
      return 0;
    }

  XFree (formatValues);
  return 1;
}

/* called xlocked */
static void xxmc_check_capability (xxmc_driver_t *this,
				   int property, XvAttribute attr,
				   int base_id, char *str_prop,
				   char *config_name,
				   char *config_desc,
				   char *config_help) {
  int          int_default;
  cfg_entry_t *entry;
  
  if (VO_PROP_COLORKEY && (attr.max_value == ~0))
    attr.max_value = 2147483615;

  this->props[property].min  = attr.min_value;
  this->props[property].max  = attr.max_value;
  this->props[property].atom = XInternAtom (this->display, str_prop, False);

  XvGetPortAttribute (this->display, this->xv_port,
		      this->props[property].atom, &int_default);

  xprintf(this->xine, XINE_VERBOSITY_DEBUG,
	  "video_out_xxmc: port attribute %s (%d) value is %d\n", str_prop, property, int_default);

  /*
   * We enable autopaint by default.
   */
  if(strcmp(str_prop, "XV_AUTOPAINT_COLORKEY") == 0)
    int_default = 1;
    
  if (config_name) {
    /* is this a boolean property ? */
    if ((attr.min_value == 0) && (attr.max_value == 1)) {
      this->config->register_bool (this->config, config_name, int_default,
				   config_desc,
				   config_help, 20, xxmc_property_callback, &this->props[property]);

    } else {
      this->config->register_range (this->config, config_name, int_default,
				    this->props[property].min, this->props[property].max,
				    config_desc,
				    config_help, 20, xxmc_property_callback, &this->props[property]);
    }

    entry = this->config->lookup_entry (this->config, config_name);

    if((entry->num_value < this->props[property].min) || 
       (entry->num_value > this->props[property].max)) {

      this->config->update_num(this->config, config_name, 
			       ((this->props[property].min + this->props[property].max) >> 1));
      
      entry = this->config->lookup_entry (this->config, config_name);
    }

    this->props[property].entry = entry;

    xxmc_set_property (&this->vo_driver, property, entry->num_value);

    
    if (strcmp(str_prop, "XV_COLORKEY") == 0) {
      this->use_colorkey |= 1;
      this->colorkey = entry->num_value;
    } else if(strcmp(str_prop, "XV_AUTOPAINT_COLORKEY") == 0) {
      if(entry->num_value==1)
        this->use_colorkey |= 2;	
    }
  } else
    this->props[property].value  = int_default;
}

static void xxmc_update_XV_FILTER(void *this_gen, xine_cfg_entry_t *entry) {
  xxmc_driver_t *this = (xxmc_driver_t *) this_gen;
  Atom atom;
  int xv_filter;

  xv_filter = entry->num_value;

  XLockDisplay(this->display);
  atom = XInternAtom (this->display, "XV_FILTER", False);
  XvSetPortAttribute (this->display, this->xv_port, atom, xv_filter);
  XUnlockDisplay(this->display);

  xprintf(this->xine, XINE_VERBOSITY_DEBUG,
	  "video_out_xxmc: bilinear scaling mode (XV_FILTER) = %d\n",xv_filter);
}

static void xxmc_update_XV_DOUBLE_BUFFER(void *this_gen, xine_cfg_entry_t *entry) {
  xxmc_driver_t *this = (xxmc_driver_t *) this_gen;
  Atom         atom;
  int          xv_double_buffer;

  xv_double_buffer = entry->num_value;

  XLockDisplay(this->display);
  atom = XInternAtom (this->display, "XV_DOUBLE_BUFFER", False);
  XvSetPortAttribute (this->display, this->xv_port, atom, xv_double_buffer);
  XUnlockDisplay(this->display);

  xprintf(this->xine, XINE_VERBOSITY_DEBUG,
	  "video_out_xxmc: double buffering mode = %d\n", xv_double_buffer);
}

static void xxmc_update_xv_pitch_alignment(void *this_gen, xine_cfg_entry_t *entry) {
  xxmc_driver_t *this = (xxmc_driver_t *) this_gen;

  this->use_pitch_alignment = entry->num_value;
}

static void xxmc_update_cpu_save(void *this_gen, xine_cfg_entry_t *entry) {
  xxmc_driver_t *this = (xxmc_driver_t *) this_gen;

  this->cpu_save_enabled = entry->num_value;
}

static void xxmc_update_nvidia_fix(void *this_gen, xine_cfg_entry_t *entry) {
  xxmc_driver_t *this = (xxmc_driver_t *) this_gen;

  this->reverse_nvidia_palette = entry->num_value;
}

static void xxmc_update_bob(void *this_gen, xine_cfg_entry_t *entry) {
  xxmc_driver_t *this = (xxmc_driver_t *) this_gen;

  this->bob = entry->num_value;
}


static void checkXvMCCap( xxmc_driver_t *this, XvPortID xv_port) 
{
  int
    numSurf,numSub,i,j;
  XvMCSurfaceInfo
    *surfaceInfo,*curInfo;
  XvMCContext 
    c;
  xvmc_capabilities_t
    *curCap;
  XvImageFormatValues
    *formatValues;

  this->xvmc_cap = 0;
  init_context_lock( &this->xvmc_lock );
  xvmc_context_writer_lock( &this->xvmc_lock );
  this->old_subpic = NULL;
  this->new_subpic = NULL;
  this->contextActive = 0;
  this->subImage = NULL;
  this->hwSubpictures = 0;
  this->xvmc_palette = NULL;

  XVMCLOCKDISPLAY( this->display );

  if ( !XvMCQueryExtension(this->display, &this->xvmc_eventbase, 
			   &this->xvmc_errbase)) {
    XVMCUNLOCKDISPLAY( this->display );
    xvmc_context_writer_unlock( &this->xvmc_lock );
    return;
  }
  xprintf (this->xine, XINE_VERBOSITY_DEBUG, 
	   "video_out_xxmc: XvMC extension present.\n");

  surfaceInfo = XvMCListSurfaceTypes(this->display, xv_port, &numSurf);
  if (0 == surfaceInfo) {
    XVMCUNLOCKDISPLAY( this->display );
    xvmc_context_writer_unlock( &this->xvmc_lock );
    return;
  }
  this->xvmc_cap = (xvmc_capabilities_t *)
    xine_xmalloc(numSurf * sizeof(xvmc_capabilities_t));
  if (NULL == this->xvmc_cap) return;
  this->xvmc_num_cap = numSurf;
  curInfo = surfaceInfo;
  curCap = this->xvmc_cap;

  xprintf (this->xine, XINE_VERBOSITY_DEBUG, 
	   "video_out_xxmc: Found %d XvMC surface types\n",numSurf);

  for (i=0; i< numSurf; ++i) {
    curCap->mpeg_flags = 0;
    curCap->accel_flags = 0;
    if (curInfo->chroma_format == XVMC_CHROMA_FORMAT_420) {
      curCap->mpeg_flags |= ((curInfo->mc_type & XVMC_MPEG_1) ? 
			     XINE_XVMC_MPEG_1 : 0); 
      curCap->mpeg_flags |= ((curInfo->mc_type & XVMC_MPEG_2) ? 
			     XINE_XVMC_MPEG_2 : 0);
      curCap->mpeg_flags |= ((curInfo->mc_type & XVMC_MPEG_4) ? 
			     XINE_XVMC_MPEG_4 : 0);
      curCap->accel_flags |= ((curInfo->mc_type & XVMC_VLD) ? 
			      XINE_XVMC_ACCEL_VLD : 0);
      curCap->accel_flags |= ((curInfo->mc_type & XVMC_IDCT) ? 
			      XINE_XVMC_ACCEL_IDCT : 0);
      curCap->accel_flags |= ((curInfo->mc_type & (XVMC_VLD | XVMC_IDCT)) ? 
			      0 : XINE_XVMC_ACCEL_MOCOMP);
      curCap->max_width = curInfo->max_width;
      curCap->max_height = curInfo->max_height;
      curCap->sub_max_width = curInfo->subpicture_max_width;
      curCap->sub_max_height = curInfo->subpicture_max_height;
      curCap->flags = curInfo->flags;
      xprintf (this->xine, XINE_VERBOSITY_DEBUG, 
	       "video_out_xxmc: Surface type %d: Max size: %d %d.\n",
	       i,curCap->max_width,curCap->max_height);
      xprintf (this->xine, XINE_VERBOSITY_DEBUG, 
	       "video_out_xxmc: Surface type %d: Max subpic size: %d %d.\n",
	       i,curCap->sub_max_width,curCap->sub_max_height);
      
      curCap->type_id = curInfo->surface_type_id;
      formatValues = XvMCListSubpictureTypes( this->display, xv_port, 
					      curCap->type_id, &numSub);
      curCap->subPicType.id = 0;
      if (formatValues) {
	xprintf (this->xine, XINE_VERBOSITY_DEBUG, 
		 "video_out_xxmc: Surface type %d: Found %d XvMC subpicture "
		 "types\n",i,numSub);
	for (j = 0; j<numSub; ++j) {
	  if (formatValues[j].id == FOURCC_IA44) {
	    curCap->subPicType = formatValues[j];
	    xprintf (this->xine, XINE_VERBOSITY_DEBUG, 
		     "video_out_xxmc: Surface type %d: Detected and using "
		     "IA44 subpicture type.\n",i);
	    /* Prefer IA44 */
	    break;
	  } else if (formatValues[j].id == FOURCC_AI44) {
	    curCap->subPicType = formatValues[j];
	    xprintf (this->xine, XINE_VERBOSITY_DEBUG, 
		     "video_out_xxmc: Surface type %d: Detected AI44 "
		     "subpicture type.\n",i);
	  }
	}
      }

      XFree(formatValues);

      curInfo++;
      curCap++;      
    }
  }
  XFree(surfaceInfo);

  /*
   * Try to create a direct rendering context. This will fail if we are not
   * on the displaying computer or an indirect context is not available.
   */
  
  curCap = this->xvmc_cap;
  if (Success == XvMCCreateContext( this->display, xv_port, curCap->type_id,
				    curCap->max_width,curCap->max_height,
				    XVMC_DIRECT, &c)) {
    this->context_flags = XVMC_DIRECT;
  } else if (Success == XvMCCreateContext( this->display, xv_port, curCap->type_id,
					   curCap->max_width,curCap->max_height,
					   0, &c)) {
    this->context_flags = 0;
  } else {
    free(this->xvmc_cap);
    this->xvmc_cap = 0;
    xprintf (this->xine, XINE_VERBOSITY_DEBUG, 
	     "video_out_xxmc: Apparent attempt to use a direct XvMC "
	     "context\nvideo_out_xxmc:   on a remote display. "
	     "Falling back to XV.\n");
    XVMCUNLOCKDISPLAY( this->display );
    xvmc_context_writer_unlock( &this->xvmc_lock );
    return;
  }
  XvMCDestroyContext( this->display, &c);
  xxmc_xvmc_surface_handler_construct(this);
  this->capabilities |= VO_CAP_XXMC;
  XVMCUNLOCKDISPLAY( this->display );
  _x_init_xx44_palette( &this->palette , 0);
  this->last_accel_request = 0xFFFFFFFF;
  xvmc_context_writer_unlock( &this->xvmc_lock );
  return;

}
  
  


static vo_driver_t *open_plugin (video_driver_class_t *class_gen, const void *visual_gen) {
  xxmc_class_t           *class = (xxmc_class_t *) class_gen;
  config_values_t      *config = class->config;
  xxmc_driver_t          *this;
  int                   i, formats;
  XvAttribute          *attr;
  XvImageFormatValues  *fo;
  int                   nattr;
  x11_visual_t         *visual = (x11_visual_t *) visual_gen;
  XColor                dummy;
  XvImage              *myimage;
  unsigned int          adaptors, j;
  unsigned int          ver,rel,req,ev,err;
  XShmSegmentInfo       myshminfo;
  XvPortID              xv_port;
  XvAdaptorInfo        *adaptor_info;
  unsigned int          adaptor_num;
  cfg_entry_t          *entry;     
  int                   use_more_frames;
  int                   use_unscaled;

  this = (xxmc_driver_t *) xine_xmalloc (sizeof (xxmc_driver_t));
  if (!this)
    return NULL;

  _x_alphablend_init(&this->alphablend_extra_data, class->xine);
  
  this->display           = visual->display;
  this->screen            = visual->screen;
  this->config            = config;

  /*
   * check for Xvideo support
   */

  XLockDisplay(this->display);
  if (Success != XvQueryExtension(this->display, &ver,&rel, &req, &ev,&err)) {
    xprintf (class->xine, XINE_VERBOSITY_LOG, _("video_out_xxmc: Xv extension not present.\n"));
    XUnlockDisplay(this->display);
    return NULL;
  }

  /*
   * check adaptors, search for one that supports (at least) yuv12
   */

  if (Success != XvQueryAdaptors(this->display,DefaultRootWindow(this->display), &adaptors, &adaptor_info))  {
    xprintf(class->xine, XINE_VERBOSITY_DEBUG, "video_out_xxmc: XvQueryAdaptors failed.\n");
    XUnlockDisplay(this->display);
    return NULL;
  }

  xv_port = 0;

  for ( adaptor_num = 0; (adaptor_num < adaptors) && !xv_port; adaptor_num++ ) {

    if (adaptor_info[adaptor_num].type & XvImageMask) {

      for (j = 0; j < adaptor_info[adaptor_num].num_ports && !xv_port; j++)
        if (( !(xxmc_check_yv12 (this->display,
				 adaptor_info[adaptor_num].base_id + j)))
            && (XvGrabPort (this->display,
			    adaptor_info[adaptor_num].base_id + j,
			    0) == Success)) {
          xv_port = adaptor_info[adaptor_num].base_id + j;
        }
      
      if( xv_port )
        break;
    }
  }

  if (!xv_port) {
    xprintf(class->xine, XINE_VERBOSITY_LOG,
	    _("video_out_xxmc: Xv extension is present but I couldn't find a usable yuv12 port.\n"
	      "              Looks like your graphics hardware driver doesn't support Xv?!\n"));
    
    /* XvFreeAdaptorInfo (adaptor_info); this crashed on me (gb)*/
    XUnlockDisplay(this->display);
    return NULL;
  } 
  else
    xprintf(class->xine, XINE_VERBOSITY_LOG,
	    _("video_out_xxmc: using Xv port %ld from adaptor %s for hardware "
	      "colorspace conversion and scaling.\n"), xv_port,
            adaptor_info[adaptor_num].name);
  
  XUnlockDisplay(this->display);
  
  this->xv_port           = xv_port;

  _x_vo_scale_init (&this->sc, 1, 0, config );
  this->sc.frame_output_cb   = visual->frame_output_cb;
  this->sc.user_data         = visual->user_data;

  this->drawable                = visual->d;
  XLockDisplay (this->display);
  this->gc                      = XCreateGC (this->display, this->drawable, 0, NULL);
  XUnlockDisplay (this->display);
  this->capabilities            = VO_CAP_CROP;
  this->use_shm                 = 1;
  this->use_colorkey            = 0;
  this->colorkey                = 0;
  this->xoverlay                = NULL;
  this->ovl_changed             = 0;
  this->x11_old_error_handler   = NULL;
  this->xine                    = class->xine;

  XLockDisplay (this->display);
  XAllocNamedColor (this->display,
		    DefaultColormap(this->display, this->screen),
		    "black", &this->black, &dummy);
  XUnlockDisplay (this->display);

  this->vo_driver.get_capabilities     = xxmc_get_capabilities;
  this->vo_driver.alloc_frame          = xxmc_alloc_frame;
  this->vo_driver.update_frame_format  = xxmc_update_frame_format;
  this->vo_driver.overlay_begin        = xxmc_overlay_begin;
  this->vo_driver.overlay_blend        = xxmc_overlay_blend;
  this->vo_driver.overlay_end          = xxmc_overlay_end;
  this->vo_driver.display_frame        = xxmc_display_frame;
  this->vo_driver.get_property         = xxmc_get_property;
  this->vo_driver.set_property         = xxmc_set_property;
  this->vo_driver.get_property_min_max = xxmc_get_property_min_max;
  this->vo_driver.gui_data_exchange    = xxmc_gui_data_exchange;
  this->vo_driver.dispose              = xxmc_dispose;
  this->vo_driver.redraw_needed        = xxmc_redraw_needed;

  /*
   * init properties
   */

  for (i = 0; i < VO_NUM_PROPERTIES; i++) {
    this->props[i].value = 0;
    this->props[i].min   = 0;
    this->props[i].max   = 0;
    this->props[i].atom  = None;
    this->props[i].entry = NULL;
    this->props[i].this  = this;
  }

  this->props[VO_PROP_INTERLACED].value      = 0;
  this->sc.user_ratio                        =
    this->props[VO_PROP_ASPECT_RATIO].value  = XINE_VO_ASPECT_AUTO;
  this->props[VO_PROP_ZOOM_X].value          = 100;
  this->props[VO_PROP_ZOOM_Y].value          = 100;

  /*
   * check this adaptor's capabilities
   */

  XLockDisplay (this->display);
  attr = XvQueryPortAttributes(this->display, xv_port, &nattr);
  if(attr && nattr) {
    int k;

    for(k = 0; k < nattr; k++) {
      if((attr[k].flags & XvSettable) && (attr[k].flags & XvGettable)) {
	if(!strcmp(attr[k].name, "XV_HUE")) {
	  if (!strncmp(adaptor_info[adaptor_num].name, "NV", 2)) {
            xprintf (this->xine, XINE_VERBOSITY_NONE, "video_out_xxmc: ignoring broken XV_HUE settings on NVidia cards\n");
	  } else {
	    xxmc_check_capability (this, VO_PROP_HUE, attr[k],
				   adaptor_info[adaptor_num].base_id, "XV_HUE",
				   NULL, NULL, NULL);
	  }
	} else if(!strcmp(attr[k].name, "XV_SATURATION")) {
	  xxmc_check_capability (this, VO_PROP_SATURATION, attr[k],
				 adaptor_info[adaptor_num].base_id, "XV_SATURATION",
				 NULL, NULL, NULL);

	} else if(!strcmp(attr[k].name, "XV_BRIGHTNESS")) {
	  xxmc_check_capability (this, VO_PROP_BRIGHTNESS, attr[k],
				 adaptor_info[adaptor_num].base_id, "XV_BRIGHTNESS",
				 NULL, NULL, NULL);

	} else if(!strcmp(attr[k].name, "XV_CONTRAST")) {
	  xxmc_check_capability (this, VO_PROP_CONTRAST, attr[k],
				 adaptor_info[adaptor_num].base_id, "XV_CONTRAST",
				 NULL, NULL, NULL);

	} else if(!strcmp(attr[k].name, "XV_COLORKEY")) {
	  xxmc_check_capability (this, VO_PROP_COLORKEY, attr[k],
				 adaptor_info[adaptor_num].base_id, "XV_COLORKEY",
				 "video.device.xv_colorkey",
				 _("video overlay colour key"),
				 _("The colour key is used to tell the graphics card where to "
				   "overlay the video image. Try different values, if you experience "
				   "windows becoming transparent."));

	} else if(!strcmp(attr[k].name, "XV_AUTOPAINT_COLORKEY")) {
	  xxmc_check_capability (this, VO_PROP_AUTOPAINT_COLORKEY, attr[k],
				 adaptor_info[adaptor_num].base_id, "XV_AUTOPAINT_COLORKEY",
				 "video.device.xv_autopaint_colorkey",
				 _("autopaint colour key"),
				 _("Make Xv autopaint its colorkey."));

	} else if(!strcmp(attr[k].name, "XV_FILTER")) {
	  int xv_filter;
	  /* This setting is specific to Permedia 2/3 cards. */
	  xv_filter = config->register_range (config, "video.device.xv_filter", 0,
					      attr[k].min_value, attr[k].max_value,
					      _("bilinear scaling mode"),
					      _("Selects the bilinear scaling mode for Permedia cards. "
						"The individual values are:\n\n"
						"Permedia 2\n"
						"0 - disable bilinear filtering\n"
						"1 - enable bilinear filtering\n\n"
						"Permedia 3\n"
						"0 - disable bilinear filtering\n"
						"1 - horizontal linear filtering\n"
						"2 - enable full bilinear filtering"),
					      20, xxmc_update_XV_FILTER, this);
	  config->update_num(config,"video.device.xv_filter",xv_filter);
	} else if(!strcmp(attr[k].name, "XV_DOUBLE_BUFFER")) {
	  int xv_double_buffer;
	  xv_double_buffer = 
	    config->register_bool (config, "video.device.xv_double_buffer", 1,
				   _("enable double buffering"),
				   _("Double buffering will synchronize the update of the video image to the "
				     "repainting of the entire screen (\"vertical retrace\"). This eliminates "
				     "flickering and tearing artifacts, but will use more graphics memory."),
				   20, xxmc_update_XV_DOUBLE_BUFFER, this);
	  config->update_num(config,"video.device.xv_double_buffer",xv_double_buffer);
	}
      }
    }
    XFree(attr);
  }
  else
    xprintf(this->xine, XINE_VERBOSITY_DEBUG, "video_out_xxmc: no port attributes defined.\n");
  XvFreeAdaptorInfo(adaptor_info);

  /*
   * check XvMC capabilities
   */

  checkXvMCCap( this, xv_port );

  /*
   * check supported image formats
   */


  fo = XvListImageFormats(this->display, this->xv_port, (int*)&formats);
  XUnlockDisplay (this->display);
  
  this->xv_format_yv12 = 0;
  this->xv_format_yuy2 = 0;
  
  for(i = 0; i < formats; i++) {
    lprintf ("Xv image format: 0x%x (%4.4s) %s\n",
	     fo[i].id, (char*)&fo[i].id,
	     (fo[i].format == XvPacked) ? "packed" : "planar");

    if (fo[i].id == XINE_IMGFMT_YV12)  {
      this->xv_format_yv12 = fo[i].id;
      this->capabilities |= VO_CAP_YV12;
      xprintf(this->xine, XINE_VERBOSITY_LOG,
	      _("video_out_xxmc: this adaptor supports the yv12 format.\n"));
    } else if (fo[i].id == XINE_IMGFMT_YUY2) {
      this->xv_format_yuy2 = fo[i].id;
      this->capabilities |= VO_CAP_YUY2;
      xprintf(this->xine, XINE_VERBOSITY_LOG, 
	      _("video_out_xxmc: this adaptor supports the yuy2 format.\n"));
    }
  }

  if(fo) {
    XLockDisplay(this->display);
    XFree(fo);
    XUnlockDisplay(this->display);
  }

  /*
   * try to create a shared image
   * to find out if MIT shm really works, using supported format
   */

  XLockDisplay (this->display);
  myimage = create_ximage (this, &myshminfo, 100, 100,
			   (this->xv_format_yv12 != 0) ? XINE_IMGFMT_YV12 : XINE_IMGFMT_YUY2);
  dispose_ximage (this, &myshminfo, myimage);
  XUnlockDisplay (this->display);

  this->use_pitch_alignment = 
    config->register_bool (config, "video.device.xv_pitch_alignment", 0,
			   _("pitch alignment workaround"),
			   _("Some buggy video drivers need a workaround to function properly."),
			   10, xxmc_update_xv_pitch_alignment, this);

  use_more_frames= 
    config->register_bool (config, "video.device.xvmc_more_frames", 0,
			   _("Make XvMC allocate more frames for better buffering."),
			   _("Some XvMC implementations allow more than 8 frames.\n"
			     "This option, when turned on, makes the driver try to\n"
			     "allocate 15 frames. A must for unichrome and live VDR.\n"),
			   10, NULL, this);
  this->cpu_save_enabled = 
    config->register_bool (config, "video.device.unichrome_cpu_save", 0,
			   _("Unichrome cpu save"),
			   _("Saves CPU time by sleeping while decoder works.\n"
			     "Only for Linux kernel 2.6 series or 2.4 with multimedia patch.\n"
			     "Experimental.\n"),
			   10, xxmc_update_cpu_save, this);
  this->reverse_nvidia_palette =
    config->register_bool (config, "video.device.xvmc_nvidia_color_fix", 0,
			   _("Fix buggy NVIDIA XvMC subpicture colors"),
			   _("There's a bug in NVIDIA's XvMC lib that makes red OSD colors\n"
			     "look blue and vice versa. This option provides a workaround.\n"),
			   10, xxmc_update_nvidia_fix, this);
  this->bob =
    config->register_bool (config, "video.device.xvmc_bob_deinterlacing", 0,
			   _("Use bob as accelerated deinterlace method."),
			   _("When interlacing is enabled for hardware accelerated frames,\n"
			     "Alternate between top and bottom field at double the frame rate.\n"),
			   10, xxmc_update_bob, this);

  this->deinterlace_enabled = 0;
  this->cur_field = XVMC_FRAME_PICTURE;

#ifdef HAVE_VLDXVMC
  printf("video_out_xxmc: Unichrome CPU saving is %s.\n",
	 (this->cpu_save_enabled) ? "on":"off"); 
#else
  printf("video_out_xxmc: warning - compiled with no vld extensions.\n");
#endif
  this->props[VO_PROP_MAX_NUM_FRAMES].value  = (use_more_frames) ? 15:8;
  this->cpu_saver = 0.;

  this->xoverlay = NULL;

  use_unscaled = 1;
  entry = this->config->lookup_entry (this->config, "gui.osd_use_unscaled");
  if (entry) use_unscaled = entry->num_value;
  if (use_unscaled) {
    XLockDisplay (this->display);
    if( this->use_colorkey ) {
      this->xoverlay = x11osd_create (this->xine, this->display, this->screen,
				      this->drawable, X11OSD_COLORKEY);
      this->xv_xoverlay_type = X11OSD_COLORKEY;
      if(this->xoverlay)
	x11osd_colorkey(this->xoverlay, this->colorkey, &this->sc);
      this->xoverlay_type = X11OSD_COLORKEY;
    } else {
      this->xoverlay = x11osd_create (this->xine, this->display, this->screen,
				      this->drawable, X11OSD_SHAPED);
      this->xv_xoverlay_type = X11OSD_SHAPED;
      this->xoverlay_type = X11OSD_SHAPED;
    }
    XUnlockDisplay (this->display);
  }
  
  if( this->xoverlay ) {
    this->capabilities |= VO_CAP_UNSCALED_OVERLAY;
  } 
  return &this->vo_driver;
}

/*
 * class functions
 */

static char* get_identifier (video_driver_class_t *this_gen) {
  return "XxMC";
}

static char* get_description (video_driver_class_t *this_gen) {
  return _("xine video output plugin using the MIT X video extension");
}

static void dispose_class (video_driver_class_t *this_gen) {
  xxmc_class_t        *this = (xxmc_class_t *) this_gen;
  
  free (this);
}

static void *init_class (xine_t *xine, void *visual_gen) {
  xxmc_class_t        *this = (xxmc_class_t *) xine_xmalloc (sizeof (xxmc_class_t));

  this->driver_class.open_plugin     = open_plugin;
  this->driver_class.get_identifier  = get_identifier;
  this->driver_class.get_description = get_description;
  this->driver_class.dispose         = dispose_class;

  this->config                       = xine->config;
  this->xine                         = xine;

  return this;
}

static vo_info_t vo_info_xxmc = {
  /* keep priority lower than Xv for now. we may increase this
   * when the xxmc driver is more mature/tested.
   */
  5,                    /* priority    */
  XINE_VISUAL_TYPE_X11  /* visual type */
};

/*
 * exported plugin catalog entry
 */

plugin_info_t xine_plugin_info[] = {
  /* type, API, "name", version, special_info, init_function */
  { PLUGIN_VIDEO_OUT, 21, "xxmc", XINE_VERSION_CODE, &vo_info_xxmc, init_class },
  { PLUGIN_NONE, 0, "", 0, NULL, NULL }
};
 
