640 lines
12 KiB
C
640 lines
12 KiB
C
//Copyright Paul Reiche, Fred Ford. 1992-2002
|
|
|
|
/*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
#include "libs/graphics/cmap.h"
|
|
#include "libs/threadlib.h"
|
|
#include "libs/timelib.h"
|
|
#include "libs/inplib.h"
|
|
#include "libs/strlib.h"
|
|
// for GetStringAddress()
|
|
#include "libs/log.h"
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
|
|
typedef struct xform_control
|
|
{
|
|
int CMapIndex; // -1 means unused
|
|
COLORMAPPTR CMapPtr;
|
|
SIZE Ticks;
|
|
DWORD StartTime;
|
|
DWORD EndTime;
|
|
Color OldCMap[NUMBER_OF_PLUTVALS];
|
|
} XFORM_CONTROL;
|
|
|
|
#define MAX_XFORMS 16
|
|
static struct
|
|
{
|
|
XFORM_CONTROL TaskControl[MAX_XFORMS];
|
|
volatile int Highest;
|
|
// 'pending' is Highest >= 0
|
|
Mutex Lock;
|
|
} XFormControl;
|
|
|
|
static int fadeAmount = FADE_NORMAL_INTENSITY;
|
|
static int fadeDelta;
|
|
static TimeCount fadeStartTime;
|
|
static sint32 fadeInterval;
|
|
|
|
#define SPARE_COLORMAPS 20
|
|
|
|
// Colormaps are rapidly replaced in some parts of the game, so
|
|
// it pays to have some spares on hand
|
|
static TFB_ColorMap *poolhead;
|
|
static int poolcount;
|
|
|
|
static TFB_ColorMap * colormaps[MAX_COLORMAPS];
|
|
static int mapcount;
|
|
Mutex maplock;
|
|
|
|
|
|
void
|
|
InitColorMaps (void)
|
|
{
|
|
int i;
|
|
|
|
// init colormaps
|
|
maplock = CreateMutex ("Colormaps Lock", SYNC_CLASS_TOPLEVEL | SYNC_CLASS_VIDEO);
|
|
|
|
// init xform control
|
|
XFormControl.Highest = -1;
|
|
XFormControl.Lock = CreateMutex ("Transform Lock", SYNC_CLASS_TOPLEVEL | SYNC_CLASS_VIDEO);
|
|
for (i = 0; i < MAX_XFORMS; ++i)
|
|
XFormControl.TaskControl[i].CMapIndex = -1;
|
|
}
|
|
|
|
void
|
|
UninitColorMaps (void)
|
|
{
|
|
TFB_ColorMap *next;
|
|
|
|
// free spares
|
|
for ( ; poolhead; poolhead = next)
|
|
{
|
|
next = poolhead->next;
|
|
HFree (poolhead);
|
|
}
|
|
|
|
// uninit xform control
|
|
DestroyMutex (XFormControl.Lock);
|
|
|
|
// uninit colormaps
|
|
DestroyMutex (maplock);
|
|
}
|
|
|
|
static inline TFB_ColorMap *
|
|
alloc_colormap (void)
|
|
// returns an addrefed object
|
|
{
|
|
TFB_ColorMap *map;
|
|
|
|
if (poolhead)
|
|
{ // have some spares
|
|
map = poolhead;
|
|
poolhead = map->next;
|
|
--poolcount;
|
|
}
|
|
else
|
|
{ // no spares, need a new one
|
|
map = HMalloc (sizeof (*map));
|
|
map->palette = AllocNativePalette ();
|
|
if (!map->palette)
|
|
{
|
|
HFree (map);
|
|
return NULL;
|
|
}
|
|
}
|
|
map->next = NULL;
|
|
map->index = -1;
|
|
map->refcount = 1;
|
|
map->version = 0;
|
|
|
|
return map;
|
|
}
|
|
|
|
static TFB_ColorMap *
|
|
clone_colormap (TFB_ColorMap *from, int index)
|
|
// returns an addrefed object
|
|
{
|
|
TFB_ColorMap *map;
|
|
|
|
map = alloc_colormap ();
|
|
if (!map)
|
|
{
|
|
log_add (log_Warning, "FATAL: clone_colormap(): "
|
|
"could not allocate a map");
|
|
exit (EXIT_FAILURE);
|
|
}
|
|
else
|
|
{ // fresh new map
|
|
map->index = index;
|
|
if (from)
|
|
map->version = from->version;
|
|
}
|
|
map->version++;
|
|
|
|
return map;
|
|
}
|
|
|
|
static inline void
|
|
free_colormap (TFB_ColorMap *map)
|
|
{
|
|
if (!map)
|
|
{
|
|
log_add (log_Warning, "free_colormap(): tried to free a NULL map");
|
|
return;
|
|
}
|
|
|
|
if (poolcount < SPARE_COLORMAPS)
|
|
{ // return to the spare pool
|
|
map->next = poolhead;
|
|
poolhead = map;
|
|
++poolcount;
|
|
}
|
|
else
|
|
{ // don't need any more spares
|
|
FreeNativePalette (map->palette);
|
|
HFree (map);
|
|
}
|
|
}
|
|
|
|
static inline TFB_ColorMap *
|
|
get_colormap (int index)
|
|
{
|
|
TFB_ColorMap *map;
|
|
|
|
map = colormaps[index];
|
|
if (!map)
|
|
{
|
|
log_add (log_Fatal, "BUG: get_colormap(): map not present");
|
|
exit (EXIT_FAILURE);
|
|
}
|
|
|
|
map->refcount++;
|
|
return map;
|
|
}
|
|
|
|
static inline void
|
|
release_colormap (TFB_ColorMap *map)
|
|
{
|
|
if (!map)
|
|
return;
|
|
|
|
if (map->refcount <= 0)
|
|
{
|
|
log_add (log_Warning, "BUG: release_colormap(): refcount not >0");
|
|
return;
|
|
}
|
|
|
|
map->refcount--;
|
|
if (map->refcount == 0)
|
|
free_colormap (map);
|
|
}
|
|
|
|
void
|
|
TFB_ReturnColorMap (TFB_ColorMap *map)
|
|
{
|
|
LockMutex (maplock);
|
|
release_colormap (map);
|
|
UnlockMutex (maplock);
|
|
}
|
|
|
|
TFB_ColorMap *
|
|
TFB_GetColorMap (int index)
|
|
{
|
|
TFB_ColorMap *map;
|
|
|
|
LockMutex (maplock);
|
|
map = get_colormap (index);
|
|
UnlockMutex (maplock);
|
|
|
|
return map;
|
|
}
|
|
|
|
void
|
|
GetColorMapColors (Color *colors, TFB_ColorMap *map)
|
|
{
|
|
int i;
|
|
|
|
if (!map)
|
|
return;
|
|
|
|
for (i = 0; i < NUMBER_OF_PLUTVALS; ++i)
|
|
colors[i] = GetNativePaletteColor (map->palette, i);
|
|
}
|
|
|
|
BOOLEAN
|
|
SetColorMap (COLORMAPPTR map)
|
|
{
|
|
int start, end;
|
|
int total_size;
|
|
UBYTE *colors = (UBYTE*)map;
|
|
TFB_ColorMap **mpp;
|
|
|
|
if (!map)
|
|
return TRUE;
|
|
|
|
start = *colors++;
|
|
end = *colors++;
|
|
if (start > end)
|
|
{
|
|
log_add (log_Warning, "ERROR: SetColorMap(): "
|
|
"starting map (%d) not less or eq ending (%d)",
|
|
start, end);
|
|
return FALSE;
|
|
}
|
|
if (start >= MAX_COLORMAPS)
|
|
{
|
|
log_add (log_Warning, "ERROR: SetColorMap(): "
|
|
"starting map (%d) beyond range (0-%d)",
|
|
start, (int)MAX_COLORMAPS - 1);
|
|
return FALSE;
|
|
}
|
|
if (end >= MAX_COLORMAPS)
|
|
{
|
|
log_add (log_Warning, "SetColorMap(): "
|
|
"ending map (%d) beyond range (0-%d)\n",
|
|
end, (int)MAX_COLORMAPS - 1);
|
|
end = MAX_COLORMAPS - 1;
|
|
}
|
|
|
|
total_size = end + 1;
|
|
|
|
LockMutex (maplock);
|
|
|
|
if (total_size > mapcount)
|
|
mapcount = total_size;
|
|
|
|
// parse the supplied PLUTs into our colormaps
|
|
for (mpp = colormaps + start; start <= end; ++start, ++mpp)
|
|
{
|
|
int i;
|
|
TFB_ColorMap *newmap;
|
|
TFB_ColorMap *oldmap;
|
|
|
|
oldmap = *mpp;
|
|
newmap = clone_colormap (oldmap, start);
|
|
|
|
for (i = 0; i < NUMBER_OF_PLUTVALS; ++i, colors += PLUTVAL_BYTE_SIZE)
|
|
{
|
|
Color color;
|
|
|
|
color.a = 0xff;
|
|
color.r = colors[PLUTVAL_RED];
|
|
color.g = colors[PLUTVAL_GREEN];
|
|
color.b = colors[PLUTVAL_BLUE];
|
|
SetNativePaletteColor (newmap->palette, i, color);
|
|
}
|
|
|
|
*mpp = newmap;
|
|
release_colormap (oldmap);
|
|
}
|
|
|
|
UnlockMutex (maplock);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* Fade Transforms */
|
|
|
|
int
|
|
GetFadeAmount (void)
|
|
{
|
|
int newAmount;
|
|
|
|
LockMutex (XFormControl.Lock);
|
|
|
|
if (fadeInterval)
|
|
{ // have a pending fade
|
|
TimeCount Now = GetTimeCounter ();
|
|
sint32 elapsed;
|
|
|
|
elapsed = Now - fadeStartTime;
|
|
if (elapsed > fadeInterval)
|
|
elapsed = fadeInterval;
|
|
|
|
newAmount = fadeAmount + (long)fadeDelta * elapsed / fadeInterval;
|
|
|
|
if (elapsed >= fadeInterval)
|
|
{ // fade is over
|
|
fadeAmount = newAmount;
|
|
fadeInterval = 0;
|
|
}
|
|
}
|
|
else
|
|
{ // no fade pending, return the current
|
|
newAmount = fadeAmount;
|
|
}
|
|
|
|
UnlockMutex (XFormControl.Lock);
|
|
|
|
return newAmount;
|
|
}
|
|
|
|
static void
|
|
finishPendingFade (void)
|
|
{
|
|
if (fadeInterval)
|
|
{ // end the fade immediately
|
|
fadeAmount += fadeDelta;
|
|
fadeInterval = 0;
|
|
}
|
|
}
|
|
|
|
static void
|
|
FlushFadeXForms (void)
|
|
{
|
|
LockMutex (XFormControl.Lock);
|
|
finishPendingFade ();
|
|
UnlockMutex (XFormControl.Lock);
|
|
}
|
|
|
|
DWORD
|
|
FadeScreen (ScreenFadeType fadeType, SIZE TimeInterval)
|
|
{
|
|
TimeCount TimeOut;
|
|
int FadeEnd;
|
|
|
|
switch (fadeType)
|
|
{
|
|
case FadeAllToBlack:
|
|
case FadeSomeToBlack:
|
|
FadeEnd = FADE_NO_INTENSITY;
|
|
break;
|
|
case FadeAllToColor:
|
|
case FadeSomeToColor:
|
|
FadeEnd = FADE_NORMAL_INTENSITY;
|
|
break;
|
|
case FadeAllToWhite:
|
|
case FadeSomeToWhite:
|
|
FadeEnd = FADE_FULL_INTENSITY;
|
|
break;
|
|
default:
|
|
return (GetTimeCounter ());
|
|
}
|
|
|
|
// Don't make users wait for fades
|
|
if (QuitPosted)
|
|
TimeInterval = 0;
|
|
|
|
LockMutex (XFormControl.Lock);
|
|
|
|
finishPendingFade ();
|
|
|
|
if (TimeInterval <= 0)
|
|
{ // end the fade immediately
|
|
fadeAmount = FadeEnd;
|
|
// cancel any pending fades
|
|
fadeInterval = 0;
|
|
TimeOut = GetTimeCounter ();
|
|
}
|
|
else
|
|
{
|
|
fadeInterval = TimeInterval;
|
|
fadeDelta = FadeEnd - fadeAmount;
|
|
fadeStartTime = GetTimeCounter ();
|
|
TimeOut = fadeStartTime + TimeInterval + 1;
|
|
}
|
|
|
|
UnlockMutex (XFormControl.Lock);
|
|
|
|
return TimeOut;
|
|
}
|
|
|
|
/* Colormap Transforms */
|
|
|
|
static void
|
|
finish_colormap_xform (int which)
|
|
{
|
|
SetColorMap (XFormControl.TaskControl[which].CMapPtr);
|
|
XFormControl.TaskControl[which].CMapIndex = -1;
|
|
// check Highest ptr
|
|
if (which == XFormControl.Highest)
|
|
{
|
|
do
|
|
--which;
|
|
while (which >= 0 && XFormControl.TaskControl[which].CMapIndex == -1);
|
|
|
|
XFormControl.Highest = which;
|
|
}
|
|
}
|
|
|
|
static inline BYTE
|
|
blendChan (BYTE c1, BYTE c2, int weight, int scale)
|
|
{
|
|
return c1 + ((int)c2 - c1) * weight / scale;
|
|
}
|
|
|
|
/* This gives the XFormColorMap task a timeslice to do its thing
|
|
* Only one thread should ever be allowed to be calling this at any time
|
|
*/
|
|
BOOLEAN
|
|
XFormColorMap_step (void)
|
|
{
|
|
BOOLEAN Changed = FALSE;
|
|
int x;
|
|
DWORD Now = GetTimeCounter ();
|
|
|
|
LockMutex (XFormControl.Lock);
|
|
|
|
for (x = 0; x <= XFormControl.Highest; ++x)
|
|
{
|
|
XFORM_CONTROL *control = &XFormControl.TaskControl[x];
|
|
int index = control->CMapIndex;
|
|
int TicksLeft = control->EndTime - Now;
|
|
TFB_ColorMap *curmap;
|
|
|
|
if (index < 0)
|
|
continue; // unused slot
|
|
|
|
LockMutex (maplock);
|
|
|
|
curmap = colormaps[index];
|
|
if (!curmap)
|
|
{
|
|
UnlockMutex (maplock);
|
|
log_add (log_Error, "BUG: XFormColorMap_step(): no current map");
|
|
finish_colormap_xform (x);
|
|
continue;
|
|
}
|
|
|
|
if (TicksLeft > 0)
|
|
{
|
|
#define XFORM_SCALE 0x10000
|
|
TFB_ColorMap *newmap = NULL;
|
|
UBYTE *newClr;
|
|
Color *oldClr;
|
|
int frac;
|
|
int i;
|
|
|
|
newmap = clone_colormap (curmap, index);
|
|
|
|
oldClr = control->OldCMap;
|
|
newClr = (UBYTE*)control->CMapPtr + 2;
|
|
|
|
frac = (int)(control->Ticks - TicksLeft) * XFORM_SCALE
|
|
/ control->Ticks;
|
|
|
|
for (i = 0; i < NUMBER_OF_PLUTVALS; ++i, ++oldClr,
|
|
newClr += PLUTVAL_BYTE_SIZE)
|
|
{
|
|
Color color;
|
|
|
|
color.a = 0xff;
|
|
color.r = blendChan (oldClr->r, newClr[PLUTVAL_RED],
|
|
frac, XFORM_SCALE);
|
|
color.g = blendChan (oldClr->g, newClr[PLUTVAL_GREEN],
|
|
frac, XFORM_SCALE);
|
|
color.b = blendChan (oldClr->b, newClr[PLUTVAL_BLUE],
|
|
frac, XFORM_SCALE);
|
|
SetNativePaletteColor (newmap->palette, i, color);
|
|
}
|
|
|
|
colormaps[index] = newmap;
|
|
release_colormap (curmap);
|
|
}
|
|
|
|
UnlockMutex (maplock);
|
|
|
|
if (TicksLeft <= 0)
|
|
{ // asked for immediate xform or already done
|
|
finish_colormap_xform (x);
|
|
}
|
|
|
|
Changed = TRUE;
|
|
}
|
|
|
|
UnlockMutex (XFormControl.Lock);
|
|
|
|
return Changed;
|
|
}
|
|
|
|
static void
|
|
FlushPLUTXForms (void)
|
|
{
|
|
int i;
|
|
|
|
LockMutex (XFormControl.Lock);
|
|
|
|
for (i = 0; i <= XFormControl.Highest; ++i)
|
|
{
|
|
if (XFormControl.TaskControl[i].CMapIndex >= 0)
|
|
finish_colormap_xform (i);
|
|
}
|
|
XFormControl.Highest = -1; // all gone
|
|
|
|
UnlockMutex (XFormControl.Lock);
|
|
}
|
|
|
|
static DWORD
|
|
XFormPLUT (COLORMAPPTR ColorMapPtr, SIZE TimeInterval)
|
|
{
|
|
TFB_ColorMap *map;
|
|
XFORM_CONTROL *control;
|
|
int index;
|
|
int x;
|
|
int first_avail = -1;
|
|
DWORD EndTime;
|
|
DWORD Now;
|
|
|
|
Now = GetTimeCounter ();
|
|
index = *(UBYTE*)ColorMapPtr;
|
|
|
|
LockMutex (XFormControl.Lock);
|
|
// Find an available slot, or reuse if required
|
|
for (x = 0; x <= XFormControl.Highest
|
|
&& index != XFormControl.TaskControl[x].CMapIndex;
|
|
++x)
|
|
{
|
|
if (first_avail == -1 && XFormControl.TaskControl[x].CMapIndex == -1)
|
|
first_avail = x;
|
|
}
|
|
|
|
if (index == XFormControl.TaskControl[x].CMapIndex)
|
|
{ // already xforming this colormap -- cancel and reuse slot
|
|
finish_colormap_xform (x);
|
|
}
|
|
else if (first_avail >= 0)
|
|
{ // picked up a slot along the way
|
|
x = first_avail;
|
|
}
|
|
else if (x >= MAX_XFORMS)
|
|
{ // flush some xforms if the queue is full
|
|
log_add (log_Debug, "WARNING: XFormPLUT(): no slots available");
|
|
x = XFormControl.Highest;
|
|
finish_colormap_xform (x);
|
|
}
|
|
// take next unused one
|
|
control = &XFormControl.TaskControl[x];
|
|
if (x > XFormControl.Highest)
|
|
XFormControl.Highest = x;
|
|
|
|
// make a copy of the current map
|
|
LockMutex (maplock);
|
|
map = colormaps[index];
|
|
if (!map)
|
|
{
|
|
UnlockMutex (maplock);
|
|
UnlockMutex (XFormControl.Lock);
|
|
log_add (log_Warning, "BUG: XFormPLUT(): no current map");
|
|
return (0);
|
|
}
|
|
GetColorMapColors (control->OldCMap, map);
|
|
UnlockMutex (maplock);
|
|
|
|
control->CMapIndex = index;
|
|
control->CMapPtr = ColorMapPtr;
|
|
control->Ticks = TimeInterval;
|
|
if (control->Ticks < 0)
|
|
control->Ticks = 0; /* prevent negative fade */
|
|
control->StartTime = Now;
|
|
control->EndTime = EndTime = Now + control->Ticks;
|
|
|
|
UnlockMutex (XFormControl.Lock);
|
|
|
|
return (EndTime);
|
|
}
|
|
|
|
DWORD
|
|
XFormColorMap (COLORMAPPTR ColorMapPtr, SIZE TimeInterval)
|
|
{
|
|
if (!ColorMapPtr)
|
|
return (0);
|
|
|
|
// Don't make users wait for transforms
|
|
if (QuitPosted)
|
|
TimeInterval = 0;
|
|
|
|
return XFormPLUT (ColorMapPtr, TimeInterval);
|
|
}
|
|
|
|
void
|
|
FlushColorXForms (void)
|
|
{
|
|
FlushFadeXForms ();
|
|
FlushPLUTXForms ();
|
|
}
|
|
|
|
// The type conversions are implicit and will generate errors
|
|
// or warnings if types change imcompatibly
|
|
COLORMAPPTR
|
|
GetColorMapAddress (COLORMAP colormap)
|
|
{
|
|
return GetStringAddress (colormap);
|
|
}
|