1/*
2 * LegacyClonk
3 *
4 * Copyright (c) 1998-2000, Matthes Bender (RedWolf Design)
5 * Copyright (c) 2017-2021, The LegacyClonk Team and contributors
6 *
7 * Distributed under the terms of the ISC license; see accompanying file
8 * "COPYING" for details.
9 *
10 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11 * See accompanying file "TRADEMARK" for details.
12 *
13 * To redistribute this file separately, substitute the full license texts
14 * for the above references.
15 */
16
17/* a wrapper class to DirectDraw surfaces */
18
19#include <C4Config.h>
20#include <C4Group.h>
21#include <C4GroupSet.h>
22#include <C4Log.h>
23#include <C4Surface.h>
24
25#include <Bitmap256.h>
26#include "StdApp.h"
27#include <StdBitmap.h>
28#include <StdGL.h>
29#include <StdJpeg.h>
30#include <StdPNG.h>
31#include "C4ResStrTable.h"
32#include <StdDDraw2.h>
33
34#include <algorithm>
35#include <cstdint>
36#include <memory>
37#include <stdexcept>
38
39C4Surface::C4Surface() : fIsBackground(false)
40{
41 Default();
42}
43
44C4Surface::C4Surface(int iWdt, int iHgt) : fIsBackground(false)
45{
46 Default();
47 // create
48 Create(iWdt, iHgt);
49}
50
51C4Surface::~C4Surface()
52{
53 Clear();
54}
55
56C4Surface::C4Surface(C4Surface &&other) : C4Surface{}
57{
58 *this = std::move(other);
59}
60
61C4Surface &C4Surface::operator=(C4Surface &&other)
62{
63 using std::swap;
64
65#ifndef NDEBUG
66 swap(dbg_idx, other.dbg_idx);
67#endif
68 swap(a&: Wdt, b&: other.Wdt);
69 swap(a&: Hgt, b&: other.Hgt);
70 swap(a&: PrimarySurfaceLockPitch, b&: other.PrimarySurfaceLockPitch);
71 swap(a&: PrimarySurfaceLockBits, b&: other.PrimarySurfaceLockBits);
72 swap(a&: ClipX, b&: other.ClipX);
73 swap(a&: ClipY, b&: other.ClipY);
74 swap(a&: ClipX2, b&: other.ClipX2);
75 swap(a&: ClipY2, b&: other.ClipY2);
76 swap(a&: Locked, b&: other.Locked);
77 swap(a&: fPrimary, b&: other.fPrimary);
78 swap(a&: ppTex, b&: other.ppTex);
79 swap(a&: pMainSfc, b&: other.pMainSfc);
80 swap(a&: ClrByOwnerClr, b&: other.ClrByOwnerClr);
81 swap(a&: iTexSize, b&: other.iTexSize);
82 swap(a&: iTexX, b&: other.iTexX);
83 swap(a&: iTexY, b&: other.iTexY);
84#ifndef USE_CONSOLE
85 swap(a&: Format, b&: other.Format);
86#endif
87 swap(a&: fIsBackground, b&: other.fIsBackground);
88
89 return *this;
90}
91
92void C4Surface::Default()
93{
94 Wdt = Hgt = 0;
95 PrimarySurfaceLockPitch = 0; PrimarySurfaceLockBits = nullptr;
96 ClipX = ClipY = ClipX2 = ClipY2 = 0;
97 Locked = 0;
98 fPrimary = false;
99 ppTex = nullptr;
100 pMainSfc = nullptr;
101 ClrByOwnerClr = 0;
102 iTexSize = iTexX = iTexY = 0;
103 fIsRenderTarget = false;
104 fIsBackground = false;
105#ifndef NDEBUG
106 dbg_idx = nullptr;
107#endif
108}
109
110void C4Surface::Clear()
111{
112 // Undo all locks
113 while (Locked) Unlock();
114 // release surface
115 FreeTextures();
116 ppTex = nullptr;
117#ifndef NDEBUG
118 delete dbg_idx;
119 dbg_idx = nullptr;
120#endif
121}
122
123bool C4Surface::IsRenderTarget()
124{
125 // primary is always OK...
126 return fPrimary;
127}
128
129void C4Surface::NoClip()
130{
131 ClipX = 0; ClipY = 0; ClipX2 = Wdt - 1; ClipY2 = Hgt - 1;
132}
133
134void C4Surface::Clip(int iX, int iY, int iX2, int iY2)
135{
136 ClipX = BoundBy(bval: iX, lbound: 0, rbound: Wdt - 1); ClipY = BoundBy(bval: iY, lbound: 0, rbound: Hgt - 1);
137 ClipX2 = BoundBy(bval: iX2, lbound: 0, rbound: Wdt - 1); ClipY2 = BoundBy(bval: iY2, lbound: 0, rbound: Hgt - 1);
138}
139
140bool C4Surface::Create(int iWdt, int iHgt, bool fOwnPal, bool fIsRenderTarget)
141{
142 Clear(); Default();
143 // check size
144 if (!iWdt || !iHgt) return false;
145 Wdt = iWdt; Hgt = iHgt;
146 // create texture: check gfx system
147 if (!lpDDraw) return false;
148 if (!lpDDraw->DeviceReady()) return false;
149
150 // store color format that will be used
151#ifndef USE_CONSOLE
152 if (pGL)
153 Format = pGL->sfcFmt;
154 else
155#endif
156 /* nothing to do */;
157 this->fIsRenderTarget = fIsRenderTarget;
158 // create textures
159 if (!CreateTextures()) { Clear(); return false; }
160 // update clipping
161 NoClip();
162 // success
163 return true;
164}
165
166bool C4Surface::CreateTextures()
167{
168 // free previous
169 FreeTextures();
170 // get max texture size
171 int iMaxTexSize = 4096;
172#ifndef USE_CONSOLE
173 if (pGL)
174 {
175 GLint iMaxTexSize2 = 0;
176 glGetIntegerv(GL_MAX_TEXTURE_SIZE, params: &iMaxTexSize2);
177 if (iMaxTexSize2 > 0) if (iMaxTexSize2 < iMaxTexSize) iMaxTexSize = iMaxTexSize2;
178 }
179 else
180#endif
181 /* keep standard texture size */;
182 // get needed tex size - begin with smaller value of wdt/hgt, so there won't be too much space wasted
183 int iNeedSize = (std::min)(a: Wdt, b: Hgt); int n = 0; while ((1 << ++n) < iNeedSize); iNeedSize = 1 << n;
184 // adjust to available texture size
185 iTexSize = (std::min)(a: iNeedSize, b: iMaxTexSize);
186 // get the number of textures needed for this size
187 iTexX = (Wdt - 1) / iTexSize + 1;
188 iTexY = (Hgt - 1) / iTexSize + 1;
189 // get mem for texture array
190 ppTex = new C4TexRef *[iTexX * iTexY]{};
191 // cvan't be render target if it's not a single surface
192 if (!IsSingleSurface()) fIsRenderTarget = false;
193 // create textures
194 C4TexRef **ppCTex = ppTex;
195 for (int i = iTexX * iTexY; i; --i, ++ppCTex)
196 {
197 // regular textures or if last texture fits exactly into the space by Wdt or Hgt
198 if (i - 1 || !(Wdt % iTexSize) || !(Hgt % iTexSize))
199 *ppCTex = new C4TexRef(iTexSize, fIsRenderTarget);
200 else
201 {
202 // last texture might be smaller
203 iNeedSize = (std::max)(a: Wdt % iTexSize, b: Hgt % iTexSize);
204 int n = 0; while ((1 << ++n) < iNeedSize); iNeedSize = 1 << n;
205 *ppCTex = new C4TexRef(iNeedSize, fIsRenderTarget);
206 }
207 if (fIsBackground && ppCTex)(*ppCTex)->FillBlack();
208 }
209#ifndef NDEBUG
210 static int dbg_counter = 0;
211 dbg_idx = new int;
212 *dbg_idx = dbg_counter++;
213#endif
214 // success
215 return true;
216}
217
218void C4Surface::FreeTextures()
219{
220 if (ppTex)
221 {
222 // clear all textures
223 C4TexRef **ppTx = ppTex;
224 for (int i = 0; i < iTexX * iTexY; ++i, ++ppTx)
225 delete *ppTx;
226 // clear texture list
227 delete[] ppTex;
228 ppTex = nullptr;
229 }
230}
231
232#define RANGE 255
233#define HLSMAX RANGE
234#define RGBMAX 255
235
236bool ClrByOwner(uint32_t &dwClr) // new style, based on Microsoft Knowledge Base Article - 29240
237{
238 int H, L, S;
239 uint16_t R, G, B;
240 uint8_t cMax, cMin;
241 uint16_t Rdelta, Gdelta, Bdelta;
242 // get RGB (from BGR...?)
243 R = GetBValue(dwClr);
244 G = GetGValue(dwClr);
245 B = GetRValue(dwClr);
246 // calculate lightness
247 cMax = std::max<int>(a: std::max<int>(a: R, b: G), b: B);
248 cMin = std::min<int>(a: std::min<int>(a: R, b: G), b: B);
249 L = (((cMax + cMin) * HLSMAX) + RGBMAX) / (2 * RGBMAX);
250 // achromatic case
251 if (cMax == cMin)
252 {
253 S = 0;
254 H = (HLSMAX * 2 / 3);
255 }
256 // chromatic case
257 else
258 {
259 // saturation
260 if (L <= (HLSMAX / 2))
261 S = (((cMax - cMin) * HLSMAX) + ((cMax + cMin) / 2)) / (cMax + cMin);
262 else
263 S = (((cMax - cMin) * HLSMAX) + ((2 * RGBMAX - cMax - cMin) / 2))
264 / (2 * RGBMAX - cMax - cMin);
265 // hue
266 Rdelta = (((cMax - R) * (HLSMAX / 6)) + ((cMax - cMin) / 2)) / (cMax - cMin);
267 Gdelta = (((cMax - G) * (HLSMAX / 6)) + ((cMax - cMin) / 2)) / (cMax - cMin);
268 Bdelta = (((cMax - B) * (HLSMAX / 6)) + ((cMax - cMin) / 2)) / (cMax - cMin);
269 if (R == cMax)
270 H = Bdelta - Gdelta;
271 else if (G == cMax)
272 H = (HLSMAX / 3) + Rdelta - Bdelta;
273 else
274 H = ((2 * HLSMAX) / 3) + Gdelta - Rdelta;
275 if (H < 0)
276 H += HLSMAX;
277 if (H > HLSMAX)
278 H -= HLSMAX;
279 }
280 // Not blue
281 if (!(Inside(ival: H, lbound: 145, rbound: 175) && (S > 100))) return false;
282 // It's blue: make it gray
283 uint8_t b = GetRValue(dwClr);
284 dwClr = RGB(r: b, g: b, b) | (dwClr & 0xff000000);
285 return true;
286}
287
288bool C4Surface::CreateColorByOwner(C4Surface *pBySurface)
289{
290 // safety
291 if (!pBySurface) return false;
292 if (!pBySurface->ppTex) return false;
293 // create in same size
294 if (!Create(iWdt: pBySurface->Wdt, iHgt: pBySurface->Hgt, fOwnPal: false)) return false;
295 // set main surface
296 pMainSfc = pBySurface;
297 // lock it
298 if (!pMainSfc->Lock()) return false;
299 if (!Lock()) { pMainSfc->Unlock(); return false; }
300 // set ColorByOwner-pixels
301 for (int iY = 0; iY < Hgt; ++iY)
302 for (int iX = 0; iX < Wdt; ++iX)
303 {
304 // get pixel
305 uint32_t dwPix = pMainSfc->GetPixDw(iX, iY, fApplyModulation: false);
306 // is it a ClrByOwner-px?
307 if (!ClrByOwner(dwClr&: dwPix)) continue;
308 // set in this surface
309 SetPixDw(iX, iY, dwCol: dwPix);
310 // clear in the other
311 pMainSfc->SetPixDw(iX, iY, dwCol: 0xffffffff);
312 }
313 // unlock
314 Unlock();
315 pMainSfc->Unlock();
316 // success
317 return true;
318}
319
320bool C4Surface::SetAsClrByOwnerOf(C4Surface *pOfSurface)
321{
322 // safety
323 if (!pOfSurface) return false;
324 if (Wdt != pOfSurface->Wdt || Hgt != pOfSurface->Hgt)
325 return false;
326 // set main surface
327 pMainSfc = pOfSurface;
328 // success
329 return true;
330}
331
332bool C4Surface::AttachSfc(void *sfcSurface)
333{
334 Clear(); Default();
335 fPrimary = true;
336 if (lpDDraw && lpDDraw->pApp)
337 {
338 // use application size
339 Wdt = lpDDraw->pApp->ScreenWidth();
340 Hgt = lpDDraw->pApp->ScreenHeight();
341 }
342 // reset clipping
343 NoClip();
344 return true;
345}
346
347bool C4Surface::Read(C4Group &hGroup, bool fOwnPal)
348{
349 int lcnt, iLineRest;
350 CBitmap256Info BitmapInfo;
351 // read bmpinfo-header
352 if (!hGroup.Read(pBuffer: &BitmapInfo, iSize: sizeof(CBitmapInfo))) return false;
353 // is it 8bpp?
354 if (BitmapInfo.Info.biBitCount == 8)
355 {
356 if (!hGroup.Read(pBuffer: (reinterpret_cast<uint8_t *>(&BitmapInfo)) + sizeof(CBitmapInfo),
357 iSize: (std::min)(a: sizeof(BitmapInfo) - sizeof(CBitmapInfo), b: sizeof(BitmapInfo) - sizeof(CBitmapInfo) + BitmapInfo.FileBitsOffset())))
358 return false;
359 if (!hGroup.Advance(iOffset: BitmapInfo.FileBitsOffset())) return false;
360 }
361 else
362 {
363 // read 24bpp
364 if (BitmapInfo.Info.biBitCount != 24) return false;
365 if (!hGroup.Advance(iOffset: (static_cast<CBitmapInfo>(BitmapInfo)).FileBitsOffset())) return false;
366 }
367
368 // Create and lock surface
369 if (!Create(iWdt: BitmapInfo.Info.biWidth, iHgt: BitmapInfo.Info.biHeight, fOwnPal)) return false;
370 if (!Lock()) { Clear(); return false; }
371
372 // create line buffer
373 int iBufSize = DWordAligned(val: BitmapInfo.Info.biWidth * BitmapInfo.Info.biBitCount / 8);
374 uint8_t *pBuf = new uint8_t[iBufSize];
375 // Read lines
376 iLineRest = DWordAligned(val: BitmapInfo.Info.biWidth) - BitmapInfo.Info.biWidth;
377 for (lcnt = Hgt - 1; lcnt >= 0; lcnt--)
378 {
379 if (!hGroup.Read(pBuffer: pBuf, iSize: iBufSize))
380 {
381 Clear(); delete[] pBuf; return false;
382 }
383 uint8_t *pPix = pBuf;
384 for (int x = 0; x < BitmapInfo.Info.biWidth; ++x)
385 switch (BitmapInfo.Info.biBitCount)
386 {
387 case 8:
388 if (fOwnPal)
389 SetPixDw(iX: x, iY: lcnt, C4RGB(
390 BitmapInfo.Colors[*pPix].rgbRed,
391 BitmapInfo.Colors[*pPix].rgbGreen,
392 BitmapInfo.Colors[*pPix].rgbBlue));
393 else
394 SetPix(iX: x, iY: lcnt, byCol: *pPix);
395 ++pPix;
396 break;
397 case 24:
398 SetPixDw(iX: x, iY: lcnt, C4RGB(pPix[0], pPix[1], pPix[2]));
399 pPix += 3;
400 break;
401 }
402 }
403 // free buffer again
404 delete[] pBuf;
405
406 Unlock();
407
408 return true;
409}
410
411bool C4Surface::SavePNG(const char *szFilename, bool fSaveAlpha, bool fApplyGamma, bool fSaveOverlayOnly, float scale)
412{
413 // Lock - WARNING - maybe locking primary surface here...
414 if (!Lock()) return false;
415
416 if (lpDDraw->Gamma.GetSize() == 0)
417 fApplyGamma = false;
418
419 int realWdt = static_cast<int32_t>(ceilf(x: Wdt * scale));
420 int realHgt = static_cast<int32_t>(ceilf(x: Hgt * scale));
421
422 // Create bitmap
423 StdBitmap bmp(realWdt, realHgt, fSaveAlpha);
424
425 // reset overlay if desired
426 C4Surface *pMainSfcBackup;
427 if (fSaveOverlayOnly) { pMainSfcBackup = pMainSfc; pMainSfc = nullptr; }
428
429#ifndef USE_CONSOLE
430 if (fPrimary && pGL)
431 {
432 // Take shortcut. FIXME: Check Endian
433 for (int y = 0; y < realHgt; ++y)
434 glReadPixels(x: 0, y: realHgt - y, width: realWdt, height: 1, format: fSaveAlpha ? GL_BGRA : GL_BGR, GL_UNSIGNED_BYTE, pixels: bmp.GetPixelAddr(x: 0, y));
435 }
436 else
437#endif
438 {
439 // write pixel values
440 for (int y = 0; y < realHgt; ++y)
441 for (int x = 0; x < realWdt; ++x)
442 {
443 uint32_t dwClr = GetPixDw(iX: x, iY: y, fApplyModulation: false, scale);
444 if (fApplyGamma) dwClr = lpDDraw->Gamma.ApplyTo(dwClr);
445 bmp.SetPixel(x, y, value: dwClr);
446 }
447 }
448
449 // reset overlay
450 if (fSaveOverlayOnly) pMainSfc = pMainSfcBackup;
451
452 // Unlock
453 Unlock();
454
455 // Save bitmap to PNG file
456 try
457 {
458 CPNGFile(szFilename, realWdt, realHgt, fSaveAlpha).Encode(pixels: bmp.GetBytes());
459 }
460 catch (const std::runtime_error &)
461 {
462 return false;
463 }
464
465 // Success
466 return true;
467}
468
469bool C4Surface::Wipe()
470{
471 if (!ppTex) return false;
472 // simply clear it (currently slow...)
473 if (!Lock()) return false;
474 for (int i = 0; i < Wdt * Hgt; ++i)
475 if (!fIsBackground)
476 SetPix(iX: i % Wdt, iY: i / Wdt, byCol: 0);
477 else
478 SetPixDw(iX: i % Wdt, iY: i / Wdt, dwCol: 0x00000000);
479 Unlock();
480 // success
481 return true;
482}
483
484bool C4Surface::GetSurfaceSize(int &irX, int &irY)
485{
486 // simply assign stored values
487 irX = Wdt;
488 irY = Hgt;
489 // success
490 return true;
491}
492
493bool C4Surface::Lock()
494{
495 // lock main sfc
496 if (pMainSfc) if (!pMainSfc->Lock()) return false;
497 // not yet locked?
498 if (!Locked)
499 {
500 if (fPrimary)
501 {
502 // OpenGL:
503 // cannot really lock primary surface, but Get/SetPix will emulate it
504 }
505 else
506 {
507 if (!ppTex) return false;
508 // lock texture
509 // textures will be locked when needed
510 }
511 }
512 // count lock
513 Locked++; return true;
514}
515
516bool C4Surface::LockForUpdate(const C4Rect rect)
517{
518 // texture present?
519 if (!ppTex) return false;
520
521 // clip
522 const auto right = rect.x + rect.Wdt;
523 const auto bottom = rect.y + rect.Hgt;
524 if (rect.x < 0 || rect.y < 0 || right > iTexX * iTexSize || bottom > iTexY * iTexSize)
525 {
526 return false;
527 }
528
529 ++Locked;
530 for (auto tileY = rect.y / iTexSize; ; ++tileY)
531 {
532 const auto tileTop = tileY * iTexSize;
533 C4Rect subRect;
534 subRect.y = std::max<decltype(subRect.y)>(a: rect.y - tileTop, b: 0);
535 subRect.Hgt = std::min<decltype(subRect.Hgt)>(a: bottom - tileTop, b: iTexSize) - subRect.y;
536 for (auto tileX = rect.x / iTexSize; ; ++tileX)
537 {
538 // get texture by pos
539 auto &texRef = *ppTex[tileY * iTexX + tileX];
540
541 const auto tileLeft = tileX * iTexSize;
542 subRect.x = std::max<decltype(subRect.x)>(a: rect.x - tileLeft, b: 0);
543 subRect.Wdt = std::min<decltype(subRect.Wdt)>(a: right - tileLeft, b: iTexSize) - subRect.x;
544
545 if (!texRef.LockForUpdate(rect: subRect))
546 {
547 Unlock();
548 return false;
549 }
550
551 if (tileLeft + iTexSize >= right)
552 break;
553 }
554
555 if (tileTop + iTexSize >= bottom)
556 break;
557 }
558
559 return true;
560}
561
562bool C4Surface::Unlock(bool noUpload)
563{
564 // unlock main sfc
565 if (pMainSfc) pMainSfc->Unlock(noUpload);
566 // locked?
567 if (!Locked) return false;
568 // decrease lock counter; check if zeroed
569 Locked--;
570 if (!Locked)
571 {
572 // zeroed: unlock
573 if (fPrimary)
574 {
575 {
576 // emulated primary locks in OpenGL
577 delete[] static_cast<unsigned char *>(PrimarySurfaceLockBits);
578 PrimarySurfaceLockBits = nullptr;
579 return true;
580 }
581 }
582 else
583 {
584 // non-primary unlock: unlock all texture surfaces (if locked)
585 C4TexRef **ppTx = ppTex;
586 for (int i = 0; i < iTexX * iTexY; ++i, ++ppTx)
587 (*ppTx)->Unlock(noUpload);
588 }
589 }
590 return true;
591}
592
593bool C4Surface::GetTexAt(C4TexRef **ppTexRef, int &rX, int &rY)
594{
595 // texture present?
596 if (!ppTex) return false;
597 // get pos
598 int iX = rX / iTexSize;
599 int iY = rY / iTexSize;
600 // clip
601 if (iX < 0 || iY < 0 || iX >= iTexX || iY >= iTexY) return false;
602 // get texture by pos
603 *ppTexRef = *(ppTex + iY * iTexX + iX);
604 // adjust pos
605 rX -= iX * iTexSize;
606 rY -= iY * iTexSize;
607 // success
608 return true;
609}
610
611bool C4Surface::GetLockTexAt(C4TexRef **ppTexRef, int &rX, int &rY)
612{
613 // texture present?
614 if (!GetTexAt(ppTexRef, rX, rY)) return false;
615 // Already partially locked
616 if ((*ppTexRef)->texLock.pBits)
617 {
618 // But not for the requested pixel
619 const auto &r = (*ppTexRef)->LockSize;
620 if (r.x > rX || r.y > rY || r.x + r.Wdt < rX || r.y + r.Hgt < rY)
621 // Unlock, then relock the whole thing
622 (*ppTexRef)->Unlock();
623 else return true;
624 }
625 // ensure it's locked
626 if (!(*ppTexRef)->Lock()) return false;
627 // success
628 return true;
629}
630
631bool C4Surface::SetPix(int iX, int iY, uint8_t byCol)
632{
633 return SetPixDw(iX, iY, dwCol: lpDDrawPal->GetClr(byCol));
634}
635
636uint32_t C4Surface::GetPixDw(int iX, int iY, bool fApplyModulation, float scale)
637{
638 uint8_t *pBuf; int iPitch;
639 // backup pos
640 int iX2 = iX; int iY2 = iY;
641 // primary?
642 if (fPrimary)
643 {
644#ifndef USE_CONSOLE
645 // OpenGL?
646 if (pGL)
647 {
648 int hgt = static_cast<int32_t>(ceilf(x: Hgt * scale));
649 if (!PrimarySurfaceLockBits)
650 {
651 int wdt = static_cast<int32_t>(ceilf(x: Wdt * scale));
652 wdt = ((wdt + 3) / 4) * 4; // round up to the next multiple of 4
653 PrimarySurfaceLockBits = new unsigned char[wdt * hgt * 3];
654 glReadPixels(x: 0, y: 0, width: wdt, height: hgt, GL_BGR, GL_UNSIGNED_BYTE, pixels: PrimarySurfaceLockBits);
655 PrimarySurfaceLockPitch = wdt * 3;
656 }
657 return *reinterpret_cast<uint32_t *>(PrimarySurfaceLockBits + (hgt - iY - 1) * PrimarySurfaceLockPitch + iX * 3);
658 }
659#endif
660 }
661 else
662 {
663 // get+lock affected texture
664 if (!ppTex) return 0;
665 C4TexRef *pTexRef;
666 if (!GetLockTexAt(ppTexRef: &pTexRef, rX&: iX, rY&: iY)) return 0;
667 pBuf = reinterpret_cast<uint8_t *>(pTexRef->texLock.pBits);
668 iPitch = pTexRef->texLock.Pitch;
669 }
670 // get pix of surface
671 uint32_t dwPix = *reinterpret_cast<uint32_t *>(pBuf + iY * iPitch + iX * 4);
672 // this is a ColorByOwner-surface?
673 if (pMainSfc)
674 {
675 uint8_t byAlpha = uint8_t(dwPix >> 24);
676 // pix is fully transparent?
677 if (byAlpha == 0xff)
678 // then get the main surfaces's pixel
679 dwPix = pMainSfc->GetPixDw(iX: iX2, iY: iY2, fApplyModulation);
680 else
681 {
682 // otherwise, it's a ColorByOwner-pixel: adjust the color
683 if (fApplyModulation)
684 {
685 if (lpDDraw->dwBlitMode & C4GFXBLIT_CLRSFC_MOD2)
686 ModulateClrMOD2(dst&: dwPix, src: ClrByOwnerClr);
687 else
688 ModulateClr(dst&: dwPix, src: ClrByOwnerClr);
689 if (lpDDraw->BlitModulated && !(lpDDraw->dwBlitMode & C4GFXBLIT_CLRSFC_OWNCLR))
690 ModulateClr(dst&: dwPix, src: lpDDraw->BlitModulateClr);
691 }
692 else
693 ModulateClr(dst&: dwPix, src: ClrByOwnerClr);
694 // does it contain transparency? then blit on main sfc
695 if (byAlpha)
696 {
697 uint32_t dwMainPix = pMainSfc->GetPixDw(iX: iX2, iY: iY2, fApplyModulation);
698 BltAlpha(dst&: dwMainPix, src: dwPix); dwPix = dwMainPix;
699 }
700 }
701 }
702 else
703 {
704 // single main surface
705 // apply color modulation if desired
706 if (fApplyModulation && lpDDraw->BlitModulated)
707 {
708 if (lpDDraw->dwBlitMode & C4GFXBLIT_MOD2)
709 ModulateClrMOD2(dst&: dwPix, src: lpDDraw->BlitModulateClr);
710 else
711 ModulateClr(dst&: dwPix, src: lpDDraw->BlitModulateClr);
712 }
713 }
714 // return pixel value
715 return dwPix;
716}
717
718bool C4Surface::IsPixTransparent(int iX, int iY)
719{
720 // get pixel value
721 uint32_t dwPix = GetPixDw(iX, iY, fApplyModulation: false);
722 // get alpha value
723 return (dwPix >> 24) >= 128;
724}
725
726bool C4Surface::SetPixDw(int iX, int iY, uint32_t dwClr)
727{
728 // clip
729 if ((iX < ClipX) || (iX > ClipX2) || (iY < ClipY) || (iY > ClipY2)) return true;
730 // get+lock affected texture
731 if (!ppTex) return false;
732 // if color is fully transparent, ensure it's black
733 if (dwClr >> 24 == 0xff) dwClr = 0xff000000;
734 C4TexRef *pTexRef;
735 if (!GetLockTexAt(ppTexRef: &pTexRef, rX&: iX, rY&: iY)) return false;
736 // ...and set in actual surface
737 pTexRef->SetPix(iX, iY, v: dwClr);
738 // success
739 return true;
740}
741
742bool C4Surface::BltPix(int iX, int iY, C4Surface *sfcSource, int iSrcX, int iSrcY, bool fTransparency)
743{
744 // lock target
745 C4TexRef *pTexRef;
746 if (!GetLockTexAt(ppTexRef: &pTexRef, rX&: iX, rY&: iY)) return false;
747
748 uint32_t *pPix = reinterpret_cast<uint32_t *>(reinterpret_cast<uint8_t *>(pTexRef->texLock.pBits) + iY * pTexRef->texLock.Pitch + iX * 4);
749 // get source pix as dword
750 uint32_t srcPix = sfcSource->GetPixDw(iX: iSrcX, iY: iSrcY, fApplyModulation: true);
751 // merge
752 if (!fTransparency)
753 {
754 // set it
755 *pPix = srcPix;
756 }
757 else
758 {
759 if (lpDDraw->dwBlitMode & C4GFXBLIT_ADDITIVE)
760 BltAlphaAdd(dst&: *pPix, src: srcPix);
761 else
762 BltAlpha(dst&: *pPix, src: srcPix);
763 }
764 // done
765 return true;
766}
767
768void C4Surface::ClearBoxDw(int iX, int iY, int iWdt, int iHgt)
769{
770 // lock
771 if (!Locked) return;
772 // clip to target size
773 if (iX < 0) { iWdt += iX; iX = 0; }
774 if (iY < 0) { iHgt += iY; iY = 0; }
775 int iOver;
776 iOver = Wdt - (iX + iWdt); if (iOver < 0) iWdt += iOver;
777 iOver = Hgt - (iY + iHgt); if (iOver < 0) iHgt += iOver;
778 // get textures involved
779 int iTexX1 = iX / iTexSize;
780 int iTexY1 = iY / iTexSize;
781 int iTexX2 = (std::min)(a: (iX + iWdt - 1) / iTexSize + 1, b: iTexX);
782 int iTexY2 = (std::min)(a: (iY + iHgt - 1) / iTexSize + 1, b: iTexY);
783 // clear basesfc?
784 bool fBaseSfc = false;
785 if (pMainSfc) if (pMainSfc->ppTex) fBaseSfc = true;
786 // clear all these textures
787 for (int y = iTexY1; y < iTexY2; ++y)
788 {
789 for (int x = iTexX1; x < iTexX2; ++x)
790 {
791 C4TexRef *pTex = *(ppTex + y * iTexX + x);
792 // get current offset in texture
793 int iBlitX = iTexSize * x;
794 int iBlitY = iTexSize * y;
795 // get clearing bounds in texture
796 C4Rect rtClear;
797 rtClear.x = std::max(a: iX - iBlitX, b: 0);
798 rtClear.y = std::max(a: iY - iBlitY, b: 0);
799 rtClear.Wdt = std::min(a: iX + iWdt - iBlitX, b: iTexSize) - rtClear.x;
800 rtClear.Hgt = std::min(a: iY + iHgt - iBlitY, b: iTexSize) - rtClear.y;
801 // is there a base-surface to be cleared first?
802 if (fBaseSfc)
803 {
804 // then get this surface as same offset as from other surface
805 // assuming this is only valid as long as there's no texture management,
806 // organizing partially used textures together!
807 C4TexRef *pBaseTex = *(pMainSfc->ppTex + y * iTexX + x);
808 pBaseTex->ClearRect(rect: rtClear);
809 }
810 // clear this texture
811 pTex->ClearRect(rect: rtClear);
812 }
813 }
814}
815
816bool C4Surface::CopyBytes(uint8_t *pImageData)
817{
818 // copy image data directly into textures
819 C4TexRef **ppCurrTex = ppTex, *pTex = *ppTex;
820 int iSrcPitch = Wdt * 4; int iLineTotal = 0;
821 for (int iY = 0; iY < iTexY; ++iY)
822 {
823 uint8_t *pSource = pImageData + iSrcPitch * iLineTotal;
824 int iLastHeight = pTex->iSize; int iXImgPos = 0;
825 for (int iX = 0; iX < iTexX; ++iX)
826 {
827 pTex = *ppCurrTex++;
828 if (!pTex->Lock()) return false;
829 uint8_t *pTarget = reinterpret_cast<uint8_t *>(pTex->texLock.pBits);
830 int iCpyNum = (std::min)(a: pTex->iSize, b: Wdt - iXImgPos) * 4;
831 int iYMax = (std::min)(a: pTex->iSize, b: Hgt - iLineTotal);
832 for (int iLine = 0; iLine < iYMax; ++iLine)
833 {
834 memcpy(dest: pTarget, src: pSource, n: iCpyNum);
835 pSource += iSrcPitch;
836 pTarget += pTex->iSize * 4;
837 }
838 pSource += iCpyNum - iSrcPitch * iYMax;
839 iXImgPos += pTex->iSize;
840 }
841 iLineTotal += iLastHeight;
842 }
843 return true;
844}
845
846bool C4Surface::LoadAny(C4Group &hGroup, const char *szName, bool fOwnPal, bool fNoErrIfNotFound)
847{
848 // Entry name
849 char szFilename[_MAX_FNAME + 1];
850 SCopy(szSource: szName, sTarget: szFilename, _MAX_FNAME);
851 char *szExt = GetExtension(fname: szFilename);
852 if (!*szExt)
853 {
854 // no extension: Default to extension that is found as file in group
855 const char *const extensions[] = { "png", "bmp", "jpeg", "jpg", nullptr };
856 int i = 0; const char *szExt;
857 while (szExt = extensions[i++])
858 {
859 EnforceExtension(szFileName: szFilename, szExtension: szExt);
860 if (hGroup.FindEntry(szWildCard: szFilename)) break;
861 }
862 }
863 // Load surface
864 return Load(hGroup, szFilename, fOwnPal, fNoErrIfNotFound);
865}
866
867bool C4Surface::LoadAny(C4GroupSet &hGroupset, const char *szName, bool fOwnPal, bool fNoErrIfNotFound)
868{
869 // Entry name
870 char szFilename[_MAX_FNAME + 1];
871 SCopy(szSource: szName, sTarget: szFilename, _MAX_FNAME);
872 char *szExt = GetExtension(fname: szFilename);
873 C4Group *pGroup{nullptr};
874 if (!*szExt)
875 {
876 // no extension: Default to extension that is found as file in group
877 const char *const extensions[] = { "png", "bmp", "jpeg", "jpg", nullptr };
878 int i = 0; const char *szExt;
879 while (szExt = extensions[i++])
880 {
881 EnforceExtension(szFileName: szFilename, szExtension: szExt);
882 pGroup = hGroupset.FindEntry(szWildcard: szFilename);
883 if (pGroup) break;
884 }
885 }
886 if (!pGroup) return false;
887 // Load surface
888 return Load(hGroup&: *pGroup, szFilename, fOwnPal, fNoErrIfNotFound);
889}
890
891bool C4Surface::Load(C4Group &hGroup, const char *szFilename, bool fOwnPal, bool fNoErrIfNotFound)
892{
893 if (!hGroup.AccessEntry(szWildCard: szFilename))
894 {
895 // file not found
896 if (!fNoErrIfNotFound) LogNTr(level: spdlog::level::err, fmt: "{}: {}" DirSep "{}", args: LoadResStr(id: C4ResStrTableKey::IDS_PRC_FILENOTFOUND), args: hGroup.GetFullName().getData(), args&: szFilename);
897 return false;
898 }
899 // determine file type by file extension and load accordingly
900 bool fSuccess;
901 if (SEqualNoCase(szStr1: GetExtension(fname: szFilename), szStr2: "png"))
902 fSuccess = !!ReadPNG(hGroup);
903 else if (SEqualNoCase(szStr1: GetExtension(fname: szFilename), szStr2: "jpeg")
904 || SEqualNoCase(szStr1: GetExtension(fname: szFilename), szStr2: "jpg"))
905 fSuccess = ReadJPEG(hGroup);
906 else
907 fSuccess = !!Read(hGroup, fOwnPal);
908 // loading error? log!
909 if (!fSuccess)
910 LogNTr(level: spdlog::level::err, fmt: "{}: {}" DirSep "{}", args: LoadResStr(id: C4ResStrTableKey::IDS_ERR_NOFILE), args: hGroup.GetFullName().getData(), args&: szFilename);
911 // done, success
912 return fSuccess;
913}
914
915bool C4Surface::ReadPNG(C4Group &hGroup)
916{
917 // create mem block
918 int iSize = hGroup.AccessedEntrySize();
919 std::unique_ptr<uint8_t[]> pData(new uint8_t[iSize]);
920 // load file into mem
921 hGroup.Read(pBuffer: pData.get(), iSize);
922 // load as png file
923 std::unique_ptr<StdBitmap> bmp;
924 std::uint32_t width, height; bool useAlpha;
925 try
926 {
927 CPNGFile png(pData.get(), iSize);
928 width = png.Width(); height = png.Height(), useAlpha = png.UsesAlpha();
929 bmp.reset(p: new StdBitmap(width, height, useAlpha));
930 png.Decode(pixels: bmp->GetBytes());
931 }
932 catch (const std::runtime_error &e)
933 {
934 LogNTr(level: spdlog::level::err, fmt: "Could not create surface from PNG file: {}", args: e.what());
935 bmp.reset();
936 }
937 // free file data
938 pData.reset();
939 // abort if loading wasn't successful
940 if (!bmp) return false;
941 // create surface(s) - do not create an 8bit-buffer!
942 if (!Create(iWdt: width, iHgt: height)) return false;
943 // lock for writing data
944 if (!Lock()) return false;
945 if (!ppTex)
946 {
947 Unlock();
948 return false;
949 }
950 // write pixels
951 for (int tY = 0; tY * iTexSize < Hgt; ++tY) for (int tX = 0; tX * iTexSize < Wdt; ++tX)
952 {
953 assert(tX >= 0 && tY >= 0 && tX < iTexX && tY < iTexY);
954 // Get Texture and lock it
955 C4TexRef *pTexRef = *(ppTex + tY * iTexX + tX);
956 if (!pTexRef->Lock()) continue;
957 // At the edges, not the whole texture is used
958 int maxY = (std::min)(a: iTexSize, b: Hgt - tY * iTexSize), maxX = (std::min)(a: iTexSize, b: Wdt - tX * iTexSize);
959 for (int iY = 0; iY < maxY; ++iY)
960 {
961 // The global, not texture-relative position
962 int rY = iY + tY * iTexSize;
963#ifndef __BIG_ENDIAN__
964 if (useAlpha)
965 {
966 // Optimize the easy case of a png in the same format as the display
967 // 32 bit
968 uint32_t *pPix = reinterpret_cast<uint32_t *>((reinterpret_cast<char *>(pTexRef->texLock.pBits)) + iY * pTexRef->texLock.Pitch);
969 memcpy(dest: pPix, src: static_cast<const std::uint32_t *>(bmp->GetPixelAddr32(x: 0, y: rY)) +
970 tX * iTexSize, n: maxX * 4);
971 int iX = maxX;
972 while (iX--) { if (reinterpret_cast<uint8_t *>(pPix)[3] == 0xff) *pPix = 0xff000000; ++pPix; }
973 }
974 else
975#endif
976 {
977 // Loop through every pixel and convert
978 for (int iX = 0; iX < maxX; ++iX)
979 {
980 uint32_t dwCol = bmp->GetPixel(x: iX + tX * iTexSize, y: rY);
981 // if color is fully transparent, ensure it's black
982 if (dwCol >> 24 == 0xff) dwCol = 0xff000000;
983 // set pix in surface
984 uint32_t *pPix = reinterpret_cast<uint32_t *>((reinterpret_cast<char *>(pTexRef->texLock.pBits)) + iY * pTexRef->texLock.Pitch + iX * 4);
985 *pPix = dwCol;
986 }
987 }
988 }
989 pTexRef->Unlock();
990 }
991 // unlock
992 Unlock();
993 // Success
994 return true;
995}
996
997bool C4Surface::SavePNG(C4Group &hGroup, const char *szFilename, bool fSaveAlpha, bool fApplyGamma, bool fSaveOverlayOnly)
998{
999 // Using temporary file at C4Group temp path
1000 char szTemp[_MAX_PATH + 1];
1001 SCopy(szSource: C4Group_GetTempPath(), sTarget: szTemp);
1002 SAppend(szSource: GetFilename(path: szFilename), szTarget: szTemp);
1003 MakeTempFilename(szFileName: szTemp);
1004 // Save to temporary file
1005 if (!SavePNG(szFilename: szTemp, fSaveAlpha, fApplyGamma, fSaveOverlayOnly)) return false;
1006 // Move temp file to group
1007 if (!hGroup.Move(szFile: szTemp, szAddAs: GetFilename(path: szFilename))) return false;
1008 // Success
1009 return true;
1010}
1011
1012bool C4Surface::Copy(C4Surface &fromSfc)
1013{
1014 // Clear anything old
1015 Clear();
1016 // Default to other surface's color depth
1017 Default();
1018 // Create surface
1019 if (!Create(iWdt: fromSfc.Wdt, iHgt: fromSfc.Hgt)) return false;
1020 // Blit copy
1021 if (!lpDDraw->BlitSurface(sfcSurface: &fromSfc, sfcTarget: this, tx: 0, ty: 0, fBlitBase: false))
1022 {
1023 Clear(); return false;
1024 }
1025 // Success
1026 return true;
1027}
1028
1029bool C4Surface::ReadJPEG(C4Group &hGroup)
1030{
1031 // create mem block
1032 size_t size = hGroup.AccessedEntrySize();
1033 unsigned char *pData = new unsigned char[size];
1034 // load file into mem
1035 hGroup.Read(pBuffer: pData, iSize: size);
1036
1037 bool locked = false;
1038 try
1039 {
1040 StdJpeg jpeg(pData, size);
1041 const std::uint32_t width = jpeg.Width(), height = jpeg.Height();
1042
1043 // create surface(s) - do not create an 8bit-buffer!
1044 if (!Create(iWdt: width, iHgt: height)) return false;
1045
1046 // lock for writing data
1047 if (!Lock()) return false;
1048 locked = true;
1049
1050 // put the data in the image
1051 for (std::uint32_t y = 0; y < height; ++y)
1052 {
1053 const auto row = jpeg.DecodeRow();
1054 for (std::uint32_t x = 0; x < width; ++x)
1055 {
1056 const auto pixel = static_cast<const uint8_t *>(row) + x * 3;
1057 SetPixDw(iX: x, iY: y, C4RGB(pixel[0], pixel[1], pixel[2]));
1058 }
1059 }
1060 jpeg.Finish();
1061 }
1062 catch (const std::runtime_error &e)
1063 {
1064 LogNTr(level: spdlog::level::err, fmt: "Could not create surface from JPEG file: {}", args: e.what());
1065 }
1066
1067 // unlock
1068 if (locked) Unlock();
1069 // free data
1070 delete[] pData;
1071 // return if successful
1072 return true;
1073}
1074
1075C4TexRef::C4TexRef(int iSize, bool fSingle) : LockCount{0}
1076{
1077 // zero fields
1078#ifndef USE_CONSOLE
1079 texName = 0;
1080#endif
1081 texLock.pBits = nullptr; fIntLock = false;
1082 // store size
1083 this->iSize = iSize;
1084 // add to texture manager
1085 if (!pTexMgr) pTexMgr = new C4TexMgr();
1086 pTexMgr->RegTex(pTex: this);
1087 // create texture: check ddraw
1088 if (!lpDDraw) return;
1089 if (!lpDDraw->DeviceReady()) return;
1090
1091#ifndef USE_CONSOLE
1092 glPixelStorei(GL_UNPACK_ALIGNMENT, param: 1);
1093
1094 glGenTextures(n: 1, textures: &texName);
1095
1096 if (!texName)
1097 {
1098 throw std::runtime_error{"Could not create texture"};
1099 }
1100
1101 glBindTexture(GL_TEXTURE_2D, texture: texName);
1102 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
1103 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
1104 // Default, changed in PerformBlt if necessary
1105 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
1106 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
1107 glTexImage2D(GL_TEXTURE_2D, level: 0, internalformat: 4, width: iSize, height: iSize, border: 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, pixels: nullptr);
1108#endif
1109
1110 // create mem array for texture creation
1111 texLock.pBits = new unsigned char[iSize * iSize * 4];
1112 texLock.Pitch = iSize * 4;
1113 memset(s: texLock.pBits, c: 0xff, n: texLock.Pitch * iSize);
1114 // Always locked
1115 LockSize = {0, 0, iSize, iSize};
1116}
1117
1118C4TexRef::~C4TexRef()
1119{
1120 fIntLock = false;
1121 // free texture
1122#ifndef USE_CONSOLE
1123 if (pGL && pGL->pCurrCtx)
1124 {
1125 glDeleteTextures(n: 1, textures: &texName);
1126 }
1127#endif
1128 if (lpDDraw) delete[] texLock.pBits; texLock.pBits = nullptr;
1129 // remove from texture manager
1130 pTexMgr->UnregTex(pTex: this);
1131}
1132
1133bool C4TexRef::LockForUpdate(const C4Rect rect)
1134{
1135 // already locked?
1136 if (texLock.pBits)
1137 {
1138 // sufficiently locked?
1139 if (
1140 LockSize.x <= rect.x && LockSize.x + LockSize.Wdt >= rect.x + rect.Wdt &&
1141 LockSize.y <= rect.y && LockSize.y + LockSize.Hgt >= rect.y + rect.Hgt
1142 )
1143 {
1144 return true;
1145 }
1146 else
1147 {
1148 // Commit previous changes to the texture
1149 Unlock();
1150 }
1151 }
1152 // lock
1153#ifndef USE_CONSOLE
1154 if (pGL)
1155 {
1156 // prepare texture data
1157 texLock.pBits = new unsigned char[rect.Wdt * rect.Hgt * 4];
1158 texLock.Pitch = rect.Wdt * 4;
1159 LockSize = rect;
1160 return true;
1161 }
1162 else
1163#endif
1164 {
1165 // nothing to do
1166 }
1167 // failure
1168 return false;
1169}
1170
1171bool C4TexRef::Lock()
1172{
1173 // already locked?
1174 if (texLock.pBits) return true;
1175 LockSize = {0, 0, iSize, iSize};
1176 // lock
1177#ifndef USE_CONSOLE
1178 if (pGL)
1179 {
1180 // select context, if not already done
1181 if (!pGL->pCurrCtx) if (!pGL->MainCtx.Select()) return false;
1182 // get texture
1183 texLock.pBits = new unsigned char[iSize * iSize * 4];
1184 texLock.Pitch = iSize * 4;
1185 glBindTexture(GL_TEXTURE_2D, texture: texName);
1186 glGetTexImage(GL_TEXTURE_2D, level: 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, pixels: texLock.pBits);
1187 ++LockCount;
1188 return true;
1189 }
1190 else
1191#endif
1192 {
1193 // nothing to do
1194 }
1195 // failure
1196 return false;
1197}
1198
1199void C4TexRef::Unlock([[maybe_unused]] bool noUpload)
1200{
1201 // locked?
1202 if (!texLock.pBits || fIntLock) return;
1203#ifndef USE_CONSOLE
1204 if (pGL)
1205 {
1206 if (!noUpload)
1207 {
1208 // select context, if not already done
1209 if (!pGL->pCurrCtx) if (!pGL->MainCtx.Select()) return;
1210 glPixelStorei(GL_UNPACK_ALIGNMENT, param: 1);
1211 glBindTexture(GL_TEXTURE_2D, texture: texName);
1212 glTexSubImage2D(GL_TEXTURE_2D, level: 0,
1213 xoffset: LockSize.x, yoffset: LockSize.y, width: LockSize.Wdt, height: LockSize.Hgt,
1214 GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, pixels: texLock.pBits);
1215 }
1216 if (!noUpload || Config.Graphics.CacheTexturesInRAM == -1 || LockCount < Config.Graphics.CacheTexturesInRAM)
1217 {
1218 delete[] texLock.pBits; texLock.pBits = nullptr;
1219 }
1220 // switch back to original context
1221 }
1222 else
1223#endif
1224 {
1225 // nothing to do
1226 }
1227}
1228
1229bool C4TexRef::ClearRect(const C4Rect rect)
1230{
1231 // ensure locked
1232 if (!LockForUpdate(rect)) return false;
1233 // clear pixels
1234 for (int y = rect.y; y < rect.y + rect.Hgt; ++y)
1235 {
1236 for (int x = rect.x; x < rect.x + rect.Wdt; ++x)
1237 SetPix(iX: x, iY: y, v: 0xff000000);
1238 }
1239 // success
1240 return true;
1241}
1242
1243bool C4TexRef::FillBlack()
1244{
1245 // ensure locked
1246 if (!Lock()) return false;
1247 // clear pixels
1248 std::fill_n(first: reinterpret_cast<std::uint32_t *>(texLock.pBits), n: iSize * iSize, value: 0);
1249 // success
1250 return true;
1251}
1252
1253// texture manager
1254
1255C4TexMgr::C4TexMgr()
1256{
1257 // clear textures
1258 Textures.clear();
1259}
1260
1261C4TexMgr::~C4TexMgr()
1262{
1263 // unlock all textures
1264 IntUnlock();
1265}
1266
1267void C4TexMgr::RegTex(C4TexRef *pTex)
1268{
1269 // add texture to list
1270 Textures.push_front(x: pTex);
1271}
1272
1273void C4TexMgr::UnregTex(C4TexRef *pTex)
1274{
1275 // remove texture from list
1276 Textures.remove(value: pTex);
1277 // if list is empty, remove self
1278 if (Textures.empty()) { delete this; pTexMgr = nullptr; }
1279}
1280
1281void C4TexMgr::IntLock()
1282{
1283 // lock all textures
1284 int j = Textures.size();
1285 for (std::list<C4TexRef *>::iterator i = Textures.begin(); j--; ++i)
1286 {
1287 C4TexRef *pRef = *i;
1288 if (pRef->Lock() && !pRef->texLock.pBits) pRef->fIntLock = true;
1289 }
1290}
1291
1292void C4TexMgr::IntUnlock()
1293{
1294 // unlock all internally locked textures
1295 int j = Textures.size();
1296 for (std::list<C4TexRef *>::iterator i = Textures.begin(); j--; ++i)
1297 {
1298 C4TexRef *pRef = *i;
1299 if (pRef->fIntLock) { pRef->fIntLock = false; pRef->Unlock(); }
1300 }
1301}
1302
1303C4TexMgr *pTexMgr;
1304const uint8_t FColors[] = { 31, 16, 39, 47, 55, 63, 71, 79, 87, 95, 23, 30, 99, 103 };
1305