perf: Speed up texture replacment#1
Merged
Phantomical merged 1 commit intoKSPModStewards:masterfrom Nov 8, 2025
Merged
Conversation
When I profile my save with HUDReplacer installed (+ZTheme) it takes about 1.5s as part of every scene switch. Most of that time was spent iterating over the `images` dictionary again and again for every loaded texture. This commit gets the overhead of HUDReplacer down to ~130ms (on my save). There are a bunch of linked changes here, most of which are needed in order to make the whole thing work. I'll walk through these one by one. 1. The first thing is an overhaul of how the replacement info is stored. There are now two dictionaries: a global set of replacements, and a set of per-scene replacement dictionaries. These are keyed off of the image name, and have a list of all the available replacements for that image name, ordered by priority. This means that we can parse all the available texture replacements at startup and then not have to worry about needing to parse them again. We also pre-parse the texture filenames into their basename and size, if available. This means that we can actually just look them up by name, instead of having to search everything. As a bonus, I have added a ModuleManagerPostLoad call to load the textures so that it happens during the loading screen, instead of during the black screen when switching to the main screen. 2. Next, I have rewritten ReplaceTextures to take advantage of the new dictionary. There's a bit of extra work, because we need to deal with the fact that the replacements could possibly be either in the global dictionary or the scene-specific one. This all gets wrapped up in the GetMatchingReplacement methods. I have used the priority I saved from the first step to disambiguate. If that doesn't I just pick the scene specific one. This is a small behaviour change but I don't think it will matter in practice. 3. Caching. With the two changes above the runtime goes down to about 300ms - which is split 50/50 between reading textures off disk and loading them into a Texture2D. To speed this up I added a cachedTextureBytes field to ReplacementInfo. This makes it so the disk read can be avoided the next time the replacement is used. I have also made a couple of small changes and/or bugfixes as I was going through: - LoadTextures now emits useful error messages if the config node is invalid or if the format of the file name is wrong, instead of crashing. - ReplaceTextures now picks the texture with the highest priority, instead of the one with the lowest.
bf4def5 to
75c0ae3
Compare
This was referenced Nov 10, 2025
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
When I profile my save with HUDReplacer installed (+ZTheme) it takes about 1.5s as part of every scene switch. Most of that time was spent iterating over the
imagesdictionary again and again for every loaded texture.This commit gets the overhead of HUDReplacer down to ~140ms (on my save).
I'm not sure if this entirely fixes issue UltraJohn#7, since there are still things that can be improved. It does make things drastically better though.
There are a bunch of linked changes here, most of which are needed in order to make the whole thing work. I'll walk through these one by one.
The first thing is an overhaul of how the replacement info is stored. There are now two dictionaries: a global set of replacements, and a set of per-scene replacement dictionaries. These are keyed off of the image name, and have a list of all the available replacements for that image name, ordered by priority.
This means that we can parse all the available texture replacements at startup and then not have to worry about needing to parse them again.
We also pre-parse the texture filenames into their basename and size, if available. This means that we can actually just look them up by name, instead of having to search everything.
As a bonus, I have added a ModuleManagerPostLoad call to load the textures so that it happens during the loading screen, instead of during the black screen when switching to the main screen.
Next, I have rewritten ReplaceTextures to take advantage of the new dictionary. There's a bit of extra work, because we need to deal with the fact that the replacements could possibly be either in the global dictionary or the scene-specific one. This all gets wrapped up in the GetMatchingReplacement methods. I have used the priority I saved from the first step to disambiguate. If that doesn't I just pick the scene specific one.
This is a small behaviour change but I don't think it will matter in practice.
Caching. With the two changes above the runtime goes down to about 300ms - which is split 50/50 between reading textures off disk and loading them into a Texture2D. To speed this up I added a cachedTextureBytes field to ReplacementInfo. This makes it so the disk read can be avoided the next time the replacement is used.
I have also made a couple of small changes and/or bugfixes as I was going through:
Flame Graphs
Before. Note that

HUDReplacer.Awaketakes 1.5s here.After. Now it takes 140ms.
