From 881c6847452828d88d3070e6ab4d65de2ffe86c0 Mon Sep 17 00:00:00 2001 From: Erimel Date: Thu, 30 Nov 2023 22:56:14 -0500 Subject: [PATCH] Fix like 1000 problems and bugs --- README.md | 38 ++++---- settings.ini | 6 +- src/main.cpp | 241 +++++++++++++++++++++++---------------------------- 3 files changed, 134 insertions(+), 151 deletions(-) diff --git a/README.md b/README.md index d8aac6e..e12711d 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,13 @@ [![Windows build testing](https://github.com/Louka3000/OpenVR-Dynamic-Resolution/actions/workflows/vs17.yml/badge.svg)](https://github.com/Louka3000/OpenVR-Dynamic-Resolution/actions/workflows/vs17.yml) -OpenVR app that dynamically adjusts the HMD resolution to the GPU frametime. +OpenVR app that dynamically adjusts the HMD's resolution to the GPU frametime, CPU frametime and VRAM. -This allows you to always have the maximum resolution your GPU can handle while hitting your target FPS (without reprojecting). Good for games in which GPU requirement varies a lot (e.g. VRChat). +This allows you to always have the maximum resolution your GPU can handle while hitting your target FPS (without reprojecting). Useful for games in which GPU requirement varies a lot (e.g. VRChat). -This is **not** the same as SteamVR's Auto Resolution, which behaviour's is quite bizarre, but seems to be using some benchmarking approach that doesn't work for the same as OVRDR. +This is **not** the same as SteamVR's Auto Resolution, which seems to be using some benchmarking approach that does not work for the same as OVRDR. -This won't work in games that require a restart for resolution changes, or that use their own dynamic resolution. **For a list of working and non-working games, check out [WorkingGames.md](WorkingGames.md)** +**Check out [WorkingGames.md](WorkingGames.md) for the list of working and non-working games.** ## Installation/Using It @@ -26,7 +26,7 @@ Settings are found in the `settings.ini` file. Do not rename that file. It shoul - `minimizeOnStart`: (0 = show, 1 = minimize, 2 = hide) Will automatically minimize or hide the window on launch. If set to 2 (hide), you won't be able to exit the program manually, but it will automatically exit with SteamVR. -- `initialRes`: The resolution the program sets when starting. +- `initialRes`: The resolution the program sets your HMD's resolution when starting. Also the resolution that is targeted in vramOnlyMode. - `minRes`: The minimum value the program will be allowed to set your HMD's resolution to. @@ -36,15 +36,15 @@ Settings are found in the `settings.ini` file. Do not rename that file. It shoul - `resChangeDelayMs`: The delay in milliseconds (1000ms = 1s) between each resolution change. Lowering it will make the resolution change more responsive, but will cause more stuttering from resolution changes. -- `minGpuTimeThreshold`: If GPU time in milliseconds is below this value, don't change resolution. Useful to avoid messing with resolution in the SteamVR void or during loading screens. +- `minCpuTimeThreshold`: Don't increase resolution when CPU time in milliseconds is below this value. Useful to avoid the resolution increasing in the SteamVR void or during loading screens. Also see resetOnThreshold. -- `resIncreaseMin`: How many static % to increase resolution when we have GPU or VRAM headroom. +- `resIncreaseMin`: How many static % to increase resolution when we have GPU or/and VRAM headroom. -- `resDecreaseMin`: How many static % to decrease resolution when GPU time or VRAM is too high. +- `resDecreaseMin`: How many static % to decrease resolution when GPU frametime or/and VRAM usage is too high. -- `resIncreaseScale`: How much to increase resolution depending on GPU frametime headroom available +- `resIncreaseScale`: How much to increase resolution depending on GPU frametime headroom. -- `resDecreaseScale`: How much to decrease resolution depending on GPU frametime difference needed. +- `resDecreaseScale`: How much to decrease resolution depending on GPU frametime excess. - `resIncreaseThreshold`: Percentage of the target frametime at which the program will stop increase resolution @@ -52,15 +52,21 @@ Settings are found in the `settings.ini` file. Do not rename that file. It shoul - `dataAverageSamples`: Number of samples to use for the average GPU time. Depends on dataPullDelayMs. -- `resetOnThreshold`: (0 = disabled, 1 = enabled) Enabling will reset the resolution to initialRes whenever minGpuTimeThreshold is met. Useful if you wanna go from playing a supported game to an unsuported games without having to reset your resolution/the program/SteamVR. +- `resetOnThreshold`: (0 = disabled, 1 = enabled) Enabling will reset the resolution to initialRes whenever minCpuTimeThreshold is met. Useful if you wanna go from playing a supported game to an unsuported games without having to reset your resolution/the program/SteamVR. - `alwaysReproject`: (0 = disabled, 1 = enabled) Enabling will double the target frametime, so if you're at a target FPS of 120, it'll target 60. Useful if you have a bad CPU but good GPU. -- `vramTarget`: Your target vram usage in percents. Once your vram usage exceeds this point, the resolution will stop increasing. -- `vramLimit`: Your maximum vram usage in percents. Once your vram usage exceeds this point, the resolution will start decreasing. + +- `vramTarget`: The target VRAM usage in percents. Once your VRAM usage exceeds this amount, the resolution will stop increasing. + +- `vramLimit`: The maximum VRAM usage in percents. Once your VRAM usage exceeds this amount, the resolution will start decreasing. + - `vramMonitorEnabled`: (0 = disabled, 1 = enabled) If enabled, vram specific features will be enabled, otherwise it is assumed that free vram is always available. -- `vramOnlyMode`: (0 = disabled, 1 = enabled) Only adjust resolution based off VRAM; ignore GPU and CPU frametimes. + +- `vramOnlyMode`: (0 = disabled, 1 = enabled) Only adjust resolution based off VRAM; ignore GPU and CPU frametimes. Will always stay at initialRes or lower (if VRAM limit is reached). + - `preferReprojection`: (0 = disabled, 1 = enabled) If enabled, the GPU target frametime will double as soon as the CPU frametime is over the target frametime; else, the CPU frametime needs to be 2 times greater than the target frametime for the GPU target frametime to double. -- `ignoreCpuTime`: (0 = disabled, 1 = enabled) Don't use CPU frametime to adjust resolution. + +- `ignoreCpuTime`: (0 = disabled, 1 = enabled) Don't use the CPU frametime to adjust resolution. ## Building from source @@ -74,7 +80,7 @@ cmake -B build cmake --build build --config Release ``` -You can then execute the newly built binary: +You can then execute the newly built binary by running: ``` .\build\Release\OpenVR-Dynamic-Resolution.exe diff --git a/settings.ini b/settings.ini index 9abfed6..3941644 100644 --- a/settings.ini +++ b/settings.ini @@ -4,11 +4,11 @@ minimizeOnStart=0 initialRes=100 [Resolution change] -minRes=80 +minRes=85 maxRes=500 dataPullDelayMs=200 -resChangeDelayMs=1600 -minGpuTimeThreshold=1.3 +resChangeDelayMs=1700 +minCpuTimeThreshold=1.0 resIncreaseMin=3 resDecreaseMin=9 resIncreaseScale=60 diff --git a/src/main.cpp b/src/main.cpp index 30eb39f..b4eae6b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -27,14 +27,14 @@ float minRes = 0.60; float maxRes = 5.0f; long dataPullDelayMs = 250; long resChangeDelayMs = 1400; -float minGpuTimeThreshold = 0.75f; +float minCpuTimeThreshold = 1.0f; float resIncreaseMin = 0.03f; float resDecreaseMin = 0.09f; float resIncreaseScale = 0.60f; float resDecreaseScale = 0.9; float resIncreaseThreshold = 0.75; float resDecreaseThreshold = 0.85f; -int dataAverageSamples = 3; +int dataAverageSamples = 16; int resetOnThreshold = 1; int alwaysReproject = 0; float vramTarget = 0.8; @@ -116,7 +116,7 @@ bool loadSettings() maxRes = std::stof(ini.GetValue("Resolution change", "maxRes", std::to_string(maxRes * 100.0f).c_str())) / 100.0f; dataPullDelayMs = std::stol(ini.GetValue("Resolution change", "dataPullDelayMs", std::to_string(dataPullDelayMs).c_str())); resChangeDelayMs = std::stol(ini.GetValue("Resolution change", "resChangeDelayMs", std::to_string(resChangeDelayMs).c_str())); - minGpuTimeThreshold = std::stof(ini.GetValue("Resolution change", "minGpuTimeThreshold", std::to_string(minGpuTimeThreshold).c_str())); + minCpuTimeThreshold = std::stof(ini.GetValue("Resolution change", "minCpuTimeThreshold", std::to_string(minCpuTimeThreshold).c_str())); resIncreaseMin = std::stof(ini.GetValue("Resolution change", "resIncreaseMin", std::to_string(resIncreaseMin * 100.0f).c_str())) / 100.0f; resDecreaseMin = std::stof(ini.GetValue("Resolution change", "resDecreaseMin", std::to_string(resDecreaseMin * 100.0f).c_str())) / 100.0f; resIncreaseScale = std::stof(ini.GetValue("Resolution change", "resIncreaseScale", std::to_string(resIncreaseScale * 100.0f).c_str())) / 100.0f; @@ -155,7 +155,7 @@ int main(int argc, char *argv[]) initscr(); // Initialize screen cbreak(); // Disable line-buffering (for input) noecho(); // Don't show what the user types - resize_term(18, 64); // Sets the initial (y, x) resolution + resize_term(20, 64); // Sets the initial (y, x) resolution // Check for errors EVRInitError init_error = VRInitError_None; @@ -259,7 +259,6 @@ int main(int argc, char *argv[]) long lastChangeTime = getCurrentTimeMillis(); std::list gpuTimes; std::list cpuTimes; - float lastRes = initialRes; // event loop while (true) @@ -268,9 +267,11 @@ int main(int argc, char *argv[]) long currentTime = getCurrentTimeMillis(); // Fetch resolution and target fps - float currentRes = vr::VRSettings()->GetFloat(vr::k_pch_SteamVR_Section, vr::k_pch_SteamVR_SupersampleScale_Float); + float newRes = vr::VRSettings()->GetFloat(vr::k_pch_SteamVR_Section, vr::k_pch_SteamVR_SupersampleScale_Float); + float lastRes = newRes; float targetFps = std::round(vr::VRSystem()->GetFloatTrackedDeviceProperty(0, Prop_DisplayFrequency_Float)); float targetFrametime = 1000 / targetFps; + float realTargetFrametime = targetFrametime; // Get current frame auto *frameTiming = new vr::Compositor_FrameTiming; @@ -281,19 +282,17 @@ int main(int argc, char *argv[]) float gpuTime = frameTiming->m_flTotalRenderGpuMs; // Calculate total CPU Frametime // https://github.com/Louka3000/OpenVR-Dynamic-Resolution/issues/18#issuecomment-1833105172 - float cpuTime = frameTiming->m_flCompositorRenderCpuMs // Compositor - + (frameTiming->m_flNewFrameReadyMs - frameTiming->m_flNewPosesReadyMs) // Application - + max(0, frameTiming->m_flWaitGetPosesCalledMs - frameTiming->m_flNewPosesReadyMs); // Late Start + float cpuTime = frameTiming->m_flCompositorRenderCpuMs // Compositor + + (frameTiming->m_flNewFrameReadyMs - frameTiming->m_flNewPosesReadyMs); // Application & Late Start // How many times the current frame repeated (>1 = reprojecting) - uint32_t frameRepeat = max(1, frameTiming->m_nNumFramePresents); + uint32_t frameShown = frameTiming->m_nNumFramePresents; // Reason reprojection is happening uint32_t reprojectionFlag = frameTiming->m_nReprojectionFlags; // Adjust the CPU time off GPU reprojection. - int gpuFrameRepeat = floor(gpuTime / targetFrametime) + 1; - if(frameRepeat > 1) - cpuTime *= gpuFrameRepeat; + float realCpuTime = cpuTime; + cpuTime *= min(frameShown, floor(gpuTime / targetFrametime) + 1); // Calculate average GPU frametime gpuTimes.push_front(gpuTime); @@ -313,48 +312,108 @@ int main(int argc, char *argv[]) averageCpuTime /= cpuTimes.size(); // Estimated current FPS - uint32_t currentFps = targetFps / frameRepeat; - if(averageCpuTime > targetFrametime) + uint32_t currentFps = targetFps / frameShown; + if (averageCpuTime > targetFrametime) currentFps /= fmod(averageCpuTime, targetFrametime) / targetFrametime + 1; - // Double the target frametime if the user wants to. - if (alwaysReproject) + // Double the target frametime if the user wants to, + // or if CPU Frametime is double the target frametime, + // or if preferReprojection is true and CPU Frametime is greated than targetFrametime. + if ((((averageCpuTime > targetFrametime && preferReprojection) || averageCpuTime / 2 > targetFrametime) && !ignoreCpuTime) || alwaysReproject) { - targetFps /= 2; targetFrametime *= 2; } + // Get VRAM usage + float vramUsage = getVramUsage(nvmlLibrary); + + // Resolution handling + if (currentTime - resChangeDelayMs > lastChangeTime && !VROverlay()->IsDashboardVisible()) + { + lastChangeTime = currentTime; + + // Adjust resolution + if ((averageCpuTime > minCpuTimeThreshold || vramOnlyMode)) + { + // Frametime + if (averageGpuTime < targetFrametime * resIncreaseThreshold && vramUsage < vramTarget && !vramOnlyMode) + { + // Increase resolution + newRes += ((((targetFrametime * resIncreaseThreshold) - averageGpuTime) / targetFrametime) * + resIncreaseScale) + + resIncreaseMin; + } + else if (averageGpuTime > targetFrametime * resDecreaseThreshold && !vramOnlyMode) + { + // Decrease resolution + newRes -= (((averageGpuTime - (targetFrametime * resDecreaseThreshold)) / targetFrametime) * + resDecreaseScale) + + resDecreaseMin; + } + + // VRAM + if (vramUsage > vramLimit) + { + // Force the resolution to decrease when the vram limit is reached + newRes -= resDecreaseMin; + } + else if (vramOnlyMode && newRes < initialRes && vramUsage < vramTarget) + { + // When in VRAM-only mode, make sure the res goes back up when possible. + newRes = min(initialRes, newRes + resIncreaseMin); + } + + // Clamp the new resolution + newRes = std::clamp(newRes, minRes, maxRes); + } + else if (resetOnThreshold && lastRes != initialRes && averageCpuTime < minCpuTimeThreshold && !vramOnlyMode) + { + // Reset to initialRes because CPU time fell below the threshold + newRes = initialRes; + } + + if (newRes != lastRes) + { + // Sets the new resolution + vr::VRSettings()->SetFloat(vr::k_pch_SteamVR_Section, vr::k_pch_SteamVR_SupersampleScale_Float, newRes); + } + } + // Clear console clear(); getmaxyx(stdscr, rows, cols); - // Write info to console + // Title attron(A_UNDERLINE); mvprintw(0, 0, "%s", fmt::format("OpenVR Dynamic Resolution {}", version).c_str()); attroff(A_UNDERLINE); + + // Settings status if (settingsLoaded) mvprintw(1, 0, "%s", fmt::format("settings.ini successfully loaded").c_str()); else mvprintw(1, 0, "%s", fmt::format("Error loading settings.ini").c_str()); + // HMD Hz + mvprintw(3, 0, "%s", fmt::format("HMD Hz: {} fps", std::to_string(int(targetFps))).c_str()); + + // Target frametime if (!vramOnlyMode) { - mvprintw(4, 0, "%s", fmt::format("Target FPS: {} fps", std::to_string(int(targetFps))).c_str()); - - std::string displayedTime = std::to_string(targetFrametime).substr(0, 4); - mvprintw(5, 0, "%s", fmt::format("Target frametime: {} ms", displayedTime).c_str()); + mvprintw(4, 0, "%s", fmt::format("HMD Hz target frametime: {} ms", std::to_string(realTargetFrametime).substr(0, 4)).c_str()); + mvprintw(5, 0, "%s", fmt::format("Adjusted target frametime: {} ms", std::to_string(targetFrametime).substr(0, 4)).c_str()); } else { - mvprintw(4, 0, "%s", "Target FPS: Disalbed"); - mvprintw(5, 0, "%s", "Target frametime: Disabled"); + mvprintw(4, 0, "%s", "Adjusted target frametime: Disabled"); + mvprintw(5, 0, "%s", "HMD Hz target frametime: Disabled"); } + + // VRAM target and limit if (vramMonitorEnabled) { - std::string vramTargetString = std::to_string(vramTarget * 100).substr(0, 4); - mvprintw(6, 0, "%s", fmt::format("VRAM target: {}%", vramTargetString).c_str()); - std::string vramLimitString = std::to_string(vramLimit * 100).substr(0, 4); - mvprintw(7, 0, "%s", fmt::format("VRAM limit: {}%", vramLimitString).c_str()); + mvprintw(6, 0, "%s", fmt::format("VRAM target: {}%", std::to_string(vramTarget * 100).substr(0, 4)).c_str()); + mvprintw(7, 0, "%s", fmt::format("VRAM limit: {}%", std::to_string(vramLimit * 100).substr(0, 4)).c_str()); } else { @@ -362,135 +421,53 @@ int main(int argc, char *argv[]) mvprintw(7, 0, "%s", fmt::format("VRAM limit: Disabled").c_str()); } + // FPS and frametimes mvprintw(9, 0, "%s", fmt::format("FPS: {} fps", std::to_string(currentFps)).c_str()); + mvprintw(10, 0, "%s", fmt::format("GPU frametime: {} ms", std::to_string(averageGpuTime).substr(0, 4)).c_str()); + mvprintw(11, 0, "%s", fmt::format("CPU frametime: {} ms", std::to_string(averageCpuTime).substr(0, 4)).c_str()); + mvprintw(12, 0, "%s", fmt::format("Raw CPU frametime: {} ms", std::to_string(realCpuTime).substr(0, 4)).c_str()); - std::string displayedAverageGpuTime = std::to_string(averageGpuTime).substr(0, 4); - mvprintw(10, 0, "%s", fmt::format("GPU frametime: {} ms", displayedAverageGpuTime).c_str()); - - std::string displayedAverageCpuTime = std::to_string(averageCpuTime).substr(0, 4); - if(frameRepeat > 1 && gpuFrameRepeat > 1) - displayedAverageCpuTime += fmt::format(" (x{})", gpuFrameRepeat).c_str(); - mvprintw(11, 0, "%s", fmt::format("CPU frametime: {} ms", displayedAverageCpuTime).c_str()); - + // VRAM usage if (vramMonitorEnabled) - { - std::string vramUsage = std::to_string(getVramUsage(nvmlLibrary) * 100).substr(0, 4); - mvprintw(12, 0, "%s", fmt::format("VRAM usage: {}%", vramUsage).c_str()); - } + mvprintw(13, 0, "%s", fmt::format("VRAM usage: {}%", std::to_string(vramUsage * 100).substr(0, 4)).c_str()); else - { - mvprintw(12, 0, "%s", fmt::format("VRAM usage: Disabled").c_str()); - } + mvprintw(13, 0, "%s", fmt::format("VRAM usage: Disabled").c_str()); - if (frameRepeat > 1) + // Reprojecting status + if (frameShown > 1) { - mvprintw(14, 0, "Reprojecting: Yes"); - - std::string reason = fmt::format("Other ({})", reprojectionFlag).c_str(); + std::string reason = fmt::format("Other [{}]", reprojectionFlag).c_str(); if (reprojectionFlag == 20) reason = "CPU"; else if (reprojectionFlag == 276) reason = "GPU"; - mvprintw(15, 0, "%s", fmt::format("Reprojection reason: {}", reason).c_str()); + + mvprintw(15, 0, fmt::format("Reprojecting: Yes ({}x, {})", frameShown, reason).c_str()); } else { - mvprintw(14, 0, "Reprojecting: No"); + mvprintw(15, 0, "Reprojecting: No"); } + // Current resolution attron(A_BOLD); - mvprintw(17, 0, "%s", fmt::format("Resolution = {}%", std::to_string(int(currentRes * 100))).c_str()); + mvprintw(17, 0, "%s", fmt::format("Resolution = {}%", std::to_string(int(newRes * 100))).c_str()); attroff(A_BOLD); - // Resolution handling - if (currentTime - resChangeDelayMs > lastChangeTime && !VROverlay()->IsDashboardVisible()) - { - lastChangeTime = currentTime; - bool vramLimitReached = vramLimit < getVramUsage(nvmlLibrary); - bool vramTargetReached = vramTarget < getVramUsage(nvmlLibrary); - - // If CPU Frametime is double the target frametime, - // or if preferReprojection is true and CPU Frametime is greated than targetFrametime. - if (((averageCpuTime > targetFrametime && preferReprojection) || averageCpuTime / 2 > targetFrametime) && !alwaysReproject && !ignoreCpuTime) - { - // Double the target frametime - targetFrametime *= 2; - } - - // Adjust resolution - if ((averageGpuTime > minGpuTimeThreshold || vramOnlyMode)) - { - float newRes = currentRes; - - // Frametime - if (averageGpuTime < targetFrametime * resIncreaseThreshold && !vramTargetReached && !vramOnlyMode) - { - // Increase resolution - newRes += ((((targetFrametime * resIncreaseThreshold) - averageGpuTime) / targetFrametime) * - resIncreaseScale) + - resIncreaseMin; - } - else if (averageGpuTime > targetFrametime * resDecreaseThreshold && !vramOnlyMode) - { - // Decrease resolution - newRes -= (((averageGpuTime - (targetFrametime * resDecreaseThreshold)) / targetFrametime) * - resDecreaseScale) + - resDecreaseMin; - } - - // VRAM - if (vramTargetReached) - { - if (newRes > lastRes) - { - // Make sure the resolution doesn't ever increase when the vram target is reached - newRes = lastRes; - } - if (vramLimitReached) - { - // Force the resolution to decrease when the vram limit is reached - newRes -= resDecreaseMin; - } - } - else if (vramOnlyMode) - { - // When in VRAM-only mode, make sure the res goes back up when possible. - if(newRes < initialRes) - newRes = min(initialRes, newRes + resIncreaseMin); - } - - // Clamp the new resolution - newRes = std::clamp(newRes, minRes, maxRes); - - if (lastRes != newRes) - { - // Sets the new resolution - vr::VRSettings()->SetFloat(vr::k_pch_SteamVR_Section, - vr::k_pch_SteamVR_SupersampleScale_Float, - newRes); - lastRes = newRes; - } - } - else if (resetOnThreshold && lastRes != initialRes && averageGpuTime < minGpuTimeThreshold) - { - // Reset to initialRes because resolution fell below the threshold - vr::VRSettings()->SetFloat(vr::k_pch_SteamVR_Section, - vr::k_pch_SteamVR_SupersampleScale_Float, - initialRes); - lastRes = initialRes; - } - } - // Displays the information refresh(); // Calculate how long to sleep for long sleepTime = dataPullDelayMs; - if (dataPullDelayMs < currentTime - lastChangeTime && - resChangeDelayMs < currentTime - lastChangeTime) + if (dataPullDelayMs < currentTime - lastChangeTime && resChangeDelayMs < currentTime - lastChangeTime) + { sleepTime = (currentTime - lastChangeTime) - resChangeDelayMs; + } else if (resChangeDelayMs < dataPullDelayMs) + { sleepTime = resChangeDelayMs; + } + // ZZzzzz std::this_thread::sleep_for(std::chrono::milliseconds(sleepTime)); }