feat: add fallback texture, persist size

This commit is contained in:
goat 2023-08-02 18:48:43 +02:00
parent 22a6261c98
commit 7a6916c732
No known key found for this signature in database
GPG key ID: 49E2AA8C6A76498B

View file

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Numerics;
using Dalamud.Data; using Dalamud.Data;
using Dalamud.Game; using Dalamud.Game;
@ -40,6 +41,8 @@ internal class TextureManager : IDisposable, IServiceType, ITextureSubstitutionP
private readonly Dictionary<string, TextureInfo> activeTextures = new(); private readonly Dictionary<string, TextureInfo> activeTextures = new();
private TextureWrap? fallbackTextureWrap;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="TextureManager"/> class. /// Initializes a new instance of the <see cref="TextureManager"/> class.
/// </summary> /// </summary>
@ -56,6 +59,8 @@ internal class TextureManager : IDisposable, IServiceType, ITextureSubstitutionP
this.startInfo = startInfo; this.startInfo = startInfo;
this.framework.Update += this.FrameworkOnUpdate; this.framework.Update += this.FrameworkOnUpdate;
Service<InterfaceManager.InterfaceManagerWithScene>.GetAsync().ContinueWith(_ => this.CreateFallbackTexture());
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -175,6 +180,7 @@ internal class TextureManager : IDisposable, IServiceType, ITextureSubstitutionP
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() public void Dispose()
{ {
this.fallbackTextureWrap?.Dispose();
this.framework.Update -= this.FrameworkOnUpdate; this.framework.Update -= this.FrameworkOnUpdate;
Log.Verbose("Disposing {Num} left behind textures."); Log.Verbose("Disposing {Num} left behind textures.");
@ -192,8 +198,12 @@ internal class TextureManager : IDisposable, IServiceType, ITextureSubstitutionP
/// </summary> /// </summary>
/// <param name="path">Path to the texture.</param> /// <param name="path">Path to the texture.</param>
/// <param name="refresh">Whether or not the texture should be reloaded if it was unloaded.</param> /// <param name="refresh">Whether or not the texture should be reloaded if it was unloaded.</param>
/// <param name="rethrow">
/// 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.
/// </param>
/// <returns>Info object storing texture metadata.</returns> /// <returns>Info object storing texture metadata.</returns>
internal TextureInfo GetInfo(string path, bool refresh = true) internal TextureInfo GetInfo(string path, bool refresh = true, bool rethrow = false)
{ {
TextureInfo? info; TextureInfo? info;
lock (this.activeTextures) lock (this.activeTextures)
@ -232,39 +242,53 @@ internal class TextureManager : IDisposable, IServiceType, ITextureSubstitutionP
} }
TextureWrap? wrap; TextureWrap? wrap;
try
// TODO: The actual loading here may fail due to circumstances outside of our control.
// We should create a fallback texture and return it instead, so that plugins don't crash.
// 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") // 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))
{ {
// Attempt to load via Lumina if (Path.GetExtension(path) == ".tex")
var file = this.dataManager.GameData.GetFileFromDisk<TexFile>(path); {
wrap = this.dataManager.GetImGuiTexture(file); // Attempt to load via Lumina
Log.Verbose("Texture {Path} loaded FS via Lumina", path); var file = this.dataManager.GameData.GetFileFromDisk<TexFile>(path);
wrap = this.dataManager.GetImGuiTexture(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);
}
} }
else else
{ {
// Attempt to load image // Load regularly from dats
wrap = this.im.LoadImage(path); var file = this.dataManager.GetFile<TexFile>(path);
Log.Verbose("Texture {Path} loaded FS via LoadImage", path); wrap = this.dataManager.GetImGuiTexture(file);
Log.Verbose("Texture {Path} loaded from SqPack", path);
} }
}
else if (wrap == null)
{ throw new Exception("Could not create texture");
// Load regularly from dats
var file = this.dataManager.GetFile<TexFile>(path);
wrap = this.dataManager.GetImGuiTexture(file);
Log.Verbose("Texture {Path} loaded from SqPack", path);
}
if (wrap == null) info.Extents = new Vector2(wrap.Width, wrap.Height);
throw new Exception("Could not create texture"); }
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;
}
info.Wrap = wrap; info.Wrap = wrap;
} }
@ -306,13 +330,13 @@ internal class TextureManager : IDisposable, IServiceType, ITextureSubstitutionP
{ {
// This will create the texture. // This will create the texture.
// That's fine, it's probably used immediately and this will let the plugin catch load errors. // That's fine, it's probably used immediately and this will let the plugin catch load errors.
var info = this.GetInfo(path, keepAlive); var info = this.GetInfo(path, rethrow: true);
info.RefCount++; info.RefCount++;
if (keepAlive) if (keepAlive)
info.KeepAliveCount++; info.KeepAliveCount++;
return new TextureManagerTextureWrap(path, keepAlive, this); return new TextureManagerTextureWrap(path, info.Extents, keepAlive, this);
} }
private void FrameworkOnUpdate(Framework fw) private void FrameworkOnUpdate(Framework fw)
@ -353,6 +377,13 @@ internal class TextureManager : IDisposable, IServiceType, ITextureSubstitutionP
} }
} }
private void CreateFallbackTexture()
{
var fallbackTexBytes = new byte[] { 0xFF, 0x00, 0xDC, 0xFF };
this.fallbackTextureWrap = this.im.LoadImageRaw(fallbackTexBytes, 1, 1, 4);
Debug.Assert(this.fallbackTextureWrap != null, "this.fallbackTextureWrap != null");
}
/// <summary> /// <summary>
/// Internal representation of a managed texture. /// Internal representation of a managed texture.
/// </summary> /// </summary>
@ -377,6 +408,11 @@ internal class TextureManager : IDisposable, IServiceType, ITextureSubstitutionP
/// Gets or sets the number of active holders that want this texture to stay alive forever. /// Gets or sets the number of active holders that want this texture to stay alive forever.
/// </summary> /// </summary>
public uint KeepAliveCount { get; set; } public uint KeepAliveCount { get; set; }
/// <summary>
/// Gets or sets the extents of the texture.
/// </summary>
public Vector2 Extents { get; set; }
} }
} }
@ -474,30 +510,30 @@ internal class TextureManagerTextureWrap : IDalamudTextureWrap
private readonly string path; private readonly string path;
private readonly bool keepAlive; private readonly bool keepAlive;
private int? width;
private int? height;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="TextureManagerTextureWrap"/> class. /// Initializes a new instance of the <see cref="TextureManagerTextureWrap"/> class.
/// </summary> /// </summary>
/// <param name="path">The path to the texture.</param> /// <param name="path">The path to the texture.</param>
/// <param name="extents">The extents of the texture.</param>
/// <param name="keepAlive">Keep alive or not.</param> /// <param name="keepAlive">Keep alive or not.</param>
/// <param name="manager">Manager that we obtained this from.</param> /// <param name="manager">Manager that we obtained this from.</param>
internal TextureManagerTextureWrap(string path, bool keepAlive, TextureManager manager) internal TextureManagerTextureWrap(string path, Vector2 extents, bool keepAlive, TextureManager manager)
{ {
this.path = path; this.path = path;
this.keepAlive = keepAlive; this.keepAlive = keepAlive;
this.manager = manager; this.manager = manager;
this.Width = (int)extents.X;
this.Height = (int)extents.Y;
} }
/// <inheritdoc/> /// <inheritdoc/>
public IntPtr ImGuiHandle => this.manager.GetInfo(this.path).Wrap!.ImGuiHandle; public IntPtr ImGuiHandle => this.manager.GetInfo(this.path).Wrap!.ImGuiHandle;
/// <inheritdoc/> /// <inheritdoc/>
public int Width => this.width ??= this.manager.GetInfo(this.path).Wrap!.Width; public int Width { get; private set; }
/// <inheritdoc/> /// <inheritdoc/>
public int Height => this.height ??= this.manager.GetInfo(this.path).Wrap!.Height; public int Height { get; private set; }
/// <summary> /// <summary>
/// Gets a value indicating whether or not this wrap has already been disposed. /// Gets a value indicating whether or not this wrap has already been disposed.