1 //Copyright Paul Reiche, Fred Ford. 1992-2002
2 
3 /*
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17  */
18 
19 #include "libs/graphics/cmap.h"
20 #include "libs/threadlib.h"
21 #include "libs/timelib.h"
22 #include "libs/inplib.h"
23 #include "libs/strlib.h"
24 		// for GetStringAddress()
25 #include "libs/log.h"
26 #include <string.h>
27 #include <stdlib.h>
28 
29 
30 typedef struct xform_control
31 {
32 	int CMapIndex; // -1 means unused
33 	COLORMAPPTR CMapPtr;
34 	SIZE Ticks;
35 	DWORD StartTime;
36 	DWORD EndTime;
37 	Color OldCMap[NUMBER_OF_PLUTVALS];
38 } XFORM_CONTROL;
39 
40 #define MAX_XFORMS 16
41 static struct
42 {
43 	XFORM_CONTROL TaskControl[MAX_XFORMS];
44 	volatile int Highest;
45 			// 'pending' is Highest >= 0
46 	Mutex Lock;
47 } XFormControl;
48 
49 static int fadeAmount = FADE_NORMAL_INTENSITY;
50 static int fadeDelta;
51 static TimeCount fadeStartTime;
52 static sint32 fadeInterval;
53 static Mutex fadeLock;
54 
55 #define SPARE_COLORMAPS  20
56 
57 // Colormaps are rapidly replaced in some parts of the game, so
58 // it pays to have some spares on hand
59 static TFB_ColorMap *poolhead;
60 static int poolcount;
61 
62 static TFB_ColorMap * colormaps[MAX_COLORMAPS];
63 static int mapcount;
64 static Mutex maplock;
65 
66 
67 static void release_colormap (TFB_ColorMap *map);
68 static void delete_colormap (TFB_ColorMap *map);
69 
70 
71 void
InitColorMaps(void)72 InitColorMaps (void)
73 {
74 	int i;
75 
76 	// init colormaps
77 	maplock = CreateMutex ("Colormaps Lock", SYNC_CLASS_TOPLEVEL | SYNC_CLASS_VIDEO);
78 
79 	// init xform control
80 	XFormControl.Highest = -1;
81 	XFormControl.Lock = CreateMutex ("Transform Lock", SYNC_CLASS_TOPLEVEL | SYNC_CLASS_VIDEO);
82 	for (i = 0; i < MAX_XFORMS; ++i)
83 		XFormControl.TaskControl[i].CMapIndex = -1;
84 
85 	fadeLock = CreateMutex ("Fade Lock", SYNC_CLASS_TOPLEVEL | SYNC_CLASS_VIDEO);
86 }
87 
88 void
UninitColorMaps(void)89 UninitColorMaps (void)
90 {
91 	int i;
92 	TFB_ColorMap *next;
93 
94 	for (i = 0; i < MAX_COLORMAPS; ++i)
95 	{
96 		TFB_ColorMap *map = colormaps[i];
97 		if (!map)
98 			continue;
99 		release_colormap (map);
100 		colormaps[i] = 0;
101 	}
102 
103 	// free spares
104 	for ( ; poolhead; poolhead = next, --poolcount)
105 	{
106 		next = poolhead->next;
107 		delete_colormap (poolhead);
108 	}
109 
110 	DestroyMutex (fadeLock);
111 	// uninit xform control
112 	DestroyMutex (XFormControl.Lock);
113 
114 	// uninit colormaps
115 	DestroyMutex (maplock);
116 }
117 
118 static inline TFB_ColorMap *
alloc_colormap(void)119 alloc_colormap (void)
120 		// returns an addrefed object
121 {
122 	TFB_ColorMap *map;
123 
124 	if (poolhead)
125 	{	// have some spares
126 		map = poolhead;
127 		poolhead = map->next;
128 		--poolcount;
129 	}
130 	else
131 	{	// no spares, need a new one
132 		map = HMalloc (sizeof (*map));
133 		map->palette = AllocNativePalette ();
134 		if (!map->palette)
135 		{
136 			HFree (map);
137 			return NULL;
138 		}
139 	}
140 	map->next = NULL;
141 	map->index = -1;
142 	map->refcount = 1;
143 	map->version = 0;
144 
145 	return map;
146 }
147 
148 static TFB_ColorMap *
clone_colormap(TFB_ColorMap * from,int index)149 clone_colormap (TFB_ColorMap *from, int index)
150 		// returns an addrefed object
151 {
152 	TFB_ColorMap *map;
153 
154 	map = alloc_colormap ();
155 	if (!map)
156 	{
157 		log_add (log_Warning, "FATAL: clone_colormap(): "
158 					"could not allocate a map");
159 		exit (EXIT_FAILURE);
160 	}
161 	else
162 	{	// fresh new map
163 		map->index = index;
164 		if (from)
165 			map->version = from->version;
166 	}
167 	map->version++;
168 
169 	return map;
170 }
171 
172 static void
delete_colormap(TFB_ColorMap * map)173 delete_colormap (TFB_ColorMap *map)
174 {
175 	FreeNativePalette (map->palette);
176 	HFree (map);
177 }
178 
179 static inline void
free_colormap(TFB_ColorMap * map)180 free_colormap (TFB_ColorMap *map)
181 {
182 	if (!map)
183 	{
184 		log_add (log_Warning, "free_colormap(): tried to free a NULL map");
185 		return;
186 	}
187 
188 	if (poolcount < SPARE_COLORMAPS)
189 	{	// return to the spare pool
190 		map->next = poolhead;
191 		poolhead = map;
192 		++poolcount;
193 	}
194 	else
195 	{	// don't need any more spares
196 		delete_colormap (map);
197 	}
198 }
199 
200 static inline TFB_ColorMap *
get_colormap(int index)201 get_colormap (int index)
202 {
203 	TFB_ColorMap *map;
204 
205 	map = colormaps[index];
206 	if (!map)
207 	{
208 		log_add (log_Fatal, "BUG: get_colormap(): map not present");
209 		exit (EXIT_FAILURE);
210 	}
211 
212 	map->refcount++;
213 	return map;
214 }
215 
216 static void
release_colormap(TFB_ColorMap * map)217 release_colormap (TFB_ColorMap *map)
218 {
219 	if (!map)
220 		return;
221 
222 	if (map->refcount <= 0)
223 	{
224 		log_add (log_Warning, "BUG: release_colormap(): refcount not >0");
225 		return;
226 	}
227 
228 	map->refcount--;
229 	if (map->refcount == 0)
230 		free_colormap (map);
231 }
232 
233 void
TFB_ReturnColorMap(TFB_ColorMap * map)234 TFB_ReturnColorMap (TFB_ColorMap *map)
235 {
236 	LockMutex (maplock);
237 	release_colormap (map);
238 	UnlockMutex (maplock);
239 }
240 
241 TFB_ColorMap *
TFB_GetColorMap(int index)242 TFB_GetColorMap (int index)
243 {
244 	TFB_ColorMap *map;
245 
246 	LockMutex (maplock);
247 	map = get_colormap (index);
248 	UnlockMutex (maplock);
249 
250 	return map;
251 }
252 
253 void
GetColorMapColors(Color * colors,TFB_ColorMap * map)254 GetColorMapColors (Color *colors, TFB_ColorMap *map)
255 {
256 	int i;
257 
258 	if (!map)
259 		return;
260 
261 	for (i = 0; i < NUMBER_OF_PLUTVALS; ++i)
262 		colors[i] = GetNativePaletteColor (map->palette, i);
263 }
264 
265 BOOLEAN
SetColorMap(COLORMAPPTR map)266 SetColorMap (COLORMAPPTR map)
267 {
268 	int start, end;
269 	int total_size;
270 	UBYTE *colors = (UBYTE*)map;
271 	TFB_ColorMap **mpp;
272 
273 	if (!map)
274 		return TRUE;
275 
276 	start = *colors++;
277 	end = *colors++;
278 	if (start > end)
279 	{
280 		log_add (log_Warning, "ERROR: SetColorMap(): "
281 				"starting map (%d) not less or eq ending (%d)",
282 				start, end);
283 		return FALSE;
284 	}
285 	if (start >= MAX_COLORMAPS)
286 	{
287 		log_add (log_Warning, "ERROR: SetColorMap(): "
288 				"starting map (%d) beyond range (0-%d)",
289 				start, (int)MAX_COLORMAPS - 1);
290 		return FALSE;
291 	}
292 	if (end >= MAX_COLORMAPS)
293 	{
294 		log_add (log_Warning, "SetColorMap(): "
295 				"ending map (%d) beyond range (0-%d)\n",
296 				end, (int)MAX_COLORMAPS - 1);
297 		end = MAX_COLORMAPS - 1;
298 	}
299 
300 	total_size = end + 1;
301 
302 	LockMutex (maplock);
303 
304 	if (total_size > mapcount)
305 		mapcount = total_size;
306 
307 	// parse the supplied PLUTs into our colormaps
308 	for (mpp = colormaps + start; start <= end; ++start, ++mpp)
309 	{
310 		int i;
311 		TFB_ColorMap *newmap;
312 		TFB_ColorMap *oldmap;
313 
314 		oldmap = *mpp;
315 		newmap = clone_colormap (oldmap, start);
316 
317 		for (i = 0; i < NUMBER_OF_PLUTVALS; ++i, colors += PLUTVAL_BYTE_SIZE)
318 		{
319 			Color color;
320 
321 			color.a = 0xff;
322 			color.r = colors[PLUTVAL_RED];
323 			color.g = colors[PLUTVAL_GREEN];
324 			color.b = colors[PLUTVAL_BLUE];
325 			SetNativePaletteColor (newmap->palette, i, color);
326 		}
327 
328 		*mpp = newmap;
329 		release_colormap (oldmap);
330 	}
331 
332 	UnlockMutex (maplock);
333 
334 	return TRUE;
335 }
336 
337 /* Fade Transforms */
338 
339 int
GetFadeAmount(void)340 GetFadeAmount (void)
341 {
342 	int newAmount;
343 
344 	LockMutex (fadeLock);
345 
346 	if (fadeInterval)
347 	{	// have a pending fade
348 		TimeCount Now = GetTimeCounter ();
349 		sint32 elapsed;
350 
351 		elapsed = Now - fadeStartTime;
352 		if (elapsed > fadeInterval)
353 			elapsed = fadeInterval;
354 
355 		newAmount = fadeAmount + (long)fadeDelta * elapsed / fadeInterval;
356 
357 		if (elapsed >= fadeInterval)
358 		{	// fade is over
359 			fadeAmount = newAmount;
360 			fadeInterval = 0;
361 		}
362 	}
363 	else
364 	{	// no fade pending, return the current
365 		newAmount = fadeAmount;
366 	}
367 
368 	UnlockMutex (fadeLock);
369 
370 	return newAmount;
371 }
372 
373 static void
finishPendingFade(void)374 finishPendingFade (void)
375 {
376 	if (fadeInterval)
377 	{	// end the fade immediately
378 		fadeAmount += fadeDelta;
379 		fadeInterval = 0;
380 	}
381 }
382 
383 static void
FlushFadeXForms(void)384 FlushFadeXForms (void)
385 {
386 	LockMutex (fadeLock);
387 	finishPendingFade ();
388 	UnlockMutex (fadeLock);
389 }
390 
391 DWORD
FadeScreen(ScreenFadeType fadeType,SIZE TimeInterval)392 FadeScreen (ScreenFadeType fadeType, SIZE TimeInterval)
393 {
394 	TimeCount TimeOut;
395 	int FadeEnd;
396 
397 	switch (fadeType)
398 	{
399 	case FadeAllToBlack:
400 	case FadeSomeToBlack:
401 		FadeEnd = FADE_NO_INTENSITY;
402 		break;
403 	case FadeAllToColor:
404 	case FadeSomeToColor:
405 		FadeEnd = FADE_NORMAL_INTENSITY;
406 		break;
407 	case FadeAllToWhite:
408 	case FadeSomeToWhite:
409 		FadeEnd = FADE_FULL_INTENSITY;
410 		break;
411 	default:
412 		return (GetTimeCounter ());
413 	}
414 
415 	// Don't make users wait for fades
416 	if (QuitPosted)
417 		TimeInterval = 0;
418 
419 	LockMutex (fadeLock);
420 
421 	finishPendingFade ();
422 
423 	if (TimeInterval <= 0)
424 	{	// end the fade immediately
425 		fadeAmount = FadeEnd;
426 		// cancel any pending fades
427 		fadeInterval = 0;
428 		TimeOut = GetTimeCounter ();
429 	}
430 	else
431 	{
432 		fadeInterval = TimeInterval;
433 		fadeDelta = FadeEnd - fadeAmount;
434 		fadeStartTime = GetTimeCounter ();
435 		TimeOut = fadeStartTime + TimeInterval + 1;
436 	}
437 
438 	UnlockMutex (fadeLock);
439 
440 	return TimeOut;
441 }
442 
443 /* Colormap Transforms */
444 
445 static void
finish_colormap_xform(int which)446 finish_colormap_xform (int which)
447 {
448 	SetColorMap (XFormControl.TaskControl[which].CMapPtr);
449 	XFormControl.TaskControl[which].CMapIndex = -1;
450 	// check Highest ptr
451 	if (which == XFormControl.Highest)
452 	{
453 		do
454 			--which;
455 		while (which >= 0 && XFormControl.TaskControl[which].CMapIndex == -1);
456 
457 		XFormControl.Highest = which;
458 	}
459 }
460 
461 static inline BYTE
blendChan(BYTE c1,BYTE c2,int weight,int scale)462 blendChan (BYTE c1, BYTE c2, int weight, int scale)
463 {
464 	return c1 + ((int)c2 - c1) * weight / scale;
465 }
466 
467 /* This gives the XFormColorMap task a timeslice to do its thing
468  * Only one thread should ever be allowed to be calling this at any time
469  */
470 BOOLEAN
XFormColorMap_step(void)471 XFormColorMap_step (void)
472 {
473 	BOOLEAN Changed = FALSE;
474 	int x;
475 	DWORD Now = GetTimeCounter ();
476 
477 	LockMutex (XFormControl.Lock);
478 
479 	for (x = 0; x <= XFormControl.Highest; ++x)
480 	{
481 		XFORM_CONTROL *control = &XFormControl.TaskControl[x];
482 		int index = control->CMapIndex;
483 		int TicksLeft = control->EndTime - Now;
484 		TFB_ColorMap *curmap;
485 
486 		if (index < 0)
487 			continue; // unused slot
488 
489 		LockMutex (maplock);
490 
491 		curmap = colormaps[index];
492 		if (!curmap)
493 		{
494 			UnlockMutex (maplock);
495 			log_add (log_Error, "BUG: XFormColorMap_step(): no current map");
496 			finish_colormap_xform (x);
497 			continue;
498 		}
499 
500 		if (TicksLeft > 0)
501 		{
502 #define XFORM_SCALE 0x10000
503 			TFB_ColorMap *newmap = NULL;
504 			UBYTE *newClr;
505 			Color *oldClr;
506 			int frac;
507 			int i;
508 
509 			newmap = clone_colormap (curmap, index);
510 
511 			oldClr = control->OldCMap;
512 			newClr = (UBYTE*)control->CMapPtr + 2;
513 
514 			frac = (int)(control->Ticks - TicksLeft) * XFORM_SCALE
515 					/ control->Ticks;
516 
517 			for (i = 0; i < NUMBER_OF_PLUTVALS; ++i, ++oldClr,
518 					newClr += PLUTVAL_BYTE_SIZE)
519 			{
520 				Color color;
521 
522 				color.a = 0xff;
523 				color.r = blendChan (oldClr->r, newClr[PLUTVAL_RED],
524 						frac, XFORM_SCALE);
525 				color.g = blendChan (oldClr->g, newClr[PLUTVAL_GREEN],
526 						frac, XFORM_SCALE);
527 				color.b = blendChan (oldClr->b, newClr[PLUTVAL_BLUE],
528 						frac, XFORM_SCALE);
529 				SetNativePaletteColor (newmap->palette, i, color);
530 			}
531 
532 			colormaps[index] = newmap;
533 			release_colormap (curmap);
534 		}
535 
536 		UnlockMutex (maplock);
537 
538 		if (TicksLeft <= 0)
539 		{	// asked for immediate xform or already done
540 			finish_colormap_xform (x);
541 		}
542 
543 		Changed = TRUE;
544 	}
545 
546 	UnlockMutex (XFormControl.Lock);
547 
548 	return Changed;
549 }
550 
551 static void
FlushPLUTXForms(void)552 FlushPLUTXForms (void)
553 {
554 	int i;
555 
556 	LockMutex (XFormControl.Lock);
557 
558 	for (i = 0; i <= XFormControl.Highest; ++i)
559 	{
560 		if (XFormControl.TaskControl[i].CMapIndex >= 0)
561 			finish_colormap_xform (i);
562 	}
563 	XFormControl.Highest = -1; // all gone
564 
565 	UnlockMutex (XFormControl.Lock);
566 }
567 
568 static DWORD
XFormPLUT(COLORMAPPTR ColorMapPtr,SIZE TimeInterval)569 XFormPLUT (COLORMAPPTR ColorMapPtr, SIZE TimeInterval)
570 {
571 	TFB_ColorMap *map;
572 	XFORM_CONTROL *control;
573 	int index;
574 	int x;
575 	int first_avail = -1;
576 	DWORD EndTime;
577 	DWORD Now;
578 
579 	Now = GetTimeCounter ();
580 	index = *(UBYTE*)ColorMapPtr;
581 
582 	LockMutex (XFormControl.Lock);
583 	// Find an available slot, or reuse if required
584 	for (x = 0; x <= XFormControl.Highest
585 			&& index != XFormControl.TaskControl[x].CMapIndex;
586 			++x)
587 	{
588 		if (first_avail == -1 && XFormControl.TaskControl[x].CMapIndex == -1)
589 			first_avail = x;
590 	}
591 
592 	if (index == XFormControl.TaskControl[x].CMapIndex)
593 	{	// already xforming this colormap -- cancel and reuse slot
594 		finish_colormap_xform (x);
595 	}
596 	else if (first_avail >= 0)
597 	{	// picked up a slot along the way
598 		x = first_avail;
599 	}
600 	else if (x >= MAX_XFORMS)
601 	{	// flush some xforms if the queue is full
602 		log_add (log_Debug, "WARNING: XFormPLUT(): no slots available");
603 		x = XFormControl.Highest;
604 		finish_colormap_xform (x);
605 	}
606 	// take next unused one
607 	control = &XFormControl.TaskControl[x];
608 	if (x > XFormControl.Highest)
609 		XFormControl.Highest = x;
610 
611 	// make a copy of the current map
612 	LockMutex (maplock);
613 	map = colormaps[index];
614 	if (!map)
615 	{
616 		UnlockMutex (maplock);
617 		UnlockMutex (XFormControl.Lock);
618 		log_add (log_Warning, "BUG: XFormPLUT(): no current map");
619 		return (0);
620 	}
621 	GetColorMapColors (control->OldCMap, map);
622 	UnlockMutex (maplock);
623 
624 	control->CMapIndex = index;
625 	control->CMapPtr = ColorMapPtr;
626 	control->Ticks = TimeInterval;
627 	if (control->Ticks < 0)
628 		control->Ticks = 0; /* prevent negative fade */
629 	control->StartTime = Now;
630 	control->EndTime = EndTime = Now + control->Ticks;
631 
632 	UnlockMutex (XFormControl.Lock);
633 
634 	return (EndTime);
635 }
636 
637 DWORD
XFormColorMap(COLORMAPPTR ColorMapPtr,SIZE TimeInterval)638 XFormColorMap (COLORMAPPTR ColorMapPtr, SIZE TimeInterval)
639 {
640 	if (!ColorMapPtr)
641 		return (0);
642 
643 	// Don't make users wait for transforms
644 	if (QuitPosted)
645 		TimeInterval = 0;
646 
647 	return XFormPLUT (ColorMapPtr, TimeInterval);
648 }
649 
650 void
FlushColorXForms(void)651 FlushColorXForms (void)
652 {
653 	FlushFadeXForms ();
654 	FlushPLUTXForms ();
655 }
656 
657 // The type conversions are implicit and will generate errors
658 // or warnings if types change imcompatibly
659 COLORMAPPTR
GetColorMapAddress(COLORMAP colormap)660 GetColorMapAddress (COLORMAP colormap)
661 {
662 	return GetStringAddress (colormap);
663 }
664