using System.Linq;
using AcDream.App.Rendering;
using AcDream.App.Rendering.Gpu;
using AcDream.App.Tests.Rendering.Gpu;
using Xunit;
namespace AcDream.App.Tests.Rendering;
///
/// Campaign LA gate round 2 (register AD-98 filtering fidelity, 2026-08-15):
/// pins — the GPU-table half
/// of the fixed-canvas jagged-text fix (TextRendererLinearTwinTests pins
/// the renderer-side handle swap this method feeds).
///
///
/// Uses rather than
/// to get a nearest-sampled
/// handle without any DAT fixture — UploadRgba8 never touches the injected
/// IDatReaderWriter, so the simple TextureCache(device, dats)
/// constructor can run with /
/// and no live GPU, matching this suite's existing renderer-test-double idiom.
///
///
public sealed class TextureCacheLinearTwinTests
{
private static (RecordingGpuDevice device, TextureCache cache) Build()
{
var device = new RecordingGpuDevice();
// UploadRgba8/GetOrCreateLinearUiTwin/Dispose never touch the dats
// reference — see the class doc comment.
var cache = new TextureCache(device, dats: null!);
// RecordingGpuDevice's own constructor registers a 1x1 default-white
// placeholder (DefaultTextureSlot) — clear that registration out of the
// recorded call log so each test's assertions only see the
// registrations IT caused. Clear() only wipes the call log, not the
// slot/sampler state, so DefaultTextureSlot itself is untouched.
device.Clear();
return (device, cache);
}
[Fact]
public void UnknownHandle_ReturnsUnchanged()
{
// Chrome/background art (never registered nearest) and any handle this
// cache never saw (including UiTextureTableHandle.None == 0) pass
// through untouched — there is nothing to swap.
(_, TextureCache cache) = Build();
Assert.Equal(0u, cache.GetOrCreateLinearUiTwin(0u));
Assert.Equal(12345u, cache.GetOrCreateLinearUiTwin(12345u));
}
[Fact]
public void NearestHandle_GetsADifferentTwinHandle_SampledLinear()
{
(RecordingGpuDevice device, TextureCache cache) = Build();
byte[] rgba = new byte[4 * 4 * 4];
uint nearestHandle = cache.UploadRgba8(rgba, 4, 4, nearest: true);
uint twinHandle = cache.GetOrCreateLinearUiTwin(nearestHandle);
Assert.NotEqual(0u, twinHandle);
Assert.NotEqual(nearestHandle, twinHandle);
// The SECOND registration recorded against the device (the first is the
// original nearest upload) must carry a linear filter — the whole point
// of the twin.
var registrations = device.OfKind().ToList();
Assert.Equal(2, registrations.Count);
Assert.Equal(GpuFilter.Nearest, registrations[0].Sampler.MinFilter);
Assert.Equal(GpuFilter.Linear, registrations[1].Sampler.MinFilter);
Assert.Equal(GpuFilter.Linear, registrations[1].Sampler.MagFilter);
// Both registrations name the SAME underlying texture — the twin reuses
// the original decoded pixels rather than re-uploading.
Assert.Equal(registrations[0].TextureName, registrations[1].TextureName);
}
[Fact]
public void NearestHandle_RepeatedRequest_ReturnsTheSameCachedTwin()
{
(RecordingGpuDevice device, TextureCache cache) = Build();
byte[] rgba = new byte[4 * 4 * 4];
uint nearestHandle = cache.UploadRgba8(rgba, 4, 4, nearest: true);
uint first = cache.GetOrCreateLinearUiTwin(nearestHandle);
uint second = cache.GetOrCreateLinearUiTwin(nearestHandle);
Assert.Equal(first, second);
// Exactly one twin registration — the second request must not create
// another device-table slot.
Assert.Equal(2, device.OfKind().Count());
}
[Fact]
public void NonNearestUpload_HasNoTwin()
{
// UploadRgba8's default (nearest: false) mirrors chrome/background art:
// it already samples GpuSamplerDescription.WorldRepeat (linear), so it
// never enters the nearest-source table and GetOrCreateLinearUiTwin
// hands the same handle straight back.
(_, TextureCache cache) = Build();
byte[] rgba = new byte[4 * 4 * 4];
uint linearHandle = cache.UploadRgba8(rgba, 4, 4, nearest: false);
Assert.Equal(linearHandle, cache.GetOrCreateLinearUiTwin(linearHandle));
}
[Fact]
public void Dispose_ReleasesTheTwinSlot()
{
(RecordingGpuDevice device, TextureCache cache) = Build();
byte[] rgba = new byte[4 * 4 * 4];
uint nearestHandle = cache.UploadRgba8(rgba, 4, 4, nearest: true);
uint twinHandle = cache.GetOrCreateLinearUiTwin(nearestHandle);
GpuTextureSlot twinSlot = UiTextureTableHandle.ToSlot(twinHandle);
cache.Dispose();
Assert.Contains(
device.OfKind(),
release => release.Slot == twinSlot.Index);
}
}