/*
** Copyright 1991, Silicon Graphics, Inc.
** All Rights Reserved.
**
** This is UNPUBLISHED PROPRIETARY SOURCE CODE of Silicon Graphics, Inc.;
** the contents of this file may not be disclosed to third parties, copied or
** duplicated in any form, in whole or in part, without the prior written
** permission of Silicon Graphics, Inc.
**
** RESTRICTED RIGHTS LEGEND:
** Use, duplication or disclosure by the Government is subject to restrictions
** as set forth in subdivision (c)(1)(ii) of the Rights in Technical Data
** and Computer Software clause at DFARS 252.227-7013, and/or in similar or
** successor clauses in the FAR, DOD or NASA FAR Supplement. Unpublished -
** rights reserved under the Copyright Laws of the United States.
**
** $Revision: 1.4 $
** $Date: 1993/07/27 17:42:12 $
*/
#include "precomp.h"
#pragma hdrstop

/*
** A simple few routines which saves a cache for specular and spotlight
** computation (rather than recomputing the tables every time the user
** changes the specular or spotlight exponents).
*/

/*
** Any more than TOO_MANY_LUT_ENTRIES entries, and we free any that 
** become unreferenced.
*/
#define TOO_MANY_LUT_ENTRIES	32

typedef struct {
    __GLfloat exp;
    __GLspecLUTEntry *table;
} __GLspecLUTEntryPtr;

typedef struct __GLspecLUTCache_Rec {
    GLint nentries;
    GLint allocatedSize;
    __GLspecLUTEntryPtr entries[1];
} __GLspecLUTCache;

void FASTCALL __glInitLUTCache(__GLcontext *gc)
{
    __GLspecLUTCache *lutCache;

    lutCache = gc->light.lutCache = (__GLspecLUTCache *) 
	    GCALLOC(gc, sizeof(__GLspecLUTCache));
#ifdef NT
    if (!lutCache)
        return;
#endif // NT
    lutCache->nentries = 0;
    lutCache->allocatedSize = 1;
}

void FASTCALL __glFreeLUTCache(__GLcontext *gc)
{
    int i;
    GLint nentries;
    __GLspecLUTEntryPtr *entry;
    __GLspecLUTCache *lutCache;

    lutCache = gc->light.lutCache;
#ifdef NT
    if (!lutCache)
        return;
#endif // NT
    nentries = lutCache->nentries;
    for (i = 0; i < nentries; i++) {
	entry = &(lutCache->entries[i]);
	GCFREE(gc, entry->table);
    }
    GCFREE(gc, lutCache);
}

static __GLspecLUTEntry *findEntry(__GLspecLUTCache *lutCache, __GLfloat exp, 
				   GLint *location)
{
    GLint nentries;
    GLint bottom, half, top;
    __GLspecLUTEntry *table;
    __GLspecLUTEntryPtr *entry;

#ifdef NT
    ASSERTOPENGL(lutCache != NULL, "No LUT cache\n");
#endif // NT
    nentries = lutCache->nentries;
    /* First attempt to find this entry in our cache */
    bottom = 0;
    top = nentries;
    while (top > bottom) {
	/* Entry might exist in the range [bottom, top-1] */
	half = (bottom+top)/2;
	entry = &(lutCache->entries[half]);
	if (entry->exp == exp) {
	    /* Found the table already cached! */
	    table = entry->table;
	    *location = half;
	    return table;
	}
	if (exp < entry->exp) {
	    /* exp might exist somewhere earlier in the table */
	    top = half;
	} else /* exp > entry->exp */ {
	    /* exp might exist somewhere later in the table */
	    bottom = half+1;
	}
    }
    *location = bottom;
    return NULL;
}

__GLspecLUTEntry *__glCreateSpecLUT(__GLcontext *gc, __GLfloat exp)
{
    GLint nentries, allocatedSize;
    GLint location;
    __GLspecLUTCache *lutCache;
    __GLspecLUTEntryPtr *entry;
    __GLspecLUTEntry *table;
    __GLfloat *tableEntry;
    GLdouble threshold, scale, x, dx;
    GLint i;

    /* This code uses double-precision math, so make sure that the FPU */
    /* is set properly: */

    FPU_SAVE_MODE();
    FPU_CHOP_OFF_PREC_HI();

    lutCache = gc->light.lutCache;
#ifdef NT
    if (!lutCache)
        return (__GLspecLUTEntry *)NULL;
#endif // NT

    if (table = findEntry(lutCache, exp, &location)) {
	table->refcount++;
	return table;
    }
    /* 
    ** We failed to find our entry in our cache anywhere, and have to compute 
    ** it.
    */
    lutCache->nentries = nentries = 1 + lutCache->nentries;
    allocatedSize = lutCache->allocatedSize;

    if (nentries > allocatedSize) {
        /* Allocate space for another six entries (arbitrarily) */
        lutCache->allocatedSize = allocatedSize = allocatedSize + 6;
        if (!(lutCache = (__GLspecLUTCache *)
                GCREALLOC(gc, lutCache, sizeof(__GLspecLUTCache) +
                          allocatedSize * sizeof(__GLspecLUTEntryPtr))))
        {
            gc->light.lutCache->allocatedSize -= 6;
            gc->light.lutCache->nentries -= 1;
            return (__GLspecLUTEntry *)NULL;
        }
        gc->light.lutCache = lutCache;
    }

    /*
    ** We have enough space now.  So we stick the new entry in the array
    ** at entry 'location'.  The rest of the entries need to be moved up
    ** (move [location, nentries-2] up to [location+1, nentries-1]).
    */
    if (nentries-location-1) {
#ifdef NT
	__GL_MEMMOVE(&(lutCache->entries[location+1]), 
		&(lutCache->entries[location]),
		(nentries-location-1) * sizeof(__GLspecLUTEntryPtr));
#else
	__GL_MEMCOPY(&(lutCache->entries[location+1]), 
		&(lutCache->entries[location]),
		(nentries-location-1) * sizeof(__GLspecLUTEntryPtr));
#endif
    }
    entry = &(lutCache->entries[location]);
    entry->exp = exp;
    table = entry->table = (__GLspecLUTEntry *) 
	    GCALLOC(gc, sizeof(__GLspecLUTEntry));
#ifdef NT
    if (!table)
        return (__GLspecLUTEntry *)NULL;
#endif // NT

    /* Compute threshold */
    if (exp == (__GLfloat) 0.0) {
	threshold = (GLdouble) 0.0;
    } else {
#ifdef NT
    // Changing this enabled conformance to pass for color index visuals
    // with 4096 colors, and did not seem to affect anything else.
    // What this does is sort of reduce the granularity at the beginning
    // of the table, because without it we were getting too big a jump
    // between 0 and the first entry in the table, causing l_sen.c to fail.
	threshold = (GLdouble) __GL_POWF((__GLfloat) 0.0005, (__GLfloat) 1.0 / exp);
#else
	threshold = __GL_POWF(0.002, 1.0 / exp);
#endif
    }

    scale = (GLdouble) (__GL_SPEC_LOOKUP_TABLE_SIZE - 1) / ((GLdouble) 1.0 - threshold);
    dx = (GLdouble) 1.0 / scale;
    x = threshold;
    tableEntry = table->table;
    for (i = __GL_SPEC_LOOKUP_TABLE_SIZE; --i >= 0; ) {
	*tableEntry++ = __GL_POWF(x, exp);
	x += dx;
    }
    table->threshold = threshold;
    table->scale = scale;
    table->refcount = 1;
    table->exp = exp;

    FPU_RESTORE_MODE();

    return table;
}

void FASTCALL __glFreeSpecLUT(__GLcontext *gc, __GLspecLUTEntry *lut)
{
    __GLspecLUTCache *lutCache;
    GLint location, nentries;
    __GLspecLUTEntry *table;

    if (lut == NULL) return;

    ASSERTOPENGL(lut->refcount != 0, "Invalid refcount\n");

    lut->refcount--;
    if (lut->refcount > 0) return;

    lutCache = gc->light.lutCache;
#ifdef NT
    ASSERTOPENGL(lutCache != NULL, "No LUT cache\n");
#endif // NT

    table = findEntry(lutCache, lut->exp, &location);

    ASSERTOPENGL(table == lut, "Wrong LUT found\n");

    if (table->refcount == 0 && lutCache->nentries >= TOO_MANY_LUT_ENTRIES) {
	/* 
	** Free entry 'location'.
	** This requires reducing lutCache->nentries by one, and copying 
	** entries [location+1, nentries] to [location, nentries-1].
	*/
	lutCache->nentries = nentries = lutCache->nentries - 1;
#ifdef NT
	__GL_MEMMOVE(&(lutCache->entries[location]),
		&(lutCache->entries[location+1]),
		(nentries-location) * sizeof(__GLspecLUTEntryPtr));
#else
	__GL_MEMCOPY(&(lutCache->entries[location]),
		&(lutCache->entries[location+1]),
		(nentries-location) * sizeof(__GLspecLUTEntryPtr));
#endif
	GCFREE(gc, table);
    }
}