diff --git a/src/AcDream.App/Rendering/TextureCache.cs b/src/AcDream.App/Rendering/TextureCache.cs new file mode 100644 index 0000000..2794038 --- /dev/null +++ b/src/AcDream.App/Rendering/TextureCache.cs @@ -0,0 +1,92 @@ +// src/AcDream.App/Rendering/TextureCache.cs +using AcDream.Core.Textures; +using DatReaderWriter; +using DatReaderWriter.DBObjs; +using Silk.NET.OpenGL; + +namespace AcDream.App.Rendering; + +public sealed unsafe class TextureCache : IDisposable +{ + private readonly GL _gl; + private readonly DatCollection _dats; + private readonly Dictionary _handlesBySurfaceId = new(); + private uint _magentaHandle; + + public TextureCache(GL gl, DatCollection dats) + { + _gl = gl; + _dats = dats; + } + + /// + /// Get or upload the GL texture handle for a Surface id. Returns a + /// 1x1 magenta fallback if the Surface or its RenderSurface chain is + /// missing or uses an unsupported format. + /// + public uint GetOrUpload(uint surfaceId) + { + if (_handlesBySurfaceId.TryGetValue(surfaceId, out var h)) + return h; + + var decoded = DecodeFromDats(surfaceId); + h = UploadRgba8(decoded); + _handlesBySurfaceId[surfaceId] = h; + return h; + } + + private DecodedTexture DecodeFromDats(uint surfaceId) + { + var surface = _dats.Get(surfaceId); + if (surface is null) + return DecodedTexture.Magenta; + + var surfaceTexture = _dats.Get((uint)surface.OrigTextureId); + if (surfaceTexture is null || surfaceTexture.Textures.Count == 0) + return DecodedTexture.Magenta; + + var rs = _dats.Get((uint)surfaceTexture.Textures[0]); + if (rs is null) + return DecodedTexture.Magenta; + + return SurfaceDecoder.DecodeRenderSurface(rs); + } + + private uint UploadRgba8(DecodedTexture decoded) + { + uint tex = _gl.GenTexture(); + _gl.BindTexture(TextureTarget.Texture2D, tex); + + fixed (byte* p = decoded.Rgba8) + _gl.TexImage2D( + TextureTarget.Texture2D, + 0, + InternalFormat.Rgba8, + (uint)decoded.Width, + (uint)decoded.Height, + 0, + PixelFormat.Rgba, + PixelType.UnsignedByte, + p); + + _gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); + _gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); + _gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat); + _gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat); + + _gl.BindTexture(TextureTarget.Texture2D, 0); + return tex; + } + + public void Dispose() + { + foreach (var h in _handlesBySurfaceId.Values) + _gl.DeleteTexture(h); + _handlesBySurfaceId.Clear(); + if (_magentaHandle != 0) + { + _gl.DeleteTexture(_magentaHandle); + _magentaHandle = 0; + } + } +}