Skip to content

Commit ed37b43

Browse files
Use Cemu hook to force shader cache re-compile when there is no existing backup
Set a default tolerance of 0.01MB when comparing the size of the GLCache to the backup, to avoid unnecessary copying
1 parent c9a2242 commit ed37b43

File tree

4 files changed

+159
-27
lines changed

4 files changed

+159
-27
lines changed

Cemu_withGLCacheBackup.ps1

Lines changed: 144 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<#
22
33
This PowerShell script is designed to intelligently backup and
4-
restore the NVIDIA GL shader cache for CEMU on a PER-GAME basis.
4+
restore the NVIDIA GL shader cache for Cemu on a PER-GAME basis.
55
66
Please see https://github.com/dkr88/CemuGLCacheBackup/blob/master/README.md
77
@@ -19,14 +19,17 @@ param (
1919

2020
[string]$logFile = "Cemu_withGLCacheBackup.log.txt",
2121

22+
[decimal]$glCacheSizeTolerance = 0.01,
23+
2224
[switch]$showPrompts = $false,
2325
[switch]$testMode = $false
2426
)
2527

28+
#region Helper functions
2629
function Write-Log {
2730
param(
2831
[Parameter(Mandatory=$true,Position=0)][string]$Message,
29-
[string]$Level = "info"
32+
[string]$Level = "trace"
3033
)
3134

3235
if ($noConsole) {
@@ -39,6 +42,7 @@ function Write-Log {
3942
else {
4043
switch ($Level) {
4144
"debug" { Write-Host $Message -ForegroundColor DarkGray }
45+
"info" { Write-Host $Message -ForegroundColor Cyan }
4246
"success" { Write-Host $Message -ForegroundColor Green }
4347
"warn" { Write-Host $Message -ForegroundColor Yellow }
4448
"error" { Write-Host $Message -ForegroundColor Red }
@@ -48,6 +52,90 @@ function Write-Log {
4852
}
4953
}
5054

55+
function Set-CemuHookIgnorePrecompiled {
56+
if (Test-Path $cemuHookIniFile) {
57+
$ini = Get-IniContent $cemuHookIniFile
58+
}
59+
else {
60+
if (!(Test-Path "$cemuDir\dbghelp.dll")) {
61+
Write-Log "Cemu hook not detected - delete files from 'shaderCache/precompiled' to ensure clean backup!" -Level "warn"
62+
return $false
63+
}
64+
$ini = @{}
65+
}
66+
67+
if (!$ini.ContainsKey("Graphics")) {
68+
$ini.Graphics = @{}
69+
}
70+
elseif ($ini.Graphics.ignorePrecompiledShaderCache -eq "true") {
71+
Write-Log "You should change the 'Ignore precompiled shader cache' setting to 'Enabled'" -Level "warn"
72+
return $false
73+
}
74+
75+
$ini.Graphics.ignorePrecompiledShaderCache = "true"
76+
77+
if (Test-Path $cemuHookIniFile) {
78+
Copy-Item $cemuHookIniFile $cemuHookIniBackupFile -Force
79+
}
80+
81+
Out-IniFile $ini -FilePath $cemuHookIniFile
82+
Write-Log "Forced ignorePrecompiledShaderCache = true in cemuhook.ini" -Level "info"
83+
84+
return $true
85+
}
86+
87+
function Get-IniContent ($FilePath) {
88+
$ini = @{}
89+
switch -regex -file $FilePath
90+
{
91+
"^\[(.+)\]" # Section
92+
{
93+
$section = $matches[1]
94+
$ini[$section] = @{}
95+
$CommentCount = 0
96+
}
97+
"^(;.*)$" # Comment
98+
{
99+
$value = $matches[1]
100+
$CommentCount = $CommentCount + 1
101+
$name = "Comment" + $CommentCount
102+
$ini[$section][$name] = $value
103+
}
104+
"(.+?)\s*=\s*(.*)" # Key
105+
{
106+
$name,$value = $matches[1..2]
107+
$ini[$section][$name] = $value
108+
}
109+
}
110+
return $ini
111+
}
112+
113+
function Out-IniFile($InputObject, $FilePath) {
114+
$outFile = New-Item -ItemType file -Path $FilePath -Force
115+
foreach ($i in $InputObject.keys)
116+
{
117+
if (!($($InputObject[$i].GetType().Name) -eq "Hashtable"))
118+
{
119+
#No Sections
120+
Add-Content -Path $outFile -Value "$i = $($InputObject[$i])"
121+
} else {
122+
#Sections
123+
Add-Content -Path $outFile -Value "[$i]"
124+
foreach ($j in ($InputObject[$i].keys | Sort-Object))
125+
{
126+
if ($j -match "^Comment[\d]+") {
127+
Add-Content -Path $outFile -Value "$($InputObject[$i][$j])"
128+
} else {
129+
Add-Content -Path $outFile -Value "$j = $($InputObject[$i][$j])"
130+
}
131+
132+
}
133+
Add-Content -Path $outFile -Value ""
134+
}
135+
}
136+
}
137+
#endregion
138+
51139
# Determine the current script path
52140
if ($MyInvocation.MyCommand.CommandType -eq "ExternalScript") {
53141
$scriptPath = Split-Path -Parent -Path $MyInvocation.MyCommand.Definition
@@ -56,8 +144,10 @@ else {
56144
$scriptPath = Split-Path -Parent -Path ([Environment]::GetCommandLineArgs()[0])
57145
}
58146

59-
# Default to current script path for CEMU and backup directories
60-
if (!$cemuDir) { $cemuDir = "$scriptPath" }
147+
# Default to current script path for Cemu and backup directories
148+
if (!$cemuDir) {
149+
$cemuDir = "$scriptPath"
150+
}
61151
if (!$backupDir) {
62152
$backupDir = "$scriptPath\GLCacheBackup"
63153
if (!(Test-Path $backupDir)) {
@@ -66,7 +156,12 @@ if (!$backupDir) {
66156
}
67157
}
68158

69-
# CEMU args need to be prefixed with '+' instead of '-' otherwise powershell gets confused
159+
# In case we need to make changes to cemuhook settings
160+
$cemuHookIniFile = "$cemuDir\cemuhook.ini"
161+
$cemuHookIniBackupFile = "$cemuDir\cemuhook.ini.glcachebackup"
162+
$cemuHookIniModified = $false
163+
164+
# Cemu args need to be prefixed with '+' instead of '-' otherwise powershell gets confused
70165
$cemuArgs = $cemuArgs.Replace("+", "-")
71166

72167
# This will be modified at build time to reflect the exe version being built
@@ -97,11 +192,12 @@ Write-Log "cemuArgs = $cemuArgs" -Level "debug"
97192
Write-Log "backupDir = $backupDir" -Level "debug"
98193
Write-Log "glCacheDir = $glCacheDir" -Level "debug"
99194
Write-Log "glCacheId = $glCacheId" -Level "debug"
195+
Write-Log "glCacheSizeTolerance = $glCacheSizeTolerance" -Level "debug"
100196

101197
$errors = @()
102198

103199
if (!(Test-Path "$cemuDir/$cemuExe")) {
104-
$errors += "CEMU executable was not found - is this script in the CEMU install directory?"
200+
$errors += "Cemu executable was not found - is this script in the CEMU install directory?"
105201
}
106202

107203
if (!(Test-Path $backupDir)) {
@@ -132,28 +228,36 @@ if ($testMode) {
132228
}
133229

134230
$copyCache = $true
231+
$forceRecompile = $false
135232

136233
$backupExists = Test-Path "$backupDir\$gameId.bin"
137234
$backupSize = 0
138235

139-
if ($backupExists -eq $true) {
236+
if ($backupExists) {
140237
$backupSize = ((Get-ChildItem $backupDir -Recurse -Filter "$gameId.*" | Measure-Object -Property Length -Sum -ErrorAction Stop).Sum / 1MB)
141238
Write-Log ("GLCache backup size for $gameId is {0:N2} MB" -f $backupSize)
142239
}
143240
else {
144241
Write-Log "GLCache backup doesn't exist for $gameId"
145242
$copyCache = $false
243+
244+
if (!$showPrompts -or (Read-Host "Force re-compile shader cache? ( [y] / n ) ") -ne "n") {
245+
if (!$testMode) {
246+
$forceRecompile = $true
247+
$cemuHookIniModified = Set-CemuHookIgnorePrecompiled
248+
}
249+
}
146250
}
147251

148252
$glCacheExists = Test-Path "$glCacheDir\$glCacheId.bin"
149253
$glCacheSize = 0
150254

151-
if ($glCacheExists -eq $true) {
255+
if ($glCacheExists) {
152256
$glCacheSize = ((Get-ChildItem $glCacheDir -Recurse -Filter "$glCacheId.*" | Measure-Object -Property Length -Sum -ErrorAction Stop).Sum / 1MB)
153257
Write-Log ("Existing GLCache size is {0:N2} MB" -f $glCacheSize)
154258

155-
if ($backupExists -eq $true) {
156-
if ($glCacheSize -eq $backupSize) {
259+
if ($backupExists -and !$forceRecompile) {
260+
if ([Math]::Abs($glCacheSize - $backupSize) -lt $glCacheSizeTolerance) {
157261
Write-Log "Existing GLCache will be re-used - it is the same size as backup"
158262
$copyCache = $false
159263
}
@@ -165,7 +269,14 @@ if ($glCacheExists -eq $true) {
165269
}
166270
}
167271
else {
168-
if (!$showPrompts -or (Read-Host "Delete existing GLCache before starting? ( [y] / n ) ") -ne "n") {
272+
if ($forceRecompile) {
273+
Write-Log "Deleting existing GLCache because shader cache will be re-compiled"
274+
if (!$testMode) {
275+
Remove-Item "$glCacheDir\$glCacheId.bin" -Force
276+
Remove-Item "$glCacheDir\$glCacheId.toc" -Force
277+
}
278+
}
279+
elseif (!$showPrompts -or (Read-Host "Delete existing GLCache before starting? ( [y] / n ) ") -ne "n") {
169280
Write-Log "Deleting existing GLCache and starting fresh"
170281
if (!$testMode) {
171282
Remove-Item "$glCacheDir\$glCacheId.bin" -Force
@@ -178,28 +289,42 @@ else {
178289
Write-Log "GLCache doesn't exist at the specified location yet" -Level "warn"
179290
}
180291

181-
If ($copyCache -eq $true) {
292+
If ($copyCache) {
182293
Write-Log "Backup GLCache will be used" -Level "success"
183294
if (!$testMode) {
184295
Copy-Item "$backupDir\$gameId.bin" "$glCacheDir\$glCacheId.bin" -Force
185296
Copy-Item "$backupDir\$gameId.toc" "$glCacheDir\$glCacheId.toc" -Force
186297
}
187298
}
188299

189-
Write-Log "Starting CEMU... "
300+
Write-Log "Starting Cemu... "
190301

191302
Set-Location -Path $cemuDir
192303
Start-Process -FilePath "$cemuDir/$cemuExe" -ArgumentList "$cemuArgs -g ""$gamePath""" -Wait
193304

194-
Write-Log "CEMU stopped."
305+
Write-Log "Cemu stopped."
306+
307+
if ($cemuHookIniModified) {
308+
if (Test-Path $cemuHookIniBackupFile) {
309+
Copy-Item $cemuHookIniBackupFile $cemuHookIniFile -Force
310+
Write-Log "Restored cemuhook.ini" -Level "info"
311+
}
312+
else {
313+
Remove-Item $cemuHookIniFile -Force
314+
Write-Log "Removed temporary cemuhook.ini"
315+
}
316+
}
317+
else {
318+
Write-Log "cemuhook.ini wasn't modified"
319+
}
195320

196321
$glCacheExists = Test-Path "$glCacheDir\$glCacheId.bin"
197322

198323
if ($glCacheExists) {
199324
$newGlCacheSize = ((Get-ChildItem "$glCacheDir\$glCacheId.*" -Recurse | Measure-Object -Property Length -Sum -ErrorAction Stop).Sum / 1MB)
200325
Write-Log ("New GLCache size is {0:N2} MB" -f $newGlCacheSize)
201326

202-
if ($newGlCacheSize -gt $backupSize) {
327+
if ([Math]::Abs($newGlCacheSize - $backupSize) -gt $glCacheSizeTolerance) {
203328
if ($backupSize -gt 0) {
204329
Write-Log ("New GLCache is {0:N8} MB larger than current backup" -f ($newGlCacheSize - $backupSize))
205330
}
@@ -225,4 +350,8 @@ if ($noConsole) {
225350
else {
226351
Stop-Transcript | Out-Null
227352
Start-Sleep -Seconds 3
353+
}
354+
355+
if ($showPrompts) {
356+
Read-Host "Press enter to exit"
228357
}

README.md

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ the NVIDIA GL shader cache for CEMU on a PER-GAME basis.
55

66
The script has been wrapped as an exe using PS2EXE-GUI.
77

8+
# Requirements
9+
10+
- Cemu
11+
- Cemu hook (recommended)
12+
- Only tested on Windows 10
13+
814
# What it fixes
915

1016
Primarily, micro-stutters. NVIDIA caches GL shaders in a file on your PC as they are invoked during the course of gameplay in CEMU. These are based on the recompiled shader caches that CEMU creates when it loads a game, but crucially NVIDIA does not cache these at the time CEMU loads the game, but rather as they are invoked during gameplay. This can result in micro-stutters as new shaders are encountered. The problem is, NVIDIA invalidates / deletes these caches on occasion, which means the next time you play the same game the shader might not be cached and thus the stuttering.
@@ -19,15 +25,19 @@ Quite simply, before launching a specific game in CEMU, this script tries to res
1925

2026
So effectively this script maintains a per-game GL shader cache backup.
2127

22-
> If you were previously setting the `Precompiled shader cache` setting in Cemuhook to `Disabled/ignored` to fix stuttering, you should try setting it back to `Enabled` when using GLCache backups.
28+
>When running a game for the first time, this script will use the Cemu hook .ini file to force a re-compile of the shader cache. This is necessary to get a good clean GLCache to make a backup from.
29+
>
30+
>When you exit Cemu after the first run of the game, the backup will be taken and the setting will be reverted to enable the precompiled shader cache again.
31+
>
32+
>WARNING: If you make any changes to Cemu hook config on the first run and this script has forced the re-compile, the settings you changed will be reverted when you exit Cemu.
2333
2434
# Setup
2535

2636
1) Place this script/exe *and* 'Cemu_withGLCacheBackup.xml' in the same directory as Cemu.exe.
2737

2838
2) Edit 'Cemu_withGLCacheBackup.xml' and set the NVIDIA GL cache path and ID appropriately.
2939

30-
3) Create shortcuts for each game you want to run.
40+
3) For each CEMU game you play create a Windows and/or Steam shortcut
3141

3242
# Cemu_withGLCacheBackup.xml
3343

@@ -136,18 +146,11 @@ If you use this option, you'll get prompted before restoring and saving backups.
136146
This flag will make the script not actually restore or backup the cache files. Useful if you just want to test your config and see the size of the shader caches.
137147
```
138148

139-
# How do I know if it's working?
149+
# Troubleshooting
140150

141-
Once you've configured everything correctly and backups are being taken, you can do this simple test:
151+
- Check the `GLCacheBackup` directory in your Cemu install dir to verify that backups are being created.
142152

143-
1. Run a game using this script and play a section you can easily replay
144-
2. Exit CEMU
145-
3. Find the GLCache backup for your game (by default, in the `GLCacheBackup` folder within the CEMU install directory)
146-
4. Rename the `GLCacheBackup` folder to `GLCacheBackup_off`
147-
5. Re-launch the game and play the same section, do you get stutters?
148-
6. Exit CEMU
149-
7. Delete the new `GLCacheBackup` folder that got created and rename `GLCacheBackup_off` back to `GLCacheBackup`
150-
8. Re-launch the game and play the same section - there should be less micro-stuttering
153+
- Check the `Cemu_withGLCacheBackup.log.txt` file to look for errors or warnings.
151154

152155
# Logging
153156

dist/Cemu_withGLCacheBackup.exe

11 KB
Binary file not shown.
11 KB
Binary file not shown.

0 commit comments

Comments
 (0)