feat(render): Vulkan campaign V11 step 2 — delete the OpenGL backend
Vulkan is the sole, user-signed-off backend (V10 landed) and step 1 already removed ImGui/Studio/DevTools. This step deletes the GL rendering backend itself: every Gpu/Gl/** implementation, the Wb ManagedGL*/GLHelpers/GLSLShader/GLStateScope/RenderStateCache/ BindlessSupport family, Shader/ShaderProgramConstruction/SamplerCache, RenderBootstrap, and RenderFrameGlStateController. GameWindow.cs's Run()/CreateGraphics()/CreateBackbufferReader()/ OnLoad() collapse to their Vulkan-only arm; GameWindowGraphics loses its OpenGlGameWindowGraphics subclass. RuntimeOptions.RenderBackend and RenderBackendKind (incl. the Gl member of GpuBackendKind) are gone — there is nothing left to select between. The five world-draw dual-arm renderers (WbDrawDispatcher, EnvCellRenderer, TerrainModernRenderer, ParticleRenderer, SkyRenderer) and the composition roots (WorldRenderComposition, HostInputCameraComposition, LivePresentationComposition, FrameRootComposition) collapse to their RHI-only arm. GL-only diagnostic properties with a live external reader (DynamicBufferCount and friends) simplify to a documented `=> 0`/no-op rather than disappearing, since the reader is out of this commit's scope. A few GL-flavored mechanisms turned out to be backend-neutral once isolated: GlConstructionCleanupLedger is renamed ResourceConstructionCleanupLedger (exception-chain walking has nothing to do with GL), and GlfwNativePlatformProbe moved out of the otherwise GL-only GraphicalCapabilityRecord.cs into GraphicalWindowBackendSelection.cs before the rest of that file was deleted. Test files with no surviving subject are deleted outright (GraphicalCapabilityRequirementsTests, ShaderProgramConstructionTests, PortalDepthShaderParityTests, TextureCacheBindlessTests, TextRendererFailureSafetyTests, ClipFrameUploadTests, every Gpu/Gl/*Tests, GlTextureOwnershipTests, RenderFrameGlStateControllerTests); others get their dead GL-only members trimmed while their live assertions stay (ClipFrameLayoutTests' MeshClipSsboBinding check now reads GpuBindingModel.StorageClipRegions, the same binding index under its new backend-neutral name; GpuResourceRetirementTransactionTests drops its OpenGLGraphicsDevice-subclassing test double and the two GL queue tests it existed for). EnvCellRendererTests' construction helper now builds a real ObjectMeshManager via VulkanMeshPipelineDevice instead of passing null through a null-forgiving operator, since the RHI constructor never tolerated a null mesh manager and the old GL constructor (which did) is gone. Deferred to the next two steps, deliberately not touched here: the Silk.NET.OpenGL/.Extensions.ARB package references, IMeshPipelineDevice.Gl (WbMeshAdapter's GL? threading stays in place), Chorizite.Core's stale csproj comment (the package itself is still load-bearing — TextureFormat and friends are used well beyond the deleted ManagedGLUniformBuffer), and the CI/gate scripts. Build: `dotnet build AcDream.slnx -c Release` — 0 warnings, 0 errors. Tests: full-solution `dotnet test` green across every project (App.Tests 3937/3940 + 3 skips, Core.Tests 3296/3298 + 2 skips, all others 100%); the 2 App.Tests names that flake under full-suite parallel execution (#250-family, documented pre-existing) pass in isolation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
b70b9832ff
commit
8a7a0837e1
121 changed files with 1243 additions and 19840 deletions
|
|
@ -1,198 +0,0 @@
|
|||
using AcDream.App.Rendering.Gpu.Gl;
|
||||
|
||||
namespace AcDream.App.Tests.Rendering.Gpu.Gl;
|
||||
|
||||
/// <summary>
|
||||
/// The texture table's flush is mapped with GL_MAP_UNSYNCHRONIZED_BIT and
|
||||
/// GL_MAP_INVALIDATE_RANGE_BIT, so a run this tracker reports must contain
|
||||
/// nothing but slots that were actually written. A run that swallowed a clean
|
||||
/// slot in between would let the driver discard a live bindless handle while a
|
||||
/// submitted draw was reading it — invisible in any pixel gate, and exactly the
|
||||
/// class of fault the ring rewrite exists to remove. These tests pin that.
|
||||
/// </summary>
|
||||
public sealed class GlDirtySlotRunsTests
|
||||
{
|
||||
[Fact]
|
||||
public void NothingMarkedYieldsNoRuns()
|
||||
{
|
||||
var runs = new GlDirtySlotRuns(16);
|
||||
|
||||
Assert.False(runs.HasDirtySlots);
|
||||
Assert.False(runs.TryTakeNextRun(out _, out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OneMarkedSlotIsOneRunOfOne()
|
||||
{
|
||||
var runs = new GlDirtySlotRuns(16);
|
||||
runs.Mark(7);
|
||||
|
||||
Assert.True(runs.HasDirtySlots);
|
||||
Assert.True(runs.TryTakeNextRun(out uint first, out uint count));
|
||||
Assert.Equal(7u, first);
|
||||
Assert.Equal(1u, count);
|
||||
Assert.False(runs.TryTakeNextRun(out _, out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConsecutiveSlotsMergeIntoOneRun()
|
||||
{
|
||||
var runs = new GlDirtySlotRuns(16);
|
||||
runs.Mark(4);
|
||||
runs.Mark(5);
|
||||
runs.Mark(6);
|
||||
|
||||
Assert.True(runs.TryTakeNextRun(out uint first, out uint count));
|
||||
Assert.Equal(4u, first);
|
||||
Assert.Equal(3u, count);
|
||||
Assert.False(runs.TryTakeNextRun(out _, out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AGapBetweenMarkedSlotsSplitsTheRuns()
|
||||
{
|
||||
var runs = new GlDirtySlotRuns(64);
|
||||
runs.Mark(5);
|
||||
runs.Mark(50);
|
||||
|
||||
Assert.True(runs.TryTakeNextRun(out uint firstStart, out uint firstCount));
|
||||
Assert.Equal(5u, firstStart);
|
||||
Assert.Equal(1u, firstCount);
|
||||
|
||||
Assert.True(runs.TryTakeNextRun(out uint secondStart, out uint secondCount));
|
||||
Assert.Equal(50u, secondStart);
|
||||
Assert.Equal(1u, secondCount);
|
||||
|
||||
Assert.False(runs.TryTakeNextRun(out _, out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RunsComeBackInAscendingSlotOrderRegardlessOfMarkOrder()
|
||||
{
|
||||
var runs = new GlDirtySlotRuns(64);
|
||||
runs.Mark(40);
|
||||
runs.Mark(1);
|
||||
runs.Mark(41);
|
||||
runs.Mark(20);
|
||||
runs.Mark(0);
|
||||
|
||||
Assert.True(runs.TryTakeNextRun(out uint start, out uint count));
|
||||
Assert.Equal(0u, start);
|
||||
Assert.Equal(2u, count);
|
||||
|
||||
Assert.True(runs.TryTakeNextRun(out start, out count));
|
||||
Assert.Equal(20u, start);
|
||||
Assert.Equal(1u, count);
|
||||
|
||||
Assert.True(runs.TryTakeNextRun(out start, out count));
|
||||
Assert.Equal(40u, start);
|
||||
Assert.Equal(2u, count);
|
||||
|
||||
Assert.False(runs.TryTakeNextRun(out _, out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MarkingTheSameSlotTwiceStillYieldsOneRun()
|
||||
{
|
||||
var runs = new GlDirtySlotRuns(16);
|
||||
runs.Mark(3);
|
||||
runs.Mark(3);
|
||||
|
||||
Assert.True(runs.TryTakeNextRun(out uint start, out uint count));
|
||||
Assert.Equal(3u, start);
|
||||
Assert.Equal(1u, count);
|
||||
Assert.False(runs.TryTakeNextRun(out _, out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DrainingLeavesTheTrackerCleanForTheNextFlush()
|
||||
{
|
||||
var runs = new GlDirtySlotRuns(16);
|
||||
runs.Mark(2);
|
||||
runs.Mark(9);
|
||||
while (runs.TryTakeNextRun(out _, out _))
|
||||
{
|
||||
}
|
||||
|
||||
Assert.False(runs.HasDirtySlots);
|
||||
|
||||
runs.Mark(11);
|
||||
Assert.True(runs.TryTakeNextRun(out uint start, out uint count));
|
||||
Assert.Equal(11u, start);
|
||||
Assert.Equal(1u, count);
|
||||
Assert.False(runs.TryTakeNextRun(out _, out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SlotsMarkedAfterAPartialDrainAreStillReported()
|
||||
{
|
||||
var runs = new GlDirtySlotRuns(32);
|
||||
runs.Mark(4);
|
||||
runs.Mark(20);
|
||||
|
||||
Assert.True(runs.TryTakeNextRun(out uint start, out uint count));
|
||||
Assert.Equal(4u, start);
|
||||
Assert.Equal(1u, count);
|
||||
|
||||
// A draw between two flushes can register another texture.
|
||||
runs.Mark(21);
|
||||
|
||||
Assert.True(runs.TryTakeNextRun(out start, out count));
|
||||
Assert.Equal(20u, start);
|
||||
Assert.Equal(2u, count);
|
||||
Assert.False(runs.TryTakeNextRun(out _, out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TheLastSlotInTheTableIsAddressable()
|
||||
{
|
||||
var runs = new GlDirtySlotRuns(8);
|
||||
runs.Mark(7);
|
||||
|
||||
Assert.True(runs.TryTakeNextRun(out uint start, out uint count));
|
||||
Assert.Equal(7u, start);
|
||||
Assert.Equal(1u, count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MarkingBeyondCapacityThrows()
|
||||
{
|
||||
var runs = new GlDirtySlotRuns(8);
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => runs.Mark(8));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ZeroCapacityIsRejected()
|
||||
{
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => new GlDirtySlotRuns(0));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The whole point, stated as one property: every slot a run covers was
|
||||
/// marked, and every marked slot is covered exactly once.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void EveryReportedSlotWasMarkedAndEveryMarkedSlotIsReportedOnce()
|
||||
{
|
||||
const int Capacity = 200;
|
||||
var random = new Random(20260727);
|
||||
var expected = new HashSet<uint>();
|
||||
var runs = new GlDirtySlotRuns(Capacity);
|
||||
for (int i = 0; i < 60; i++)
|
||||
{
|
||||
uint slot = (uint)random.Next(Capacity);
|
||||
expected.Add(slot);
|
||||
runs.Mark(slot);
|
||||
}
|
||||
|
||||
var reported = new List<uint>();
|
||||
while (runs.TryTakeNextRun(out uint start, out uint count))
|
||||
{
|
||||
for (uint slot = start; slot < start + count; slot++)
|
||||
reported.Add(slot);
|
||||
}
|
||||
|
||||
Assert.Equal(reported.Count, reported.Distinct().Count());
|
||||
Assert.Equal(expected.OrderBy(slot => slot), reported);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
using AcDream.App.Rendering.Gpu;
|
||||
using AcDream.App.Rendering.Gpu.Gl;
|
||||
using Silk.NET.OpenGL;
|
||||
|
||||
namespace AcDream.App.Tests.Rendering.Gpu.Gl;
|
||||
|
||||
public sealed class GlEnumMappingTests
|
||||
{
|
||||
[Fact]
|
||||
public void VertexShapesMatchTheDeclaredComponentCounts()
|
||||
{
|
||||
Assert.Equal(new GlVertexAttributeShape(1, VertexAttribPointerType.Float, false), GlEnumMapping.VertexShapeOf(GpuVertexFormat.Float1));
|
||||
Assert.Equal(new GlVertexAttributeShape(2, VertexAttribPointerType.Float, false), GlEnumMapping.VertexShapeOf(GpuVertexFormat.Float2));
|
||||
Assert.Equal(new GlVertexAttributeShape(3, VertexAttribPointerType.Float, false), GlEnumMapping.VertexShapeOf(GpuVertexFormat.Float3));
|
||||
Assert.Equal(new GlVertexAttributeShape(4, VertexAttribPointerType.Float, false), GlEnumMapping.VertexShapeOf(GpuVertexFormat.Float4));
|
||||
Assert.Equal(new GlVertexAttributeShape(4, VertexAttribPointerType.UnsignedByte, true), GlEnumMapping.VertexShapeOf(GpuVertexFormat.UByte4Normalized));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IndexTypesMapToTheirGlSizesAndEnums()
|
||||
{
|
||||
Assert.Equal(DrawElementsType.UnsignedShort, GlEnumMapping.DrawElementsTypeOf(GpuIndexType.UInt16));
|
||||
Assert.Equal(2, GlEnumMapping.IndexSizeBytesOf(GpuIndexType.UInt16));
|
||||
Assert.Equal(DrawElementsType.UnsignedInt, GlEnumMapping.DrawElementsTypeOf(GpuIndexType.UInt32));
|
||||
Assert.Equal(4, GlEnumMapping.IndexSizeBytesOf(GpuIndexType.UInt32));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BlendFactorsMatchTheDocumentedFormulas()
|
||||
{
|
||||
(BlendingFactor source, BlendingFactor destination) straight = GlEnumMapping.BlendFactorsOf(GpuBlendMode.StraightAlpha);
|
||||
Assert.Equal((BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha), straight);
|
||||
|
||||
(BlendingFactor source, BlendingFactor destination) additive = GlEnumMapping.BlendFactorsOf(GpuBlendMode.Additive);
|
||||
Assert.Equal((BlendingFactor.SrcAlpha, BlendingFactor.One), additive);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BlendNoneHasNoGlFactors()
|
||||
{
|
||||
Assert.Throws<NotSupportedException>(() => GlEnumMapping.BlendFactorsOf(GpuBlendMode.None));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CullNoneHasNoGlCullFaceMode()
|
||||
{
|
||||
Assert.Throws<NotSupportedException>(() => GlEnumMapping.CullFaceModeOf(GpuCullMode.None));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DepthCompareOpsMapOneToOne()
|
||||
{
|
||||
Assert.Equal(DepthFunction.Never, GlEnumMapping.DepthFunctionOf(GpuCompareOp.Never));
|
||||
Assert.Equal(DepthFunction.Less, GlEnumMapping.DepthFunctionOf(GpuCompareOp.Less));
|
||||
Assert.Equal(DepthFunction.Lequal, GlEnumMapping.DepthFunctionOf(GpuCompareOp.LessOrEqual));
|
||||
Assert.Equal(DepthFunction.Equal, GlEnumMapping.DepthFunctionOf(GpuCompareOp.Equal));
|
||||
Assert.Equal(DepthFunction.Greater, GlEnumMapping.DepthFunctionOf(GpuCompareOp.Greater));
|
||||
Assert.Equal(DepthFunction.Gequal, GlEnumMapping.DepthFunctionOf(GpuCompareOp.GreaterOrEqual));
|
||||
Assert.Equal(DepthFunction.Always, GlEnumMapping.DepthFunctionOf(GpuCompareOp.Always));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FilterCombinationsProduceTheExpectedMinFilter()
|
||||
{
|
||||
Assert.Equal(TextureMinFilter.Nearest, GlEnumMapping.MinFilterOf(GpuFilter.Nearest, GpuMipFilter.None));
|
||||
Assert.Equal(TextureMinFilter.Linear, GlEnumMapping.MinFilterOf(GpuFilter.Linear, GpuMipFilter.None));
|
||||
Assert.Equal(TextureMinFilter.LinearMipmapLinear, GlEnumMapping.MinFilterOf(GpuFilter.Linear, GpuMipFilter.Linear));
|
||||
Assert.Equal(TextureMinFilter.NearestMipmapNearest, GlEnumMapping.MinFilterOf(GpuFilter.Nearest, GpuMipFilter.Nearest));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EveryNonNoneEnumValueResolvesForFrontFaceAndWrapMode()
|
||||
{
|
||||
Assert.Equal(FrontFaceDirection.Ccw, GlEnumMapping.FrontFaceDirectionOf(GpuFrontFace.CounterClockwise));
|
||||
Assert.Equal(FrontFaceDirection.CW, GlEnumMapping.FrontFaceDirectionOf(GpuFrontFace.Clockwise));
|
||||
Assert.Equal(TextureWrapMode.Repeat, GlEnumMapping.WrapModeOf(GpuAddressMode.Repeat));
|
||||
Assert.Equal(TextureWrapMode.ClampToEdge, GlEnumMapping.WrapModeOf(GpuAddressMode.ClampToEdge));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
using AcDream.App.Rendering.Gpu;
|
||||
using AcDream.App.Rendering.Gpu.Gl;
|
||||
using Silk.NET.OpenGL;
|
||||
|
||||
namespace AcDream.App.Tests.Rendering.Gpu.Gl;
|
||||
|
||||
public sealed class GlGpuTextureFormatMappingTests
|
||||
{
|
||||
[Fact]
|
||||
public void Rgba8AndItsRenderTargetVariantShareTheSameGlShape()
|
||||
{
|
||||
GlTextureFormatInfo plain = GlGpuTextureFormatMapping.Resolve(GpuTextureFormat.Rgba8Unorm);
|
||||
GlTextureFormatInfo target = GlGpuTextureFormatMapping.Resolve(GpuTextureFormat.Rgba8UnormRenderTarget);
|
||||
|
||||
Assert.Equal(plain, target);
|
||||
Assert.Equal(SizedInternalFormat.Rgba8, plain.SizedInternalFormat);
|
||||
Assert.Equal(PixelFormat.Rgba, plain.UploadPixelFormat);
|
||||
Assert.Equal(PixelType.UnsignedByte, plain.UploadPixelType);
|
||||
Assert.False(plain.IsCompressed);
|
||||
Assert.Equal(4, plain.LayerByteCount(1, 1));
|
||||
Assert.Equal(4 * 16 * 16, plain.LayerByteCount(16, 16));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void R8IsOneByteUncompressed()
|
||||
{
|
||||
GlTextureFormatInfo info = GlGpuTextureFormatMapping.Resolve(GpuTextureFormat.R8Unorm);
|
||||
Assert.Equal(SizedInternalFormat.R8, info.SizedInternalFormat);
|
||||
Assert.False(info.IsCompressed);
|
||||
Assert.Equal(1, info.LayerByteCount(1, 1));
|
||||
Assert.Equal(64, info.LayerByteCount(8, 8));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Bc1IsCompressedFourByFourEightByteBlocks() =>
|
||||
AssertBcFormat(GpuTextureFormat.Bc1Unorm, expectedBlockBytes: 8);
|
||||
|
||||
[Fact]
|
||||
public void Bc2IsCompressedFourByFourSixteenByteBlocks() =>
|
||||
AssertBcFormat(GpuTextureFormat.Bc2Unorm, expectedBlockBytes: 16);
|
||||
|
||||
[Fact]
|
||||
public void Bc3IsCompressedFourByFourSixteenByteBlocks() =>
|
||||
AssertBcFormat(GpuTextureFormat.Bc3Unorm, expectedBlockBytes: 16);
|
||||
|
||||
private static void AssertBcFormat(GpuTextureFormat format, int expectedBlockBytes)
|
||||
{
|
||||
GlTextureFormatInfo info = GlGpuTextureFormatMapping.Resolve(format);
|
||||
Assert.True(info.IsCompressed);
|
||||
Assert.Equal(4, info.BlockDimension);
|
||||
Assert.Equal(expectedBlockBytes, info.BlockOrTexelBytes);
|
||||
|
||||
// A single 4x4 block for a 4x4 texture.
|
||||
Assert.Equal(expectedBlockBytes, info.LayerByteCount(4, 4));
|
||||
// Partial blocks round up: a 5x5 texture needs a 2x2 block grid.
|
||||
Assert.Equal(expectedBlockBytes * 4, info.LayerByteCount(5, 5));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Depth24Stencil8IsAttachmentShapedNotCompressed()
|
||||
{
|
||||
GlTextureFormatInfo info = GlGpuTextureFormatMapping.Resolve(GpuTextureFormat.Depth24Stencil8);
|
||||
Assert.Equal(SizedInternalFormat.Depth24Stencil8, info.SizedInternalFormat);
|
||||
Assert.Equal(PixelFormat.DepthStencil, info.UploadPixelFormat);
|
||||
Assert.Equal(PixelType.UnsignedInt248, info.UploadPixelType);
|
||||
Assert.False(info.IsCompressed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EveryGpuTextureFormatValueResolvesWithoutThrowing()
|
||||
{
|
||||
foreach (GpuTextureFormat format in Enum.GetValues<GpuTextureFormat>())
|
||||
GlGpuTextureFormatMapping.Resolve(format);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,123 +0,0 @@
|
|||
using AcDream.App.Rendering.Gpu.Gl;
|
||||
|
||||
namespace AcDream.App.Tests.Rendering.Gpu.Gl;
|
||||
|
||||
public sealed class GlGpuTimerPoolTests
|
||||
{
|
||||
[Fact]
|
||||
public void UnsupportedPoolReturnsNoOpScopesAndNeverResolves()
|
||||
{
|
||||
var pool = new GlGpuTimerPool(new FakeTimerQueryApi(), isSupported: false);
|
||||
|
||||
using (pool.BeginScope("world"))
|
||||
{
|
||||
}
|
||||
|
||||
Assert.False(pool.TryResolve("world", out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScopeNamesGetIndependentDoubleBufferedQueries()
|
||||
{
|
||||
var api = new FakeTimerQueryApi();
|
||||
var pool = new GlGpuTimerPool(api, isSupported: true);
|
||||
|
||||
using (pool.BeginScope("world"))
|
||||
{
|
||||
}
|
||||
using (pool.BeginScope("ui"))
|
||||
{
|
||||
}
|
||||
|
||||
Assert.Equal(4, api.CreatedQueryCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NestingAScopeBeforeDisposingThePreviousOneThrows()
|
||||
{
|
||||
var pool = new GlGpuTimerPool(new FakeTimerQueryApi(), isSupported: true);
|
||||
pool.BeginScope("world");
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => pool.BeginScope("ui"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AResolvedResultIsPromotedTheNextTimeTheSameScopeBegins()
|
||||
{
|
||||
var api = new FakeTimerQueryApi();
|
||||
var pool = new GlGpuTimerPool(api, isSupported: true);
|
||||
|
||||
using (pool.BeginScope("world"))
|
||||
{
|
||||
}
|
||||
// No result available yet — the first slot hasn't been revisited.
|
||||
Assert.False(pool.TryResolve("world", out _));
|
||||
|
||||
api.MakeNextResultReady(milliseconds: 1.5);
|
||||
using (pool.BeginScope("world"))
|
||||
{
|
||||
}
|
||||
|
||||
Assert.True(pool.TryResolve("world", out double milliseconds));
|
||||
Assert.Equal(1.5, milliseconds);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisposeQueriesDeletesEveryCreatedQuery()
|
||||
{
|
||||
var api = new FakeTimerQueryApi();
|
||||
var pool = new GlGpuTimerPool(api, isSupported: true);
|
||||
using (pool.BeginScope("world"))
|
||||
{
|
||||
}
|
||||
|
||||
pool.DisposeQueries();
|
||||
|
||||
Assert.Equal(2, api.DeletedQueryCount);
|
||||
}
|
||||
|
||||
private sealed class FakeTimerQueryApi : IGlTimerQueryApi
|
||||
{
|
||||
private uint _nextQuery = 1;
|
||||
private bool _nextResultReady;
|
||||
private double _nextResultMilliseconds;
|
||||
|
||||
public int CreatedQueryCount { get; private set; }
|
||||
public int DeletedQueryCount { get; private set; }
|
||||
|
||||
public uint CreateQuery()
|
||||
{
|
||||
CreatedQueryCount++;
|
||||
return _nextQuery++;
|
||||
}
|
||||
|
||||
public void DeleteQuery(uint query) => DeletedQueryCount++;
|
||||
|
||||
public void Begin(uint query)
|
||||
{
|
||||
}
|
||||
|
||||
public void End()
|
||||
{
|
||||
}
|
||||
|
||||
public void MakeNextResultReady(double milliseconds)
|
||||
{
|
||||
_nextResultReady = true;
|
||||
_nextResultMilliseconds = milliseconds;
|
||||
}
|
||||
|
||||
public bool TryGetResult(uint query, out double milliseconds)
|
||||
{
|
||||
if (_nextResultReady)
|
||||
{
|
||||
milliseconds = _nextResultMilliseconds;
|
||||
_nextResultReady = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
milliseconds = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
using AcDream.App.Rendering.Gpu.Gl;
|
||||
|
||||
namespace AcDream.App.Tests.Rendering.Gpu.Gl;
|
||||
|
||||
public sealed class GlPushConstantUniformNamesTests
|
||||
{
|
||||
[Fact]
|
||||
public void EveryDocumentedFieldMapsToItsDocumentedUniformName()
|
||||
{
|
||||
Assert.Equal("uViewProjection", GlPushConstantUniformNames.ByFieldName["ViewProjection"]);
|
||||
Assert.Equal("uDrawIDOffset", GlPushConstantUniformNames.ByFieldName["DrawIdOffset"]);
|
||||
Assert.Equal("uLightingMode", GlPushConstantUniformNames.ByFieldName["LightingMode"]);
|
||||
Assert.Equal("uRenderPass", GlPushConstantUniformNames.ByFieldName["RenderPass"]);
|
||||
Assert.Equal("uLightDebug", GlPushConstantUniformNames.ByFieldName["LightDebug"]);
|
||||
Assert.Equal("uTextureIndexA", GlPushConstantUniformNames.ByFieldName["TextureIndexA"]);
|
||||
Assert.Equal("uTextureIndexB", GlPushConstantUniformNames.ByFieldName["TextureIndexB"]);
|
||||
Assert.Equal("uParamA", GlPushConstantUniformNames.ByFieldName["ParamA"]);
|
||||
Assert.Equal("uParamB", GlPushConstantUniformNames.ByFieldName["ParamB"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TableCoversEveryFieldOnTheSharedStructWithNoStaleEntries()
|
||||
{
|
||||
// This is the drift guard: if a later slice adds/removes/renames a
|
||||
// GpuPushConstants field without updating the mapping table, this
|
||||
// test fails instead of the GL backend silently skipping a uniform.
|
||||
GlPushConstantUniformNames.AssertMapsEveryField();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TableHasExactlyNineEntries()
|
||||
{
|
||||
Assert.Equal(9, GlPushConstantUniformNames.ByFieldName.Count);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,140 +0,0 @@
|
|||
using AcDream.App.Rendering.Gpu;
|
||||
using AcDream.App.Rendering.Gpu.Gl;
|
||||
|
||||
namespace AcDream.App.Tests.Rendering.Gpu.Gl;
|
||||
|
||||
public sealed class GlRenderStateCacheTests
|
||||
{
|
||||
private static GlRenderStateSnapshot Default(uint program = 1) => new(
|
||||
program,
|
||||
GpuBlendMode.None,
|
||||
DepthTest: true,
|
||||
DepthWrite: true,
|
||||
GpuCompareOp.LessOrEqual,
|
||||
GpuCullMode.Back,
|
||||
GpuFrontFace.CounterClockwise,
|
||||
AlphaToCoverage: false,
|
||||
ColorWrite: true,
|
||||
// Slice V6l: the stencil dimension. Off is what every pipeline in the
|
||||
// tree but the portal depth mask asks for.
|
||||
StencilTest: false,
|
||||
GpuStencilState.Default);
|
||||
|
||||
[Fact]
|
||||
public void FirstApplyReportsEveryDimensionChanged()
|
||||
{
|
||||
var cache = new GlRenderStateCache();
|
||||
GlRenderStateChanges changes = cache.Apply(Default());
|
||||
Assert.Equal(GlRenderStateChanges.All, changes);
|
||||
Assert.True(changes.AnyChange);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReapplyingTheIdenticalSnapshotReportsNoChanges()
|
||||
{
|
||||
var cache = new GlRenderStateCache();
|
||||
GlRenderStateSnapshot snapshot = Default();
|
||||
cache.Apply(snapshot);
|
||||
|
||||
GlRenderStateChanges changes = cache.Apply(snapshot);
|
||||
|
||||
Assert.False(changes.AnyChange);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ChangingOnlyCullModeReportsOnlyThatDimension()
|
||||
{
|
||||
var cache = new GlRenderStateCache();
|
||||
cache.Apply(Default());
|
||||
|
||||
GlRenderStateChanges changes = cache.Apply(Default() with { Cull = GpuCullMode.None });
|
||||
|
||||
Assert.True(changes.Cull);
|
||||
Assert.True(changes.AnyChange);
|
||||
Assert.False(changes.Program);
|
||||
Assert.False(changes.Blend);
|
||||
Assert.False(changes.DepthTest);
|
||||
Assert.False(changes.DepthWrite);
|
||||
Assert.False(changes.DepthCompare);
|
||||
Assert.False(changes.FrontFace);
|
||||
Assert.False(changes.AlphaToCoverage);
|
||||
Assert.False(changes.ColorWrite);
|
||||
Assert.False(changes.StencilTest);
|
||||
Assert.False(changes.Stencil);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ChangingOnlyTheStencilValuesReportsOnlyThatDimension()
|
||||
{
|
||||
// Slice V6l. #117's portal punch changes compare, ops, reference and
|
||||
// masks between its mark pass and its punch pass while the test stays
|
||||
// enabled, so the two stencil dimensions have to move independently.
|
||||
var cache = new GlRenderStateCache();
|
||||
cache.Apply(Default() with { StencilTest = true });
|
||||
|
||||
GlRenderStateChanges changes = cache.Apply(Default() with
|
||||
{
|
||||
StencilTest = true,
|
||||
Stencil = GpuStencilState.Default with
|
||||
{
|
||||
Compare = GpuCompareOp.Equal,
|
||||
Pass = GpuStencilOp.Zero,
|
||||
Reference = 1,
|
||||
},
|
||||
});
|
||||
|
||||
Assert.True(changes.Stencil);
|
||||
Assert.False(changes.StencilTest);
|
||||
Assert.False(changes.DepthTest);
|
||||
Assert.False(changes.ColorWrite);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ChangingTheProgramReportsProgramChangedEvenWhenEveryOtherFieldMatches()
|
||||
{
|
||||
var cache = new GlRenderStateCache();
|
||||
cache.Apply(Default(program: 1));
|
||||
|
||||
GlRenderStateChanges changes = cache.Apply(Default(program: 2));
|
||||
|
||||
Assert.True(changes.Program);
|
||||
Assert.False(changes.Blend);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResetForcesTheNextApplyToReportEveryDimensionAgain()
|
||||
{
|
||||
var cache = new GlRenderStateCache();
|
||||
GlRenderStateSnapshot snapshot = Default();
|
||||
cache.Apply(snapshot);
|
||||
|
||||
cache.Reset();
|
||||
GlRenderStateChanges changes = cache.Apply(snapshot);
|
||||
|
||||
Assert.Equal(GlRenderStateChanges.All, changes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MultipleDimensionsChangingAreAllReported()
|
||||
{
|
||||
var cache = new GlRenderStateCache();
|
||||
cache.Apply(Default());
|
||||
|
||||
GlRenderStateChanges changes = cache.Apply(Default() with
|
||||
{
|
||||
Blend = GpuBlendMode.StraightAlpha,
|
||||
DepthWrite = false,
|
||||
FrontFace = GpuFrontFace.Clockwise,
|
||||
});
|
||||
|
||||
Assert.True(changes.Blend);
|
||||
Assert.True(changes.DepthWrite);
|
||||
Assert.True(changes.FrontFace);
|
||||
Assert.False(changes.Cull);
|
||||
Assert.False(changes.DepthTest);
|
||||
Assert.False(changes.DepthCompare);
|
||||
Assert.False(changes.AlphaToCoverage);
|
||||
Assert.False(changes.ColorWrite);
|
||||
Assert.False(changes.Program);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,182 +0,0 @@
|
|||
using AcDream.App.Rendering.Gpu.Gl;
|
||||
|
||||
namespace AcDream.App.Tests.Rendering.Gpu.Gl;
|
||||
|
||||
public sealed class GlRingBufferStateTests
|
||||
{
|
||||
[Fact]
|
||||
public void FirstAllocationStartsAtZero()
|
||||
{
|
||||
var state = new GlRingBufferState(1024);
|
||||
uint offset = state.Allocate(64, alignmentBytes: 16);
|
||||
Assert.Equal(0u, offset);
|
||||
Assert.Equal(64u, state.AllocatedBytes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SubsequentAllocationsAlignUpToTheRequestedBoundary()
|
||||
{
|
||||
var state = new GlRingBufferState(1024);
|
||||
state.Allocate(12, alignmentBytes: 4);
|
||||
uint second = state.Allocate(64, alignmentBytes: 256);
|
||||
Assert.Equal(0u, second % 256);
|
||||
Assert.True(second >= 12);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllocationsMergeIntoOneDirtyRange()
|
||||
{
|
||||
var state = new GlRingBufferState(1024);
|
||||
state.Allocate(16, alignmentBytes: 4);
|
||||
state.Allocate(32, alignmentBytes: 4);
|
||||
|
||||
(int start, int length) = state.TakeDirtyRange();
|
||||
Assert.Equal(0, start);
|
||||
Assert.Equal(48, length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TakingTheDirtyRangeClearsItUntilTheNextWrite()
|
||||
{
|
||||
var state = new GlRingBufferState(1024);
|
||||
state.Allocate(16, alignmentBytes: 4);
|
||||
state.TakeDirtyRange();
|
||||
|
||||
(int start, int length) = state.TakeDirtyRange();
|
||||
Assert.Equal(0, start);
|
||||
Assert.Equal(0, length);
|
||||
Assert.False(state.HasDirtyBytes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WritesAfterAFlushExtendANewDirtyRangeWithoutRewindingTheCursor()
|
||||
{
|
||||
var state = new GlRingBufferState(1024);
|
||||
state.Allocate(16, alignmentBytes: 4);
|
||||
state.TakeDirtyRange();
|
||||
|
||||
uint secondOffset = state.Allocate(8, alignmentBytes: 4);
|
||||
Assert.Equal(16u, secondOffset);
|
||||
|
||||
(int start, int length) = state.TakeDirtyRange();
|
||||
Assert.Equal(16, start);
|
||||
Assert.Equal(8, length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The precondition for issuing the ring's writes with
|
||||
/// GL_MAP_UNSYNCHRONIZED_BIT: within one frame, no flush may cover a byte an
|
||||
/// earlier flush already handed to the GPU. This walks a frame's worth of
|
||||
/// mixed-alignment allocations and asserts the ranges are disjoint and
|
||||
/// ascending.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void SuccessiveDirtyRangesWithinAFrameNeverOverlap()
|
||||
{
|
||||
var state = new GlRingBufferState(64 * 1024);
|
||||
int previousEnd = 0;
|
||||
|
||||
foreach (int size in new[] { 100, 4, 1024, 36, 7, 512, 3 })
|
||||
{
|
||||
state.Allocate(size, alignmentBytes: 256);
|
||||
state.Allocate(size, alignmentBytes: 4);
|
||||
|
||||
(int start, int length) = state.TakeDirtyRange();
|
||||
Assert.True(
|
||||
start >= previousEnd,
|
||||
$"flush [{start}, {start + length}) reaches below the already-flushed {previousEnd} bytes");
|
||||
previousEnd = start + length;
|
||||
Assert.Equal(previousEnd, state.FlushedEndBytes);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AWriteBelowTheFlushedHighWaterMarkIsRefused()
|
||||
{
|
||||
var state = new GlRingBufferState(1024);
|
||||
state.Allocate(128, alignmentBytes: 4);
|
||||
state.TakeDirtyRange();
|
||||
|
||||
Assert.Equal(128, state.FlushedEndBytes);
|
||||
InvalidOperationException error = Assert.Throws<InvalidOperationException>(
|
||||
() => state.MarkDirty(64, 96));
|
||||
Assert.Contains("GL_MAP_UNSYNCHRONIZED_BIT", error.Message, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FlushedHighWaterMarkOnlyAdvancesWhenSomethingWasFlushed()
|
||||
{
|
||||
var state = new GlRingBufferState(1024);
|
||||
state.Allocate(48, alignmentBytes: 4);
|
||||
state.TakeDirtyRange();
|
||||
Assert.Equal(48, state.FlushedEndBytes);
|
||||
|
||||
// A draw with nothing newly written must not move the mark, or the next
|
||||
// allocation's guard would compare against a number no flush produced.
|
||||
state.TakeDirtyRange();
|
||||
Assert.Equal(48, state.FlushedEndBytes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResetClearsTheFlushedHighWaterMarkSoTheSlotIsWritableAgain()
|
||||
{
|
||||
var state = new GlRingBufferState(1024);
|
||||
state.Allocate(256, alignmentBytes: 4);
|
||||
state.TakeDirtyRange();
|
||||
Assert.Equal(256, state.FlushedEndBytes);
|
||||
|
||||
// BeginFrame has waited on this slot's fence by the time Reset runs, so
|
||||
// the whole slot is writable from zero again.
|
||||
state.Reset();
|
||||
|
||||
Assert.Equal(0, state.FlushedEndBytes);
|
||||
Assert.Equal(0u, state.Allocate(64, alignmentBytes: 4));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResetRewindsTheCursorAndClearsDirtyState()
|
||||
{
|
||||
var state = new GlRingBufferState(1024);
|
||||
state.Allocate(64, alignmentBytes: 4);
|
||||
|
||||
state.Reset();
|
||||
|
||||
Assert.Equal(0u, state.AllocatedBytes);
|
||||
Assert.False(state.HasDirtyBytes);
|
||||
Assert.Equal(0u, state.Allocate(4, alignmentBytes: 4));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OverCapacityRequestThrowsRatherThanTruncating()
|
||||
{
|
||||
var state = new GlRingBufferState(64);
|
||||
Assert.Throws<InvalidOperationException>(() => state.Allocate(128, alignmentBytes: 4));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AlignmentPushingPastCapacityAlsoThrows()
|
||||
{
|
||||
var state = new GlRingBufferState(64);
|
||||
state.Allocate(60, alignmentBytes: 4);
|
||||
Assert.Throws<InvalidOperationException>(() => state.Allocate(8, alignmentBytes: 4));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0u, 256u, 0u)]
|
||||
[InlineData(1u, 256u, 256u)]
|
||||
[InlineData(255u, 256u, 256u)]
|
||||
[InlineData(256u, 256u, 256u)]
|
||||
[InlineData(10u, 1u, 10u)]
|
||||
public void AlignUpMatchesStandardAlignmentArithmetic(uint value, uint alignment, uint expected)
|
||||
{
|
||||
Assert.Equal(expected, GlRingBufferState.AlignUp(value, alignment));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ZeroByteAllocationDoesNotMarkAnythingDirty()
|
||||
{
|
||||
var state = new GlRingBufferState(1024);
|
||||
state.Allocate(0, alignmentBytes: 4);
|
||||
Assert.False(state.HasDirtyBytes);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
using AcDream.App.Rendering.Gpu.Gl;
|
||||
|
||||
namespace AcDream.App.Tests.Rendering.Gpu.Gl;
|
||||
|
||||
public sealed class GlTextureSlotAllocatorTests
|
||||
{
|
||||
[Fact]
|
||||
public void AllocationsBumpSequentiallyFromZero()
|
||||
{
|
||||
var allocator = new GlTextureSlotAllocator(capacity: 4);
|
||||
Assert.Equal(0u, allocator.Allocate());
|
||||
Assert.Equal(1u, allocator.Allocate());
|
||||
Assert.Equal(2u, allocator.Allocate());
|
||||
Assert.Equal(3, allocator.LiveCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExhaustingCapacityThrows()
|
||||
{
|
||||
var allocator = new GlTextureSlotAllocator(capacity: 2);
|
||||
allocator.Allocate();
|
||||
allocator.Allocate();
|
||||
Assert.Throws<InvalidOperationException>(() => allocator.Allocate());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReleasedSlotsAreReusedBeforeBumpingFurther()
|
||||
{
|
||||
var allocator = new GlTextureSlotAllocator(capacity: 4);
|
||||
uint first = allocator.Allocate();
|
||||
allocator.Allocate();
|
||||
|
||||
allocator.Release(first);
|
||||
Assert.Equal(1, allocator.LiveCount);
|
||||
|
||||
uint reused = allocator.Allocate();
|
||||
Assert.Equal(first, reused);
|
||||
Assert.Equal(2, allocator.LiveCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReleasingAnUnallocatedSlotThrows()
|
||||
{
|
||||
var allocator = new GlTextureSlotAllocator(capacity: 4);
|
||||
allocator.Allocate();
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => allocator.Release(3));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FreeingThenExhaustingTheBumpRangeStillThrowsPastCapacity()
|
||||
{
|
||||
var allocator = new GlTextureSlotAllocator(capacity: 2);
|
||||
uint first = allocator.Allocate();
|
||||
allocator.Allocate();
|
||||
allocator.Release(first);
|
||||
allocator.Allocate();
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => allocator.Allocate());
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue