/*	Direct3D OpenGL driver
 *
 * Copyright 2001-2002 TransGaming Technologies Inc.
 */
#include "config.h"

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>

#include "winbase.h"
#include "wine/debug.h"
#include "ddrawi.h"
#include "d3d9.h"
#include "d3dhal.h"
#include "d3dgl_private.h"

/* #define DUMP_TEXTURE_DATA */
/* #define DUMP_TEXTURE_RELOAD */
/* unfortunately, GIMP don't seem to grok BITMAPV4HEADER (or BITMAPV5HEADER) yet */
#define DUMP_OLD_INFOHEADER

WINE_DEFAULT_DEBUG_CHANNEL(ddraw);

extern int use_texmem_fix;

#ifdef DUMP_TEXTURE_DATA
static void dump_texture(GL_D3D_priv *priv, LPD3D_T texture)
{
    static unsigned count = 0;
    char buf[128];
    FILE* f;
    size_t size, y;
    BITMAPFILEHEADER fh;
#ifdef DUMP_OLD_INFOHEADER
    BITMAPINFOHEADER bh;
#else
    BITMAPV4HEADER bh;
#endif
    LPBYTE data;
    LPDDRAWI_DDRAWPALETTE_INT pal_int = SURF_LCL(texture)->lpDDPalette;
    LPDDRAWI_DDRAWPALETTE_GBL pal = NULL;
    DWORD fcc = 0;

    if (SURF_GBL(texture)->ddpfSurface.dwFlags & DDPF_PALETTEINDEXED8) {
	if (priv->gbl_pal) {
	    /* Direct3D 8 shared palette */
	    pal = priv->gbl_pal;
	}
	else {
	    /* Direct3D 7 texture palette */
	    pal = pal_int ? pal_int->lpLcl->lpGbl : NULL;
	}
    }

    if (SURF_GBL(texture)->ddpfSurface.dwFlags & DDPF_FOURCC)
	fcc = SURF_GBL(texture)->ddpfSurface.dwFourCC;

    sprintf(buf, "texture_%d_%d.bmp", TEXTURE_ID(texture), ++count);

    size = fcc ? SURF_GBL(texture)->u4.dwLinearSize
	       : (SURF_GBL(texture)->u4.lPitch * SURF_GBL(texture)->wHeight);

    memset(&fh, 0, sizeof(fh));
    fh.bfType = 0x4d42; /* BM */
    fh.bfSize = sizeof(fh) + sizeof(bh) + size;
    fh.bfOffBits = sizeof(fh) + sizeof(bh);

    memset(&bh, 0, sizeof(bh));
#ifdef DUMP_OLD_INFOHEADER
    bh.biSize = sizeof(bh);
    bh.biWidth = SURF_GBL(texture)->wWidth;
    bh.biHeight = SURF_GBL(texture)->wHeight;
    bh.biPlanes = 1;
    bh.biBitCount = SURF_GBL(texture)->ddpfSurface.u1.dwRGBBitCount;
    bh.biCompression = fcc ? fcc : BI_RGB;
    bh.biSizeImage = size;
    if (!fcc && (bh.biBitCount == 16 || bh.biBitCount == 32)) {
	bh.biCompression = BI_BITFIELDS;
	fh.bfOffBits += 3 * sizeof(DWORD);
    }
    bh.biClrUsed = pal ? 256 : 0;
#else
    bh.bV4Size = sizeof(bh);
    bh.bV4Width = SURF_GBL(texture)->wWidth;
    bh.bV4Height = SURF_GBL(texture)->wHeight;
    bh.bV4Planes = 1;
    bh.bV4BitCount = SURF_GBL(texture)->ddpfSurface.u1.dwRGBBitCount;
    bh.bV4Compression = fcc ? fcc : (pal ? BI_RGB : BI_BITFIELDS);
    bh.bV4SizeImage = size;
    bh.bV4ClrUsed = pal ? 256 : 0;
    bh.bV4RedMask   = SURF_GBL(texture)->ddpfSurface.u2.dwRBitMask;
    bh.bV4GreenMask = SURF_GBL(texture)->ddpfSurface.u3.dwGBitMask;
    bh.bV4BlueMask  = SURF_GBL(texture)->ddpfSurface.u4.dwBBitMask;
    bh.bV4AlphaMask = SURF_GBL(texture)->ddpfSurface.u5.dwRGBAlphaBitMask;
    bh.bV4CSType = LCS_DEVICE_RGB;
#endif

    if (pal) fh.bfOffBits += sizeof(RGBQUAD) * 256;

    f = fopen(buf, "w+");
    fwrite(&fh, 1, sizeof(fh), f);
    fwrite(&bh, 1, sizeof(bh), f);

#ifdef DUMP_OLD_INFOHEADER
    if (!fcc && (bh.biBitCount == 16 || bh.biBitCount == 32)) {
	fwrite(&SURF_GBL(texture)->ddpfSurface.u2.dwRBitMask, 1, sizeof(DWORD), f);
	fwrite(&SURF_GBL(texture)->ddpfSurface.u3.dwGBitMask, 1, sizeof(DWORD), f);
	fwrite(&SURF_GBL(texture)->ddpfSurface.u4.dwBBitMask, 1, sizeof(DWORD), f);
    }
#endif

    if (pal) {
	RGBQUAD entry;
	for (y=0; y<256; y++) {
	    entry.rgbRed      = pal->lpColorTable[y].peRed;
	    entry.rgbGreen    = pal->lpColorTable[y].peGreen;
	    entry.rgbBlue     = pal->lpColorTable[y].peBlue;
	    entry.rgbReserved = pal->lpColorTable[y].peFlags;
	    fwrite(&entry, 1, sizeof(entry), f);
	}
    }

    data = (LPVOID)SURF_GBL(texture)->fpVidMem;
    if (fcc) {
	fwrite(data, 1, size, f);
    }
    else {
	y = SURF_GBL(texture)->wHeight;
	while (y) {
	    y--;
	    fwrite(data + y*SURF_GBL(texture)->u4.lPitch, 1, SURF_GBL(texture)->u4.lPitch, f);
	}
    }
    fclose(f);
}
#endif

static const GLenum GL_cube_targets[6] = {
    GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB,
    GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB,
    GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB,
    GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB,
    GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB,
    GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB
};

static inline GLenum GL_tgt_load(LPD3D_T tex) ATTR_PURE;
static inline GLenum GL_tgt_load(LPD3D_T tex)
{
    if (SURF_MORE(tex)->ddsCapsEx.dwCaps2 & DDSCAPS2_CUBEMAP) {
	unsigned u;
	for (u=0; u<6; u++)
	    if (SURF_MORE(tex)->ddsCapsEx.dwCaps2 & (0x400 << u))
		return GL_cube_targets[u];
    }
    return GL_TEXTURE_2D;
}

static inline GLenum GL_tgt_par(LPD3D_T tex) ATTR_PURE;
static inline GLenum GL_tgt_par(LPD3D_T tex)
{
    if (SURF_MORE(tex)->ddsCapsEx.dwCaps2 & DDSCAPS2_CUBEMAP)
	return GL_TEXTURE_CUBE_MAP_ARB;
    return GL_TEXTURE_2D;
}

static inline GLenum GL_tgt_bind(LPD3D_T tex) ATTR_PURE;
static inline GLenum GL_tgt_bind(LPD3D_T tex)
{
    if (SURF_MORE(tex)->ddsCapsEx.dwCaps2 & DDSCAPS2_CUBEMAP)
	return GL_TEXTURE_BINDING_CUBE_MAP_ARB;
    return GL_TEXTURE_BINDING_2D;
}

/* Right now, this only supports the formats we enumerate. */
static BOOL GL_PixelFormat_To_TextureFormat(GL_D3D_priv *priv,
					    const DDPIXELFORMAT *pfmt,
					    GLenum *fmt,
					    GLint *ifmt,
					    GLenum *type)
{
    static const DWORD unsupported = DDPF_BUMPDUDV | DDPF_BUMPLUMINANCE
	| DDPF_PALETTEINDEXED1
	| DDPF_PALETTEINDEXED2 | DDPF_PALETTEINDEXED4
	| DDPF_PALETTEINDEXEDTO8 | DDPF_RGBTOYUV | DDPF_STENCILBUFFER
	| DDPF_YUV | DDPF_ZBUFFER | DDPF_ZPIXELS;

    /* And ALPHAPREMULT might cause trouble. */

    TRACE("surface format %08lx, %ld\n", pfmt->dwFlags, pfmt->u1.dwRGBBitCount);
    TRACE("masks: %08lx, %08lx, %08lx, %08lx\n", pfmt->u2.dwRBitMask, pfmt->u3.dwGBitMask, pfmt->u4.dwBBitMask, pfmt->u5.dwRGBAlphaBitMask);

    if (pfmt->dwFlags & unsupported)
	return FALSE;

    if (pfmt->dwFlags & DDPF_FOURCC)
    {
	DD_GL_ext ext;
	TRACE("fourcc: %4.4s\n", (const char*)&pfmt->dwFourCC);
	if (!HAVE_GL_EXT(priv, ARB_texture_compression))
	    return FALSE;
	ext = 0;
	switch (pfmt->dwFourCC) {
	case 0x31545844: /* DXT1 */
	    if (pfmt->dwFlags & DDPF_ALPHAPIXELS)
		*fmt = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
	    else
		*fmt = GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
	    ext = EXT_texture_compression_s3tc;
	    break;
	case 0x33545844: /* DXT3 */
	    *fmt = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
	    ext = EXT_texture_compression_s3tc;
	    break;
	case 0x35545844: /* DXT5 */
	    *fmt = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
	    ext = EXT_texture_compression_s3tc;
	    break;
	}
	if (!ext) {
	    FIXME("unsupported texture format %4.4s\n", (const char*)&pfmt->dwFourCC);
	    return FALSE;
	}
	if (!HAVE_GL_EXT(priv, ext))
	    return FALSE;
	*ifmt = *fmt;
	*type = 0;
	return TRUE;
    }

    if (pfmt->dwFlags & DDPF_COMPRESSED)
	return FALSE;

    if (pfmt->dwFlags & DDPF_PALETTEINDEXED8)
    {
	if (!HAVE_GL_EXT(priv, EXT_paletted_texture))
	    return FALSE;
	*fmt = GL_COLOR_INDEX;
	*ifmt = GL_COLOR_INDEX;
	*type = GL_UNSIGNED_BYTE;
	return TRUE;
    }

    if (pfmt->dwFlags & DDPF_RGB)
    {
	if (pfmt->dwFlags & (DDPF_LUMINANCE|DDPF_ALPHA))
	    return FALSE;

	if (pfmt->dwFlags & DDPF_ALPHAPIXELS)
	{
	    *ifmt = GL_RGBA;
	    *fmt = GL_BGRA;

	    if (pfmt->u1.dwRGBBitCount == 32)
	    {
		*type = GL_UNSIGNED_BYTE;
		return TRUE;
	    }
	    else if (pfmt->u1.dwRGBBitCount == 16)
	    {
		if (pfmt->u5.dwRGBAlphaBitMask == 0x8000)
		    *type = GL_UNSIGNED_SHORT_1_5_5_5_REV;
		else
		    *type = GL_UNSIGNED_SHORT_4_4_4_4_REV;

		return TRUE;
	    }
	    else
		return FALSE;
	}
	else
	{
	    *ifmt = GL_RGB;

	    if (pfmt->u1.dwRGBBitCount == 32)
	    {
		*fmt = GL_BGRA;
		*type = GL_UNSIGNED_BYTE;
		return TRUE;
	    }
	    else if (pfmt->u1.dwRGBBitCount == 16)
	    {
		if (pfmt->u2.dwRBitMask & 0x8000)
		{
		    *fmt = GL_RGB;
		    *type = GL_UNSIGNED_SHORT_5_6_5;
		}
		else
		{
		    *fmt = GL_BGRA;
		    *type = GL_UNSIGNED_SHORT_1_5_5_5_REV;
		}

		return TRUE;
	    }
	    else
		return FALSE;
	}
    }

    if (pfmt->dwFlags & DDPF_LUMINANCE)
    {
	if (pfmt->dwFlags & DDPF_ALPHAPIXELS)
	{
	    *fmt = GL_LUMINANCE_ALPHA;
	    *ifmt = GL_LUMINANCE_ALPHA;
	    *type = GL_UNSIGNED_BYTE;
	}
	else
	{
	    *fmt = GL_LUMINANCE;
	    *ifmt = GL_LUMINANCE;
	    *type = GL_UNSIGNED_BYTE;
	}

	return TRUE;
    }

    return FALSE;
}

#ifdef HAVE_GLUBUILD2DMIPMAPLEVELS
static unsigned int GL_count_levels(unsigned int width, unsigned int height)
{
    unsigned int x = max(width, height);
    unsigned int levels = 0;

    while (x > 1)
    {
	x /= 2;
	levels++;
    }

    return levels;
}
#endif

static LPD3D_T GL_parent_texture(LPD3D_T texture)
{
    LPATTACHLIST from = SURF_LCL(texture)->lpAttachListFrom;
    if (from) {
	LPD3D_T surf = (LPD3D_T)from->lpAttached;
	return surf;
    }
    return NULL;
}

static LPD3D_T GL_top_texture(LPD3D_T texture)
{
    LPD3D_T cur = NULL, next = texture;
    while (next) {
	cur = next;
	if (!(SURF_MORE(cur)->ddsCapsEx.dwCaps2 & DDSCAPS2_MIPMAPSUBLEVEL))
	    break;
	next = GL_parent_texture(cur);
    }
    if (SURF_MORE(cur)->ddsCapsEx.dwCaps2 & DDSCAPS2_CUBEMAP) {
	/* assume that positive-X is first surface */
	while (next) {
	    cur = next;
	    if ((SURF_MORE(cur)->ddsCapsEx.dwCaps2 & DDSCAPS2_CUBEMAP_ALLFACES)
	        == DDSCAPS2_CUBEMAP_POSITIVEX)
		break;
	    next = GL_parent_texture(cur);
	}
    }
    return cur;
}

static LPD3D_T GL_attached_texture(LPD3D_T texture, DWORD dwCaps2)
{
    LPATTACHLIST next = SURF_LCL(texture)->lpAttachList;
    if ((dwCaps2 & DDSCAPS2_CUBEMAP_ALLFACES) == dwCaps2) {
	/* check the current one first */
	if (SURF_MORE(texture)->ddsCapsEx.dwCaps2 & dwCaps2)
	    return texture;
    }
    while (next) {
	LPD3D_T surf = (LPD3D_T)next->lpAttached;
	if ((SURF_LCL(surf)->ddsCaps.dwCaps & DDSCAPS_TEXTURE) &&
	    (SURF_MORE(surf)->ddsCapsEx.dwCaps2 & dwCaps2))
	    return surf;
	if (surf == texture) break;
	next = next->lpLink;
    }
    return NULL;
}

static LPD3D_T GL_next_mipmap(LPD3D_T texture)
{
    return GL_attached_texture(texture, DDSCAPS2_MIPMAPSUBLEVEL);
}

static void GL_load_color_mapping(GL_D3D_priv *priv, LPDDRAWI_DDRAWPALETTE_GBL pal, BOOL alpha)
{
    LPPALETTEENTRY table = pal->lpColorTable;
    GLfloat p_r[256], p_g[256], p_b[256], p_a[256];
    unsigned u;

    for (u=0; u<256; u++) {
	p_r[u] = table[u].peRed/255.0;
	p_g[u] = table[u].peGreen/255.0;
	p_b[u] = table[u].peBlue/255.0;
	if (alpha)
	    p_a[u] = table[u].peFlags/255.0;
	else
	    p_a[u] = 1.0;
    }

    glPixelMapfv(GL_PIXEL_MAP_I_TO_R, 256, p_r);
    glPixelMapfv(GL_PIXEL_MAP_I_TO_G, 256, p_g);
    glPixelMapfv(GL_PIXEL_MAP_I_TO_B, 256, p_b);
    glPixelMapfv(GL_PIXEL_MAP_I_TO_A, 256, p_a);
}

static int GL_load_texture_target(GL_D3D_priv *priv, LPD3D_T texture, LPD3D_T srctex,
				  GLenum load_target, GLint ifmt,
				  GLenum fmt, GLenum type, GLuint texid)
{
    LPD3D_T mipmap, srcmipmap;
    LPBYTE data;
    int level;

#ifdef DUMP_TEXTURE_DATA
    dump_texture(priv, texture);
#endif

    mipmap = texture;
    srcmipmap = srctex;
    level = 0;

    while (1)
    {
	LPD3D_T next;

	TRACE("loading target %04x, level %d, %dx%d, ptr %p\n",
	      load_target, level,
	      SURF_GBL(mipmap)->wWidth, SURF_GBL(mipmap)->wHeight,
	      (LPVOID)SURF_GBL(mipmap)->fpVidMem);
	data = srcmipmap ? (LPBYTE)SURF_GBL(srcmipmap)->fpVidMem : NULL;

	if (data) {
	    if (type) {
		unsigned p = SURF_GBL(srcmipmap)->u4.lPitch;
		unsigned bpp = (SURF_GBL(srcmipmap)->ddpfSurface.u1.dwRGBBitCount+7)/8;

		glPixelStorei(GL_UNPACK_ROW_LENGTH, p/bpp); /* the unpack alignment takes care of p%bpp */
		glTexImage2D(load_target, level, ifmt,
			     SURF_GBL(mipmap)->wWidth,
			     SURF_GBL(mipmap)->wHeight,
			     0, fmt, type,
			     data);
		glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
	    } else
		GLD(priv)->glCompressedTexImage2DARB(load_target, level, ifmt,
						     SURF_GBL(mipmap)->wWidth,
						     SURF_GBL(mipmap)->wHeight,
						     0, SURF_GBL(mipmap)->u4.dwLinearSize,
						     data);
	    CHECK_GL_ERROR("D3D_GL_load_texture");
	}

	TGLDEVICE(mipmap) = (DWORD)priv;
	TGLFMT(mipmap) = fmt;
	TGLIFMT(mipmap) = ifmt;
	TGLTYPE(mipmap) = type;
	TGLLEVEL(mipmap) = level;
	if( !TEXTURE_ID(mipmap) ) {
	  TEXTURE_ID(mipmap) = texid;
	}

	level++;

	/* Get the next mipmap. */
	if (!(next = GL_next_mipmap(mipmap)))
	{
	    TRACE("provided mipmap levels: %d\n", level);
	    level--; /* level is the max (smallest) mipmap level */

	    /* Fill in the remaining mipmaps as a workaround for drivers that
	     * do not support GL_TEXTURE_MAX_LEVEL.
	     *
	     * XXX Should we do this for compressed textures too?
	     *
	     * Note that old Mesa libGLU may not have this function.
	     * Get a recent version that includes the SGI libGLU.
	     */
#ifdef HAVE_GLUBUILD2DMIPMAPLEVELS
	    if (level && type
		&& (SURF_GBL(mipmap)->wWidth > 1
		    || SURF_GBL(mipmap)->wHeight > 1))
	    {
		DWORD max_level = level
		    + GL_count_levels(SURF_GBL(mipmap)->wWidth,
				      SURF_GBL(mipmap)->wHeight);

		TRACE("generating mipmap levels %d-%ld\n", level+1, max_level);
		gluBuild2DMipmapLevels(load_target, ifmt,
				       SURF_GBL(mipmap)->wWidth,
				       SURF_GBL(mipmap)->wHeight,
				       fmt, type, level, level+1, max_level,
				       data);
	    }
#endif
	    break;
	}

	if (mipmap == srcmipmap) srcmipmap = next;
	else srcmipmap = srcmipmap ? GL_next_mipmap(srcmipmap) : NULL;
	mipmap = next;
    }

    return level;
}

HRESULT GL_PrepTexture(GL_D3D_priv *priv, LPD3D_T texture, LPD3D_T srctex)
{
    LPDDPIXELFORMAT pfmt = &(SURF_GBL(srctex)->ddpfSurface);
    GLint oldtex, ifmt;
    GLenum target, fmt, type;
    int level;

    if (!TEXTURE_ID(texture) || TCONTEXTLESS(texture)) {
	LPDDRAWI_DDRAWSURFACE_LCL local = SURF_LCL(texture);
	DDHAL_CREATESURFACEDATA data;
	HRESULT hr;

	/* store which 3D context texture belongs to */
	local->dwReserved1 = priv->d3dp->dwhContext;

	/* call upon the HAL to allocate a texture ID */
	data.lpDD = SURF_GBL(texture)->u3.lpDD;
	data.lpDDSurfaceDesc = (LPDDSURFACEDESC)(SURF_MORE(texture)->pCreatedDDSurfaceDesc2);
	data.lplpSList = &local;
	data.dwSCnt = 1;
	data.ddRVal = 0;
	data.CreateSurface = priv->dd_gbl->lpDDCBtmp->HALDD.CreateSurface;
	hr = data.CreateSurface(&data);

	if (!TEXTURE_ID(texture)) {
	    ERR("can't allocate texture ID\n");
	    return DDERR_GENERIC;
	}
    }

    if (!GL_PixelFormat_To_TextureFormat(priv, pfmt, &fmt, &ifmt, &type))
    {
	FIXME("unhandled format %lx, %ld\n", pfmt->dwFlags,
	      pfmt->u1.dwRGBBitCount);
	return DDERR_INVALIDPARAMS;
    }
    TRACE("ID %d, fmt %X, ifmt %X, type %X\n", TEXTURE_ID(texture), fmt, ifmt, type);

    target = GL_tgt_par(texture);

    glGetIntegerv(GL_tgt_bind(texture), &oldtex);

    glBindTexture(target, (GLuint)TEXTURE_ID(texture));

    if (fmt == GL_COLOR_INDEX) {
	LPDDRAWI_DDRAWPALETTE_INT pal_int = SURF_LCL(texture)->lpDDPalette;
	LPDDRAWI_DDRAWPALETTE_GBL pal = NULL;
	GLenum palfmt = 0;

	if (priv->gbl_pal) {
	    /* Direct3D 8 shared palette */
	    pal = priv->gbl_pal;
	}
	else {
	    /* Direct3D 7 texture palette */
	    pal = pal_int ? pal_int->lpLcl->lpGbl : NULL;
	}

	if (pal) {
	    palfmt = (pal->dwFlags & DDPCAPS_ALPHA) ? GL_RGBA : GL_RGB;
	}

	TRACE("loading palette (%p) entries (%p) (fmt=%x)\n", pal, pal ? pal->lpColorTable : NULL, palfmt);

	if (HAVE_GL_EXT(priv, EXT_shared_texture_palette) && priv->gbl_pal) {
	    ifmt = GL_COLOR_INDEX8_EXT;
	} else
	if (HAVE_GL_EXT(priv, EXT_paletted_texture)) {
	    if (pal) {
		glColorTable(target, palfmt, 256,
			     GL_RGBA, GL_UNSIGNED_BYTE, pal->lpColorTable);
	    } /* else hopefully the palette will be set later */
	    ifmt = GL_COLOR_INDEX8_EXT;
	} else
	{
	    /* without EXT_paletted_texture, the internal format must be RGB/RGBA */
	    ifmt = palfmt;

	    /* give OpenGL the palette entries so it can convert to the internal format */
	    if (pal) {
		GL_load_color_mapping(priv, pal, ifmt == GL_RGBA);
	    } else {
		ERR("converting paletted texture without palette\n");
		/* FIXME: wait for set_palette before loading texture? */
	    }
	}

	TPALETTE(texture) = (DWORD)pal;
	TPALSTAMP(texture) = pal ? pal->dwContentsStamp : 0;
    }

    if (target == GL_TEXTURE_CUBE_MAP_ARB) {
      int nlevel;
      unsigned u;
      LPD3D_T face, srcface;
      level = -1;
      for (u=0; u<6; u++) {
	face = GL_attached_texture(texture, 0x400 << u);
	srcface = GL_attached_texture(srctex, 0x400 << u);
	if (face) {
	  nlevel = GL_load_texture_target(priv, face, srcface, GL_cube_targets[u], ifmt, fmt, type,
                                          TEXTURE_ID(texture));
	  if (level == -1 || nlevel < level) level = nlevel;
	}
      }
      if (level == -1) level = 0;
    } else
      level = GL_load_texture_target(priv, texture, srctex, target, ifmt, fmt, type, TEXTURE_ID(texture));

    glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, level);

    glBindTexture(target, oldtex);

    TRACE("initialized GL texture %d\n", TEXTURE_ID(texture));

    return D3D_OK;
}

#ifdef HAVE_GLUBUILD2DMIPMAPLEVELS
static BOOL
D3D_GL_is_last_mipmap(LPD3D_T texture)
{
    return !GL_next_mipmap(texture);
}
#endif

HRESULT D3D_GL_set_palette(GL_D3D_priv* priv, LPD3DHAL_DP2COMMAND cmd, LPVOID data1, LPVOID data2)
{
    LPD3DHAL_DP2SETPALETTE dat = (LPD3DHAL_DP2SETPALETTE)data1;
    LPDDRAWI_DDRAWPALETTE_GBL pal = (LPDDRAWI_DDRAWPALETTE_GBL)dat->dwPaletteHandle;
    LPD3D_T texture = (LPD3D_T)dat->dwSurfaceHandle;

    TRACE("(%p): pal=%p, flags=%08lx, tex=%p\n", priv, pal, dat->dwPaletteFlags, texture);

    if (!texture) priv->gbl_pal = pal;

    /* let D3D_GL_update_palettes deal with it */

    return D3D_OK;
}

HRESULT D3D_GL_update_palette(GL_D3D_priv* priv, LPD3DHAL_DP2COMMAND cmd, LPVOID data1, LPVOID data2)
{
    LPD3DHAL_DP2UPDATEPALETTE dat = (LPD3DHAL_DP2UPDATEPALETTE)data1;
    LPDDRAWI_DDRAWPALETTE_GBL pal = (LPDDRAWI_DDRAWPALETTE_GBL)dat->dwPaletteHandle;

    TRACE("(%p): pal=%p, start=%d, count=%d\n", priv, pal, dat->wStartIndex, dat->wNumEntries);

    if (!pal) return D3D_OK;

#if 0 /* shouldn't call GL from here without calling RenderLock/RenderUnlock */
    if (priv->gbl_pal) {
	/* Direct3D 8 shared palette */
	if (pal != priv->gbl_pal) return D3D_OK;
	if (HAVE_GL_EXT(priv, EXT_shared_texture_palette)) {
	    glColorSubTable(GL_SHARED_TEXTURE_PALETTE_EXT, dat->wStartIndex, dat->wNumEntries,
			    GL_RGBA, GL_UNSIGNED_BYTE, pal->lpColorTable + dat->wStartIndex);
	    priv->gbl_pal_stamp = pal->dwContentsStamp;
	    return D3D_OK;
	}
    }
#endif

    /* let D3D_GL_update_palettes deal with it */

    return D3D_OK;
}

HRESULT D3D_GL_update_palettes(GL_D3D_priv* priv)
{
    unsigned u;

    /* update shared palette */
    if (HAVE_GL_EXT(priv, EXT_shared_texture_palette)) {
	if (priv->gbl_pal) {
	    LPDDRAWI_DDRAWPALETTE_GBL pal = priv->gbl_pal;
	    GLenum palfmt = (pal->dwFlags & DDPCAPS_ALPHA) ? GL_RGBA : GL_RGB;
	    if (priv->gbl_pal_last != pal ||
		priv->gbl_pal_stamp != pal->dwContentsStamp) {
		glColorTable(GL_SHARED_TEXTURE_PALETTE_EXT, palfmt, 256,
			     GL_RGBA, GL_UNSIGNED_BYTE, pal->lpColorTable);
		if (!priv->gbl_pal_last)
		    glEnable(GL_SHARED_TEXTURE_PALETTE_EXT);
		priv->gbl_pal_last = pal;
		priv->gbl_pal_stamp = pal->dwContentsStamp;
	    }
	}
	else if (priv->gbl_pal_last) {
	    glDisable(GL_SHARED_TEXTURE_PALETTE_EXT);
	    priv->gbl_pal_last = NULL;
	}
    }

    /* update currently bound textures */
    for (u=0; u<priv->lpCaps->wMaxTextureBlendStages; u++) {
	LPD3D_T texture = (LPD3D_T)priv->d3dp->texstagestate[u][D3DTSS_TEXTUREMAP];
	LPDDRAWI_DDRAWPALETTE_INT pal_int = texture ? SURF_LCL(texture)->lpDDPalette : NULL;
	LPDDRAWI_DDRAWPALETTE_GBL pal = NULL;
	if (!texture) continue;
	if (TGLFMT(texture) != GL_COLOR_INDEX) goto unload_texture;
	if (priv->gbl_pal) {
	    /* Direct3D 8 shared palette */
	    pal = priv->gbl_pal;
	    if (HAVE_GL_EXT(priv, EXT_shared_texture_palette)) {
		/* the shared palette is updated by D3D_GL_update_palette */
		TPALETTE(texture) = (DWORD)pal;
		TPALSTAMP(texture) = pal->dwContentsStamp;
		goto unload_texture;
	    }
	}
	else {
	    /* Direct3D 7 texture palette */
	    pal = pal_int ? pal_int->lpLcl->lpGbl : NULL;
	}
	if (!pal) continue; /* can't proceed without a palette */

	/* check if reloading texture palette is needed */
	if (TPALETTE(texture) != (DWORD)pal ||
	    TPALSTAMP(texture) != pal->dwContentsStamp) {
	    /* ok, need to reload */
	    if (HAVE_GL_EXT(priv, EXT_paletted_texture)) {
		GLenum target = GL_tgt_par(texture);
		GLenum palfmt = (pal->dwFlags & DDPCAPS_ALPHA) ? GL_RGBA : GL_RGB;
		GLint oldtex;
		glGetIntegerv(GL_tgt_bind(texture), &oldtex);
		glBindTexture(target, TEXTURE_ID(texture));
		glColorTable(target, palfmt, 256,
			     GL_RGBA, GL_UNSIGNED_BYTE, pal->lpColorTable);
		glBindTexture(target, oldtex);
		TPALETTE(texture) = (DWORD)pal;
		TPALSTAMP(texture) = pal->dwContentsStamp;
		goto unload_texture;
	    } else {
		/* in case we're switching between RGB and RGBA palettes,
		 * the texture must be completely reloaded with GL_PrepTexture
		 * (but we could do better if we're not switching, I guess?) */
		GL_PrepTexture(priv, texture, texture);
		continue; /* can't unload texture */
	    }
	}

	/* if we can't store the indexed data in OpenGL, we need to retain
	 * the system-memory copy for future palette changes. */
	if (!HAVE_GL_EXT(priv, EXT_paletted_texture)) continue;
unload_texture:
	/* if the texture has a DIB section, we can't kill it like this */
	if (texture->gbl_more.hKernelSurface) continue;

	/* if it's a dynamic texture, we should keep a lockable copy around */
	if (texture->more.ddsCapsEx.dwCaps2 & DDSCAPS2_HINTDYNAMIC) continue;

	/* some d3d8 games treat textures as dynamic, but the d3d8 api doesn't
	 * have a way to say so (although d3d9 does), so we'll make this configurable.
	 */
	if (!use_texmem_fix) continue;

	/* once we're sure that we won't lose any information by doing it,
	 * we can free the original system-memory copy, and count on our ability
	 * to re-fetch the data from OpenGL later if needed. */
	while (texture) {
	    LPVOID data = (LPVOID)texture->gbl.fpVidMem;
	    if (data) {
		if (HeapFree(priv->d3dp->surface_heap, 0, data)) {
		    texture->gbl.fpVidMem = 0;
		    TRACE("freed system-memory copy of texture %p (level %ld, bits=%p)\n", texture, TGLLEVEL(texture), data);
		} else {
		    FIXME("system-memory copy of texture %p (level %ld, bits=%p) could not be freed\n", texture, TGLLEVEL(texture), data);
		}
	    }
	    texture = GL_next_mipmap(texture);
	}
    }
    return D3D_OK;
}

static HRESULT D3D_GL_texblt(GL_D3D_priv* priv, LPD3DHAL_DP2COMMAND cmd, LPVOID data1, LPVOID data2)
{
    LPD3DHAL_DP2TEXBLT dat = (LPD3DHAL_DP2TEXBLT)data1;
    LPD3D_T srctex = (LPD3D_T)dat->dwDDSrcSurface;
    LPD3D_T texture = (LPD3D_T)dat->dwDDDestSurface;
    GLint oldtex;
    GLenum target, load_target;
    LPBYTE data;

    TRACE("(%p): src=%p, dst=%p\n", priv, srctex, texture);

    /* PreLoad? */
    if (!texture) {
	if (TCONTEXTLESS(srctex))
	    return GL_PrepTexture(priv, srctex, srctex);
	return D3D_OK;
    }

    if (srctex) {
	/* Texture previously been loaded into this context? */
	if (TGLDEVICE(texture) != (DWORD)priv)
	    /* no, load texture */
	    return GL_PrepTexture(priv, texture, srctex);
	/* yes, update from source */
    }
    else {
	/* Texture previously been loaded into this context? */
	if (TGLDEVICE(texture) != (DWORD)priv) return D3D_OK;
	srctex = texture;
    }

    data = (LPBYTE)SURF_GBL(srctex)->fpVidMem;
    if (!data) {
	FIXME("no source data for texture load\n");
	return D3D_OK;
    }

    target = GL_tgt_par(texture);
    load_target = GL_tgt_load(texture);

    /* OK, perform requested update */
    glGetIntegerv(GL_tgt_bind(texture), &oldtex);
    glBindTexture(target, TEXTURE_ID(texture));

    if (TGLFMT(texture) == GL_COLOR_INDEX) {
	LPDDRAWI_DDRAWPALETTE_INT pal_int = SURF_LCL(texture)->lpDDPalette;
	LPDDRAWI_DDRAWPALETTE_GBL pal = NULL;

	if (priv->gbl_pal) {
	    /* Direct3D 8 shared palette */
	    pal = priv->gbl_pal;
	}
	else {
	    /* Direct3D 7 texture palette */
	    pal = pal_int ? pal_int->lpLcl->lpGbl : NULL;
	}

	if (!HAVE_GL_EXT(priv, EXT_paletted_texture))
	{
	    TRACE("loading palette (%p)\n", pal);
	    GL_load_color_mapping(priv, pal, TGLFMT(texture) == GL_RGBA);
	}
    }

#ifdef DUMP_TEXTURE_DATA
#ifdef DUMP_TEXTURE_RELOAD
    if (!TGLLEVEL(texture)) dump_texture(priv, texture);
#endif
#endif

    if (TGLTYPE(texture))
    {
	unsigned p = SURF_GBL(srctex)->u4.lPitch;
	unsigned bpp = (SURF_GBL(srctex)->ddpfSurface.u1.dwRGBBitCount+7)/8;
	data += p * dat->rSrc.top + bpp * dat->rSrc.left;

	glPixelStorei(GL_UNPACK_ROW_LENGTH, p/bpp); /* the unpack alignment takes care of p%bpp */
	glTexSubImage2D(load_target, TGLLEVEL(texture),
			dat->rSrc.left, dat->rSrc.top,
			dat->rSrc.right - dat->rSrc.left, dat->rSrc.bottom - dat->rSrc.top,
			TGLFMT(texture), TGLTYPE(texture), data);
	glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);

	/* Despite GL_TEXTURE_MAX_LEVEL, we actually load all the mipmap
	 * levels to support buggy hardware.
	 * If the smallest mipmap level is being reloaded, then we need to
	 * regenerate the mipmap levels from it. */
#ifdef HAVE_GLUBUILD2DMIPMAPLEVELS
	if (TGLLEVEL(texture) && D3D_GL_is_last_mipmap(texture)
	    && (SURF_GBL(texture)->wWidth > 1
		|| SURF_GBL(texture)->wHeight > 1))

	{
	    DWORD max_level = TGLLEVEL(texture);
		+ GL_count_levels(SURF_GBL(texture)->wWidth,
				  SURF_GBL(texture)->wHeight);

	    gluBuild2DMipmapLevels(load_target, TGLIFMT(texture),
				   SURF_GBL(texture)->wWidth,
				   SURF_GBL(texture)->wHeight,
				   TGLFMT(texture), TGLTYPE(texture),
				   TGLLEVEL(texture), TGLLEVEL(texture)+1,
				   max_level, data);
	}
#endif

    }
    else
	/* let's just reload entire texture here */
	GLD(priv)->glCompressedTexSubImage2DARB(load_target, TGLLEVEL(texture),
						0, 0, SURF_GBL(texture)->wWidth, SURF_GBL(texture)->wHeight,
						TGLFMT(texture), SURF_GBL(texture)->u4.dwLinearSize, data);

    glBindTexture(target, oldtex);
    CHECK_GL_ERROR("D3D_GL_texblt");
    TRACE("updated GL texture %d\n", TEXTURE_ID(texture));

    return D3D_OK;
}

HRESULT D3D_GL_sync_texblt(GL_D3D_priv* priv, LPD3DHAL_DP2COMMAND cmd, LPVOID data1, LPVOID data2)
{
#if 0
    LPD3DHAL_DP2TEXBLT dat = (LPD3DHAL_DP2TEXBLT)data1;

    /* sync texblt should only handle srctex=NULL (ordinary dirty texture update) */
    if (dat->dwDDSrcSurface) return D3D_OK;

    return D3D_GL_texblt(priv, cmd, data1, data2);
#else
    return D3D_OK;
#endif
}

HRESULT D3D_GL_async_texblt(GL_D3D_priv* priv, LPD3DHAL_DP2COMMAND cmd, LPVOID data1, LPVOID data2)
{
    HRESULT hr;
#if 0
    LPD3DHAL_DP2TEXBLT dat = (LPD3DHAL_DP2TEXBLT)data1;

    /* async texblt should only do preloads and texture loads from another surface */
    if (!dat->dwDDSrcSurface) return D3D_OK;
#endif

    if (priv->callbacks.D3dRenderLock)
      priv->callbacks.D3dRenderLock(priv->d3dp->dwhContext, FALSE, NULL);

    hr = D3D_GL_texblt(priv, cmd, data1, data2);

    if (priv->callbacks.D3dRenderUnlock)
      priv->callbacks.D3dRenderUnlock(priv->d3dp->dwhContext, FALSE);

    return hr;
}

DWORD PASCAL D3DGL_Lock(LPDDHAL_LOCKDATA data)
{
    DD_GL_data *gld = (DD_GL_data *)data->lpDD->dwReserved3;
    LPD3D_T tex = (LPD3D_T)data->lpDDSurface;
    LPD3D_T toptex = GL_top_texture(tex);
    GL_D3D_priv *priv = (GL_D3D_priv *)TGLDEVICE(toptex);
    DWORD ret = DDHAL_DRIVER_NOTHANDLED;

    TRACE("surf=%p, topsurf=%p (ID=%d, dev=0x%08lx, ctx=0x%08x, bits=0x%08x)\n",
          tex, toptex, TEXTURE_ID(toptex),
          TGLDEVICE(toptex), SURF_LCL(toptex)->dwReserved1, tex->gbl.fpVidMem);
    TLOCKS(tex)++;
    if (priv && TEXTURE_ID(toptex) && !TCONTEXTLESS(toptex) && !tex->gbl.fpVidMem) {
      LPVOID ptr;
      DWORD dwSize = SURF_GBL(tex)->ddpfSurface.dwFourCC ? SURF_GBL(tex)->u4.dwLinearSize :
                                                           SURF_GBL(tex)->u4.lPitch * SURF_GBL(tex)->wHeight;

      ptr = HeapAlloc(priv->d3dp->surface_heap, 0, dwSize);
      if( ptr == NULL ) {
        /* Expect trouble, but perhaps something will work. Most likely gl will crash */
        ERR( "HeapAlloc failed\n" );
      }

      if (data->bHasRect || !(data->dwFlags & DDLOCK_WRITEONLY)) {
        GLint oldtex;
        GLenum target;

        if (priv->callbacks.D3dRenderLock)
          priv->callbacks.D3dRenderLock(priv->d3dp->dwhContext, FALSE, NULL);

        target = GL_tgt_par(tex);

        glGetIntegerv(GL_tgt_bind(tex), &oldtex);
        glBindTexture(target, (GLuint)TEXTURE_ID(toptex));

        /* FIXME: grab only the requested rectangle? */
        if (TGLTYPE(tex)) {
          unsigned p = SURF_GBL(tex)->u4.lPitch;
          unsigned bpp = (SURF_GBL(tex)->ddpfSurface.u1.dwRGBBitCount+7)/8;

          glPixelStorei(GL_PACK_ROW_LENGTH, p/bpp); /* the pack alignment takes care of p%bpp */
          glGetTexImage(GL_tgt_load(tex), TGLLEVEL(tex), TGLFMT(tex), TGLTYPE(tex), ptr);
          glPixelStorei(GL_PACK_ROW_LENGTH, 0);
        } else
          GLD(priv)->glGetCompressedTexImageARB(GL_tgt_load(tex), TGLLEVEL(tex), ptr);

        glBindTexture(target, oldtex);

        if (priv->callbacks.D3dRenderUnlock)
          priv->callbacks.D3dRenderUnlock(priv->d3dp->dwhContext, FALSE);
        TRACE("reallocated system-memory copy of texture %p level %ld (bits=%p, has_rect=%ld, flags=0x%lx)\n", tex, TGLLEVEL(tex), ptr, data->bHasRect, data->dwFlags);
      }
      else {
        TRACE("reallocated system-memory-copy of texture %p level %ld (bits=%p, discarding existing contents)\n", tex, TGLLEVEL(tex), ptr);
      }

      tex->gbl.fpVidMem = (FLATPTR)ptr;
      data->lpSurfData = ptr;
      ret = DDHAL_DRIVER_HANDLED;
    }
    if (gld->old_lock && gld->old_lock(data) == DDHAL_DRIVER_HANDLED)
      ret = DDHAL_DRIVER_HANDLED;
    return ret;
}

DWORD PASCAL D3DGL_Unlock(LPDDHAL_UNLOCKDATA data)
{
    DD_GL_data *gld = (DD_GL_data *)data->lpDD->dwReserved3;
    LPD3D_T tex = (LPD3D_T)data->lpDDSurface;
    /* GL_D3D_priv *priv = (GL_D3D_priv *)TGLDEVICE(tex); */
    TRACE("surf=%p\n", tex);
    TLOCKS(tex)--;
    /* ddraw/d3d8 will schedule a texblt, don't have to do anything here */
    return gld->old_unlock ? gld->old_unlock(data) : DDHAL_DRIVER_NOTHANDLED;
}

DWORD PASCAL D3DGL_Blt(LPDDHAL_BLTDATA data)
{
    DD_GL_data *gld = (DD_GL_data *)data->lpDD->dwReserved3;
    LPD3D_T dest = (LPD3D_T)data->lpDDDestSurface;
    LPD3D_T src = (LPD3D_T)data->lpDDSrcSurface;
    TRACE("dest=%p src=%p\n", dest, src);
    return gld->old_blt ? gld->old_blt(data) : DDHAL_DRIVER_NOTHANDLED;
}
