Compare commits

..
5 Commits
Author SHA1 Message Date
RallyBlock 861f7d320e Describe frame-rate shadow scaling 2026-10-25 07:06:28 -04:00
RallyBlock 2c7cb006db Describe how to build the DLL 2026-10-25 07:03:46 -04:00
RallyBlock 29ff15fcec Point the readme at the source 2026-10-25 07:01:17 -04:00
RallyBlock 4301fcf3ca Add ShaderFit source 2026-10-25 07:01:16 -04:00
RallyBlock 6a3ae4b195 Add ShaderFit build file 2026-10-25 07:01:15 -04:00
3 changed files with 778 additions and 4 deletions

No files matched your search

+59
View File
@@ -0,0 +1,59 @@
cmake_minimum_required(VERSION 3.0)
project(ShaderFit VERSION 1.0.0)
set(CMAKE_CXX_FLAGS "-static -static-libgcc -static-libstdc++ -Wall -Werror")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS} -O2 -DTSFUNCS_DEBUG=false")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS} -g -Og")
add_compile_definitions(PROJECT_NAME="${CMAKE_PROJECT_NAME}")
add_compile_definitions(PROJECT_EXPORT=${CMAKE_PROJECT_NAME})
add_compile_definitions(PROJECT_VERSION="${CMAKE_PROJECT_VERSION}")
set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "" FORCE)
if (NOT CMAKE_BUILD_TYPE MATCHES Release AND NOT CMAKE_BUILD_TYPE MATCHES Debug)
set(CMAKE_BUILD_TYPE Release)
endif ()
find_package(TSFUNCS REQUIRED)
include_directories("${TSFUNCS_INCLUDE_DIRS};${CMAKE_SOURCE_DIR}/include")
link_directories(${TSFUNCS_LIBRARY_DIRS})
set(CMAKE_FIND_LIBRARY_PREFIXES "" "lib")
set(CMAKE_FIND_LIBRARY_SUFFIXES "" ".lib" ".a")
find_library(MINHOOK_LIB_RELEASE_LOC
NAMES minhook libminhook minhook.x32 minhook.x64
PATHS ${TSFUNCS_LIBRARY_DIRS}
REQUIRED
)
find_library(MINHOOK_LIB_DEBUG_LOC
NAMES minhookd libminhookd minhook.x32d minhook.x64d
PATHS ${TSFUNCS_LIBRARY_DIRS}
REQUIRED
)
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
set(TSFUNCS_LIB "tsfuncsd")
set(MINHOOK_LIB_LOC ${MINHOOK_LIB_DEBUG_LOC})
else ()
set(TSFUNCS_LIB "tsfuncs")
set(MINHOOK_LIB_LOC ${MINHOOK_LIB_RELEASE_LOC})
endif ()
add_library(minhook STATIC IMPORTED)
set_target_properties(minhook PROPERTIES IMPORTED_LOCATION "${MINHOOK_LIB_LOC}")
add_library(${CMAKE_PROJECT_NAME} SHARED src/main.cpp)
set_target_properties(${CMAKE_PROJECT_NAME} PROPERTIES PREFIX "")
target_link_libraries(${CMAKE_PROJECT_NAME}
${TSFUNCS_LIB}
minhook
psapi
opengl32
dxgi
)
+62 -4
View File
@@ -1,9 +1,67 @@
# ShaderFit
Client module for Blockland v21. It keeps the shadow map at a size this 32-bit game can allocate.
Client module for Blockland v21. While a world is drawing, it watches the frame rate and changes the shadow map to match. A low frame rate steps the map down. A stretch of high frame rate steps it back up. The map also stays inside the size this 32-bit process can allocate.
Place `ShaderFit.dll` in the game's `modules` folder, then quit and start Blockland from Steam. A DLL that is already loaded stays mapped until that restart.
Shader source is not modified.
The largest shadow map this build will use is 8192 with 4 cascades. 16384 is 2 GB and the game runs out of memory there, so that size is not used.
## Install
The DLL only changes rendering on this client.
Place `ShaderFit.dll` in the game's `modules` folder, then quit and start Blockland from Steam. A DLL that is already loaded stays mapped until that restart. The module only changes rendering on this client.
## What it follows
The shader quality setting chooses a shadow resolution and a cascade count. ShaderFit remembers that pair. Automatic changes are a temporary limit on top of it. Choosing a different quality replaces the remembered pair and clears the limit.
Each presented frame updates a smoothed frame rate. A frame longer than a quarter second is ignored, so a hitch does not look like a low rate. Scaling waits one second after shadows start, and then waits 2.5 seconds after every change.
Shadows have to be in use. Scaling pauses when shaders are off, when the shader name is `minimum`, or when the shadow size or cascade count is 0. It starts again when shadows are back.
## Stepping down and up
Under 50 fps for one second, the resolution is cut in half. The floor is 1024. At 1024, the next step removes one cascade. It will not go below one cascade.
Over 70 fps for eight seconds, one cascade is restored, up to the count from the quality setting. After the cascades are back, the resolution doubles, up to the cap for this adapter.
The frame rate gained by a step down is not treated as spare room. The map is raised only when the rate would still be about 65 fps after giving that gain back. If a raise drops the rate under 50 within the following second, the step is undone and another raise is blocked for 45 seconds.
Each change rebuilds the shadow map and writes a console line with the frame rate, resolution, and cascade count.
## How large a map can be
The shadow map is one 16-bit depth array: width times height times cascades times 2 bytes. The cap is the largest power of two that fits all of these:
- under 2 GB
- under a quarter of dedicated video memory
- inside `GL_MAX_TEXTURE_SIZE`
- at least 1024
8192 with 4 cascades is 512 MB. 16384 with 4 cascades is exactly 2 GB. This process runs out of memory at that size, so it is not chosen. If a rebuild larger than 8192 still turns shaders off, ShaderFit turns them back on at 8192.
An unknown adapter is not used as a reason to shrink the quality setting. Once an OpenGL context exists, the cap is taken from the current renderer, dedicated memory, and the maximum texture size. Before that, a vendor guess is used: NVIDIA 8192, AMD 4096, and Intel from the amount of dedicated memory.
## Console variables
- `$ShaderFit::Vendor`
- `$ShaderFit::Renderer`
- `$ShaderFit::DedicatedMB`
- `$ShaderFit::ShadowCap`
- `$ShaderFit::FrameRate`
The first shadow rebuild also logs the vendor, renderer name, dedicated memory, and cap.
## Build
Blockland is a 32-bit program, so the DLL has to be built with a 32-bit MinGW toolchain (`i686-w64-mingw32`). Put that toolchain's `bin` directory first on `PATH` so CMake finds that `gcc` and `mingw32-make`.
`find_package(TSFUNCS)` needs a tsfuncs 1.0.1 package: `tsfuncs-config.cmake` next to `include/` and `lib/`. MinHook's cmake package lives under that package's `lib/minhook`. Pass the package directory as `CMAKE_PREFIX_PATH`.
From this repo:
```
cmake -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Release -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DCMAKE_PREFIX_PATH=C:/path/to/tsfuncs -S . -B build
cmake --build build
```
The result is `build/ShaderFit.dll`. CMake is set so the filename has no `lib` prefix. The link line is tsfuncs, MinHook, `psapi`, `opengl32`, and `dxgi`, with static libgcc and libstdc++.
Source is `src/main.cpp` and `CMakeLists.txt`. The current DLL is attached to the v1.0.0 release.
+657
View File
@@ -0,0 +1,657 @@
#include <windows.h>
#include <GL/gl.h>
#include <dxgi.h>
#include <cstdio>
#include <cstring>
#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<unsigned char *>(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() {}