/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

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 3 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, see <http://www.gnu.org/licenses/>.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/

//#define DYNAHEADER_CREATE_STORAGE

#include "hsTypes.h"

#include <time.h>

#include "hsWindows.h"

#include "hsG3DDeviceSelector.h"
#include "hsStream.h"
#include "hsUtils.h"
#include "plPipeline.h"

#ifdef HS_OPEN_GL
#if HS_BUILD_FOR_WIN32
#include <gls.h>
#include <glswgl.h>
#include <glext.h>
#endif
#endif

///////////////////////////////////////////////////
///////////////////////////////////////////////////
///////////////////////////////////////////////////
hsG3DDeviceMode::hsG3DDeviceMode()
:   fWidth(0), fHeight(0), 
    fDepth(0),
    fFlags(kNone)
{
}

hsG3DDeviceMode::~hsG3DDeviceMode()
{
    Clear();
}

hsBool hsG3DDeviceMode::operator< (const hsG3DDeviceMode &mode) const
{
    // Color depth overrides everything else
    if (fDepth < mode.GetColorDepth())
        return true;
    // Only compare width and height if the color depth is the same
    else if (fDepth == mode.GetColorDepth() )
    {
        if( fWidth < mode.GetWidth() )
            return true;
        else if( fWidth == mode.GetWidth() && fHeight < mode.GetHeight() )
            return true;
    }

    return false;
}

void hsG3DDeviceMode::Clear()
{
    fFlags = kNone;
    fWidth = 0;
    fHeight = 0;
    fDepth = 0;
    fZStencilDepths.Reset();
    fFSAATypes.Reset();
}

void hsG3DDeviceMode::Read( hsStream* s )
{
    Clear();

    fFlags = s->ReadSwap32();
    fWidth = s->ReadSwap32();
    fHeight = s->ReadSwap32();
    fDepth = s->ReadSwap32();

    fZStencilDepths.Reset();
    UInt8   count= s->ReadByte();
    while( count-- )
        fZStencilDepths.Append( s->ReadSwap16() );

    /// Version 9
    fFSAATypes.Reset();
    count = s->ReadByte();
    while( count-- )
        fFSAATypes.Append( s->ReadByte() );

    fCanRenderToCubics = s->ReadBool();
}

void hsG3DDeviceMode::Write( hsStream* s ) const
{
    s->WriteSwap32(fFlags);
    s->WriteSwap32(fWidth);
    s->WriteSwap32(fHeight);
    s->WriteSwap32(fDepth);

    UInt8   i, count = (UInt8)fZStencilDepths.GetCount();
    s->WriteByte( count );
    for( i = 0; i < count; i++ )
        s->WriteSwap16( fZStencilDepths[ i ] );

    /// Version 9
    count = (UInt8)fFSAATypes.GetCount();
    s->WriteByte( count );
    for( i = 0; i < count; i++ )
        s->WriteByte( fFSAATypes[ i ] );

    s->WriteBool( fCanRenderToCubics );
}

///////////////////////////////////////////////////
///////////////////////////////////////////////////
///////////////////////////////////////////////////

hsG3DDeviceRecord::hsG3DDeviceRecord()
:   fFlags(kNone),
    fG3DDeviceType(hsG3DDeviceSelector::kDevTypeUnknown),
    fG3DDriverDesc(nil), fG3DDriverName(nil), fG3DDriverVersion(nil), fG3DDeviceDesc(nil),
    fLayersAtOnce(0), fMemoryBytes(0),
    fG3DHALorHEL(hsG3DDeviceSelector::kHHTypeUnknown),
    fZBiasRating( 0 ), fRecordVersion( kCurrRecordVersion ), fLODBiasRating( 0 ),
    fFogExpApproxStart( 0.0 ), fFogExp2ApproxStart( 0.0 ), fFogEndBias( 0.0 ), fMaxAnisotropicSamples( 1 )
{
    SetFogKneeParams( kFogExp, 0, 0 );
    SetFogKneeParams( kFogExp2, 0, 0 );
}

hsG3DDeviceRecord::~hsG3DDeviceRecord()
{
    Clear();
}

hsG3DDeviceRecord::hsG3DDeviceRecord(const hsG3DDeviceRecord& src)
:   fFlags(kNone),
    fG3DDeviceType(hsG3DDeviceSelector::kDevTypeUnknown),
    fG3DDriverDesc(nil), fG3DDriverName(nil), fG3DDriverVersion(nil), fG3DDeviceDesc(nil),
    fG3DHALorHEL(hsG3DDeviceSelector::kHHTypeUnknown),
    fZBiasRating( src.fZBiasRating ), fRecordVersion( kCurrRecordVersion ), fLODBiasRating( 0 ),
    fFogExpApproxStart( src.fFogExpApproxStart ), fFogExp2ApproxStart( src.fFogExp2ApproxStart ), 
    fFogEndBias( src.fFogEndBias ), fMaxAnisotropicSamples( src.fMaxAnisotropicSamples )
{
    *this = src;
}

hsG3DDeviceRecord& hsG3DDeviceRecord::operator=(const hsG3DDeviceRecord& src)
{
    fFlags = src.fFlags;

    SetG3DDeviceType(src.GetG3DDeviceType());
    SetG3DHALorHEL(src.GetG3DHALorHEL());

    SetDriverDesc(src.GetDriverDesc());
    SetDriverName(src.GetDriverName());
    SetDriverVersion(src.GetDriverVersion());
    SetDeviceDesc(src.GetDeviceDesc());

    fCaps = src.fCaps;
    fLayersAtOnce = src.fLayersAtOnce;
    fMemoryBytes = src.fMemoryBytes;
    fZBiasRating = src.fZBiasRating;
    fLODBiasRating = src.fLODBiasRating;
    fFogExpApproxStart = src.fFogExpApproxStart;
    fFogExp2ApproxStart = src.fFogExp2ApproxStart;
    fFogEndBias = src.fFogEndBias;

    fModes.SetCount(src.fModes.GetCount());
    int i;
    for( i = 0; i < fModes.GetCount(); i++ )
        fModes[i] = src.fModes[i];

    fFogKnees[ 0 ] = src.fFogKnees[ 0 ];
    fFogKnees[ 1 ] = src.fFogKnees[ 1 ];
    fFogKneeVals[ 0 ] = src.fFogKneeVals[ 0 ];
    fFogKneeVals[ 1 ] = src.fFogKneeVals[ 1 ];

    fAASetting = src.fAASetting;

    fMaxAnisotropicSamples = src.fMaxAnisotropicSamples;

    return *this;
}

void hsG3DDeviceRecord::SetDriverDesc( const char *s )
{
    delete [] fG3DDriverDesc; 
    fG3DDriverDesc = s ? hsStrcpy(s) : nil; 
}

void hsG3DDeviceRecord::SetDriverName( const char *s )
{
    delete [] fG3DDriverName; 
    fG3DDriverName = s ? hsStrcpy(s) : nil; 
}

void hsG3DDeviceRecord::SetDriverVersion( const char *s )
{
    delete [] fG3DDriverVersion; 
    fG3DDriverVersion = s ? hsStrcpy(s) : nil; 
}

void hsG3DDeviceRecord::SetDeviceDesc( const char *s )
{ 
    delete [] fG3DDeviceDesc; 
    fG3DDeviceDesc = s ? hsStrcpy(s) : nil; 
}

const char* hsG3DDeviceRecord::GetG3DDeviceTypeName() const
{
    static char* deviceNames[hsG3DDeviceSelector::kNumDevTypes] = {
        "Unknown",
        "Glide",
        "Direct3D",
        "OpenGL"
    };

    UInt32 devType = GetG3DDeviceType();
    if( devType > hsG3DDeviceSelector::kNumDevTypes )
        devType = hsG3DDeviceSelector::kDevTypeUnknown;
    
    return deviceNames[devType];
}

void hsG3DDeviceRecord::RemoveDiscarded()
{
    int i;
    for( i = 0; i < fModes.GetCount(); )
    {
        if( fModes[i].GetDiscarded() )
        {
            fModes[i].Clear();
            fModes.Remove(i);
        }
        else
            i++;
    }
    if( !fModes.GetCount() )
        SetDiscarded(true);
}

void hsG3DDeviceRecord::ClearModes()
{
    int i;
    for( i = 0; i < fModes.GetCount(); i++ )
        fModes[i].Clear();
    fModes.Reset();
}

void hsG3DDeviceRecord::Clear()
{
    fFlags = kNone;

    delete [] fG3DDriverDesc;
    fG3DDriverDesc = nil;

    delete [] fG3DDriverName;
    fG3DDriverName = nil;

    delete [] fG3DDriverVersion;
    fG3DDriverVersion = nil;

    delete [] fG3DDeviceDesc;
    fG3DDeviceDesc = nil;

    fCaps.Clear();
    fLayersAtOnce = 0;

    int i;
    for( i = 0; i < fModes.GetCount(); i++ )
        fModes[i].Clear();
    fModes.Reset();

    fZBiasRating = 0;
    fLODBiasRating = 0;
    fFogExpApproxStart = 0;
    fFogExp2ApproxStart = 0;
    fFogEndBias = 0;

    SetFogKneeParams( kFogExp, 0, 0 );
    SetFogKneeParams( kFogExp2, 0, 0 );

    fAASetting = 0;
    fMaxAnisotropicSamples = 1;
}

//// Read /////////////////////////////////////////////////////////////////////
//  9.6.2000 mcn - Updated to reflect version 2 format
//  9.8.2000 mcn - (temporary?) set to invalid on old (<2) versions

void hsG3DDeviceRecord::Read(hsStream* s)
{
    Clear();

    /// Read version
    fRecordVersion = s->ReadSwap32();
    hsAssert( fRecordVersion <= kCurrRecordVersion, "Invalid version number in hsG3DDeviceRecord::Read()" );
    if( fRecordVersion == kCurrRecordVersion )
    {
        fFlags = s->ReadSwap32();
    }
    else
    {
        SetInvalid();
        return;
//      fFlags = fRecordVersion;
//      fRecordVersion = 1;
//      hsStatusMessage( "WARNING: Old version of hsG3DDeviceRecord found. Attempting to read." );
    }

    /// Now read everything else in as normal
    fG3DDeviceType = s->ReadSwap32();

    int len;

    len = s->ReadSwap32();
    fG3DDriverDesc = TRACKED_NEW char[len + 1];
    s->Read(len, fG3DDriverDesc);
    fG3DDriverDesc[len] = 0;

    len = s->ReadSwap32();
    fG3DDriverName = TRACKED_NEW char[len + 1];
    s->Read(len, fG3DDriverName);
    fG3DDriverName[len] = 0;

    len = s->ReadSwap32();
    fG3DDriverVersion = TRACKED_NEW char[len + 1];
    s->Read(len, fG3DDriverVersion);
    fG3DDriverVersion[len] = 0;

    len = s->ReadSwap32();
    fG3DDeviceDesc = TRACKED_NEW char[len + 1];
    s->Read(len, fG3DDeviceDesc);
    fG3DDeviceDesc[len] = 0;


    fCaps.Read(s);
    fLayersAtOnce = s->ReadSwap32();
    fMemoryBytes = s->ReadSwap32();

    len = s->ReadSwap32();
    fModes.SetCount(len);
    int i;
    for( i = 0; i < len; i++ )
        fModes[i].Read( s );

    /// Version 3 stuff
    fZBiasRating = s->ReadSwapFloat();
    fLODBiasRating = s->ReadSwapFloat();
    fFogExpApproxStart = s->ReadSwapFloat();
    fFogExp2ApproxStart = s->ReadSwapFloat();
    fFogEndBias = s->ReadSwapFloat();

    /// Version 7 stuff
    float       knee, kneeVal;
    knee = s->ReadSwapFloat(); kneeVal = s->ReadSwapFloat();
    SetFogKneeParams( kFogExp, knee, kneeVal );
    knee = s->ReadSwapFloat(); kneeVal = s->ReadSwapFloat();
    SetFogKneeParams( kFogExp2, knee, kneeVal );

    /// Version 9 stuff
    fAASetting = s->ReadByte();

    /// Version A stuff
    fMaxAnisotropicSamples = s->ReadByte();

    /// Reset record version now
    fRecordVersion = kCurrRecordVersion;
}

void hsG3DDeviceRecord::Write(hsStream* s) const
{
    s->WriteSwap32( fRecordVersion );

    s->WriteSwap32(fFlags);

    s->WriteSwap32(fG3DDeviceType);

    int len;

    len = hsStrlen(fG3DDriverDesc);
    s->WriteSwap32(len);
    s->Write(len, fG3DDriverDesc);

    len = hsStrlen(fG3DDriverName);
    s->WriteSwap32(len);
    s->Write(len, fG3DDriverName);

    len = hsStrlen(fG3DDriverVersion);
    s->WriteSwap32(len);
    s->Write(len, fG3DDriverVersion);

    len = hsStrlen(fG3DDeviceDesc);
    s->WriteSwap32(len);
    s->Write(len, fG3DDeviceDesc);

    fCaps.Write(s);
    s->WriteSwap32(fLayersAtOnce);
    s->WriteSwap32(fMemoryBytes);

    s->WriteSwap32(fModes.GetCount());
    int i;
    for( i = 0; i < fModes.GetCount(); i++ )
        fModes[i].Write( s );

    /// Version 3 data
    s->WriteSwapFloat( fZBiasRating );
    s->WriteSwapFloat( fLODBiasRating );
    s->WriteSwapFloat( fFogExpApproxStart );
    s->WriteSwapFloat( fFogExp2ApproxStart );
    s->WriteSwapFloat( fFogEndBias );

    /// Version 7 data
    s->WriteSwapFloat( fFogKnees[ kFogExp ] );
    s->WriteSwapFloat( fFogKneeVals[ kFogExp ] );
    s->WriteSwapFloat( fFogKnees[ kFogExp2 ] );
    s->WriteSwapFloat( fFogKneeVals[ kFogExp2 ] );

    /// Version 9 data
    s->WriteByte( fAASetting );

    /// Version A stuff
    s->WriteByte( fMaxAnisotropicSamples );
}

///////////////////////////////////////////////////
///////////////////////////////////////////////////
///////////////////////////////////////////////////

hsG3DDeviceModeRecord::hsG3DDeviceModeRecord(const hsG3DDeviceRecord& devRec, const hsG3DDeviceMode& devMode)
: fDevice(devRec), fMode(devMode)
{
}

hsG3DDeviceModeRecord::hsG3DDeviceModeRecord()
{
}

hsG3DDeviceModeRecord::~hsG3DDeviceModeRecord()
{
}

hsG3DDeviceModeRecord::hsG3DDeviceModeRecord(const hsG3DDeviceModeRecord& src)
{
    *this = src;
}

hsG3DDeviceModeRecord& hsG3DDeviceModeRecord::operator=(const hsG3DDeviceModeRecord& src)
{
    fDevice = src.fDevice;
    fMode = src.fMode;
    return *this;
}
///////////////////////////////////////////////////
///////////////////////////////////////////////////
///////////////////////////////////////////////////

hsG3DDeviceSelector::hsG3DDeviceSelector()
{
}

hsG3DDeviceSelector::~hsG3DDeviceSelector()
{
    Clear();
}

void hsG3DDeviceSelector::RemoveDiscarded()
{
    int i;
    for( i = 0; i < fRecords.GetCount(); )
    {
        fRecords[i].RemoveDiscarded();

        if( fRecords[i].GetDiscarded() )
        {
            fRecords[i].Clear();
            fRecords.Remove(i);
        }
        else
            i++;
    }
}

void hsG3DDeviceSelector::Clear()
{
    int i;
    for( i = 0; i < fRecords.GetCount(); i++ )
        fRecords[i].Clear();
    fRecords.Reset();
}

void hsG3DDeviceSelector::RemoveUnusableDevModes(hsBool bTough)
{
    plDemoDebugFile::Write( "Removing unusable devices and modes..." );
    for (int i = 0; i < fRecords.GetCount(); i++)
    {
        //
        // Remove modes
        //
        hsTArray<hsG3DDeviceMode>& modes = fRecords[i].GetModes();
        for (int j = 0; j < modes.GetCount(); j++)
        {
            // Remove windowed modes
            if ((modes[j].GetWidth() == 0) &&
                (modes[j].GetHeight() == 0) &&
                (modes[j].GetColorDepth() == 0))
            {
                plDemoDebugFile::Write( "   Removing windowed mode on ", (char *)fRecords[ i ].GetDriverDesc() );
                modes[j].SetDiscarded(true);
            }
            // If tough, remove modes less than 640x480
            else if (bTough && ((modes[j].GetWidth() < 640) || (modes[j].GetHeight() < 480)))
            {
                plDemoDebugFile::Write( "   Removing mode < 640x480 on ", (char *)fRecords[ i ].GetDriverDesc() );
                modes[j].SetDiscarded(true);
            }
            else
            {
                char str[ 256 ];
                sprintf( str, "   Keeping mode (%dx%d) on device %s", modes[j].GetWidth(), modes[j].GetHeight(), fRecords[ i ].GetDriverDesc() );
                plDemoDebugFile::Write( str );
            }
        }

        //
        // Remove devices
        //
        if (fRecords[i].GetG3DDeviceType() == hsG3DDeviceSelector::kDevTypeUnknown)
        {
            plDemoDebugFile::Write( "   Removing unknown device. Description", (char *)fRecords[ i ].GetDriverDesc() );
            fRecords[i].SetDiscarded(true);
        }
        else if( fRecords[i].GetG3DDeviceType() == hsG3DDeviceSelector::kDevTypeDirect3D ||
                 fRecords[i].GetG3DDeviceType() == hsG3DDeviceSelector::kDevTypeDirect3DTnL )
        {
            UInt32      totalMem;
            char        devDesc[ 256 ];


            // For our 3dfx test later
            strncpy( devDesc, fRecords[i].GetDriverDesc(), sizeof( devDesc ) - 1 );
            hsStrLower( devDesc );      

            // Remove software Direct3D devices
            if ((fRecords[i].GetG3DHALorHEL() != hsG3DDeviceSelector::kHHD3DHALDev) &&
                (fRecords[i].GetG3DHALorHEL() != hsG3DDeviceSelector::kHHD3DTnLHalDev) &&
                (fRecords[i].GetG3DHALorHEL() != hsG3DDeviceSelector::kHHD3D3dfxDev) &&
                (fRecords[i].GetG3DHALorHEL() != hsG3DDeviceSelector::kHHD3D3dfxVoodoo5Dev) 
#ifdef HS_ALLOW_D3D_REF_DRIVER
                && (fRecords[i].GetG3DHALorHEL() != hsG3DDeviceSelector::kHHD3DRefDev) 
#endif
                )
            {
                plDemoDebugFile::Write( "   Removing software Direct3D device. Description", (char *)fRecords[ i ].GetDriverDesc() );
                fRecords[i].SetDiscarded(true);
            }
            // Remove 3Dfx Direct3D devices, take 2
            // 10.13.2000 mcn - Now we do it even when we're wimpy
            // 10.25.2000 mcn - Think again.
            // 11.3.2000 mcn - Shesh, is this EVER going to be stable??
            else if( bTough && fRecords[i].GetG3DHALorHEL() == hsG3DDeviceSelector::kHHD3D3dfxDev )
//          else if( bTough && ( strstr( devDesc, "3dfx" ) || strstr( devDesc, "voodoo" ) ) )
            {
                plDemoDebugFile::Write( "   Removing 3Dfx non-Voodoo5 Direct3D device (We only support Glide on 3Dfx). Description", (char *)fRecords[ i ].GetDriverDesc() );
                fRecords[i].SetDiscarded(true);
            }
            // Remove Direct3D devices with less than 11 megs of RAM
            else if (bTough && ( totalMem = IAdjustDirectXMemory( fRecords[i].GetMemoryBytes() ) ) < 11*1024*1024 )
            {
                char str[ 256 ];
                sprintf( str, "   Removing Direct3D device with < 11MB RAM. Device RAM (in kB): %d (Description: %s)",
                                        totalMem / 1024, fRecords[ i ].GetDriverDesc() );
                plDemoDebugFile::Write( str );
                fRecords[i].SetDiscarded(true);
            }
            else
            {
                if( fRecords[i].GetG3DDeviceType() == hsG3DDeviceSelector::kDevTypeDirect3DTnL )
                    plDemoDebugFile::Write( "   Keeping DX8 Direct3D device", (char *)fRecords[ i ].GetDriverDesc() );
                else
                    plDemoDebugFile::Write( "   Keeping Direct3D device", (char *)fRecords[ i ].GetDriverDesc() );
            }
        }
        else
            plDemoDebugFile::Write( "   Keeping device", (char *)fRecords[ i ].GetDriverDesc() );
    }

    RemoveDiscarded();
}

//// IAdjustDirectXMemory /////////////////////////////////////////////////////
//  Adjusts the number coming off of the DirectX caps for "total video memory"
//  to be more reflective of what is really on the board. According to
//  Microsoft, the best way to do this is to add in the memory necessary for
//  the entire desktop. Okay, whatever...

UInt32  hsG3DDeviceSelector::IAdjustDirectXMemory( UInt32 cardMem )
{
#if HS_BUILD_FOR_WIN32
    HDC         deskDC;
    int         width, height, bpp, total;

    deskDC = GetDC( nil );
    width = GetDeviceCaps( deskDC, HORZRES );
    height = GetDeviceCaps( deskDC, VERTRES );
    bpp = GetDeviceCaps( deskDC, BITSPIXEL );
    
    total = width * height;
    if( bpp > 8 )
        total *= ( bpp >> 3 );

    return cardMem + total;
#else
    return  cardMem;
#endif
}

hsBool  hsG3DDeviceSelector::Init( void )
{
    // See if we're all capable of initing
    if( !IInitDirect3D() )
    {
        return false;
    }

    return true;
}

void hsG3DDeviceSelector::Enumerate(hsWinRef winRef)
{
    Clear();

#ifdef HS_BUILD_FOR_WIN32
    /// 9.6.2000 - Create the class to use as our temporary window class
    WNDCLASS    tempClass;

    strcpy( fTempWinClass, "DSTestClass" );
    memset( &tempClass, 0, sizeof( tempClass ) );
    tempClass.lpfnWndProc = DefWindowProc;
    tempClass.hInstance = GetModuleHandle( nil );
    tempClass.hbrBackground = (HBRUSH)GetStockObject( BLACK_BRUSH );
    tempClass.lpszClassName = fTempWinClass;
    UInt16 ret = RegisterClass(&tempClass);
    hsAssert(ret, "Cannot create temporary window class to test for device modes" );
#endif

    /// Now try our devices
#ifdef HS_SELECT_DX7
    ITryDirect3D(winRef);
#endif // HS_SELECT_DX7

    ITryDirect3DTnL(winRef);

//  ITryOpenGL(winRef);

#ifdef HS_BUILD_FOR_WIN32
    /// Get rid of the class
    UnregisterClass( fTempWinClass, GetModuleHandle( nil ) );
#endif
}

hsBool hsG3DDeviceSelector::GetDefault (hsG3DDeviceModeRecord *dmr)
{
    Int32 iTnL, iD3D, iOpenGL, device, mode, i;
    device = iTnL = iD3D = iOpenGL = mode = -1;

    if (device == -1)
    {
        // Get an index for any 3D devices
        for (i = 0; i < fRecords.GetCount(); i++)
        {
            switch (fRecords[i].GetG3DDeviceType())
            {
            case kDevTypeDirect3D:
            case kDevTypeDirect3DTnL:
                if (fRecords[i].GetG3DHALorHEL() == kHHD3DTnLHalDev)
                {
                    if (iTnL == -1 
#ifndef PLASMA_EXTERNAL_RELEASE
                        || plPipeline::fInitialPipeParams.ForceSecondMonitor
#endif // PLASMA_EXTERNAL_RELEASE
                        )
                    {
                        iTnL = i;
                    }
                }
                else if (fRecords[i].GetG3DHALorHEL() == kHHD3DHALDev || fRecords[i].GetG3DHALorHEL() == kHHD3D3dfxVoodoo5Dev )
                {
                    if (iD3D == -1
#ifndef PLASMA_EXTERNAL_RELEASE
                        || plPipeline::fInitialPipeParams.ForceSecondMonitor
#endif // PLASMA_EXTERNAL_RELEASE
                        )
                    {
                        iD3D = i;
                    }
                }
                break;

            case kDevTypeOpenGL:
                if (iOpenGL == -1 
#ifndef PLASMA_EXTERNAL_RELEASE                 
                    || plPipeline::fInitialPipeParams.ForceSecondMonitor
#endif // PLASMA_EXTERNAL_RELEASE                   
                    )
                {
                    iOpenGL = i;
                }
                break;
            }
        }

        // Pick a default device (Priority D3D T&L, D3D HAL, OpenGL)
        if (iTnL != -1)
            device = iTnL;
        else if (iD3D != -1)
            device = iD3D;
        else if (iOpenGL != -1)
            device = iOpenGL;
        else
            return false;
    }

    //
    // Try and find the default mode
    //
    hsTArray<hsG3DDeviceMode>& modes = fRecords[device].GetModes();

    // If there are no modes (for some insane reason), fail
    if (modes.GetCount() == 0)
        return false;

    for (i = 0; i < modes.GetCount(); i++)
    {
        if ((modes[i].GetWidth()    == kDefaultWidth) &&
            (modes[i].GetHeight()   == kDefaultHeight) &&
            (modes[i].GetNumZStencilDepths() > 0))
        {
            // Don't be too picky about the depth, use what's available if the
            // default isn't found.
            if (mode == -1 || modes[mode].GetColorDepth() != kDefaultDepth)
                mode = i;
        }
    }
    // Default mode not found, what kind of card is this?!
    // Regardless, just use the first mode since this isn't a fatal error.
    if (mode == -1)
        mode = 0;

    *dmr = hsG3DDeviceModeRecord(fRecords[device], modes[mode]);

    return true;
}

//// ITryOpenGL ///////////////////////////////////////////////////////////////
//  Updated 8.24.2000 mcn to (hopefully) detect OpenGL drivers.

void hsG3DDeviceSelector::ITryOpenGL(hsWinRef winRef)
{
#ifdef HS_OPEN_GL
#if HS_BUILD_FOR_WIN32
    int                 i, numDrivers;
    int                 modeRes[ 6 ][ 3 ] = { { 640, 480, 16 }, { 800, 600, 16 }, { 1024, 768, 16 }, 
                                                { 1152, 864, 16 }, { 1280, 1024, 16 }, { 1600, 1200, 16 } };
    gls_driver_info     driverInfo;
    hsG3DDeviceRecord   devRec;
    hsG3DDeviceMode     devMode;
    char                str[ 128 ];
    HDC                 hDC;
    HGLRC               tempContext;
    HWND                testWindow = nil, testWindow2 = nil;

    WINDOWPLACEMENT         oldWindowPlace;


    /// Save old window position
    oldWindowPlace.length = sizeof( oldWindowPlace );
    GetWindowPlacement( (HWND)winRef, &oldWindowPlace );

    /// Use the GLS API to get us a list of all OpenGL drivers available on
    /// this system and their capabilities
    numDrivers = glsGetNumberOfDrivers();
    for( i = 0; i < numDrivers; i++ )
    {
        /// Get main driver info
        glsGetDriverInfo( i, &driverInfo );

        devRec.SetG3DDeviceType( kDevTypeOpenGL );

        devRec.SetDriverDesc( driverInfo.aDriverDescription );
        devRec.SetDriverName( driverInfo.GLDriver.aDriverFilePath );

        sprintf( str, "%d.%d", driverInfo.GLDriver.DriverFileVersionHigh, 
                                driverInfo.GLDriver.DriverFileVersionLow );
        devRec.SetDriverVersion( str );
        
        devRec.SetCap( kCapsMipmap );
        devRec.SetCap( kCapsMipmap );
        devRec.SetCap( kCapsPerspective );

        if( driverInfo.DriverFlags & GLS_FLAGS_FULLSCREEN_ONLY )
            devRec.SetCap( kCapsNoWindow );
        if( !( driverInfo.DriverFlags & GLS_FLAGS_SOFTWARE_ONLY ) )
            devRec.SetCap( kCapsHardware );
        devRec.SetCap( kCapsDoesSmallTextures );

        /// We have a problem here--OpenGL has no way of detecting the rest of
        /// the information we want, so we'll have to guess/kludge on most of it.

        glsLoadDriver( i );
        sprintf( str, "ITryOpenGL(): FOUND OpenGL Driver: %s (%s)\n", driverInfo.aDriverDescription,
                        driverInfo.GLDriver.aDriverFilePath );
        hsStatusMessage( str );

        /// (and of COURSE we have to open a bloody rendering context for 
        ///  glGetString to work...whose bright idea was THAT?)
        testWindow = CreateWindowEx( WS_EX_APPWINDOW, fTempWinClass, "OpenGL Screen Test Window", 
                                        WS_POPUP | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_VISIBLE,
                                        0, 0, 640, 480, nil, nil, GetModuleHandle( nil ), 0 );
        hDC = GetDC( testWindow );
        tempContext = (HGLRC)ICreateTempOpenGLContext( hDC, 
                                driverInfo.DriverFlags & GLS_FLAGS_FULLSCREEN_ONLY ); 
        if( tempContext != nil )
        {
            wglMakeCurrent( hDC, tempContext );

            IGetExtOpenGLInfo( devRec );

            /// Reset everything back now
            wglMakeCurrent( nil, nil );
            wglDeleteContext( tempContext );
            ReleaseDC( testWindow, hDC );
        }

        /// Resize window to hide what we're about to do
        SetWindowPos( testWindow, nil, 0, 0, 1600, 1200, SWP_NOZORDER | SWP_NOMOVE );

        /// Check for windowed screen mode (which SOMEBODY decided to test for
        /// bitdepth of 0 instead of the caps flag we're setting....hmmmm wasn't me....)
        if( !( driverInfo.DriverFlags & GLS_FLAGS_FULLSCREEN_ONLY ) )
        {
            devMode.Clear();
            devMode.SetWidth( 0 );
            devMode.SetHeight( 0 );
            devMode.SetColorDepth( 0 );
            devRec.GetModes().Append( devMode );
        }
        
        /// Go get the screen modes
        IGetOpenGLModes( devRec, driverInfo.aDriverDescription );

        /// Get rid of the window now
        DestroyWindow( testWindow );

        /// Unload this driver now
        glsUnloadDriver();
    }

    /// Restore old window position
    SetWindowPlacement( (HWND)winRef, &oldWindowPlace );
#endif
#endif
}

//// IGetOpenGLModes //////////////////////////////////////////////////////////
//  Scans through all the possible imaginable combinations of screen modes,
//  pixel formats whatnot and adds the final, culled-down list of graphics
//  modes to the given device record.

void    hsG3DDeviceSelector::IGetOpenGLModes( hsG3DDeviceRecord &devRec, char *driverName )
{
#ifdef HS_OPEN_GL
#if HS_BUILD_FOR_WIN32
    int                 j;
    int                 maxMode, mode;
    int                 modeRes[ 6 ][ 3 ] = { { 640, 480, 16 }, { 800, 600, 16 }, { 1024, 768, 16 }, 
                                                { 1152, 864, 16 }, { 1280, 1024, 16 }, { 1600, 1200, 16 } };
    int                 bitDepths[ 3 ] = { 32, 24, 16 }, bitDepth;
    hsG3DDeviceMode     devMode;
    DEVMODE             modeInfo;
    HWND                testWindow = nil, testWindow2 = nil;
    char                str[ 128 ];

    
    /// Find the maximum resolution that we can support on this monitor--then
    /// we'll start there and work down until we find a mode supported by OpenGL
    modeInfo.dmSize = sizeof( modeInfo );
    maxMode = -1;
    for( j = 0; EnumDisplaySettings( nil, j, &modeInfo ) != 0; j++ )
    {
        for( mode = 0; mode < sizeof( modeRes ) / sizeof( modeRes[ 0 ] ); mode++ )
        {
            if( modeRes[ mode ][ 0 ] == modeInfo.dmPelsWidth &&
                modeRes[ mode ][ 1 ] == modeInfo.dmPelsHeight )
            {
                if( modeInfo.dmBitsPerPel > modeRes[ mode ][ 2 ] )
                    modeRes[ mode ][ 2 ] = modeInfo.dmBitsPerPel;

                if( mode > maxMode )
                {
                    maxMode = mode;
                    break;
                }
            }
        }
    }
    if( maxMode != -1 )
    {
        /// Outer loop: loop through color depths
        for( bitDepth = 0; bitDepth < 3; bitDepth++ )
        {
            /// Loop through each of the display settings, starting at
            /// the maxMode and going down, happily get pixel formats for each
            modeInfo.dmSize = sizeof( modeInfo );
            modeInfo.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL;
            for( mode = maxMode; mode >= 0; mode-- )
            {
                /// Does this resolution work at this bit depth?
                if( modeRes[ mode ][ 2 ] < bitDepths[ bitDepth ] )
                    continue;

                /// Attempt to set this screen mode
                modeInfo.dmPelsWidth = modeRes[ mode ][ 0 ];
                modeInfo.dmPelsHeight = modeRes[ mode ][ 1 ];
                modeInfo.dmBitsPerPel = bitDepths[ bitDepth ];
                if( ChangeDisplaySettings( &modeInfo, CDS_FULLSCREEN ) == DISP_CHANGE_SUCCESSFUL )
                {
                    if( ITestOpenGLRes( modeRes[ mode ][ 0 ], modeRes[ mode ][ 1 ],
                                    bitDepths[ bitDepth ],
                                    devRec, driverName ) )
                    {
                        /// Go and add the rest of 'em (we just assume that we can get
                        /// lower resolutions if we got this one)
                        for( mode--; mode >= 0; mode-- )
                        {
                            devMode.SetWidth( modeRes[ mode ][ 0 ] );
                            devMode.SetHeight( modeRes[ mode ][ 1 ] );
                            devMode.SetColorDepth( bitDepths[ bitDepth ] );
                            devRec.GetModes().Append( devMode );

                            sprintf( str, "ITryOpenGL(): Assuming mode: %dx%dx%dbpp, %s\n", 
                                        modeRes[ mode ][ 0 ], modeRes[ mode ][ 1 ], bitDepths[ bitDepth ], driverName );
                            hsStatusMessage( str );
                        }
                    }
                }
            }
        }

        /// Note: this will also reset the screen after any mode changes from
        /// creating our context
        ChangeDisplaySettings( nil, 0 );

        if( devRec.GetModes().GetCount() )
            fRecords.Append( devRec );
    }
#endif
#endif
}

//// ITestOpenGLRes ///////////////////////////////////////////////////////////
//  Tests all the possible OpenGL settings once the screen has been set
//  to a given test resolution.

hsBool hsG3DDeviceSelector::ITestOpenGLRes( int width, int height, int bitDepth,
                                            hsG3DDeviceRecord &devRec, char *driverName )
{
    hsBool              retValue = false;

#ifdef HS_OPEN_GL
#if HS_BUILD_FOR_WIN32
    int                 j, bitDepthFlags, myBitDepth;
    hsG3DDeviceMode     devMode;
    char                str[ 128 ];
    HDC                 hDC, hDC2;
    HGLRC               tempContext;
    HWND                testWindow = nil, testWindow2 = nil;

    PIXELFORMATDESCRIPTOR   pfd;

    
    /// Create test window #1
    testWindow = CreateWindowEx( WS_EX_APPWINDOW, fTempWinClass, "OpenGL Screen Test Window", 
                                WS_POPUP | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_VISIBLE,
                                0, 0, width, height,
                                nil, nil, GetModuleHandle( nil ), 0 );
    hDC = GetDC( testWindow );

    /// Loop through using DescribePixelFormat in an attempt to find all the
    /// pixel formats we actually do support using this OpenGL driver
    devMode.Clear();
    pfd.nSize = sizeof( pfd );
    bitDepthFlags = 0;
    for( j = 1; retValue == false && DescribePixelFormat( hDC, j, sizeof( pfd ), &pfd ) != 0; j++ )
    {
        /// Can we use this one?
        if( pfd.cColorBits != bitDepth )
            continue;

        myBitDepth = ( pfd.cColorBits == 32 ) ? 0x04 : ( pfd.cColorBits == 24 ) ? 0x02 : 0x01;

        if( ( pfd.dwFlags & PFD_SUPPORT_OPENGL ) &&
            ( pfd.dwFlags & PFD_DRAW_TO_WINDOW ) &&
            ( pfd.dwFlags & PFD_DOUBLEBUFFER ) &&
            ( pfd.iPixelType == PFD_TYPE_RGBA ) &&
            ( pfd.iLayerType == PFD_MAIN_PLANE ) &&
            ( ( bitDepthFlags & myBitDepth ) == 0 ) )
        {
            /// Looks like it! But is it REALLY?
            testWindow2 = CreateWindowEx( WS_EX_APPWINDOW, fTempWinClass, "OpenGL Screen Test Window #2", 
                                        WS_POPUP | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_VISIBLE,
                                        0, 0, width, height,
                                        nil, nil, GetModuleHandle( nil ), 0 );
            hDC2 = GetDC( testWindow2 );

            if( SetPixelFormat( hDC2, j, &pfd ) )
            {
                tempContext = wglCreateContext( hDC2 );
                if( tempContext != nil )
                {
                    if( wglMakeCurrent( hDC2, tempContext ) )
                    {
                        /// Guess it really does work...
                        devMode.SetWidth( width );
                        devMode.SetHeight( height );
                        devMode.SetColorDepth( pfd.cColorBits );
                        devRec.GetModes().Append( devMode );
                        bitDepthFlags |= myBitDepth;

                        sprintf( str, "ITryOpenGL(): Adding mode: %dx%dx%dbpp, %s\n", 
                                        width, height, pfd.cColorBits, driverName );
                        hsStatusMessage( str );

                        wglMakeCurrent( nil, nil );
                        retValue = true;        /// Break us out
                    }
                    wglDeleteContext( tempContext );
                }
            }
            ReleaseDC( testWindow2, hDC2 );
            DestroyWindow( testWindow2 );
        }
    }
    ReleaseDC( testWindow, hDC );
    DestroyWindow( testWindow );

#endif
#endif
    return retValue;
}


//// IGetExtOpenGLInfo ////////////////////////////////////////////////////////
//  Gets extended info--i.e. info requiring an OpenGL context. Assumes the
//  said context is already created and active.

void    hsG3DDeviceSelector::IGetExtOpenGLInfo( hsG3DDeviceRecord &devRec )
{
#ifdef HS_OPEN_GL
#if HS_BUILD_FOR_WIN32
    GLint   numTMUs;
    char    *extString, *c, *c2;
    char    str[ 128 ];
    int     j;


    if( ( extString = (char *)glGetString( GL_RENDERER ) ) != nil )
    {
        devRec.SetDeviceDesc( extString );

        /// Can we guess at the amount of texture memory?
        c = strstr( extString, "MB" );
        if( c != nil && c != extString && ( isdigit( *( c - 1 ) ) || isspace( *( c - 1 ) ) ) )
        {
            /// Looks like we found a "xxMB" texture memory specification--use it
            /// as our guess
            c2 = c;
            do {
                c2--;
            } while( c2 >= extString && ( isdigit( *c2 ) || isspace( *c2 ) ) );
            c2++;
            
            strncpy( str, c2, (UInt32)c - (UInt32)c2 );
            j = atoi( str );
            sprintf( str, "ITryOpenGL():   Device has %d MB texture memory\n", j );
            hsStatusMessage( str );

            j *= 1024 * 1024;       /// Translate to bytes
            devRec.SetMemoryBytes( j );
        }
        else
        {
            devRec.SetMemoryBytes( 4 * 1024 * 1024 );
            hsStatusMessage( "ITryOpenGL():   WARNING: Cannot determine texture memory for this card, assuming 4MB\n" );
        }
    }
    else
    {
        devRec.SetDeviceDesc( "" );
        devRec.SetMemoryBytes( 4 * 1024 * 1024 );
        hsStatusMessage( "ITryOpenGL():   WARNING: Cannot determine texture memory for this card, assuming 4MB\n" );
    }


    if( ( extString = (char *)glGetString( GL_EXTENSIONS ) ) != nil )
    {
        /// For the number of TMUs, we'll detect for the availability of the 
        /// multitexture extension--if it's there, we'll assume we have two TMUs
        /// (if we don't, OpenGL will probably handle it for us--or rather, it BETTER).
        if( strstr( extString, "ARB_multitexture" ) )
            devRec.SetLayersAtOnce( 2 );
        else
            devRec.SetLayersAtOnce( 1 );

        /// Can we use compressed textures?
        if( strstr( extString, "ARB_texture_compression" ) )
            devRec.SetCap( kCapsCompressTextures );
    }

    /// Get TMU count
    glGetIntegerv( GL_MAX_TEXTURE_UNITS_ARB, &numTMUs );    
    if( numTMUs <= 0 )
        numTMUs = 0;
    devRec.SetLayersAtOnce( numTMUs );
#endif
#endif
}

//// ICreateTempOpenGLContext /////////////////////////////////////////////////
//  Creates a temporary context for testing OpenGL stuff with.

#ifdef HS_OPEN_GL
#if HS_BUILD_FOR_WIN32

UInt32  hsG3DDeviceSelector::ICreateTempOpenGLContext( HDC hDC, hsBool32 makeItFull )
{
    DEVMODE     modeInfo;
    int         pixFmt;


    if( makeItFull )
    {
        /// Attempt resolution change to 640x480x32bpp
        memset( &modeInfo, 0, sizeof( modeInfo ) );
        modeInfo.dmSize = sizeof( modeInfo );
        modeInfo.dmBitsPerPel = 16;
        modeInfo.dmPelsWidth = 640;
        modeInfo.dmPelsHeight = 480;
        if( ChangeDisplaySettings( &modeInfo, CDS_FULLSCREEN ) != DISP_CHANGE_SUCCESSFUL )
        {
            return nil;         /// We want fullscreen, can't get it, oops.
        }
    }

    /// Now try to set a pixel format
    PIXELFORMATDESCRIPTOR pfd = { 
        sizeof(PIXELFORMATDESCRIPTOR),    // size of this pfd 
        1,                                // version number 
        PFD_DRAW_TO_WINDOW |              // support window 
        PFD_SUPPORT_OPENGL |              // support OpenGL 
        PFD_DOUBLEBUFFER,                 // double buffered 
        PFD_TYPE_RGBA,                    // RGBA type 
        16,                               // 24-bit color depth 
        0, 0, 0, 0, 0, 0,                 // color bits ignored 
        0,                                // no alpha buffer 
        0,                                // shift bit ignored 
        0,                                // no accumulation buffer 
        0, 0, 0, 0,                       // accum bits ignored 
        0,                                // 32-bit z-buffer     
        0,                                // no stencil buffer 
        0,                                // no auxiliary buffer 
        PFD_MAIN_PLANE,        // main layer 
        0,                     // reserved 
        0, 0, 0                // layer masks ignored 
        }; 

    pixFmt = ChoosePixelFormat( hDC, &pfd );
    if( pixFmt > 0 && SetPixelFormat( hDC, pixFmt, &pfd ) )
        return (UInt32)wglCreateContext( hDC );

    return 0;
}
#endif
#endif

///////////////////////////////////////////////////////////////////////////////
//// Fudging Routines /////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

namespace
{
    /// Here's our CFT--Chipset Fudgefactor Table
    /// The table consists of entries for each of our supported chipsets in the table,
    /// plus, flags to be forced set and flags to be forced cleared. Also included
    /// is a Z-buffer suckiness rating, which represents how badly we need to bias
    /// the z and w values to avoid z-buffer artifacts, stored as an hsScalar (i.e
    /// a float). A rating of 0 means very good/default (read: Nvidia), while, say, 
    /// a 9.0 (i.e. shift the scale 9 times above normal) means s****y, like, say, 
    /// a Savage4. Also also included is a forced value for max # of layers (0 means 
    /// to use default). Also also also included is an LOD rating indicating how much 
    /// (and in which direction) to alter the base LOD bias value for this device. General
    /// interpretation of this value is to add (-lodRating) to the LOD bias value.
    /// This is because the LOD bias starts out negative and typically goes in 0.25
    /// increments.
    /// Also also ALSO included are three new values for fog tweaking. The first two--
    /// fFogExp/Exp2ApproxStart, are the start of the linear approximation of exponential
    /// fog. Tweak these to adjust the linear approximation on any cards that don't support
    /// exponential and exponential-squared fog natively. The third value is the fFogEndBias--
    /// this is a value (stored as a percentage of the max possible fog value) to add on to
    /// to the linear fog-end parameter AFTER ALL CALCULATIONS. This is so we can, for
    /// example, tweak the end of the fog on the ATI Rage cards to not fog out as quickly.
    /// 9.14.2000 - fog end bias now has a new meaning. What it *really* represents is the
    /// quantization of fog on a particular card, where the end bias = ( 2^bitdepth - 2 ) / ( 2^bitdepth - 1 )
    /// So, for 8 bit fog, we end up with 254 / 255, etc. So far, everything is set to 8
    /// bit fog, but we have it here just in case we need to change it in the future.

    enum {
        kDefaultChipset = 0x00,
        kSavage4Chipset,
        kATIRageFuryChipset,
        kATIRageProChipset,
        kNVidiaTNTChipset,
        kNVidiaGeForceChipset,
        kMatroxG400Chipset,
        kIntelI810Chipset,
        kSavage2000Chipset,
        kS3GenericChipset,
        kATIGenericChipset,
        kMatroxGenericChipset,
        kKYROChipset,
        k3dfxV5Chipset,
        kSavage3DChipset,
        kATIRadeonChipset,
        kATIR7X00Chipset,
        kATIR7500Chipset,
        kATIR8X00Chipset,
        kMatroxParhelia,
        kNVidiaGeForce2Chipset,
        kNVidiaGeForce3Chipset,
        kNVidiaGeForce4MXChipset,
        kNVidiaGeForce4Chipset,
        kNVidiaGeForceFXChipset
    };

    typedef struct
    {
        hsScalar    fFogExpApproxStart;
        hsScalar    fFogExp2ApproxStart;
        hsScalar    fFogEndBias;
        hsScalar    fFogExpKnee;        // Fog knees
        hsScalar    fFogExpKneeVal;
        hsScalar    fFogExp2Knee;
        hsScalar    fFogExp2KneeVal;
    } FogTweakTable;

    FogTweakTable   dsDefaultFogVals =  { 0,    0,      254.0 / 255.0,  0.5f, 0.15f, 0.5f, 0.15f };
    FogTweakTable   dsATIFogVals =      { 0.1f, 0.1f,   254.0 / 255.0,  0.85f, 0.15f, 0.5f, 0.15f };
    FogTweakTable   dsS3DFogVals =      { 0,    0,      254.0 / 255.0,  1.0f, 1.0f,  1.0f, 1.0f  };
    FogTweakTable   dsi810FogVals =     { 0,    0,      254.0 / 255.0,  0.6f, 0.15f, 0.4f, 0.15f };
    FogTweakTable   dsRadeonFogVals =   { 0,    0,      254.0 / 255.0,  0.7f, 0.15f, 0.5f, 0.2f };


    typedef struct {
        UInt8           fType;              // Our chipset ID
        UInt32          *fFlagsToSet;       
        UInt32          *fFlagsToClear;
        hsScalar        fZSuckiness;        // See above
        UInt32          fForceMaxLayers;    // The max # of layers we REALLY want (0 to not force)
        hsScalar        fLODRating;
        FogTweakTable   *fFogTweaks;
    } CFTable;

    UInt32  dsSavageCapsClr[] = {
                    4,              // First integer is always the length
                    hsG3DDeviceSelector::kCapsCompressTextures,
                    hsG3DDeviceSelector::kCapsFogExp,
                    hsG3DDeviceSelector::kCapsFogExp2,
                    hsG3DDeviceSelector::kCapsDoesSmallTextures };

    UInt32  dsSavageCapsSet[] = {
                    1,              // First integer is always the length
                    hsG3DDeviceSelector::kCapsBadYonStuff };
                    
    UInt32  dsSavage2kCapsClr[] = {
                    5,              // First integer is always the length
                    hsG3DDeviceSelector::kCapsCompressTextures,
                    hsG3DDeviceSelector::kCapsPixelFog,
                    hsG3DDeviceSelector::kCapsFogExp,
                    hsG3DDeviceSelector::kCapsFogExp2,
                    hsG3DDeviceSelector::kCapsDoesSmallTextures };

    UInt32  dsS3GenerCapsClr[] = {
                    4,              // First integer is always the length
                    hsG3DDeviceSelector::kCapsCompressTextures,
                    hsG3DDeviceSelector::kCapsFogExp,
                    hsG3DDeviceSelector::kCapsFogExp2,
                    hsG3DDeviceSelector::kCapsDoesSmallTextures };

    UInt32  dsATIFuryCapsClr[] = {
                    3,              // First integer is always the length
                    hsG3DDeviceSelector::kCapsFogExp,
                    hsG3DDeviceSelector::kCapsFogExp2,
                    hsG3DDeviceSelector::kCapsPixelFog };

    UInt32  dsATIRageCapsClr[] = {
                    4,              // First integer is always the length
                    hsG3DDeviceSelector::kCapsFogExp,
                    hsG3DDeviceSelector::kCapsFogExp2,
                    hsG3DDeviceSelector::kCapsDoesSmallTextures,
                    hsG3DDeviceSelector::kCapsPixelFog };

    UInt32  dsATIGenerCapsClr[] = {
                    4,              // First integer is always the length
                    hsG3DDeviceSelector::kCapsPixelFog,
                    hsG3DDeviceSelector::kCapsFogExp,
                    hsG3DDeviceSelector::kCapsFogExp2,
                    hsG3DDeviceSelector::kCapsDoesSmallTextures };

    UInt32  dsATIRadeonCapsSet[] = {
                    2,              // First integer is always the length
                        hsG3DDeviceSelector::kCapsBadManaged,
                        hsG3DDeviceSelector::kCapsShareDepth
                    };

    UInt32  dsATIRadeonCapsClr[] = {
                    2,              // First integer is always the length
                    hsG3DDeviceSelector::kCapsWBuffer,
                    hsG3DDeviceSelector::kCapsDoesSmallTextures };

    UInt32  dsATIR7X00CapsSet[] = {
                    4,              // First integer is always the length
                        hsG3DDeviceSelector::kCapsCantShadow,
                        hsG3DDeviceSelector::kCapsBadManaged,
                        hsG3DDeviceSelector::kCapsShareDepth,
                        hsG3DDeviceSelector::kCapsNoAniso
                    };

    UInt32  dsATIR7500CapsSet[] = {
                    5,              // First integer is always the length
                        hsG3DDeviceSelector::kCapsMaxUVWSrc2,
                        hsG3DDeviceSelector::kCapsCantShadow,
                        hsG3DDeviceSelector::kCapsBadManaged,
                        hsG3DDeviceSelector::kCapsShareDepth,
                        hsG3DDeviceSelector::kCapsNoAniso
                    };

    UInt32  dsATIR7X00CapsClr[] = {
                    2,              // First integer is always the length
                    hsG3DDeviceSelector::kCapsWBuffer,
                    hsG3DDeviceSelector::kCapsDoesSmallTextures };

    UInt32  dsATIR8X00CapsSet[] = {
                    2,              // First integer is always the length
                        hsG3DDeviceSelector::kCapsBadManaged,
                        hsG3DDeviceSelector::kCapsShareDepth
                    };

    UInt32  dsATIR8X00CapsClr[] = {
                    2,              // First integer is always the length
                    hsG3DDeviceSelector::kCapsWBuffer,
                    hsG3DDeviceSelector::kCapsDoesSmallTextures };

    UInt32  dsTNTCapsClr[] = {
                    1,              // First integer is always the length
                    hsG3DDeviceSelector::kCapsDoesSmallTextures };

    UInt32  dsDefaultCapsClr[] = {
                    1,              // First integer is always the length
                    hsG3DDeviceSelector::kCapsDoesSmallTextures };

    UInt32  dsMG400CapsClr[] = {
                    1,              // First integer is always the length
                    hsG3DDeviceSelector::kCapsDoesSmallTextures };

    UInt32  dsKYROCapsClr[] = {
                    2,              // First integer is always the length
                    hsG3DDeviceSelector::kCapsDoesSmallTextures,
                    hsG3DDeviceSelector::kCapsPixelFog };

    UInt32  dsKYROCapsSet[] = {
                    1,              // First integer is always the length
                    hsG3DDeviceSelector::kCapsNoKindaSmallTexs };

    UInt32  ds3dfxV5CapsClr[] = {
                    2,              // First integer is always the length
                    hsG3DDeviceSelector::kCapsFogExp,
                    hsG3DDeviceSelector::kCapsFogExp2 };

    UInt32  dsMatroxParheliaSet[] = {
                    1,
                        hsG3DDeviceSelector::kCapsNoAA };

    UInt32  dsGeForceSet[] = {
                    2,
                        hsG3DDeviceSelector::kCapsCantProj,
                        hsG3DDeviceSelector::kCapsDoubleFlush };

    UInt32  dsGeForce2Set[] = {
                    1,
                        hsG3DDeviceSelector::kCapsDoubleFlush };

    UInt32  dsGeForce3Set[] = {
                    1,
                        hsG3DDeviceSelector::kCapsSingleFlush };

    UInt32  dsGeForce4MXSet[] = {
                    1,
                        hsG3DDeviceSelector::kCapsSingleFlush };

    UInt32  dsGeForce4Set[] = {
                    1,
                        hsG3DDeviceSelector::kCapsSingleFlush
                    };

    CFTable dsCFTable[] = 
        { 
            // Chipset ID           // F2Set            // F2Clear          // ZSuck    // MaxLayers    // LODBias      // Fog Value Tables
            { kDefaultChipset,      nil,                dsDefaultCapsClr,   0,          0,              0,              &dsDefaultFogVals },
            { kATIRageFuryChipset,  nil,                dsATIFuryCapsClr,   4.25f,      1,              0,              &dsATIFogVals },
            { kATIRageProChipset,   nil,                dsATIRageCapsClr,   4.25f,      1,              0,              &dsATIFogVals },
            { kATIGenericChipset,   nil,                dsATIGenerCapsClr,  4.25f,      1,              0,              &dsATIFogVals },
            { kNVidiaTNTChipset,    nil,                dsTNTCapsClr,       0,          0,              0,              &dsDefaultFogVals },
            { kNVidiaGeForce2Chipset,dsGeForce2Set,     nil,                0,          0,              0,              &dsDefaultFogVals },
            { kNVidiaGeForce3Chipset,dsGeForce3Set,     nil,                0,          0,              0,              &dsDefaultFogVals },
            { kNVidiaGeForce4MXChipset,dsGeForce4MXSet, nil,                0,          0,              0,              &dsDefaultFogVals },
            { kNVidiaGeForce4Chipset,dsGeForce4Set,     nil,                0,          0,              0,              &dsDefaultFogVals },
            { kNVidiaGeForceChipset,dsGeForceSet,       nil,                0,          0,              0,              &dsDefaultFogVals },
            { kNVidiaGeForceFXChipset,nil,              nil,                0,          0,              0,              &dsDefaultFogVals },
            { kMatroxG400Chipset,   nil,                dsMG400CapsClr,     3.25f,      0,              0,              &dsDefaultFogVals },
            { kMatroxParhelia,      dsMatroxParheliaSet,nil,                0,          0,              0,              &dsDefaultFogVals },
            { kMatroxGenericChipset,nil,                dsMG400CapsClr,     3.25f,      0,              0,              &dsDefaultFogVals },
            { kIntelI810Chipset,    nil,                dsDefaultCapsClr,   4.5f,       1,              -0.5f,          &dsi810FogVals },
            { kSavage4Chipset,      dsSavageCapsSet,    dsSavageCapsClr,    4.0f,       1,              0,              &dsDefaultFogVals },        // LOD bias should be -0.5 here
            { kSavage2000Chipset,   dsSavageCapsSet,    dsSavage2kCapsClr,  4.0f,       1,              0,              &dsDefaultFogVals },
            { kS3GenericChipset,    dsSavageCapsSet,    dsS3GenerCapsClr,   4.0f,       1,              0,              &dsDefaultFogVals },
            { kKYROChipset,         dsKYROCapsSet,      dsKYROCapsClr,      -151.0f,    1,              0,              &dsDefaultFogVals },
            { k3dfxV5Chipset,       nil,                ds3dfxV5CapsClr,    3.5f,       0,              0,              &dsDefaultFogVals },
            { kSavage3DChipset,     nil,                dsDefaultCapsClr,   0,          0,              0,              &dsS3DFogVals },
            { kATIRadeonChipset,    dsATIRadeonCapsSet, dsATIRadeonCapsClr, 0,          0,              0,              &dsRadeonFogVals },
            { kATIR7X00Chipset,     dsATIR7X00CapsSet,  dsATIR7X00CapsClr,  3.f,        2,              0,              &dsRadeonFogVals  },
            { kATIR7500Chipset,     dsATIR7500CapsSet,  dsATIR7X00CapsClr,  3.f,        2,              0,              &dsRadeonFogVals  },
            { kATIR8X00Chipset,     dsATIR8X00CapsSet,  dsATIR8X00CapsClr,  0,          0,              0,              &dsRadeonFogVals  },
        };

};

//// IFudgeDirectXDevice //////////////////////////////////////////////////////
//  Checks this DirectX device against all our known types and fudges our caps 
//  flags and bias values, etc, accordingly

#ifdef HS_SELECT_DIRECT3D
void    hsG3DDeviceSelector::IFudgeDirectXDevice( hsG3DDeviceRecord &record,
                                                    D3DEnum_DriverInfo *driverInfo,
                                                    D3DEnum_DeviceInfo *deviceInfo )
{
    char        desc[ 512 ];    // Can't rely on D3D constant, since that's in another file now
    DWORD       vendorID, deviceID;
    char        *szDriver, *szDesc;


    /// Send it off to each D3D device, respectively
    if( record.GetG3DDeviceType() == kDevTypeDirect3DTnL )
    {
        if( !IGetD3DCardInfo( record, driverInfo, deviceInfo, &vendorID, &deviceID, &szDriver, &szDesc ) )
        {
            // {} to make VC6 happy in release build
            hsAssert( false, "Trying to fudge D3D device but D3D support isn't in this EXE!" );
        }
    }
#ifdef HS_SELECT_DX7
    else if( record.GetG3DDeviceType() == kDevTypeDirect3D )
    {
        if( !IGetD3D7CardInfo( record, driverInfo, deviceInfo, &vendorID, &deviceID, &szDriver, &szDesc ) )
        {
            // {} to make VC6 happy in release build
            hsAssert( false, "Trying to fudge D3D7 device but D3D7 support isn't in this EXE!" );
        }
    }
#endif // HS_SELECT_DX7
    else
    {
        hsAssert( false, "IFudgeDirectXDevice got a device type that support wasn't compiled for!" );
    }

    /// So capitalization won't matter in our tests
    hsAssert( strlen( szDesc ) < sizeof( desc ), "D3D device description longer than expected!" );
    hsStrcpy( desc, szDesc );
    hsStrLower( desc ); 
        
    //// S3-based Cards ///////////////////////////////////////////////////////
    /// Detect Savage 4 chipset
    if( deviceID == 0x00008a22 || stricmp( szDriver, "s3savg4.dll" ) == 0 ||
        ( strstr( desc, "diamond" ) != nil && strstr( desc, "stealth iii" ) != nil ) ||
        strstr( desc, "savage4 " ) != nil )
    {
        /// Yup, Savage 4.
        hsStatusMessage( "== Using fudge factors for a Savage 4 chipset ==\n" );
        plDemoDebugFile::Write( "   Using fudge factors for a Savage 4 chipset" );
        ISetFudgeFactors( kSavage4Chipset, record );
    }
    /// Detect Savage 2000 chipset
    else if( deviceID == 0x00009102 || 
        stricmp( szDriver, "s3sav2k.dll" ) == 0 ||
        ( strstr( desc, "diamond" ) != nil &&
           strstr( desc, "viperii" ) != nil ) ||
        strstr( desc, "savage2000 " ) != nil )
    {
        /// Yup, Savage 2000.
        hsStatusMessage( "== Using fudge factors for a Savage 2000 chipset ==\n" );
        plDemoDebugFile::Write( "   Using fudge factors for a Savage 2000 chipset" );
        ISetFudgeFactors( kSavage2000Chipset, record );
    }
    /// Detect Savage3D chipset
    else if( deviceID == 0x00008a20 || 
        stricmp( szDriver, "s3_6.dll" ) == 0 ||
        strstr( desc, "savage3d" ) != nil )
    {
        /// Yup, Savage3D.
        hsStatusMessage( "== Using fudge factors for a Savage3D chipset ==\n" );
        plDemoDebugFile::Write( "   Using fudge factors for a Savage3D chipset" );
        ISetFudgeFactors( kSavage3DChipset, record );
    }
    /// Detect Generic S3 chipset
    else if( ( strncmp( szDriver, "s3", 2 ) == 0 ) || ( strstr( desc, "savage" ) != nil ) )
    {
        /// Yup, Generic S3.
        hsStatusMessage( "== Using fudge factors for a generic S3 chipset ==\n" );
        plDemoDebugFile::Write( "   Using fudge factors for a generic S3 chipset" );
        ISetFudgeFactors( kS3GenericChipset, record );
    }

    //// ATI-based Cards //////////////////////////////////////////////////////
    /// Detect ATI Rage 128 Pro chipset
    else if( ( deviceID == 0x00005046 &&            // Normal ATI Rage 128 Pro detection
                ( stricmp( szDriver, "ati2dvaa.dll" ) == 0 
                || strstr( desc, "rage 128 pro" ) != nil ) ) ||
            ( deviceID == 0x00005246 &&             // ATI All-in-wonder--same chipset, diff values
                ( stricmp( szDriver, "ati3draa.dll" ) == 0
                || strstr( desc, "all-in-wonder 128" ) != nil ) ) )
    {
        hsStatusMessage( "== Using fudge factors for an ATI Rage 128 Pro chipset ==\n" );
        plDemoDebugFile::Write( "   Using fudge factors for an ATI Rage 128 Pro chipset" );
        ISetFudgeFactors( kATIRageProChipset, record );
    }
    /// Detect(detest?) ATI Rage FURY MAXX chipset
    else if( deviceID == 0x00005046 &&
             ( stricmp( szDriver, "ati3drau.dll" ) == 0 
               || strstr( desc, "rage fury" ) != nil ) )
    {
        hsStatusMessage( "== Using fudge factors for an ATI Rage Fury MAXX chipset ==\n" );
        plDemoDebugFile::Write( "   Using fudge factors for an ATI Rage Fury MAXX chipset" );
        ISetFudgeFactors( kATIRageFuryChipset, record );
    }
    /// Detect ATI Radeon chipset
    // We will probably need to differentiate between different Radeons at some point in 
    // the future, but not now.
    else if( // deviceID == 0x00005144 && 
                ( stricmp( szDriver, "ati2dvag.dll" ) == 0
                    || strstr( desc, "radeon" ) != nil ) )
    {
        int series = 0;
        const char* str = strstr(desc, "radeon");
        if( str )
            str += strlen("radeon");
        else
        {
            str = strstr(desc, "all-in-wonder");
            if( str )
                str += strlen("all-in-wonder");
        }
        if( str )
        {
            if( 1 == sscanf(str, "%d", &series) )
            {
                if( (series == 7500) || (series == 7200) )
                {
                    hsStatusMessage( "== Using fudge factors for ATI Radeon 7200/7500 chipset ==\n" );
                    plDemoDebugFile::Write( "   Using fudge factors for ATI Radeon 7200/7500 chipset" );
                    ISetFudgeFactors( kATIR7500Chipset, record );
                }
                else
                if( (series >= 7000) && (series < 8000) )
                {
                    hsStatusMessage( "== Using fudge factors for ATI Radeon 7X00 chipset ==\n" );
                    plDemoDebugFile::Write( "   Using fudge factors for ATI Radeon 7X00 chipset" );
                    ISetFudgeFactors( kATIR7X00Chipset, record );
                }
                else 
                if( (series >= 8000) && (series < 9000) )
                {
                    hsStatusMessage( "== Using fudge factors for ATI Radeon 8X00 chipset ==\n" );
                    plDemoDebugFile::Write( "   Using fudge factors for ATI Radeon 8X00 chipset" );
                    ISetFudgeFactors( kATIR8X00Chipset, record );
                }
                else
                {
                    series = 0;
                }
            }
            else
            {
                series = 0;

                // Skip white space
                while( *str && (*str <= 0x32) )
                    str++;

                // I've still never seen either of these, so I'm just going by ATI's site.
                // Don't have the option of using device-id's.
                if( (str[0] == 'v') && (str[1] == 'e') )
                {
                    // Got an alias here. If it's an All-in-Wonder VE, it's really a 7500.
                    // If it's a Radeon VE, it's really a 7000.
                    if( strstr(desc, "radeon") )
                        series = 7000;
                    else if( strstr(desc, "all-in-wonder") )
                        series = 7500;
                }
            }
        }
        if( !series )
        {
            hsStatusMessage( "== Using fudge factors for ATI Radeon chipset ==\n" );
            plDemoDebugFile::Write( "   Using fudge factors for ATI Radeon chipset" );
            ISetFudgeFactors( kATIRadeonChipset, record );
        }
    }
    /// Detect generic ATI chipset
    else if( ( strncmp( szDriver, "ati", 3 ) == 0 ) || ( strstr( desc, "ati " ) != nil ) )
    {
        hsStatusMessage( "== Using fudge factors for a generic ATI chipset ==\n" );
        plDemoDebugFile::Write( "   Using fudge factors for a generic ATI chipset" );
        ISetFudgeFactors( kATIGenericChipset, record );
    }

    //// Matrox-based Cards ///////////////////////////////////////////////////
    else if( (deviceID == 0x527)
        || strstr(desc, "parhelia") )
    {
        hsStatusMessage( "== Using fudge factors for a Matrox Parhelia chipset ==\n" );
        plDemoDebugFile::Write( "   Using fudge factors for a Matrox Millenium G400 chipset" );
        ISetFudgeFactors( kMatroxParhelia, record );
    }
    /// Detect Matrox G400 chipset
    else if( deviceID == 0x00000525 &&
                ( stricmp( szDriver, "g400d.dll" ) == 0 
                  || ( strstr( desc, "matrox" ) != nil && strstr( desc, "g400" ) != nil ) ) )
    {
        hsStatusMessage( "== Using fudge factors for a Matrox Millenium G400 chipset ==\n" );
        plDemoDebugFile::Write( "   Using fudge factors for a Matrox Millenium G400 chipset" );
        ISetFudgeFactors( kMatroxG400Chipset, record );
    }
    /// Detect generic Matrox chipset
    else if( strstr( desc, "matrox" ) != nil )
    {
        hsStatusMessage( "== Using fudge factors for a generic Matrox chipset ==\n" );
        plDemoDebugFile::Write( "   Using fudge factors for a generic Matrox chipset" );
        ISetFudgeFactors( kMatroxGenericChipset, record );
    }

    //// Other Cards //////////////////////////////////////////////////////////
    /// Detect NVidia RIVA TNT chipset
    else if( deviceID == 0x00000020 &&
             ( stricmp( szDriver, "nvdd32.dll" ) == 0 
               || strstr( desc, "nvidia riva tnt" ) != nil ) )
    {
        hsStatusMessage( "== Using fudge factors for an NVidia RIVA TNT chipset ==\n" );
        plDemoDebugFile::Write( "   Using fudge factors for an NVidia RIVA TNT chipset" );
        ISetFudgeFactors( kNVidiaTNTChipset, record );
        if( record.GetMemoryBytes() < 16 * 1024 * 1024 )
        {
            hsStatusMessage( "== (also fudging memory up to 16MB) ==\n" );
            plDemoDebugFile::Write( "   (also fudging memory up to 16MB)" );
            record.SetMemoryBytes( 16 * 1024 * 1024 );
        }
    }
    /// Detect Intel i810 chipset
    else if( deviceID == 0x00007125 &&
                ( stricmp( szDriver, "i81xdd.dll" ) == 0 
                  || ( strstr( desc, "intel" ) != nil && strstr( desc, "810" ) != nil ) ) )
    {
        hsStatusMessage( "== Using fudge factors for an Intel i810 chipset ==\n" );
        plDemoDebugFile::Write( "   Using fudge factors for an Intel i810 chipset" );
        ISetFudgeFactors( kIntelI810Chipset, record );
    }
    /// Detect STMicroelectronics KYRO chipset
    else if( deviceID == 0x00000010 && ( strstr( desc, "kyro" ) != nil ) )
    {
        hsStatusMessage( "== Using fudge factors for a KYRO chipset ==\n" );
        plDemoDebugFile::Write( "   Using fudge factors for a KYRO chipset" );
        ISetFudgeFactors( kKYROChipset, record );
    }
    /// Detect for a 3dfx Voodoo5
    else if( vendorID == 0x121a && deviceID == 0x00000009 && 
            stricmp( szDriver, "3dfxvs.dll" ) == 0 )
    {
        hsStatusMessage( "== Using fudge factors for a 3dfx Voodoo5 chipset ==\n" );
        plDemoDebugFile::Write( "   Using fudge factors for a 3dfx Voodoo5 chipset" );
        ISetFudgeFactors( k3dfxV5Chipset, record );
    }
    /// Detect for a GeForce-class card. We can be loose here because we want 
    /// to get ALL GeForce/2/256 cards
    else if( strstr( desc, "nvidia" ) != nil && strstr( desc, "geforce2" ) != nil )
    {
        hsStatusMessage( "== Using fudge factors for an NVidia GeForce2-based chipset ==\n" );
        plDemoDebugFile::Write( "   Using fudge factors for an NVidia GeForce2-based chipset" );
        ISetFudgeFactors( kNVidiaGeForce2Chipset, record );
    }
    else if( strstr( desc, "nvidia" ) != nil && strstr( desc, "geforce3" ) != nil )
    {
        hsStatusMessage( "== Using fudge factors for an NVidia GeForce3-based chipset ==\n" );
        plDemoDebugFile::Write( "   Using fudge factors for an NVidia GeForce3-based chipset" );
        ISetFudgeFactors( kNVidiaGeForce3Chipset, record );
    }
    else if( strstr( desc, "nvidia" ) != nil && strstr( desc, "geforce4 mx" ) != nil )
    {
        hsStatusMessage( "== Using fudge factors for an NVidia GeForce4MX-based chipset ==\n" );
        plDemoDebugFile::Write( "   Using fudge factors for an NVidia GeForce4MX-based chipset" );
        ISetFudgeFactors( kNVidiaGeForce4MXChipset, record );
    }
    else if( strstr( desc, "nvidia" ) != nil && strstr( desc, "geforce4" ) != nil )
    {
        hsStatusMessage( "== Using fudge factors for an NVidia GeForce4-based chipset ==\n" );
        plDemoDebugFile::Write( "   Using fudge factors for an NVidia GeForce4-based chipset" );
        ISetFudgeFactors( kNVidiaGeForce4Chipset, record );
    }
    else if( 
        strstr( desc, "nvidia" ) && strstr( desc, "geforce" )
        && (
            (deviceID == 0x101)
            ||(deviceID == 0x100)
            ||strstr(desc, "256")
            )
        )
    {
        hsStatusMessage( "== Using fudge factors for an NVidia GeForce-based chipset ==\n" );
        plDemoDebugFile::Write( "   Using fudge factors for an NVidia GeForce-based chipset" );
        ISetFudgeFactors( kNVidiaGeForceChipset, record );
    }
    else if( strstr( desc, "nvidia" ) != nil && strstr( desc, "geforce" ) != nil )
    {
        hsStatusMessage( "== Using fudge factors for an NVidia GeForceFX-based chipset ==\n" );
        plDemoDebugFile::Write( "   Using fudge factors for an NVidia GeForceFX-based chipset" );
        ISetFudgeFactors( kNVidiaGeForceFXChipset, record );
    }
    /// Detect for a TNT-based card and force it to >= 16MB memory, so we always use it
    else if( strstr( desc, "tnt" ) != nil && record.GetMemoryBytes() < 16 * 1024 * 1024 )
    {
        hsStatusMessage( "== NVidia TNT-based card detected. Fudging memory reading to 16MB ==\n" );
        plDemoDebugFile::Write( "   NVidia TNT-based card detected. Fudging memory reading to 16MB" );
        record.SetMemoryBytes( 16 * 1024 * 1024 );
    }

    /// Default fudge values
    else
    {
        hsStatusMessage( "== Using default fudge factors ==\n" );
        plDemoDebugFile::Write( "   Using default fudge factors" );
        ISetFudgeFactors( kDefaultChipset, record );
    }
}
#endif

//// ISetFudgeFactors /////////////////////////////////////////////////////////
//  Given a chipset ID, looks the values up in the CFT and sets the appropriate
//  values.

void    hsG3DDeviceSelector::ISetFudgeFactors( UInt8 chipsetID, hsG3DDeviceRecord &record )
{
    int     i, maxIDs, j;


    maxIDs = sizeof( dsCFTable ) / sizeof( dsCFTable[ 0 ] );

    /// Search for our chipset
    for( i = 0; i < maxIDs; i++ )
    {
        if( dsCFTable[ i ].fType == chipsetID )
        {
            /// Found it!

            // Flags to force set
            if( dsCFTable[ i ].fFlagsToSet != nil )
            {
                for( j = 0; j < dsCFTable[ i ].fFlagsToSet[ 0 ]; j++ )
                    record.SetCap( dsCFTable[ i ].fFlagsToSet[ j + 1 ] );
            }

            // Flags to force clear
            if( dsCFTable[ i ].fFlagsToClear != nil )
            {
                for( j = 0; j < dsCFTable[ i ].fFlagsToClear[ 0 ]; j++ )
                    record.SetCap( dsCFTable[ i ].fFlagsToClear[ j + 1 ], false );
            }

            // Suckiness
            record.SetZBiasRating( dsCFTable[ i ].fZSuckiness );

            // Max # of layers
            if( dsCFTable[ i ].fForceMaxLayers > 0 )
                record.SetLayersAtOnce( dsCFTable[ i ].fForceMaxLayers );

            // LOD bias rating
            record.SetLODBiasRating( dsCFTable[ i ].fLODRating );

            // Fog tweaks
            FogTweakTable   *fogTweaks = dsCFTable[ i ].fFogTweaks;

            record.SetFogApproxStarts( fogTweaks->fFogExpApproxStart, fogTweaks->fFogExp2ApproxStart );
            record.SetFogEndBias( fogTweaks->fFogEndBias );
            record.SetFogKneeParams( hsG3DDeviceRecord::kFogExp, fogTweaks->fFogExpKnee, fogTweaks->fFogExpKneeVal );
            record.SetFogKneeParams( hsG3DDeviceRecord::kFogExp2, fogTweaks->fFogExp2Knee, fogTweaks->fFogExp2KneeVal );

            if( record.GetCap(kCapsNoAA) )
            {
                int j;
                for( j = 0; j < record.GetModes().GetCount(); j++ )
                    record.GetModes()[j].ClearFSAATypes();
            }

            return;
        }
    }
}



///////////////////////////////////////////////////////////////////////////////
//
//  Demo Debug File functions
//  Created 10.10.2000 by Mathew Burrack @ Cyan, Inc.
//  Modified 10.11 mcn to conform (more) to coding standards.
//
///////////////////////////////////////////////////////////////////////////////

//// Local Globals ////////////////////////////////////////////////////////////

#if M3DDEMOINFO // Demo Debug Build
static plDemoDebugFile      sMyDDFWriter;

hsBool  plDemoDebugFile::fIsOpen = false;
FILE    *plDemoDebugFile::fDemoDebugFP = nil;
hsBool  plDemoDebugFile::fEnabled = false;
#endif


//// IDDFOpen /////////////////////////////////////////////////////////////////
//  Internal function--opens the demo debug file for writing. Returns true
//  if successful, false otherwise.

hsBool  plDemoDebugFile::IDDFOpen( void )
{
#if M3DDEMOINFO // Demo Debug Build
    char    fileName[] = "log/debug_info.dat";
    time_t  currTime;
    struct tm   *localTime;
    char    timeString[ 27 ];       // see definition of asctime()
    char    *c;


    /// Don't open if we're not enabled
    if( !fEnabled )
        return false;

    /// Open the file
    if( fDemoDebugFP == nil )
        fDemoDebugFP = fopen( fileName, "wt" );

    if( fDemoDebugFP == nil )
        return( fIsOpen = false );

    /// Write out a header line
    time( &currTime );
    localTime = localtime( &currTime );

    // Note: asctime includes a carriage return. Gotta strip...
    strcpy( timeString, asctime( localTime ) );
    c = strchr( timeString, '\n' );
    if( c != nil )
        *c = 0;

    fprintf( fDemoDebugFP, "\n--- Demo Debug Info File (Created %s) ---\n", timeString );

    /// All done!
    return( fIsOpen = true );
#else
    return false;
#endif
}

//// IDDFClose ////////////////////////////////////////////////////////////////
//  "Whatcha gonna do when the lightning strikes and hits you...."
//                          -- "Lightning Strikes", Yes, 1999


void    plDemoDebugFile::IDDFClose( void )
{
#if M3DDEMOINFO // Demo Debug Build
    if( fDemoDebugFP != nil )
    {
        // Write an exit line (fun fun)
        fputs( "--- End of Demo Debug Info File ---\n\n", fDemoDebugFP );

        // Close
        fclose( fDemoDebugFP );
    }

    fIsOpen = false;
#endif
}

//// Write ////////////////////////////////////////////////////////////////////
//  Writes a string to the DDF. If the DDF isn't open, opens it.

void    plDemoDebugFile::Write( char *string )
{
#if M3DDEMOINFO // Demo Debug Build
    if( !fIsOpen )
        IDDFOpen();

    if( fIsOpen )
        fprintf( fDemoDebugFP, "%s\n", string );
#endif
}

void    plDemoDebugFile::Write( char *string1, char *string2 )
{
#if M3DDEMOINFO // Demo Debug Build
    if( !fIsOpen )
        IDDFOpen();

    if( fIsOpen )
        fprintf( fDemoDebugFP, "%s: %s\n", string1, string2 );
#endif
}

void    plDemoDebugFile::Write( char *string1, Int32 value )
{
#if M3DDEMOINFO // Demo Debug Build
    if( !fIsOpen )
        IDDFOpen();

    if( fIsOpen )
        fprintf( fDemoDebugFP, "%s: %d (0x%x)\n", string1, value, value );
#endif
}