/*
 * OpenClonk, http://www.openclonk.org
 *
 * Copyright (c) 1998-2000, 2003, 2008  Matthes Bender
 * Copyright (c) 2002, 2004-2008  Sven Eberhardt
 * Copyright (c) 2005, 2007-2011  Günther Brammer
 * Copyright (c) 2005  Peter Wortmann
 * Copyright (c) 2009-2010  Armin Burgmeier
 * Copyright (c) 2009  Nicolas Hake
 * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de
 *
 * Portions might be copyrighted by other authors who have contributed
 * to OpenClonk.
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 * See isc_license.txt for full license and disclaimer.
 *
 * "Clonk" is a registered trademark of Matthes Bender.
 * See clonk_trademark_license.txt for full license.
 */
// a wrapper class to DirectDraw surfaces

#include "C4Include.h"
#include <C4Surface.h>

#include <StdFile.h>
#include <CStdFile.h>
#include "C4App.h"
#include <C4DrawGL.h>
#include <C4Window.h>
#include <StdRegistry.h>
#include <StdResStr.h>
#include <C4Draw.h>
#include <C4DrawD3D.h>
#include <Bitmap256.h>
#include <StdPNG.h>
#include <C4Config.h>


#ifdef HAVE_IO_H
#include <io.h>
#endif

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <limits.h>
#include <list>

C4Surface::C4Surface() : fIsBackground(false)
{
	Default();
}

C4Surface::C4Surface(int iWdt, int iHgt) : fIsBackground(false)
{
	Default();
	// create
	Create(iWdt, iHgt);
}

C4Surface::C4Surface(C4AbstractApp * pApp, C4Window * pWindow):
		Wdt(0), Hgt(0)
{
	Default();
	fPrimary=true;
	this->pWindow=pWindow;
	// create rendering context
#ifdef USE_GL
	if (pGL)
		pCtx = pGL->CreateContext(pWindow, pApp);
#endif
	// reset clipping
	NoClip();
}

C4Surface::~C4Surface()
{
	/*  for (C4ObjectLink *lnk = ::Objects.First; lnk; lnk=lnk->Next)
	    if (lnk->Obj->Menu)
	      lnk->Obj->Menu->AssertSurfaceNotUsed(this);*/
	Clear();
}

void C4Surface::Default()
{
	Wdt=Hgt=0;
	Scale=1;
	PrimarySurfaceLockPitch=0; PrimarySurfaceLockBits=NULL;
	ClipX=ClipY=ClipX2=ClipY2=0;
	Locked=0;
	Attached=false;
	fPrimary=false;
#ifdef USE_DIRECTX
	pSfc=NULL;
#endif
	ppTex=NULL;
	pMainSfc=NULL;
#ifdef USE_GL
	pCtx=NULL;
#endif
	pWindow=NULL;
	ClrByOwnerClr=0;
	iTexSize=iTexX=iTexY=0;
	fIsRenderTarget=false;
	fIsBackground=false;
#ifdef _DEBUG
	dbg_idx = NULL;
#endif
}

void C4Surface::MoveFrom(C4Surface *psfcFrom)
{
	// clear own
	Clear();
	// safety
	if (!psfcFrom) return;
	// grab data from other sfc
#ifdef _DEBUG
	dbg_idx = psfcFrom->dbg_idx;
#endif
	Wdt=psfcFrom->Wdt; Hgt=psfcFrom->Hgt;
	PrimarySurfaceLockPitch=psfcFrom->PrimarySurfaceLockPitch;
	PrimarySurfaceLockBits=psfcFrom->PrimarySurfaceLockBits;
	psfcFrom->PrimarySurfaceLockBits=NULL;
	ClipX=psfcFrom->ClipX; ClipY=psfcFrom->ClipY;
	ClipX2=psfcFrom->ClipX2; ClipY2=psfcFrom->ClipY2;
	Locked=psfcFrom->Locked;
	Attached=psfcFrom->Attached;
	fPrimary=psfcFrom->fPrimary; // shouldn't be true!
	ppTex=psfcFrom->ppTex;
	pMainSfc=psfcFrom->pMainSfc;
	ClrByOwnerClr=psfcFrom->ClrByOwnerClr;
	iTexSize=psfcFrom->iTexSize;
	iTexX=psfcFrom->iTexX; iTexY=psfcFrom->iTexY;
	byBytesPP=psfcFrom->byBytesPP;
#ifdef USE_DIRECTX
	dwClrFormat=psfcFrom->dwClrFormat;
	pSfc=psfcFrom->pSfc;
#endif
#ifdef USE_GL
	Format=psfcFrom->Format;
#endif
	fIsBackground = psfcFrom->fIsBackground;
	// default other sfc
	psfcFrom->Default();
}

void C4Surface::Clear()
{
	// Undo all locks
	while (Locked) Unlock();
	// release surface
#ifdef USE_DIRECTX
	if (pD3D)
	{
		if (pSfc) pSfc->Release();
	}
	pSfc=NULL;
#endif
#ifdef USE_GL
	if (pCtx)
	{
		delete pCtx;
		pCtx = 0;
	}
#endif
	FreeTextures();
	ppTex=NULL;
#ifdef _DEBUG
	delete dbg_idx;
	dbg_idx = NULL;
#endif
}

bool C4Surface::IsRenderTarget()
{
	// primary is always OK...
	return fPrimary
	       // other surfaces may be used as render targets, if offscreen rendertargets are not disabled by config,
	       //  or the surface is split (large sfcs) or locked (landscape)
	       //  (only D3D for now)
#ifdef USE_DIRECTX
	       || (!Locked && !Config.Graphics.NoOffscreenBlits && pD3D && fIsRenderTarget)
#endif
	       ;
}

void C4Surface::NoClip()
{
	ClipX=0; ClipY=0; ClipX2=Wdt-1; ClipY2=Hgt-1;
}

void C4Surface::Clip(int iX, int iY, int iX2, int iY2)
{
	ClipX=BoundBy(iX,0,Wdt-1); ClipY=BoundBy(iY,0,Hgt-1);
	ClipX2=BoundBy(iX2,0,Wdt-1); ClipY2=BoundBy(iY2,0,Hgt-1);
}

bool C4Surface::Create(int iWdt, int iHgt, bool, bool fIsRenderTarget, int MaxTextureSize)
{
	Clear(); Default();
	// check size
	if (!iWdt || !iHgt) return false;
	Wdt=iWdt; Hgt=iHgt;
	// create texture: check gfx system
	if (!pDraw) return false;
	if (!pDraw->DeviceReady()) return false;

	// store color format that will be used
#ifdef USE_DIRECTX
	if (pD3D)
		dwClrFormat=pD3D->dwSurfaceType;
	else
#endif
#ifdef USE_GL
		if (pGL)
			Format=pGL->sfcFmt;
		else
#endif
			{/* nothing to do */}
	byBytesPP=pDraw->byByteCnt;
	this->fIsRenderTarget = fIsRenderTarget;
	// create textures
	if (!CreateTextures(MaxTextureSize)) { Clear(); return false; }
	// update clipping
	NoClip();
	// success
	return true;
}

bool C4Surface::Copy(C4Surface &fromSfc)
{
	// Clear anything old
	Clear();
	// Default to other surface's color depth
	Default();
	// Create surface
	if (!Create(fromSfc.Wdt, fromSfc.Hgt)) return false;
	// Blit copy
	if (!pDraw->BlitSurface(&fromSfc, this, 0, 0, false))
		{ Clear(); return false; }
	// Success
	return true;
}

namespace
{
	int GetNeedTexSize(int Size)
	{
		int iNeedSize = Size;

	#ifdef USE_GL
		if (!pGL || !GLEW_ARB_texture_non_power_of_two)
	#endif
		{
			int n=0;
			while ((1<<++n) < iNeedSize) {}
			iNeedSize = 1<<n;
		}

		return iNeedSize;
	}
}

bool C4Surface::CreateTextures(int MaxTextureSize)
{
	// free previous
	FreeTextures();
	iTexSize=Min(GetNeedTexSize(Max(Wdt, Hgt)), pDraw->MaxTexSize);
	if (MaxTextureSize)
		iTexSize=Min(iTexSize, MaxTextureSize);
	// get the number of textures needed for this size
	iTexX=(Wdt-1)/iTexSize +1;
	iTexY=(Hgt-1)/iTexSize +1;
	// get mem for texture array
	ppTex = new C4TexRef * [iTexX*iTexY];
	memset(ppTex, 0, iTexX*iTexY*sizeof(C4TexRef *));
	// cvan't be render target if it's not a single surface
	if (!IsSingleSurface()) fIsRenderTarget = false;
	// create textures
	C4TexRef **ppCTex=ppTex;
	for (int y = 0; y < iTexY; ++y)
	{
		for(int x = 0; x < iTexX; ++x)
		{
			int sizeX = iTexSize;
			int sizeY = iTexSize;
			if(x == iTexX-1) sizeX = GetNeedTexSize( (Wdt - 1) % iTexSize + 1);
			if(y == iTexY-1) sizeY = GetNeedTexSize( (Hgt - 1) % iTexSize + 1);

			*ppCTex = new C4TexRef(sizeX, sizeY, fIsRenderTarget);
			
			if (fIsBackground && ppCTex) (*ppCTex)->FillBlack();

#ifdef USE_DIRECTX
			if (!(*ppCTex)->pTex && pD3D)
			{
				// error creating texture
				return false;
			}
#endif

			++ppCTex;
		}
	}
#if 0
	for (int i=iTexX*iTexY; i; --i,++ppCTex)
	{
		// regular textures or if last texture fits exactly into the space by Wdt or Hgt
		if (i-1 || !(Wdt%iTexSize) || !(Hgt%iTexSize))
			*ppCTex = new C4TexRef(iTexSize, fIsRenderTarget);
		else
		{
			// last texture might be smaller
			iNeedSize=Max(Wdt%iTexSize, Hgt%iTexSize);
#ifdef USE_GL
			if (!pGL || !GLEW_ARB_texture_non_power_of_two)
#endif
			{
				int n=0;
				while ((1<<++n) < iNeedSize) {}
				iNeedSize=1<<n;
			}
			*ppCTex = new C4TexRef(iNeedSize, fIsRenderTarget);
		}
		if (fIsBackground && ppCTex) (*ppCTex)->FillBlack();
#ifdef USE_DIRECTX
		if (!(*ppCTex)->pTex && pD3D)
		{
			// error creating texture
			return false;
		}
#endif
	}
#endif

#ifdef _DEBUG
	static int dbg_counter = 0;
	if (dbg_idx) delete dbg_idx;
	dbg_idx = new int;
	*dbg_idx = dbg_counter++;
#endif
	// success
	return true;
}

void C4Surface::FreeTextures()
{
	if (ppTex)
	{
		// clear all textures
		C4TexRef **ppTx=ppTex;
		for (int i=0; i<iTexX*iTexY; ++i,++ppTx)
			if (*ppTx) delete *ppTx;
		// clear texture list
		delete [] ppTex;
		ppTex=NULL;
	}
}

/*bool ClrByOwner(DWORD &rClr) old style...
  {
  // red value must be approx. same to green
  BYTE byR=GetBValue(rClr), byG=GetGValue(rClr);
  int diff=Abs(byR-byG);
  if (diff>byR/8) return false;
  // get blue value; mustn't be 0 or equal to R/G (grey)
  BYTE byB=GetRValue(rClr); if (!byB || Inside(byB, Min(byR, byG), Max(byR, byG))) return false;
  // medium r/g and blue is very close (additional gray shade check)
  if ((byR > 50) && (Abs(byB - byR) < 20)) return false;
  // if blue is not fully lit, red and green should be very low
  if (byB<240 && byR>15) return false;
  // so, the color seems to be truly blue-ish
  rClr=RGB(byB, byB, byB) | (rClr&0xff000000);
  return true;
  }*/

#define  RANGE    255
#define  HLSMAX   RANGE
#define  RGBMAX   255

bool ClrByOwner(DWORD &dwClr) // new style, based on Microsoft Knowledge Base Article - 29240
{
	int H,L,S;
	WORD R,G,B;
	BYTE cMax,cMin;
	WORD  Rdelta,Gdelta,Bdelta;
	// get RGB
	R = GetRedValue(dwClr);
	G = GetGreenValue(dwClr);
	B = GetBlueValue(dwClr);
	// calculate lightness
	cMax = Max<int>(Max<int>(R,G),B);
	cMin = Min<int>(Min<int>(R,G),B);
	L = ( ((cMax+cMin)*HLSMAX) + RGBMAX )/(2*RGBMAX);
	// achromatic case
	if (cMax == cMin)
	{
		S = 0;
		H = (HLSMAX*2/3);
	}
	// chromatic case
	else
	{
		// saturation
		if (L <= (HLSMAX/2))
			S = ( ((cMax-cMin)*HLSMAX) + ((cMax+cMin)/2) ) / (cMax+cMin);
		else
			S = ( ((cMax-cMin)*HLSMAX) + ((2*RGBMAX-cMax-cMin)/2) )
			    / (2*RGBMAX-cMax-cMin);
		// hue
		Rdelta = ( ((cMax-R)*(HLSMAX/6)) + ((cMax-cMin)/2) ) / (cMax-cMin);
		Gdelta = ( ((cMax-G)*(HLSMAX/6)) + ((cMax-cMin)/2) ) / (cMax-cMin);
		Bdelta = ( ((cMax-B)*(HLSMAX/6)) + ((cMax-cMin)/2) ) / (cMax-cMin);
		if (R == cMax)
			H = Bdelta - Gdelta;
		else if (G == cMax)
			H = (HLSMAX/3) + Rdelta - Bdelta;
		else
			H = ((2*HLSMAX)/3) + Gdelta - Rdelta;
		if (H < 0)
			H += HLSMAX;
		if (H > HLSMAX)
			H -= HLSMAX;
	}
	// Not blue
	if (!(Inside(H, 145, 175) && (S > 100))) return false;
	// It's blue: make it gray
	BYTE b = GetBlueValue(dwClr);
	dwClr = RGBA(b, b, b, 0) | (dwClr & 0xff000000);
	return true;
}

bool C4Surface::CreateColorByOwner(C4Surface *pBySurface)
{
	// safety
	if (!pBySurface) return false;
	if (!pBySurface->ppTex) return false;
	// create in same size
	if (!Create(pBySurface->Wdt, pBySurface->Hgt, false)) return false;
	// copy scale
	Scale = pBySurface->Scale;
	// set main surface
	pMainSfc=pBySurface;
	// lock it
	if (!pMainSfc->Lock()) return false;
	if (!Lock()) { pMainSfc->Unlock(); return false; }
	// set ColorByOwner-pixels
	for (int iY=0; iY<Hgt; ++iY)
		for (int iX=0; iX<Wdt; ++iX)
		{
			// get pixel
			DWORD dwPix=pMainSfc->GetPixDw(iX, iY, false);
			// is it a ClrByOwner-px?
			if (!ClrByOwner(dwPix)) continue;
			// set in this surface
			SetPixDw(iX, iY, dwPix);
			// clear in the other
			pMainSfc->SetPixDw(iX, iY, 0x00ffffff);
		}
	// unlock
	Unlock();
	pMainSfc->Unlock();
	// success
	return true;
}

bool C4Surface::SetAsClrByOwnerOf(C4Surface *pOfSurface)
{
	// safety
	if (!pOfSurface) return false;
	if (Wdt != pOfSurface->Wdt || Hgt != pOfSurface->Hgt)
		return false;
	// set main surface
	pMainSfc=pOfSurface;
	// success
	return true;
}

#ifdef USE_GL
/*bool C4Surface::CreatePrimaryGLTextures()
  {
  if (!pGL) return false;
  // primary OpenGL-surface: ensure context is selected
  if (!pGL->pCurrCtx) if (!pGL->MainCtx.Select()) return false;
  // create texture array
  CreateTextures();
  // get from framebuffer
  C4TexRef **ppTexRef = ppTex;
  for (int iY=0; iY<Hgt; iY+=iTexSize)
    for (int iX=0; iX<Wdt; iX+=iTexSize)
      {
      // get tex size
      int txWdt=Min(Wdt-iX, iTexSize), txHgt=Min(Hgt-iY, iTexSize);
      // copy surface into texture
      glBindTexture(GL_TEXTURE_2D, (*ppTexRef)->texName);
      glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, iX, iY, txWdt, txHgt, 0);
      // next texture reference
      ++ppTexRef;
      }
  // done, success
  return true;
  }*/
#endif

bool C4Surface::UpdateSize(int wdt, int hgt)
{
	assert(fPrimary);
	if (!fPrimary)
		return false;
	this->Wdt = wdt; this->Hgt = hgt;
	return true;
}

bool C4Surface::PageFlip(C4Rect *pSrcRt, C4Rect *pDstRt)
{
	assert(fPrimary);
	if (!fPrimary)
		return false;
	// call from gfx thread only!
	if (!pDraw->pApp || !pDraw->pApp->AssertMainThread()) return false;
#ifdef USE_GL
	if (pGL)
		return pCtx->PageFlip();
#endif
#ifdef USE_DIRECTX
	if (pD3D)
		return pD3D->PageFlip(pSrcRt, pDstRt);
#endif
	return true;
}

#ifdef USE_DIRECTX
IDirect3DSurface9 *C4Surface::GetSurface()
{
	// direct surface?
	if (pSfc)
	{
		pSfc->AddRef();
		return pSfc;
	}
	// surface by texture?
	if (fIsRenderTarget && ppTex)
	{
		IDirect3DTexture9 *pTex = (*ppTex)->pTex;
		IDirect3DSurface9 *pSfcResult=NULL;
		if (pTex) pTex->GetSurfaceLevel(0, &pSfcResult);
		return pSfcResult;
	}
	// split surfaces: Won't work; we're no render target anyway
	return NULL;
}
#endif //USE_DIRECTX

bool C4Surface::ReadBMP(CStdStream &hGroup)
{
	int lcnt;
	C4BMP256Info BitmapInfo;
	// read bmpinfo-header
	if (!hGroup.Read(&BitmapInfo,sizeof(C4BMPInfo))) return false;
	// is it 8bpp?
	if (BitmapInfo.Info.biBitCount == 8)
	{
		if (!hGroup.Read(((BYTE *) &BitmapInfo)+sizeof(C4BMPInfo),
		                 Min(sizeof(BitmapInfo)-sizeof(C4BMPInfo),sizeof(BitmapInfo)-sizeof(C4BMPInfo)+BitmapInfo.FileBitsOffset())))
			return false;
		if (!hGroup.Advance(BitmapInfo.FileBitsOffset())) return false;
	}
	else
	{
		// read 24bpp
		if (BitmapInfo.Info.biBitCount != 24) return false;
		if (!hGroup.Advance(((C4BMPInfo) BitmapInfo).FileBitsOffset())) return false;
	}

	// Create and lock surface
	if (!Create(BitmapInfo.Info.biWidth,BitmapInfo.Info.biHeight)) return false;
	if (!Lock()) { Clear(); return false; }

	// create line buffer
	int iBufSize=DWordAligned(BitmapInfo.Info.biWidth*BitmapInfo.Info.biBitCount/8);
	BYTE *pBuf = new BYTE[iBufSize];
	// Read lines
	for (lcnt=Hgt-1; lcnt>=0; lcnt--)
	{
		if (!hGroup.Read(pBuf, iBufSize))
			{ Clear(); delete [] pBuf; return false; }
		BYTE *pPix=pBuf;
		for (int x=0; x<BitmapInfo.Info.biWidth; ++x)
			switch (BitmapInfo.Info.biBitCount)
			{
			case 8:
				SetPixDw(x, lcnt, C4RGB(
				         BitmapInfo.Colors[*pPix].rgbRed,
				         BitmapInfo.Colors[*pPix].rgbGreen,
				         BitmapInfo.Colors[*pPix].rgbBlue));
				++pPix;
				break;
			case 24:
				SetPixDw(x, lcnt, C4RGB(pPix[0], pPix[1], pPix[2]));
				pPix+=3;
				break;
			}
	}
	// free buffer again
	delete [] pBuf;

	Unlock();

	return true;
}

/*bool C4Surface::Save(const char *szFilename)
  {
  C4BMPInfo BitmapInfo2;
  C4BMP256Info BitmapInfo;
  // Set bitmap info
  if (fPrimary)
    {
    if (byBytesPP==4)
      BitmapInfo2.Set(Wdt,Hgt,32);
    else
      BitmapInfo2.Set(Wdt,Hgt,16);
    }
  else
    BitmapInfo.Set(Wdt,Hgt,pPal->Colors);

  // Lock - WARNING - maybe locking primary surface here...
  if (!Lock()) return false;

  // Create file & write info
  CStdFile hFile;

  if(fPrimary)
    {
    if ( !hFile.Create(szFilename)
    || !hFile.Write(&BitmapInfo2,sizeof(BitmapInfo2)) )
      { Unlock(); return false; }

    // write lines
    char bpEmpty[4]; int iEmpty = DWordAligned(Wdt*byBytesPP)-Wdt*byBytesPP;
    for (int cnt=Hgt-1; cnt>=0; cnt--)
      {
      if (!hFile.Write(Bits+(Pitch*cnt),Wdt*byBytesPP))
        { Unlock(); return false; }
      if (iEmpty)
        if (!hFile.Write(bpEmpty,iEmpty))
          { Unlock(); return false; }
      }

    }
  else
    {
    if ( !hFile.Create(szFilename)
    || !hFile.Write(&BitmapInfo,sizeof(BitmapInfo)) )
      { Unlock(); return false; }

    // Write lines
    char bpEmpty[4]; int iEmpty = DWordAligned(Wdt)-Wdt;
    for (int cnt=Hgt-1; cnt>=0; cnt--)
      {
      if (!hFile.Write(Bits+(Pitch*cnt),Wdt))
        { Unlock(); return false; }
      if (iEmpty)
        if (!hFile.Write(bpEmpty,iEmpty))
          { Unlock(); return false; }
      }
    }

  // Close file
  hFile.Close();

  // Unlock
  Unlock();

  // Success
  return true;
  }
*/
bool C4Surface::SavePNG(const char *szFilename, bool fSaveAlpha, bool fApplyGamma, bool fSaveOverlayOnly)
{
	// Lock - WARNING - maybe locking primary surface here...
	if (!Lock()) return false;

	// create png file
	CPNGFile png;
	if (!png.Create(Wdt, Hgt, fSaveAlpha)) { Unlock(); return false; }

	// reset overlay if desired
	C4Surface *pMainSfcBackup = NULL;
	if (fSaveOverlayOnly) { pMainSfcBackup=pMainSfc; pMainSfc=NULL; }

#ifdef USE_GL
	if (fPrimary && pGL)
	{
		// Take shortcut. FIXME: Check Endian
		for (int y = 0; y < Hgt; ++y)
			glReadPixels(0, Hgt - y, Wdt, 1, fSaveAlpha ? GL_BGRA : GL_BGR, GL_UNSIGNED_BYTE, png.GetImageData() + y * Wdt * (3 + fSaveAlpha));
	}
	else
#endif
	{
		// write pixel values
		for (int y=0; y<Hgt; ++y)
			for (int x=0; x<Wdt; ++x)
			{
				DWORD dwClr = GetPixDw(x, y, false);
				if (fApplyGamma) dwClr = pDraw->Gamma.ApplyTo(dwClr);
				png.SetPix(x, y, dwClr);
			}
	}

	// reset overlay
	if (fSaveOverlayOnly) pMainSfc=pMainSfcBackup;

	// save png
	if (!png.Save(szFilename)) { Unlock(); return false; }

	// Unlock
	Unlock();

	// Success
	return true;
}


bool C4Surface::AttachPalette()
{
	return true;
}

double ColorDistance(BYTE *bpRGB1, BYTE *bpRGB2)
{
	return (double) (Abs(bpRGB1[0]-bpRGB2[0]) + Abs(bpRGB1[1]-bpRGB2[1]) + Abs(bpRGB1[2]-bpRGB2[2])) / 6.0;
}

bool C4Surface::GetSurfaceSize(int &irX, int &irY)
{
	// simply assign stored values
	irX=Wdt;
	irY=Hgt;
	// success
	return true;
}

bool C4Surface::Lock()
{
	// lock main sfc
	if (pMainSfc) if (!pMainSfc->Lock()) return false;
	// not yet locked?
	if (!Locked)
	{
		if (fPrimary)
		{
#ifdef USE_DIRECTX
			if (pD3D)
			{
				D3DLOCKED_RECT lock;
				// locking primary
				if (!pSfc) return false;
				// lock it
				if (pSfc->LockRect(&lock, NULL, 0) != D3D_OK)
					return false;
				pDraw->LockingPrimary();
				// store pitch and pointer
				PrimarySurfaceLockPitch=lock.Pitch;
				PrimarySurfaceLockBits=(BYTE*) lock.pBits;
			}
#endif //USE_DIRECTX

			// OpenGL:
			// cannot really lock primary surface, but Get/SetPix will emulate it
		}
		else
		{
			if (!ppTex) return false;
			// lock texture
			// textures will be locked when needed
		}
	}
	// count lock
	Locked++; return true;
}

bool C4Surface::Unlock()
{
	// unlock main sfc
	if (pMainSfc) pMainSfc->Unlock();
	// locked?
	if (!Locked) return false;
	// decrease lock counter; check if zeroed
	Locked--;
	if (!Locked)
	{
		// zeroed: unlock
		if (fPrimary)
		{
#ifdef USE_DIRECTX
			if (pD3D)
			{
				if (!pSfc) return false;
				// unlocking primary?
				if (pSfc->UnlockRect() != D3D_OK)
					return false;
				pDraw->PrimaryUnlocked();
			}
			else
#endif
			{
				// if tex refs exist, free them
				/*FreeTextures();*/
				// otherwise, emulated primary locks in OpenGL
				delete[] PrimarySurfaceLockBits;
				PrimarySurfaceLockBits = 0;
				return true;
			}
		}
		else
		{
			// non-primary unlock: unlock all texture surfaces (if locked)
			C4TexRef **ppTx=ppTex;
			for (int i=0; i<iTexX*iTexY; ++i,++ppTx)
				(*ppTx)->Unlock();
		}
	}
	return true;
}

bool C4Surface::GetTexAt(C4TexRef **ppTexRef, int &rX, int &rY)
{
	// texture present?
	if (!ppTex) return false;
	// get pos
	int iX=rX/iTexSize;
	int iY=rY/iTexSize;
	// clip
	if (iX<0 || iY<0 || iX>=iTexX || iY>=iTexY) return false;
	// get texture by pos
	*ppTexRef=*(ppTex+iY*iTexX+iX);
	// adjust pos
	rX-=iX*iTexSize;
	rY-=iY*iTexSize;
	// success
	return true;
}

bool C4Surface::GetLockTexAt(C4TexRef **ppTexRef, int &rX, int &rY)
{
	// texture present?
	if (!GetTexAt(ppTexRef, rX, rY)) return false;
	// Already partially locked
	if ((*ppTexRef)->texLock.pBits)
	{
		// But not for the requested pixel
		C4Rect & r = (*ppTexRef)->LockSize;
		if (r.x > rX || r.y > rY || (r.x + r.Wdt) < rX || (r.y + r.Hgt) < rY)
			// Unlock, then relock the whole thing
			(*ppTexRef)->Unlock();
		else return true;
	}
	// ensure it's locked
	if (!(*ppTexRef)->Lock()) return false;
	// success
	return true;
}

DWORD C4Surface::GetPixDw(int iX, int iY, bool fApplyModulation)
{
	BYTE *pBuf = NULL; int iPitch = 0; // TODO: are those initialised to something sensible?
	// backup pos
	int iX2=iX; int iY2=iY;
	// primary?
	if (fPrimary)
	{
#ifdef USE_GL
		// OpenGL?
		if (pGL)
		{
			if (!PrimarySurfaceLockBits)
			{
				PrimarySurfaceLockBits = new unsigned char[Wdt*Hgt*3 + 1];
				glReadPixels( 0, 0, Wdt, Hgt, GL_BGR, GL_UNSIGNED_BYTE, PrimarySurfaceLockBits);
				PrimarySurfaceLockPitch = Wdt*3;
			}
			return * (DWORD *) (PrimarySurfaceLockBits+(Hgt-iY-1)*PrimarySurfaceLockPitch+iX*3);

			// copy content into textures
			/*if (!ppTex) if (!CreatePrimaryGLTextures()) return 0;
			// get+lock affected texture - inverse Y as primary is locked upside down!
			iY = Hgt-iY-1;
			C4TexRef *pTexRef;
			if (!GetLockTexAt(&pTexRef, iX, iY)) return 0;
			pBuf=(BYTE *) pTexRef->texLock.pBits;
			iPitch=pTexRef->texLock.Pitch;
			// get pixel
			return *(DWORD *)(pBuf+iY*iPitch+iX*4);*/
		}
#endif
#ifdef USE_DIRECTX
		if (!PrimarySurfaceLockBits)
		{
			return 0;
		}
		else
		{
			// clip
			if (iX<0 || iY<0 || iX>=Wdt || iY>=Hgt) return 0;
			// get pixel from primary surface
			WORD pix16;
			switch (dwClrFormat)
			{
			case D3DFMT_X1R5G5B5:
				// 16 bit 5-5-5
				pix16= * (WORD *) (((BYTE *) PrimarySurfaceLockBits)+iY*PrimarySurfaceLockPitch+iX*2);
				return ((pix16 & 0x001f) << 3)
				       | ((pix16 & 0x03e0) << 6)
				       | ((pix16 & 0x7c00) << 9);

			case D3DFMT_R5G6B5:
				// 16 bit 5-6-5
				pix16= * (WORD *) (((BYTE *) PrimarySurfaceLockBits)+iY*PrimarySurfaceLockPitch+iX*2);
				return ((pix16 & 0x001f) <<  3)
				       | ((pix16 & 0x07e0) <<  5)
				       | ((pix16 & 0xf800) <<  8);
				break;

			case D3DFMT_X8R8G8B8:
				// 32 bit
				return * (DWORD *) (((BYTE *) PrimarySurfaceLockBits)+iY*PrimarySurfaceLockPitch+iX*4);
			default: assert(false); return 0; // should not happen
			}
		}
#endif
	}
	else
	{
		// get+lock affected texture
		if (!ppTex) return 0;
		C4TexRef *pTexRef;
		if (!GetLockTexAt(&pTexRef, iX, iY)) return 0;
		pBuf=(BYTE *) pTexRef->texLock.pBits;
		iPitch=pTexRef->texLock.Pitch;
	}
	// get pix of surface
	DWORD dwPix;
	if (byBytesPP == 4)
	{
		// 32 bit
		DWORD *pPix=(DWORD *) (pBuf+iY*iPitch+iX*4);
		dwPix = *pPix;
	}
	else
	{
		// 16 bit
		WORD *pPix=(WORD *) (pBuf+iY*iPitch+iX*2);
		dwPix = ClrW2Dw(*pPix);
	}
	// this is a ColorByOwner-surface?
	if (pMainSfc)
	{
		BYTE byAlpha=BYTE(dwPix>>24);
		// pix is fully transparent?
		if (byAlpha==0x00)
			// then get the main surfaces's pixel
			dwPix = pMainSfc->GetPixDw(iX2, iY2, fApplyModulation);
		else
		{
			// otherwise, it's a ColorByOwner-pixel: adjust the color
			if (fApplyModulation)
			{
				if (pDraw->dwBlitMode & C4GFXBLIT_CLRSFC_MOD2)
					ModulateClrMOD2(dwPix, ClrByOwnerClr);
				else
					ModulateClr(dwPix, ClrByOwnerClr);
				if (pDraw->BlitModulated && !(pDraw->dwBlitMode & C4GFXBLIT_CLRSFC_OWNCLR))
					ModulateClr(dwPix, pDraw->BlitModulateClr);
			}
			else
				ModulateClr(dwPix, ClrByOwnerClr);
			// does it contain transparency? then blit on main sfc
			if (byAlpha)
			{
				DWORD dwMainPix = pMainSfc->GetPixDw(iX2, iY2, fApplyModulation);
				BltAlpha(dwMainPix, dwPix); dwPix=dwMainPix;
			}
		}
	}
	else
	{
		// single main surface
		// apply color modulation if desired
		if (fApplyModulation && pDraw->BlitModulated)
		{
			if (pDraw->dwBlitMode & C4GFXBLIT_MOD2)
				ModulateClrMOD2(dwPix, pDraw->BlitModulateClr);
			else
				ModulateClr(dwPix, pDraw->BlitModulateClr);
		}
	}
	// return pixel value
	return dwPix;
}

bool C4Surface::IsPixTransparent(int iX, int iY)
{
	// get pixel value
	DWORD dwPix=GetPixDw(iX, iY, false);
	// get alpha value
	return (dwPix>>24) < 128;
}

/*bool C4Surface::SetPixEx(int iX, int iY, BYTE byCol, DWORD dwClr)
  {
  // clip
  if ((iX<ClipX) || (iX>ClipX2) || (iY<ClipY) || (iY>ClipY2)) return true;
  // primary?
  if (fPrimary)
    {
#ifdef USE_GL
    // OpenGL: Use OpenGL API
    if (pGL)
      {
      pGL->DrawPixInt(this, iX, iY, dwClr);
      }
    else
#endif
      {
#ifdef USE_DIRECTX
      // must be locked!
      if (!Bits) return false;
      // set according to pixel format
      DWORD *pPix32; WORD *pPix16;
      switch (dwClrFormat)
        {
        case D3DFMT_X1R5G5B5:
          // 16 bit 5-5-5
          pPix16=(WORD *) (((BYTE *) Bits)+iY*Pitch+iX*2);
          *pPix16=WORD((dwClr & 0x000000f8) >> 3)
                | WORD((dwClr & 0x0000f800) >> 6)
                | WORD((dwClr & 0x00f80000) >> 9);
          break;

        case D3DFMT_R5G6B5:
          // 16 bit 5-6-5
          pPix16=(WORD *) (((BYTE *) Bits)+iY*Pitch+iX*2);
          *pPix16=WORD((dwClr & 0x000000f8) >> 3)
                | WORD((dwClr & 0x0000fc00) >> 5)
                | WORD((dwClr & 0x00f80000) >> 8);
          break;

        case D3DFMT_X8R8G8B8:
          // 32 bit
          pPix32=(DWORD *) (((BYTE *) Bits)+iY*Pitch+iX*4);
          *pPix32=dwClr;
          break;
        }
#endif
      }
    return true;
    }
  else
    {
    SetPixDw(iX, iY, dwClr);
    }
  return true;
  }*/

bool C4Surface::SetPixDw(int iX, int iY, DWORD dwClr)
{
	// clip
	if ((iX<ClipX) || (iX>ClipX2) || (iY<ClipY) || (iY>ClipY2)) return true;
	// get+lock affected texture
	if (!ppTex) return false;
	// if color is fully transparent, ensure it's black
	if (dwClr>>24 == 0x00) dwClr=0x00000000;
	C4TexRef *pTexRef;
#ifdef USE_GL
	// openGL: use glTexSubImage2D
	// This optimization was moved to LockForUpdate, as it only slows down mass updates here
	// Keep this code in case there is a need for fast single pixel updates again
	if (0 && pGL && pGL->pCurrCtx)
	{
		if (!GetTexAt(&pTexRef, iX, iY))
			return false;
		// If the texture is not copied into system memory, modify it directly in the video memory
		if (!pTexRef->texLock.pBits)
		{
			glBindTexture(GL_TEXTURE_2D, pTexRef->texName);
			if (byBytesPP == 4)
			{
				// 32 Bit
				glTexSubImage2D(GL_TEXTURE_2D, 0, iX, iY, 1, 1, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, &dwClr);
			}
			else
			{
				// 16 bit
				uint16_t wClr=ClrDw2W(dwClr);
				glTexSubImage2D(GL_TEXTURE_2D, 0, iX, iY, 1, 1, GL_BGRA, GL_UNSIGNED_SHORT_4_4_4_4_REV, &wClr);
			}
			return true;
		}
		// Otherwise, make sure that the texlock covers the new pixel
		C4Rect & r = pTexRef->LockSize;
		if (r.x > iX || r.y > iY || (r.x + r.Wdt) < iX || (r.y + r.Hgt) < iY)
		{
			// Unlock, then relock the whole thing
			pTexRef->Unlock();
			if (!pTexRef->Lock()) return false;
		}
	}
	else
#endif
	{
		if (!GetLockTexAt(&pTexRef, iX, iY)) return false;
	}
	// ...and set in actual surface
	if (byBytesPP == 4)
	{
		// 32 bit
		pTexRef->SetPix4(iX, iY, dwClr);
	}
	else
	{
		// 16 bit
		pTexRef->SetPix2(iX, iY, ClrDw2W(dwClr));
	}
	// success
	return true;
}

bool C4Surface::SetPixAlpha(int iX, int iY, BYTE byAlpha)
{
	// clip
	if ((iX<ClipX) || (iX>ClipX2) || (iY<ClipY) || (iY>ClipY2)) return true;
	// get+lock affected texture
	if (!ppTex) return false;
	C4TexRef *pTexRef;
	if (!GetLockTexAt(&pTexRef, iX, iY)) return false;
	// set alpha value of pix in surface
	if (byBytesPP == 4)
		// 32 bit
		*(((BYTE *) pTexRef->texLock.pBits)+iY*pTexRef->texLock.Pitch+iX*4+3)=byAlpha;
	else
	{
		// 16 bit
		BYTE *pPix=((BYTE *) pTexRef->texLock.pBits)+iY*pTexRef->texLock.Pitch+iX*2+1;
		*pPix = (*pPix & 0x0f) | (byAlpha & 0xf0);
	}
	// success
	return true;
}

bool C4Surface::BltPix(int iX, int iY, C4Surface *sfcSource, int iSrcX, int iSrcY, bool fTransparency)
{
	// 16- or 32bit-blit. lock target
	C4TexRef *pTexRef;
	if (!GetLockTexAt(&pTexRef, iX, iY)) return false;
	if (byBytesPP == 4)
	{
		// 32 bit
		DWORD *pPix32=(DWORD *) (((BYTE *) pTexRef->texLock.pBits)+iY*pTexRef->texLock.Pitch+iX*4);
		// get source pix as dword
		DWORD srcPix=sfcSource->GetPixDw(iSrcX, iSrcY, true);
		// merge
		if (!fTransparency)
		{
			// set it
			*pPix32=srcPix;
		}
		else
		{
			if (pDraw->dwBlitMode & C4GFXBLIT_ADDITIVE)
				BltAlphaAdd(*pPix32, srcPix);
			else
				BltAlpha(*pPix32, srcPix);
		}
	}
	else
	{
		// 16 bit
		WORD *pPix16=(WORD *) (((BYTE *) pTexRef->texLock.pBits)+iY*pTexRef->texLock.Pitch+iX*2);
		// get source pix as dword
		DWORD srcPix=sfcSource->GetPixDw(iSrcX, iSrcY, true);
		if (!fTransparency)
		{
			// set it
			*pPix16=ClrDw2W(srcPix);
		}
		else
		{
			// merge in 32 bit
			DWORD dwDst=ClrW2Dw(*pPix16);
			if (pDraw->dwBlitMode & C4GFXBLIT_ADDITIVE)
				BltAlphaAdd(dwDst, srcPix);
			else
				BltAlpha(dwDst, srcPix);
			// set
			*pPix16=ClrDw2W(dwDst);
		}
	}
	// done
	return true;
}

void C4Surface::ClearBoxDw(int iX, int iY, int iWdt, int iHgt)
{
	// lock
	if (!Locked) return;
	// clip to target size
	if (iX<0) { iWdt+=iX; iX=0; }
	if (iY<0) { iHgt+=iY; iY=0; }
	int iOver;
	iOver=Wdt-(iX+iWdt); if (iOver<0) iWdt+=iOver;
	iOver=Hgt-(iY+iHgt); if (iOver<0) iHgt+=iOver;
	// get textures involved
	int iTexX1=iX/iTexSize;
	int iTexY1=iY/iTexSize;
	int iTexX2=Min((iX+iWdt-1)/iTexSize +1, iTexX);
	int iTexY2=Min((iY+iHgt-1)/iTexSize +1, iTexY);
	// clear basesfc?
	bool fBaseSfc=false;
	if (pMainSfc) if (pMainSfc->ppTex) fBaseSfc=true;
	// clear all these textures
	for (int y=iTexY1; y<iTexY2; ++y)
	{
		for (int x=iTexX1; x<iTexX2; ++x)
		{
			C4TexRef *pTex = *(ppTex + y * iTexX + x);
			// get current offset in texture
			int iBlitX=iTexSize*x;
			int iBlitY=iTexSize*y;
			// get clearing bounds in texture
			C4Rect rtClear = C4Rect(0, 0, pTex->iSizeX, pTex->iSizeY);
			rtClear.Intersect(C4Rect(iX - iBlitX, iY - iBlitY, iWdt, iHgt));
			// is there a base-surface to be cleared first?
			if (fBaseSfc)
			{
				// then get this surface as same offset as from other surface
				// assuming this is only valid as long as there's no texture management,
				// organizing partially used textures together!
				C4TexRef *pBaseTex = *(pMainSfc->ppTex + y * iTexX + x);
				pBaseTex->ClearRect(rtClear);
			}
			// clear this texture
			pTex->ClearRect(rtClear);
		}
	}
}

bool C4Surface::CopyBytes(BYTE *pImageData)
{
	// copy image data directly into textures
	C4TexRef **ppCurrTex = ppTex, *pTex = *ppTex;
	int iSrcPitch = Wdt * byBytesPP; int iLineTotal = 0;
	for (int iY=0; iY<iTexY; ++iY)
	{
		BYTE *pSource = pImageData + iSrcPitch * iLineTotal;
		int iLastHeight=pTex->iSizeY; int iXImgPos=0;
		for (int iX=0; iX<iTexX; ++iX)
		{
			pTex = *ppCurrTex++;
			if (!pTex->Lock()) return false;
			BYTE *pTarget = (BYTE*)pTex->texLock.pBits;
			int iCpyNum = Min(pTex->iSizeX, Wdt-iXImgPos)*byBytesPP;
			int iYMax = Min(pTex->iSizeY, Hgt-iLineTotal);
			for (int iLine = 0; iLine < iYMax; ++iLine)
			{
				memcpy(pTarget, pSource, iCpyNum);
				pSource += iSrcPitch;
				// FIXME: use pTex->texLock.Pitch here?
				pTarget += pTex->iSizeX*byBytesPP;
			}
			pSource += iCpyNum - iSrcPitch*iYMax;
			iXImgPos += pTex->iSizeX;
		}
		iLineTotal += iLastHeight;
	}
	return true;
}

C4TexRef::C4TexRef(int iSizeX, int iSizeY, bool fSingle)
{
	// zero fields
#ifdef USE_DIRECTX
	pTex=NULL;
#endif
#ifdef USE_GL
	texName=0;
#endif
	texLock.pBits=NULL; fIntLock=false;
	// store size
	this->iSizeX=iSizeX;
	this->iSizeY=iSizeY;
	// add to texture manager
	if (!pTexMgr) pTexMgr = new C4TexMgr();
	pTexMgr->RegTex(this);
	// create texture: check ddraw
	if (!pDraw) return;
	if (!pDraw->DeviceReady()) return;
	// create it!
#ifdef USE_DIRECTX
	if (pD3D)
	{
		// Direct3D
		bool fRenderTarget = fSingle && !Config.Graphics.NoOffscreenBlits;
		if (pD3D->lpDevice->CreateTexture(iSizeX, iSizeY, 1, fRenderTarget ? D3DUSAGE_RENDERTARGET : 0, pD3D->dwSurfaceType, fRenderTarget ? D3DPOOL_DEFAULT : D3DPOOL_MANAGED, &pTex, NULL) != D3D_OK)
		{
			pDraw->Error("Error creating surface");
			return;
		}
		// empty texture
		if (!Lock()) return;
		FillMemory(texLock.pBits, texLock.Pitch*iSizeY, 0x00);
		Unlock();
	}
	else
#endif
#ifdef USE_GL
		if (pGL)
		{
			// OpenGL
			// create mem array for texture creation
			texLock.pBits = new unsigned char[iSizeX*iSizeY*pGL->byByteCnt];
			texLock.Pitch = iSizeX*pGL->byByteCnt;
			memset(texLock.pBits, 0x00, texLock.Pitch*iSizeY);
			// turn mem array into texture
			Unlock();
		}
		else
#endif
			if (pDraw)
			{
				texLock.pBits = new unsigned char[iSizeX*iSizeY*pDraw->byByteCnt];
				texLock.Pitch = iSizeX*pDraw->byByteCnt;
				memset(texLock.pBits, 0x00, texLock.Pitch*iSizeY);
				// Always locked
				LockSize.x = LockSize.y = 0;
				LockSize.Wdt = iSizeX; LockSize.Hgt = iSizeY;
			}
}

C4TexRef::~C4TexRef()
{
	fIntLock=false;
	// free texture
#ifdef USE_DIRECTX
	if (pD3D)
	{
		if (texLock.pBits) Unlock();
		if (pTex) pTex->Release();
	}
#endif
#ifdef USE_GL
	if (pGL)
	{
		if (texName && pGL->pCurrCtx) glDeleteTextures(1, &texName);
	}
#endif
	if (pDraw) delete [] static_cast<unsigned char*>(texLock.pBits); texLock.pBits = 0;
	// remove from texture manager
	pTexMgr->UnregTex(this);
}

bool C4TexRef::LockForUpdate(C4Rect & rtUpdate)
{
	// already locked?
	if (texLock.pBits)
	{
		// fully locked
		if (LockSize.x == 0 && LockSize.Wdt == iSizeX && LockSize.y == 0 && LockSize.Hgt == iSizeY)
		{
			return true;
		}
		else
		{
			// Commit previous changes to the texture
			Unlock();
		}
	}
	// lock
#ifdef USE_DIRECTX
	if (pD3D)
	{
		RECT r;
		r.left   = rtUpdate.x;
		r.top    = rtUpdate.y;
		r.right  = rtUpdate.x + rtUpdate.Wdt;
		r.bottom = rtUpdate.y + rtUpdate.Hgt;
		if (pTex)
			if (pTex->LockRect(0, &texLock, &r, D3DLOCK_DISCARD) == D3D_OK)
			{
				LockSize = rtUpdate;
				return true;
			}
	}
	else
#endif
#ifdef USE_GL
		if (pGL)
		{
			if (texName)
			{
				// prepare texture data
				texLock.pBits = new unsigned char[rtUpdate.Wdt * rtUpdate.Hgt * pGL->byByteCnt];
				texLock.Pitch = rtUpdate.Wdt * pGL->byByteCnt;
				LockSize = rtUpdate;
				return true;
			}
		}
		else
#endif
		{
			// nothing to do
		}
	// failure
	return false;
}

bool C4TexRef::Lock()
{
	// already locked?
	if (texLock.pBits) return true;
	LockSize.Wdt = iSizeX; LockSize.Hgt = iSizeY;
	LockSize.x = LockSize.y = 0;
	// lock
#ifdef USE_DIRECTX
	if (pD3D)
	{
		if (pTex)
			if (pTex->LockRect(0, &texLock, NULL, 0) == D3D_OK) return true;
	}
	else
#endif
#ifdef USE_GL
		if (pGL)
		{
			if (texName)
			{
				if (!pGL->pCurrCtx) return false;
				// get texture
				texLock.pBits = new unsigned char[iSizeX*iSizeY*pGL->byByteCnt];
				texLock.Pitch = iSizeX * pGL->byByteCnt;
				glBindTexture(GL_TEXTURE_2D, texName);
				glGetTexImage(GL_TEXTURE_2D, 0, GL_BGRA, pDraw->byByteCnt == 2 ? GL_UNSIGNED_SHORT_4_4_4_4_REV : GL_UNSIGNED_INT_8_8_8_8_REV, texLock.pBits);
				return true;
			}
		}
		else
#endif
		{
			// nothing to do
		}
	// failure
	return false;
}

void C4TexRef::Unlock()
{
	// locked?
	if (!texLock.pBits || fIntLock) return;
#ifdef USE_DIRECTX
	if (pD3D)
	{
		// unlock
		if (pTex) pTex->UnlockRect(0);
		texLock.pBits=NULL;
	}
	else
#endif
#ifdef USE_GL
		if (pGL)
		{
			if (!pGL->pCurrCtx)
			{
//      BREAKPOINT_HERE;
				assert(pGL->pMainCtx);
				pGL->pMainCtx->Select();
			}
			glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
			if (!texName)
			{
				// create a new texture
				glGenTextures(1, &texName);
				glBindTexture(GL_TEXTURE_2D, texName);
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
				// Default, changed in PerformBlt if necessary
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
				glTexImage2D(GL_TEXTURE_2D, 0, 4, iSizeX, iSizeY, 0, GL_BGRA, pDraw->byByteCnt == 2 ? GL_UNSIGNED_SHORT_4_4_4_4_REV : GL_UNSIGNED_INT_8_8_8_8_REV, texLock.pBits);
			}
			else
			{
				// reuse the existing texture
				glBindTexture(GL_TEXTURE_2D, texName);
				glTexSubImage2D(GL_TEXTURE_2D, 0,
				                LockSize.x, LockSize.y, LockSize.Wdt, LockSize.Hgt,
				                GL_BGRA, pDraw->byByteCnt == 2 ? GL_UNSIGNED_SHORT_4_4_4_4_REV : GL_UNSIGNED_INT_8_8_8_8_REV, texLock.pBits);
			}
			delete[] static_cast<unsigned char*>(texLock.pBits); texLock.pBits=NULL;
			// switch back to original context
		}
		else
#endif
		{
			// nothing to do
		}
}

bool C4TexRef::ClearRect(C4Rect &rtClear)
{
	// ensure locked
	if (!LockForUpdate(rtClear)) return false;
	// clear pixels
	int y;
	switch (pDraw->byByteCnt)
	{
	case 2:
		for (y = rtClear.y; y < rtClear.y + rtClear.Hgt; ++y)
		{
			for (int x = rtClear.x; x < rtClear.x + rtClear.Wdt; ++x)
				SetPix2(x, y, 0x0000);
		}
		break;
	case 4:
		for (y = rtClear.y; y < rtClear.y + rtClear.Hgt; ++y)
		{
			for (int x = rtClear.x; x < rtClear.x + rtClear.Wdt; ++x)
				SetPix4(x, y, 0x00000000);
		}
		break;
	}
	// success
	return true;
}

bool C4TexRef::FillBlack()
{
	// ensure locked
	if (!Lock()) return false;
	// clear pixels
	int y;
	switch (pDraw->byByteCnt)
	{
	case 2:
		for (y=0; y<iSizeY; ++y)
		{
			for (int x = 0; x < iSizeX; ++x)
				SetPix2(x, y, 0xf000);
		}
		break;
	case 4:
		for (y=0; y<iSizeY; ++y)
		{
			for (int x = 0; x < iSizeX; ++x)
				SetPix4(x, y, 0xff000000);
		}
		break;
	}
	// success
	return true;
}

// texture manager

C4TexMgr::C4TexMgr()
{
	// clear textures
	Textures.clear();
}

C4TexMgr::~C4TexMgr()
{
	// unlock all textures
	IntUnlock();
}

void C4TexMgr::RegTex(C4TexRef *pTex)
{
	// add texture to list
	Textures.push_front(pTex);
}

void C4TexMgr::UnregTex(C4TexRef *pTex)
{
	// remove texture from list
	Textures.remove(pTex);
	// if list is empty, remove self
	if (Textures.empty()) { delete this; pTexMgr=NULL; }
}

void C4TexMgr::IntLock()
{
	// lock all textures
	int j=Textures.size();
	for (std::list<C4TexRef *>::iterator i=Textures.begin(); j--; ++i)
	{
		C4TexRef *pRef = *i;
		if (pRef->Lock() && pRef->texLock.pBits)
		{
			pRef->fIntLock = true;
#ifdef USE_GL
			// Release the underlying texture with GL and recreate
			// it on unlock, so that the texture survives
			// context recreation.
			if(pGL)
			{
				glDeleteTextures(1, &pRef->texName);
				pRef->texName = 0;
			}
#endif
		}
	}
}

void C4TexMgr::IntUnlock()
{
	// unlock all internally locked textures
	int j=Textures.size();
	for (std::list<C4TexRef *>::iterator i=Textures.begin(); j--; ++i)
	{
		C4TexRef *pRef = *i;
		if (pRef->fIntLock) { pRef->fIntLock = false; pRef->Unlock(); }
	}
}

C4TexMgr *pTexMgr;
