/** * @file llfeaturemanager.cpp * @brief LLFeatureManager class implementation * * Copyright (c) 2003-$CurrentYear$, Linden Research, Inc. * $License$ */ #include #include #include "llviewerprecompiledheaders.h" #include "llfeaturemanager.h" #include "lldir.h" #include "llsys.h" #include "llgl.h" #include "llsecondlifeurls.h" #include "llviewercontrol.h" #include "llworld.h" #include "pipeline.h" #include "lldrawpoolterrain.h" #include "llviewerimagelist.h" #include "llwindow.h" #include "llui.h" #if LL_WINDOWS #include "lldxhardware.h" #endif // // externs // extern LLMemoryInfo gSysMemory; extern LLCPUInfo gSysCPU; extern void write_debug(const char *str); extern void write_debug(const std::string& str); #if LL_DARWIN const char FEATURE_TABLE_FILENAME[] = "featuretable_mac.txt"; #else const char FEATURE_TABLE_FILENAME[] = "featuretable.txt"; #endif const char GPU_TABLE_FILENAME[] = "gpu_table.txt"; LLFeatureManager *gFeatureManagerp = NULL; LLFeatureInfo::LLFeatureInfo(const char *name, const BOOL available, const S32 level) : mValid(TRUE) { mName = name; mAvailable = available; mRecommendedLevel = level; } LLFeatureList::LLFeatureList(const char *name) { mName = name; } LLFeatureList::~LLFeatureList() { } void LLFeatureList::addFeature(const char *name, const BOOL available, const S32 level) { if (mFeatures.count(name)) { llwarns << "LLFeatureList::Attempting to add preexisting feature " << name << llendl; } LLFeatureInfo fi(name, available, level); mFeatures[name] = fi; } BOOL LLFeatureList::isFeatureAvailable(const char *name) { if (mFeatures.count(name)) { return mFeatures[name].mAvailable; } llwarns << "Feature " << name << " not on feature list!" << llendl; return FALSE; } S32 LLFeatureList::getRecommendedLevel(const char *name) { if (mFeatures.count(name)) { return mFeatures[name].mRecommendedLevel; } llwarns << "Feature " << name << " not on feature list!" << llendl; return -1; } BOOL LLFeatureList::maskList(LLFeatureList &mask) { //llinfos << "Masking with " << mask.mName << llendl; // // Lookup the specified feature mask, and overlay it on top of the // current feature mask. // LLFeatureInfo mask_fi; feature_map_t::iterator feature_it; for (feature_it = mask.mFeatures.begin(); feature_it != mask.mFeatures.end(); ++feature_it) { mask_fi = feature_it->second; // // Look for the corresponding feature // if (!mFeatures.count(mask_fi.mName)) { llwarns << "Feature " << mask_fi.mName << " in mask not in top level!" << llendl; continue; } LLFeatureInfo &cur_fi = mFeatures[mask_fi.mName]; if (mask_fi.mAvailable && !cur_fi.mAvailable) { llwarns << "Mask attempting to reenabling disabled feature, ignoring " << cur_fi.mName << llendl; continue; } cur_fi.mAvailable = mask_fi.mAvailable; cur_fi.mRecommendedLevel = llmin(cur_fi.mRecommendedLevel, mask_fi.mRecommendedLevel); #ifndef LL_RELEASE_FOR_DOWNLOAD llinfos << "Feature mask " << mask.mName << " Feature " << mask_fi.mName << " Mask: " << mask_fi.mRecommendedLevel << " Now: " << cur_fi.mRecommendedLevel << llendl; #endif } #if 0 && !LL_RELEASE_FOR_DOWNLOAD llinfos << "After appling mask " << mask.mName << llendl; dump(); #endif return TRUE; } void LLFeatureList::dump() { llinfos << "Feature list: " << mName << llendl; llinfos << "--------------" << llendl; LLFeatureInfo fi; feature_map_t::iterator feature_it; for (feature_it = mFeatures.begin(); feature_it != mFeatures.end(); ++feature_it) { fi = feature_it->second; llinfos << fi.mName << "\t\t" << fi.mAvailable << ":" << fi.mRecommendedLevel << llendl; } llinfos << llendl; } LLFeatureList *LLFeatureManager::findMask(const char *name) { if (mMaskList.count(name)) { return mMaskList[name]; } return NULL; } BOOL LLFeatureManager::maskFeatures(const char *name) { LLFeatureList *maskp = findMask(name); if (!maskp) { llwarns << "Unknown feature mask " << name << llendl; return FALSE; } llinfos << "Applying Feature Mask: " << name << llendl; return maskList(*maskp); } BOOL LLFeatureManager::loadFeatureTables() { std::string data_path = gDirUtilp->getAppRODataDir(); data_path += gDirUtilp->getDirDelimiter(); data_path += FEATURE_TABLE_FILENAME; char name[MAX_STRING+1]; llifstream file; U32 version; file.open(data_path.c_str()); if (!file) { llwarns << "Unable to open feature table!" << llendl; return FALSE; } // Check file version file >> name; file >> version; if (strcmp(name, "version")) { llwarns << data_path << " does not appear to be a valid feature table!" << llendl; return FALSE; } mTableVersion = version; LLFeatureList *flp = NULL; while (!file.eof()) { char buffer[MAX_STRING]; name[0] = 0; file >> name; if (strlen(name) >= 2 && name[0] == '/' && name[1] == '/') { // This is a comment. file.getline(buffer, MAX_STRING); continue; } if (strlen(name) == 0) { // This is a blank line file.getline(buffer, MAX_STRING); continue; } if (!strcmp(name, "list")) { if (flp) { //flp->dump(); } // It's a new mask, create it. file >> name; if (mMaskList.count(name)) { llerrs << "Overriding mask " << name << ", this is invalid!" << llendl; } if (!flp) { // // The first one is always the default // flp = this; } else { flp = new LLFeatureList(name); mMaskList[name] = flp; } } else { if (!flp) { llerrs << "Specified parameter before keyword!" << llendl; } S32 available, recommended; file >> available >> recommended; flp->addFeature(name, available, recommended); } } file.close(); //flp->dump(); return TRUE; } S32 LLFeatureManager::getGPUClass() { return mGPUClass; } S32 LLFeatureManager::loadGPUClass() { std::string data_path = gDirUtilp->getAppRODataDir(); data_path += gDirUtilp->getDirDelimiter(); data_path += GPU_TABLE_FILENAME; llifstream file; file.open(data_path.c_str()); if (!file) { llwarns << "Unable to open GPU table: " << data_path << "!" << llendl; return 0; } std::string renderer = gGLManager.getRawGLString(); for (std::string::iterator i = renderer.begin(); i != renderer.end(); ++i) { *i = tolower(*i); } while (!file.eof()) { char buffer[MAX_STRING]; buffer[0] = 0; file.getline(buffer, MAX_STRING); if (strlen(buffer) >= 2 && buffer[0] == '/' && buffer[1] == '/') { // This is a comment. continue; } if (strlen(buffer) == 0) { // This is a blank line continue; } char* cls, *label, *expr; label = strtok(buffer, "\t"); expr = strtok(NULL, "\t"); cls = strtok(NULL, "\t"); if (label == NULL || expr == NULL || cls == NULL) { continue; } for (U32 i = 0; i < strlen(expr); i++) { expr[i] = tolower(expr[i]); } char* ex = strtok(expr, ".*"); char* rnd = (char*) renderer.c_str(); while (ex != NULL && rnd != NULL) { rnd = strstr(rnd, ex); ex = strtok(NULL, ".*"); } if (rnd != NULL) { file.close(); llinfos << "GPU is " << label << llendl; return (S32) strtol(cls, NULL, 10); } } file.close(); //flp->dump(); llwarns << "Couldn't match GPU to a class: " << gGLManager.getRawGLString() << llendl; return 0; } void LLFeatureManager::cleanupFeatureTables() { std::for_each(mMaskList.begin(), mMaskList.end(), DeletePairedPointer()); mMaskList.clear(); } void LLFeatureManager::initCPUFeatureMasks() { if (gSysMemory.getPhysicalMemory() <= 256*1024*1024) { maskFeatures("RAM256MB"); } else if (gSysMemory.getPhysicalMemory() <= 512*1024*1024) { //maskFeatures("RAM512MB"); } if (gSysCPU.getMhz() < 1100) { maskFeatures("CPUSlow"); } if (isSafe()) { maskFeatures("safe"); } } void LLFeatureManager::initGraphicsFeatureMasks() { mGPUClass = loadGPUClass(); if (mGPUClass >= 0 && mGPUClass < 4) { const char* class_table[] = { "Class0", "Class1", "Class2", "Class3" }; llinfos << "Setting GPU Class to " << class_table[mGPUClass] << llendl; maskFeatures(class_table[mGPUClass]); } if (!gGLManager.mHasFragmentShader) { maskFeatures("NoPixelShaders"); } if (!gGLManager.mHasVertexShader) { maskFeatures("NoVertexShaders"); } if (gGLManager.mIsNVIDIA) { maskFeatures("NVIDIA"); } if (gGLManager.mIsGF2or4MX) { maskFeatures("GeForce2"); } if (gGLManager.mIsATI) { maskFeatures("ATI"); } if (gGLManager.mIsRadeon8500) { maskFeatures("Radeon8500"); } if (gGLManager.mIsRadeon9700) { maskFeatures("Radeon9700"); } if (gGLManager.mIsGFFX) { maskFeatures("GeForceFX"); } if (gGLManager.mIsIntel) { maskFeatures("Brookdale"); } if (gGLManager.mIsMobilityRadeon9000) { maskFeatures("MobilityRadeon9000"); } if (isSafe()) { maskFeatures("safe"); } } extern LLOSInfo gSysOS; BOOL bad_hardware_dialog(const LLString &info_str, const LLString &url) { if (!gSavedSettings.getWarning("AboutBadPCI")) { return FALSE; } // XUI:translate std::string msg = llformat( "[SECOND_LIFE] has detected that there may be a problem with.\n" "hardware or drivers on your computer. Often resolving these\n" "issues can result in enhanced stability and performance.\n" " \n" "%s\n" " \n" "Would you like to view a web page with more detailed\n" "information on this problem?\n", info_str.c_str()); // Warn them that runnin without DirectX 9 will // not allow us to tell them about driver issues S32 button = OSMessageBox(msg.c_str(), "Warning", OSMB_YESNO); if (OSBTN_YES== button) { llinfos << "User quitting after detecting bad drivers" << llendl; spawn_web_browser(url.c_str()); return TRUE; } else { // Don't warn about bad PCI stuff again, they've clicked past it. gSavedSettings.setWarning("AboutBadPCI", FALSE); } return FALSE; } BOOL LLFeatureManager::initPCIFeatureMasks() { #if LL_WINDOWS BOOL exit_after_bad = FALSE; BOOL is_2000 = FALSE; BOOL is_xp = FALSE; if (gSysOS.mMajorVer != 5) { // Unknown windows version number, exit!" llwarns << "Unknown Windows major version " << gSysOS.mMajorVer << ", aborting detection!" << llendl; return FALSE; } if (gSysOS.mMinorVer == 0) { is_2000 = TRUE; } else if (gSysOS.mMinorVer == 1) { is_xp = TRUE; } else { llwarns << "Unknown Windows minor version " << gSysOS.mMinorVer << ", aborting detection!" << llendl; return FALSE; } // This only works on Win32, as it relies on DX9 hardware detection // The PCI masks are actually the inverse of the normal masks // We actually look through the masks,and see if any hardware matches it. // This is because the masks encode logic about // Check for the broken AMD AGP controllers (751, 761, 762) // Horrible cruddy fixed lookup table. // Figure out what OS we're on, the version numbers are different. Sigh... LLDXDriverFile *dfilep = NULL; LLDXDevice *devp = NULL; // AMD(1022) AGP controllers // 7007 AMD-751 AGP Controller // 700F AMD-761 AGP Controller // 700D AMD-762 AGP Controller devp = gDXHardware.findDevice("VEN_1022", "DEV_7007|DEV_700F|DEV_700D"); if (devp) { // We're just pretty much screwed here, there are big problems with this hardware // We've got trouble with the most recent nVidia drivers. Check for this and warn. // Note: Need to detect that we're running with older nVidia hardware, probably exit_after_bad |= bad_hardware_dialog("AMD AGP Controller", AMD_AGP_URL); } // VIA(1106) AGP Controllers // These need upgrading on both Win2K and WinXP // // 8305 VT8363/8365 CPU to AGP - Apollo KT133/KM133 // 8598 VT82C598MVP/694X CPU to AGP - Apollo MVP3/Pro133A // 8605 VT8605 CPU to AGP - Apollo PM133 // B091 VT8633 CPU to AGP - Apollo Pro 266 // B099 VT8366/A/T CPU to AGP - Apollo KT266/A/333 // B168 VT8235 CPU to AGP (AGP 2.0/3.0) - ProSavageDDR P4X333 chipset // B188 VT8237 CPU to AGP (AGP 2.0/3.0) - K8T800 // B198 VT8237 CPU to AGP (AGP 2.0/3.0) - ProSavageDDR P4X600 chipset devp = gDXHardware.findDevice("VEN_1106", "DEV_8305|DEV_8598|DEV_8605|DEV_B091|" "DEV_B099|DEV_B168|DEV_B188|DEV_B198"); if (devp) { BOOL out_of_date = FALSE; // Wanted driver: VIAAGP1.SYS // Version Format: M.mm.0000.vvvv // M.mm - Major/minor OS version (5.0 for Win2000, 5.1 for WinXP) // vvvv - driver version number // // Notes: // 3442 is most recent as of 2/25/04, probably want at least 3430 (seems to be a common version) // These are DELIBERATE assignments inside if statements, blech. if (dfilep = devp->findDriver("pci.sys")) { // Old driver: pci.sys // Version: 5.01.2600.xxxx // // Notes: // Default WinXP driver for B168, B198? // Old driver: pci.sys // Version: 5.01.2195.xxxx // // Notes: // Default Win2K driver for 8305? llwarns << "Detected pci.sys" << llendl; write_debug("Old driver (pci.sys) for VIA detected!"); out_of_date = TRUE; } else if (dfilep = devp->findDriver("VIAAGP.SYS")) { // Old driver: VIAAGP.SYS // Version: 5.01.2600.xxxx // // Notes: // Default WinXP driver for B09x? llwarns << "Detected VIAAGP.SYS" << llendl; write_debug("Old driver (VIAAGP.SYS) for VIA detected!"); out_of_date = TRUE; } else if (dfilep = devp->findDriver("VIAAGP1.SYS")) { if (dfilep->mVersion.getField(3) < 3430) { // They're using a pretty old version of the VIA AGP drivers // Maybe they want to upgrade? llwarns << "Detected VIAAGP1.SYS" << llendl; write_debug("Old driver (VIAAGP1.SYS) for VIA detected!"); out_of_date = TRUE; } } if (out_of_date) { exit_after_bad |= bad_hardware_dialog("Out of date VIA AGP chipset driver", VIA_URL); } } // Intel(8086) AGP controllers (Win2K) // These particular controllers only may need drivers on Win2K // // 1A31 82845[MP|MZ] Processor to AGP Controller // 2532 82850/860 Processor to AGP Controller if (is_2000) { devp = gDXHardware.findDevice("VEN_8086", "DEV_1A31"); if (devp) { if (dfilep = devp->findDriver("pci.sys")) { // Old driver: pci.sys // Version 5.01.21[9|6]5.xxxx // // Notes: // Default driver for Win2K? Not sure what the "correct" driver is - // maybe some variant of AGP440.SYS? llwarns << "Detected pci.sys" << llendl; write_debug("Old driver (pci.sys) for Intel 82845/850 on Win2K detected!"); exit_after_bad |= bad_hardware_dialog("Out of date Intel chipset driver", INTEL_CHIPSET_URL); } } } /* Removed 4/3/2006 by JC After talking with Doug, we don't know what the proper driver and/or version number should be for Intel 865. Regardless, this code would _always_ complain if you had that chipset. // Intel(8086) AGP controllers (All) // These particular controllers may need drivers on both Win2K and WinXP // // 2561 82845G/GL/GE/PE/GV Processor to AGP Controller // 2571 82865G/PE/P/GV/28248P Processor to AGP Controller devp = gDXHardware.findDevice("VEN_8086", "DEV_2571"); if (devp) { // Wanted driver: AGP440.SYS(?) // // Notes: // Not sure, need to verify with an actual 82865/75 (Dell 8300?) // Old driver: pci.sys // Version 5.01.21[9|6]5.xxxx // // Notes: // Default driver for Win2K? Not sure what the "correct" driver is - // maybe some variant of AGP440.SYS? exit_after_bad |= bad_hardware_dialog("Out of date Intel chipset driver", INTEL_CHIPSET_URL); } */ // SiS(1039) AGP controllers (All) // These particular controllers may need drivers on both Win2K and WinXP // // 0001 SiS 530 // 0002 SiS SG86C202(???) // 0003 SiS 648FX devp = gDXHardware.findDevice("VEN_1039", "DEV_0001|DEV_0002|DEV_0003"); if (devp) { BOOL out_of_date = FALSE; // Wanted driver: SISAGPX.SYS // // Notes: // Not sure, need to verify with an actual 82865/75 (Dell 8300?) // Old driver: pci.sys // Version 5.01.21[9|6]5.xxxx // // Notes: // Default driver for Win2K? Not sure what the "correct" driver is - // maybe some variant of AGP440.SYS? if (dfilep = devp->findDriver("pci.sys")) { // Old driver: pci.sys // Version 5.01.21[9|6]5.xxxx // llwarns << "Detected pci.sys" << llendl; write_debug("Old driver (pci.sys) for SiS detected!"); out_of_date = TRUE; } if (dfilep = devp->findDriver("sisagp.sys")) { // Old driver: pci.sys // Version 5.01.21[9|6]5.xxxx // llwarns << "Detected sisagp.sys" << llendl; write_debug("Old driver (sisagp.sys) for SiS detected!"); out_of_date = TRUE; } if (dfilep = devp->findDriver("sisagpx.sys")) { // Old driver: pci.sys // Version 7.02.0000.xxxx // // Notes: // Default driver for Win2K? Not sure what the "correct" driver is - // maybe some variant of AGP440.SYS? if (dfilep->mVersion.getField(3) < 1160) { out_of_date = TRUE; llwarns << "Detected sisagpx.sys" << llendl; write_debug("Old driver (sisagpx.sys) for SiS detected!"); } } if (out_of_date) { exit_after_bad |= bad_hardware_dialog("Out of date SiS chipset driver", SIS_CHIPSET_URL); } } return exit_after_bad; #else return TRUE; #endif } void LLFeatureManager::applyRecommendedFeatures() { // see featuretable.txt llinfos << "Applying Recommended Features" << llendl; #ifndef LL_RELEASE_FOR_DOWNLOAD dump(); #endif // Enabling AGP if (getRecommendedLevel("RenderAGP")) { gSavedSettings.setBOOL("RenderUseAGP", TRUE); } else { gSavedSettings.setBOOL("RenderUseAGP", FALSE); } // Anisotropic rendering BOOL aniso = getRecommendedLevel("RenderAniso"); LLImageGL::sGlobalUseAnisotropic = aniso; gSavedSettings.setBOOL("RenderAnisotropic", LLImageGL::sGlobalUseAnisotropic); // Render Avatar Mode BOOL avatar_vp = getRecommendedLevel("RenderAvatarVP"); S32 avatar_mode = getRecommendedLevel("RenderAvatarMode"); if (avatar_vp == FALSE) avatar_mode = 0; gSavedSettings.setBOOL("RenderAvatarVP", avatar_vp); gSavedSettings.setS32("RenderAvatarMode", avatar_mode); // Render Distance S32 far_clip = getRecommendedLevel("RenderDistance"); gSavedSettings.setF32("RenderFarClip", (F32)far_clip); // Lighting S32 lighting = getRecommendedLevel("RenderLighting"); gSavedSettings.setS32("RenderLightingDetail", lighting); // ObjectBump BOOL bump = getRecommendedLevel("RenderObjectBump"); gSavedSettings.setBOOL("RenderObjectBump", bump); // Particle Count S32 max_parts = getRecommendedLevel("RenderParticleCount"); gSavedSettings.setS32("RenderMaxPartCount", max_parts); LLViewerPartSim::setMaxPartCount(max_parts); // RippleWater BOOL ripple = getRecommendedLevel("RenderRippleWater"); gSavedSettings.setBOOL("RenderRippleWater", ripple); // Vertex Shaders S32 shaders = getRecommendedLevel("VertexShaderEnable"); gSavedSettings.setBOOL("VertexShaderEnable", shaders); // Terrain S32 terrain = getRecommendedLevel("RenderTerrainDetail"); gSavedSettings.setS32("RenderTerrainDetail", terrain); LLDrawPoolTerrain::sDetailMode = terrain; // Set the amount of VRAM we have available if (isSafe()) { gSavedSettings.setS32("GraphicsCardMemorySetting", 1); // 32 MB in 'safe' mode } else { S32 idx = gSavedSettings.getS32("GraphicsCardMemorySetting"); // -1 indicates use default (max), don't change if (idx != -1) { idx = LLViewerImageList::getMaxVideoRamSetting(-2); // get max recommended setting gSavedSettings.setS32("GraphicsCardMemorySetting", idx); } } }