/*==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/>.

Additional permissions under GNU GPL version 3 section 7

If you modify this Program, or any covered work, by linking or
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
(or a modified version of those libraries),
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
licensors of this Program grant you additional
permission to convey the resulting work. Corresponding Source for a
non-source form of such a combination shall include the source code for
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
work.

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 "HeadSpin.h"
#include "hsWindows.h"

#include <ctime>



#include "hsG3DDeviceSelector.h"
#include "hsStream.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();
}

bool 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->ReadLE32();
    fWidth = s->ReadLE32();
    fHeight = s->ReadLE32();
    fDepth = s->ReadLE32();

    fZStencilDepths.Reset();
    uint8_t   count= s->ReadByte();
    while( count-- )
        fZStencilDepths.Append( s->ReadLE16() );

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

    fCanRenderToCubics = s->ReadBool();
}

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

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

    /// Version 9
    count = (uint8_t)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 const char* deviceNames[hsG3DDeviceSelector::kNumDevTypes] = {
        "Unknown",
        "Glide",
        "Direct3D",
        "OpenGL"
    };

    uint32_t 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->ReadLE32();
    hsAssert( fRecordVersion <= kCurrRecordVersion, "Invalid version number in hsG3DDeviceRecord::Read()" );
    if( fRecordVersion == kCurrRecordVersion )
    {
        fFlags = s->ReadLE32();
    }
    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->ReadLE32();

    int len;

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

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

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

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


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

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

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

    /// Version 7 stuff
    float       knee, kneeVal;
    knee = s->ReadLEFloat(); kneeVal = s->ReadLEFloat();
    SetFogKneeParams( kFogExp, knee, kneeVal );
    knee = s->ReadLEFloat(); kneeVal = s->ReadLEFloat();
    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->WriteLE32( fRecordVersion );

    s->WriteLE32(fFlags);

    s->WriteLE32(fG3DDeviceType);

    int len;

    len = strlen(fG3DDriverDesc);
    s->WriteLE32(len);
    s->Write(len, fG3DDriverDesc);

    len = strlen(fG3DDriverName);
    s->WriteLE32(len);
    s->Write(len, fG3DDriverName);

    len = strlen(fG3DDriverVersion);
    s->WriteLE32(len);
    s->Write(len, fG3DDriverVersion);

    len = strlen(fG3DDeviceDesc);
    s->WriteLE32(len);
    s->Write(len, fG3DDeviceDesc);

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

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

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

    /// Version 7 data
    s->WriteLEFloat( fFogKnees[ kFogExp ] );
    s->WriteLEFloat( fFogKneeVals[ kFogExp ] );
    s->WriteLEFloat( fFogKnees[ kFogExp2 ] );
    s->WriteLEFloat( 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(bool 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_t      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_t  hsG3DDeviceSelector::IAdjustDirectXMemory( uint32_t 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
}

bool    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_t 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
}

bool hsG3DDeviceSelector::GetDefault (hsG3DDeviceModeRecord *dmr)
{
    int32_t 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.

bool hsG3DDeviceSelector::ITestOpenGLRes( int width, int height, int bitDepth,
                                            hsG3DDeviceRecord &devRec, char *driverName )
{
    bool                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_t)c - (uint32_t)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_t  hsG3DDeviceSelector::ICreateTempOpenGLContext( HDC hDC, bool32 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_t)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 float (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
    {
        float    fFogExpApproxStart;
        float    fFogExp2ApproxStart;
        float    fFogEndBias;
        float    fFogExpKnee;        // Fog knees
        float    fFogExpKneeVal;
        float    fFogExp2Knee;
        float    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_t           fType;              // Our chipset ID
        uint32_t          *fFlagsToSet;       
        uint32_t          *fFlagsToClear;
        float        fZSuckiness;        // See above
        uint32_t          fForceMaxLayers;    // The max # of layers we REALLY want (0 to not force)
        float        fLODRating;
        FogTweakTable   *fFogTweaks;
    } CFTable;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    uint32_t  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
    uint32_t    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_t 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;

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


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

bool    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_t value )
{
#if M3DDEMOINFO // Demo Debug Build
    if( !fIsOpen )
        IDDFOpen();

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