diff --git a/Dalamud/Interface/Internal/TextureManager.cs b/Dalamud/Interface/Internal/TextureManager.cs index 66a2cf731..905cac55a 100644 --- a/Dalamud/Interface/Internal/TextureManager.cs +++ b/Dalamud/Interface/Internal/TextureManager.cs @@ -233,13 +233,12 @@ internal class TextureManager : IDisposable, IServiceType, ITextureSubstitutionP /// Get texture info. /// /// Path to the texture. - /// Whether or not the texture should be reloaded if it was unloaded. /// /// If true, exceptions caused by texture load will not be caught. /// If false, exceptions will be caught and a dummy texture will be returned to prevent plugins from using invalid texture handles. /// /// Info object storing texture metadata. - internal TextureInfo? GetInfo(string path, bool refresh = true, bool rethrow = false) + internal TextureInfo GetInfo(string path, bool rethrow = false) { TextureInfo? info; lock (this.activeTextures) @@ -248,106 +247,94 @@ internal class TextureManager : IDisposable, IServiceType, ITextureSubstitutionP { Debug.Assert(rethrow, "This should never run when getting outside of creator"); - if (!refresh) - return null; - info = new TextureInfo(); this.activeTextures.Add(path, info); } if (info == null) throw new Exception("null info in activeTextures"); - - // NOTE: We need to increase the refcount here while locking the collection! - // Otherwise, if this is loaded from a task, cleanup might already try to delete it - // before it can be increased. - info.RefCount++; } - if (refresh && info.KeepAliveCount == 0) + if (info.KeepAliveCount == 0) info.LastAccess = DateTime.UtcNow; if (info is { Wrap: not null }) return info; - if (refresh) - { - if (!this.im.IsReady) + if (!this.im.IsReady) throw new InvalidOperationException("Cannot create textures before scene is ready"); - string? interceptPath = null; - this.InterceptTexDataLoad?.Invoke(path, ref interceptPath); + string? interceptPath = null; + this.InterceptTexDataLoad?.Invoke(path, ref interceptPath); - if (interceptPath != null) - { - Log.Verbose("Intercept: {OriginalPath} => {ReplacePath}", path, interceptPath); - path = interceptPath; - } + if (interceptPath != null) + { + Log.Verbose("Intercept: {OriginalPath} => {ReplacePath}", path, interceptPath); + path = interceptPath; + } - TextureWrap? wrap; - try + TextureWrap? wrap; + try + { + // We want to load this from the disk, probably, if the path has a root + // Not sure if this can cause issues with e.g. network drives, might have to rethink + // and add a flag instead if it does. + if (Path.IsPathRooted(path)) { - // We want to load this from the disk, probably, if the path has a root - // Not sure if this can cause issues with e.g. network drives, might have to rethink - // and add a flag instead if it does. - if (Path.IsPathRooted(path)) + if (Path.GetExtension(path) == ".tex") { - if (Path.GetExtension(path) == ".tex") - { - // Attempt to load via Lumina - var file = this.dataManager.GameData.GetFileFromDisk(path); - wrap = this.GetTexture(file); - Log.Verbose("Texture {Path} loaded FS via Lumina", path); - } - else - { - // Attempt to load image - wrap = this.im.LoadImage(path); - Log.Verbose("Texture {Path} loaded FS via LoadImage", path); - } + // Attempt to load via Lumina + var file = this.dataManager.GameData.GetFileFromDisk(path); + wrap = this.GetTexture(file); + Log.Verbose("Texture {Path} loaded FS via Lumina", path); } else { - // Load regularly from dats - var file = this.dataManager.GetFile(path); - if (file == null) - throw new Exception("Could not load TexFile from dat."); - - wrap = this.GetTexture(file); - Log.Verbose("Texture {Path} loaded from SqPack", path); + // Attempt to load image + wrap = this.im.LoadImage(path); + Log.Verbose("Texture {Path} loaded FS via LoadImage", path); } - - if (wrap == null) - throw new Exception("Could not create texture"); - - // TODO: We could support this, but I don't think it's worth it at the moment. - var extents = new Vector2(wrap.Width, wrap.Height); - if (info.Extents != Vector2.Zero && info.Extents != extents) - Log.Warning("Texture at {Path} changed size between reloads, this is currently not supported.", path); - - info.Extents = extents; } - catch (Exception e) + else { - Log.Error(e, "Could not load texture from {Path}", path); - - // When creating the texture initially, we want to be able to pass errors back to the plugin - if (rethrow) - throw; - - // This means that the load failed due to circumstances outside of our control, - // and we can't do anything about it. Return a dummy texture so that the plugin still - // has something to draw. - wrap = this.fallbackTextureWrap; - - // Prevent divide-by-zero - if (info.Extents == Vector2.Zero) - info.Extents = Vector2.One; + // Load regularly from dats + var file = this.dataManager.GetFile(path); + if (file == null) + throw new Exception("Could not load TexFile from dat."); + + wrap = this.GetTexture(file); + Log.Verbose("Texture {Path} loaded from SqPack", path); } + + if (wrap == null) + throw new Exception("Could not create texture"); - info.Wrap = wrap; + // TODO: We could support this, but I don't think it's worth it at the moment. + var extents = new Vector2(wrap.Width, wrap.Height); + if (info.Extents != Vector2.Zero && info.Extents != extents) + Log.Warning("Texture at {Path} changed size between reloads, this is currently not supported.", path); + + info.Extents = extents; + } + catch (Exception e) + { + Log.Error(e, "Could not load texture from {Path}", path); + + // When creating the texture initially, we want to be able to pass errors back to the plugin + if (rethrow) + throw; + + // This means that the load failed due to circumstances outside of our control, + // and we can't do anything about it. Return a dummy texture so that the plugin still + // has something to draw. + wrap = this.fallbackTextureWrap; + + // Prevent divide-by-zero + if (info.Extents == Vector2.Zero) + info.Extents = Vector2.One; } + info.Wrap = wrap; return info; } @@ -359,23 +346,23 @@ internal class TextureManager : IDisposable, IServiceType, ITextureSubstitutionP /// Whether or not this handle was created in keep-alive mode. internal void NotifyTextureDisposed(string path, bool keepAlive) { - var info = this.GetInfo(path, false); - - // This texture was already disposed - if (info == null) + lock (this.activeTextures) { - Log.Warning("Disposing unknown texture {Path}", path); - return; + if (!this.activeTextures.TryGetValue(path, out var info)) + { + Log.Warning("Disposing texture that didn't exist: {Path}", path); + return; + } + + info.RefCount--; + + if (keepAlive) + info.KeepAliveCount--; + + // Clean it up by the next update. If it's re-requested in-between, we don't reload it. + if (info.RefCount <= 0) + info.LastAccess = default; } - - info.RefCount--; - - if (keepAlive) - info.KeepAliveCount--; - - // Clean it up by the next update. If it's re-requested in-between, we don't reload it. - if (info.RefCount <= 0) - info.LastAccess = default; } private static string FormatIconPath(uint iconId, string? type, bool highResolution) @@ -391,14 +378,22 @@ internal class TextureManager : IDisposable, IServiceType, ITextureSubstitutionP private TextureManagerTextureWrap? CreateWrap(string path, bool keepAlive) { - // This will create the texture. - // That's fine, it's probably used immediately and this will let the plugin catch load errors. - var info = this.GetInfo(path, rethrow: true)!; + lock (this.activeTextures) + { + // This will create the texture. + // That's fine, it's probably used immediately and this will let the plugin catch load errors. + var info = this.GetInfo(path, rethrow: true); - if (keepAlive) - info.KeepAliveCount++; + // We need to increase the refcounts here while locking the collection! + // Otherwise, if this is loaded from a task, cleanup might already try to delete it + // before it can be increased. + info.RefCount++; - return new TextureManagerTextureWrap(path, info.Extents, keepAlive, this); + if (keepAlive) + info.KeepAliveCount++; + + return new TextureManagerTextureWrap(path, info.Extents, keepAlive, this); + } } private void FrameworkOnUpdate(Framework fw) @@ -589,7 +584,7 @@ internal class TextureManagerTextureWrap : IDalamudTextureWrap /// public IntPtr ImGuiHandle => !this.IsDisposed ? - this.manager.GetInfo(this.path)!.Wrap!.ImGuiHandle : + this.manager.GetInfo(this.path).Wrap!.ImGuiHandle : throw new InvalidOperationException("Texture already disposed. You may not render it."); ///