feat: paint-list dynamic resize + mip-cap fix for heavy paint packs#5
feat: paint-list dynamic resize + mip-cap fix for heavy paint packs#5AdaInTheLab wants to merge 7 commits into
Conversation
|
Thanks for contributing back, will take a look at it soonish. I may see a way to do this a bit more "backwards" compatible, but having a working example certainly helps with improving this approach a bit more. One main thing though, I don't intend to put it under MIT license, as I have kept most my mods a bit more closed. So question is if that is an issue for your contribution or not? Thanks again and have a nice week! |
|
So I took a quick look and wonder if it wouldn't be possible for your mod to patch everything you need in my mod in order to make it seamlessly compatible more easily? I'm willing to refactor some parts to make that easier for you, since I see one or two changes that might be a bit difficult to patch, but other than those it should be possible to patch my mod with Harmony from your other mod!? |
|
This made my day, thank you ~ honestly I'd been hoping for a path forward that doesn't involve maintaining a fork forever. Yes, very much yes to this. 1. BlockTextureData.list sizing 2. Custom paint ID floor 3. Atlas dimensioning 4. The bundle-collision case (separate from PU but related) Things I'd genuinely call "hard to patch": I'm happy to:
End state I'd love: users install your official OCB from Nexus + my PaintUnlocked, and they just work together. Fork retired. One less pitfall in the install guide. Let me know how you want to coordinate ~ GitHub issue, Discord, email, whatever works for you. Thanks again for reaching out. |
|
Will try to take a look at the PR this weekend (no guarantee though). Can you maybe elaborate a little what it changes and if it is already what you would like to have for it to be compatible with your extension mod? Also, does it contain any change that is incompatible with how it was working before. Will gladly like to collaborate via GitHub here. E.g. just change it the way you want, while keeping the original functionality intact without any impact on existing users would be best. |
|
sorry for the lag here ~ just pushed a cleanup commit and an updated PR description that should make the "is this safe to merge" question a lot easier to answer. The previous version of this PR had fork-branding (LICENSE, README rewrite, ModInfo flip) and committed working-notes mixed in with the technical changes, which made the diff harder to read than it needed to be. All that came out in the most recent push; what's left is just the code. Walking through your "is anything incompatible with how it was working before" question per item:
The 512 floor that the previous PR description mentioned isn't actually in the code anymore. I'd disabled it before the 5/12 conversation because the dynamic-resize approach made it unnecessary, but the description was still claiming it. Fixed in the new description.
This one is independent of paint capacity. This benefits any heavy-paint-pack OCB install regardless of whether PaintUnlocked is in play. Happy to split it into its own one-file PR if that makes it easier to land independent of the rest of the conversation. Still the same end-state preference Your extension-points proposal from 5/12 is still the win for both of us. The specific upstream changes I'd want most (so PaintUnlocked can drop the rest of these Harmony patches entirely):
Happy to PR each of those as small individual changes against your repo whenever you have bandwidth. Zero deadline. Let me know how you'd like to proceed ~ a few options:
🦊 |
Dedicated server installs have no GPU atlas, so the diffuse-texture check leaves builtinOpaques at -1. That falls through to incorrect ID assignment in the paint registration loop below. Clamping to 0 makes the headless install run cleanly ~ tiling.index is a render concern, so the value doesn't matter on server beyond not being -1.
Previously threw 'No more free Paint IDs' when BlockTextureData.list filled up at 255 entries. Now resizes by 256 and returns the next slot. Only fires when the array is genuinely full, so vanilla-sized configs never hit it ~ this is a transparent fallback for paint packs that exceed the default capacity, not a behavior change for default installs.
The old sizing 'texture.depth + OpaquesAdded' assumes texture.depth reflects the current builtinOpaques count, which fails when the initial texture was allocated against an older atlas size and the current builtinOpaques is larger. The Max() takes the same value as the original in the common case where texture.depth >= builtinOpaques, and only changes behavior when the texture would otherwise be undersized.
Hooks into BlockTexturesFromXML.CreateBlockTextures Prefix to grow BlockTextureData.list to a 1024-entry floor before any chunk deserialization can access high paint IDs. Without this, reloading a save built against a larger texture set than the current process can fit throws IndexOutOfRange from the chunk-paint accessor. The minSize floor of 1024 covers the existing vanilla range plus a margin large enough that paint packs with several hundred entries don't trigger an additional resize during config-load.
Right before the paint registration loop, grow the array to fit the upcoming ID assignments in 256-slot blocks. A small config gets a small bump and a large pack doesn't trigger an array resize per registered paint. Complements the config-load-time pre-size (which handles save reload ordering): this one handles the case where the config is freshly parsed but the array is still vanilla-sized.
Final safety net: if any code path (e.g. chunk deserialization running before InitOpaqueConfig fires) tries to register a BlockTextureData with ID >= list.Length, auto-grow the array in 256-slot blocks. This is a no-op for the common case where the array is already sized correctly by the two pre-size hooks above; it exists to catch ordering bugs where a high-ID paint gets touched before either pre-size pass has run.
PatchTexture walked dst.mipmapCount unconditionally, which is correct when src is the same size or larger (the '2k into 1k' case the quality offset is designed for) but overruns the source when src is SMALLER than dst. The loop would ask src for mip levels it doesn't have, and Unity logs ERR Graphics.CopyTexture called with invalid source mip level (got 8, have 8 mips) for every iteration past src's last. That floods the console at chunk paint time, especially with third-party paint mods that ship small paint textures (128×128, 8 mips) being patched into bigger atlas slots (1024+, 11+ mips). Cap at Math.Min(dst.mipmapCount, src.mipmapCount - offset) and bail when src has nothing usable at the current quality offset. Destination's smallest mip levels are left unwritten; those only matter at extreme view distance and existing destination contents are preserved. Independent of paint capacity ~ benefits any OCB install with mixed- size paint packs.
9dc9cae to
f102967
Compare
|
small follow-up ~ just force-pushed the branch reshaped into 7 per-topic commits so each one is a single review decision. the net diff is unchanged from what i described above, just the history is now:
if any of them don't sit right you can cherry-pick the ones you like and skip the rest, or i'm happy to drop individual commits on request. #7 is the one that's fully independent of paint capacity so that one stands on its own if you'd rather not deal with the paint-list stuff right now. 🦊 |
|
Thanks a lot for the work, really need to get some time to include this, much appreciated |
Summary
Updated 2026-05-28: cleaned up the original PR surface so the diff is just the technical changes. Fork-branding (LICENSE, README rewrite, ModInfo flip) and committed working-notes have been reverted out. The actual code changes below are net-additive for any OcbCustomTextures install.
What's in the diff
Harmony/OpaqueTextures.cs~ additive safety nets for paint-list capacityBlockTextureData.listto ≥1024 at config-load time, before chunk deserialization can access high paint IDs from saved data. Vanilla-sized configs don't observe a difference (later passes resize back down as needed).BlockTextureData.Initsafety net: auto-grows the list on demand if any code path tries to register a paint withID >= list.Length. No-op for the common case where the array is already large enough.GetFreePaintID()grow-instead-of-throw: wasthrow new Exception("No more free Paint IDs")when the array filled at 255. Now resizes by 256 and returns the next free slot. The only behavior change in vanilla terms ~ happy to revert to throwing if you prefer to keep that as an explicit failure mode rather than a transparent grow.builtinOpaques == -1(no GPU atlas) is now clamped to 0 instead of falling through to incorrect ID assignment on headless installs.ApplyTexturesMax() sizing:texture.depth + OpaquesAdded→Math.Max(texture.depth, builtinOpaques) + OpaquesAdded. Returns the same value as the original whentexture.depth >= builtinOpaques(the common case); properly sized when the texture allocation was smaller than the current builtinOpaques count.Utils/OcbTextureUtils.cs~ mip-cap bug fix (independent of paint capacity)PatchTexturelooped0..dst.mipmapCountregardless of how many mipssrcactually had. When src is smaller than dst (e.g. a 128px paint texture copied into a bigger atlas slot through here, which is how third-party paint packs ship), Unity logsERR Graphics.CopyTexture called with invalid source mip level (got 8, have 8 mips)for every iteration past src's last. Caps the loop atMath.Min(dst.mipmapCount, src.mipmapCount - offset)and bails when src has nothing usable at the current quality offset.This one is unrelated to PaintUnlocked and benefits any OCB install with mixed-size paint packs.
Compatibility profile
Every new branch in this PR fires only when something would have previously crashed, thrown, or log-spammed. Specifically:
"No more free Paint IDs"Users not hitting those conditions don't observe any difference.
End-state I'm still hoping for
The extension-points refactor we discussed in 5/12 is still my preferred outcome ~ a few of OCB's currently-inlined paint-list constants becoming named
static readonly intfields, plus a public floor/offset forbuiltinOpaquesthat PaintUnlocked can set from its InitMod, would let me drop most of these Harmony patches entirely.If it'd help, I'd happily break those out into separate small PRs once you've had a chance to look at this one. Each can land independently.
🦊