/* Direct3D Texture
 * Copyright (c) 1998 Lionel ULMER
 *
 * This file contains the implementation of interface Direct3DTexture2.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "config.h"

#include <stdarg.h>
#include <string.h>

#define NONAMELESSUNION
#define NONAMELESSSTRUCT

#include "windef.h"
#include "winbase.h"
#include "winerror.h"
#include "objbase.h"
#include "wingdi.h"
#include "ddraw.h"
#include "d3d.h"
#include "wine/debug.h"

#include "opengl_private.h"

WINE_DEFAULT_DEBUG_CHANNEL(ddraw);
WINE_DECLARE_DEBUG_CHANNEL(ddraw_tex);

#include <stdio.h>

static void 
snoop_texture(IDirectDrawSurfaceImpl *This) {
    IDirect3DTextureGLImpl *glThis = (IDirect3DTextureGLImpl *) This->tex_private;
    char buf[128];
    FILE *f;

    TRACE_(ddraw_tex)("Dumping surface id (%5d) level (%2d) : \n", glThis->tex_name, This->mipmap_level);
    DDRAW_dump_surface_desc(&(This->surface_desc));
    
    sprintf(buf, "tex_%05d_%02d.pnm", glThis->tex_name, This->mipmap_level);
    f = fopen(buf, "wb");
    DDRAW_dump_surface_to_disk(This, f, 1);
}

static IDirectDrawSurfaceImpl *
get_sub_mimaplevel(IDirectDrawSurfaceImpl *tex_ptr)
{
    /* Now go down the mipmap chain to the next surface */
    static const DDSCAPS2 mipmap_caps = { DDSCAPS_MIPMAP | DDSCAPS_TEXTURE, 0, 0, 0 };
    LPDIRECTDRAWSURFACE7 next_level;
    IDirectDrawSurfaceImpl *surf_ptr;
    HRESULT hr;
    
    hr = IDirectDrawSurface7_GetAttachedSurface(ICOM_INTERFACE(tex_ptr, IDirectDrawSurface7),
						(DDSCAPS2 *) &mipmap_caps, &next_level);
    if (FAILED(hr)) return NULL;
    
    surf_ptr = ICOM_OBJECT(IDirectDrawSurfaceImpl, IDirectDrawSurface7, next_level);
    IDirectDrawSurface7_Release(next_level);
    
    return surf_ptr;
}

/*******************************************************************************
 *			   IDirectSurface callback methods
 */

HRESULT
gltex_download_texture(IDirectDrawSurfaceImpl *surf_ptr) {
    IDirect3DTextureGLImpl *gl_surf_ptr = (IDirect3DTextureGLImpl *) surf_ptr->tex_private;

    FIXME("This is not supported yet... Expect some graphical glitches !!!\n");

    /* GL and memory are in sync again ... 
       No need to change the 'global' flag as it only handles the 'MEMORY_DIRTY' case.
    */
    gl_surf_ptr->dirty_flag = SURFACE_MEMORY;
    
    return DD_OK;
}

static GLenum
convert_min_filter_to_GL(D3DTEXTUREMINFILTER dwMinState, D3DTEXTUREMIPFILTER dwMipState)
{
    GLenum gl_state;

    if (dwMipState == D3DTFP_NONE) {
        switch (dwMinState) {
            case D3DTFN_POINT:  gl_state = GL_NEAREST; break;
	    case D3DTFN_LINEAR: gl_state = GL_LINEAR;  break;
	    default:            gl_state = GL_LINEAR;  break;
	}
    } else if (dwMipState == D3DTFP_POINT) {
        switch (dwMinState) {
            case D3DTFN_POINT:  gl_state = GL_NEAREST_MIPMAP_NEAREST; break;
	    case D3DTFN_LINEAR: gl_state = GL_LINEAR_MIPMAP_NEAREST;  break;
	    default:            gl_state = GL_LINEAR_MIPMAP_NEAREST;  break;
	}
    } else {
        switch (dwMinState) {
            case D3DTFN_POINT:  gl_state = GL_NEAREST_MIPMAP_LINEAR; break;
	    case D3DTFN_LINEAR: gl_state = GL_LINEAR_MIPMAP_LINEAR;  break;
	    default:            gl_state = GL_LINEAR_MIPMAP_LINEAR;  break;
	}
    }
    return gl_state;
}

static GLenum
convert_mag_filter_to_GL(D3DTEXTUREMAGFILTER dwState)
{
    GLenum gl_state;

    switch (dwState) {
        case D3DTFG_POINT:
	    gl_state = GL_NEAREST;
	    break;
        case D3DTFG_LINEAR:
	    gl_state = GL_LINEAR;
	    break;
        default:
	    gl_state = GL_LINEAR;
	    break;
    }
    return gl_state;
}

static GLenum
convert_tex_address_to_GL(D3DTEXTUREADDRESS dwState)
{
    GLenum gl_state;
    switch (dwState) {
        case D3DTADDRESS_WRAP:   gl_state = GL_REPEAT; break;
	case D3DTADDRESS_CLAMP:  gl_state = GL_CLAMP; break;
	case D3DTADDRESS_BORDER: gl_state = GL_CLAMP_TO_EDGE; break;
	case D3DTADDRESS_MIRROR:
	    if (GL_extensions.mirrored_repeat) {
		gl_state = GL_MIRRORED_REPEAT_WINE;
	    } else {
		gl_state = GL_REPEAT;
		/* This is a TRACE instead of a FIXME as the FIXME was already printed when the game
		   actually set D3DTADDRESS_MIRROR.
		*/
		TRACE(" setting GL_REPEAT instead of GL_MIRRORED_REPEAT.\n");
	    }
	break;
	default:                 gl_state = GL_REPEAT; break;
    }
    return gl_state;
}

HRESULT
gltex_upload_texture(IDirectDrawSurfaceImpl *surf_ptr, IDirect3DDeviceImpl *d3ddev, DWORD stage) {
    IDirect3DTextureGLImpl *gl_surf_ptr = (IDirect3DTextureGLImpl *) surf_ptr->tex_private;
    IDirect3DDeviceGLImpl *gl_d3ddev = (IDirect3DDeviceGLImpl *) d3ddev;
    BOOLEAN changed = FALSE;
    GLenum unit = GL_TEXTURE0_WINE + stage;
    
    if (surf_ptr->mipmap_level != 0) {
        WARN(" application activating a sub-level of the mipmapping chain (level %d) !\n", surf_ptr->mipmap_level);
    }

    /* Now check if the texture parameters for this texture are still in-line with what D3D expect
       us to do..

       NOTE: there is no check for the situation where the same texture is bound to multiple stage
             but with different parameters per stage.
    */
    if ((gl_surf_ptr->tex_parameters == NULL) ||
	(gl_surf_ptr->tex_parameters[D3DTSS_MAXMIPLEVEL - D3DTSS_ADDRESSU] != 
	 d3ddev->state_block.texture_stage_state[stage][D3DTSS_MAXMIPLEVEL - 1])) {
	DWORD max_mip_level;
	
	if ((surf_ptr->surface_desc.ddsCaps.dwCaps & DDSCAPS_MIPMAP) == 0) {
	    max_mip_level = 0;
	} else {
	    max_mip_level = surf_ptr->surface_desc.u2.dwMipMapCount - 1;
	    if (d3ddev->state_block.texture_stage_state[stage][D3DTSS_MAXMIPLEVEL - 1] != 0) {
		if (max_mip_level >= d3ddev->state_block.texture_stage_state[stage][D3DTSS_MAXMIPLEVEL - 1]) {
		    max_mip_level = d3ddev->state_block.texture_stage_state[stage][D3DTSS_MAXMIPLEVEL - 1] - 1;
		}
	    }
	}
	if (unit != gl_d3ddev->current_active_tex_unit) {
	    GL_extensions.glActiveTexture(unit);
	    gl_d3ddev->current_active_tex_unit = unit;
	}
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, max_mip_level);
	changed = TRUE;
    }
    
    if ((gl_surf_ptr->tex_parameters == NULL) ||
	(gl_surf_ptr->tex_parameters[D3DTSS_MAGFILTER - D3DTSS_ADDRESSU] != 
	 d3ddev->state_block.texture_stage_state[stage][D3DTSS_MAGFILTER - 1])) {
	if (unit != gl_d3ddev->current_active_tex_unit) {
	    GL_extensions.glActiveTexture(unit);
	    gl_d3ddev->current_active_tex_unit = unit;
	}
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, 
			convert_mag_filter_to_GL(d3ddev->state_block.texture_stage_state[stage][D3DTSS_MAGFILTER - 1]));
	changed = TRUE;
    }
    if ((gl_surf_ptr->tex_parameters == NULL) ||
	(gl_surf_ptr->tex_parameters[D3DTSS_MINFILTER - D3DTSS_ADDRESSU] != 
	 d3ddev->state_block.texture_stage_state[stage][D3DTSS_MINFILTER - 1]) ||
	(gl_surf_ptr->tex_parameters[D3DTSS_MIPFILTER - D3DTSS_ADDRESSU] != 
	 d3ddev->state_block.texture_stage_state[stage][D3DTSS_MIPFILTER - 1])) {
	if (unit != gl_d3ddev->current_active_tex_unit) {
	    GL_extensions.glActiveTexture(unit);
	    gl_d3ddev->current_active_tex_unit = unit;
	}
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
			convert_min_filter_to_GL(d3ddev->state_block.texture_stage_state[stage][D3DTSS_MINFILTER - 1],
						 d3ddev->state_block.texture_stage_state[stage][D3DTSS_MIPFILTER - 1]));
	changed = TRUE;
    }
    if ((gl_surf_ptr->tex_parameters == NULL) ||
	(gl_surf_ptr->tex_parameters[D3DTSS_ADDRESSU - D3DTSS_ADDRESSU] != 
	 d3ddev->state_block.texture_stage_state[stage][D3DTSS_ADDRESSU - 1])) {
	if (unit != gl_d3ddev->current_active_tex_unit) {
	    GL_extensions.glActiveTexture(unit);
	    gl_d3ddev->current_active_tex_unit = unit;
	}
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,
			convert_tex_address_to_GL(d3ddev->state_block.texture_stage_state[stage][D3DTSS_ADDRESSU - 1]));
	changed = TRUE;
    }
    if ((gl_surf_ptr->tex_parameters == NULL) ||
	(gl_surf_ptr->tex_parameters[D3DTSS_ADDRESSV - D3DTSS_ADDRESSU] != 
	 d3ddev->state_block.texture_stage_state[stage][D3DTSS_ADDRESSV - 1])) {
	if (unit != gl_d3ddev->current_active_tex_unit) {
	    GL_extensions.glActiveTexture(unit);
	    gl_d3ddev->current_active_tex_unit = unit;
	}
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,
			convert_tex_address_to_GL(d3ddev->state_block.texture_stage_state[stage][D3DTSS_ADDRESSV - 1]));	
	changed = TRUE;
    }
    if ((gl_surf_ptr->tex_parameters == NULL) ||
	(gl_surf_ptr->tex_parameters[D3DTSS_BORDERCOLOR - D3DTSS_ADDRESSU] != 
	 d3ddev->state_block.texture_stage_state[stage][D3DTSS_BORDERCOLOR - 1])) {
	GLfloat color[4];
	
	color[0] = ((d3ddev->state_block.texture_stage_state[stage][D3DTSS_BORDERCOLOR - 1] >> 16) & 0xFF) / 255.0;
	color[1] = ((d3ddev->state_block.texture_stage_state[stage][D3DTSS_BORDERCOLOR - 1] >>  8) & 0xFF) / 255.0;
	color[2] = ((d3ddev->state_block.texture_stage_state[stage][D3DTSS_BORDERCOLOR - 1] >>  0) & 0xFF) / 255.0;
	color[3] = ((d3ddev->state_block.texture_stage_state[stage][D3DTSS_BORDERCOLOR - 1] >> 24) & 0xFF) / 255.0;
	if (unit != gl_d3ddev->current_active_tex_unit) {
	    GL_extensions.glActiveTexture(unit);
	    gl_d3ddev->current_active_tex_unit = unit;
	}
	glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, color);
	changed = TRUE;
    }

    if (changed) {
	if (gl_surf_ptr->tex_parameters == NULL) {
	    gl_surf_ptr->tex_parameters = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
						    sizeof(DWORD) * (D3DTSS_MAXMIPLEVEL + 1 - D3DTSS_ADDRESSU));
	}
	memcpy(gl_surf_ptr->tex_parameters, &(d3ddev->state_block.texture_stage_state[stage][D3DTSS_ADDRESSU - 1]),
	       sizeof(DWORD) * (D3DTSS_MAXMIPLEVEL + 1 - D3DTSS_ADDRESSU));
    }

    if (*(gl_surf_ptr->global_dirty_flag) != SURFACE_MEMORY_DIRTY) {
	TRACE(" nothing to do - memory copy and GL state in synch for all texture levels.\n");
	return DD_OK;
    }
    
    while (surf_ptr != NULL) {
        IDirect3DTextureGLImpl *gl_surf_ptr = (IDirect3DTextureGLImpl *) surf_ptr->tex_private;
	
	if (gl_surf_ptr->dirty_flag != SURFACE_MEMORY_DIRTY) {
            TRACE("   - level %d already uploaded.\n", surf_ptr->mipmap_level);
	} else {
	    TRACE("   - uploading texture level %d (initial done = %d).\n",
		  surf_ptr->mipmap_level, gl_surf_ptr->initial_upload_done);

	    /* Texture snooping for the curious :-) */
	    if (TRACE_ON(ddraw_tex)) {
		snoop_texture(surf_ptr);
	    }

	    if (unit != gl_d3ddev->current_active_tex_unit) {
		GL_extensions.glActiveTexture(unit);
		gl_d3ddev->current_active_tex_unit = unit;
	    }
	    
	    if (upload_surface_to_tex_memory_init(surf_ptr, surf_ptr->mipmap_level, &(gl_surf_ptr->current_internal_format),
						  gl_surf_ptr->initial_upload_done == FALSE, TRUE, 0, 0) == DD_OK) {
	        upload_surface_to_tex_memory(NULL, 0, 0, &(gl_surf_ptr->surface_ptr));
		upload_surface_to_tex_memory_release();
		gl_surf_ptr->dirty_flag = SURFACE_MEMORY;
		gl_surf_ptr->initial_upload_done = TRUE;
	    } else {
		ERR("Problem for upload of texture %d (level = %d / initial done = %d).\n",
		    gl_surf_ptr->tex_name, surf_ptr->mipmap_level, gl_surf_ptr->initial_upload_done);
	    }
	}
	
	if (surf_ptr->surface_desc.ddsCaps.dwCaps & DDSCAPS_MIPMAP) {
	    surf_ptr = get_sub_mimaplevel(surf_ptr);
	} else {
	    surf_ptr = NULL;
	}
    }
    
    *(gl_surf_ptr->global_dirty_flag) = SURFACE_MEMORY;
    
    return DD_OK;
}

HRESULT WINAPI
Main_IDirect3DTextureImpl_1_Initialize(LPDIRECT3DTEXTURE iface,
                                       LPDIRECT3DDEVICE lpDirect3DDevice,
                                       LPDIRECTDRAWSURFACE lpDDSurface)
{
    ICOM_THIS_FROM(IDirectDrawSurfaceImpl, IDirect3DTexture, iface);
    FIXME("(%p/%p)->(%p,%p) no-op...\n", This, iface, lpDirect3DDevice, lpDDSurface);
    return DD_OK;
}

static HRESULT
gltex_setcolorkey_cb(IDirectDrawSurfaceImpl *This, DWORD dwFlags, LPDDCOLORKEY ckey )
{
    IDirect3DTextureGLImpl *glThis = (IDirect3DTextureGLImpl *) This->tex_private;

    if (glThis->dirty_flag == SURFACE_GL) {
	GLint cur_tex;

	TRACE(" flushing GL texture back to memory.\n");
	
	ENTER_GL();
	glGetIntegerv(GL_TEXTURE_BINDING_2D, &cur_tex);
	glBindTexture(GL_TEXTURE_2D, glThis->tex_name);
	gltex_download_texture(This);
	glBindTexture(GL_TEXTURE_2D, cur_tex);
	LEAVE_GL();
    }

    glThis->dirty_flag = SURFACE_MEMORY_DIRTY;
    *(glThis->global_dirty_flag) = SURFACE_MEMORY_DIRTY;
    /* TODO: check color-keying on mipmapped surfaces... */
    
    return DD_OK;
}

HRESULT
gltex_blt(IDirectDrawSurfaceImpl *This, LPRECT rdst,
	  LPDIRECTDRAWSURFACE7 src, LPRECT rsrc,
	  DWORD dwFlags, LPDDBLTFX lpbltfx)
{
    if (src != NULL) {
	IDirectDrawSurfaceImpl *src_ptr = ICOM_OBJECT(IDirectDrawSurfaceImpl, IDirectDrawSurface7, src);
	if (src_ptr->surface_desc.ddsCaps.dwCaps & DDSCAPS_3DDEVICE) {
	    FIXME("Blt from framebuffer to texture - unsupported now, please report !\n");
	}
    }
    return DDERR_INVALIDPARAMS;
}

HRESULT
gltex_bltfast(IDirectDrawSurfaceImpl *surf_ptr, DWORD dstx,
	      DWORD dsty, LPDIRECTDRAWSURFACE7 src,
	      LPRECT rsrc, DWORD trans)
{
    if (src != NULL) {
	IDirectDrawSurfaceImpl *src_ptr = ICOM_OBJECT(IDirectDrawSurfaceImpl, IDirectDrawSurface7, src);
	
	if ((src_ptr->surface_desc.ddsCaps.dwCaps & DDSCAPS_3DDEVICE) &&
	    ((trans & (DDBLTFAST_SRCCOLORKEY | DDBLTFAST_DESTCOLORKEY)) == 0)) {
	    /* This is a blt without color keying... We can use the direct copy. */
	    RECT rsrc2;
	    DWORD width, height;
	    GLint cur_tex;
	    IDirect3DTextureGLImpl *gl_surf_ptr = surf_ptr->tex_private;
	    int y;
	    
	    if (rsrc == NULL) {
		WARN("rsrc is NULL\n");
		rsrc = &rsrc2;
		
		rsrc->left = 0;
		rsrc->top = 0;
		rsrc->right = src_ptr->surface_desc.dwWidth;
		rsrc->bottom = src_ptr->surface_desc.dwHeight;
	    }
	    
	    width = rsrc->right - rsrc->left;
	    height = rsrc->bottom - rsrc->top;
	    
	    if (((dstx + width)  > surf_ptr->surface_desc.dwWidth) ||
		((dsty + height) > surf_ptr->surface_desc.dwHeight)) {
		FIXME("Does not handle clipping yet in FB => Texture blits !\n");
		return DDERR_INVALIDPARAMS;
	    }

	    if ((width == 0) || (height == 0)) {
		return DD_OK;
	    }
	    
	    TRACE(" direct frame buffer => texture BltFast override.\n");
	    
	    ENTER_GL();
	    
	    glGetIntegerv(GL_TEXTURE_BINDING_2D, &cur_tex);
	    /* This call is to create the actual texture name in GL (as we do 'late' ID creation) */
	    gltex_get_tex_name(surf_ptr);
	    glBindTexture(GL_TEXTURE_2D, gl_surf_ptr->tex_name);
	    
	    if ((gl_surf_ptr->dirty_flag == SURFACE_MEMORY_DIRTY) &&
		!((dstx == 0) && (dsty == 0) &&
		  (width == surf_ptr->surface_desc.dwWidth) && (height == surf_ptr->surface_desc.dwHeight))) {
		/* If not 'full size' and the surface is dirty, first flush it to GL before doing the copy. */
	        if (upload_surface_to_tex_memory_init(surf_ptr, surf_ptr->mipmap_level, &(gl_surf_ptr->current_internal_format),
						      gl_surf_ptr->initial_upload_done == FALSE, TRUE, 0, 0) == DD_OK) {
		    upload_surface_to_tex_memory(NULL, 0, 0, &(gl_surf_ptr->surface_ptr));
		    upload_surface_to_tex_memory_release();
		    gl_surf_ptr->dirty_flag = SURFACE_MEMORY;
		    gl_surf_ptr->initial_upload_done = TRUE;
		} else {
		    glBindTexture(GL_TEXTURE_2D, cur_tex);
		    LEAVE_GL();
		    ERR("Error at texture upload !\n");
		    return DDERR_INVALIDPARAMS;
		}
	    }

	    /* This is a hack and would need some clean-up :-) */
	    if (gl_surf_ptr->initial_upload_done == FALSE) {
		if (upload_surface_to_tex_memory_init(surf_ptr, surf_ptr->mipmap_level, &(gl_surf_ptr->current_internal_format),
						      TRUE, TRUE, 0, 0) == DD_OK) {
		    upload_surface_to_tex_memory(NULL, 0, 0, &(gl_surf_ptr->surface_ptr));
		    upload_surface_to_tex_memory_release();
		    gl_surf_ptr->dirty_flag = SURFACE_MEMORY;
		    gl_surf_ptr->initial_upload_done = TRUE;
		} else {
		    glBindTexture(GL_TEXTURE_2D, cur_tex);
		    LEAVE_GL();
		    ERR("Error at texture upload (initial case) !\n");
		    return DDERR_INVALIDPARAMS;
		}
	    }
	    
	    if ((src_ptr->surface_desc.ddsCaps.dwCaps & (DDSCAPS_FRONTBUFFER|DDSCAPS_PRIMARYSURFACE)) != 0)
		glReadBuffer(GL_FRONT);
	    else if ((src_ptr->surface_desc.ddsCaps.dwCaps & (DDSCAPS_BACKBUFFER)) == (DDSCAPS_BACKBUFFER))
		glReadBuffer(GL_BACK);
	    else {
		ERR("Wrong surface type for locking !\n");
		glBindTexture(GL_TEXTURE_2D, cur_tex);
		LEAVE_GL();
		return DDERR_INVALIDPARAMS;
	    }
	    
	    for (y = (src_ptr->surface_desc.dwHeight - rsrc->top - 1);
		 y >= (src_ptr->surface_desc.dwHeight - (rsrc->top + height));
		 y--) {
		glCopyTexSubImage2D(GL_TEXTURE_2D, surf_ptr->mipmap_level,
				    dstx, dsty,
				    rsrc->left, y,
				    width, 1);
		dsty++;
	    }
	    
	    glBindTexture(GL_TEXTURE_2D, cur_tex);
	    LEAVE_GL();
	    
	    /* The SURFACE_GL case is not handled by the 'global' dirty flag */
	    gl_surf_ptr->dirty_flag = SURFACE_GL;
	    
	    return DD_OK;
	}
    }
    return DDERR_INVALIDPARAMS;
}

HRESULT WINAPI
Main_IDirect3DTextureImpl_2_1T_PaletteChanged(LPDIRECT3DTEXTURE2 iface,
                                              DWORD dwStart,
                                              DWORD dwCount)
{
    ICOM_THIS_FROM(IDirectDrawSurfaceImpl, IDirect3DTexture2, iface);
    FIXME("(%p/%p)->(%08lx,%08lx): stub!\n", This, iface, dwStart, dwCount);
    return DD_OK;
}

HRESULT WINAPI
Main_IDirect3DTextureImpl_1_Unload(LPDIRECT3DTEXTURE iface)
{
    ICOM_THIS_FROM(IDirectDrawSurfaceImpl, IDirect3DTexture, iface);
    FIXME("(%p/%p)->(): stub!\n", This, iface);
    return DD_OK;
}

HRESULT WINAPI
Main_IDirect3DTextureImpl_2_1T_GetHandle(LPDIRECT3DTEXTURE2 iface,
					 LPDIRECT3DDEVICE2 lpDirect3DDevice2,
					 LPD3DTEXTUREHANDLE lpHandle)
{
    ICOM_THIS_FROM(IDirectDrawSurfaceImpl, IDirect3DTexture2, iface);
    IDirect3DDeviceImpl *lpDeviceImpl = ICOM_OBJECT(IDirect3DDeviceImpl, IDirect3DDevice2, lpDirect3DDevice2);
    
    TRACE("(%p/%p)->(%p,%p)\n", This, iface, lpDirect3DDevice2, lpHandle);

    /* The handle is simply the pointer to the implementation structure */
    *lpHandle = (D3DTEXTUREHANDLE) This;

    TRACE(" returning handle %08lx.\n", *lpHandle);
    
    /* Now set the device for this texture */
    This->d3ddevice = lpDeviceImpl;

    return D3D_OK;
}

HRESULT WINAPI
Main_IDirect3DTextureImpl_2_1T_Load(LPDIRECT3DTEXTURE2 iface,
				    LPDIRECT3DTEXTURE2 lpD3DTexture2)
{
    ICOM_THIS_FROM(IDirectDrawSurfaceImpl, IDirect3DTexture2, iface);
    FIXME("(%p/%p)->(%p): stub!\n", This, iface, lpD3DTexture2);
    return DD_OK;
}

static void gltex_set_palette(IDirectDrawSurfaceImpl* This, IDirectDrawPaletteImpl* pal)
{
    IDirect3DTextureGLImpl *glThis = (IDirect3DTextureGLImpl *) This->tex_private;

    if (glThis->dirty_flag == SURFACE_GL) {
	GLint cur_tex;
	
	TRACE(" flushing GL texture back to memory.\n");
	
	ENTER_GL();
	glGetIntegerv(GL_TEXTURE_BINDING_2D, &cur_tex);
	glBindTexture(GL_TEXTURE_2D, glThis->tex_name);
	gltex_download_texture(This);
	glBindTexture(GL_TEXTURE_2D, cur_tex);
	LEAVE_GL();
    }
    
    /* First call the previous set_palette function */
    glThis->set_palette(This, pal);
    
    /* And set the dirty flag */
    glThis->dirty_flag = SURFACE_MEMORY_DIRTY;
    *(glThis->global_dirty_flag) = SURFACE_MEMORY_DIRTY;
    
    /* TODO: check palette on mipmapped surfaces...
       TODO: do we need to re-upload in case of usage of the paletted texture extension ? */
}

static void
gltex_final_release(IDirectDrawSurfaceImpl *This)
{
    IDirect3DTextureGLImpl *glThis = (IDirect3DTextureGLImpl *) This->tex_private;
    DWORD mem_used;
    int i;

    TRACE(" deleting texture with GL id %d.\n", glThis->tex_name);

    /* And delete texture handle */
    ENTER_GL();
    if (glThis->tex_name != 0)
        glDeleteTextures(1, &(glThis->tex_name));
    LEAVE_GL();	

    HeapFree(GetProcessHeap(), 0, glThis->surface_ptr);

    /* And if this texture was the current one, remove it at the device level */
    if (This->d3ddevice != NULL)
        for (i = 0; i < MAX_TEXTURES; i++)
	    if (This->d3ddevice->current_texture[i] == This)
	        This->d3ddevice->current_texture[i] = NULL;

    /* All this should be part of main surface management not just a hack for texture.. */
    if (glThis->loaded) {
        mem_used = This->surface_desc.dwHeight *
	           This->surface_desc.u1.lPitch;
	This->ddraw_owner->free_memory(This->ddraw_owner, mem_used);
    }

    glThis->final_release(This);
}

static void
gltex_lock_update(IDirectDrawSurfaceImpl* This, LPCRECT pRect, DWORD dwFlags)
{
    IDirect3DTextureGLImpl *glThis = (IDirect3DTextureGLImpl *) This->tex_private;
    
    glThis->lock_update(This, pRect, dwFlags);
}

static void
gltex_unlock_update(IDirectDrawSurfaceImpl* This, LPCRECT pRect)
{
    IDirect3DTextureGLImpl *glThis = (IDirect3DTextureGLImpl *) This->tex_private;

    glThis->unlock_update(This, pRect);
    
    /* Set the dirty flag according to the lock type */
    if ((This->lastlocktype & DDLOCK_READONLY) == 0) {
        glThis->dirty_flag = SURFACE_MEMORY_DIRTY;
	*(glThis->global_dirty_flag) = SURFACE_MEMORY_DIRTY;
    }
}

HRESULT WINAPI
GL_IDirect3DTextureImpl_2_1T_Load(LPDIRECT3DTEXTURE2 iface,
				  LPDIRECT3DTEXTURE2 lpD3DTexture2)
{
    ICOM_THIS_FROM(IDirectDrawSurfaceImpl, IDirect3DTexture2, iface);
    IDirectDrawSurfaceImpl *src_ptr = ICOM_OBJECT(IDirectDrawSurfaceImpl, IDirect3DTexture2, lpD3DTexture2);
    IDirectDrawSurfaceImpl *dst_ptr = This;
    HRESULT ret_value = D3D_OK;
   
    TRACE("(%p/%p)->(%p)\n", This, iface, lpD3DTexture2);

    if (((src_ptr->surface_desc.ddsCaps.dwCaps & DDSCAPS_MIPMAP) != (dst_ptr->surface_desc.ddsCaps.dwCaps & DDSCAPS_MIPMAP)) ||
	(src_ptr->surface_desc.u2.dwMipMapCount != dst_ptr->surface_desc.u2.dwMipMapCount)) {
        ERR("Trying to load surfaces with different mip-map counts !\n");
    }

    /* Now loop through all mipmap levels and load all of them... */
    while (1) {
        IDirect3DTextureGLImpl *gl_dst_ptr = (IDirect3DTextureGLImpl *) dst_ptr->tex_private;
	DDSURFACEDESC *src_d, *dst_d;
	
	if (gl_dst_ptr != NULL) {
	    if (gl_dst_ptr->loaded == FALSE) {
	        /* Only check memory for not already loaded texture... */
	        DWORD mem_used;
		if (dst_ptr->surface_desc.u4.ddpfPixelFormat.dwFlags & DDPF_FOURCC)
                    mem_used = dst_ptr->surface_desc.u1.dwLinearSize;
		else
                    mem_used = dst_ptr->surface_desc.dwHeight * dst_ptr->surface_desc.u1.lPitch;
		if (This->ddraw_owner->allocate_memory(This->ddraw_owner, mem_used) < 0) {
		    TRACE(" out of virtual memory... Warning application.\n");
		    return D3DERR_TEXTURE_LOAD_FAILED;
		}
	    }
	    gl_dst_ptr->loaded = TRUE;
	}
    
	TRACE(" copying surface %p to surface %p (mipmap level %d)\n", src_ptr, dst_ptr, src_ptr->mipmap_level);

	if ( dst_ptr->surface_desc.ddsCaps.dwCaps & DDSCAPS_ALLOCONLOAD )
	    /* If the surface is not allocated and its location is not yet specified,
	       force it to video memory */ 
	    if ( !(dst_ptr->surface_desc.ddsCaps.dwCaps & (DDSCAPS_SYSTEMMEMORY|DDSCAPS_VIDEOMEMORY)) )
	        dst_ptr->surface_desc.ddsCaps.dwCaps |= DDSCAPS_VIDEOMEMORY;

	/* Suppress the ALLOCONLOAD flag */
	dst_ptr->surface_desc.ddsCaps.dwCaps &= ~DDSCAPS_ALLOCONLOAD;
    
	/* After seeing some logs, not sure at all about this... */
	if (dst_ptr->palette == NULL) {
	    dst_ptr->palette = src_ptr->palette;
	    if (src_ptr->palette != NULL) IDirectDrawPalette_AddRef(ICOM_INTERFACE(src_ptr->palette, IDirectDrawPalette));
	} else {
	    if (src_ptr->palette != NULL) {
	        PALETTEENTRY palent[256];
		IDirectDrawPalette_GetEntries(ICOM_INTERFACE(src_ptr->palette, IDirectDrawPalette),
					      0, 0, 256, palent);
		IDirectDrawPalette_SetEntries(ICOM_INTERFACE(dst_ptr->palette, IDirectDrawPalette),
					      0, 0, 256, palent);
	    }
	}
	
	/* Copy one surface on the other */
	dst_d = (DDSURFACEDESC *)&(dst_ptr->surface_desc);
	src_d = (DDSURFACEDESC *)&(src_ptr->surface_desc);

	if ((src_d->dwWidth != dst_d->dwWidth) || (src_d->dwHeight != dst_d->dwHeight)) {
	    /* Should also check for same pixel format, u1.lPitch, ... */
	    ERR("Error in surface sizes\n");
	    return D3DERR_TEXTURE_LOAD_FAILED;
	} else {
	    /* LPDIRECT3DDEVICE2 d3dd = (LPDIRECT3DDEVICE2) This->D3Ddevice; */
	    /* I should put a macro for the calculus of bpp */
	  
	    /* Copy also the ColorKeying stuff */
	    if (src_d->dwFlags & DDSD_CKSRCBLT) {
	        dst_d->dwFlags |= DDSD_CKSRCBLT;
		dst_d->ddckCKSrcBlt.dwColorSpaceLowValue = src_d->ddckCKSrcBlt.dwColorSpaceLowValue;
		dst_d->ddckCKSrcBlt.dwColorSpaceHighValue = src_d->ddckCKSrcBlt.dwColorSpaceHighValue;
	    }

	    /* Copy the main memory texture into the surface that corresponds to the OpenGL
	       texture object. */
	    if (dst_ptr->surface_desc.u4.ddpfPixelFormat.dwFlags & DDPF_FOURCC)
	        memcpy(dst_d->lpSurface, src_d->lpSurface, src_ptr->surface_desc.u1.dwLinearSize);
	    else
	        memcpy(dst_d->lpSurface, src_d->lpSurface, src_d->u1.lPitch * src_d->dwHeight);

	    if (gl_dst_ptr != NULL) {
		/* Set this texture as dirty */
		gl_dst_ptr->dirty_flag = SURFACE_MEMORY_DIRTY;
		*(gl_dst_ptr->global_dirty_flag) = SURFACE_MEMORY_DIRTY;
	    }
	}

	if (src_ptr->surface_desc.ddsCaps.dwCaps & DDSCAPS_MIPMAP) {
	    src_ptr = get_sub_mimaplevel(src_ptr);
	} else {
	    src_ptr = NULL;
	}
	if (dst_ptr->surface_desc.ddsCaps.dwCaps & DDSCAPS_MIPMAP) {
	    dst_ptr = get_sub_mimaplevel(dst_ptr);
	} else {
	    dst_ptr = NULL;
	}

	if ((src_ptr == NULL) || (dst_ptr == NULL)) {
	    if (src_ptr != dst_ptr) {
	        ERR(" Loading surface with different mipmap structure !!!\n");
	    }
	    break;
	}
    }

    return ret_value;
}

HRESULT WINAPI
Thunk_IDirect3DTextureImpl_2_QueryInterface(LPDIRECT3DTEXTURE2 iface,
                                            REFIID riid,
                                            LPVOID* obp)
{
    TRACE("(%p)->(%s,%p) thunking to IDirectDrawSurface7 interface.\n", iface, debugstr_guid(riid), obp);
    return IDirectDrawSurface7_QueryInterface(COM_INTERFACE_CAST(IDirectDrawSurfaceImpl, IDirect3DTexture2, IDirectDrawSurface7, iface),
					      riid,
					      obp);
}

ULONG WINAPI
Thunk_IDirect3DTextureImpl_2_AddRef(LPDIRECT3DTEXTURE2 iface)
{
    TRACE("(%p)->() thunking to IDirectDrawSurface7 interface.\n", iface);
    return IDirectDrawSurface7_AddRef(COM_INTERFACE_CAST(IDirectDrawSurfaceImpl, IDirect3DTexture2, IDirectDrawSurface7, iface));
}

ULONG WINAPI
Thunk_IDirect3DTextureImpl_2_Release(LPDIRECT3DTEXTURE2 iface)
{
    TRACE("(%p)->() thunking to IDirectDrawSurface7 interface.\n", iface);
    return IDirectDrawSurface7_Release(COM_INTERFACE_CAST(IDirectDrawSurfaceImpl, IDirect3DTexture2, IDirectDrawSurface7, iface));
}

HRESULT WINAPI
Thunk_IDirect3DTextureImpl_1_QueryInterface(LPDIRECT3DTEXTURE iface,
                                            REFIID riid,
                                            LPVOID* obp)
{
    TRACE("(%p)->(%s,%p) thunking to IDirectDrawSurface7 interface.\n", iface, debugstr_guid(riid), obp);
    return IDirectDrawSurface7_QueryInterface(COM_INTERFACE_CAST(IDirectDrawSurfaceImpl, IDirect3DTexture, IDirectDrawSurface7, iface),
					      riid,
					      obp);
}

ULONG WINAPI
Thunk_IDirect3DTextureImpl_1_AddRef(LPDIRECT3DTEXTURE iface)
{
    TRACE("(%p)->() thunking to IDirectDrawSurface7 interface.\n", iface);
    return IDirectDrawSurface7_AddRef(COM_INTERFACE_CAST(IDirectDrawSurfaceImpl, IDirect3DTexture, IDirectDrawSurface7, iface));
}

ULONG WINAPI
Thunk_IDirect3DTextureImpl_1_Release(LPDIRECT3DTEXTURE iface)
{
    TRACE("(%p)->() thunking to IDirectDrawSurface7 interface.\n", iface);
    return IDirectDrawSurface7_Release(COM_INTERFACE_CAST(IDirectDrawSurfaceImpl, IDirect3DTexture, IDirectDrawSurface7, iface));
}

HRESULT WINAPI
Thunk_IDirect3DTextureImpl_1_PaletteChanged(LPDIRECT3DTEXTURE iface,
                                            DWORD dwStart,
                                            DWORD dwCount)
{
    TRACE("(%p)->(%08lx,%08lx) thunking to IDirect3DTexture2 interface.\n", iface, dwStart, dwCount);
    return IDirect3DTexture2_PaletteChanged(COM_INTERFACE_CAST(IDirectDrawSurfaceImpl, IDirect3DTexture, IDirect3DTexture2, iface),
                                            dwStart,
                                            dwCount);
}

HRESULT WINAPI
Thunk_IDirect3DTextureImpl_1_GetHandle(LPDIRECT3DTEXTURE iface,
				       LPDIRECT3DDEVICE lpDirect3DDevice,
				       LPD3DTEXTUREHANDLE lpHandle)
{
    TRACE("(%p)->(%p,%p) thunking to IDirect3DTexture2 interface.\n", iface, lpDirect3DDevice, lpHandle);
    return IDirect3DTexture2_GetHandle(COM_INTERFACE_CAST(IDirectDrawSurfaceImpl, IDirect3DTexture, IDirect3DTexture2, iface),
				       COM_INTERFACE_CAST(IDirect3DDeviceImpl, IDirect3DDevice, IDirect3DDevice2, lpDirect3DDevice),
				       lpHandle);
}

HRESULT WINAPI
Thunk_IDirect3DTextureImpl_1_Load(LPDIRECT3DTEXTURE iface,
				  LPDIRECT3DTEXTURE lpD3DTexture)
{
    TRACE("(%p)->(%p) thunking to IDirect3DTexture2 interface.\n", iface, lpD3DTexture);
    return IDirect3DTexture2_Load(COM_INTERFACE_CAST(IDirectDrawSurfaceImpl, IDirect3DTexture, IDirect3DTexture2, iface),
				  COM_INTERFACE_CAST(IDirectDrawSurfaceImpl, IDirect3DTexture, IDirect3DTexture2, lpD3DTexture));
}

#if !defined(__STRICT_ANSI__) && defined(__GNUC__)
# define XCAST(fun)     (typeof(VTABLE_IDirect3DTexture2.fun))
#else
# define XCAST(fun)     (void*)
#endif

static const IDirect3DTexture2Vtbl VTABLE_IDirect3DTexture2 =
{
    XCAST(QueryInterface) Thunk_IDirect3DTextureImpl_2_QueryInterface,
    XCAST(AddRef) Thunk_IDirect3DTextureImpl_2_AddRef,
    XCAST(Release) Thunk_IDirect3DTextureImpl_2_Release,
    XCAST(GetHandle) Main_IDirect3DTextureImpl_2_1T_GetHandle,
    XCAST(PaletteChanged) Main_IDirect3DTextureImpl_2_1T_PaletteChanged,
    XCAST(Load) GL_IDirect3DTextureImpl_2_1T_Load,
};

#if !defined(__STRICT_ANSI__) && defined(__GNUC__)
#undef XCAST
#endif


#if !defined(__STRICT_ANSI__) && defined(__GNUC__)
# define XCAST(fun)     (typeof(VTABLE_IDirect3DTexture.fun))
#else
# define XCAST(fun)     (void*)
#endif

static const IDirect3DTextureVtbl VTABLE_IDirect3DTexture =
{
    XCAST(QueryInterface) Thunk_IDirect3DTextureImpl_1_QueryInterface,
    XCAST(AddRef) Thunk_IDirect3DTextureImpl_1_AddRef,
    XCAST(Release) Thunk_IDirect3DTextureImpl_1_Release,
    XCAST(Initialize) Main_IDirect3DTextureImpl_1_Initialize,
    XCAST(GetHandle) Thunk_IDirect3DTextureImpl_1_GetHandle,
    XCAST(PaletteChanged) Thunk_IDirect3DTextureImpl_1_PaletteChanged,
    XCAST(Load) Thunk_IDirect3DTextureImpl_1_Load,
    XCAST(Unload) Main_IDirect3DTextureImpl_1_Unload,
};

#if !defined(__STRICT_ANSI__) && defined(__GNUC__)
#undef XCAST
#endif

HRESULT d3dtexture_create(IDirectDrawImpl *d3d, IDirectDrawSurfaceImpl *surf, BOOLEAN at_creation, 
			  IDirectDrawSurfaceImpl *main)
{
    /* First, initialize the texture vtables... */
    ICOM_INIT_INTERFACE(surf, IDirect3DTexture,  VTABLE_IDirect3DTexture);
    ICOM_INIT_INTERFACE(surf, IDirect3DTexture2, VTABLE_IDirect3DTexture2);
	
    /* Only create all the private stuff if we actually have an OpenGL context.. */
    if (d3d->current_device != NULL) {
        IDirect3DTextureGLImpl *private;

        private = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(IDirect3DTextureGLImpl));
	if (private == NULL) return DDERR_OUTOFMEMORY;

	surf->tex_private = private;
	
	private->final_release = surf->final_release;
	private->lock_update = surf->lock_update;
	private->unlock_update = surf->unlock_update;
	private->set_palette = surf->set_palette;
	
	/* If at creation, we can optimize stuff and wait the first 'unlock' to upload a valid stuff to OpenGL.
	   Otherwise, it will be uploaded here (and may be invalid). */
	surf->final_release = gltex_final_release;
	surf->lock_update = gltex_lock_update;
	surf->unlock_update = gltex_unlock_update;
	surf->aux_setcolorkey_cb = gltex_setcolorkey_cb;
	surf->set_palette = gltex_set_palette;

	/* We are the only one to use the aux_blt and aux_bltfast overrides, so no need
	   to save those... */
	surf->aux_blt = gltex_blt;
	surf->aux_bltfast = gltex_bltfast;
	
	TRACE(" GL texture created for surface %p (private data at %p)\n", surf, private);

	/* Do not create the OpenGL texture id here as some game generate textures from a different thread which
	    cause problems.. */
	private->tex_name = 0;
	if (surf->mipmap_level == 0) {
	    private->main = NULL;
	    private->global_dirty_flag = &(private->__global_dirty_flag);
	} else {
	    private->main = main;
	    private->global_dirty_flag = &(((IDirect3DTextureGLImpl *) (private->main->tex_private))->__global_dirty_flag);
	}
	private->initial_upload_done = FALSE;
    }

    return D3D_OK;
}

GLuint gltex_get_tex_name(IDirectDrawSurfaceImpl *surf)
{
    IDirect3DTextureGLImpl *private = (IDirect3DTextureGLImpl *) (surf->tex_private);
    
    if (private->tex_name == 0) {
	/* The texture was not created yet... */	
	ENTER_GL();
	if (surf->mipmap_level == 0) {
	    glGenTextures(1, &(private->tex_name));
	    if (private->tex_name == 0) ERR("Error at creation of OpenGL texture ID !\n");
	    TRACE(" GL texture id is : %d.\n", private->tex_name);
	    private->__global_dirty_flag = SURFACE_MEMORY_DIRTY;
	} else {
	    private->tex_name = gltex_get_tex_name(private->main);
	    TRACE(" GL texture id reusing id %d from surface %p (private at %p)).\n", private->tex_name, private->main, private->main->tex_private);
	}
	LEAVE_GL();

	/* And set the dirty flag accordingly */
	private->dirty_flag = SURFACE_MEMORY_DIRTY;
    }
    return ((IDirect3DTextureGLImpl *) (surf->tex_private))->tex_name;
}