#include #include #include #include #include #include "TSFuncs.hpp" // Identifies the graphics adapter and only shrinks the shadow map when that // adapter cannot back the size the quality setting asked for. Shader source // is left alone: patching it in memory disabled the NVIDIA shader compile. static char gDescription[160] = "unknown"; static const char *gVendor = "unknown"; static const char *gDescriptionPtr = gDescription; static int gDedicatedMB = 0; static int gShadowCap = 8192; static bool gHaveProfile = false; static bool gScriptsReady = false; static bool gPublished = false; static bool gLogged = false; static int *gShadowSize = 0; static int *gShadowCount = 0; static int gUserSize = 0; static int gUserCount = 0; static int gAppliedSize = -1; static int gAppliedCount = -1; static int gLimitSize = 0; static int gLimitCount = 0; static int gFrameRate = 0; static int gInRegen = 0; static bool gSwapHooked = false; static bool gScaleActive = false; static bool gWasPaused = false; static const double kFpsLow = 50.0; static const double kFpsHigh = 70.0; static const double kFpsUpMargin = 15.0; static const double kLowHold = 1.0; static const double kHighHold = 8.0; static const double kCooldown = 2.5; static const double kGainSettle = 2.5; static const double kStepUpWatch = 1.0; static const double kStepUpBan = 45.0; // The FPS gained by lowering shadows is not spare headroom. Raising the map // again would give that cost back and the counter would jump. static double gClock = 0.0; static double gFpsBeforeStep = 0.0; static double gStepGain = 0.0; static double gGainReadyAt = 0.0; static double gStepUpBanUntil = 0.0; static double gStepUpWatchAt = 0.0; static bool gHaveGain = false; static bool gWatchingStepUp = false; typedef BOOL (WINAPI *SwapBuffersFn)(HDC); static SwapBuffersFn wglSwapBuffersOriginal = 0; static const unsigned kVendorNvidia = 0x10DE; static const unsigned kVendorAmd = 0x1002; static const unsigned kVendorIntel = 0x8086; static int powerOfTwoFloor(int value) { int pot = 256; while (pot <= 32768 / 2 && pot * 2 <= value) pot *= 2; return pot; } // regenerateShadowMapFBOs allocates one GL_TEXTURE_2D_ARRAY of // GL_DEPTH_COMPONENT16: width * height * cascades * 2 bytes. 16384x4 is // exactly 2 GiB. This 32-bit process was given that size and the driver // returned out of memory, so the cap stays one byte under 2 GiB. 8192x4 // is 512 MB. Also stay under a quarter of dedicated memory and inside // GL_MAX_TEXTURE_SIZE. static int maxShadowSize(int dedicatedMB, int layers, int glMax) { int glCap = powerOfTwoFloor(glMax); if (glCap < 1024) glCap = 8192; if (layers < 1) layers = 4; long long budget = (1ll << 31) - 1; if (dedicatedMB > 0) { const long long quarter = (long long)dedicatedMB * 1024ll * 1024ll / 4ll; if (quarter < budget) budget = quarter; } int size = 1024; int best = 1024; while (size <= glCap && size <= 32768) { const long long bytes = (long long)size * (long long)size * (long long)layers * 2ll; if (bytes > budget) break; best = size; if (size > 32768 / 2) break; size *= 2; } return best; } static bool textHas(const char *text, const char *needle) { return text != 0 && needle != 0 && std::strstr(text, needle) != 0; } static void applyProfile(unsigned vendorId, const char *description, int dedicatedMB) { const char *vendor = "unknown"; int cap = 8192; if (dedicatedMB > 0) gDedicatedMB = dedicatedMB; if (description != 0 && description[0] != '\0' && std::strcmp(description, "unknown") != 0) std::snprintf(gDescription, sizeof gDescription, "%s", description); if (vendorId == kVendorNvidia || textHas(description, "NVIDIA")) { vendor = "NVIDIA"; cap = 8192; } else if (vendorId == kVendorAmd || textHas(description, "AMD") || textHas(description, "Radeon") || textHas(description, "ATI")) { vendor = "AMD"; cap = 4096; } else if (vendorId == kVendorIntel || textHas(description, "Intel")) { vendor = "Intel"; cap = gDedicatedMB >= 4096 ? 4096 : 1024; if (gDedicatedMB >= 1536 && gDedicatedMB < 4096) cap = 2048; } if (gDedicatedMB > 0 && gDedicatedMB < 512) cap = 1024; else if (gDedicatedMB > 0 && gDedicatedMB < 1536 && cap > 2048) cap = 2048; gVendor = vendor; gShadowCap = cap; gHaveProfile = vendorId != 0 || textHas(gDescription, "NVIDIA") || textHas(gDescription, "AMD") || textHas(gDescription, "Radeon") || textHas(gDescription, "Intel"); } static void detectAdapter() { IDXGIFactory *factory = 0; if (FAILED(CreateDXGIFactory(IID_IDXGIFactory, (void **)&factory)) || factory == 0) return; IDXGIAdapter *best = 0; DXGI_ADAPTER_DESC bestDesc; std::memset(&bestDesc, 0, sizeof bestDesc); SIZE_T bestMemory = 0; for (UINT index = 0; index < 8; ++index) { IDXGIAdapter *adapter = 0; if (FAILED(factory->EnumAdapters(index, &adapter)) || adapter == 0) break; DXGI_ADAPTER_DESC desc; std::memset(&desc, 0, sizeof desc); if (SUCCEEDED(adapter->GetDesc(&desc)) && desc.DedicatedVideoMemory >= bestMemory) { if (best != 0) best->Release(); best = adapter; bestDesc = desc; bestMemory = desc.DedicatedVideoMemory; } else adapter->Release(); } if (best != 0) { char name[128]; WideCharToMultiByte(CP_UTF8, 0, bestDesc.Description, -1, name, sizeof name, 0, 0); int dedicatedMB = (int)(bestDesc.DedicatedVideoMemory / (1024u * 1024u)); applyProfile(bestDesc.VendorId, name, dedicatedMB); best->Release(); } factory->Release(); } static void refineFromGl() { if (!gHaveProfile) detectAdapter(); if (wglGetCurrentContext() == 0) return; const char *vendor = (const char *)glGetString(GL_VENDOR); const char *renderer = (const char *)glGetString(GL_RENDERER); if (vendor != 0 || renderer != 0) { const char *who = renderer != 0 ? renderer : vendor; unsigned id = 0; if (textHas(vendor, "NVIDIA") || textHas(renderer, "NVIDIA")) id = kVendorNvidia; else if (textHas(vendor, "AMD") || textHas(vendor, "ATI") || textHas(renderer, "Radeon")) id = kVendorAmd; else if (textHas(vendor, "Intel") || textHas(renderer, "Intel")) id = kVendorIntel; if (id != 0) applyProfile(id, who, gDedicatedMB); } for (int i = 0; i < 8; ++i) { if (glGetError() == GL_NO_ERROR) break; } GLint maxTex = 0; glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTex); GLint dedicatedKB = 0; glGetIntegerv(0x9047, &dedicatedKB); if (glGetError() != GL_NO_ERROR) dedicatedKB = 0; if (dedicatedKB > 0) { int nvMB = dedicatedKB / 1024; if (nvMB > gDedicatedMB) gDedicatedMB = nvMB; } int layers = gUserCount > 0 ? gUserCount : 4; int cap = maxShadowSize(gDedicatedMB, layers, (int)maxTex); if (cap >= 1024) gShadowCap = cap; } static void publishVars() { if (gPublished || !gScriptsReady) return; tsf_AddVar("$ShaderFit::Vendor", &gVendor); tsf_AddVar("$ShaderFit::Renderer", &gDescriptionPtr); tsf_AddVar("$ShaderFit::DedicatedMB", &gDedicatedMB); tsf_AddVar("$ShaderFit::ShadowCap", &gShadowCap); tsf_AddVar("$ShaderFit::FrameRate", &gFrameRate); gPublished = true; } static bool varIsOff(const char *value) { return value != 0 && (value[0] == '0' || std::strcmp(value, "false") == 0); } // Off and Minimum both leave the shadow map at size 0. Minimum also keeps the // shader name "minimum" while Enabled stays on. static bool shaderModeUsesShadows() { if (gScriptsReady) { if (varIsOff(tsf_GetVar("$Shader::Enabled"))) return false; const char *name = tsf_GetVar("$Shader::ShaderName"); if (name != 0 && std::strcmp(name, "minimum") == 0) return false; } if (gShadowSize == 0 || gShadowCount == 0) return false; return *gShadowSize > 0 && *gShadowCount > 0; } static void resetStepMemory() { gFpsBeforeStep = 0.0; gStepGain = 0.0; gGainReadyAt = 0.0; gStepUpBanUntil = 0.0; gStepUpWatchAt = 0.0; gHaveGain = false; gWatchingStepUp = false; } static void pauseShadowScaling() { gLimitSize = 0; gLimitCount = 0; gScaleActive = false; resetStepMemory(); BlPrintf("%s: shadow scaling paused", PROJECT_NAME); } static void rememberUserChoice() { if (gShadowSize == 0 || gShadowCount == 0) return; int size = *gShadowSize; int count = *gShadowCount; if (size == gAppliedSize && count == gAppliedCount) return; gUserSize = size; gUserCount = count; gLimitSize = 0; gLimitCount = 0; if (size <= 0) { gLimitSize = 0; gLimitCount = 0; gAppliedSize = size; gAppliedCount = count; } } static void applyShadowLevel() { refineFromGl(); publishVars(); if (!gLogged) { BlPrintf("%s: %s, %s, %d MB, shadow cap %d", PROJECT_NAME, gVendor, gDescription, gDedicatedMB, gShadowCap); gLogged = true; } if (gShadowSize == 0 || gShadowCount == 0) return; rememberUserChoice(); if (gUserSize <= 0) return; int wantSize = gUserSize; int wantCount = gUserCount > 0 ? gUserCount : *gShadowCount; if (gLimitSize > 0) wantSize = gLimitSize; if (gLimitCount > 0) wantCount = gLimitCount; if (wantSize > gShadowCap && wantSize > gUserSize) wantSize = gShadowCap; if (wantCount < 1) wantCount = 1; if (*gShadowSize != wantSize) *gShadowSize = wantSize; if (*gShadowCount != wantCount) *gShadowCount = wantCount; gAppliedSize = *gShadowSize; gAppliedCount = *gShadowCount; } static bool stepShadowsDown() { int size = gLimitSize > 0 ? gLimitSize : gUserSize; int count = gLimitCount > 0 ? gLimitCount : gUserCount; if (size > 1024) { int next = size / 2; if (next < 1024) next = 1024; gLimitSize = next; gLimitCount = count; return true; } if (count > 1) { gLimitSize = size; gLimitCount = count - 1; return true; } return false; } static bool stepShadowsUp() { int size = gLimitSize > 0 ? gLimitSize : gUserSize; int count = gLimitCount > 0 ? gLimitCount : gUserCount; if (count < gUserCount) count += 1; else if (size < gShadowCap) { int next = size * 2; if (next > gShadowCap) next = gShadowCap; if (next <= size) return false; size = next; } else return false; if (size == gUserSize && count == gUserCount) { gLimitSize = 0; gLimitCount = 0; } else { gLimitSize = size; gLimitCount = count; } return true; } static void rebuildShadows(); static void regenerateFitted(); static void sampleFrameRate() { static LARGE_INTEGER freq; static LARGE_INTEGER last; static bool haveLast = false; static double avg = 60.0; static double warm = 0.0; static double lowHold = 0.0; static double highHold = 0.0; static double cooldown = 0.0; if (!shaderModeUsesShadows()) { if (gScaleActive) { pauseShadowScaling(); gWasPaused = true; } haveLast = false; warm = 0.0; lowHold = 0.0; highHold = 0.0; cooldown = 0.0; avg = 60.0; return; } if (!gScaleActive) { gScaleActive = true; haveLast = false; warm = 0.0; lowHold = 0.0; highHold = 0.0; cooldown = 0.0; avg = 60.0; if (gWasPaused) { gWasPaused = false; BlPrintf("%s: shadow scaling resumed", PROJECT_NAME); } } if (freq.QuadPart == 0) QueryPerformanceFrequency(&freq); LARGE_INTEGER now; QueryPerformanceCounter(&now); if (!haveLast) { last = now; haveLast = true; return; } double dt = (double)(now.QuadPart - last.QuadPart) / (double)freq.QuadPart; last = now; if (dt <= 0.0 || dt > 0.25) return; warm += dt; gClock += dt; if (cooldown > 0.0) cooldown -= dt; avg += ((1.0 / dt) - avg) * 0.1; gFrameRate = (int)(avg + 0.5); if (gGainReadyAt > 0.0 && gClock >= gGainReadyAt) { gStepGain = avg - gFpsBeforeStep; if (gStepGain < 0.0) gStepGain = 0.0; gHaveGain = true; gGainReadyAt = 0.0; } if (warm < 1.0 || cooldown > 0.0 || gUserSize <= 0 || gInRegen != 0) return; if (gWatchingStepUp && gClock >= gStepUpWatchAt) { if (avg < kFpsLow) { gWatchingStepUp = false; gStepUpBanUntil = gClock + kStepUpBan; gStepGain = gFpsBeforeStep - avg; if (gStepGain < kFpsUpMargin) gStepGain = kFpsUpMargin; gHaveGain = true; gGainReadyAt = 0.0; if (stepShadowsDown()) { BlPrintf("%s: keeping this shadow level, raising it fell to %d fps", PROJECT_NAME, gFrameRate); lowHold = 0.0; highHold = 0.0; cooldown = kCooldown; rebuildShadows(); } return; } if (gClock >= gStepUpWatchAt + 3.0) { gWatchingStepUp = false; gHaveGain = true; gStepGain = 0.0; } } if (avg < kFpsLow) { lowHold += dt; highHold = 0.0; } else if (avg > kFpsHigh) { highHold += dt; lowHold = 0.0; } else { lowHold = 0.0; highHold = 0.0; } bool changed = false; bool steppedDown = false; if (lowHold >= kLowHold) { changed = stepShadowsDown(); steppedDown = changed; } else if (highHold >= kHighHold && gGainReadyAt == 0.0 && gClock >= gStepUpBanUntil && (!gHaveGain || (avg - gStepGain) >= (kFpsLow + kFpsUpMargin))) { changed = stepShadowsUp(); } if (!changed) return; if (steppedDown) { gFpsBeforeStep = avg; gHaveGain = false; gGainReadyAt = gClock + kGainSettle; gWatchingStepUp = false; } else { gFpsBeforeStep = avg; gWatchingStepUp = true; gStepUpWatchAt = gClock + kStepUpWatch; gHaveGain = false; gGainReadyAt = 0.0; } lowHold = 0.0; highHold = 0.0; cooldown = kCooldown; rebuildShadows(); } static BOOL WINAPI wglSwapBuffersHook(HDC dc) { BOOL ok = wglSwapBuffersOriginal(dc); sampleFrameRate(); return ok; } static void installSwapHook() { if (gSwapHooked) return; HMODULE gl = GetModuleHandleA("opengl32.dll"); void *target = gl == 0 ? 0 : (void *)GetProcAddress(gl, "wglSwapBuffers"); if (target == 0) return; if (MH_CreateHook(target, (void *)wglSwapBuffersHook, (void **)&wglSwapBuffersOriginal) != MH_OK) return; if (MH_EnableHook(target) != MH_OK) return; gSwapHooked = true; } BlFunctionDef(void, , regenerateShadowMapFBOs); BlFunctionHookDef(regenerateShadowMapFBOs); static unsigned char *shaderEnabledFlag() { unsigned char *base = reinterpret_cast(GetModuleHandle(NULL)); return base + (0x7C2142 - 0x400000); } static void regenerateFitted() { unsigned char *enabled = shaderEnabledFlag(); unsigned char wasEnabled = *enabled; int tried = gShadowSize != 0 ? *gShadowSize : 0; regenerateShadowMapFBOsOriginal(); if (wasEnabled != 0 && *enabled == 0 && tried > 8192 && gShadowSize != 0) { BlPrintf("%s: %d did not fit, keeping 8192", PROJECT_NAME, tried); gShadowCap = 8192; gLimitSize = 8192; *gShadowSize = 8192; gAppliedSize = 8192; *enabled = 1; regenerateShadowMapFBOsOriginal(); } } void regenerateShadowMapFBOsHook() { if (gInRegen != 0) { regenerateShadowMapFBOsOriginal(); return; } gInRegen += 1; installSwapHook(); applyShadowLevel(); regenerateFitted(); gInRegen -= 1; } static void rebuildShadows() { if (regenerateShadowMapFBOsOriginal == 0 || gInRegen != 0) return; gInRegen += 1; applyShadowLevel(); int size = gShadowSize != 0 ? *gShadowSize : 0; int count = gShadowCount != 0 ? *gShadowCount : 0; BlPrintf("%s: %d fps, shadows %dx%d", PROJECT_NAME, gFrameRate, size, count); regenerateFitted(); gInRegen -= 1; } bool init() { if (!tsh_InitInternal()) return true; gScriptsReady = tsf_InitInternal(); ADDR shadowFn = tsh_ScanFunctionHex((char *)"83 EC 08 56 8B 35 ? ? ? ? FF 15"); if (shadowFn != 0) { gShadowSize = *(int **)(shadowFn + 6); gShadowCount = gShadowSize - 1; regenerateShadowMapFBOs = (tsh_regenerateShadowMapFBOsFnT)shadowFn; BlCreateHook(regenerateShadowMapFBOs); BlTestEnableHook(regenerateShadowMapFBOs); } BlPrintf("%s (v%s): loaded, waiting for the graphics device", PROJECT_NAME, PROJECT_VERSION); return true; } bool deinit() { if (regenerateShadowMapFBOs != 0) MH_DisableHook((void *)regenerateShadowMapFBOs); if (gSwapHooked) { void *target = (void *)GetProcAddress(GetModuleHandleA("opengl32.dll"), "wglSwapBuffers"); if (target != 0) MH_DisableHook(target); } return true; } bool __stdcall DllMain(HINSTANCE hinstance, unsigned int reason, void *reserved) { (void)hinstance; (void)reserved; switch (reason) { case DLL_PROCESS_ATTACH: return init(); case DLL_PROCESS_DETACH: return deinit(); default: return true; } } extern "C" void __declspec(dllexport) __cdecl PROJECT_EXPORT() {}