/*
 * IWineD3DSurface Implementation
 *
 * Copyright 2002-2005 Jason Edmeades
 * Copyright 2002-2003 Raphael Junqueira
 * Copyright 2004 Christian Costa
 *
 * 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 "wined3d_private.h"

WINE_DEFAULT_DEBUG_CHANNEL(d3d_surface);
#define GLINFO_LOCATION ((IWineD3DImpl *)(((IWineD3DDeviceImpl *)This->resource.wineD3DDevice)->wineD3D))->gl_info

/* *******************************************
   IWineD3DSurface IUnknown parts follow
   ******************************************* */
HRESULT WINAPI IWineD3DSurfaceImpl_QueryInterface(IWineD3DSurface *iface, REFIID riid, LPVOID *ppobj)
{
    IWineD3DSurfaceImpl *This = (IWineD3DSurfaceImpl *)iface;
    WARN("(%p)->(%s,%p) should not be called\n",This,debugstr_guid(riid),ppobj);
    return E_NOINTERFACE;
}

ULONG WINAPI IWineD3DSurfaceImpl_AddRef(IWineD3DSurface *iface) {
    IWineD3DSurfaceImpl *This = (IWineD3DSurfaceImpl *)iface;
    ULONG ref = InterlockedIncrement(&This->resource.ref);
    TRACE("(%p) : AddRef increasing from %ld\n", This,ref - 1);
    IUnknown_AddRef(This->resource.parent);
    return ref;
}

ULONG WINAPI IWineD3DSurfaceImpl_Release(IWineD3DSurface *iface) {
    IWineD3DSurfaceImpl *This = (IWineD3DSurfaceImpl *)iface;
    ULONG ref = InterlockedDecrement(&This->resource.ref);
    TRACE("(%p) : Releasing from %ld\n", This, ref + 1);
    if (ref == 0) {
        HeapFree(GetProcessHeap(), 0, This->allocatedMemory);
        IWineD3DDevice_Release((IWineD3DDevice *)This->resource.wineD3DDevice);
        HeapFree(GetProcessHeap(), 0, This);
    } else {
        IUnknown_Release(This->resource.parent);  /* Released the reference to the d3dx object */
    }
    return ref;
}

/* ****************************************************
   IWineD3DSurface IWineD3DResource parts follow
   **************************************************** */
HRESULT WINAPI IWineD3DSurfaceImpl_GetDevice(IWineD3DSurface *iface, IWineD3DDevice** ppDevice) {
    return IWineD3DResourceImpl_GetDevice((IWineD3DResource *)iface, ppDevice);
}

HRESULT WINAPI IWineD3DSurfaceImpl_SetPrivateData(IWineD3DSurface *iface, REFGUID refguid, CONST void* pData, DWORD SizeOfData, DWORD Flags) {
    return IWineD3DResourceImpl_SetPrivateData((IWineD3DResource *)iface, refguid, pData, SizeOfData, Flags);
}

HRESULT WINAPI IWineD3DSurfaceImpl_GetPrivateData(IWineD3DSurface *iface, REFGUID refguid, void* pData, DWORD* pSizeOfData) {
    return IWineD3DResourceImpl_GetPrivateData((IWineD3DResource *)iface, refguid, pData, pSizeOfData);
}

HRESULT WINAPI IWineD3DSurfaceImpl_FreePrivateData(IWineD3DSurface *iface, REFGUID refguid) {
    return IWineD3DResourceImpl_FreePrivateData((IWineD3DResource *)iface, refguid);
}

DWORD    WINAPI        IWineD3DSurfaceImpl_SetPriority(IWineD3DSurface *iface, DWORD PriorityNew) {
    return IWineD3DResourceImpl_SetPriority((IWineD3DResource *)iface, PriorityNew);
}

DWORD    WINAPI        IWineD3DSurfaceImpl_GetPriority(IWineD3DSurface *iface) {
    return IWineD3DResourceImpl_GetPriority((IWineD3DResource *)iface);
}

void     WINAPI        IWineD3DSurfaceImpl_PreLoad(IWineD3DSurface *iface) {
    return IWineD3DResourceImpl_PreLoad((IWineD3DResource *)iface);
}

D3DRESOURCETYPE WINAPI IWineD3DSurfaceImpl_GetType(IWineD3DSurface *iface) {
    return IWineD3DResourceImpl_GetType((IWineD3DResource *)iface);
}

HRESULT WINAPI IWineD3DSurfaceImpl_GetParent(IWineD3DSurface *iface, IUnknown **pParent) {
    return IWineD3DResourceImpl_GetParent((IWineD3DResource *)iface, pParent);
}

/* ******************************************************
   IWineD3DSurface IWineD3DSurface parts follow
   ****************************************************** */

HRESULT WINAPI IWineD3DSurfaceImpl_GetContainer(IWineD3DSurface *iface, REFIID riid, void** ppContainer) {
    IWineD3DSurfaceImpl *This = (IWineD3DSurfaceImpl *)iface;

    /** From MSDN:
     * If the surface is created using CreateImageSurface/CreateOffscreenPlainSurface, CreateRenderTarget, 
     * or CreateDepthStencilSurface, the surface is considered stand alone. In this case, 
     * GetContainer will return the Direct3D device used to create the surface. 
     */
    WARN("Query of container implementation currently ignores riid\n");
    *ppContainer = This->container;
    IUnknown_AddRef((IUnknown *)*ppContainer);
    TRACE("(%p) : returning %p\n", This, *ppContainer);
    return D3D_OK;
}

HRESULT WINAPI IWineD3DSurfaceImpl_GetDesc(IWineD3DSurface *iface, WINED3DSURFACE_DESC *pDesc) {
    IWineD3DSurfaceImpl *This = (IWineD3DSurfaceImpl *)iface;

    TRACE("(%p) : copying into %p\n", This, pDesc);
    *(pDesc->Format)             = This->currentDesc.Format;
    *(pDesc->Type)               = This->currentDesc.Type;
    *(pDesc->Usage)              = This->currentDesc.Usage;
    *(pDesc->Pool)               = This->currentDesc.Pool;
    *(pDesc->Size)               = This->currentDesc_size;   /* dx8 only */
    *(pDesc->MultiSampleType)    = This->currentDesc.MultiSampleType;
    *(pDesc->MultiSampleQuality) = This->currentDesc.MultiSampleQuality;
    *(pDesc->Width)              = This->currentDesc.Width;
    *(pDesc->Height)             = This->currentDesc.Height;
    return D3D_OK;
}

HRESULT WINAPI IWineD3DSurfaceImpl_LockRect(IWineD3DSurface *iface, D3DLOCKED_RECT* pLockedRect, CONST RECT* pRect, DWORD Flags) {
    IWineD3DSurfaceImpl *This = (IWineD3DSurfaceImpl *)iface;

    /* fixme: should we really lock as such? */
    if (This->inTexture && This->inPBuffer) {
        FIXME("Warning: Surface is in texture memory or pbuffer\n");
        This->inTexture = 0;
        This->inPBuffer = 0;
    }

    if (FALSE == This->lockable) {
        /* Note: UpdateTextures calls CopyRects which calls this routine to populate the 
              texture regions, and since the destination is an unlockable region we need
              to tolerate this                                                           */
        TRACE("Warning: trying to lock unlockable surf@%p\n", This);  
        /*return D3DERR_INVALIDCALL; */
    }

    if (This == This->resource.wineD3DDevice->backBuffer || This == This->resource.wineD3DDevice->renderTarget || This == This->resource.wineD3DDevice->frontBuffer || This->resource.wineD3DDevice->depthStencilBuffer) {
        if (This == This->resource.wineD3DDevice->backBuffer) {
            TRACE("(%p, backBuffer) : rect@%p flags(%08lx), output lockedRect@%p, memory@%p\n", This, pRect, Flags, pLockedRect, This->allocatedMemory);
        } else if (This == This->resource.wineD3DDevice->frontBuffer) {
            TRACE("(%p, frontBuffer) : rect@%p flags(%08lx), output lockedRect@%p, memory@%p\n", This, pRect, Flags, pLockedRect, This->allocatedMemory);
        } else if (This == This->resource.wineD3DDevice->renderTarget) {
            TRACE("(%p, renderTarget) : rect@%p flags(%08lx), output lockedRect@%p, memory@%p\n", This, pRect, Flags, pLockedRect, This->allocatedMemory);
        } else if (This == This->resource.wineD3DDevice->depthStencilBuffer) {
            TRACE("(%p, stencilBuffer) : rect@%p flags(%08lx), output lockedRect@%p, memory@%p\n", This, pRect, Flags, pLockedRect, This->allocatedMemory);
        }
    } else {
        TRACE("(%p) : rect@%p flags(%08lx), output lockedRect@%p, memory@%p\n", This, pRect, Flags, pLockedRect, This->allocatedMemory);
    }

    /* DXTn formats don't have exact pitches as they are to the new row of blocks,
         where each block is 4x4 pixels, 8 bytes (dxt1) and 16 bytes (dxt3/5)      
          ie pitch = (width/4) * bytes per block                                  */
    if (This->currentDesc.Format == D3DFMT_DXT1) /* DXT1 is 8 bytes per block */
        pLockedRect->Pitch = (This->currentDesc.Width/4) * 8;
    else if (This->currentDesc.Format == D3DFMT_DXT3 || This->currentDesc.Format == D3DFMT_DXT5) /* DXT3/5 is 16 bytes per block */
        pLockedRect->Pitch = (This->currentDesc.Width/4) * 16;
    else
        pLockedRect->Pitch = This->bytesPerPixel * This->currentDesc.Width;  /* Bytes / row */    

    if (NULL == pRect) {
        pLockedRect->pBits = This->allocatedMemory;
        This->lockedRect.left   = 0;
        This->lockedRect.top    = 0;
        This->lockedRect.right  = This->currentDesc.Width;
        This->lockedRect.bottom = This->currentDesc.Height;
        TRACE("Locked Rect (%p) = l %ld, t %ld, r %ld, b %ld\n", &This->lockedRect, This->lockedRect.left, This->lockedRect.top, This->lockedRect.right, This->lockedRect.bottom);
    } else {
        TRACE("Lock Rect (%p) = l %ld, t %ld, r %ld, b %ld\n", pRect, pRect->left, pRect->top, pRect->right, pRect->bottom);

        if (This->currentDesc.Format == D3DFMT_DXT1) { /* DXT1 is half byte per pixel */
            pLockedRect->pBits = This->allocatedMemory + (pLockedRect->Pitch * pRect->top) + ((pRect->left * This->bytesPerPixel/2));
        } else {
            pLockedRect->pBits = This->allocatedMemory + (pLockedRect->Pitch * pRect->top) + (pRect->left * This->bytesPerPixel);
        }
        This->lockedRect.left   = pRect->left;
        This->lockedRect.top    = pRect->top;
        This->lockedRect.right  = pRect->right;
        This->lockedRect.bottom = pRect->bottom;
    }


    if (0 == This->currentDesc.Usage) { /* classic surface */

        /* Nothing to do ;) */

    } else if (D3DUSAGE_RENDERTARGET & This->currentDesc.Usage && !(Flags&D3DLOCK_DISCARD)) { /* render surfaces */

        if (This == This->resource.wineD3DDevice->backBuffer || This == This->resource.wineD3DDevice->renderTarget || This == This->resource.wineD3DDevice->frontBuffer) {
            GLint  prev_store;
            GLenum prev_read;

            ENTER_GL();

            /**
             * for render->surface copy begin to begin of allocatedMemory
             * unlock can be more easy
             */
            pLockedRect->pBits = This->allocatedMemory;

            glFlush();
            vcheckGLcall("glFlush");
            glGetIntegerv(GL_READ_BUFFER, &prev_read);
            vcheckGLcall("glIntegerv");
            glGetIntegerv(GL_PACK_SWAP_BYTES, &prev_store);
            vcheckGLcall("glIntegerv");

            if (This == This->resource.wineD3DDevice->backBuffer) {
                glReadBuffer(GL_BACK);
            } else if (This == This->resource.wineD3DDevice->frontBuffer || This == This->resource.wineD3DDevice->renderTarget) {
                glReadBuffer(GL_FRONT);
            } else if (This == This->resource.wineD3DDevice->depthStencilBuffer) {
                ERR("Stencil Buffer lock unsupported for now\n");
            }
            vcheckGLcall("glReadBuffer");

            {
                long j;
                GLenum format = D3DFmt2GLFmt(This->resource.wineD3DDevice, This->currentDesc.Format);
                GLenum type   = D3DFmt2GLType(This->resource.wineD3DDevice, This->currentDesc.Format);
                for (j = This->lockedRect.top; j < This->lockedRect.bottom - This->lockedRect.top; ++j) {
                    glReadPixels(This->lockedRect.left, 
                                 This->lockedRect.bottom - j - 1, 
                                 This->lockedRect.right - This->lockedRect.left, 
                                 1,
                                 format, 
                                 type, 
                                 (char *)pLockedRect->pBits + (pLockedRect->Pitch * (j-This->lockedRect.top)));
                    vcheckGLcall("glReadPixels");
                }
            }

            glReadBuffer(prev_read);
            vcheckGLcall("glReadBuffer");

            LEAVE_GL();

        } else {
            FIXME("unsupported locking to Rendering surface surf@%p usage(%lu)\n", This, This->currentDesc.Usage);
        }

    } else if (D3DUSAGE_DEPTHSTENCIL & This->currentDesc.Usage) { /* stencil surfaces */

        FIXME("TODO stencil depth surface locking surf@%p usage(%lu)\n", This, This->currentDesc.Usage);

    } else {
        FIXME("unsupported locking to surface surf@%p usage(%lu)\n", This, This->currentDesc.Usage);
    }

    if (Flags & (D3DLOCK_NO_DIRTY_UPDATE | D3DLOCK_READONLY)) {
        /* Don't dirtify */
    } else {
        /**
         * Dirtify on lock
         * as seen in msdn docs
         */
        IWineD3DSurface_AddDirtyRect(iface, &This->lockedRect);

        /** Dirtify Container if needed */
        if ((NULL != This->container) && ((IWineD3DDeviceImpl *)This->container != This->resource.wineD3DDevice)) {
            IWineD3DBaseTexture_SetDirty((IWineD3DBaseTexture *)This->container, TRUE);
        }
    }

    TRACE("returning memory@%p, pitch(%d) dirtyfied(%d)\n", pLockedRect->pBits, pLockedRect->Pitch, This->Dirty);

    This->locked = TRUE;
    return D3D_OK;
}

HRESULT WINAPI IWineD3DSurfaceImpl_UnlockRect(IWineD3DSurface *iface) {
    GLint skipBytes = 0;
    IWineD3DSurfaceImpl *This = (IWineD3DSurfaceImpl *)iface;

    if (FALSE == This->locked) {
        ERR("trying to Unlock an unlocked surf@%p\n", This);  
        return D3DERR_INVALIDCALL;
    }

    if (This == This->resource.wineD3DDevice->backBuffer || This == This->resource.wineD3DDevice->frontBuffer || This->resource.wineD3DDevice->depthStencilBuffer || This == This->resource.wineD3DDevice->renderTarget) {
        if (This == This->resource.wineD3DDevice->backBuffer) {
            TRACE("(%p, backBuffer) : dirtyfied(%d)\n", This, This->Dirty);
        } else if (This == This->resource.wineD3DDevice->frontBuffer) {
            TRACE("(%p, frontBuffer) : dirtyfied(%d)\n", This, This->Dirty);
        } else if (This == This->resource.wineD3DDevice->depthStencilBuffer) {
            TRACE("(%p, stencilBuffer) : dirtyfied(%d)\n", This, This->Dirty);
        } else if (This == This->resource.wineD3DDevice->renderTarget) {
            TRACE("(%p, renderTarget) : dirtyfied(%d)\n", This, This->Dirty);
        }
    } else {
        TRACE("(%p) : dirtyfied(%d)\n", This, This->Dirty);
    }

    if (FALSE == This->Dirty) {
        TRACE("(%p) : Not Dirtified so nothing to do, return now\n", This);
        goto unlock_end;
    }

    if (0 == This->currentDesc.Usage) { /* classic surface */
        /**
         * nothing to do
         * waiting to reload the surface via IDirect3DDevice8::UpdateTexture
         */
    } else if (D3DUSAGE_RENDERTARGET & This->currentDesc.Usage) { /* render surfaces */

        if (This == This->resource.wineD3DDevice->backBuffer || This == This->resource.wineD3DDevice->frontBuffer || This == This->resource.wineD3DDevice->renderTarget) {
            GLint  prev_store;
            GLenum prev_draw;
            GLint  prev_rasterpos[4];

            ENTER_GL();

            glFlush();
            vcheckGLcall("glFlush");
            glGetIntegerv(GL_DRAW_BUFFER, &prev_draw);
            vcheckGLcall("glIntegerv");
            glGetIntegerv(GL_PACK_SWAP_BYTES, &prev_store);
            vcheckGLcall("glIntegerv");
            glGetIntegerv(GL_CURRENT_RASTER_POSITION, &prev_rasterpos[0]);
            vcheckGLcall("glIntegerv");
            glPixelZoom(1.0, -1.0);
            vcheckGLcall("glPixelZoom");

            /* glDrawPixels transforms the raster position as though it was a vertex -
               we want to draw at screen position 0,0 - Set up ortho (rhw) mode as   
               per drawprim (and leave set - it will sort itself out due to last_was_rhw */
            if (!This->resource.wineD3DDevice->last_was_rhw) {

                double X, Y, height, width, minZ, maxZ;
                This->resource.wineD3DDevice->last_was_rhw = TRUE;

                /* Transformed already into viewport coordinates, so we do not need transform
                   matrices. Reset all matrices to identity and leave the default matrix in world 
                   mode.                                                                         */
                glMatrixMode(GL_MODELVIEW);
                checkGLcall("glMatrixMode");
                glLoadIdentity();
                checkGLcall("glLoadIdentity");

                glMatrixMode(GL_PROJECTION);
                checkGLcall("glMatrixMode");
                glLoadIdentity();
                checkGLcall("glLoadIdentity");

                /* Set up the viewport to be full viewport */
                X      = This->resource.wineD3DDevice->stateBlock->viewport.X;
                Y      = This->resource.wineD3DDevice->stateBlock->viewport.Y;
                height = This->resource.wineD3DDevice->stateBlock->viewport.Height;
                width  = This->resource.wineD3DDevice->stateBlock->viewport.Width;
                minZ   = This->resource.wineD3DDevice->stateBlock->viewport.MinZ;
                maxZ   = This->resource.wineD3DDevice->stateBlock->viewport.MaxZ;
                TRACE("Calling glOrtho with %f, %f, %f, %f\n", width, height, -minZ, -maxZ);
                glOrtho(X, X + width, Y + height, Y, -minZ, -maxZ);
                checkGLcall("glOrtho");

                /* Window Coord 0 is the middle of the first pixel, so translate by half
                   a pixel (See comment above glTranslate below)                         */
                glTranslatef(0.5, 0.5, 0);
                checkGLcall("glTranslatef(0.5, 0.5, 0)");
            }

            if (This == This->resource.wineD3DDevice->backBuffer) {
                glDrawBuffer(GL_BACK);
            } else if (This == This->resource.wineD3DDevice->frontBuffer || This == This->resource.wineD3DDevice->renderTarget) {
                glDrawBuffer(GL_FRONT);
            }
            vcheckGLcall("glDrawBuffer");

            /* If not fullscreen, we need to skip a number of bytes to find the next row of data */
            glGetIntegerv(GL_UNPACK_ROW_LENGTH, &skipBytes);
            glPixelStorei(GL_UNPACK_ROW_LENGTH, This->currentDesc.Width);

            /* And back buffers are not blended */
            glDisable(GL_BLEND);

            glRasterPos3i(This->lockedRect.left, This->lockedRect.top, 1);
            vcheckGLcall("glRasterPos2f");
            switch (This->currentDesc.Format) {
            case D3DFMT_R5G6B5:
                {
                    glDrawPixels(This->lockedRect.right - This->lockedRect.left, (This->lockedRect.bottom - This->lockedRect.top)-1,
                                 GL_RGB, GL_UNSIGNED_SHORT_5_6_5, This->allocatedMemory);
                    vcheckGLcall("glDrawPixels");
                }
                break;
            case D3DFMT_R8G8B8:
                {
                    glDrawPixels(This->lockedRect.right - This->lockedRect.left, (This->lockedRect.bottom - This->lockedRect.top)-1,
                                 GL_RGB, GL_UNSIGNED_BYTE, This->allocatedMemory);
                    vcheckGLcall("glDrawPixels");
                }
                break;
            case D3DFMT_A8R8G8B8:
                {
                    glPixelStorei(GL_PACK_SWAP_BYTES, TRUE);
                    vcheckGLcall("glPixelStorei");
                    glDrawPixels(This->lockedRect.right - This->lockedRect.left, (This->lockedRect.bottom - This->lockedRect.top)-1,
                                 GL_BGRA, GL_UNSIGNED_BYTE, This->allocatedMemory);
                    vcheckGLcall("glDrawPixels");
                    glPixelStorei(GL_PACK_SWAP_BYTES, prev_store);
                    vcheckGLcall("glPixelStorei");
                }
                break;
            default:
                FIXME("Unsupported Format %u in locking func\n", This->currentDesc.Format);
            }

            glPixelZoom(1.0,1.0);
            vcheckGLcall("glPixelZoom");
            glDrawBuffer(prev_draw);
            vcheckGLcall("glDrawBuffer");
            glRasterPos3iv(&prev_rasterpos[0]);
            vcheckGLcall("glRasterPos3iv");

            /* Reset to previous pack row length / blending state */
            glPixelStorei(GL_UNPACK_ROW_LENGTH, skipBytes);
            if (This->resource.wineD3DDevice->stateBlock->renderState[D3DRS_ALPHABLENDENABLE]) glEnable(GL_BLEND);

            LEAVE_GL();

            /** restore clean dirty state */
            IWineD3DSurface_CleanDirtyRect(iface);

        } else {
            FIXME("unsupported unlocking to Rendering surface surf@%p usage(%lu)\n", This, This->currentDesc.Usage);
        }

    } else if (D3DUSAGE_DEPTHSTENCIL & This->currentDesc.Usage) { /* stencil surfaces */

        if (This == This->resource.wineD3DDevice->depthStencilBuffer) {
            FIXME("TODO stencil depth surface unlocking surf@%p usage(%lu)\n", This, This->currentDesc.Usage);
        } else {
            FIXME("unsupported unlocking to StencilDepth surface surf@%p usage(%lu)\n", This, This->currentDesc.Usage);
        }

    } else {
        FIXME("unsupported unlocking to surface surf@%p usage(%lu)\n", This, This->currentDesc.Usage);
    }

    unlock_end:
    This->locked = FALSE;
    memset(&This->lockedRect, 0, sizeof(RECT));
    return D3D_OK;
}

HRESULT WINAPI IWineD3DSurfaceImpl_GetDC(IWineD3DSurface *iface, HDC *pHDC) {
    IWineD3DSurfaceImpl *This = (IWineD3DSurfaceImpl *)iface;
    FIXME("No support for GetDC yet for surface %p\n", This);
    return D3DERR_INVALIDCALL;
}

HRESULT WINAPI IWineD3DSurfaceImpl_ReleaseDC(IWineD3DSurface *iface, HDC hDC) {
    IWineD3DSurfaceImpl *This = (IWineD3DSurfaceImpl *)iface;
    FIXME("No support for ReleaseDC yet for surface %p\n", This);
    return D3DERR_INVALIDCALL;
}

/* ******************************************************
   IWineD3DSurface Internal (No mapping to directx api) parts follow
   ****************************************************** */
HRESULT WINAPI IWineD3DSurfaceImpl_LoadTexture(IWineD3DSurface *iface, GLenum gl_target, GLenum gl_level) {
    IWineD3DSurfaceImpl *This = (IWineD3DSurfaceImpl *)iface;

    if (This->inTexture)
        return D3D_OK;

    if (This->inPBuffer) {
        ENTER_GL();

        if (gl_level != 0)
            FIXME("Surface in texture is only supported for level 0\n");
        else if (This->currentDesc.Format == D3DFMT_P8 || This->currentDesc.Format == D3DFMT_A8P8 ||
                 This->currentDesc.Format == D3DFMT_DXT1 || This->currentDesc.Format == D3DFMT_DXT3 ||
                 This->currentDesc.Format == D3DFMT_DXT5)
            FIXME("Format %d not supported\n", This->currentDesc.Format);
        else {
            glCopyTexImage2D(gl_target,
                             0,
                             D3DFmt2GLIntFmt(This->resource.wineD3DDevice,
                                             This->currentDesc.Format),
                             0,
                             0,
                             This->currentDesc.Width,
                             This->currentDesc.Height,
                             0);
            TRACE("Updating target %d\n", gl_target);
            This->inTexture = TRUE;
        }
        LEAVE_GL();
        return D3D_OK;
    }

    if ((This->currentDesc.Format == D3DFMT_P8 || This->currentDesc.Format == D3DFMT_A8P8) && 
        !GL_SUPPORT(EXT_PALETTED_TEXTURE)) {
        /**
         * wanted a paletted texture and not really support it in HW 
         * so software emulation code begin
         */
        UINT i;
        PALETTEENTRY* pal = This->resource.wineD3DDevice->palettes[This->resource.wineD3DDevice->currentPalette];
        VOID* surface = (VOID*) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, This->currentDesc.Width * This->currentDesc.Height * sizeof(DWORD));
        BYTE* dst = (BYTE*) surface;
        BYTE* src = (BYTE*) This->allocatedMemory;

        for (i = 0; i < This->currentDesc.Width * This->currentDesc.Height; i++) {
            BYTE color = *src++;
            *dst++ = pal[color].peRed;
            *dst++ = pal[color].peGreen;
            *dst++ = pal[color].peBlue;
            if (This->currentDesc.Format == D3DFMT_A8P8)
                *dst++ = pal[color].peFlags;
            else
                *dst++ = 0xFF; 
        }

        ENTER_GL();

        TRACE("Calling glTexImage2D %x i=%d, intfmt=%x, w=%d, h=%d,0=%d, glFmt=%x, glType=%x, Mem=%p\n",
              gl_target,
              gl_level, 
              GL_RGBA,
              This->currentDesc.Width, 
              This->currentDesc.Height, 
              0, 
              GL_RGBA,
              GL_UNSIGNED_BYTE,
              surface);
        glTexImage2D(gl_target,
                     gl_level, 
                     GL_RGBA,
                     This->currentDesc.Width,
                     This->currentDesc.Height,
                     0,
                     GL_RGBA,
                     GL_UNSIGNED_BYTE,
                     surface);
        checkGLcall("glTexImage2D");
        HeapFree(GetProcessHeap(), 0, surface);

        LEAVE_GL();

        return D3D_OK;    
    }

    if (This->currentDesc.Format == D3DFMT_DXT1 || 
        This->currentDesc.Format == D3DFMT_DXT3 || 
        This->currentDesc.Format == D3DFMT_DXT5) {
        if (GL_SUPPORT(EXT_TEXTURE_COMPRESSION_S3TC)) {
            TRACE("Calling glCompressedTexImage2D %x i=%d, intfmt=%x, w=%d, h=%d,0=%d, sz=%d, Mem=%p\n",
                  gl_target, 
                  gl_level, 
                  D3DFmt2GLIntFmt(This->resource.wineD3DDevice, This->currentDesc.Format), 
                  This->currentDesc.Width, 
                  This->currentDesc.Height, 
                  0, 
                  This->currentDesc_size,
                  This->allocatedMemory);

            ENTER_GL();

            GL_EXTCALL(glCompressedTexImage2DARB)(gl_target, 
                                                  gl_level, 
                                                  D3DFmt2GLIntFmt(This->resource.wineD3DDevice, This->currentDesc.Format),
                                                  This->currentDesc.Width,
                                                  This->currentDesc.Height,
                                                  0,
                                                  This->currentDesc_size,
                                                  This->allocatedMemory);
            checkGLcall("glCommpressedTexTexImage2D");

            LEAVE_GL();
        } else {
            FIXME("Using DXT1/3/5 without advertized support\n");
        }
    } else {

        TRACE("Calling glTexImage2D %x i=%d, d3dfmt=%s, intfmt=%x, w=%d, h=%d,0=%d, glFmt=%x, glType=%x, Mem=%p\n",
              gl_target, 
              gl_level, 
              debug_d3dformat(This->currentDesc.Format),
              D3DFmt2GLIntFmt(This->resource.wineD3DDevice, This->currentDesc.Format), 
              This->currentDesc.Width, 
              This->currentDesc.Height, 
              0, 
              D3DFmt2GLFmt(This->resource.wineD3DDevice, This->currentDesc.Format), 
              D3DFmt2GLType(This->resource.wineD3DDevice, This->currentDesc.Format),
              This->allocatedMemory);

        ENTER_GL();

        glTexImage2D(gl_target, 
                     gl_level,
                     D3DFmt2GLIntFmt(This->resource.wineD3DDevice, This->currentDesc.Format),
                     This->currentDesc.Width,
                     This->currentDesc.Height,
                     0,
                     D3DFmt2GLFmt(This->resource.wineD3DDevice, This->currentDesc.Format),
                     D3DFmt2GLType(This->resource.wineD3DDevice, This->currentDesc.Format),
                     This->allocatedMemory);
        checkGLcall("glTexImage2D");

        LEAVE_GL();

#if 0
        {
            static unsigned int gen = 0;
            char buffer[4096];
            ++gen;
            if ((gen % 10) == 0) {
                snprintf(buffer, sizeof(buffer), "/tmp/surface%p_type%u_level%u_%u.ppm", This, gl_target, gl_level, gen);
                IWineD3DSurfaceImpl_SaveSnapshot((LPDIRECT3DSURFACE8) This, buffer);
            }
            /*
             * debugging crash code
            if (gen == 250) {
              void** test = NULL;
              *test = 0;
            }
            */
        }
#endif
    }

    return D3D_OK;
}

#include <errno.h>
#include <stdio.h>
HRESULT WINAPI IWineD3DSurfaceImpl_SaveSnapshot(IWineD3DSurface *iface, const char* filename) {
    FILE* f = NULL;
    ULONG i;
    IWineD3DSurfaceImpl *This = (IWineD3DSurfaceImpl *)iface;

    f = fopen(filename, "w+");
    if (NULL == f) {
        ERR("opening of %s failed with: %s\n", filename, strerror(errno));
        return D3DERR_INVALIDCALL;
    }

    TRACE("opened %s with format %s\n", filename, debug_d3dformat(This->currentDesc.Format));

    fprintf(f, "P6\n%u %u\n255\n", This->currentDesc.Width, This->currentDesc.Height);
    switch (This->currentDesc.Format) {
    case D3DFMT_X8R8G8B8:
    case D3DFMT_A8R8G8B8:
        {
            DWORD color;
            for (i = 0; i < This->currentDesc.Width * This->currentDesc.Height; i++) {
                color = ((DWORD*) This->allocatedMemory)[i];
                fputc((color >> 16) & 0xFF, f);
                fputc((color >>  8) & 0xFF, f);
                fputc((color >>  0) & 0xFF, f);
            }
        }
        break;
    case D3DFMT_R8G8B8:
        {
            BYTE* color;
            for (i = 0; i < This->currentDesc.Width * This->currentDesc.Height; i++) {
                color = ((BYTE*) This->allocatedMemory) + (3 * i);
                fputc((color[0]) & 0xFF, f);
                fputc((color[1]) & 0xFF, f);
                fputc((color[2]) & 0xFF, f);
            }
        }
        break;
    case D3DFMT_A1R5G5B5: 
        {
            WORD color;
            for (i = 0; i < This->currentDesc.Width * This->currentDesc.Height; i++) {
                color = ((WORD*) This->allocatedMemory)[i];
                fputc(((color >> 10) & 0x1F) * 255 / 31, f);
                fputc(((color >>  5) & 0x1F) * 255 / 31, f);
                fputc(((color >>  0) & 0x1F) * 255 / 31, f);
            }
        }
        break;
    case D3DFMT_A4R4G4B4:
        {
            WORD color;
            for (i = 0; i < This->currentDesc.Width * This->currentDesc.Height; i++) {
                color = ((WORD*) This->allocatedMemory)[i];
                fputc(((color >>  8) & 0x0F) * 255 / 15, f);
                fputc(((color >>  4) & 0x0F) * 255 / 15, f);
                fputc(((color >>  0) & 0x0F) * 255 / 15, f);
            }
        }
        break;

    case D3DFMT_R5G6B5: 
        {
            WORD color;
            for (i = 0; i < This->currentDesc.Width * This->currentDesc.Height; i++) {
                color = ((WORD*) This->allocatedMemory)[i];
                fputc(((color >> 11) & 0x1F) * 255 / 31, f);
                fputc(((color >>  5) & 0x3F) * 255 / 63, f);
                fputc(((color >>  0) & 0x1F) * 255 / 31, f);
            }
        }
        break;
    default: 
        FIXME("Unimplemented dump mode format(%u,%s)\n", This->currentDesc.Format, debug_d3dformat(This->currentDesc.Format));
    }
    fclose(f);
    return D3D_OK;
}

HRESULT WINAPI IWineD3DSurfaceImpl_CleanDirtyRect(IWineD3DSurface *iface) {
    IWineD3DSurfaceImpl *This = (IWineD3DSurfaceImpl *)iface;
    This->Dirty = FALSE;
    This->dirtyRect.left   = This->currentDesc.Width;
    This->dirtyRect.top    = This->currentDesc.Height;
    This->dirtyRect.right  = 0;
    This->dirtyRect.bottom = 0;
    TRACE("(%p) : Dirty?%d, Rect:(%ld,%ld,%ld,%ld)\n", This, This->Dirty, This->dirtyRect.left, 
          This->dirtyRect.top, This->dirtyRect.right, This->dirtyRect.bottom);
    return D3D_OK;
}

/**
 *   Slightly inefficient way to handle multiple dirty rects but it works :)
 */
extern HRESULT WINAPI IWineD3DSurfaceImpl_AddDirtyRect(IWineD3DSurface *iface, CONST RECT* pDirtyRect) {
    IWineD3DSurfaceImpl *This = (IWineD3DSurfaceImpl *)iface;
    This->Dirty = TRUE;
    if (NULL != pDirtyRect) {
        This->dirtyRect.left   = min(This->dirtyRect.left,   pDirtyRect->left);
        This->dirtyRect.top    = min(This->dirtyRect.top,    pDirtyRect->top);
        This->dirtyRect.right  = max(This->dirtyRect.right,  pDirtyRect->right);
        This->dirtyRect.bottom = max(This->dirtyRect.bottom, pDirtyRect->bottom);
    } else {
        This->dirtyRect.left   = 0;
        This->dirtyRect.top    = 0;
        This->dirtyRect.right  = This->currentDesc.Width;
        This->dirtyRect.bottom = This->currentDesc.Height;
    }
    TRACE("(%p) : Dirty?%d, Rect:(%ld,%ld,%ld,%ld)\n", This, This->Dirty, This->dirtyRect.left, 
          This->dirtyRect.top, This->dirtyRect.right, This->dirtyRect.bottom);
    return D3D_OK;
}

IWineD3DSurfaceVtbl IWineD3DSurface_Vtbl =
{
    IWineD3DSurfaceImpl_QueryInterface,
    IWineD3DSurfaceImpl_AddRef,
    IWineD3DSurfaceImpl_Release,
    IWineD3DSurfaceImpl_GetParent,
    IWineD3DSurfaceImpl_GetDevice,
    IWineD3DSurfaceImpl_SetPrivateData,
    IWineD3DSurfaceImpl_GetPrivateData,
    IWineD3DSurfaceImpl_FreePrivateData,
    IWineD3DSurfaceImpl_SetPriority,
    IWineD3DSurfaceImpl_GetPriority,
    IWineD3DSurfaceImpl_PreLoad,
    IWineD3DSurfaceImpl_GetType,
    IWineD3DSurfaceImpl_GetContainer,
    IWineD3DSurfaceImpl_GetDesc,
    IWineD3DSurfaceImpl_LockRect,
    IWineD3DSurfaceImpl_UnlockRect,
    IWineD3DSurfaceImpl_GetDC,
    IWineD3DSurfaceImpl_ReleaseDC,
    /* Internal use: */
    IWineD3DSurfaceImpl_CleanDirtyRect,
    IWineD3DSurfaceImpl_AddDirtyRect,
    IWineD3DSurfaceImpl_LoadTexture,
    IWineD3DSurfaceImpl_SaveSnapshot
};
