// pch
#include "pch.h"

// my header
#include "GraphicsSystem.h"

// cegui
#include "CEGUISystem.h"

// ogre
#include "ParticleUniverseSystemManager.h"
// See header comments for IsDeviceLost()
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
#include "..\ogre\source\ogre_src_v1-7-1\RenderSystems\Direct3D9\include\OgreD3D9RenderSystem.h"
#include "..\ogre\source\ogre_src_v1-7-1\RenderSystems\Direct3D9\include\OgreD3D9DeviceManager.h"
#include "..\ogre\source\ogre_src_v1-7-1\RenderSystems\Direct3D9\include\OgreD3D9Device.h"
#endif

// game
#include "WormholeSystem.h"

// SDL
// - on linux, I'm getting kinda jumpy mouse movement with OIS.
// - it feels like every once in a while, there's a random(?) mouse jump, most noticable when turning fairly far in the same direction.
// - I think this might have something to do with the OIS linux code which needs to warp the (invisible) system mouse pointer back to the middle of the screen 
//   if it reaches the edge, and this causes a mouse jump somehow.
// - reading up on SDL, this is actually a problem with some implementations of a fps/tps mouse under SDL too, implementations which 
//   use SDL_WarpMouse to keep the mouse in the window (which is required because mouse movements beyond the edge of the window aren't reported).
//   However the SDL_WarpMouse call generates a (big) mouse movement event too.  This might be what's happening under linux OIS as well.
// - SDL provides a method that is not susceptible to this problem:
//
// From: http://sdl.beuc.net/sdl.wiki/SDL_MouseMotionEvent
// Quote:  
//    If the cursor is hidden (SDL_ShowCursor(0)) and the input is grabbed (SDL_WM_GrabInput(SDL_GRAB_ON)), 
//    then the mouse will give relative motion events even when the cursor reaches the edge of the screen. 
//    This is currently only implemented on Windows and Linux/Unix-alikes. 
//
// So, on linux, I'm going to change to SDL for handling of mouse movement, to get rid of the jumpy mouse problem.
//
// -- UPDATE:  no I'm not going to use SDL.
//    - The problem is that you can't just use SDL input without using an SDL window.  Even though ogre sorta supports using an externally created window
//      (the SDL one), I had trouble with it.  
//    - So instead, I just fixed / hacked the problem in OIS code - see LinuxMouse.cpp changes
//    - also see my OIS forum post here:  http://www.wreckedgames.com/forum/index.php/topic,5825.0.html
//
// -- UPDATE TO UPDATE:  
//    So I am going to use SDL *VIDEO* (not input) on linux to solve the issue described here:
//    http://www.ogre3d.org/forums/viewtopic.php?f=2&t=76967
//    See also header comments for OptionSystem::UseSDLVideo()
//
// #if OGRE_PLATFORM != OGRE_PLATFORM_WIN32
// #include "SDL/SDL.h"
// #endif

// Trying SDL again, this time because I WANT the window to fix the linux fullscreen dual-monitor bug.
// http://www.ogre3d.org/forums/viewtopic.php?f=2&t=76967
//
// The SDL version I'm using is:
// Source snapshot for SDL 2.0 (Updated Tue Apr 2 ):  SDL-2.0.tar.gz
// from:  http://www.libsdl.org/hg.php
// quote:  WARNING: UNDER CONSTRUCTION
//         Note: These are not official releases and may be unstable!
//
// I needed to make some modifications to the SDL2.0 code to get things to work with OIS.
// Details:  http://www.wreckedgames.com/forum/index.php/topic,1233.msg11996.html#msg11996
// Copy of my post:
/*
 I ran into the same problems, for the same reason (using SDL+OIS on linux).

To solve it, you need to modify the SDL source code.  I'm using SDL2.0

If you look in SDL_x11windows.c, you'll see this code:

    XSelectInput(display, w,
                 (FocusChangeMask | EnterWindowMask | LeaveWindowMask |
                 ExposureMask | ButtonPressMask | ButtonReleaseMask |
                 PointerMotionMask | KeyPressMask | KeyReleaseMask | 
                 PropertyChangeMask | StructureNotifyMask |
                 KeymapStateMask | fevent));


If you modify this to be:

    XSelectInput(display, w,
                 (FocusChangeMask | EnterWindowMask | LeaveWindowMask |
                 ExposureMask | 
                 PropertyChangeMask | StructureNotifyMask |
                 KeymapStateMask | fevent));


Then the error goes away, and you can use OIS + SDL.

I also removed the calls to these functions in SDL_video.c, although I'm not sure if this was necessary or not:

        SDL_KeyboardInit()
        SDL_MouseInit()
        SDL_TouchInit()
        SDL_QuitInit()   // Should this be removed?  Not sure...
*/


#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX
#include "SDL2/SDL.h"
#include "SDLlinux/SDL_test_common.h"
#include "SDL2/SDL_opengl.h"

// declared like done in SDL2/test/testgl2.c
static SDLTest_CommonState *state;
static SDL_GLContext context;
#endif

// Try to tell nvidia optimus to use the dedicated graphics card, although reports of this working are varied.
// From http://developer.download.nvidia.com/devzone/devcenter/gamegraphics/files/OptimusRenderingPolicies.pdf
// and http://www.ogre3d.org/forums/viewtopic.php?f=5&t=66688
extern "C" {
   _declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;
}


// game
#include "CameraSystem.h"
#include "DiagnosticsSystem.h"
#include "GameSystem.h"
#include "GraphicsUtil.h"
#include "InputSystem.h"
#include "Landscape.h"
#include "LoadSaveSystem.h"
#include "OgreGpuCommandBufferFlush.h"
#include "OptionSystem.h"
#include "Scene.h"
#include "SceneSystem.h"
#include "SoundSystem.h"
#include "SPZipArchive.h"
#include "TextDirectory.h"
#include "TextureArchive.h"
#include "TimeSystem.h"
#include "UIDialog.h"
#include "UIGalaxyView.h"
#include "GalaxyView3D.h"
#include "UIMainMenu.h"
#include "UISlideShow.h"
#include "UISystem.h"

// static member variables
GraphicsSystem* GraphicsSystem::s_pSingleton = 0;

#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
HWND GraphicsSystem::m_hwnd = 0;
#endif

// member functions
GraphicsSystem::GraphicsSystem()
   : m_pOgreRoot                 ( 0 )
   , m_pWindow                   ( 0 )
   // Note:  We'll need to add some more code if we want to support a different rendering system
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
   , m_strDefaultRenderSystem    ( "Direct3D9 Rendering Subsystem" )
#else
   , m_strDefaultRenderSystem    ( "OpenGL Rendering Subsystem" )
#endif
   , m_fDesktopAspectRatio       ( 4.f/3 )
   , m_fBrightness               ( DEFAULT_BRIGHTNESS )
   , m_msLastFrame               ( 0 )
   , m_eTerrainQuality           ( High_TerrainQualityType )
   , m_eTextureQuality           ( High_TextureQualityType )
   , m_pTextureArchiveFactory    ( 0 )
   , m_pSPZipArchiveFactory      ( 0 )
   , m_eShadowQuality            ( Medium_ShadowQualityType )
   , m_bUseSimpleWormholeGraphics( false )

#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX   
   , m_pSDLWindow                ( 0 )
#endif
{
}


GraphicsSystem::~GraphicsSystem()
{
   ASSERT( s_pSingleton );
   s_pSingleton = 0;

   m_gpuBufferFlush.stop();

   WindowEventUtilities::removeWindowEventListener( m_pWindow, this );
    
   /* This causes a crash.
      I think Ogre::ArchiveManager::~ArchiveManager handles deletion of all archive factories.
      Or is this a problem with how we handle memory in TextureArchiveFactory?
   if ( m_pTextureArchiveFactory )
   {
      OGRE_DELETE m_pTextureArchiveFactory;
   }
   */

   /* Ditto?  Haven't verified a crash here.
   if ( m_pSPZipArchiveFactory )
   {
      OGRE_DELETE m_pSPZipArchiveFactory;
   }
   */

   if (m_pOgreRoot)
   {
      OGRE_DELETE m_pOgreRoot;
   }
   
#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX
   if ( HasSDLWindow() )
   {
      SDLTest_CommonQuit(state);
   }
#endif
}


void GraphicsSystem::Create()
{
   // There can be only one.
   ASSERT( !s_pSingleton );
   s_pSingleton = new GraphicsSystem;
}


void GraphicsSystem::Init()
{
#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX
   if ( OptionSys()->UseSDLVideo() )
   {
      InitSDL();
      return;
   }
#endif
   
   LogSys()->Message("GraphicsSystem::Init", LML_CRITICAL );

   XMLNode graphicsNode=XMLNode::openFileHelper( "../../media/spreadsheet/graphics.xml","graphics");
   LoadXML( graphicsNode );

   // Create OgreRoot
   // - We're going to put our ogre.cfg file in our "AppData" directory,
   //   since it's writeable and so it can't go in "Program Files" or "ProgramData" (which is only writable with admin privs).
   path_string wstrConfigFile = LoadSaveSys()->GetAppDataFolder();
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
   wstrConfigFile.append( L"\\ogre.cfg" );
#else
   wstrConfigFile.append( "/ogre.cfg" );
#endif
   
   // If ogre.cfg doesn't exist yet (either the first run of the game, or the user deleted it),
   // try copying the one from bin/release.
   // - see discussion in LoadSaveSystem about how we must install to ProgramData, but we must write to AppData
   if ( !LoadSaveSys()->FileExists( wstrConfigFile ) )
   {
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
      bool bFileCopied = LoadSaveSys()->FileCopy( L"ogre.cfg" , wstrConfigFile, true );
#else
      bool bFileCopied = LoadSaveSys()->FileCopy( "ogre.cfg" , wstrConfigFile, true );
#endif

      if ( !bFileCopied )
      {
         // If this fails, then something is wrong with the installation (missing ogre.cfg), or maybe
         // we're running with the wrong working directory (which will kill everything anyway, as a bunch of
         // our XML reading code depends on it), or maybe there's some problem with permissions and we can't write
         // to the proper directory.
         LogSys()->AbortMessage( "Cannot find ogre.cfg file" );
      }
   }

   // Tried using SDL on linux, bailed out - see big note at top of this file.
   //#if OGRE_PLATFORM != OGRE_PLATFORM_WIN32
   //   SDL_Init(SDL_INIT_VIDEO);
   //   SDL_Surface *screen = SDL_SetVideoMode(640, 480, 0, SDL_OPENGL);
   //
   //   SDL_WM_GrabInput( SDL_GRAB_ON );
   //   SDL_ShowCursor( SDL_DISABLE );
   //#endif   
   
   // We need to temporarily undefine our memory leak macros (see vs_debug.h), or else the macros interfere with
   // Ogre's memory allocation macros (OGRE_NEW) used below.
#undef new
   // I needed to make some changes in ogre to support wstring config file names,
   // which is required in case the UserName of this computer includes unicode chars, which end up
   // in the AppData file path name.
   
   m_pOgreRoot = OGRE_NEW Root( "plugins.cfg", wstrConfigFile );
   
// Hmm, does it work to re-include our memory macros after this? 
// Hey neat, it does!
#include "vs_debug.h"

   if ( !m_pOgreRoot )
   {
      LogSys()->AbortMessage( "Failed to initialize Ogre::Root." );
      return;
   }

   m_pOgreRoot->addFrameListener(this);
   
   // Oops, after I added this line of code, my holo targets started appearing as big white blobs.
   // Removing:
   //    ParticleUniverse::ParticleSystemManager::getSingleton().setAutoLoadMaterials( false );
   //
   // Tracked this problem down to the code in:
   //    file:  ParticleUniversePrimitiveShapeSet.cpp,
   //    function:  PrimitiveShapeSet::setMaterialName
   // where the material is not loaded if we setAutoLoadMaterials to false.
   //
   // For some reason, it only affected my (very few) particles that use the SphereRenderer.
   // Maybe because the SphereRenderer looks to be completely implemented by ParticleUniverse, whereas
   // the BillboardRenderer uses a bunch of ogre code which would know nothing about this setting.

   // Setup the resource locations
   SetupResources();

   /* This was some test code I added when trying to get Ogre to display a custom desktop icon for this app (instead of the generic ubuntu "?" one).
      I noticed that in my ogre log, I always saw this:
      01:20:52: OGRE EXCEPTION(6:FileNotFoundException): Cannot locate resource GLX_icon.png in resource group General or any other group. in ResourceGroupManager::openResource at /home/sean/ogre/ogre_src_v1-7-4/OgreMain/src/OgreResourceGroupManager.cpp (line 753)
      It turns out that Ogre will try to set the desktop icon to GLX_icon.png - see code in OgreGLXWindow.cpp
      But the problem is that it tries to load this file from the resource system before we've initialized the resource group.
      I changed the ogre code to search in the "icon" resource group for GLX_icon.png, rather than "general" because I can't initialize resource group "general" this early
      in the initialization process - it causes a crash in OgreTechnique which tries to access some render-related code before it's ready, when parsing material files.
      (I have all my materials in "general".)
      But adding a new resource location "icon" with only the .png inside of it worked.
      The bad part is that the desktop icon on Ubuntu always looked low res and crappy, and it turns out the installer I want to use (nixstaller) sets up a proper .desktop entry
      for the app anyway, which includes icon info, so I'm just going to abandon the use of this code because the icon was too ugly
      
   ResourceGroupManager::getSingleton().addResourceLocation( "../../media/icon", "FileSystem", "icon" );
   ResourceGroupManager::getSingleton().initialiseResourceGroup( "icon" );
   */
   
   // Configure the graphics options
   RetrieveDesktopAspectRatio();
   ConfigureGraphics();

   // Create the render window
   try {
      m_pWindow = m_pOgreRoot->initialise(true, "Salvation Prophecy");
   }
   catch ( Exception& e)
   {
      // Unrecoverable error.
      // Ogre.log should indicate what went wrong.
      std::stringstream ssErr;
      ssErr << " Failed to initialize Ogre3D renderer: " << e.what();
      ExitWithNonRecoverableErrorMessageBox( ssErr.str() );
   }

   if ( !m_pWindow )
   {
      ExitWithNonRecoverableErrorMessageBox( "Failed to create render window.  Exiting." );
   }
   
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
   // Save the window handler for main.cpp's exception handler.
   m_pWindow->getCustomAttribute( "WINDOW", &m_hwnd );
#endif

   // The video card must support ps_2_x
   // - this is an ancient shader model, so any card that can't support ps_2_x would never be powerful enough to run the game, anyway.
   VerifyShaderModel();

   // Set default mipmap level (NB some APIs ignore this)
   TextureManager::getSingleton().setDefaultNumMipmaps(5);

   // Set the default texture filtering
   if ( OptionSys()->IsTextureFilteringEnabled() )
   {
      MaterialManager::getSingleton().setDefaultTextureFiltering( TFO_ANISOTROPIC );
      // possible anisotropy vals:  2,4,8,16.
      // - should this be configurable?
      MaterialManager::getSingleton().setDefaultAnisotropy( 4 );  
   }

   WindowEventUtilities::addWindowEventListener( m_pWindow, this );
    
   // see:  http://www.ogre3d.org/wiki/index.php/FlushGPUBuffer
   if ( OptionSys()->IsGpuCommandBufferFlushEnabled() )
   {
      m_gpuBufferFlush.start();
   }

   // Now that we have initialized our render system, we know what GPU vendor we're using.
   // So we can now support the "non-AMD" option for "enable gpu command buffer flush" (see options.cfg)
   if ( OptionSys()->GetBufferFlushRestrictedVendor() != GPU_UNKNOWN )
   {
      // Ok, we want to enable the buffer flush unless we've got a match with the restricted vendor.
      if ( GetGPUVendor() != OptionSys()->GetBufferFlushRestrictedVendor() )
      {
         OptionSys()->EnableGpuCommandBufferFlush( true );
         // ... This will call GraphicsSys()->GetGpuCommandBufferFlush().start();
      }
   }

   // Load shadow quality and stuff.
   LoadSettings();
}


#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX
void GraphicsSystem::InitSDL()
{
   // Note:  This function is the same as GraphicsSystem::Init except it uses SDL2 for xwindows
   LogSys()->Message("GraphicsSystem::InitSDL", LML_CRITICAL );

   XMLNode graphicsNode=XMLNode::openFileHelper( "../../media/spreadsheet/graphics.xml","graphics");
   LoadXML( graphicsNode );

   // Create OgreRoot
   // - We're going to put our ogre.cfg file in our "AppData" directory,
   //   since it's writeable and so it can't go in "Program Files" or "ProgramData" (which is only writable with admin privs).
   path_string wstrConfigFile = LoadSaveSys()->GetAppDataFolder();
   wstrConfigFile.append( "/ogre.cfg" );
   
   // If ogre.cfg doesn't exist yet (either the first run of the game, or the user deleted it),
   // try copying the one from bin/release.
   // - see discussion in LoadSaveSystem about how we must install to ProgramData, but we must write to AppData
   if ( !LoadSaveSys()->FileExists( wstrConfigFile ) )
   {
      bool bFileCopied = LoadSaveSys()->FileCopy( "ogre.cfg" , wstrConfigFile, true );

      if ( !bFileCopied )
      {
         // If this fails, then something is wrong with the installation (missing ogre.cfg), or maybe
         // we're running with the wrong working directory (which will kill everything anyway, as a bunch of
         // our XML reading code depends on it), or maybe there's some problem with permissions and we can't write
         // to the proper directory.
         LogSys()->AbortMessage( "Cannot find ogre.cfg file" );
      }
   }
   
   // We need to temporarily undefine our memory leak macros (see vs_debug.h), or else the macros interfere with
   // Ogre's memory allocation macros (OGRE_NEW) used below.
#undef new
   // I needed to make some changes in ogre to support wstring config file names,
   // which is required in case the UserName of this computer includes unicode chars, which end up
   // in the AppData file path name.
   
   m_pOgreRoot = OGRE_NEW Root( "plugins.cfg", wstrConfigFile );
   
// Hmm, does it work to re-include our memory macros after this? 
// Hey neat, it does!
#include "vs_debug.h"

   if ( !m_pOgreRoot )
   {
      LogSys()->AbortMessage( "Failed to initialize Ogre::Root." );
      return;
   }

   m_pOgreRoot->addFrameListener(this);
   
   // Oops, after I added this line of code, my holo targets started appearing as big white blobs.
   // Removing:
   //    ParticleUniverse::ParticleSystemManager::getSingleton().setAutoLoadMaterials( false );
   //
   // Tracked this problem down to the code in:
   //    file:  ParticleUniversePrimitiveShapeSet.cpp,
   //    function:  PrimitiveShapeSet::setMaterialName
   // where the material is not loaded if we setAutoLoadMaterials to false.
   //
   // For some reason, it only affected my (very few) particles that use the SphereRenderer.
   // Maybe because the SphereRenderer looks to be completely implemented by ParticleUniverse, whereas
   // the BillboardRenderer uses a bunch of ogre code which would know nothing about this setting.

   // Setup the resource locations
   SetupResources();

   // Configure the graphics options
   RetrieveDesktopAspectRatio();
   ConfigureGraphics();   



   //
   // From SDL_test_common.c
   //

   // "[--video driver] [--renderer driver] [--info all|video|modes|render|event] [--log all|error|system|audio|video|render|input] [--display N] [--fullscreen | --fullscreen-desktop | --windows N] [--title title] [--icon icon.bmp] [--center | --position X,Y] [--geometry WxH] [--min-geometry WxH] [--max-geometry WxH] [--depth N] [--refresh R] [--vsync] [--noframe] [--resize] [--minimize] [--maximize] [--grab]"
   
   // Since SDL_test_common.c (which I use in this project) has support for a bunch of command line args, I might as well process those here.
   // Maybe it'll help out downline with support for a shipped version of the game.
   int argc = GameSys()->GetArgC();
   char** argv = GameSys()->GetArgV();

    /* Initialize test framework */
    state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO);
    if (!state) {
        return;
    }

   // Some extra flags for opengl apps (like from SDL2/test/testgl2.c)
   int fsaa = 0;
   int accel = -1;
   for (int i = 1; i < argc;) {
      int consumed;

      consumed = SDLTest_CommonArg(state, i);
      if (consumed == 0) {
         if (SDL_strcasecmp(argv[i], "--fsaa") == 0) {
            ++fsaa;
            consumed = 1;
         } else if (SDL_strcasecmp(argv[i], "--accel") == 0 && i+1 < argc) {
            accel = atoi(argv[i+1]);
            consumed = 2;
         } else {
            consumed = -1;
         }
      }
      if (consumed < 0) {
         // Unlike in the SDL samples, I'm just going to ignore bad arguments.
         // fprintf(stderr, "Usage: %s %s [--fsaa] [--accel n]\n", argv[0], SDLTest_CommonUsage(state));
         ++i;
         continue;
      }
      i += consumed;
   }

   // -- end SDL_test_common.c code --
    
   // Set the window title
   state->window_title = "Salvation Prophecy";

   // Let's make the window resizable
   state->window_flags |= SDL_WINDOW_RESIZABLE;

   // Parse options from ogre.cfg
   ConfigFile cfg;

   try {
      // Don't trim whitespace
      cfg.load(wstrConfigFile, "\t:=", false);
   }
   catch (Exception& e)
   {
      if (e.getNumber() == Exception::ERR_FILE_NOT_FOUND)
      {
         LogSys()->AbortMessage( "GraphicsSystem::InitSDL:  Cannot find ogre.cfg file" );
      }
      else
      {
         throw;
      }
   }

   // Now, parse the options in ogre.cfg and feed them to SDL.
   // -- note:  we want to respect ogre.cfg settings regardless of whether we're using SDL or not.

   // We need to pass our vsync setting to our createRenderWindow call below, so let's detect our options.cfg VSync setting here.
   String strVSync = "No";
   // Ditto for FSAA, although actually that doesn't seem to be working properly on linux.
   String strFSAA = "0";
   
   ConfigFile::SectionIterator iSection = cfg.getSectionIterator();
   while (iSection.hasMoreElements())
   {
      const String& renderSystem = iSection.peekNextKey();
      const ConfigFile::SettingsMultiMap& settings = *iSection.getNext();

      if ( !StringUtil::match( renderSystem, "OpenGL Rendering Subsystem", false ) )
      {
         // DirectX stuff?
         continue;
      }

      ConfigFile::SettingsMultiMap::const_iterator itSetting;
      for (itSetting = settings.begin(); itSetting != settings.end(); ++itSetting)
      {
         String strOption = itSetting->first;  
         String strValue = itSetting->second;
         SDLTest_OgreConfig( state, strOption.c_str(), strValue.c_str() );
         
         if ( StringUtil::match( strOption, "VSync", false ) )
         {
            strVSync = strValue;
         }

         else if ( StringUtil::match( strOption, "FSAA", false ) )
         {
            strFSAA = strValue;
         }
      }
   }

   /* SDL is no longer doing anything opengl related - see "SDL + Ogre happiness" comments in this file.
   // Set OpenGL parameters 
   // - this SDL-related code is largely borrowed from the SDL2 test program, testgl2.c
   state->window_flags |= SDL_WINDOW_OPENGL;
   state->gl_red_size = 5;
   state->gl_green_size = 5;
   state->gl_blue_size = 5;
   state->gl_depth_size = 16;
   state->gl_double_buffer = 1;
   
   // In SDL_test_common.c this is handled by the --accel flag, should we add this here?
   // state->gl_accelerated = accel;

   // Do we need to request a specific version of gl?
   // state->gl_major_version = ???;
   // state->gl_minor_version = ???;
   */
   
   // Hmm, it seems SDL will create an opengl context even if we skip the SDL_WINDOW_OPENGL flag unless we set this:
   state->skip_renderer = SDL_TRUE;
   // and note that I discovered this when diagnosing a crash in the SDL shutdown code.
   // Maybe it was some complication having 2 opengl contexts (the SDL one and the opengl one).
   // The crash was in function GL_DestroyRenderer file SDL_render_gl.c, line:  SDL_GL_DeleteContext(data->context);
   // And note that this only happened if I'd been in a few different scenes before shutdown.
   // Anyway, this fixes it.
    
   // Initialize SDL using the initialization code from SDL_test_common.c, which is used to run
   // the test programs in SDL2.
   if (!SDLTest_CommonInit(state)) {
      LogSys()->AbortMessage( "SDL initialization failed." );
   }

   /* SDL is no longer doing anything opengl related - see "SDL + Ogre happiness" comments in this file.
   // Create OpenGL context
   context = SDL_GL_CreateContext(state->windows[0]);
   if (!context) {
      String strError = "SDL_GL_CreateContext failed: ";
      strError += SDL_GetError();
      LogSys()->AbortMessage( strError );
   }
   */

   if (state->render_flags & SDL_RENDERER_PRESENTVSYNC) {
      // try late-swap-tearing first. If not supported, try normal vsync.
      if (SDL_GL_SetSwapInterval(-1) == -1) {
          SDL_GL_SetSwapInterval(1);
      }
   } else {
      SDL_GL_SetSwapInterval(0);  // disable vsync.
   }

   SDL_DisplayMode mode;
   SDL_GetCurrentDisplayMode(0, &mode);
   std::stringstream ssDisplayMode;
   ssDisplayMode 
      << "SDL_DisplayMode:" << std::endl
      << "   Screen BPP    : " << SDL_BITSPERPIXEL(mode.format) << std::endl
      // Swap Interval seems to always display 0 here, even if we're in functional vsync mode.  Not sure why.
      << "   Swap Interval : " << SDL_GL_GetSwapInterval() << std::endl << std::endl
      << "   Vendor        : " << glGetString(GL_VENDOR) << std::endl 
      << "   Renderer      : " << glGetString(GL_RENDERER) << std::endl 
      << "   Version       : " << glGetString(GL_VERSION) << std::endl << std::endl;

   int status = 0;
   int value = 0;
   status = SDL_GL_GetAttribute(SDL_GL_RED_SIZE, &value);
   if (!status) {
      ssDisplayMode << "   SDL_GL_RED_SIZE: "<< value << std::endl;
   } else {
      ssDisplayMode << "   Failed to get SDL_GL_RED_SIZE: " << SDL_GetError() << std::endl;
   }
   status = SDL_GL_GetAttribute(SDL_GL_GREEN_SIZE, &value);
   if (!status) {
      ssDisplayMode << "   SDL_GL_GREEN_SIZE: "<< value << std::endl;
   } else {
      ssDisplayMode << "   Failed to get SDL_GL_GREEN_SIZE: " << SDL_GetError() << std::endl;
   }
   status = SDL_GL_GetAttribute(SDL_GL_BLUE_SIZE, &value);
   if (!status) {
      ssDisplayMode << "   SDL_GL_BLUE_SIZE: "<< value << std::endl;
   } else {
      ssDisplayMode << "   Failed to get SDL_GL_BLUE_SIZE: " << SDL_GetError() << std::endl;
   }
   status = SDL_GL_GetAttribute(SDL_GL_DEPTH_SIZE, &value);
   if (!status) {
      ssDisplayMode << "   SDL_GL_DEPTH_SIZE: "<< value << std::endl;
   } else {
      ssDisplayMode << "   Failed to get SDL_GL_DEPTH_SIZE: " << SDL_GetError() << std::endl;
   }
   status = SDL_GL_GetAttribute(SDL_GL_MULTISAMPLEBUFFERS, &value);
   if (!status) {
      ssDisplayMode << "   SDL_GL_MULTISAMPLEBUFFERS: "<< value << std::endl;
   } else {
      ssDisplayMode << "   Failed to get SDL_GL_MULTISAMPLEBUFFERS: " << SDL_GetError() << std::endl;
   }
   status = SDL_GL_GetAttribute(SDL_GL_MULTISAMPLESAMPLES, &value);
   if (!status) {
      ssDisplayMode << "   SDL_GL_MULTISAMPLESAMPLES: "<< value << std::endl;
   } else {
      ssDisplayMode << "   Failed to get SDL_GL_MULTISAMPLESAMPLES: " << SDL_GetError() << std::endl;
   }
   status = SDL_GL_GetAttribute(SDL_GL_ACCELERATED_VISUAL, &value);
   if (!status) {
      ssDisplayMode << "   SDL_GL_ACCELERATED_VISUAL: "<< value << std::endl;
   } else {
      ssDisplayMode << "   Failed to get SDL_GL_ACCELERATED_VISUAL: " << SDL_GetError() << std::endl;
   }

   LogSys()->Message( ssDisplayMode.str(), LML_CRITICAL );

   // Since we only ever use 1 SDL window, this should be ok...
   m_pSDLWindow = state->windows[0];
   
   // --- end SDL initialization ---

   
   
   // Create the render window
   try {
      m_pOgreRoot->initialise(false, "Salvation Prophecy");
      
      // SDL + Ogre happiness
      //
      // Notes about the params we can pass to Ogre::createRenderWindow to work with an SDL window.
      // There's 3 relevent params we can play with here:
      //      misc["currentGLContext"] = String("True");
      //      misc["externalGLControl"] = String("True");
      //      misc["parentWindowHandle"] = ...
      //
      // These are discussed a bit here:
      // http://www.ogre3d.org/tikiwiki/Using+SDL+Input
      //
      // currentGLContext:  this means that ogre will not set up it's own GLContext, but rather use the one
      // established by SDL.  I tried using this at first, and it was a DEEP rabbit hole of problems.  Most
      // notably, all of our RTTs (shadows, CEGUI's AutoRenderingSufrace, SceneImage stuff, etc) stopped working.
      // I tried MANY MANY hours to get this to work, but with no luck.  Finally, I switched approaches and
      // decided NOT to call SDL_GL_CreateContext at all, and instead just rely on SDL to handle the windowing,
      // while letting ogre create the opengl context as normal.  This worked out beautifully as soon as I
      // switched to using the "parentWindowHandle" param instead, as seen below.
      //
      // externalGLControl:  This param mostly seems to just determine whether ogre should swap the window buffer or not.
      //
      // parentWindowHandle:  This is the magic for working with SDL, if we just want it to do windowing code and 
      // nothing else.  Basically, it seems if we let SDL create the window, then pass that window handle along to
      // ogre via this param, then ogre works happily inside the window created by SDL, which is exactly what we want.
      
      
      Ogre::NameValuePairList misc;
     
      String strWindowHandle = StringConverter::toString( GraphicsUtil::GetWindowHandle() );
      misc["parentWindowHandle"] = strWindowHandle; 

      // ... as is done in GLXGLSupport::createWindow, although with this code path we don't call that function since we're
      //     using a parentWindowHandle.
      misc["vsync"] = strVSync;
      misc["FSAA"] = strFSAA;
      
      m_pWindow = m_pOgreRoot->createRenderWindow("MainRenderWindow", state->window_w, state->window_h, false, &misc);
      m_pWindow->setVisible( true );
      
      // Abandoned - see notes in OgreRoot.cpp
      // m_pOgreRoot->addSwapWindowBuffersListener( this );
   }
   
   catch ( Exception& e)
   {
      // Unrecoverable error.
      // Ogre.log should indicate what went wrong.
      std::stringstream ssErr;
      ssErr << " Failed to initialize Ogre3D renderer: " << e.what();
      ExitWithNonRecoverableErrorMessageBox( ssErr.str() );
   }

   if ( !m_pWindow )
   {
      ExitWithNonRecoverableErrorMessageBox( "Failed to create render window.  Exiting." );
   }

   // The video card must support ps_2_x
   // - this is an ancient shader model, so any card that can't support ps_2_x would never be powerful enough to run the game, anyway.
   VerifyShaderModel();

   // Set default mipmap level (NB some APIs ignore this)
   TextureManager::getSingleton().setDefaultNumMipmaps(5);

   // Set the default texture filtering
   if ( OptionSys()->IsTextureFilteringEnabled() )
   {
      MaterialManager::getSingleton().setDefaultTextureFiltering( TFO_ANISOTROPIC );
      // possible anisotropy vals:  2,4,8,16.
      // - should this be configurable?
      MaterialManager::getSingleton().setDefaultAnisotropy( 4 );  
   }

   WindowEventUtilities::addWindowEventListener( m_pWindow, this );
    
   // see:  http://www.ogre3d.org/wiki/index.php/FlushGPUBuffer
   if ( OptionSys()->IsGpuCommandBufferFlushEnabled() )
   {
      m_gpuBufferFlush.start();
   }

   // Now that we have initialized our render system, we know what GPU vendor we're using.
   // So we can now support the "non-AMD" option for "enable gpu command buffer flush" (see options.cfg)
   if ( OptionSys()->GetBufferFlushRestrictedVendor() != GPU_UNKNOWN )
   {
      // Ok, we want to enable the buffer flush unless we've got a match with the restricted vendor.
      if ( GetGPUVendor() != OptionSys()->GetBufferFlushRestrictedVendor() )
      {
         OptionSys()->EnableGpuCommandBufferFlush( true );
         // ... This will call GraphicsSys()->GetGpuCommandBufferFlush().start();
      }
   }

   // Load shadow quality and stuff.
   LoadSettings();
}
#endif


void GraphicsSystem::Tick( float i_fSecFrame )
{
   // Measure how long our frame takes
   static String strTimerID( "Graphics" );
   DiagnosticsSys()->StartTimer( strTimerID );

   // Added this timer to see how much time we're wasting by not using frameRenderingQueued
   // to drive our game loop.
   // FrameRenderingQueued is supposed to be a bit quicker, because it gets called before the
   // buffers are switched, whereas renderOneFrame waits until after that is done.
   // Very little difference on my dev machine, but should probably check on a lower powered machine.
   static String strRenderQueuedTimerID( "frameRenderingQueued" );
   DiagnosticsSys()->StartTimer( strRenderQueuedTimerID );

   
   /* Appears to be unnecessary.  Toyed around with this while trying to fix FBO while using currentGLContext param
   glClearColor(0.0, 0.0, 0.0, 1.0);
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   */
   
   // We play one stale frame when shutting down. The "are you sure" confirmation dialog
   // pops back up after you click accept for the last frame.
   if ( !GameSys()->IsShuttingDown() )
   {
      // see header notes
      ClearSceneManagerPassMaps();

      m_pOgreRoot->renderOneFrame();
   }

   // We need to provide a config option to limit FPS.
   // This user with a super computer overheated:  http://www.salvationprophecy.com/forum/viewtopic.php?f=4&t=448&p=1283&hilit=invasion#p1283
   // I'm going to do this AFTER renderOneFrame(), because otherwise I expect we'd get choppy motion, as all our physics calculations will have been
   // based on the amount of time that has passed since the last frame.
   LimitMaxFPS();

   DiagnosticsSys()->StopTimer( strTimerID );

   WindowEventUtilities::messagePump();
   
#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX
   if ( HasSDLWindow() )
   {
      SDL_Event event;
      while (SDL_PollEvent(&event)) 
      {
         if ( event.type == SDL_WINDOWEVENT )
         {
            switch (event.window.event) 
            {
               case SDL_WINDOWEVENT_SIZE_CHANGED:
               {
                  SDL_Window *window = SDL_GetWindowFromID(event.window.windowID);
                  if (window) {
                     for (int i = 0; i < state->num_windows; ++i) {
                           if (window == state->windows[i] &&
                              (state->window_flags & SDL_WINDOW_RESIZABLE)) {
                              SDL_Rect viewport;

                              viewport.x = 0;
                              viewport.y = 0;
                              SDL_GetWindowSize(window, &viewport.w, &viewport.h);
                              SDL_RenderSetViewport(state->renderers[i], &viewport);

                              RenderWindow* pWindow = GraphicsSys()->GetRenderWindow();
                              pWindow->resize( viewport.w, viewport.h );
                              windowResized( pWindow );
                        }
                     }
                  }
               }
               break;

               case SDL_WINDOWEVENT_CLOSE:
               {
                  SDL_Window *window = SDL_GetWindowFromID(event.window.windowID);
                  if (window) {
                     // FIXME call this here or somewhere?  Or does the SDL quit function handle it?  SDL_DestroyWindow(window);
                     GameSys()->RequestShutdown();
                  }
               }
               break;
            }
         }
      }
      // We are no longer using externalGLControl.  See notes in InitSDL
      // SDL_GL_SwapWindow( m_pSDLWindow );
   }
#endif
}


void GraphicsSystem::LoadXML( XMLNode& i_node )
{
   //
   // Load m_mapTextureQualityOverride
   //
   std::vector< String > vecTextureName;
   std::vector< TextureQualitySetting > vecTextureQualitySetting;

   // Our XML class doesn't have functionality to load a map, so load 2 vectors then insert
   // the values into m_mapTextureQualityOverride
   LoadDataMember( i_node, "texture", vecTextureName, XML::DATA_REQUIRED );
   LoadDataMember( i_node, "quality", vecTextureQualitySetting, XML::DATA_REQUIRED );

   ASSERT( vecTextureName.size() == vecTextureQualitySetting.size() );

   for ( int i = 0; i < (int)vecTextureName.size(); ++i )
   {
      const String& strTextureName = vecTextureName[i];
      const TextureQualitySetting& setting = vecTextureQualitySetting[i];

      m_mapTextureQualityOverride[ strTextureName ] = setting;
   }
}


void GraphicsSystem::LoadSettings()
{
   path_string wstrSettingsFile = LoadSaveSys()->GetSettingsFolder();

#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
   wstrSettingsFile.append( L"\\graphics.xml" );
#else
   wstrSettingsFile.append( "/graphics.xml" );
#endif
   
   if ( !LoadSaveSys()->FileExists( wstrSettingsFile ) ) 
   {
      // That's fine, this is probably a new install, and the user hasn't changed their sound
      // settings yet.
      return;
   }

#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
   XMLNode settingsNode = XMLNode::openFileHelperW( wstrSettingsFile.c_str(), "settings" );
#else
   XMLNode settingsNode = XMLNode::openFileHelper( wstrSettingsFile.c_str(), "settings" );
#endif

   LoadDataMember( settingsNode, "brightness", m_fBrightness, XML::DATA_OPTIONAL );
   LoadDataMember( settingsNode, "terrain_quality", TerrainQualityTypeStrings, (int&)m_eTerrainQuality, XML::DATA_OPTIONAL );
   LoadDataMember( settingsNode, "texture_quality", TextureQualityTypeStrings, (int&)m_eTextureQuality, XML::DATA_OPTIONAL );
   LoadDataMember( settingsNode, "shadow_quality", ShadowQualityTypeStrings, (int&)m_eShadowQuality, XML::DATA_OPTIONAL );
   LoadDataMember( settingsNode, "simple_wormhole_graphics", m_bUseSimpleWormholeGraphics, XML::DATA_OPTIONAL );
}


void GraphicsSystem::SaveSettings()
{
   XMLNode settingsNode = XMLNode::createXMLTopNode( "settings" );

   SaveDataMember( settingsNode, "brightness", GetBrightness() );
   SaveDataMember( settingsNode, "terrain_quality", TerrainQualityTypeStrings, GetTerrainQuality() );
   SaveDataMember( settingsNode, "texture_quality", TextureQualityTypeStrings, GetTextureQuality() );
   SaveDataMember( settingsNode, "shadow_quality", ShadowQualityTypeStrings, GetShadowQuality() );
   SaveDataMember( settingsNode, "simple_wormhole_graphics", m_bUseSimpleWormholeGraphics );

   // Format the settings filename.
   path_string wstrSettingsFile = LoadSaveSys()->GetSettingsFolder();
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
   wstrSettingsFile.append( L"\\graphics.xml" );
#else
   wstrSettingsFile.append( "/graphics.xml" );
#endif

   // Write out our settings to the graphics.xml file
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
   XMLError eError = settingsNode.writeToFileW( wstrSettingsFile.c_str() );
#else
   XMLError eError = settingsNode.writeToFile( wstrSettingsFile.c_str() );
#endif

   if ( eError != eXMLErrorNone )
   {
      ASSERT( false );

      String strError = "XML Save Error (GraphicsSystem::SaveSettings):  ";
      strError += XMLNode::getError( eError );
      LogSys()->Error( strError, Ogre::LML_CRITICAL );
   }
}


Root* GraphicsSystem::GetOgreRoot() const
{
   return m_pOgreRoot;
}


RenderWindow* GraphicsSystem::GetRenderWindow() const
{
   return m_pWindow;
}


SceneManager* GraphicsSystem::CreateSceneManager( SceneTypeMask i_eSceneType, const String& i_strName )
{
   SceneManager* pSceneMgr = GetOgreRoot()->createSceneManager( i_eSceneType, i_strName );

   // Adding some logging to the creation and destruction of SceneManagers.  
   // I got a crash recently where the culprit was a bad OgreRoot::mCurrentSceneManager pointer.  
   // I've seen this problem before, when I've destroyed a SceneManager and then made a call to 
   // MovableObject::IsVisible or some other function which uses this pointer, before the next call to RenderOneFrame.
   // I haven't checked into it, but the point is that OgreRoot::mCurrentSceneManager can point to a
   // destroyed SceneManager for some amount of time.
   // The crash mentioned above may have had something to do with a device lost kind of scenario, I think 
   // the window may have been minimized or screen-saver activated or something when the crash happened - 
   // I had left the game running in demo mode for a while.

   std::stringstream ssLog;
   ssLog << "[GraphicsSystem::CreateSceneManager]  name=" << i_strName << ", pSceneMgr=" << pSceneMgr;
   LogSys()->Message( ssLog.str(), LML_CRITICAL );

   // Ok, I think I see how this OgreRoot::mCurrentSceneManager problem is happening.
   // Inside of SceneSystem::Tick, we first activate the new Scene, then tick the new Scene.
   // At this point, OgreRoot::mCurrentSceneManager is still pointing to the recently 
   // deactivated and deleted SceneManager.  
   // So now we're in a situation where we have 1 iteration of SceneSystem::TickActiveScene
   // with a dangling OgreRoot::mCurrentSceneManager.  We run into problems with this dangling
   // pointer with certain Ogre calls, such as MovableObject::IsVisible.
   // 
   // Note Ogre3D's header comments for OgreRoot::_getCurrentSceneManager():
   // "This is only intended for internal use; it is only valid during the rendering of a frame."
   //
   // This comment seems slightly askew to me, since MovableObject::IsVisible is a public function
   // which accesses this pointer, and presumably is ok for me to be using.  
   //
   // Anyway, the problem is that OgreRoot::mCurrentSceneManager doesn't get updated until the
   // next call to SceneManager::_renderScene.  I'm going to fix this whole problem by manually
   // calling OgreRoot::_setCurrentSceneManager as we create and destroy SceneManagers, to make 
   // sure that we never have this dangling pointer.
   //
   // Side note:
   // In a case where we have multiple concurrent SceneManagers, we'll arbitrarily be pointing at
   // one of them until the next call to SceneManager::_renderScene.  But I guess this is better
   // than pointing at deleted memory.  And it is unusual to have >1 SceneManager, I think the only
   // time we do this is in our odd UIMainMenu setup, with the foreground and background SceneManagers.
   //
   // Update:
   // It looks like there are some changes in Ogre 1.7 that may affect this problem.
   // Haven't really looked into it though, because everything is currently working fine as is.
   // There may be some unnecessary code sticking around a few places to deal with an issue that might
   // be fixed in Ogre3D 1.7+

   /* 
   GetOgreRoot()->_setCurrentSceneManager( pSceneMgr );
   */

   /* Just for reference, I'm going to post the stack of a crash that was the result of this dangling pointer bug.
      Obviously this stack may not be totally accurate as future changes to the code base are made, but we can
      always go back in revision history if more clarity is needed.

   -- Call Stack for Crash --
   OgreMain_d.dll!Ogre::Viewport::getVisibilityMask()  Line 284 + 0x26 bytes	C++
   OgreMain_d.dll!Ogre::SceneManager::_getCombinedVisibilityMask()  Line 6321 + 0x1a bytes	C++
   OgreMain_d.dll!Ogre::MovableObject::isVisible()  Line 229 + 0xe bytes	C++
   Space.exe!Mobile::IsVisible()  Line 1980 + 0x20 bytes	C++
   Space.exe!AsteroidField::UpdateAsteroidDensity(const Ship * i_pShip=0x2d7fcec8)  Line 176 + 0x8 bytes	C++
   Space.exe!AsteroidField::Update(const Ship * i_pShip=0x2d7fcec8, float i_fSecFrame=0.013000000)  Line 82	C++
   Space.exe!Stellar::UpdateAsteroidField(float i_fSecFrame=0.013000000)  Line 490	C++
   Space.exe!Stellar::Tick(float i_fSecFrame=0.013000000)  Line 169	C++
   Space.exe!SceneSystem::TickActiveScene(float i_fSecFrame=0.013000000)  Line 1277 + 0x1e bytes	C++
   Space.exe!SceneSystem::Tick(float i_fSecFrame=0.013000000)  Line 129	C++
   Space.exe!GameSystem::TickSystems(float i_fSecFrame=0.013000000)  Line 1139	C++
   Space.exe!GameSystem::Tick()  Line 176	C++
   Space.exe!GameSystem::Run()  Line 142	C++
   Space.exe!WinMain(HINSTANCE__ * hInst=0x00400000, HINSTANCE__ * __formal=0x00000000, char * strCmdLine=0x002238cf, HINSTANCE__ * __formal=0x00000000)  Line 44	C++
   Space.exe!__tmainCRTStartup()  Line 589 + 0x35 bytes	C
   Space.exe!WinMainCRTStartup()  Line 414	C
   kernel32.dll!77e74911() 	
   [Frames below may be incorrect and/or missing, no symbols loaded for kernel32.dll]	
   ntdll.dll!77cee4b6() 	
   ntdll.dll!77cee489() 	
   -- end Call Stack for Crash --

   And the crash is a result of accessing the dangling *sm pointer in Ogre::MovableObject::isVisible()

   -- code for from ogremovableobject.cpp --
    bool MovableObject::isVisible(void) const
    {
        if (!mVisible || mBeyondFarDistance || mRenderingDisabled)
            return false;

        SceneManager* sm = Root::getSingleton()._getCurrentSceneManager();
        if (sm && !(mVisibilityFlags & sm->_getCombinedVisibilityMask()))
            return false;

        return true;
    }
   -- end code for from ogremovableobject.cpp --

   The call stack goes a couple extra functions in, but this isn't unusual for a dangling pointer crash.
   *sm refers to deleted memory.
   */

   return pSceneMgr;
}


void GraphicsSystem::DestroySceneManager( SceneManager* i_pSceneMgr )
{
   if ( !i_pSceneMgr )
   {
      ASSERT( false );
      return;
   }

   std::stringstream ssLog;
   ssLog << "[GraphicsSystem::DestroySceneManager]  name=" << i_pSceneMgr->getName() << ", pSceneMgr=" << i_pSceneMgr;
   LogSys()->Message( ssLog.str(), LML_CRITICAL );

   GetOgreRoot()->destroySceneManager( i_pSceneMgr );

   // See abundant comments in GraphicsSystem::CreateSceneManager
   /* 
   GetOgreRoot()->_setCurrentSceneManager( 0 );
   */
}

   
bool GraphicsSystem::IsDeviceLost() const
{
   // See copious header comments for this function.
   
   // This is/was a problem on windows only, and has windows specific (DirectX-related) code:
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32

   RenderSystem* pRS = m_pOgreRoot->getRenderSystem();
   if ( !pRS )
   {
      ASSERT( false );
      ExitWithNonRecoverableErrorMessageBox( "GraphicsSystem::IsDeviceLost() - cannot retrieve RenderSystem" );
      return false;
   }

   D3D9RenderSystem* pD3D9RS = dynamic_cast<D3D9RenderSystem*>( pRS );
   if ( !pD3D9RS )
   {
      ASSERT( false );
      ExitWithNonRecoverableErrorMessageBox( "GraphicsSystem::IsDeviceLost() - cannot retrieve D3D9RenderSystem" );
      return false;
   }

   D3D9DeviceManager* pDM = pD3D9RS->getDeviceManager();
   if ( !pDM )
   {
      ASSERT( false );
      ExitWithNonRecoverableErrorMessageBox( "GraphicsSystem::IsDeviceLost() - cannot retrieve D3D9DeviceManager" );
      return false;
   }

   D3D9Device* pDevice = pDM->getActiveDevice();
   if ( !pDevice )
   {
      ASSERT( false );
      ExitWithNonRecoverableErrorMessageBox( "GraphicsSystem::IsDeviceLost() - cannot retrieve D3D9Device" );
      return false;
   }

   return pDevice->isDeviceLost();

#else
   return false;
#endif
}


GPUVendor GraphicsSystem::GetGPUVendor() const
{
   if ( !m_pOgreRoot )
   {
      ASSERT( false );
      return GPU_UNKNOWN;
   }

   const RenderSystem* pRenderSys = m_pOgreRoot->getRenderSystem();
   if ( !pRenderSys )
   {
      ASSERT( false );
      return GPU_UNKNOWN;
   }

   const RenderSystemCapabilities* pCaps = pRenderSys->getCapabilities();
   if ( !pCaps )
   {
      ASSERT( false );
      return GPU_UNKNOWN;
   }

   return pCaps->getVendor();
}


void GraphicsSystem::LoadResources()
{
   LogSys()->Message("GraphicsSystem::LoadResources", LML_CRITICAL );

   // Load resources
   // - FIXME:  is this obsolete?
   SoundSystem::RegisterNewResourceTypes();

   // Initialise, parse scripts etc
   ResourceGroupManager::getSingleton().initialiseAllResourceGroups();
}


void GraphicsSystem::LoadIntroResources()
{
   if ( OptionSys()->UseSparArchive() )
   {
      ResourceGroupManager::getSingleton().addResourceLocation( "../../media/intro.spar", "SPAR", "Intro" );
   }
   else
   {
      ResourceGroupManager::getSingleton().addResourceLocation( "../../media/intro", "FileSystem", "Intro" );
   }

   // load_screen.png has "Salvation Prophecy" written on it, so we'll have to localize that graphic.
   // - note:  we don't convert our media/localized directory to a .spar archive, so this will always be a bare FileSystem.
   std::stringstream ssIntroLoc;
   ssIntroLoc << "../../media/localized/" << OptionSys()->GetTextLocalizationLanguage() << "/intro";
   ResourceGroupManager::getSingleton().addResourceLocation( ssIntroLoc.str(), "FileSystem", "Intro" );

   ResourceGroupManager::getSingleton().initialiseResourceGroup( "Intro" );
}


void GraphicsSystem::SetupResources()
{
   // This is the customized archive class that can strip off layers of a DDS file for lower
   // texture qualities.
   m_pTextureArchiveFactory = OGRE_NEW TextureArchiveFactory();
   ArchiveManager::getSingleton().addArchiveFactory( m_pTextureArchiveFactory );

   m_pSPZipArchiveFactory = OGRE_NEW SPZipArchiveFactory();
   ArchiveManager::getSingleton().addArchiveFactory( m_pSPZipArchiveFactory );

   if ( OptionSys()->UseSparArchive() )
   {
      // See comments in tool/sparify_data.pl about .spar archive files.
      SetupResources( "resources_arch.cfg" );
   }
   else
   {
      SetupResources( "resources.cfg" );
   }

   // We also have some language-localized resources, like our station sign textures ("armory", "ship hangar", etc.) 
   // which need to be different depending on the language.
   // - note:  since media/localized/* will only have a couple textures in it, I'm not going to bother converting it
   //   to a spar archive.  We'll just always use a bare FileSystem.
   const String& strLanguage = OptionSys()->GetTextLocalizationLanguage();
   std::stringstream ssLocResourceFile;
   ssLocResourceFile << "resources_" << strLanguage << ".cfg";
   SetupResources( ssLocResourceFile.str() );
}


void GraphicsSystem::SetupResources( const String& i_strConfigFile )
{
   // Load resource paths from config file
   ConfigFile resourcesCfg;

   try
   {
      resourcesCfg.load( i_strConfigFile );
   }
   catch ( Exception& e )
   {
      std::stringstream ssErrorMsg;
      ssErrorMsg << "Cannot find resource config file \"" << i_strConfigFile << "\": " << e.getFullDescription();

      LogSys()->AbortMessage( ssErrorMsg.str() );
   }

   // Go through all sections & settings in the file
   ConfigFile::SectionIterator itSection = resourcesCfg.getSectionIterator();

   String strGroupName, strLocation, strType;
   while (itSection.hasMoreElements())
   {
      // Resource group.  "General", etc
      strGroupName = itSection.peekNextKey();

      ConfigFile::SettingsMultiMap* mapSettings = itSection.getNext();
      ConfigFile::SettingsMultiMap::iterator itSetting = mapSettings->begin();

      for ( ; itSetting != mapSettings->end(); ++itSetting )
      {
         // Zip or FileSystem
         strType = itSetting->first;  
         // Filepath
         strLocation = itSetting->second;
         ResourceGroupManager::getSingleton().addResourceLocation( strLocation, strType, strGroupName );
      }
   }
}


void GraphicsSystem::RetrieveDesktopAspectRatio()
{
   // Record the desktop aspect ratio for later use.
   // We need to record this now, because if we go into fullscreen, it changes the ratio (in windows, anyway).
   int nDesktopWidth = 800;
   int nDesktopHeight = 600;
   GraphicsUtil::GetScreenSize( nDesktopWidth, nDesktopHeight );
   
   std::stringstream ssLog;
   ssLog << "GraphicsSystem::RetrieveDesktopAspectRatio = " << nDesktopWidth << " x " << nDesktopHeight;
   LogSys()->Message(ssLog.str(), LML_NORMAL );
   
   // prevent div-by-0 
   if ( nDesktopWidth > 0 && nDesktopHeight > 0 )
   {
      m_fDesktopAspectRatio = (float)nDesktopWidth/nDesktopHeight;
   }
   else
   {
      ASSERT( false );
      // I guess we'll just guess.
      m_fDesktopAspectRatio = 4.f/3;
   }
}


void GraphicsSystem::ConfigureGraphics()
{
   // We can specify to use Ogre's configuration dialog in option.xml
   if ( OptionSys()->UseOgreConfigDialog() )
   {
      LogSys()->Message( "OptionSys specifies UseOgreConfigDialog", LML_CRITICAL );
      ShowOgreConfigurationDialog();
   }

   // If there is no ogre.cfg file, fallback on the ogre config dialog.
   else if ( !OgreConfigFileExists() )
   {
      // It's possible tech support will need to instruct users to delete their ogre.cfg file, in order to invoke the ogre
      // configuration dialog.
      // Example:
      // - maybe he has 2 video cards, and needs to select a specific card to run the game on.
      // - in this case, we can tell the user to delete ogre.cfg, and this code will execute
      // - the config dialog will pop up
      // - the user can select the proper video card.
      // - the config file is saved, and this is a one-time invokation of the dialog to fix the problem.
      // It would be nice to have a custom "config graphics" utility for Salvation Prophecy, but until we do that
      // we'll rely on Ogre3D's configuration dialog.
      LogSys()->Message( "No ogre.cfg file found.  Falling back on ogre configuration dialog.", LML_CRITICAL );
      ShowOgreConfigurationDialog();
   }

   // Now, we should be able to read the existing ogre.cfg file (normal case), or the one we wrote above from the call to 
   // ShowOgreConfigurationDialog().
   if ( !m_pOgreRoot->restoreConfig() )
   {
      // Ok, we failed.  Likely, ogre.cfg contains a video card that does not exist on the user's computer.
      // This is expected to happen:
      // 1.  First run of the game, after a new installation.
      // 2.  If the user upgrades his computer to a new video card.
      LogSys()->Message( "First attempt at restoreConfig failed, this is expected on a new install or video card upgrade.", LML_CRITICAL );

      // Find a valid "Rendering Device" (video card) setting
      // - update:  It looks like some Ogre3D code has changed
      // - ogre now seems to insert a valid rendering device into the ogre.cfg file if an unrecognized one is found
      // - so this call to VerifyRenderingDevice() may no longer be necessary, although it does give us some extra error checking.
      VerifyRenderingDevice();

      // Try to load ogre.cfg again, with the new video card setting.
      if ( !m_pOgreRoot->restoreConfig() )
      {
         ExitWithNonRecoverableErrorMessageBox( "Unable to restore ogre.cfg - exiting." );
      }
   }

   VerifyScreenResolution();

   // I've been using a setting of "Floating-point mode=Consistent" for ages.
   // I vaguely remember I had a reason for doing this, but damnit I can't remember.  maybe it's in svn history.
   // - on yeah, it's because at very large positions in space, say >1 million away from the origin, the ship shakes
   //   a lot on "Floating-point mode=Fastest", presumably due to float imprecision.
   // Anyway, just in case the user deletes ogre.cfg or changes this setting, let's revert back to "Consistent", since
   // the game is not tested with any other value ("Fastest").
   // Here's what the ogre forums have to say about floating-point mode:  http://www.ogre3d.org/forums/viewtopic.php?f=10&t=7791
   //      DirectX, by default, switches the floating point unit to single precision (32 bit) mode for your entire program. 
   //      This means that operations on doubles will not be done in 64 bit precision but 32 bit. It might also give other stability 
   //      issues with physics packages.
   //      Consistent mode fixes this by switching the precision before and after D3D calls. Microsoft says this can harm performance 
   //      but testing using the Ogre demos didn't find any difference in speed.

   // this option is windows only.
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
   if ( GetConfigOptionCurrentValue( "Floating-point mode" ) != "Consistent" )
   {
      SetConfigOption( "Floating-point mode", "Consistent" );
      m_pOgreRoot->saveConfig();
   }
#endif
}


void GraphicsSystem::VerifyRenderingDevice()
{
   // The "Rendering Device" refers to the name of the user's video card.
   // Ensure the current video card selection is valid.
   // This value is set in ogre.cfg.
   ConfigOption* pDeviceConfig = GetConfigOption( "Rendering Device" );

   if ( !pDeviceConfig )
   {
      ASSERT( false );
      return;
   }

   // Ogre tells us about all the video cards installed in this computer in the following list.
   if ( pDeviceConfig->possibleValues.empty() )
   {
      // Unrecoverable error.
      // This will cause the program to exit.
      // Ogre.log may have more information.
      ExitWithNonRecoverableErrorMessageBox( "No video card detected.  Exiting." );
   }

   // Is a valid video card already set?
   // - note: it looks like this is always true now, as Ogre3D itself will write the name
   //   of a valid video card into the config file if it encounters an invalid one.
   bool bValidDeviceSetting = false;
   StringVector::iterator itDevice = pDeviceConfig->possibleValues.begin();
   for ( ; itDevice != pDeviceConfig->possibleValues.end(); ++itDevice )
   {
      if ( pDeviceConfig->currentValue == *itDevice )
      {
         bValidDeviceSetting = true;
      }
   }

   if ( !bValidDeviceSetting )
   {
      // Select a valid rendering device.
      // - Just picking the first device in the list, for now.
      // - Alternately, consider popping up a config dialog here?
      SetConfigOption( "Rendering Device", pDeviceConfig->possibleValues.front() );
   }


   // Setting a new rendering device will reset our "Anti aliasing" option to "None".
   // This is kind of a drag for new users to first experience the game with no anti-aliasing,
   // especially since changing anti-aliasing option requires a game restart.

   // OgreD3D9RenderSystem.cpp supports two kinds of anti-aliasing, "NonMaskable x" and "Level x".
   // From a post on the ogre forums:
   //
   //    "Nonmaskable is lower quality, it just antialiases the framebuffer result. 
   //     The 'Level n' options are true supersampling where it renders at a higher resolution 
   //     than the screen and samples down to the screen res with filtering."
   //      -- Sinbad
   //
   // http://www.ogre3d.org/phpBB2/viewtopic.php?t=15079&view=next&sid=ce193664e1d3d7c4af509e6f4e2718c6

   // *UPDATE*
   // With the change to Ogre 1.7, the "anti-aliasing" option was renamed to "FSAA", and the values are
   // now integers, rather than terms like "NonMaskable x" and "Level x" as discussed above.

   // It sounds like Nonmaskable will be much faster, lets use that, if available.
   ConfigOption* pFSAAConfig = GetConfigOption( "FSAA" );

   if ( !pFSAAConfig )
   {
      ASSERT( false );
      return;
   }

   if ( pFSAAConfig->currentValue == "0" )
   {
      // Ok, lets see if we can find the lowest level antialiasing setting.
      if ( IsValidOption( "1", pFSAAConfig->possibleValues ) )
      {
         // Cool.  Let's use this option.
         SetConfigOption( "FSAA", "1" );
      }
      // Try 2 in case 1 didn't work, it seems like the possible values are often 0,2,4,...
      else if ( IsValidOption( "2", pFSAAConfig->possibleValues ) )
      {
         // Cool.  Let's use this option.
         SetConfigOption( "FSAA", "2" );
      }
      // And as an absolute fallback, let's just try the second value in the list, expecting the 1st one to be "0".
      else if ( pFSAAConfig->possibleValues.size() >= 2 )
      {
         SetConfigOption( "FSAA", pFSAAConfig->possibleValues[1] );
      }
   }

   RenderSystem* rs = m_pOgreRoot->getRenderSystemByName( m_strDefaultRenderSystem );
   if (!rs)
   {
      // Unrecoverable error.
      // Ogre.log should indicate what went wrong.
      ExitWithNonRecoverableErrorMessageBox( "Cannot retrieve render system.  Exiting." );
   }

   // On many intel cards, anything other than FSAA = 0 crashes.
   // Let's try to fix this.
   // http://www.ogre3d.org/forums/viewtopic.php?f=2&t=67432
   // http://steamcommunity.com/app/248450/discussions/0/792923683602560129/
   // My notes above seem to indicate that FSAA should be set to 0 when we detect a new video card, but that no longer seems
   // to be the case.  Maybe that was from an earlier version of ogre.
   if ( pFSAAConfig->possibleValues.size() == 0 ) 
   {
      SetConfigOption( "FSAA", "0" );
   }

   String err = rs->validateConfigOptions();
   if ( !err.empty() )
   {
      std::stringstream ssLog;
      ssLog << "VerifyRenderingDevice failed to fix ogre.cfg.  Error: " << err;
      LogSys()->Error( ssLog.str(), LML_CRITICAL );
      return;
   }

   // We should be good to go.
   m_pOgreRoot->setRenderSystem(rs);

   // Save out the updated configuration options.
   m_pOgreRoot->saveConfig();
}


void GraphicsSystem::VerifyScreenResolution()
{
   // The purpose of this function is to ensure we have a valid video mode in our ogre.cfg file.
   // It may not be, since different computers will have different valid video modes.  So the
   // ogre.cfg file which is installed won't always have a proper value.  Also, if the user changes 
   // his graphics card, the current video mode setting may no longer be valid.
   // -- update:  actually we expect VerifyRenderingDevice to pick a valid screen resolution if
   //    it detects a new video card.  This is all handled by Ogre's RenderSystem::setConfigOption function.
   //    But it certainly doesn't hurt to check.
   //
   // A secondary purpose of this function is to make sure the game window matches the aspect ratio 
   // of the desktop.  This only applies for full-screen mode.
   // 
   // - note:  Should we only run this the first time the game is run?  This will overwrite a video mode
   //   selected by the player if that video mode does not match the desktop aspect ratio.  Maybe that's ok.

   ConfigOption* pVideoModeConfig = GetConfigOption( "Video Mode" );
   if ( !pVideoModeConfig )
   {
      ASSERT( false );
      return;
   }

   // Get the current video mode setting, stored in the ogre.cfg file.
   String strVideoMode = pVideoModeConfig->currentValue;
   if ( strVideoMode.empty() )
   {
      ASSERT( false );
      return;
   }

   // Get the possible video mode settings for the user's video card, provided by Ogre.
   StringVector vecValidMode = pVideoModeConfig->possibleValues;
   if ( vecValidMode.empty() )
   {
      // There are no possible values!
      ASSERT( false );
      return;
   }

   // Ensure the current value is a possible value.
   // This may not be true after a fresh install (where the ogre.cfg has values from a dev machine),
   // or if the user changes his video card.
   if ( !IsValidVideoMode( strVideoMode, vecValidMode ) )
   {
      // We have an invalid video mode!
      // Try falling back to 1024x768, which we expect most cards to support.

      // Note:  I've noticed that if we start the game with an invalid video mode in
      // the ogre.cfg, we'll have an empty list of anti-aliasing options in UIGraphics.
      // I guess this makes sense, Ogre (DirectX) may need to know the video mode in order
      // to know which other options are valid.
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
      strVideoMode = "1024 x 768 @ 32-bit colour";
#else
      strVideoMode = "1024 x  768";
#endif

      if ( !IsValidVideoMode( strVideoMode, vecValidMode ) )
      {
         // Gah!  This video card sux.  Just select the an arbitrary valid mode in the list.
         // Using the last mode in the list rather than the first, since 32-bit values are
         // listed after 16=bit values
         strVideoMode = vecValidMode.back();

         // If we assert here, we have a code bug
         ASSERT( IsValidVideoMode( strVideoMode, vecValidMode ) );
      }

      // Set our new valid video mode back into our config options.
      SetConfigOption( "Video Mode", strVideoMode );

      // Save out the updated configuration options.
      m_pOgreRoot->saveConfig();
   }

   // We only want to do this once.
   // Subsequently, if the user changes the resolution, don't keep overwriting it here on each subsequent start-up.
   path_string wstrFirstRunCompleteFileName = LoadSaveSys()->GetAppDataFolder();

#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
   wstrFirstRunCompleteFileName.append( L"\\first_run_complete.txt" );
#else
   wstrFirstRunCompleteFileName.append( "/first_run_complete.txt" );
#endif
   
   if ( LoadSaveSys()->FileExists( wstrFirstRunCompleteFileName ) )
   {
      return;
   }

   std::ofstream file( wstrFirstRunCompleteFileName.c_str() );
   // We only need an empty file, but let's write to it to make double sure the file gets written.
   file << "first run complete.";
   file.close();

   // Ok, we now have a valid video mode.
   // If we're in non-fullscreen (windowed) mode, we can return now.
   if ( !IsFullScreen() )
   {
      return;
   }

   // Ok we're in full screen mode.
   // Attempt to find a video mode aspect ratio that matches the desktop aspect ratio, since our window
   // is the size of the entire screen.
   SelectNewVideoMode( m_fDesktopAspectRatio, vecValidMode );
}


bool GraphicsSystem::IsValidOption( const String& i_strOption, const Ogre::StringVector& i_vecOption )
{
   // All we're doing here is iterating through a list of strings to see if i_strOption matches
   // any of them.
   StringVector::const_iterator it = i_vecOption.begin();
   for ( ; it != i_vecOption.end(); ++it )
   {
      const String& strValidOption = *it;
      
      if ( i_strOption == strValidOption )
      {
         return true;
      }
   }

   return false;
}


bool GraphicsSystem::IsValidVideoMode( const String& i_strVideoMode, const Ogre::StringVector& i_vecValidVideoMode )
{
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
   // on windows, IsValidOption works fine for our video modes
   return IsValidOption( i_strVideoMode, i_vecValidVideoMode );

#else
   // on linux, it doesn't.
   // This is because when we write ogre.cfg, the video mode looks like this:
   //    Video Mode=800 x 600 @ 32-bit colour
   // But when we ask ogre for i_vecValidVideoMode, they look like this:
   //    ' 320 x  240', ' 400 x  300', ' 512 x  384', ..., '1024 x  768', ..., '1920 x 1080'
   //
   // Notice 2 differences:
   // 1.  the config file has the "@ 32-bit colour" part
   // 2.  the video modes have extra spaces inserted, probably from a sprintf style formatting.
   //
   // So, a direct string compare, like used by IsValidOption, ain't gonna work on linux.
   // We'll parse out the widths and heights and compare then directly.

   int nWidth = 0;
   int nHeight = 0;
   if ( !ParseWidthHeightFromVideoModeString( i_strVideoMode, nWidth, nHeight ) )
   {
      // Invalid video mode string?
      ASSERT( false );
      return false;
   }
   
   StringVector::const_iterator it = i_vecValidVideoMode.begin();
   for ( ; it != i_vecValidVideoMode.end(); ++it )
   {
      const String& strValidOption = *it;

      int nValidWidth = 0;
      int nValidHeight = 0;
      if ( !ParseWidthHeightFromVideoModeString( i_strVideoMode, nValidWidth, nValidHeight ) )
      {
         // Invalid video mode string?
         ASSERT( false );
         continue;
      }

      if ( nWidth == nValidWidth &&
           nHeight == nValidHeight )
      {
         return true;
      }
   }

   return false;
#endif
}


void GraphicsSystem::SelectNewVideoMode( float i_fAspectRatio, const Ogre::StringVector& i_vecValidMode )
{
   // Find a video mode with the appropriate aspect ratio.
   int nSelectedWidth = 0;
   int nSelectedHeight = 0;
   String strSelectedVideoMode;

   for ( StringVector::const_iterator itVideoMode = i_vecValidMode.begin();
         itVideoMode != i_vecValidMode.end(); 
         ++itVideoMode )
   {
      const String& strVideoMode = *itVideoMode;

      // Ignore non-32 bit video modes.
      // - it looks like this "32-bit colour" string only exists in DirectX
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
      if ( !StringUtil::endsWith( strVideoMode, "32-bit colour" ) ) continue;
#endif

      // Parse out the width and height of this video mode
      int nWidth = 0;
      int nHeight = 0;
      if ( !ParseWidthHeightFromVideoModeString( strVideoMode, nWidth, nHeight ) )
      {
         // Invalid video mode string?
         ASSERT( false );
         continue;
      }

      if ( nWidth <= 0 || nHeight <= 0 )
      {
         // prevents a div-by-0 lower down
         ASSERT( false );
         continue;
      }

      // Calculate the aspect ratio of this video mode
      float fRatio = (float)nWidth/nHeight;

      // Does this match the requested aspect ratio?
      if ( !Math::RealEqual( fRatio, i_fAspectRatio ) )
      {
         continue;
      }

      // Ok this video mode matches our desktop ratio.
      // Pick the video mode whose height is closest to 768.
      // This should be 1024x768 for a regular or 1280x800 for a widescreen monitor.
      if ( abs( nHeight - 768 ) < abs( nSelectedHeight - 768 ) )
      {
         // This height is closer to 768.
         nSelectedWidth = nWidth;
         nSelectedHeight = nHeight;
         strSelectedVideoMode = strVideoMode;
      }
   }

   // Did we find a valid resolution?
   if ( nSelectedWidth <= 0 || nSelectedHeight <= 0 || strSelectedVideoMode.empty() )
   {
      // One playtester reported that the game started at 640x480.
      // He had a monitor resolution I've never dealt with before, something fairly squarish.
      // I'm wondering if none of his possible resolutions matched his desktop resolution,
      // so our !Math::RealEqual( fRatio, i_fAspectRatio ) check prevented us from selecting things above?
      // 
      // Just in case this is the problem, I'm going to try a second loop here with a looser
      // restriction on exactly matching the requested resolution.
      // So this is an exact copy of the above loop except the comparison to i_fAspectRatio.
      //
      // UPDATE:  I'm pretty sure this problem mensioned above was caused by our (now deleted) check
      // to conditionally call this function in VerifyScreenResolution, which was:
      // if ( !Math::RealEqual( fRatio, m_fDesktopAspectRatio ) )
      //
      // So this code is probably not necessary, but I guess we'll leave it.

      for ( StringVector::const_iterator itVideoMode = i_vecValidMode.begin();
            itVideoMode != i_vecValidMode.end(); 
            ++itVideoMode )
      {
         const String& strVideoMode = *itVideoMode;

         // Ignore non-32 bit video modes.
         // - it looks like this "32-bit colour" string only exists in DirectX
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
         if ( !StringUtil::endsWith( strVideoMode, "32-bit colour" ) ) continue;
#endif
         
         // Parse out the width and height of this video mode
         int nWidth = 0;
         int nHeight = 0;
         if ( !ParseWidthHeightFromVideoModeString( strVideoMode, nWidth, nHeight ) )
         {
            // Invalid video mode string?
            ASSERT( false );
            continue;
         }

         if ( nWidth <= 0 || nHeight <= 0 )
         {
            // prevents a div-by-0 lower down
            ASSERT( false );
            continue;
         }

         // Calculate the aspect ratio of this video mode
         float fRatio = (float)nWidth/nHeight;

         /* Removing this check completely here, see comments above.
         // Does this match the requested aspect ratio?
         if ( !Math::RealEqual( fRatio, i_fAspectRatio ) )
         {
            continue;
         }
         */

         // Ok this video mode matches our desktop ratio.
         // Pick the video mode whose height is closest to 768.
         // This should be 1024x768 for a regular or 1280x800 for a widescreen monitor.
         if ( abs( nHeight - 768 ) < abs( nSelectedHeight - 768 ) )
         {
            // This height is closer to 768.
            nSelectedWidth = nWidth;
            nSelectedHeight = nHeight;
            strSelectedVideoMode = strVideoMode;
         }
      }
   }

   // Did we find a valid resolution?
   if ( nSelectedWidth <= 0 || nSelectedHeight <= 0 || strSelectedVideoMode.empty() )
   {
      ASSERT( false );
      return;
   }

   // Set the new resolution in the config file.
   SetConfigOption( "Video Mode", strSelectedVideoMode );

   // Save out the updated configuration options.
   m_pOgreRoot->saveConfig();
}


ConfigOption* GraphicsSystem::GetConfigOption( const String& i_strOption ) const
{
   if ( !m_pOgreRoot )
   {
      ASSERT( false );
      return 0;
   }

   // Config options are stored by the RenderSystem
   RenderSystem* pRenderSys = m_pOgreRoot->getRenderSystem();
   if ( !pRenderSys )
   {
      pRenderSys = m_pOgreRoot->getRenderSystemByName( m_strDefaultRenderSystem );
   }
   if ( !pRenderSys )
   {
      ASSERT( false );
      return 0;
   }

   // Retrieve the specified config option
   ConfigOptionMap& map = pRenderSys->getConfigOptions();
   ConfigOptionMap::iterator itConfig = map.find( i_strOption );

   if ( itConfig == map.end() )
   {
      ASSERT( false );
      return 0;
   }

   return &itConfig->second;
}


String GraphicsSystem::GetConfigOptionCurrentValue( const String& i_strOption ) const
{
   ConfigOption* pConfigOpt = GetConfigOption( i_strOption );
   if ( !pConfigOpt )
   {
      ASSERT( false );
      return "";
   }

   return pConfigOpt->currentValue;
}


void GraphicsSystem::SetConfigOption( const String& i_strOption, const String& i_strValue ) const
{
   // Config options are stored by the RenderSystem (D3D9)
   RenderSystem* pRenderer = m_pOgreRoot->getRenderSystemByName( m_strDefaultRenderSystem );
   if ( !pRenderer )
   {
      // Unrecoverable error.
      // Ogre.log should indicate what went wrong.
      ExitWithNonRecoverableErrorMessageBox( "Cannot retrieve render system.  Exiting." );
   }

   // Ensure this config option exists
   if ( !GetConfigOption( i_strOption ) )
   {
      ASSERT( false );
      return;
   }

   // Ok, save the new value.
   // *** Note:  changing one config option (such as rendering device) may change other configuration options.
   pRenderer->setConfigOption( i_strOption, i_strValue );

   // Verify the configuration options
   String strValidation = pRenderer->validateConfigOptions();

   // - strValidation will be empty unless there is an error.
   if ( !strValidation.empty() )
   {
      LogSys()->Error( String("validateConfigOptions error: ") + strValidation, LML_CRITICAL );
      // FIXME:  consider adding some kind of fallback code here?  not sure exactly what.
   }
}


bool GraphicsSystem::OgreConfigFileExists() const
{
   path_string wstrConfigFile = LoadSaveSys()->GetAppDataFolder();
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
   wstrConfigFile.append( L"\\ogre.cfg" );
#else
   wstrConfigFile.append( "/ogre.cfg" );
#endif

   // I'm going to try to load the config file in the exact same way Ogre3D does it inside of Ogre::Root::restoreConfig(),
   // so that we check not only for the existance of the file, but that it can be properly loaded by Ogre.
   ConfigFile cfg;
   try {
      // Don't trim whitespace
      
      cfg.load(wstrConfigFile, "\t:=", false);
   }
   catch (Exception& e)
   {
      if (e.getNumber() == Exception::ERR_FILE_NOT_FOUND)
      {
         // I'm not going to treat this as an error, because we may need to instruct some users to delete their ogre.cfg
         // file in order to get the ogre config dialog, say to select a specific video card.
         LogSys()->Message( "ogre.cfg file does not exist", LML_CRITICAL );
         return false;
      }

      std::stringstream ssErr;
      ssErr << "ogre.cfg file exists, but cannot be parsed.  Error code: " << e.getNumber();
      LogSys()->Error( ssErr.str(), LML_CRITICAL );
      return false;
   }

   LogSys()->Message( "ogre.cfg file found.", LML_CRITICAL );
   return true;
} 

     
void GraphicsSystem::ShowOgreConfigurationDialog()
{
   bool bOK = m_pOgreRoot->showConfigDialog();

   if ( !bOK )
   {
      // The user clicked cancel on the config dialog.
      LogSys()->Message( "showConfigDialog returned false - cancel button?  exiting.", LML_CRITICAL );
      exit(0);
   }
}


void GraphicsSystem::VerifyShaderModel()
{
   GpuProgramManager& gmgr = GpuProgramManager::getSingleton();
   HighLevelGpuProgramManager& hmgr = HighLevelGpuProgramManager::getSingleton();

#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
   if ( !hmgr.isLanguageSupported("cg") )
   {
      // Let's try to include the name of the video card in the error message.
      String strVideoCard = GetConfigOptionCurrentValue( "Rendering Device" );
      std::stringstream ssErr;
      ssErr << "Your graphics card, \"" << strVideoCard << "\" does not support cg.  The game cannot run.";
      ExitWithNonRecoverableErrorMessageBox( ssErr.str() );
   }
   
   // In directX, we use ps_2_x and vs_2_x for almost everything, and nothing higher.
   if ( !gmgr.isSyntaxSupported("ps_2_x") )
   {
      String strVideoCard = GetConfigOptionCurrentValue( "Rendering Device" );
      std::stringstream ssErr;
      ssErr << "Your graphics card, \"" << strVideoCard << "\" does not support shader model 2.x.  The game cannot run.";
      ExitWithNonRecoverableErrorMessageBox( ssErr.str() );
   }
#else
   // Currently in opengl, we're using mostly glsl, and also a bit of cg for simple shaders I didn't get around to converting.
   if ( !hmgr.isLanguageSupported("glsl") || !hmgr.isLanguageSupported("cg") )
   {
      // It doesn't seem we can retrieve the rendering device under opengl the same way we do under directx
      ExitWithNonRecoverableErrorMessageBox( "Your graphics card does not support cg and/or glsl.  The game cannot run." );
   }

   // We only use arbfp1 / arbvp1 in cg, because ATI cards don't support anything higher in opengl.
   if ( !gmgr.isSyntaxSupported("arbfp1") )
   {
      ExitWithNonRecoverableErrorMessageBox( "Your graphics card does not support shader model arbfp1.  The game cannot run." );
   }
#endif
}


void GraphicsSystem::ExitWithNonRecoverableErrorMessageBox( const String& i_strMsg ) const
{
   // TODO:  this is dupe functionality of LogSystem::AbortMessage

   std::stringstream ssLog;
   ssLog << "GraphicsSystem::ExitWithNonRecoverableErrorMessageBox called: " << i_strMsg;
   LogSys()->Error( ssLog.str(), LML_CRITICAL );

   // Windows specific, TODO need a linux alternative maybe?
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
   MessageBox( NULL, i_strMsg.c_str(), "Salvation Prophecy", MB_ICONERROR | MB_TASKMODAL );
#else
   throw std::runtime_error( i_strMsg.c_str() );
#endif
   
   exit(0);
}


std::list< std::pair<int,int> > GraphicsSystem::GetVideoModes( bool i_bShowAllResolutions ) const
{
#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX
   // This function doesn't work properly on certain linux systems (maybe only dual-monitor?  maybe only with SDL?)
   if ( OptionSys()->UseSDLVideo() )
   {
      return GraphicsUtil::GetVideoModesSDL( i_bShowAllResolutions );
   }
#endif

   
   std::list< std::pair<int,int> > listVideoMode;

   ConfigOption* pVideoConfig = GetConfigOption( "Video Mode" );
   if ( !pVideoConfig )
   {
      ASSERT( false );
      return listVideoMode;
   }

   StringVector::iterator itVideo = pVideoConfig->possibleValues.begin();
   for ( ; itVideo != pVideoConfig->possibleValues.end(); ++itVideo )
   {
      String strVideo = *itVideo;
      
      // Ignore 16 bit values
      if ( StringUtil::endsWith( strVideo, "16-bit colour" ) ) continue;

      // Parse out the width and height of this video mode
      int nWidth = 0;
      int nHeight = 0;
      if ( !ParseWidthHeightFromVideoModeString( strVideo, nWidth, nHeight ) )
      {
         // Invalid video mode string?
         ASSERT( false );
         continue;
      }
      
      // don't support resolutions less than 800x600
      if ( nWidth < 800 || nHeight < 600 ) continue;

      float fRatio = (float)nWidth/nHeight;
      if ( !i_bShowAllResolutions && !Math::RealEqual( fRatio, m_fDesktopAspectRatio ) ) 
      {
         continue;
      }

      listVideoMode.push_back( std::pair<int,int>( nWidth, nHeight ) );
   }

   return listVideoMode;
}


bool GraphicsSystem::IsCurrentVideoMode( int i_nWidth, int i_nHeight, bool i_bCheckConfigValue ) const
{
   if ( i_bCheckConfigValue )
   {
      String strValue = GetConfigOptionCurrentValue( "Video Mode" );

      // Parse out the width and height of this video mode
      int nWidth = 0;
      int nHeight = 0;
      if ( !ParseWidthHeightFromVideoModeString( strValue, nWidth, nHeight ) )
      {
         // Invalid video mode string?
         ASSERT( false );
         return false;
      }
      
      if ( nWidth == i_nWidth && nHeight == i_nHeight )
      {
         return true;
      }
      return false;
   }

   // Otherwise, check the current size of the window.
   RenderWindow* pWindow = GetRenderWindow();

   if ( pWindow->getWidth() == i_nWidth && pWindow->getHeight() == i_nHeight )
   {
      return true;
   }

   return false;
}


void GraphicsSystem::GetCurrentVideoMode( int& o_nWidth, int& o_nHeight, bool i_bCheckConfigValue ) const
{
   if ( i_bCheckConfigValue )
   {
      String strValue = GetConfigOptionCurrentValue( "Video Mode" );

      // Parse out the width and height of this video mode
      int o_nWidth = 0;
      int o_nHeight = 0;
      if ( !ParseWidthHeightFromVideoModeString( strValue, o_nWidth, o_nHeight ) )
      {
         // Invalid video mode string?
         ASSERT( false );
      }
      return;
   }

   // Otherwise, check the current size of the window.
   RenderWindow* pWindow = GetRenderWindow();

   o_nWidth = pWindow->getWidth();
   o_nHeight = pWindow->getHeight();
}


void GraphicsSystem::SetVideoMode( int i_nWidth, int i_nHeight )
{
   RenderWindow* pWindow = GetRenderWindow();

#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX
   if ( HasSDLWindow() )
   {
      if ( IsFullScreen() )
      {
         // I tried to get window resizing (resolution changes) to work in fullscreen SDL, but it always
         // just screwed up the window, so that the game only displayed on part of the screen (if changing
         // to a smaller resolution) or displayed "larger" than the screen such that we only saw part of the 
         // game (if changing to a larger resolution).
         //
         // Inform the user a restart is needed.
         UISys()->GetUIDialog()->OpenOKDialog( LOC("RESTART_GAME") );
      }
      else
      {
         /* This code doesn't work because SDL doesn't seem to catch maximize events from Ubuntu
          * when you hit the "maximize" button.  I modified the SDL test program testwm2.c to print out
          * maximize events and it never got one.  When you hit the maximize button, it just registers
          * as a resize and a move event.
          * So, there's no way to fix this bug, other than to remove the SDL_WINDOW_RESIZABLE property
          * from the window (done elsewhere in this file).
          * But, I kinda like being able to resize the window, so I think I'm just going to leave it as is,
          * and we'll have to live with this bug.

         // If the window is maximized, changing the resolution doesn't work properly (Ubuntu 12.04),
         // what happens is that the window stays maximized, and we only render to a portion of the screen.
         // Let's prevent this.
         Uint32 flags = SDL_GetWindowFlags( m_pSDLWindow );
         if ( ( flags & SDL_WINDOW_MAXIMIZED ) == SDL_WINDOW_MAXIMIZED )
         {
            // FIXME localize
            CEGUI::String strMsg = "You can't change the resolution while the window is maximized.";
            UISys()->GetUIDialog()->OpenOKDialog( strMsg );            
            // ... but don't return here.
            //     We'll allow the setting to "stick" so that next time we run the game,
            //     we'll get the requested resolution.
            //     This is very important because on Ubuntu (12.04), if I run the game in windowed mode,
            //     but at max resolution, it behaves as if maximized.  So, if we returned here, the user
            //     would be stuck at this resolution forever because we wouldn't allow the ogre.cfg file
            //     to be updated.
         }
         else
         {
         */
            SDL_SetWindowSize( m_pSDLWindow, i_nWidth, i_nHeight );

            // I believe this is necessary because GLXWindow::resize needs to resize the viewports,
            // and also track the current window width and height.
            pWindow->resize( i_nWidth, i_nHeight );

            // Without this, the windowResized function doesn't seem to get called, so our UI doesn't scale.
            windowResized( pWindow );
         // }
      }

      // Update the ogre.cfg file so when we restart we have the proper setting.
      String strVideoModeSetting = StringConverter::toString( i_nWidth, 4 ) + " x " + StringConverter::toString( i_nHeight, 4 );

      RenderSystem* pRenderSys = GetOgreRoot()->getRenderSystem();
      pRenderSys->setConfigOption( "Video Mode", strVideoModeSetting );
      GetOgreRoot()->saveConfig();

      return;
   }
#endif

   // Change the size of the window
   if ( IsFullScreen() )
   {
      // In linux, if we're running in non-SDL mode, on dual monitors this
      // will cause badness to happen, like the game kinda locking up.
      // So, let's prompt a restart under linux.
#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX
      UISys()->GetUIDialog()->OpenOKDialog( LOC("RESTART_GAME") );
#else      
      pWindow->setFullscreen( true, i_nWidth, i_nHeight );
#endif
   }
   else
   {
      unsigned int nOldWidth = 0;
      unsigned int nOldHeight = 0;
      unsigned int nColourDepth = 0;
      int nLeft = 0;
      int nTop = 0;
      pWindow->getMetrics( nOldWidth, nOldHeight, nColourDepth, nLeft, nTop );

      // Move the position of the window so we stay centered.
      // --- note I tried executing this code when using an SDL window, but it didn't do anything (Ubuntu 12.04).
      // --- I figure it's probably best not to execute it at all when using an SDL window, because maybe some 
      //     distros/windows managers will get angry with this call, since Ogre doesn't own the xwindow.
      nLeft += ((int)nOldWidth-i_nWidth)/2;
      nTop += ((int)nOldHeight-i_nHeight)/2;
      if ( nLeft < 0 ) nLeft = 0;
      if ( nTop < 0 ) nTop = 0;
      pWindow->reposition( nLeft, nTop );

      // Resize the window
      pWindow->resize( i_nWidth, i_nHeight );
   }

   // Save the new video mode setting in our ogre.cfg file
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
   String strVideoModeSetting = 
      StringConverter::toString( i_nWidth ) + String(" x ") + 
      StringConverter::toString( i_nHeight ) + String(" @ 32-bit colour");
#else
   // Copying code from GLXGLSupport::refreshConfig on how we write the video mode setting to file.
   //
   // (linux) Otherwise, if the string format doesn't match exactly, this video mode setting is not recognized
   // and is cleared out by some internal ogre process in favor of (on my machine) the highest resolution.
   // So basically no matter what you change the resolution to in game, the ogre.cfg file gets the wrong setting,
   // and so your selected video mode doesn't last across reboots of the game.
   //
   // I think the code which causes this behaviour is in GLXGLSupport::refreshConfig
   String strVideoModeSetting = 
      StringConverter::toString( i_nWidth, 4 ) + " x " + StringConverter::toString( i_nHeight, 4 );
#endif

   RenderSystem* pRenderSys = GetOgreRoot()->getRenderSystem();
   pRenderSys->setConfigOption( "Video Mode", strVideoModeSetting );
   GetOgreRoot()->saveConfig();
}


float GraphicsSystem::GetDesktopAspectRatio() const
{
   return m_fDesktopAspectRatio;
}


bool GraphicsSystem::ParseWidthHeightFromVideoModeString( const String& i_strVideoMode, int& o_nWidth, int& o_nHeight ) const
{
   // Parse out the width and height of this video mode
   Ogre::StringVector vecSplit = StringUtil::split( i_strVideoMode, " ", 3 );
   if ( vecSplit.size() < 3 )
   {
      ASSERT( false );
      return false;
   }
   o_nWidth = StringConverter::parseInt( vecSplit[0] );
   o_nHeight = StringConverter::parseInt( vecSplit[2] );

   if ( o_nWidth <= 0 || o_nHeight <= 0 )
   {
      ASSERT( false );
      return false;
   }
   
   return true;
}
   
   
float GraphicsSystem::GetBrightness() const
{
   return m_fBrightness;
}


void GraphicsSystem::SetBrightness( float i_fBrightness )
{
   m_fBrightness = i_fBrightness;
}


void GraphicsSystem::SetBrightnessToDefault()
{
   m_fBrightness = DEFAULT_BRIGHTNESS;
}


bool GraphicsSystem::IsDefaultBrightness() const
{
   return Math::RealEqual( m_fBrightness, DEFAULT_BRIGHTNESS, 0.01f );
}


Ogre::StringVector GraphicsSystem::GetAntiAliasingModes() const
{
   Ogre::StringVector vecModes;

   // This config option was renamed in Ogre 1.7 to "FSAA"
   // ConfigOption* pAliasingConfig = GetConfigOption( "Anti aliasing" );
   ConfigOption* pAliasingConfig = GetConfigOption( "FSAA" );
   if ( !pAliasingConfig )
   {
      ASSERT( false );
      return vecModes;
   }

   return pAliasingConfig->possibleValues;
}


bool GraphicsSystem::IsCurrentAntiAliasingMode( const String& i_strMode )
{
   // This config option was renamed in Ogre 1.7 to "FSAA"
   // ConfigOption* pAliasingConfig = GetConfigOption( "Anti aliasing" );
   ConfigOption* pAliasingConfig = GetConfigOption( "FSAA" );
   if ( !pAliasingConfig )
   {
      ASSERT( false );
      return false;
   }

   if ( pAliasingConfig->currentValue == i_strMode )
   {
      return true;
   }

   return false;
}


void GraphicsSystem::SetAntiAliasingMode( const String& i_strMode )
{
   RenderSystem* pRenderSys = GetOgreRoot()->getRenderSystem();
   // This config option was renamed in Ogre 1.7 to "FSAA"
   // pRenderSys->setConfigOption( "Anti aliasing", i_strMode );
   pRenderSys->setConfigOption( "FSAA", i_strMode );
   GetOgreRoot()->saveConfig();

   // Inform the user a restart is needed.
   UISys()->GetUIDialog()->OpenOKDialog( LOC("RESTART_GAME") );
}


void GraphicsSystem::SetVSync( bool i_bVSync )
{
   // Save the new VSync setting in our ogre.cfg file
   RenderSystem* pRenderSys = GetOgreRoot()->getRenderSystem();
   pRenderSys->setConfigOption( "VSync", i_bVSync?"Yes":"No" );
   GetOgreRoot()->saveConfig();

   // Inform the user a restart is needed.
   UISys()->GetUIDialog()->OpenOKDialog( LOC("RESTART_GAME") );
}


bool GraphicsSystem::IsVSync() const
{
   String strValue = GetConfigOptionCurrentValue( "VSync" );
   if ( strValue == "Yes" )
   {
      return true;
   }
   else if ( strValue == "No" )
   {
      return false;
   }
   else
   {
      ASSERT( false );
      return false;
   }
}


void GraphicsSystem::LimitMaxFPS()
{
   if ( OptionSys()->GetMaxFPS() < 1.f )
   {
      return;
   }

   unsigned long msMaxFPS = 1000.f / OptionSys()->GetMaxFPS();
   unsigned long msElapsed = 0;

   // Note:  according to ogre forums, getMilliseconds is better than getMillisecondsCPU
   // http://www.ogre3d.org/forums/viewtopic.php?f=2&t=70449
   do
   {
      msElapsed = TimeSys()->GetTimer().getMilliseconds() - m_msLastFrame;
   } 
   while ( msElapsed < msMaxFPS );

   m_msLastFrame = TimeSys()->GetTimer().getMilliseconds();
}


void GraphicsSystem::SetFullScreen( bool i_bFullScreen )
{
   // Save the new FullScreen setting in our ogre.cfg file
   RenderSystem* pRenderSys = GetOgreRoot()->getRenderSystem();
   pRenderSys->setConfigOption( "Full Screen", i_bFullScreen?"Yes":"No" );
   GetOgreRoot()->saveConfig();

   // Inform the user a restart is needed.
   UISys()->GetUIDialog()->OpenOKDialog( LOC("RESTART_GAME") );
}


bool GraphicsSystem::IsFullScreen( bool i_bCheckConfigValue ) const
{
   // See header comments
   if ( i_bCheckConfigValue )
   {
      String strValue = GetConfigOptionCurrentValue( "Full Screen" );
      if ( strValue == "Yes" )
      {
         return true;
      }
      else if ( strValue == "No" )
      {
         return false;
      }
      else
      {
         ASSERT( false );
         return false;
      }
   }

   
#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX      
   // When using SDL, the value returned by Ogre::RenderWindow::isFullScreen seems to always be false,
   // so let's check the SDL window.
   if ( m_pSDLWindow )
   {
      Uint32 flags = SDL_GetWindowFlags( m_pSDLWindow );
      if ( ( flags & SDL_WINDOW_FULLSCREEN ) == SDL_WINDOW_FULLSCREEN )
      {
         return true;
      }
      return false;
   }
#endif

   // Do we have a render window?
   if ( m_pWindow )
   {
      return m_pWindow->isFullScreen();
   }

   // If we haven't created a render window yet, just check the "Full Screen" setting in our
   // ogre.cfg file.
   RenderSystem* pRenderSys = GetOgreRoot()->getRenderSystem();

   ConfigOptionMap& map = pRenderSys->getConfigOptions();
   ConfigOptionMap::iterator itConfig = map.find( "Full Screen" );

   if ( itConfig == map.end() )
   {
      ASSERT( false );
      return false;
   }

   ConfigOption& FullScreenConfig = itConfig->second;
   if ( FullScreenConfig.currentValue == "Yes" )
   {
      return true;
   }
   else if ( FullScreenConfig.currentValue == "No" )
   {
      return false;
   }
   else
   {
      ASSERT( false );
      return false;
   }
}


void GraphicsSystem::ApplyMaterialScheme( Viewport* i_pViewport )
{
   if ( !i_pViewport )
   {
      return;
   }

   switch( GetTerrainQuality() )
   {
   case( Low_TerrainQualityType ):
      // This material scheme is set up by our TerrainMaterialGeneratorCustom class
      // allowing us to switch between a faster shader and a slower, more detailed shader.
      // - We use this shader on either low or medium quality setting.
      i_pViewport->setMaterialScheme( "LowQualityTerrain" );
      break;

   case( Medium_TerrainQualityType ):
      i_pViewport->setMaterialScheme( "MediumQualityTerrain" );
      break;

   case( High_TerrainQualityType ):
      i_pViewport->setMaterialScheme( "Default" );
      break;

   default:
      ASSERT( false );
   }
}


void GraphicsSystem::RemoveAllViewports()
{
   if ( CameraSys()->m_pViewport )
   {
      CameraSys()->PreCameraViewportDestruction();
   }

   GetRenderWindow()->removeAllViewports();

   // We've invalidated all Viewport* pointers in the game.
   // see header comments
   UISys()->GetUIGalaxyView()->GetGalaxyView3D()->m_pViewport = 0;
   UISys()->GetUIMainMenu()->m_pSceneViewport = 0;
   UISys()->GetUIMainMenu()->m_pBackgroundViewport = 0;
   UISys()->GetUISlideShow()->m_pViewport = 0;
}


TerrainQualityType GraphicsSystem::GetTerrainQuality() const
{
   return m_eTerrainQuality;
}


void GraphicsSystem::SetTerrainQuality( TerrainQualityType i_eQuality )
{
   m_eTerrainQuality = i_eQuality;

   Scene* pActiveScene = SceneSys()->GetActiveScene();
   if ( pActiveScene && pActiveScene->IsLandscape() )
   {
      pActiveScene->ToLandscape()->OnTerrainQualityChanged();
   }
}


int GraphicsSystem::GetTerrainMaxPixelError() const
{
   // Note:  I made some modifications to OgreTerrainQuadTreeNode.cpp which helps allow for larger max pixel errors
   // without screwing up nearby Terrain batch transitions / skirts.
   // - See:  TerrainQuadTreeNode::calculateCurrentLod
   // - Search for *SL*, that's how I mark my changes in other sourcecode.

   switch( m_eTerrainQuality )
   {
   case( High_TerrainQualityType ):
      return 7;
      break;
   case( Medium_TerrainQualityType ):
      return 25;
      break;
   case( Low_TerrainQualityType ):
      return 50;
      break;
   default:
      ASSERT( false );
      return 50;
   }
}


TextureQualityType GraphicsSystem::GetTextureQuality() const
{
   return m_eTextureQuality;
}


void GraphicsSystem::SetTextureQuality( TextureQualityType i_eQuality )
{
   m_eTextureQuality = i_eQuality;
}


int GraphicsSystem::GetNumMipMapsToSkip( const String& i_strResourceName, int i_width, int i_height )
{
   // *** DEBUGGING NOTE ***
   // There is some difference of behaviour between when we're using .spar archives and when we're using FileSystem/Texture archives.
   //
   // Case 1:  non-release build using resources.cfg
   // In this resources.cfg file, we use "FileSystem" and "Texture" archives.
   // Only those .dds files inside a "Texture" archive will be resized, as we rely on the code
   // specialized dds within TextureArchive.cpp/h for this to work.
   //
   // Case 2:  release build using resources_arch.cfg
   // In this resources_arch.cfg file, we use "SPAR" archives.
   // When I wrote the SPAR archive class, I incorporated the special .dds file resizing code (originally implemented
   // inside of TextureArchive.cpp/h) into SPZipArchive.cpp/h.
   // So this means that _ALL_ .dds files inside a spar archive (which is pretty much everything) will be properly resized
   // based on the texture quality setting.
   //
   // So if you're wondering why the texture quality isn't working, likely:
   // a.) You're running without .spar archives, and the .dds file isn't in a "Texture" directory
   // b.) The texture in question isn't a .dds file.
   int nNumMipMapsToSkip = 0;

   // Do we have an override for this particular texture?
   std::map< String, TextureQualitySetting >::const_iterator it = m_mapTextureQualityOverride.find( i_strResourceName );
   if ( it != m_mapTextureQualityOverride.end() )
   {
      // Yes we have an override.
      const TextureQualitySetting& setting = it->second;

      switch( m_eTextureQuality )
      {
      case( VeryHigh_TextureQualityType ):
         // We don't skip any mipmaps at very high setting
         nNumMipMapsToSkip = 0;
         break;
      case( High_TextureQualityType ):
         nNumMipMapsToSkip = setting.m_nMipMapSkipHigh;
         break;
      case( Medium_TextureQualityType ):
         nNumMipMapsToSkip = setting.m_nMipMapSkipMedium;
         break;
      case( Low_TextureQualityType ):
         nNumMipMapsToSkip = setting.m_nMipMapSkipLow;
         break;
      default:
         ASSERT( false );
         nNumMipMapsToSkip = 0;
         break;
      }
   }

   else
   {
      // No override, we'll just calculate nNumMipMapsToSkip based on the size of the texture and the current m_eTextureQuality
      int nTextureSize = (i_width + i_height) / 2;

      switch( m_eTextureQuality )
      {
      case( VeryHigh_TextureQualityType ):
         // We don't skip any mipmaps at very high setting
         nNumMipMapsToSkip = 0;
         break;

      case( High_TextureQualityType ):
         {
            // At high texture quality, just reduce any giant 2048x2048+ textures one level.
            // That will be sufficient to clear up enough some VRAM on many mid-range video cards.
            if ( nTextureSize >= 2048 )
            {
               nNumMipMapsToSkip = 1;
            }
            else
            {
               nNumMipMapsToSkip = 0;
            }
         }
         break;

      case( Medium_TextureQualityType ):
         {
            // At medium texture quality, reduce any texture of size 1024x1024+ one level.
            if ( nTextureSize >= 1024 )
            {
               nNumMipMapsToSkip = 1;
            }
            else
            {
               nNumMipMapsToSkip = 0;
            }
         }
         break;

      case( Low_TextureQualityType ):
         {
            // At low texture quality, reduce giant textures by 2 levels, and all other textures by 1 level.
            if ( nTextureSize >= 2048 )
            {
               nNumMipMapsToSkip = 2;
            }
            else
            {
               nNumMipMapsToSkip = 1;
            }
         }
         break;

      default:
         ASSERT( false );
         nNumMipMapsToSkip = 0;
         break;
      }
   }

   return nNumMipMapsToSkip;
}


ShadowQualityType GraphicsSystem::GetShadowQuality() const
{
   return m_eShadowQuality;
}


void GraphicsSystem::SetShadowQuality( ShadowQualityType i_eQuality )
{
   m_eShadowQuality = i_eQuality;
}


bool GraphicsSystem::UseSimpleWormholeGraphics() const
{
   return m_bUseSimpleWormholeGraphics;
}


void GraphicsSystem::SetUseSimpleWormholeGraphics( bool i_bUseSimple )
{
   m_bUseSimpleWormholeGraphics = i_bUseSimple;
   WormholeSys()->OnWormholeGraphicsSettingChanged();
}


GpuCommandBufferFlush& GraphicsSystem::GetGpuCommandBufferFlush()
{
   return m_gpuBufferFlush;
}


bool GraphicsSystem::frameRenderingQueued(const FrameEvent& evt)
{
   // See comments by this function, above:
   // DiagnosticsSys()->StartTimer( strRenderQueuedTimerID );

   static String strRenderQueuedTimerID( "frameRenderingQueued" );
   DiagnosticsSys()->StopTimer( strRenderQueuedTimerID );

   return true;
}


/* Abandoned - see notes in OgreRoot.cpp
bool GraphicsSystem::swapWindowBuffers()
{
   SDL_GL_SwapWindow( m_pSDLWindow );
}
*/


void GraphicsSystem::windowResized( RenderWindow* rw )
{
   // Note:  this code was originally in function:  SetVideoMode(..)
   // But I had to move it because important code wasn't getting triggered when we manually resized the window on linux by clicking and dragging a corner.
   // There's a lot of comments and commented out code I removed, related to certain historical bugs and bug fixes.  But they'll be in the source code 
   // history previous to the checkin containing this comment.
   
   if ( !GameSys()->AreAllSystemsInitialized() ) return;
   
   LogSys()->Message( "windowResized", LML_NORMAL );

   unsigned int nWidth, nHeight, nDepth = 1;
   int nLeft, nTop = 0;
   rw->getMetrics( nWidth, nHeight, nDepth, nLeft, nTop );
   
   // Recreate the camera so our viewport and UI aspect ratio match the new screen dimensions
   if ( UISys()->IsActive( MainMenu_UIScreenType ) )
   {
      UISys()->GetUIMainMenu()->ConfigureAspectRatio( nWidth, nHeight );
   }
   else
   {
      CameraSys()->RecreateCamera();
   }
   
   InputSys()->OnWindowResized( nWidth, nHeight );

   CEGUI::Size newSize( (float)nWidth, (float)nHeight );
   CEGUI::System::getSingleton().notifyDisplaySizeChanged( newSize );
}


bool GraphicsSystem::windowClosing( RenderWindow* rw )
{
   if ( GameSys()->IsShuttingDown() ) 
   {
      // already underway
      return false;
   }
   
   // Ever since I added this, running in debug mode and hitting the "stop" button in visual studio
   // Caused us to spew out all the unreleased memory stuff via our debug new macros (see vs_debug.h).
   // This causes visual studio to freeze for several minutes.
   // So I'm going to not do this in debug.
   // - only a problem in visual studio / windows.
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 && _NDEBUG
   // Do nothing.
#else
   GameSys()->RequestShutdown();
#endif
   
   // Return false not to close the window, since our shutdown code will handle that.
   return false;
}


void GraphicsSystem::ClearSceneManagerPassMaps()
{
   // See header notes for a description of this function.

   if ( !m_pOgreRoot ) return;

   SceneManagerEnumerator::SceneManagerIterator itSM = m_pOgreRoot->getSceneManagerIterator();

   // This bug only manifests itself if there are more than one SceneManager.
   // I'm  not sure of the performance cost of clearing the render queue, so I'm
   // only going to do it if we have >1 SceneManager.

   // We often only have once SceneManager (for the active Scene), but sometimes create
   // additional ones for our SceneImage class, e.g. for UICommunicate or UIMission.
   int nNumSceneMgr = 0;
   while ( itSM.hasMoreElements() )
   {
      ++nNumSceneMgr;
      itSM.getNext();
   }

   if ( nNumSceneMgr <= 1 )
   {
      return;
   }

   // We have more than 1 SceneMgr.
   // Clear the RenderQueue to avoid the multiple-SceneManager bug.
   itSM = m_pOgreRoot->getSceneManagerIterator();

   while (itSM.hasMoreElements())
   {
      SceneManager* pSceneMgr = itSM.getNext();

      if ( pSceneMgr )
      {
         Ogre::RenderQueue* pQueue = pSceneMgr->getRenderQueue();
         if ( pQueue )
         {
            pQueue->clear( true );
         }
      }
   }

   Pass::processPendingPassUpdates();
}


#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX
SDL_Window* GraphicsSystem::GetSDLWindow()
{
   return m_pSDLWindow;
}


bool GraphicsSystem::HasSDLWindow() const
{
   if ( m_pSDLWindow ) return true;
   return false;
}
#endif


// TextureQualitySetting member functions
GraphicsSystem::TextureQualitySetting::TextureQualitySetting()
   : m_nMipMapSkipHigh     ( 0 )
   , m_nMipMapSkipMedium   ( 0 )
   , m_nMipMapSkipLow      ( 0 )
{}


void GraphicsSystem::TextureQualitySetting::LoadXML( XMLNode& i_node )
{
   LoadDataMember( i_node, "mipmap_skip_high", m_nMipMapSkipHigh, XML::DATA_REQUIRED );
   LoadDataMember( i_node, "mipmap_skip_medium", m_nMipMapSkipMedium, XML::DATA_REQUIRED );
   LoadDataMember( i_node, "mipmap_skip_low", m_nMipMapSkipLow, XML::DATA_REQUIRED );
}



