1 #include "Debug.h"
2 #include "HImage.h"
3 #include "MemMan.h"
4 #include "VObject.h"
5 #include "VObject_Blitters.h"
6 #include "VSurface.h"
7 
8 #include <string_theory/format>
9 #include <string_theory/string>
10 
11 #include <algorithm>
12 #include <iterator>
13 #include <stdexcept>
14 
15 // ******************************************************************************
16 //
17 // Video Object SGP Module
18 //
19 // Video Objects are used to contain any imagery which requires blitting. The data
20 // is contained within a Direct Draw surface. Palette information is in both
21 // a Direct Draw Palette and a 16BPP palette structure for 8->16 BPP Blits.
22 // Blitting is done via Direct Draw as well as custum blitters. Regions are
23 // used to define local coordinates within the surface
24 //
25 // Second Revision: Dec 10, 1996, Andrew Emmons
26 //
27 // *******************************************************************************
28 
29 
30 static SGPVObject* gpVObjectHead = 0;
31 
32 
SGPVObject(SGPImage const * const img)33 SGPVObject::SGPVObject(SGPImage const* const img) :
34 	flags_(),
35 	palette16_(),
36 	current_shade_(),
37 	ppZStripInfo(),
38 #ifdef SGP_VIDEO_DEBUGGING
39 	name_(),
40 	code_(),
41 #endif
42 	next_(gpVObjectHead)
43 {
44 	std::fill(std::begin(pShades), std::end(pShades), nullptr);
45 
46 	if (!(img->fFlags & IMAGE_TRLECOMPRESSED))
47 	{
48 		throw std::runtime_error("Image for video object creation must be TRLE compressed");
49 	}
50 
51 	ETRLEData TempETRLEData;
52 	GetETRLEImageData(img, &TempETRLEData);
53 
54 	subregion_count_ = TempETRLEData.usNumberOfObjects;
55 	etrle_object_    = TempETRLEData.pETRLEObject;
56 	pix_data_        = static_cast<UINT8*>(TempETRLEData.pPixData);
57 	pix_data_size_   = TempETRLEData.uiSizePixData;
58 	bit_depth_       = img->ubBitDepth;
59 
60 	if (img->ubBitDepth == 8)
61 	{
62 		// create palette
63 		const SGPPaletteEntry* const src_pal = img->pPalette;
64 		Assert(src_pal != NULL);
65 
66 		SGPPaletteEntry* const pal = palette_.Allocate(256);
67 		memcpy(pal, src_pal, sizeof(*pal) * 256);
68 
69 		palette16_     = Create16BPPPalette(pal);
70 		current_shade_ = palette16_;
71 	}
72 
73 	gpVObjectHead = this;
74 #ifdef SGP_VIDEO_DEBUGGING
75 	++guiVObjectSize;
76 #endif
77 }
78 
79 
~SGPVObject()80 SGPVObject::~SGPVObject()
81 {
82 	for (SGPVObject** anchor = &gpVObjectHead;; anchor = &(*anchor)->next_)
83 	{
84 		if (*anchor != this) continue;
85 		*anchor = next_;
86 #ifdef SGP_VIDEO_DEBUGGING
87 		--guiVObjectSize;
88 #endif
89 		break;
90 	}
91 
92 	DestroyPalettes();
93 
94 	if (pix_data_)     delete[] pix_data_;
95 	if (etrle_object_) delete[] etrle_object_;
96 
97 	if (ppZStripInfo != NULL)
98 	{
99 		for (UINT32 usLoop = 0; usLoop < SubregionCount(); usLoop++)
100 		{
101 			if (ppZStripInfo[usLoop] != NULL)
102 			{
103 				delete[] ppZStripInfo[usLoop]->pbZChange;
104 				delete ppZStripInfo[usLoop];
105 			}
106 		}
107 		delete[] ppZStripInfo;
108 	}
109 
110 #ifdef SGP_VIDEO_DEBUGGING
111 	if (name_) delete[] name_;
112 	if (code_) delete[] code_;
113 #endif
114 }
115 
116 
CurrentShade(size_t const idx)117 void SGPVObject::CurrentShade(size_t const idx)
118 {
119 	if (idx >= lengthof(pShades) || !pShades[idx])
120 	{
121 		throw std::logic_error("Tried to set invalid video object shade");
122 	}
123 	current_shade_ = pShades[idx];
124 }
125 
126 
SubregionProperties(size_t const idx) const127 ETRLEObject const& SGPVObject::SubregionProperties(size_t const idx) const
128 {
129 	if (idx >= SubregionCount())
130 	{
131 		throw std::logic_error("Tried to access invalid subregion in video object");
132 	}
133 	return etrle_object_[idx];
134 }
135 
136 
PixData(ETRLEObject const & e) const137 UINT8 const* SGPVObject::PixData(ETRLEObject const& e) const
138 {
139 	return &pix_data_[e.uiDataOffset];
140 }
141 
142 
143 #define COMPRESS_TRANSPARENT 0x80
144 #define COMPRESS_RUN_MASK    0x7F
145 
146 
GetETRLEPixelValue(UINT16 const usETRLEIndex,UINT16 const usX,UINT16 const usY) const147 UINT8 SGPVObject::GetETRLEPixelValue(UINT16 const usETRLEIndex, UINT16 const usX, UINT16 const usY) const
148 {
149 	ETRLEObject const& pETRLEObject = SubregionProperties(usETRLEIndex);
150 
151 	if (usX >= pETRLEObject.usWidth || usY >= pETRLEObject.usHeight)
152 	{
153 		throw std::logic_error("Tried to get pixel from invalid coordinate");
154 	}
155 
156 	// Assuming everything's okay, go ahead and look...
157 	UINT8 const* pCurrent = PixData(pETRLEObject);
158 
159 	// Skip past all uninteresting scanlines
160 	for (UINT16 usLoopY = 0; usLoopY < usY; usLoopY++)
161 	{
162 		while (*pCurrent != 0)
163 		{
164 			if (*pCurrent & COMPRESS_TRANSPARENT)
165 			{
166 				pCurrent++;
167 			}
168 			else
169 			{
170 				pCurrent += *pCurrent & COMPRESS_RUN_MASK;
171 			}
172 		}
173 	}
174 
175 	// Now look in this scanline for the appropriate byte
176 	UINT16 usLoopX = 0;
177 	do
178 	{
179 		UINT16 ubRunLength = *pCurrent & COMPRESS_RUN_MASK;
180 
181 		if (*pCurrent & COMPRESS_TRANSPARENT)
182 		{
183 			if (usLoopX + ubRunLength >= usX) return 0;
184 			pCurrent++;
185 		}
186 		else
187 		{
188 			if (usLoopX + ubRunLength >= usX)
189 			{
190 				// skip to the correct byte; skip at least 1 to get past the byte defining the run
191 				pCurrent += (usX - usLoopX) + 1;
192 				return *pCurrent;
193 			}
194 			else
195 			{
196 				pCurrent += ubRunLength + 1;
197 			}
198 		}
199 		usLoopX += ubRunLength;
200 	}
201 	while (usLoopX < usX);
202 
203 	throw std::logic_error("Inconsistent video object data");
204 }
205 
206 
207 /* Destroys the palette tables of a video object. All memory is deallocated, and
208  * the pointers set to NULL. Be careful not to try and blit this object until
209  * new tables are calculated, or things WILL go boom. */
DestroyPalettes()210 void SGPVObject::DestroyPalettes()
211 {
212 	FOR_EACH(UINT16*, i, pShades)
213 	{
214 		if (flags_ & SHADETABLE_SHARED) continue;
215 		UINT16* const p = *i;
216 		if (!p)                         continue;
217 		if (palette16_ == p) palette16_ = 0;
218 		*i = 0;
219 		delete[] p;
220 	}
221 
222 	if (UINT16* const p = palette16_)
223 	{
224 		palette16_ = 0;
225 		delete[] p;
226 	}
227 
228 	current_shade_ = 0;
229 }
230 
231 
ShareShadetables(SGPVObject * const other)232 void SGPVObject::ShareShadetables(SGPVObject* const other)
233 {
234 	flags_ |= SHADETABLE_SHARED;
235 	for (size_t i = 0; i < lengthof(pShades); ++i)
236 	{
237 		pShades[i] = other->pShades[i];
238 	}
239 }
240 
241 
InitializeVideoObjectManager(void)242 void InitializeVideoObjectManager(void)
243 {
244 	//Shouldn't be calling this if the video object manager already exists.
245 	//Call shutdown first...
246 	Assert(gpVObjectHead == NULL);
247 	gpVObjectHead = NULL;
248 }
249 
250 
ShutdownVideoObjectManager(void)251 void ShutdownVideoObjectManager(void)
252 {
253 	while (gpVObjectHead)
254 	{
255 		delete gpVObjectHead;
256 	}
257 }
258 
259 
260 #ifdef SGP_VIDEO_DEBUGGING
261 static
262 #endif
AddStandardVideoObjectFromHImage(SGPImage * const img)263 SGPVObject* AddStandardVideoObjectFromHImage(SGPImage* const img)
264 {
265 	return new SGPVObject(img);
266 }
267 
268 
269 #ifdef SGP_VIDEO_DEBUGGING
270 static
271 #endif
AddStandardVideoObjectFromFile(const char * const ImageFile)272 SGPVObject* AddStandardVideoObjectFromFile(const char* const ImageFile)
273 {
274 	AutoSGPImage hImage(CreateImage(ImageFile, IMAGE_ALLIMAGEDATA));
275 	return AddStandardVideoObjectFromHImage(hImage.get());
276 }
277 
278 
BltVideoObject(SGPVSurface * const dst,SGPVObject const * const src,UINT16 const usRegionIndex,INT32 const iDestX,INT32 const iDestY)279 void BltVideoObject(SGPVSurface* const dst, SGPVObject const* const src, UINT16 const usRegionIndex, INT32 const iDestX, INT32 const iDestY)
280 {
281 	Assert(src->BPP() ==  8);
282 	Assert(dst->BPP() == 16);
283 
284 	SGPVSurface::Lock l(dst);
285 	UINT16* const pBuffer = l.Buffer<UINT16>();
286 	UINT32  const uiPitch = l.Pitch();
287 
288 	if (BltIsClipped(src, iDestX, iDestY, usRegionIndex, &ClippingRect))
289 	{
290 		Blt8BPPDataTo16BPPBufferTransparentClip(pBuffer, uiPitch, src, iDestX, iDestY, usRegionIndex, &ClippingRect);
291 	}
292 	else
293 	{
294 		Blt8BPPDataTo16BPPBufferTransparent(pBuffer, uiPitch, src, iDestX, iDestY, usRegionIndex);
295 	}
296 }
297 
298 
BltVideoObjectOutline(SGPVSurface * const dst,SGPVObject const * const hSrcVObject,UINT16 const usIndex,INT32 const iDestX,INT32 const iDestY,INT16 const s16BPPColor)299 void BltVideoObjectOutline(SGPVSurface* const dst, SGPVObject const* const hSrcVObject, UINT16 const usIndex, INT32 const iDestX, INT32 const iDestY, INT16 const s16BPPColor)
300 {
301 	SGPVSurface::Lock l(dst);
302 	UINT16* const pBuffer = l.Buffer<UINT16>();
303 	UINT32  const uiPitch = l.Pitch();
304 
305 	if (BltIsClipped(hSrcVObject, iDestX, iDestY, usIndex, &ClippingRect))
306 	{
307 		Blt8BPPDataTo16BPPBufferOutlineClip(pBuffer, uiPitch, hSrcVObject, iDestX, iDestY, usIndex, s16BPPColor, &ClippingRect);
308 	}
309 	else
310 	{
311 		Blt8BPPDataTo16BPPBufferOutline(pBuffer, uiPitch, hSrcVObject, iDestX, iDestY, usIndex, s16BPPColor);
312 	}
313 }
314 
315 
BltVideoObjectOutlineShadow(SGPVSurface * const dst,const SGPVObject * const src,const UINT16 usIndex,const INT32 iDestX,const INT32 iDestY)316 void BltVideoObjectOutlineShadow(SGPVSurface* const dst, const SGPVObject* const src, const UINT16 usIndex, const INT32 iDestX, const INT32 iDestY)
317 {
318 	SGPVSurface::Lock l(dst);
319 	UINT16* const pBuffer = l.Buffer<UINT16>();
320 	UINT32  const uiPitch = l.Pitch();
321 
322 	if (BltIsClipped(src, iDestX, iDestY, usIndex, &ClippingRect))
323 	{
324 		Blt8BPPDataTo16BPPBufferOutlineShadowClip(pBuffer, uiPitch, src, iDestX, iDestY, usIndex, &ClippingRect);
325 	}
326 	else
327 	{
328 		Blt8BPPDataTo16BPPBufferOutlineShadow(pBuffer, uiPitch, src, iDestX, iDestY, usIndex);
329 	}
330 }
331 
332 
BltVideoObjectOnce(SGPVSurface * const dst,char const * const filename,UINT16 const region,INT32 const x,INT32 const y)333 void BltVideoObjectOnce(SGPVSurface* const dst, char const* const filename, UINT16 const region, INT32 const x, INT32 const y)
334 {
335 	AutoSGPVObject vo(AddVideoObjectFromFile(filename));
336 	BltVideoObject(dst, vo.get(), region, x, y);
337 }
338 
339 
340 #ifdef SGP_VIDEO_DEBUGGING
341 
342 UINT32 guiVObjectSize = 0;
343 
344 
345 struct DUMPINFO
346 {
347 	UINT32 Counter;
348 	char Name[256];
349 	char Code[256];
350 };
351 
352 
DumpVObjectInfoIntoFile(const char * filename,BOOLEAN fAppend)353 static void DumpVObjectInfoIntoFile(const char* filename, BOOLEAN fAppend)
354 {
355 	if (guiVObjectSize == 0) return;
356 
357 	RustPointer<File> file(File_open(filename, fAppend ? FILE_OPEN_APPEND : FILE_OPEN_WRITE));
358 	if (!file)
359 	{
360 		RustPointer<char> err(getRustError());
361 		SLOGA("DumpVObjectInfoIntoFile: %s", err.get());
362 		return;
363 	}
364 
365 	//Allocate enough strings and counters for each node.
366 	DUMPINFO* const Info = new DUMPINFO[guiVObjectSize]{};
367 
368 	//Loop through the list and record every unique filename and count them
369 	UINT32 uiUniqueID = 0;
370 	for (SGPVObject const* i = gpVObjectHead; i; i = i->next_)
371 	{
372 		char const* const Name = i->name_;
373 		char const* const Code = i->code_;
374 		BOOLEAN fFound = FALSE;
375 		for (UINT32 i = 0; i < uiUniqueID; i++)
376 		{
377 			if (strcasecmp(Name, Info[i].Name) == 0 && strcasecmp(Code, Info[i].Code) == 0)
378 			{ //same string
379 				fFound = TRUE;
380 				Info[i].Counter++;
381 				break;
382 			}
383 		}
384 		if (!fFound)
385 		{
386 			strcpy(Info[uiUniqueID].Name, Name);
387 			strcpy(Info[uiUniqueID].Code, Code);
388 			Info[uiUniqueID].Counter++;
389 			uiUniqueID++;
390 		}
391 	}
392 
393 	//Now dump the info.
394 	ST::string buf;
395 	buf += "-----------------------------------------------\n";
396 	buf += ST::format(ST::substitute_invalid, "{} unique vObject names exist in {} VObjects\n", uiUniqueID, guiVObjectSize);
397 	buf += "-----------------------------------------------\n\n";
398 	for (UINT32 i = 0; i < uiUniqueID; i++)
399 	{
400 		buf += ST::format(ST::substitute_invalid, "{} occurrences of {}\n{}\n\n", Info[i].Counter, Info[i].Name, Info[i].Code);
401 	}
402 	buf += "\n-----------------------------------------------\n\n";
403 
404 	//Free all memory associated with this operation.
405 	delete[] Info;
406 	if (!File_writeAll(file.get(), reinterpret_cast<const uint8_t*>(buf.c_str()), buf.size()))
407 	{
408 		RustPointer<char> err(getRustError());
409 		SLOGW("DumpVObjectInfoIntoFile: %s", err.get());
410 	}
411 }
412 
413 
414 //Debug wrapper for adding vObjects
RecordVObject(SGPVObject * const vo,const char * Filename,UINT32 uiLineNum,const char * pSourceFile)415 static void RecordVObject(SGPVObject* const vo, const char* Filename, UINT32 uiLineNum, const char* pSourceFile)
416 {
417 	//record the filename of the vObject (some are created via memory though)
418 	vo->name_ = new char[strlen(Filename) + 1]{};
419 	strcpy(vo->name_, Filename);
420 
421 	//record the code location of the calling creating function.
422 	char str[256];
423 	sprintf(str, "%s -- line(%d)", pSourceFile, uiLineNum);
424 	vo->code_ = new char[strlen(str) + 1]{};
425 	strcpy(vo->code_, str);
426 }
427 
428 
AddAndRecordVObjectFromHImage(SGPImage * const img,UINT32 uiLineNum,const char * pSourceFile)429 SGPVObject* AddAndRecordVObjectFromHImage(SGPImage* const img, UINT32 uiLineNum, const char* pSourceFile)
430 {
431 	SGPVObject* const vo = AddStandardVideoObjectFromHImage(img);
432 	RecordVObject(vo, "<IMAGE>", uiLineNum, pSourceFile);
433 	return vo;
434 }
435 
436 
AddAndRecordVObjectFromFile(const char * ImageFile,UINT32 uiLineNum,const char * pSourceFile)437 SGPVObject* AddAndRecordVObjectFromFile(const char* ImageFile, UINT32 uiLineNum, const char* pSourceFile)
438 {
439 	SGPVObject* const vo = AddStandardVideoObjectFromFile(ImageFile);
440 	RecordVObject(vo, ImageFile, uiLineNum, pSourceFile);
441 	return vo;
442 }
443 
444 
PerformVideoInfoDumpIntoFile(const char * filename,BOOLEAN fAppend)445 void PerformVideoInfoDumpIntoFile(const char* filename, BOOLEAN fAppend)
446 {
447 	DumpVObjectInfoIntoFile(filename, fAppend);
448 	DumpVSurfaceInfoIntoFile(filename, TRUE);
449 }
450 
451 #endif
452