/*
 * Direct3D OSMesa interface
 *
 * Copyright 2001 TransGaming Technologies, Inc.
 */

#define GL_GLEXT_PROTOTYPES

#include "config.h"

#include "ts_xlib.h"
#include "x11drv.h"
#include "x11font.h"
#include "x11ddraw.h"
#include "d3d.h"
#include "d3dhal.h"
#include "wine/wine_gl.h"
#include "wine/d3dhalgl.h"
#include <GL/osmesa.h>
#include "osmesa.h"

#include "windef.h"
#include "wingdi.h"
#include "ddrawi.h"
#include "bitmap.h"
#include "win.h"
#include "wine/debug.h"
#include "options.h"
#include <time.h>

WINE_DEFAULT_DEBUG_CHANNEL(x11drv);

#define RENDER_TO_WINDOW
/* #define TRANSGAMING_LOGO */

static BOOL osmesa_available;

void X11DRV_OSMesa_Init(void)
{
    osmesa_available = TRUE;
}

void X11DRV_OSMesa_Cleanup(void)
{
}

typedef struct
{
    OSMesaContext context;

    BOOL active;

    LPDDRAWI_DIRECTDRAW_LCL ddraw_owner;
    LPDDRAWI_DDRAWSURFACE_LCL color_buffer;
    LPDDRAWI_DDRAWSURFACE_LCL z_buffer;

    GLenum color_format, color_type;
    DWORD color_element_size; /* in bytes */

    GLuint depth_bits, stencil_bits;
    GLenum z_type, stencil_type;
    DWORD depth_element_size; /* in bytes */

    GLint texture_units;
    PFNGLACTIVETEXTUREARBPROC pglActiveTextureARB;

#ifdef TRANSGAMING_LOGO
    GLuint logo;
    GLuint logo_list;
    DWORD logo_size;
#endif

} D3DCONTEXT, *LPD3DCONTEXT;


static void X11DRV_OSMesa_SetupLogo(LPD3DCONTEXT ctx);
static void X11DRV_OSMesa_RenderLogo(LPD3DCONTEXT ctx);

static void *X11DRV_OSMesa_GetProcAddress(const char *func_name);


static BOOL X11DRV_OSMesa_ColorFormat(LPDDRAWI_DDRAWSURFACE_LCL color_surface,
				      GLenum* format, GLenum *type,
				      DWORD *size)
{
    /* Lame, but the GL enumeration / caps code needs it. */
    if (!color_surface)
    {
	*format = OSMESA_RGB_565;
	*type = GL_UNSIGNED_SHORT_5_6_5;
	*size = sizeof(GLushort);
	return TRUE;
    }

    if (color_surface->lpGbl->ddpfSurface.u1.dwRGBBitCount == 16)
    {
	*format = OSMESA_RGB_565;
	*type = GL_UNSIGNED_SHORT_5_6_5;
	*size = sizeof(GLushort);
	return TRUE;
    }
    else if (color_surface->lpGbl->ddpfSurface.u1.dwRGBBitCount == 24)
    {
        /* Do we really care if alpha is there or not? */
        /* Check the bitmasks? Hah! */
        *format = OSMESA_BGR;
        *type = GL_UNSIGNED_BYTE;
        *size = 3*sizeof(GLubyte);
        return TRUE;
    }
    else if (color_surface->lpGbl->ddpfSurface.u1.dwRGBBitCount == 32)
    {
	/* Do we really care if alpha is there or not? */
	/* Check the bitmasks? Hah! */
	*format = OSMESA_BGRA;
	*type = GL_UNSIGNED_BYTE;
	*size = sizeof(GLuint);
	return TRUE;
    }
    else return FALSE;
}

static BOOL X11DRV_OSMesa_ZFormat(LPDDRAWI_DDRAWSURFACE_LCL z_surface,
				  GLint *depthBits, GLenum *z_type,
				  GLint *stencilBits, GLenum *stencil_type,
				  DWORD *depth_size)
{
    if (!z_surface)
    {
	*depthBits = 0;
	*z_type = OSMESA_NONE_TGT;
	*stencilBits = 0;
	*stencil_type = OSMESA_NONE_TGT;
	*depth_size = 0;
	return TRUE;
    }
    else
    {
	LPDDPIXELFORMAT ddpf = &z_surface->lpGbl->ddpfSurface;

	*depthBits = ddpf->u1.dwZBufferBitDepth - ddpf->u2.dwStencilBitDepth;
	*stencilBits = ddpf->u2.dwStencilBitDepth;

	if (*depthBits == 24 && *stencilBits == 8)
	{
	    *z_type = OSMESA_UNSIGNED_INT_24_8_TGT;
	    *stencil_type = OSMESA_UNSIGNED_INT_24_8_TGT;
	}
	else
	{
	    if (*stencilBits != 0) return FALSE;
	    *stencil_type = OSMESA_NONE_TGT;

	    if (*depthBits == 32)
		*z_type = OSMESA_UNSIGNED_INT_TGT;
	    else if (*depthBits == 16)
		*z_type = OSMESA_UNSIGNED_SHORT_TGT;
	    else
		return FALSE;
	}

	*depth_size = ddpf->u1.dwZBufferBitDepth / 8;

	return TRUE;
    }
}

/* There are a few suprises in the GL enumeration / caps code that
 * made developing this tricky.
 * 1. We are expected to work even with NULL lpDDLcl, lpDDSLcl, lpDDSZLcl.
 * 2. We are expected to make the context current before returning.
 */
static DWORD CALLBACK
X11DRV_OSMesa_ContextCreate(LPD3DHAL_CONTEXTCREATEDATA data)
{
    /* We need a colour buffer in order to make the context current. */
    static char default_buffer[16];
    LPD3DCONTEXT ctx;
    const char *gl_exts;

    ctx = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(D3DCONTEXT));
    TRACE("creating 3D context %p\n", ctx);

    ENTER_GL();

    if (!X11DRV_OSMesa_ColorFormat(data->u2.lpDDSLcl, &ctx->color_format,
				   &ctx->color_type, &ctx->color_element_size))
    {
	ERR("choose format failed\n");
	goto out_freecontext;
    }

    if (!X11DRV_OSMesa_ZFormat(data->u3.lpDDSZLcl, &ctx->depth_bits,
			       &ctx->z_type, &ctx->stencil_bits,
			       &ctx->stencil_type, &ctx->depth_element_size))
    {
	ERR("choose Z format failed\n");
	goto out_freecontext;
    }

    ctx->context = OSMesaCreateContextExt(ctx->color_format, ctx->depth_bits,
					  ctx->stencil_bits, 0, NULL);
    if (!ctx->context)
    {
	ERR("OSMesaCreateContextExt failed\n");
	goto out_freecontext;
    }

    ctx->ddraw_owner = data->u1.lpDDLcl;
    ctx->color_buffer = data->u2.lpDDSLcl;
    ctx->z_buffer = data->u3.lpDDSZLcl;

    OSMesaMakeCurrent(ctx->context, default_buffer, ctx->color_type, 1, 1);

    /* Now that we have made a 1x1 buffer current, the viewport and scissor
     * settings are all wrong. Fix them. */
    if (ctx->color_buffer)
    {
	glViewport(0, 0, ctx->color_buffer->lpGbl->wWidth,
		   ctx->color_buffer->lpGbl->wHeight);
	glScissor(0, 0, ctx->color_buffer->lpGbl->wWidth,
		  ctx->color_buffer->lpGbl->wHeight);
    }

    gl_exts = glGetString(GL_EXTENSIONS);

    if (gl_exts && strstr(gl_exts, "GL_ARB_multitexture"))
    {
	glGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB, &ctx->texture_units);
	ctx->pglActiveTextureARB
	    = X11DRV_OSMesa_GetProcAddress("glActiveTextureARB");
    }

    X11DRV_OSMesa_SetupLogo(ctx);

    LEAVE_GL();

    data->dwhContext = (ULONG_PTR)ctx;
    data->ddrval = D3D_OK;
    return DDHAL_DRIVER_HANDLED;

out_freecontext:
    HeapFree(GetProcessHeap(), 0, ctx);

    data->ddrval = E_FAIL;
    return DDHAL_DRIVER_HANDLED;
}

static DWORD CALLBACK
X11DRV_OSMesa_ContextDestroy(LPD3DHAL_CONTEXTDESTROYDATA data)
{
    LPD3DCONTEXT ctx = (LPD3DCONTEXT)data->dwhContext;

    TRACE("destroyed 3D context %p\n", ctx);

    ENTER_GL();

    /* There's no way to deselect all contexts from a thread,
     * and also no need to. (Unlike GLX.) */
    OSMesaDestroyContext(ctx->context);

    LEAVE_GL();

    HeapFree(GetProcessHeap(), 0, ctx);

    data->ddrval = D3D_OK;
    return DDHAL_DRIVER_HANDLED;
}

static DWORD CALLBACK
X11DRV_OSMesa_ContextDestroyAll(LPD3DHAL_CONTEXTDESTROYALLDATA data)
{
  /* since this isn't a real display driver, we probably don't need to do anything here,
   * the kernel and the X server will clean up after Wine exits */
  data->ddrval = DD_OK;
  return DDHAL_DRIVER_HANDLED;
}

static void X11DRV_OSMesa_SetupLogo(LPD3DCONTEXT ctx)
{
#ifdef TRANSGAMING_LOGO
/* makedep is overzealous and doesn't obey the #ifdef. */
/*#include "logo.c"*/
#error "Uncomment the above line, and comment this one."

    ctx->logo_size = logo_size;

    glGenTextures(1, &ctx->logo);
    glBindTexture(GL_TEXTURE_2D, ctx->logo);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, ctx->logo_size, ctx->logo_size,
		 0, GL_RGBA, GL_UNSIGNED_BYTE, logo);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);

    ctx->logo_list = glGenLists(1);

    glNewList(ctx->logo_list, GL_COMPILE);

    glPushClientAttrib(GL_CLIENT_VERTEX_ARRAY_BIT);
    glPushAttrib(GL_CURRENT_BIT | GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT
		 | GL_DEPTH_BUFFER_BIT | GL_TEXTURE_BIT | GL_TRANSFORM_BIT
		 | GL_VIEWPORT_BIT);

    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    glOrtho(0.0, ctx->logo_size, ctx->logo_size, 0.0, -1, 1);

    glDisable(GL_ALPHA_TEST);
    glDisable(GL_COLOR_MATERIAL);
    glDisable(GL_DITHER);
    glDisable(GL_FOG);
    glDisable(GL_LIGHTING);
    glDisable(GL_STENCIL_TEST);
    glDisable(GL_SCISSOR_TEST);
    glMatrixMode(GL_TEXTURE);
    if (ctx->texture_units > 1) {
	int i;
	for (i=0; i<ctx->texture_units; i++) {
	    ctx->pglActiveTextureARB(GL_TEXTURE0_ARB + i);
	    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	    glDisable(GL_TEXTURE_2D);

	    glPushMatrix();
	    glLoadIdentity();
	    glDisable(GL_TEXTURE_GEN_S);
	    glDisable(GL_TEXTURE_GEN_T);
	    glDisable(GL_TEXTURE_GEN_R);
	    glDisable(GL_TEXTURE_GEN_Q);
	}
	ctx->pglActiveTextureARB(GL_TEXTURE0_ARB);
    }
    else
    {
	glDisable(GL_TEXTURE_2D);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	glPushMatrix();
	glLoadIdentity();
	glDisable(GL_TEXTURE_GEN_S);
	glDisable(GL_TEXTURE_GEN_T);
	glDisable(GL_TEXTURE_GEN_R);
	glDisable(GL_TEXTURE_GEN_Q);
    }
    /* disable cubemapping, 3d */

    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    glDisable(GL_DEPTH_TEST);

    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_COLOR_ARRAY);
    glColor4f(1, 1, 1, 0.333);
    if (gluCheckExtension("GL_EXT_secondary_color",
			  glGetString(GL_EXTENSIONS)))
    {
	glDisableClientState(GL_SECONDARY_COLOR_ARRAY_EXT);
	glSecondaryColor3fEXT(0, 0, 0);
    }

    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, ctx->logo);
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    glTexEnvf(GL_TEXTURE_FILTER_CONTROL_EXT, GL_TEXTURE_LOD_BIAS_EXT, 0);
    /* LOD bias shouldn't make a difference here. */

    glBegin(GL_QUADS);
    glTexCoord2f(0, 0);	glVertex2f(0, 0);
    glTexCoord2f(1, 0);	glVertex2f(ctx->logo_size, 0);
    glTexCoord2f(1, 1);	glVertex2f(ctx->logo_size, ctx->logo_size);
    glTexCoord2f(0, 1);	glVertex2f(0, ctx->logo_size);
    glEnd();

    if (ctx->texture_units > 1) {
	int i;
	for (i=0; i<ctx->texture_units; i++) {
	    ctx->pglActiveTextureARB(GL_TEXTURE0_ARB + i);
	    glPopMatrix();
	}
	ctx->pglActiveTextureARB(GL_TEXTURE0_ARB);
    }
    else
    {
	glPopMatrix();
    }
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();

    glPopAttrib();
    glPopClientAttrib();

    glEndList();
#endif
}

static void X11DRV_OSMesa_RenderLogo(LPD3DCONTEXT ctx)
{
#ifdef TRANSGAMING_LOGO
    glPushAttrib(GL_VIEWPORT_BIT);
    glViewport(ctx->color_buffer->lpGbl->wWidth - ctx->logo_size,
	       ctx->color_buffer->lpGbl->wHeight - ctx->logo_size,
	       ctx->logo_size, ctx->logo_size);

    glCallList(ctx->logo_list);

    glPopAttrib();
#endif
}

static BOOL X11DRV_OSMesa_MakeCurrent(LPD3DCONTEXT ctx)
{
    if (ctx->z_buffer)
    {
	if (!OSMesaSetBufferTGT(ctx->context, OSMESA_DEPTH_BUFFER_TGT,
				(LPVOID)ctx->z_buffer->lpGbl->fpVidMem,
				ctx->z_buffer->lpGbl->u4.lPitch / ctx->depth_element_size,
				GL_TRUE))  /* GL_FALSE if we want non-y-reversed rendering */
	    return FALSE;
    }
    else
	OSMesaSetBufferTGT(ctx->context, OSMESA_DEPTH_BUFFER_TGT, NULL, 0,
			   GL_FALSE);

    if (!OSMesaMakeCurrent(ctx->context,
			   (LPVOID)ctx->color_buffer->lpGbl->fpVidMem,
			   ctx->color_type,
			   ctx->color_buffer->lpGbl->wWidth,
			   ctx->color_buffer->lpGbl->wHeight))
	return FALSE;

    OSMesaPixelStore(OSMESA_Y_UP, GL_TRUE); /* GL_FALSE if we want non-y-reversed rendering */
    OSMesaPixelStore(OSMESA_ROW_LENGTH,
		     (ctx->color_buffer->lpGbl->u4.lPitch
		      / ctx->color_element_size));

    return TRUE;
}

static DWORD CALLBACK
X11DRV_OSMesa_SceneCapture(LPD3DHAL_SCENECAPTUREDATA data)
{
    LPD3DCONTEXT ctx = (LPD3DCONTEXT)data->dwhContext;

    switch (data->dwFlag)
    {
    case D3DHAL_SCENE_CAPTURE_START:
	/* If the surfaces are not AppMod (or InSync), we'll let OSMesa
	 * fault them back to AppMod. (We really shouldn't generate page
	 * faults in libraries we didn't write ourselves.) */
	ENTER_GL();

	if (!X11DRV_OSMesa_MakeCurrent(ctx))
	{
	    LEAVE_GL();
	    ERR("make current failed\n");
	    data->ddrval = E_FAIL;
	    return DDHAL_DRIVER_HANDLED;
	}

	ctx->active = TRUE;
	break;

    case D3DHAL_SCENE_CAPTURE_END:
	X11DRV_OSMesa_RenderLogo(ctx);
	glFinish();

	ctx->active = FALSE;
	LEAVE_GL();
	break;
    }

    data->ddrval = DD_OK;
    return DDHAL_DRIVER_HANDLED;
}

static BOOL
X11DRV_OSMesa_IsLegalColorFormatForContext(LPD3DCONTEXT ctx,
					   LPDDRAWI_DDRAWSURFACE_LCL surf)
{
    GLenum color_format, color_type;
    DWORD color_element_size;

    if (!X11DRV_OSMesa_ColorFormat(surf, &color_format,
				   &color_type, &color_element_size))
	return FALSE;

    if (color_format != ctx->color_format || color_type != ctx->color_type)
	return FALSE;

    return TRUE;
}

static DWORD CALLBACK
X11DRV_OSMesa_SetRenderTarget(LPD3DHAL_SETRENDERTARGETDATA data)
{
    LPD3DCONTEXT ctx = (LPD3DCONTEXT)data->dwhContext;

    if (!X11DRV_OSMesa_IsLegalColorFormatForContext(ctx, data->u1.lpDDSLcl))
    {
	data->ddrval = DDERR_INVALIDSURFACETYPE;
	return DDHAL_DRIVER_HANDLED;
    }

    ctx->color_buffer = data->u1.lpDDSLcl;
    ctx->z_buffer = data->u2.lpDDSZLcl;

    if (ctx->active)
	X11DRV_OSMesa_MakeCurrent(ctx);

    data->ddrval = D3D_OK;
    return DDHAL_DRIVER_HANDLED;
}

static void*
X11DRV_OSMesa_GetProcAddress(const char *name)
{
    struct name_entry
    {
	const char *name;
	void* ptr;
    };

    const struct name_entry name_table[] =
	{
	    { "glActiveTextureARB", glActiveTextureARB },
	    { "glClientActiveTextureARB", glClientActiveTextureARB },
	    { "glMultiTexCoord2fARB", glMultiTexCoord2fARB },
	    { "glSecondaryColor3ubEXT", glSecondaryColor3ubEXT },
	    { "glSecondaryColorPointerEXT", glSecondaryColorPointerEXT },
	    { "glCompressedTexImage2DARB", glCompressedTexImage2DARB },
	    { "glCompressedTexSubImage2DARB", glCompressedTexSubImage2DARB },
	    { NULL, 0 }
	};

    const struct name_entry *ne;

    for (ne = name_table; ne->name != NULL; ne++)
    {
	if (strcmp(name, ne->name) == 0)
	    return ne->ptr;
    }

    return NULL;
}

static DWORD WINAPI
X11DRV_OSMesa_CanCreateEB(LPDDHAL_CANCREATESURFACEDATA data)
{
    data->ddRVal = DD_OK;
    return DDHAL_DRIVER_HANDLED;
}

static DWORD WINAPI X11DRV_OSMesa_CreateEB(LPDDHAL_CREATESURFACEDATA data)
{
    return DDHAL_DRIVER_NOTHANDLED;
}

static DWORD WINAPI X11DRV_OSMesa_DestroyEB(LPDDHAL_DESTROYSURFACEDATA data)
{
    return DDHAL_DRIVER_NOTHANDLED;
}

static DWORD WINAPI X11DRV_OSMesa_LockEB(LPDDHAL_LOCKDATA data)
{
    return DDHAL_DRIVER_NOTHANDLED;
}

static DWORD WINAPI X11DRV_OSMesa_UnlockEB(LPDDHAL_UNLOCKDATA data)
{
    return DDHAL_DRIVER_NOTHANDLED;
}


static D3DHAL_CALLBACKS d3dhal_callbacks = {
  sizeof(D3DHAL_CALLBACKS),
  X11DRV_OSMesa_ContextCreate,
  X11DRV_OSMesa_ContextDestroy,
  X11DRV_OSMesa_ContextDestroyAll,
  X11DRV_OSMesa_SceneCapture,
  /* use lpReserved10 for something useful, like GetProcAddress */
  X11DRV_OSMesa_GetProcAddress,
};

static D3DHAL_CALLBACKS2 d3dhal_callbacks2 = {
  sizeof(D3DHAL_CALLBACKS2),
  0x1f, /* all callbacks are 32-bit */
  X11DRV_OSMesa_SetRenderTarget
};

static D3DHAL_GLOBALDRIVERDATA d3dhal_driverdata = {
  sizeof(D3DHAL_GLOBALDRIVERDATA),
  {	/* hwCaps */
   sizeof(D3DDEVICEDESC_V1),
   D3DDD_WINE_OPENGL_DEVICE
  },
  0,	/* dwNumVertices */
  0,	/* dwNumClipVertices */
  0,	/* dwNumTextureFormats */
  NULL	/* lpTextureFormats */
};

DDHAL_DDEXEBUFCALLBACKS X11DRV_OSMesa_exebufcallbacks = {
  sizeof(DDHAL_DDEXEBUFCALLBACKS),
  0x1f, /* all callbacks are 32-bit */
  X11DRV_OSMesa_CanCreateEB,
  X11DRV_OSMesa_CreateEB,
  X11DRV_OSMesa_DestroyEB,
  X11DRV_OSMesa_LockEB,
  X11DRV_OSMesa_UnlockEB
};

struct GL_D3D_DriverCallbacks X11DRV_OSMesa_GL_D3D_driver_callbacks =
{
    sizeof(struct GL_D3D_DriverCallbacks),
    NULL, NULL, NULL
};

static X11DRIVERINFO driver_info[] = {
  {&GUID_D3DCallbacks2, sizeof(D3DHAL_CALLBACKS2), &d3dhal_callbacks2, NULL},
  {&GUID_WineGLD3DCallbacks, sizeof(struct GL_D3D_DriverCallbacks),
   &X11DRV_OSMesa_GL_D3D_driver_callbacks, NULL}
};


static LPDDHAL_CREATESURFACE X11DRV_OSMesa_old_create_surface;
static LPDDHALSURFCB_DESTROYSURFACE X11DRV_OSMesa_old_destroy_surface;

static DWORD X11DRV_OSMesa_GenTextureId(void)
{
    static DWORD id = 0;

    return ++id;
}

static void X11DRV_OSMesa_DeleteTextureId(DWORD id)
{
    if (id)
    {
	ENTER_GL();
	/* We really should make the appropriate context current.
	 * Note that gDeleteTextures is really the opposite of glTexImage*,
	 * rather than glGenTextures. */
	glDeleteTextures(1, (GLuint *)&id);
	LEAVE_GL();
    }
}

static DWORD CALLBACK
X11DRV_OSMesa_CreateSurface(LPDDHAL_CREATESURFACEDATA data)
{
  LPDDRAWI_DDRAWSURFACE_LCL lcl = *data->lplpSList;
  LPDDRAWI_DDRAWSURFACE_GBL gbl = lcl->lpGbl;
  LPDDRAWI_DDRAWSURFACE_GBL_MORE gblm = GET_LPDDRAWSURFACE_GBL_MORE(gbl);
  LPDDSURFACEDESC2 desc = (LPDDSURFACEDESC2)data->lpDDSurfaceDesc;

  if (desc->ddsCaps.dwCaps & DDSCAPS_TEXTURE) {
    /* only process late allocations (with a context) */
    if (!(desc->ddsCaps.dwCaps2 & DDSCAPS2_MIPMAPSUBLEVEL)) {
      ENTER_GL();
      gblm->dwDriverReserved = X11DRV_OSMesa_GenTextureId();
      LEAVE_GL();
      TRACE("generated texture %d\n", gblm->dwDriverReserved);
    }
  }

  if (X11DRV_OSMesa_old_create_surface)
    return X11DRV_OSMesa_old_create_surface(data);
  else
    return DDHAL_DRIVER_NOTHANDLED;
}

static DWORD PASCAL X11DRV_OSMesa_DestroySurface(LPDDHAL_DESTROYSURFACEDATA data)
{
  LPDDRAWI_DDRAWSURFACE_LCL lcl = data->lpDDSurface;
  LPDDRAWI_DDRAWSURFACE_GBL gbl = lcl->lpGbl;
  LPDDRAWI_DDRAWSURFACE_GBL_MORE gblm = GET_LPDDRAWSURFACE_GBL_MORE(gbl);

  if (lcl->ddsCaps.dwCaps & DDSCAPS_TEXTURE) {
    if (gblm->dwDriverReserved) {
      TRACE("deleted texture %d\n", gblm->dwDriverReserved);
      X11DRV_OSMesa_DeleteTextureId(gblm->dwDriverReserved);
      gblm->dwDriverReserved = 0;
    }
  }

  if (X11DRV_OSMesa_old_destroy_surface)
    return X11DRV_OSMesa_old_destroy_surface(data);
  else
    return DDHAL_DRIVER_NOTHANDLED;
}

int X11DRV_OSMesa_CreateDriver(LPDDHALINFO info)
{
  LPX11DEVICE device = (LPX11DEVICE)info->lpPDevice;

  if (!osmesa_available) return 0;

  /* attach Direct3D callbacks */
  info->lpD3DGlobalDriverData = (ULONG_PTR)&d3dhal_driverdata;
  info->lpD3DHALCallbacks = (ULONG_PTR)&d3dhal_callbacks;
  info->lpDDExeBufCallbacks = &X11DRV_OSMesa_exebufcallbacks;

  /* add 3D caps bits */
  info->ddCaps.dwCaps |= DDCAPS_3D;
  info->ddCaps.dwCaps2 |= DDCAPS2_NO2DDURING3DSCENE;
  info->ddCaps.ddsCaps.dwCaps |= DDSCAPS_TEXTURE | DDSCAPS_3DDEVICE |
                                 DDSCAPS_ZBUFFER | DDSCAPS_MIPMAP |
                                 DDSCAPS_EXECUTEBUFFER | DDSCAPS_OPTIMIZED;

  /* attach our D3D callback structures to driver info linked list */
  driver_info[0].lpNext = driver_info+1;
  driver_info[1].lpNext = device->lpInfo;
  device->lpInfo = driver_info;

  /* chain some surface callbacks */
  X11DRV_OSMesa_old_create_surface = info->lpDDCallbacks->CreateSurface;
  info->lpDDCallbacks->CreateSurface = X11DRV_OSMesa_CreateSurface;

  X11DRV_OSMesa_old_destroy_surface = info->lpDDSurfaceCallbacks->DestroySurface;
  info->lpDDSurfaceCallbacks->DestroySurface = X11DRV_OSMesa_DestroySurface;

  return TRUE;
}
