1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 1998-2000, Matthes Bender
5  * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
6  * Copyright (c) 2009-2016, The OpenClonk Team and contributors
7  *
8  * Distributed under the terms of the ISC license; see accompanying file
9  * "COPYING" for details.
10  *
11  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
12  * See accompanying file "TRADEMARK" for details.
13  *
14  * To redistribute this file separately, substitute the full license texts
15  * for the above references.
16  */
17 // a wrapper class to DirectDraw surfaces
18 
19 #include "C4Include.h"
20 #include "C4ForbidLibraryCompilation.h"
21 #include "graphics/C4Surface.h"
22 
23 #include "c4group/CStdFile.h"
24 #include "graphics/Bitmap256.h"
25 #include "graphics/C4Draw.h"
26 #include "graphics/C4DrawGL.h"
27 #include "graphics/StdPNG.h"
28 #include "lib/StdColors.h"
29 #include "platform/C4App.h"
30 #include "platform/C4Window.h"
31 #include "platform/StdRegistry.h"
32 
33 #ifdef HAVE_IO_H
34 #include <io.h>
35 #endif
36 
C4Surface()37 C4Surface::C4Surface()
38 {
39 	Default();
40 }
41 
C4Surface(int iWdt,int iHgt,int iFlags)42 C4Surface::C4Surface(int iWdt, int iHgt, int iFlags)
43 {
44 	Default();
45 	// create
46 	Create(iWdt, iHgt, iFlags);
47 }
48 
C4Surface(C4AbstractApp * pApp,C4Window * pWindow)49 C4Surface::C4Surface(C4AbstractApp * pApp, C4Window * pWindow):
50 		Wdt(0), Hgt(0)
51 {
52 	Default();
53 	fPrimary=true;
54 	this->pWindow=pWindow;
55 	// create rendering context
56 #ifndef USE_CONSOLE
57 	pCtx = pGL->CreateContext(pWindow, pApp);
58 #endif
59 	// reset clipping
60 	NoClip();
61 }
62 
~C4Surface()63 C4Surface::~C4Surface()
64 {
65 	/*  for (C4ObjectLink *lnk = ::Objects.First; lnk; lnk=lnk->Next)
66 	    if (lnk->Obj->Menu)
67 	      lnk->Obj->Menu->AssertSurfaceNotUsed(this);*/
68 	Clear();
69 }
70 
Default()71 void C4Surface::Default()
72 {
73 	Wdt=Hgt=0;
74 	Scale=1;
75 	PrimarySurfaceLockPitch=0; PrimarySurfaceLockBits=nullptr;
76 	ClipX=ClipY=ClipX2=ClipY2=0;
77 	Locked=0;
78 	Attached=false;
79 	fPrimary=false;
80 	pMainSfc=nullptr;
81 	pNormalSfc=nullptr;
82 #ifndef USE_CONSOLE
83 	pCtx=nullptr;
84 #endif
85 	pWindow=nullptr;
86 	ClrByOwnerClr=0;
87 	iTexSize=0;
88 	fIsBackground=false;
89 #ifdef _DEBUG
90 	dbg_idx = 0;
91 #endif
92 }
93 
MoveFrom(C4Surface * psfcFrom)94 void C4Surface::MoveFrom(C4Surface *psfcFrom)
95 {
96 	// clear own
97 	Clear();
98 	// safety
99 	if (!psfcFrom) return;
100 	// grab data from other sfc
101 #ifdef _DEBUG
102 	dbg_idx = psfcFrom->dbg_idx;
103 #endif
104 	Wdt=psfcFrom->Wdt; Hgt=psfcFrom->Hgt;
105 	PrimarySurfaceLockPitch=psfcFrom->PrimarySurfaceLockPitch;
106 	PrimarySurfaceLockBits=psfcFrom->PrimarySurfaceLockBits;
107 	psfcFrom->PrimarySurfaceLockBits=nullptr;
108 	ClipX=psfcFrom->ClipX; ClipY=psfcFrom->ClipY;
109 	ClipX2=psfcFrom->ClipX2; ClipY2=psfcFrom->ClipY2;
110 	Locked=psfcFrom->Locked;
111 	Attached=psfcFrom->Attached;
112 	fPrimary=psfcFrom->fPrimary; // shouldn't be true!
113 	texture = std::move(psfcFrom->texture);
114 	pMainSfc=psfcFrom->pMainSfc;
115 	pNormalSfc=psfcFrom->pNormalSfc;
116 	ClrByOwnerClr=psfcFrom->ClrByOwnerClr;
117 	iTexSize=psfcFrom->iTexSize;
118 #ifndef USE_CONSOLE
119 	Format=psfcFrom->Format;
120 #endif
121 	fIsBackground = psfcFrom->fIsBackground;
122 	// default other sfc
123 	psfcFrom->Default();
124 }
125 
Clear()126 void C4Surface::Clear()
127 {
128 	// Undo all locks
129 	while (Locked) Unlock();
130 	// release surface
131 #ifndef USE_CONSOLE
132 	if (pCtx)
133 	{
134 		delete pCtx;
135 		pCtx = nullptr;
136 	}
137 #endif
138 	texture.reset();
139 #ifdef _DEBUG
140 	dbg_idx = 0;
141 #endif
142 }
143 
IsRenderTarget()144 bool C4Surface::IsRenderTarget()
145 {
146 	// primary is always OK...
147 	return fPrimary;
148 }
149 
NoClip()150 void C4Surface::NoClip()
151 {
152 	ClipX=0; ClipY=0; ClipX2=Wdt-1; ClipY2=Hgt-1;
153 }
154 
Clip(int iX,int iY,int iX2,int iY2)155 void C4Surface::Clip(int iX, int iY, int iX2, int iY2)
156 {
157 	ClipX=Clamp(iX,0,Wdt-1); ClipY=Clamp(iY,0,Hgt-1);
158 	ClipX2=Clamp(iX2,0,Wdt-1); ClipY2=Clamp(iY2,0,Hgt-1);
159 }
160 
Create(int iWdt,int iHgt,int iFlags)161 bool C4Surface::Create(int iWdt, int iHgt, int iFlags)
162 {
163 	Clear(); Default();
164 	// check size
165 	if (!iWdt || !iHgt) return false;
166 	Wdt=iWdt; Hgt=iHgt;
167 	// create texture: check gfx system
168 	if (!pDraw) return false;
169 	if (!pDraw->DeviceReady()) return false;
170 
171 	// store color format that will be used
172 #ifndef USE_CONSOLE
173 	Format=pGL->sfcFmt;
174 #endif
175 	// create texture
176 	iTexSize = std::max(iWdt, iHgt);
177 	texture = std::make_unique<C4TexRef>(iWdt, iHgt, iFlags);
178 	// update clipping
179 	NoClip();
180 	// success
181 	return true;
182 }
183 
Copy(C4Surface & fromSfc)184 bool C4Surface::Copy(C4Surface &fromSfc)
185 {
186 	// Clear anything old
187 	Clear();
188 	// Default to other surface's color depth
189 	Default();
190 	// Create surface (TODO: copy flags)
191 	if (!Create(fromSfc.Wdt, fromSfc.Hgt)) return false;
192 	// Blit copy
193 	if (!pDraw->BlitSurface(&fromSfc, this, 0, 0, false))
194 		{ Clear(); return false; }
195 	// Success
196 	return true;
197 }
198 
199 #define  RANGE    255
200 #define  HLSMAX   RANGE
201 #define  RGBMAX   255
202 
ClrByOwner(DWORD & dwClr)203 bool ClrByOwner(DWORD &dwClr) // new style, based on Microsoft Knowledge Base Article - 29240
204 {
205 	int H,L,S;
206 	WORD R,G,B;
207 	BYTE cMax,cMin;
208 	WORD  Rdelta,Gdelta,Bdelta;
209 	// get RGB
210 	R = GetRedValue(dwClr);
211 	G = GetGreenValue(dwClr);
212 	B = GetBlueValue(dwClr);
213 	// calculate lightness
214 	cMax = std::max<int>(std::max<int>(R,G),B);
215 	cMin = std::min<int>(std::min<int>(R,G),B);
216 	L = ( ((cMax+cMin)*HLSMAX) + RGBMAX )/(2*RGBMAX);
217 	// achromatic case
218 	if (cMax == cMin)
219 	{
220 		S = 0;
221 		H = (HLSMAX*2/3);
222 	}
223 	// chromatic case
224 	else
225 	{
226 		// saturation
227 		if (L <= (HLSMAX/2))
228 			S = ( ((cMax-cMin)*HLSMAX) + ((cMax+cMin)/2) ) / (cMax+cMin);
229 		else
230 			S = ( ((cMax-cMin)*HLSMAX) + ((2*RGBMAX-cMax-cMin)/2) )
231 			    / (2*RGBMAX-cMax-cMin);
232 		// hue
233 		Rdelta = ( ((cMax-R)*(HLSMAX/6)) + ((cMax-cMin)/2) ) / (cMax-cMin);
234 		Gdelta = ( ((cMax-G)*(HLSMAX/6)) + ((cMax-cMin)/2) ) / (cMax-cMin);
235 		Bdelta = ( ((cMax-B)*(HLSMAX/6)) + ((cMax-cMin)/2) ) / (cMax-cMin);
236 		if (R == cMax)
237 			H = Bdelta - Gdelta;
238 		else if (G == cMax)
239 			H = (HLSMAX/3) + Rdelta - Bdelta;
240 		else
241 			H = ((2*HLSMAX)/3) + Gdelta - Rdelta;
242 		if (H < 0)
243 			H += HLSMAX;
244 		if (H > HLSMAX)
245 			H -= HLSMAX;
246 	}
247 	// Not blue
248 	if (!(Inside(H, 145, 175) && (S > 100))) return false;
249 	// It's blue: make it gray
250 	BYTE b = GetBlueValue(dwClr);
251 	dwClr = RGBA(b, b, b, 0) | (dwClr & 0xff000000);
252 	return true;
253 }
254 
CreateColorByOwner(C4Surface * pBySurface)255 bool C4Surface::CreateColorByOwner(C4Surface *pBySurface)
256 {
257 	// safety
258 	if (!pBySurface) return false;
259 	if (!pBySurface->texture) return false;
260 	// create in same size
261 	if (!Create(pBySurface->Wdt, pBySurface->Hgt)) return false;
262 	// copy scale
263 	Scale = pBySurface->Scale;
264 	// set main surface
265 	pMainSfc=pBySurface;
266 	// lock it
267 	if (!pMainSfc->Lock()) return false;
268 	if (!Lock()) { pMainSfc->Unlock(); return false; }
269 	// set ColorByOwner-pixels
270 	for (int iY=0; iY<Hgt; ++iY)
271 		for (int iX=0; iX<Wdt; ++iX)
272 		{
273 			// get pixel
274 			DWORD dwPix=pMainSfc->GetPixDw(iX, iY, false);
275 			// is it a ClrByOwner-px?
276 			if (!ClrByOwner(dwPix)) continue;
277 			// set in this surface
278 			SetPixDw(iX, iY, dwPix);
279 			// clear in the other
280 			pMainSfc->SetPixDw(iX, iY, 0x00ffffff);
281 		}
282 	// unlock
283 	Unlock();
284 	pMainSfc->Unlock();
285 	// success
286 	return true;
287 }
288 
SetAsClrByOwnerOf(C4Surface * pOfSurface)289 bool C4Surface::SetAsClrByOwnerOf(C4Surface *pOfSurface)
290 {
291 	// safety
292 	if (!pOfSurface) return false;
293 	if (Wdt != pOfSurface->Wdt || Hgt != pOfSurface->Hgt)
294 		return false;
295 	// set main surface
296 	pMainSfc=pOfSurface;
297 	// success
298 	return true;
299 }
300 
UpdateSize(int wdt,int hgt)301 bool C4Surface::UpdateSize(int wdt, int hgt)
302 {
303 	assert(fPrimary);
304 	if (!fPrimary)
305 		return false;
306 	this->Wdt = wdt; this->Hgt = hgt;
307 	return true;
308 }
309 
PageFlip(C4Rect * pSrcRt,C4Rect * pDstRt)310 bool C4Surface::PageFlip(C4Rect *pSrcRt, C4Rect *pDstRt)
311 {
312 	assert(fPrimary);
313 	if (!fPrimary)
314 		return false;
315 	// call from gfx thread only!
316 	if (!pDraw->pApp || !pDraw->pApp->AssertMainThread()) return false;
317 #ifndef USE_CONSOLE
318 	return pCtx->PageFlip();
319 #endif
320 	return true;
321 }
322 
ReadBMP(CStdStream & hGroup,int iFlags)323 bool C4Surface::ReadBMP(CStdStream &hGroup, int iFlags)
324 {
325 	int lcnt;
326 	C4BMP256Info BitmapInfo;
327 	// read bmpinfo-header
328 	if (!hGroup.Read(&BitmapInfo,sizeof(C4BMPInfo))) return false;
329 	// is it 8bpp?
330 	if (BitmapInfo.Info.biBitCount == 8)
331 	{
332 		if (!hGroup.Read(((BYTE *) &BitmapInfo)+sizeof(C4BMPInfo),
333 		                 std::min(sizeof(BitmapInfo)-sizeof(C4BMPInfo),sizeof(BitmapInfo)-sizeof(C4BMPInfo)+BitmapInfo.FileBitsOffset())))
334 			return false;
335 		if (!hGroup.Advance(BitmapInfo.FileBitsOffset())) return false;
336 	}
337 	else
338 	{
339 		// read 24bpp
340 		if (BitmapInfo.Info.biBitCount != 24) return false;
341 		if (!hGroup.Advance(((C4BMPInfo) BitmapInfo).FileBitsOffset())) return false;
342 	}
343 
344 	// Create and lock surface
345 	if (!Create(BitmapInfo.Info.biWidth,BitmapInfo.Info.biHeight, iFlags)) return false;
346 	if (!Lock()) { Clear(); return false; }
347 
348 	// create line buffer
349 	int iBufSize=DWordAligned(BitmapInfo.Info.biWidth*BitmapInfo.Info.biBitCount/8);
350 	BYTE *pBuf = new BYTE[iBufSize];
351 	// Read lines
352 	for (lcnt=Hgt-1; lcnt>=0; lcnt--)
353 	{
354 		if (!hGroup.Read(pBuf, iBufSize))
355 			{ Clear(); delete [] pBuf; return false; }
356 		BYTE *pPix=pBuf;
357 		for (int x=0; x<BitmapInfo.Info.biWidth; ++x)
358 			switch (BitmapInfo.Info.biBitCount)
359 			{
360 			case 8:
361 				SetPixDw(x, lcnt, C4RGB(
362 				         BitmapInfo.Colors[*pPix].rgbRed,
363 				         BitmapInfo.Colors[*pPix].rgbGreen,
364 				         BitmapInfo.Colors[*pPix].rgbBlue));
365 				++pPix;
366 				break;
367 			case 24:
368 				SetPixDw(x, lcnt, C4RGB(pPix[0], pPix[1], pPix[2]));
369 				pPix+=3;
370 				break;
371 			}
372 	}
373 	// free buffer again
374 	delete [] pBuf;
375 
376 	Unlock();
377 
378 	return true;
379 }
380 
SavePNG(const char * szFilename,bool fSaveAlpha,bool fSaveOverlayOnly,bool use_background_thread)381 bool C4Surface::SavePNG(const char *szFilename, bool fSaveAlpha, bool fSaveOverlayOnly, bool use_background_thread)
382 {
383 	// Lock - WARNING - maybe locking primary surface here...
384 	if (!Lock()) return false;
385 
386 	// create png file
387 	std::unique_ptr<CPNGFile> png(new CPNGFile());
388 	if (!png->Create(Wdt, Hgt, fSaveAlpha)) { Unlock(); return false; }
389 
390 	// reset overlay if desired
391 	C4Surface *pMainSfcBackup = nullptr;
392 	if (fSaveOverlayOnly) { pMainSfcBackup=pMainSfc; pMainSfc=nullptr; }
393 
394 #ifndef USE_CONSOLE
395 	if (fPrimary)
396 	{
397 		// Take shortcut. FIXME: Check Endian
398 		for (int y = 0; y < Hgt; ++y)
399 			glReadPixels(0, Hgt - y - 1, Wdt, 1, fSaveAlpha ? GL_BGRA : GL_BGR, GL_UNSIGNED_BYTE, png->GetRow(y));
400 	}
401 	else
402 #endif
403 	{
404 		// write pixel values
405 		for (int y=0; y<Hgt; ++y)
406 			for (int x=0; x<Wdt; ++x)
407 			{
408 				DWORD dwClr = GetPixDw(x, y, false);
409 				png->SetPix(x, y, dwClr);
410 			}
411 	}
412 
413 	// reset overlay
414 	if (fSaveOverlayOnly) pMainSfc=pMainSfcBackup;
415 
416 	// Unlock
417 	Unlock();
418 
419 	// save png - either directly or delayed in a background thread if desired
420 	if (use_background_thread)
421 	{
422 		CPNGFile::ScheduleSaving(png.release(), szFilename);
423 	}
424 	else
425 	{
426 		if (!png->Save(szFilename)) return false;
427 	}
428 
429 	// Success
430 	return true;
431 }
432 
433 
AttachPalette()434 bool C4Surface::AttachPalette()
435 {
436 	return true;
437 }
438 
ColorDistance(BYTE * bpRGB1,BYTE * bpRGB2)439 double ColorDistance(BYTE *bpRGB1, BYTE *bpRGB2)
440 {
441 	return (double) (Abs(bpRGB1[0]-bpRGB2[0]) + Abs(bpRGB1[1]-bpRGB2[1]) + Abs(bpRGB1[2]-bpRGB2[2])) / 6.0;
442 }
443 
GetSurfaceSize(int & irX,int & irY)444 bool C4Surface::GetSurfaceSize(int &irX, int &irY)
445 {
446 	// simply assign stored values
447 	irX=Wdt;
448 	irY=Hgt;
449 	// success
450 	return true;
451 }
452 
Lock()453 bool C4Surface::Lock()
454 {
455 	// lock main sfc
456 	if (pMainSfc) if (!pMainSfc->Lock()) return false;
457 	// lock texture
458 	if (!Locked && !fPrimary && !texture)
459 		return false;
460 	// count lock
461 	Locked++; return true;
462 }
463 
Unlock()464 bool C4Surface::Unlock()
465 {
466 	// unlock main sfc
467 	if (pMainSfc) pMainSfc->Unlock();
468 	// locked?
469 	if (!Locked) return false;
470 	// decrease lock counter; check if zeroed and unlock then
471 	Locked--;
472 	if (!Locked)
473 	{
474 		if (fPrimary)
475 		{
476 			// emulated primary locks in OpenGL
477 			delete[] PrimarySurfaceLockBits;
478 			PrimarySurfaceLockBits = nullptr;
479 			return true;
480 		}
481 		else
482 		{
483 			// non-primary unlock: unlock all texture surfaces (if locked)
484 			if (texture)
485 				texture->Unlock();
486 		}
487 	}
488 	return true;
489 }
490 
GetPixDw(int iX,int iY,bool fApplyModulation)491 DWORD C4Surface::GetPixDw(int iX, int iY, bool fApplyModulation)
492 {
493 	BYTE *pBuf = nullptr; int iPitch = 0; // TODO: are those initialised to something sensible?
494 	// backup pos
495 	int iX2=iX; int iY2=iY;
496 	// primary?
497 	if (fPrimary)
498 	{
499 #ifndef USE_CONSOLE
500 		if (!PrimarySurfaceLockBits)
501 		{
502 			PrimarySurfaceLockBits = new unsigned char[Wdt*Hgt*3];
503 			glPixelStorei(GL_PACK_ALIGNMENT, 1);
504 			glReadPixels( 0, 0, Wdt, Hgt, GL_BGR, GL_UNSIGNED_BYTE, PrimarySurfaceLockBits);
505 			PrimarySurfaceLockPitch = Wdt*3;
506 		}
507 		return * (DWORD *) (PrimarySurfaceLockBits+(Hgt-iY-1)*PrimarySurfaceLockPitch+iX*3);
508 #endif
509 	}
510 	else
511 	{
512 		// get+lock affected texture
513 		if (!texture) return 0;
514 		texture->Lock();
515 		pBuf=(BYTE *) texture->texLock.pBits.get();
516 		iPitch=texture->texLock.Pitch;
517 	}
518 	// get pix of surface
519 	DWORD dwPix;
520 	DWORD *pPix=(DWORD *) (pBuf+iY*iPitch+iX*4);
521 	dwPix = *pPix;
522 	// this is a ColorByOwner-surface?
523 	if (pMainSfc)
524 	{
525 		BYTE byAlpha=BYTE(dwPix>>24);
526 		// pix is fully transparent?
527 		if (byAlpha==0x00)
528 			// then get the main surfaces's pixel
529 			dwPix = pMainSfc->GetPixDw(iX2, iY2, fApplyModulation);
530 		else
531 		{
532 			// otherwise, it's a ColorByOwner-pixel: adjust the color
533 			if (fApplyModulation)
534 			{
535 				if (pDraw->dwBlitMode & C4GFXBLIT_CLRSFC_MOD2)
536 					ModulateClrMOD2(dwPix, ClrByOwnerClr);
537 				else
538 					ModulateClr(dwPix, ClrByOwnerClr);
539 				if (pDraw->BlitModulated && !(pDraw->dwBlitMode & C4GFXBLIT_CLRSFC_OWNCLR))
540 					ModulateClr(dwPix, pDraw->BlitModulateClr);
541 			}
542 			else
543 				ModulateClr(dwPix, ClrByOwnerClr);
544 			// does it contain transparency? then blit on main sfc
545 			if (byAlpha)
546 			{
547 				DWORD dwMainPix = pMainSfc->GetPixDw(iX2, iY2, fApplyModulation);
548 				BltAlpha(dwMainPix, dwPix); dwPix=dwMainPix;
549 			}
550 		}
551 	}
552 	else
553 	{
554 		// single main surface
555 		// apply color modulation if desired
556 		if (fApplyModulation && pDraw->BlitModulated)
557 		{
558 			if (pDraw->dwBlitMode & C4GFXBLIT_MOD2)
559 				ModulateClrMOD2(dwPix, pDraw->BlitModulateClr);
560 			else
561 				ModulateClr(dwPix, pDraw->BlitModulateClr);
562 		}
563 	}
564 	// return pixel value
565 	return dwPix;
566 }
567 
IsPixTransparent(int iX,int iY)568 bool C4Surface::IsPixTransparent(int iX, int iY)
569 {
570 	// get pixel value
571 	DWORD dwPix=GetPixDw(iX, iY, false);
572 	// get alpha value
573 	return (dwPix>>24) < 128;
574 }
575 
SetPixDw(int iX,int iY,DWORD dwClr)576 bool C4Surface::SetPixDw(int iX, int iY, DWORD dwClr)
577 {
578 	// clip
579 	if ((iX<ClipX) || (iX>ClipX2) || (iY<ClipY) || (iY>ClipY2)) return true;
580 	// get+lock affected texture
581 	if (!texture) return false;
582 	texture->Lock();
583 	// if color is fully transparent, ensure it's black
584 	if (dwClr>>24 == 0x00) dwClr=0x00000000;
585 	// ...and set in actual surface
586 	texture->SetPix(iX, iY, dwClr);
587 	// success
588 	return true;
589 }
590 
BltPix(int iX,int iY,C4Surface * sfcSource,int iSrcX,int iSrcY,bool fTransparency)591 bool C4Surface::BltPix(int iX, int iY, C4Surface *sfcSource, int iSrcX, int iSrcY, bool fTransparency)
592 {
593 	// 32bit-blit. lock target
594 	if (!texture) return false;
595 	texture->Lock();
596 	DWORD *pPix32 = (DWORD *)(((BYTE *)texture->texLock.pBits.get()) + iY*texture->texLock.Pitch + iX * 4);
597 	// get source pix as dword
598 	DWORD srcPix=sfcSource->GetPixDw(iSrcX, iSrcY, true);
599 	// merge
600 	if (!fTransparency)
601 	{
602 		// set it
603 		*pPix32=srcPix;
604 	}
605 	else
606 	{
607 		if (pDraw->dwBlitMode & C4GFXBLIT_ADDITIVE)
608 			BltAlphaAdd(*pPix32, srcPix);
609 		else
610 			BltAlpha(*pPix32, srcPix);
611 	}
612 	// done
613 	return true;
614 }
615 
ClearBoxDw(int iX,int iY,int iWdt,int iHgt)616 void C4Surface::ClearBoxDw(int iX, int iY, int iWdt, int iHgt)
617 {
618 	// lock
619 	if (!Locked) return;
620 	// clip to target size
621 	if (iX<0) { iWdt+=iX; iX=0; }
622 	if (iY<0) { iHgt+=iY; iY=0; }
623 	int iOver;
624 	iOver=Wdt-(iX+iWdt); if (iOver<0) iWdt+=iOver;
625 	iOver=Hgt-(iY+iHgt); if (iOver<0) iHgt+=iOver;
626 	C4Rect rtClear{ iX, iY, iWdt, iHgt };
627 	if (pMainSfc && pMainSfc->texture)
628 	{
629 		// assuming this is only valid as long as there's no texture management,
630 		// organizing partially used textures together!
631 		pMainSfc->texture->ClearRect(rtClear);
632 	}
633 	// clear this texture
634 	texture->ClearRect(rtClear);
635 }
636 
C4TexRef(int iSizeX,int iSizeY,int iFlags)637 C4TexRef::C4TexRef(int iSizeX, int iSizeY, int iFlags)
638 {
639 	// zero fields
640 #ifndef USE_CONSOLE
641 	texName = 0;
642 #endif
643 	texLock.pBits.reset(); fIntLock=false;
644 	// store size
645 	this->iSizeX=iSizeX;
646 	this->iSizeY=iSizeY;
647 	this->iFlags=iFlags;
648 	// add to texture manager
649 	if (!pTexMgr) pTexMgr = new C4TexMgr();
650 	pTexMgr->RegTex(this);
651 	// create texture: check ddraw
652 	if (!pDraw) return;
653 	if (!pDraw->DeviceReady()) return;
654 	// create it!
655 	// Reserve video memory
656 	CreateTexture();
657 
658 	if ((iFlags & C4SF_Unlocked) == 0 && pDraw)
659 	{
660 		texLock.pBits = std::make_unique<unsigned char[]>(iSizeX * iSizeY * C4Draw::COLOR_DEPTH_BYTES);
661 		texLock.Pitch = iSizeX * C4Draw::COLOR_DEPTH_BYTES;
662 		memset(texLock.pBits.get(), 0x00, texLock.Pitch*iSizeY);
663 		// Always locked
664 		LockSize.x = LockSize.y = 0;
665 		LockSize.Wdt = iSizeX; LockSize.Hgt = iSizeY;
666 	}
667 }
668 
~C4TexRef()669 C4TexRef::~C4TexRef()
670 {
671 	fIntLock=false;
672 	// free texture
673 #ifndef USE_CONSOLE
674 	if (pGL && pGL->pCurrCtx) glDeleteTextures(1, &texName);
675 #endif
676 	if (pDraw) texLock.pBits = nullptr;
677 	// remove from texture manager
678 	pTexMgr->UnregTex(this);
679 }
680 
CreateTexture()681 void C4TexRef::CreateTexture()
682 {
683 #ifndef USE_CONSOLE
684 	assert(texName == 0);
685 
686 	const bool fTileable = (iFlags & C4SF_Tileable) != 0;
687 	const bool fMipMap = (iFlags & C4SF_MipMap) != 0;
688 
689 	glGenTextures(1, &texName);
690 	glBindTexture(GL_TEXTURE_2D, texName);
691 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, fTileable ? GL_REPEAT : GL_CLAMP_TO_EDGE);
692 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, fTileable ? GL_REPEAT : GL_CLAMP_TO_EDGE);
693 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
694 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, fMipMap ? GL_LINEAR_MIPMAP_NEAREST : GL_LINEAR);
695 	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, iSizeX, iSizeY, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, nullptr);
696 	if (fMipMap) glGenerateMipmap(GL_TEXTURE_2D);
697 #endif
698 }
699 
LockForUpdate(C4Rect & rtUpdate)700 bool C4TexRef::LockForUpdate(C4Rect & rtUpdate)
701 {
702 	// already locked?
703 	if (texLock.pBits)
704 	{
705 		// fully locked
706 		if (LockSize.x == 0 && LockSize.Wdt == iSizeX && LockSize.y == 0 && LockSize.Hgt == iSizeY)
707 		{
708 			return true;
709 		}
710 		else
711 		{
712 			// Commit previous changes to the texture
713 			Unlock();
714 		}
715 	}
716 	// lock
717 #ifndef USE_CONSOLE
718 	// prepare texture data
719 	texLock.pBits = std::make_unique<unsigned char[]>(rtUpdate.Wdt * rtUpdate.Hgt * C4Draw::COLOR_DEPTH_BYTES);
720 	texLock.Pitch = rtUpdate.Wdt * C4Draw::COLOR_DEPTH_BYTES;
721 	LockSize = rtUpdate;
722 	return true;
723 #endif
724 	// failure
725 	return false;
726 }
727 
Lock()728 bool C4TexRef::Lock()
729 {
730 	// already locked?
731 	if (texLock.pBits) return true;
732 	LockSize.Wdt = iSizeX; LockSize.Hgt = iSizeY;
733 	LockSize.x = LockSize.y = 0;
734 	// lock
735 #ifndef USE_CONSOLE
736 			if (texName)
737 			{
738 				if (!pGL->pCurrCtx) return false;
739 				// get texture
740 				texLock.pBits = std::make_unique<unsigned char[]>(iSizeX * iSizeY * C4Draw::COLOR_DEPTH_BYTES);
741 				texLock.Pitch = iSizeX * C4Draw::COLOR_DEPTH_BYTES;
742 				glBindTexture(GL_TEXTURE_2D, texName);
743 				glGetTexImage(GL_TEXTURE_2D, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, texLock.pBits.get());
744 				return true;
745 			}
746 #endif
747 		{
748 			// nothing to do
749 		}
750 	// failure
751 	return false;
752 }
753 
Unlock()754 void C4TexRef::Unlock()
755 {
756 	// locked?
757 	if (!texLock.pBits || fIntLock) return;
758 #ifndef USE_CONSOLE
759 			if (!pGL->pCurrCtx)
760 			{
761 //      BREAKPOINT_HERE;
762 				assert(pGL->pMainCtx);
763 				pGL->pMainCtx->Select();
764 			}
765 
766 			const bool fTileable = (iFlags & C4SF_Tileable) != 0;
767 			const bool fMipMap = (iFlags & C4SF_MipMap) != 0;
768 
769 			glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
770 
771 			// reuse the existing texture
772 			glBindTexture(GL_TEXTURE_2D, texName);
773 			glTexSubImage2D(GL_TEXTURE_2D, 0,
774 			                LockSize.x, LockSize.y, LockSize.Wdt, LockSize.Hgt,
775 			                GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, texLock.pBits.get());
776 
777 			texLock.pBits.reset();
778 			if (fMipMap) glGenerateMipmap(GL_TEXTURE_2D);
779 #endif
780 }
781 
ClearRect(C4Rect & rtClear)782 bool C4TexRef::ClearRect(C4Rect &rtClear)
783 {
784 	// ensure locked
785 	if (!LockForUpdate(rtClear)) return false;
786 	// clear pixels
787 	int y;
788 	for (y = rtClear.y; y < rtClear.y + rtClear.Hgt; ++y)
789 	{
790 		for (int x = rtClear.x; x < rtClear.x + rtClear.Wdt; ++x)
791 			SetPix(x, y, 0x00000000);
792 	}
793 	// success
794 	return true;
795 }
796 
FillBlack()797 bool C4TexRef::FillBlack()
798 {
799 	// ensure locked
800 	if (!Lock()) return false;
801 	// clear pixels
802 	int y;
803 	for (y=0; y<iSizeY; ++y)
804 	{
805 		for (int x = 0; x < iSizeX; ++x)
806 			SetPix(x, y, 0xff000000);
807 	}
808 	// success
809 	return true;
810 }
811 
812 // texture manager
813 
C4TexMgr()814 C4TexMgr::C4TexMgr()
815 {
816 	// clear textures
817 	Textures.clear();
818 }
819 
~C4TexMgr()820 C4TexMgr::~C4TexMgr()
821 {
822 	// unlock all textures
823 	IntUnlock();
824 }
825 
RegTex(C4TexRef * pTex)826 void C4TexMgr::RegTex(C4TexRef *pTex)
827 {
828 	// add texture to list
829 	Textures.push_front(pTex);
830 }
831 
UnregTex(C4TexRef * pTex)832 void C4TexMgr::UnregTex(C4TexRef *pTex)
833 {
834 	// remove texture from list
835 	Textures.remove(pTex);
836 	// if list is empty, remove self
837 	if (Textures.empty()) { delete this; pTexMgr=nullptr; }
838 }
839 
IntLock()840 void C4TexMgr::IntLock()
841 {
842 	// lock all textures
843 	int j=Textures.size();
844 	for (std::list<C4TexRef *>::iterator i=Textures.begin(); j--; ++i)
845 	{
846 		C4TexRef *pRef = *i;
847 		if (pRef->Lock() && pRef->texLock.pBits)
848 		{
849 			pRef->fIntLock = true;
850 #ifndef USE_CONSOLE
851 			// Release the underlying texture with GL and recreate
852 			// it on unlock, so that the texture survives
853 			// context recreation.
854 			if(pGL)
855 			{
856 				glDeleteTextures(1, &pRef->texName);
857 				pRef->texName = 0;
858 			}
859 #endif
860 		}
861 	}
862 }
863 
IntUnlock()864 void C4TexMgr::IntUnlock()
865 {
866 	// unlock all internally locked textures
867 	int j=Textures.size();
868 	for (std::list<C4TexRef *>::iterator i=Textures.begin(); j--; ++i)
869 	{
870 		C4TexRef *pRef = *i;
871 		if (pRef->fIntLock)
872 		{
873 			pRef->fIntLock = false;
874 			pRef->CreateTexture();
875 			pRef->Unlock();
876 		}
877 	}
878 }
879 
880 C4TexMgr *pTexMgr;
881