GameLOD.cpp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707
  1. /*
  2. ** Command & Conquer Generals Zero Hour(tm)
  3. ** Copyright 2025 Electronic Arts Inc.
  4. **
  5. ** This program is free software: you can redistribute it and/or modify
  6. ** it under the terms of the GNU General Public License as published by
  7. ** the Free Software Foundation, either version 3 of the License, or
  8. ** (at your option) any later version.
  9. **
  10. ** This program is distributed in the hope that it will be useful,
  11. ** but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ** GNU General Public License for more details.
  14. **
  15. ** You should have received a copy of the GNU General Public License
  16. ** along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. ////////////////////////////////////////////////////////////////////////////////
  19. // //
  20. // (c) 2001-2003 Electronic Arts Inc. //
  21. // //
  22. ////////////////////////////////////////////////////////////////////////////////
  23. // FILE: GameLOD.cpp ///////////////////////////////////////////////////////////
  24. //
  25. // Used to set detail levels of various game systems.
  26. //
  27. // Author: Mark Wilczynski, Sept 2002
  28. //
  29. //
  30. ///////////////////////////////////////////////////////////////////////////////
  31. #include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine
  32. #include "Common/GameLOD.h"
  33. #include "GameClient/TerrainVisual.h"
  34. #include "GameClient/GameClient.h"
  35. #include "Common/UserPreferences.h"
  36. #define DEFINE_PARTICLE_SYSTEM_NAMES
  37. #include "GameClient/ParticleSys.h"
  38. #ifdef _INTERNAL
  39. // for occasional debugging...
  40. //#pragma optimize("", off)
  41. //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
  42. #endif
  43. #define PROFILE_ERROR_LIMIT 0.94f //fraction of profiled result needed to get a match. Allows some room for error/fluctuation.
  44. //Hack to get access to a static method on the W3DDevice side. -MW
  45. extern Bool testMinimumRequirements(ChipsetType *videoChipType, CpuType *cpuType, Int *cpuFreq, Int *numRAM, Real *intBenchIndex, Real *floatBenchIndex, Real *memBenchIndex);
  46. GameLODManager *TheGameLODManager=NULL;
  47. static const FieldParse TheStaticGameLODFieldParseTable[] =
  48. {
  49. { "MinimumFPS", INI::parseInt, NULL, offsetof( StaticGameLODInfo, m_minFPS)},
  50. { "MinimumProcessorFps", INI::parseInt, NULL, offsetof( StaticGameLODInfo, m_minProcessorFPS)},
  51. { "SampleCount2D", INI::parseInt, NULL, offsetof( StaticGameLODInfo, m_sampleCount2D ) },
  52. { "SampleCount3D", INI::parseInt, NULL, offsetof( StaticGameLODInfo, m_sampleCount3D ) },
  53. { "StreamCount", INI::parseInt, NULL, offsetof( StaticGameLODInfo, m_streamCount ) },
  54. { "MaxParticleCount", INI::parseInt, NULL, offsetof( StaticGameLODInfo, m_maxParticleCount ) },
  55. { "UseShadowVolumes", INI::parseBool, NULL, offsetof( StaticGameLODInfo, m_useShadowVolumes ) },
  56. { "UseShadowDecals", INI::parseBool, NULL, offsetof( StaticGameLODInfo, m_useShadowDecals ) },
  57. { "UseCloudMap", INI::parseBool, NULL, offsetof( StaticGameLODInfo, m_useCloudMap ) },
  58. { "UseLightMap", INI::parseBool, NULL, offsetof( StaticGameLODInfo, m_useLightMap ) },
  59. { "ShowSoftWaterEdge", INI::parseBool, NULL, offsetof( StaticGameLODInfo, m_showSoftWaterEdge ) },
  60. { "MaxTankTrackEdges", INI::parseInt, NULL, offsetof( StaticGameLODInfo, m_maxTankTrackEdges) },
  61. { "MaxTankTrackOpaqueEdges", INI::parseInt, NULL, offsetof( StaticGameLODInfo, m_maxTankTrackOpaqueEdges) },
  62. { "MaxTankTrackFadeDelay", INI::parseInt, NULL, offsetof( StaticGameLODInfo, m_maxTankTrackFadeDelay) },
  63. { "UseBuildupScaffolds", INI::parseBool, NULL, offsetof( StaticGameLODInfo, m_useBuildupScaffolds ) },
  64. { "UseTreeSway", INI::parseBool, NULL, offsetof( StaticGameLODInfo, m_useTreeSway ) },
  65. { "UseEmissiveNightMaterials", INI::parseBool, NULL, offsetof( StaticGameLODInfo, m_useEmissiveNightMaterials ) },
  66. { "UseHeatEffects", INI::parseBool, NULL, offsetof( StaticGameLODInfo, m_useHeatEffects ) },
  67. { "TextureReductionFactor", INI::parseInt, NULL, offsetof( StaticGameLODInfo, m_textureReduction ) },
  68. };
  69. static const char *StaticGameLODNames[]=
  70. {
  71. "Low",
  72. "Medium",
  73. "High",
  74. "Custom"
  75. };
  76. StaticGameLODInfo::StaticGameLODInfo(void)
  77. {
  78. m_minFPS=0;
  79. m_minProcessorFPS=0;
  80. m_sampleCount2D=6;
  81. m_sampleCount3D=24;
  82. m_streamCount=2;
  83. m_maxParticleCount=2500;
  84. m_useShadowVolumes=TRUE;
  85. m_useShadowDecals=TRUE;
  86. m_useCloudMap=TRUE;
  87. m_useLightMap=TRUE;
  88. m_showSoftWaterEdge=TRUE;
  89. m_maxTankTrackEdges=100;
  90. m_maxTankTrackOpaqueEdges=25;
  91. m_maxTankTrackFadeDelay=300000;
  92. m_useBuildupScaffolds=TRUE;
  93. m_useTreeSway=TRUE;
  94. m_useEmissiveNightMaterials=TRUE;
  95. m_useHeatEffects=TRUE;
  96. m_textureReduction = 0; //none
  97. m_useFpsLimit = TRUE;
  98. m_enableDynamicLOD = TRUE;
  99. m_useTrees = TRUE;
  100. }
  101. static const FieldParse TheDynamicGameLODFieldParseTable[] =
  102. {
  103. { "MinimumFPS", INI::parseInt, NULL, offsetof( DynamicGameLODInfo, m_minFPS)},
  104. { "ParticleSkipMask", INI::parseInt, NULL, offsetof( DynamicGameLODInfo, m_dynamicParticleSkipMask)},
  105. { "DebrisSkipMask", INI::parseInt, NULL, offsetof( DynamicGameLODInfo, m_dynamicDebrisSkipMask)},
  106. { "SlowDeathScale", INI::parseReal, NULL, offsetof( DynamicGameLODInfo, m_slowDeathScale)},
  107. { "MinParticlePriority", INI::parseIndexList, ParticlePriorityNames, offsetof( DynamicGameLODInfo, m_minDynamicParticlePriority)},
  108. { "MinParticleSkipPriority", INI::parseIndexList, ParticlePriorityNames, offsetof( DynamicGameLODInfo, m_minDynamicParticleSkipPriority)},
  109. };
  110. static const char *DynamicGameLODNames[]=
  111. {
  112. "Low",
  113. "Medium",
  114. "High",
  115. "VeryHigh"
  116. };
  117. DynamicGameLODInfo::DynamicGameLODInfo(void)
  118. {
  119. m_minFPS=0;
  120. m_dynamicParticleSkipMask=0;
  121. m_dynamicDebrisSkipMask=0;
  122. m_slowDeathScale=1.0f;
  123. m_minDynamicParticlePriority = PARTICLE_PRIORITY_LOWEST;
  124. m_minDynamicParticleSkipPriority = PARTICLE_PRIORITY_LOWEST;
  125. };
  126. //Keep this in sync with enum in GameLOD.h
  127. static char *CPUNames[] =
  128. {
  129. "XX","P3", "P4","K7", NULL
  130. };
  131. //Keep this in sync with enum in GameLOD.h
  132. static char *VideoNames[] =
  133. {
  134. "XX","V2","V3","V4","V5","TNT","TNT2","GF2","R100","PS11","GF3","GF4","PS14","R200","PS20","R300", NULL
  135. };
  136. void parseReallyLowMHz(INI* ini)
  137. {
  138. Int mhz;
  139. INI::parseInt(ini,NULL,&mhz,NULL);
  140. if (TheGameLODManager)
  141. {
  142. TheGameLODManager->setReallyLowMHz(mhz);
  143. }
  144. }
  145. void INI::parseBenchProfile( INI* ini)
  146. {
  147. if( TheGameLODManager )
  148. {
  149. BenchProfile *preset = TheGameLODManager->newBenchProfile();
  150. if (preset)
  151. {
  152. INI::parseIndexList(ini,NULL,&preset->m_cpuType,CPUNames);
  153. INI::parseInt(ini,NULL,&preset->m_mhz,NULL);
  154. INI::parseReal(ini,NULL,&preset->m_intBenchIndex,NULL);
  155. INI::parseReal(ini,NULL,&preset->m_floatBenchIndex,NULL);
  156. INI::parseReal(ini,NULL,&preset->m_memBenchIndex,NULL);
  157. }
  158. }
  159. }
  160. /**Parse a description of all the LOD settings for a given detail level*/
  161. void INI::parseLODPreset( INI* ini )
  162. {
  163. const char *c;
  164. AsciiString name;
  165. // read the name
  166. c = ini->getNextToken();
  167. name.set( c ); //name of detail level - low, medium, high
  168. if( TheGameLODManager )
  169. {
  170. StaticGameLODLevel index = (StaticGameLODLevel)TheGameLODManager->getStaticGameLODIndex(name);
  171. if (index != STATIC_GAME_LOD_UNKNOWN)
  172. {
  173. LODPresetInfo *preset = TheGameLODManager->newLODPreset(index);
  174. if (preset)
  175. {
  176. INI::parseIndexList(ini,NULL,&preset->m_cpuType,CPUNames);
  177. INI::parseInt(ini,NULL,&preset->m_mhz,NULL);
  178. INI::parseIndexList(ini,NULL,&preset->m_videoType,VideoNames);
  179. INI::parseInt(ini,NULL,&preset->m_memory,NULL);
  180. }
  181. }
  182. }
  183. }
  184. GameLODManager::GameLODManager(void)
  185. {
  186. m_currentStaticLOD = STATIC_GAME_LOD_UNKNOWN;
  187. m_currentDynamicLOD = DYNAMIC_GAME_LOD_HIGH;
  188. m_numParticleGenerations=0;
  189. m_dynamicParticleSkipMask=0;
  190. m_numDebrisGenerations=0;
  191. m_dynamicDebrisSkipMask=0;
  192. m_videoPassed=false;
  193. m_cpuPassed=false;
  194. m_memPassed=false;
  195. m_slowDeathScale=1.0f;
  196. m_idealDetailLevel = STATIC_GAME_LOD_UNKNOWN;
  197. m_videoChipType = DC_MAX;
  198. m_cpuType = XX;
  199. m_numRAM=0;
  200. m_cpuFreq=0;
  201. m_intBenchIndex=0;
  202. m_floatBenchIndex=0;
  203. m_memBenchIndex=0;
  204. m_compositeBenchIndex=0;
  205. m_numBenchProfiles=0;
  206. m_currentTextureReduction=0;
  207. m_reallyLowMHz = 400;
  208. for (Int i=0; i<STATIC_GAME_LOD_CUSTOM; i++)
  209. m_numLevelPresets[i]=0;
  210. };
  211. GameLODManager::~GameLODManager()
  212. {
  213. }
  214. BenchProfile *GameLODManager::newBenchProfile(void)
  215. {
  216. if (m_numBenchProfiles < MAX_BENCH_PROFILES)
  217. {
  218. m_numBenchProfiles++;
  219. return &m_benchProfiles[m_numBenchProfiles-1];
  220. }
  221. DEBUG_CRASH(( "GameLODManager::newBenchProfile - Too many profiles defined\n"));
  222. return NULL;
  223. }
  224. LODPresetInfo *GameLODManager::newLODPreset(StaticGameLODLevel index)
  225. {
  226. if (m_numLevelPresets[index] < MAX_LOD_PRESETS_PER_LEVEL)
  227. {
  228. m_numLevelPresets[index]++;
  229. return &m_lodPresets[index][m_numLevelPresets[index]-1];
  230. }
  231. DEBUG_CRASH(( "GameLODManager::newLODPreset - Too many presets defined for '%s'\n", TheGameLODManager->getStaticGameLODLevelName(index)));
  232. return NULL;
  233. }
  234. void GameLODManager::init(void)
  235. {
  236. INI ini;
  237. //Get Presets for each LOD level.
  238. ini.load( AsciiString( "Data\\INI\\GameLOD.ini" ), INI_LOAD_OVERWRITE, NULL );
  239. //Get presets for each known hardware configuration
  240. ini.load( AsciiString( "Data\\INI\\GameLODPresets.ini"), INI_LOAD_OVERWRITE, NULL);
  241. //Get Presets for custom LOD level by pulling them out of initial globaldata (which should
  242. //have all settings already applied).
  243. refreshCustomStaticLODLevel();
  244. //Override with user preferences
  245. OptionPreferences optionPref;
  246. StaticGameLODLevel userSetDetail=(StaticGameLODLevel)optionPref.getStaticGameDetail();
  247. m_idealDetailLevel=(StaticGameLODLevel)optionPref.getIdealStaticGameDetail();
  248. //always get this data in case we need it later.
  249. testMinimumRequirements(NULL,&m_cpuType,&m_cpuFreq,&m_numRAM,NULL,NULL,NULL);
  250. if ((Real)(m_numRAM)/(Real)(256*1024*1024) >= PROFILE_ERROR_LIMIT)
  251. m_memPassed=TRUE; //check if they have at least 256 MB
  252. if (m_idealDetailLevel == STATIC_GAME_LOD_UNKNOWN || TheGlobalData->m_forceBenchmark)
  253. {
  254. if (m_cpuType == XX || TheGlobalData->m_forceBenchmark)
  255. {
  256. //need to run the benchmark
  257. testMinimumRequirements(NULL,NULL,NULL,NULL,&m_intBenchIndex,&m_floatBenchIndex,&m_memBenchIndex);
  258. if (TheGlobalData->m_forceBenchmark)
  259. { //we want to see the numbers. So dump them to a logfile.
  260. FILE *fp=fopen("Benchmark.txt","w");
  261. if (fp)
  262. {
  263. fprintf(fp,"BenchProfile = %s %d %f %f %f", CPUNames[m_cpuType], m_cpuFreq, m_intBenchIndex, m_floatBenchIndex, m_memBenchIndex);
  264. fclose(fp);
  265. }
  266. }
  267. m_compositeBenchIndex = m_intBenchIndex + m_floatBenchIndex; ///@todo: Need to scale these based on our apps usage of int/float/mem ops.
  268. StaticGameLODLevel currentLevel=STATIC_GAME_LOD_LOW;
  269. BenchProfile *prof=m_benchProfiles;
  270. m_cpuType = P3; //assume lowest spec.
  271. m_cpuFreq = 1000; //assume lowest spec.
  272. for (Int k=0; k<m_numBenchProfiles; k++)
  273. {
  274. //Check if we're within 5% of the performance of this cpu profile.
  275. if (m_intBenchIndex/prof->m_intBenchIndex >= PROFILE_ERROR_LIMIT && m_floatBenchIndex/prof->m_floatBenchIndex >= PROFILE_ERROR_LIMIT && m_memBenchIndex/prof->m_memBenchIndex >= PROFILE_ERROR_LIMIT)
  276. {
  277. for (Int i=STATIC_GAME_LOD_HIGH; i >= STATIC_GAME_LOD_LOW; i--)
  278. {
  279. LODPresetInfo *preset=&m_lodPresets[i][0]; //pointer to first preset at this LOD level.
  280. for (Int j=0; j<m_numLevelPresets[i]; j++)
  281. {
  282. if( prof->m_cpuType == preset->m_cpuType && ((Real)prof->m_mhz/(Real)preset->m_mhz >= PROFILE_ERROR_LIMIT))
  283. { currentLevel = (StaticGameLODLevel)i;
  284. m_cpuType = prof->m_cpuType;
  285. m_cpuFreq = prof->m_mhz;
  286. break;
  287. }
  288. preset++; //skip to next preset
  289. }
  290. if (currentLevel >= i)
  291. break; //we already found a higher level than the remaining presets so no need to keep searching.
  292. }
  293. }
  294. prof++;
  295. }
  296. } //finding equivalent CPU to unkown cpu.
  297. } //find data needed to determine m_idealDetailLevel
  298. if (userSetDetail == STATIC_GAME_LOD_CUSTOM)
  299. {
  300. TheWritableGlobalData->m_textureReductionFactor = optionPref.getTextureReduction();
  301. TheWritableGlobalData->m_useShadowVolumes = optionPref.get3DShadowsEnabled();
  302. TheWritableGlobalData->m_useShadowDecals = optionPref.get2DShadowsEnabled();
  303. TheWritableGlobalData->m_enableBehindBuildingMarkers = optionPref.getBuildingOcclusionEnabled();
  304. TheWritableGlobalData->m_maxParticleCount = optionPref.getParticleCap();
  305. TheWritableGlobalData->m_enableDynamicLOD = optionPref.getDynamicLODEnabled();
  306. TheWritableGlobalData->m_useFpsLimit = optionPref.getFPSLimitEnabled();
  307. TheWritableGlobalData->m_useLightMap = optionPref.getLightmapEnabled();
  308. TheWritableGlobalData->m_useCloudMap = optionPref.getCloudShadowsEnabled();
  309. TheWritableGlobalData->m_showSoftWaterEdge = optionPref.getSmoothWaterEnabled();
  310. TheWritableGlobalData->m_useHeatEffects = optionPref.getUseHeatEffects();
  311. TheWritableGlobalData->m_useDrawModuleLOD = optionPref.getExtraAnimationsDisabled();
  312. TheWritableGlobalData->m_useTreeSway = !TheWritableGlobalData->m_useDrawModuleLOD; //borrow same setting.
  313. TheWritableGlobalData->m_useTrees = optionPref.getTreesEnabled();
  314. }
  315. setStaticLODLevel(userSetDetail);
  316. }
  317. void GameLODManager::refreshCustomStaticLODLevel(void)
  318. {
  319. StaticGameLODInfo *lodInfo=&m_staticGameLODInfo[STATIC_GAME_LOD_CUSTOM];
  320. lodInfo->m_maxParticleCount=TheGlobalData->m_maxParticleCount;
  321. lodInfo->m_useShadowVolumes=TheGlobalData->m_useShadowVolumes;
  322. lodInfo->m_useShadowDecals=TheGlobalData->m_useShadowDecals;
  323. lodInfo->m_useCloudMap=TheGlobalData->m_useCloudMap;
  324. lodInfo->m_useLightMap=TheGlobalData->m_useLightMap;
  325. lodInfo->m_showSoftWaterEdge=TheGlobalData->m_showSoftWaterEdge;
  326. lodInfo->m_maxTankTrackEdges=TheGlobalData->m_maxTankTrackEdges;
  327. lodInfo->m_maxTankTrackOpaqueEdges=TheGlobalData->m_maxTankTrackOpaqueEdges;
  328. lodInfo->m_maxTankTrackFadeDelay=TheGlobalData->m_maxTankTrackFadeDelay;
  329. lodInfo->m_useBuildupScaffolds=!TheGlobalData->m_useDrawModuleLOD;
  330. lodInfo->m_useHeatEffects = TheGlobalData->m_useHeatEffects;
  331. lodInfo->m_useTreeSway=lodInfo->m_useBuildupScaffolds;// Borrow same setting. //TheGlobalData->m_useTreeSway;
  332. lodInfo->m_textureReduction=TheGlobalData->m_textureReductionFactor;
  333. lodInfo->m_useFpsLimit = TheGlobalData->m_useFpsLimit;
  334. lodInfo->m_enableDynamicLOD=TheGlobalData->m_enableDynamicLOD;
  335. lodInfo->m_useTrees = TheGlobalData->m_useTrees;
  336. }
  337. /**Convert LOD name to an index*/
  338. Int GameLODManager::getStaticGameLODIndex(AsciiString name)
  339. {
  340. for (Int i=0; i<STATIC_GAME_LOD_COUNT; ++i)
  341. {
  342. if (name.compareNoCase(StaticGameLODNames[i]) == 0)
  343. return i;
  344. }
  345. DEBUG_CRASH(( "GameLODManager::getGameLODIndex - Invalid LOD name '%s'\n", name.str() ));
  346. return STATIC_GAME_LOD_UNKNOWN;
  347. }
  348. /**Parse a description of all the LOD settings for a given detail level*/
  349. void INI::parseStaticGameLODDefinition( INI* ini )
  350. {
  351. const char *c;
  352. AsciiString name;
  353. // read the name
  354. c = ini->getNextToken();
  355. name.set( c );
  356. if( TheGameLODManager )
  357. {
  358. Int index = TheGameLODManager->getStaticGameLODIndex(name);
  359. if (index != STATIC_GAME_LOD_UNKNOWN)
  360. {
  361. StaticGameLODInfo *lodInfo = &(TheGameLODManager->m_staticGameLODInfo[index]);
  362. // parse the ini definition
  363. ini->initFromINI( lodInfo, TheStaticGameLODFieldParseTable );
  364. }
  365. }
  366. }
  367. /**Parse an LOD level*/
  368. void INI::parseStaticGameLODLevel( INI* ini, void * , void *store, const void*)
  369. {
  370. const char *tok=ini->getNextToken();
  371. for (Int i=0; i<STATIC_GAME_LOD_COUNT; i++)
  372. if( stricmp(tok, StaticGameLODNames[i]) == 0 )
  373. { *(StaticGameLODLevel*)store = (StaticGameLODLevel)i;
  374. return;
  375. }
  376. DEBUG_CRASH(("invalid GameLODLevel token %s -- expected LOW/MEDIUM/HIGH\n",tok));
  377. throw INI_INVALID_DATA;
  378. }
  379. const char *GameLODManager::getStaticGameLODLevelName(StaticGameLODLevel level)
  380. {
  381. return StaticGameLODNames[level];
  382. }
  383. /**Function which calculates the recommended LOD level for current hardware
  384. configuration.*/
  385. StaticGameLODLevel GameLODManager::findStaticLODLevel(void)
  386. {
  387. //Check if we have never done the test on current system
  388. if (m_idealDetailLevel == STATIC_GAME_LOD_UNKNOWN)
  389. {
  390. //search all our presets for matching hardware
  391. m_idealDetailLevel = STATIC_GAME_LOD_LOW;
  392. //get system configuration - only need vide chip type, got rest in ::init().
  393. testMinimumRequirements(&m_videoChipType,NULL,NULL,NULL,NULL,NULL,NULL);
  394. if (m_videoChipType == DC_UNKNOWN)
  395. m_videoChipType = DC_TNT2; //presume it's at least TNT2 level
  396. Int numMBRam=m_numRAM/(1024*1024);
  397. for (Int i=STATIC_GAME_LOD_HIGH; i >= STATIC_GAME_LOD_LOW; i--)
  398. {
  399. LODPresetInfo *preset=&m_lodPresets[i][0]; //pointer to first preset at this LOD level.
  400. for (Int j=0; j<m_numLevelPresets[i]; j++)
  401. {
  402. if( m_cpuType == preset->m_cpuType &&
  403. ((Real)m_cpuFreq/(Real)preset->m_mhz >= PROFILE_ERROR_LIMIT) &&//make sure we're within 5% or higher
  404. m_videoChipType >= preset->m_videoType &&
  405. ((Real)numMBRam/(Real)preset->m_memory >= PROFILE_ERROR_LIMIT)
  406. )
  407. { m_idealDetailLevel = (StaticGameLODLevel)i;
  408. break;
  409. }
  410. preset++; //skip to next preset
  411. }
  412. if (m_idealDetailLevel >= i)
  413. break; //we already found a higher level than the remaining presets so no need to keep searching.
  414. }
  415. //Save ideal detail level for future usage
  416. OptionPreferences optionPref;
  417. optionPref["IdealStaticGameLOD"] = getStaticGameLODLevelName(m_idealDetailLevel);
  418. if (getStaticLODLevel() == STATIC_GAME_LOD_UNKNOWN) //save for future usage.
  419. optionPref["StaticGameLOD"] = getStaticGameLODLevelName(m_idealDetailLevel);
  420. optionPref.write();
  421. }
  422. return m_idealDetailLevel;
  423. }
  424. /**Set all game systems to match the desired LOD level.*/
  425. Bool GameLODManager::setStaticLODLevel(StaticGameLODLevel level)
  426. {
  427. if (!TheGlobalData->m_enableStaticLOD)
  428. { m_currentStaticLOD = STATIC_GAME_LOD_CUSTOM;
  429. return FALSE;
  430. }
  431. if (level == STATIC_GAME_LOD_UNKNOWN || (level != STATIC_GAME_LOD_CUSTOM && m_currentStaticLOD == level))
  432. return FALSE; //level is already applied. Custom levels are always applied since random options could change.
  433. applyStaticLODLevel(level);
  434. m_currentStaticLOD = level;
  435. return TRUE;
  436. }
  437. void GameLODManager::applyStaticLODLevel(StaticGameLODLevel level)
  438. {
  439. ///@todo: Still need to implement these settings:
  440. // m_sampleCount2D=6;
  441. // m_sampleCount3D=24;
  442. // m_streamCount=2;
  443. // m_useEmissiveNightMaterials=TRUE;
  444. //save previous info for this level since it may be overwritten by refreshCustomStaticLODLevel().
  445. StaticGameLODInfo prevLodBackup;
  446. if (m_currentStaticLOD != STATIC_GAME_LOD_UNKNOWN)
  447. prevLodBackup=m_staticGameLODInfo[m_currentStaticLOD];
  448. if (level == STATIC_GAME_LOD_CUSTOM)
  449. refreshCustomStaticLODLevel(); //store current settings into custom preset
  450. StaticGameLODInfo *lodInfo=&m_staticGameLODInfo[level];
  451. StaticGameLODInfo *prevLodInfo=&prevLodBackup;
  452. Int requestedTextureReduction = 0;
  453. Bool requestedTrees = m_memPassed; //only use trees if memory requirement passed.
  454. if (level == STATIC_GAME_LOD_CUSTOM)
  455. { requestedTextureReduction = lodInfo->m_textureReduction;
  456. requestedTrees = lodInfo->m_useTrees;
  457. }
  458. else
  459. if (level >= STATIC_GAME_LOD_LOW)
  460. { //normal non-custom level gets texture reduction based on recommendation
  461. requestedTextureReduction = getRecommendedTextureReduction();
  462. }
  463. if (TheGlobalData)
  464. {
  465. TheWritableGlobalData->m_maxParticleCount=lodInfo->m_maxParticleCount;
  466. TheWritableGlobalData->m_useShadowVolumes=lodInfo->m_useShadowVolumes;
  467. TheWritableGlobalData->m_useShadowDecals=lodInfo->m_useShadowDecals;
  468. //Check if texture resolution changed. No need to apply when current is unknown because display will do it
  469. if (requestedTextureReduction != m_currentTextureReduction)
  470. {
  471. TheWritableGlobalData->m_textureReductionFactor = requestedTextureReduction;
  472. if (TheGameClient)
  473. TheGameClient->adjustLOD(0); //apply the new setting stored in globaldata
  474. }
  475. //Check if shadow state changed
  476. if (m_currentStaticLOD == STATIC_GAME_LOD_UNKNOWN ||
  477. lodInfo->m_useShadowVolumes != prevLodInfo->m_useShadowVolumes ||
  478. lodInfo->m_useShadowDecals != prevLodInfo->m_useShadowDecals)
  479. {
  480. if (TheGameClient)
  481. {
  482. TheGameClient->releaseShadows(); //free all shadows
  483. TheGameClient->allocateShadows(); //allocate those shadows that are enabled.
  484. }
  485. }
  486. TheWritableGlobalData->m_useCloudMap=lodInfo->m_useCloudMap;
  487. TheWritableGlobalData->m_useLightMap=lodInfo->m_useLightMap;
  488. TheWritableGlobalData->m_showSoftWaterEdge=lodInfo->m_showSoftWaterEdge;
  489. //Check if shoreline blending mode has changed
  490. if (m_currentStaticLOD == STATIC_GAME_LOD_UNKNOWN || lodInfo->m_showSoftWaterEdge != prevLodInfo->m_showSoftWaterEdge)
  491. {
  492. if (TheTerrainVisual)
  493. TheTerrainVisual->setShoreLineDetail();
  494. }
  495. TheWritableGlobalData->m_maxTankTrackEdges=lodInfo->m_maxTankTrackEdges;
  496. TheWritableGlobalData->m_maxTankTrackOpaqueEdges=lodInfo->m_maxTankTrackOpaqueEdges;
  497. TheWritableGlobalData->m_maxTankTrackFadeDelay=lodInfo->m_maxTankTrackFadeDelay;
  498. TheWritableGlobalData->m_useTreeSway=lodInfo->m_useTreeSway;
  499. TheWritableGlobalData->m_useDrawModuleLOD=!lodInfo->m_useBuildupScaffolds;
  500. TheWritableGlobalData->m_useHeatEffects=lodInfo->m_useHeatEffects;
  501. TheWritableGlobalData->m_enableDynamicLOD = lodInfo->m_enableDynamicLOD;
  502. TheWritableGlobalData->m_useFpsLimit = lodInfo->m_useFpsLimit;
  503. TheWritableGlobalData->m_useTrees = requestedTrees;
  504. }
  505. if (!m_memPassed || isReallyLowMHz()) {
  506. TheWritableGlobalData->m_shellMapOn = false;
  507. }
  508. if (TheTerrainVisual)
  509. TheTerrainVisual->setTerrainTracksDetail();
  510. }
  511. /**Parse a description of all the LOD settings for a given detail level*/
  512. void INI::parseDynamicGameLODDefinition( INI* ini )
  513. {
  514. const char *c;
  515. AsciiString name;
  516. // read the name
  517. c = ini->getNextToken();
  518. name.set( c );
  519. if( TheGameLODManager )
  520. {
  521. Int index = TheGameLODManager->getDynamicGameLODIndex(name);
  522. if (index != DYNAMIC_GAME_LOD_UNKNOWN)
  523. {
  524. DynamicGameLODInfo *lodInfo = &(TheGameLODManager->m_dynamicGameLODInfo[index]);
  525. // parse the ini weapon definition
  526. ini->initFromINI( lodInfo, TheDynamicGameLODFieldParseTable );
  527. }
  528. }
  529. }
  530. /**Parse an LOD level*/
  531. void INI::parseDynamicGameLODLevel( INI* ini, void * , void *store, const void*)
  532. {
  533. const char *tok=ini->getNextToken();
  534. for (Int i=0; i<DYNAMIC_GAME_LOD_COUNT; i++)
  535. if( stricmp(tok, DynamicGameLODNames[i]) == 0 )
  536. { *(DynamicGameLODLevel*)store = (DynamicGameLODLevel)i;
  537. return;
  538. }
  539. DEBUG_CRASH(("invalid GameLODLevel token %s -- expected LOW/MEDIUM/HIGH\n",tok));
  540. throw INI_INVALID_DATA;
  541. }
  542. /**Convert LOD name to an index*/
  543. Int GameLODManager::getDynamicGameLODIndex(AsciiString name)
  544. {
  545. for (Int i=0; i<DYNAMIC_GAME_LOD_COUNT; ++i)
  546. {
  547. if (name.compareNoCase(DynamicGameLODNames[i]) == 0)
  548. return i;
  549. }
  550. DEBUG_CRASH(( "GameLODManager::getGameLODIndex - Invalid LOD name '%s'\n", name.str() ));
  551. return STATIC_GAME_LOD_UNKNOWN;
  552. }
  553. const char *GameLODManager::getDynamicGameLODLevelName(DynamicGameLODLevel level)
  554. {
  555. return DynamicGameLODNames[level];
  556. }
  557. /**Given an average fps, return the optimal dynamic LOD level that matches this fps.*/
  558. DynamicGameLODLevel GameLODManager::findDynamicLODLevel(Real averageFPS)
  559. {
  560. Int ifps=(Int)(averageFPS); //convert to integer.
  561. for (Int i=DYNAMIC_GAME_LOD_VERY_HIGH; i>=DYNAMIC_GAME_LOD_LOW; i--)
  562. { //check which of the LOD levels matches our fps
  563. if (m_dynamicGameLODInfo[i].m_minFPS < ifps)
  564. return (DynamicGameLODLevel)i;
  565. }
  566. return DYNAMIC_GAME_LOD_LOW; //none of the low levels were slow enough so pick the lowest.
  567. }
  568. /**Set all game systems to match the desired LOD level.*/
  569. Bool GameLODManager::setDynamicLODLevel(DynamicGameLODLevel level)
  570. {
  571. if (level == DYNAMIC_GAME_LOD_UNKNOWN || m_currentDynamicLOD == level)
  572. return FALSE;
  573. m_currentDynamicLOD = level;
  574. applyDynamicLODLevel(level);
  575. return TRUE;
  576. }
  577. void GameLODManager::applyDynamicLODLevel(DynamicGameLODLevel level)
  578. {
  579. m_numParticleGenerations=0;
  580. m_dynamicParticleSkipMask=m_dynamicGameLODInfo[level].m_dynamicParticleSkipMask;
  581. m_numDebrisGenerations=0;
  582. m_dynamicDebrisSkipMask=m_dynamicGameLODInfo[level].m_dynamicDebrisSkipMask;
  583. m_slowDeathScale=m_dynamicGameLODInfo[level].m_slowDeathScale;
  584. m_minDynamicParticlePriority=m_dynamicGameLODInfo[level].m_minDynamicParticlePriority;
  585. m_minDynamicParticleSkipPriority=m_dynamicGameLODInfo[level].m_minDynamicParticleSkipPriority;
  586. }
  587. Int GameLODManager::getRecommendedTextureReduction(void)
  588. {
  589. if (m_idealDetailLevel == STATIC_GAME_LOD_UNKNOWN)
  590. findStaticLODLevel(); //it was never tested, so test now.
  591. if (!m_memPassed) //if they have < 256 MB, force them to low res textures.
  592. return m_staticGameLODInfo[STATIC_GAME_LOD_LOW].m_textureReduction;
  593. return m_staticGameLODInfo[m_idealDetailLevel].m_textureReduction;
  594. }
  595. Int GameLODManager::getLevelTextureReduction(StaticGameLODLevel level)
  596. {
  597. return m_staticGameLODInfo[level].m_textureReduction;
  598. }
  599. Bool GameLODManager::didMemPass( void )
  600. {
  601. return m_memPassed;
  602. }