using Chorizite.Core.Render;
using Chorizite.Core.Render.Enums;
using DatReaderWriter.Enums;
using Silk.NET.OpenGL;
using System;
using System.Collections.Generic;
using PixelFormat = Silk.NET.OpenGL.PixelFormat;
namespace AcDream.App.Rendering.Wb {
///
/// Manages texture arrays grouped by (Width, Height, Format).
/// Deduplicates textures by a TextureKey and supports reference counting.
///
public class TextureAtlasManager : IDisposable {
private static uint _nextSlot = 1;
private readonly OpenGLGraphicsDevice _graphicsDevice;
private readonly int _textureWidth;
private readonly int _textureHeight;
private readonly TextureFormat _format;
private readonly Dictionary _textureIndices = new();
private readonly Dictionary _refCounts = new();
private readonly Stack _freeSlots = new();
private int _nextIndex = 0;
private const int InitialCapacity = 32;
public uint Slot { get; }
public ManagedGLTextureArray TextureArray { get; private set; } = null!;
public int UsedSlots => _textureIndices.Count;
public int TotalSlots => TextureArray?.Size ?? InitialCapacity;
public int FreeSlots => TotalSlots - UsedSlots;
public TextureAtlasManager(OpenGLGraphicsDevice graphicsDevice, int width, int height, TextureFormat format = TextureFormat.RGBA8) {
Slot = _nextSlot++;
_graphicsDevice = graphicsDevice;
_textureWidth = width;
_textureHeight = height;
_format = format;
TextureArray = (ManagedGLTextureArray)graphicsDevice.CreateTextureArrayInternal(format, width, height, InitialCapacity, TextureParameters.ClampToEdge);
}
public int AddTexture(TextureKey key, byte[] data, PixelFormat? uploadPixelFormat = null, PixelType? uploadPixelType = null) {
if (_textureIndices.TryGetValue(key, out var existingIndex)) {
_refCounts[existingIndex]++;
return existingIndex;
}
int index;
if (_freeSlots.Count > 0) {
index = _freeSlots.Pop();
}
else {
index = _nextIndex++;
if (index >= TextureArray.Size) {
throw new Exception($"Texture atlas is full! {TextureArray.Size} / {_nextIndex} used.");
}
}
try {
TextureArray.UpdateLayer(index, data, uploadPixelFormat, uploadPixelType);
_textureIndices[key] = index;
_refCounts[index] = 1;
return index;
}
catch (Exception) {
if (!_textureIndices.ContainsKey(key)) {
_freeSlots.Push(index);
}
throw;
}
}
public void ReleaseTexture(TextureKey key) {
if (!_textureIndices.TryGetValue(key, out var index)) return;
if (!_refCounts.ContainsKey(index)) return;
_refCounts[index]--;
if (_refCounts[index] <= 0) {
_textureIndices.Remove(key);
_refCounts.Remove(index);
_freeSlots.Push(index);
TextureArray?.RemoveLayer(index);
}
}
public bool HasTexture(TextureKey key) => _textureIndices.ContainsKey(key);
public int GetTextureIndex(TextureKey key) =>
_textureIndices.TryGetValue(key, out var index) ? index : -1;
public void Dispose() {
TextureArray?.Dispose();
_textureIndices.Clear();
_refCounts.Clear();
_freeSlots.Clear();
}
public struct TextureKey : IEquatable {
public uint SurfaceId;
public uint PaletteId;
public StipplingType Stippling;
public bool IsSolid;
public bool Equals(TextureKey other) {
return SurfaceId == other.SurfaceId &&
PaletteId == other.PaletteId &&
Stippling == other.Stippling &&
IsSolid == other.IsSolid;
}
public override bool Equals(object? obj) {
return obj is TextureKey other && Equals(other);
}
public override int GetHashCode() {
return HashCode.Combine(SurfaceId, PaletteId, Stippling, IsSolid);
}
}
}
}