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