using System;
using System.Linq;
using AcDream.App.Rendering;
using AcDream.App.Rendering.Gpu;
using AcDream.App.Tests.Rendering.Gpu;
namespace AcDream.App.Tests.Rendering;
///
/// Campaign V slice V6i-2: the composite array pool's backend-neutral arm.
///
/// Plan §5.5.12 item 1 recorded that ICompositeTextureArrayBackend
/// "is already a seam and takes an RHI backend directly", which is why this arm
/// is four small methods rather than a port. What is worth pinning is the
/// contract the cache above depends on: create returns a resource whose slot is
/// live, the release order is entry-then-image, and a resource made by one
/// backend is never handed to the other.
///
public sealed class RhiCompositeTextureArrayBackendTests
{
private static byte[] Rgba(int width, int height) => new byte[width * height * 4];
[Fact]
public void CreateProducesASingleLevelArrayRegisteredIntoTheTable()
{
using var device = new RecordingGpuDevice();
var backend = new RhiCompositeTextureArrayBackend(device);
CompositeTextureArrayResource resource = backend.Create(32, 32, 8);
Assert.True(resource.Slot.IsAssigned);
Assert.Equal(32 * 32 * 4 * 8, resource.Bytes);
// The GL identity fields are meaningless on this arm and say so.
Assert.Equal(0u, resource.Name);
Assert.Equal(0ul, resource.Handle);
RecordingGpuTexture image = Assert.IsType(resource.Image);
Assert.Equal(GpuTextureKind.Texture2DArray, image.Kind);
Assert.Equal(GpuTextureFormat.Rgba8Unorm, image.Format);
Assert.Equal(8, image.LayerCount);
// Composites are the surfaces retail releases the moment they are built;
// a mip chain would be paid for nothing.
Assert.Equal(1, image.MipLevelCount);
}
[Fact]
public void UploadWritesLevelZeroOfTheNamedLayer()
{
using var device = new RecordingGpuDevice();
var backend = new RhiCompositeTextureArrayBackend(device);
CompositeTextureArrayResource resource = backend.Create(32, 32, 4);
backend.Upload(resource, 3, Rgba(32, 32));
RecordingGpuTexture image = Assert.IsType(resource.Image);
Assert.Equal([(0, 3, 32 * 32 * 4)], image.Uploads);
}
[Fact]
public void ReleaseRetiresTheTableEntryBeforeTheImage()
{
using var device = new RecordingGpuDevice();
var backend = new RhiCompositeTextureArrayBackend(device);
int before = device.LiveTextureSlotCount;
CompositeTextureArrayResource resource = backend.Create(32, 32, 4);
Assert.Equal(before + 1, device.LiveTextureSlotCount);
backend.MakeNonResident(resource);
Assert.Equal(before, device.LiveTextureSlotCount);
Assert.False(Assert.IsType(resource.Image).IsDisposed);
backend.Delete(resource);
Assert.True(Assert.IsType(resource.Image).IsDisposed);
}
///
/// A GL-made resource reaching this backend is a composition error, not a
/// runtime condition — and the message says which backend owns it rather
/// than dereferencing null.
///
[Fact]
public void AGlResourceIsRefusedRatherThanDereferenced()
{
using var device = new RecordingGpuDevice();
var backend = new RhiCompositeTextureArrayBackend(device);
var foreign = new CompositeTextureArrayResource
{
Name = 7,
Handle = 0xDEAD,
Slot = new GpuTextureSlot(3),
Width = 32,
Height = 32,
Capacity = 1,
Bytes = 32 * 32 * 4,
};
Assert.Throws(() => backend.Upload(foreign, 0, Rgba(32, 32)));
Assert.Throws(() => backend.Delete(foreign));
}
///
/// The cache caps every array at 64 layers, so the layer ceiling this
/// backend reports is never the binding constraint — which is what lets it
/// report Vulkan's guaranteed minimum instead of a capability field the
/// pinned record does not carry.
///
[Fact]
public void TheReportedLayerCeilingExceedsTheCachesOwnCap()
{
using var device = new RecordingGpuDevice();
var backend = new RhiCompositeTextureArrayBackend(device);
Assert.True(backend.MaximumArrayLayers >= CompositeTextureArrayCache.MaximumLayersPerArray);
}
}