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>
135 lines
4.7 KiB
C#
135 lines
4.7 KiB
C#
using AcDream.App.Rendering;
|
|
|
|
namespace AcDream.App.Tests.Rendering;
|
|
|
|
public sealed class ResourceCleanupGroupTests
|
|
{
|
|
[Fact]
|
|
public void CleanupRunsInReverseOrderAndNeverReplaysSuccess()
|
|
{
|
|
var calls = new List<string>();
|
|
var resources = new ResourceCleanupGroup();
|
|
int middleFailures = 1;
|
|
resources.Add("first", () => calls.Add("first"));
|
|
resources.Add("middle", () =>
|
|
{
|
|
calls.Add("middle");
|
|
if (middleFailures-- > 0)
|
|
throw new InvalidOperationException("middle failed");
|
|
});
|
|
resources.Add("last", () => calls.Add("last"));
|
|
|
|
Assert.Throws<AggregateException>(resources.RetryCleanup);
|
|
Assert.Equal(["last", "middle", "first"], calls);
|
|
|
|
resources.RetryCleanup();
|
|
resources.RetryCleanup();
|
|
|
|
Assert.True(resources.IsCleanupComplete);
|
|
Assert.Equal(["last", "middle", "first", "middle"], calls);
|
|
}
|
|
|
|
[Fact]
|
|
public void TransferAllLeavesPublishedResourcesUntouched()
|
|
{
|
|
int releaseCalls = 0;
|
|
var resources = new ResourceCleanupGroup();
|
|
resources.Add("published", () => releaseCalls++);
|
|
|
|
resources.TransferAll();
|
|
resources.RetryCleanup();
|
|
|
|
Assert.True(resources.IsCleanupComplete);
|
|
Assert.Equal(0, releaseCalls);
|
|
Assert.Throws<InvalidOperationException>(() =>
|
|
resources.Add("late", () => { }));
|
|
}
|
|
|
|
[Fact]
|
|
public void FailedConstructionRollbackRetainsOnlyUnreleasedOwnership()
|
|
{
|
|
int releases = 0;
|
|
bool releaseFails = true;
|
|
var resources = new ResourceCleanupGroup();
|
|
resources.Add("retryable", () =>
|
|
{
|
|
releases++;
|
|
if (releaseFails)
|
|
throw new InvalidOperationException("release failed");
|
|
});
|
|
|
|
ResourceConstructionException failure =
|
|
Assert.Throws<ResourceConstructionException>(() =>
|
|
resources.RollbackConstructionAndThrow(
|
|
"construction failed",
|
|
new InvalidOperationException("original failure")));
|
|
|
|
Assert.False(failure.IsCleanupComplete);
|
|
Assert.Equal(1, releases);
|
|
releaseFails = false;
|
|
failure.RetryCleanup();
|
|
failure.RetryCleanup();
|
|
|
|
Assert.True(failure.IsCleanupComplete);
|
|
Assert.Equal(2, releases);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Campaign V slice V4a moved TextRenderer's constructor off raw
|
|
/// VAO/VBO/texture GL names and onto two device-owned resources — a
|
|
/// pipeline and a 1x1 white fill texture — with a catch that disposed
|
|
/// whichever already existed when the other threw.
|
|
///
|
|
/// Slice V6d removed the white texture: the shader gained an untextured
|
|
/// branch that produces what white-times-colour produced, so the fill needs
|
|
/// no texture at all. That leaves exactly ONE owned resource, which is a
|
|
/// stronger property than correct rollback — with nothing to orphan there is
|
|
/// no partial-construction window to get wrong. This test pins that, so
|
|
/// re-growing a second resource without re-growing the rollback fails here.
|
|
/// </summary>
|
|
[Fact]
|
|
public void TextRendererConstructorOwnsExactlyOneDeviceResource()
|
|
{
|
|
string source = File.ReadAllText(Path.Combine(
|
|
FindRepoRoot(),
|
|
"src",
|
|
"AcDream.App",
|
|
"Rendering",
|
|
"TextRenderer.cs"));
|
|
|
|
Assert.Equal(1, CountOccurrences(source, "device.CreatePipeline("));
|
|
Assert.Equal(0, CountOccurrences(source, "device.CreateTexture("));
|
|
Assert.Equal(0, CountOccurrences(source, "device.CreateBuffer("));
|
|
Assert.Equal(0, CountOccurrences(source, "device.CreateSampler("));
|
|
Assert.Equal(0, CountOccurrences(source, "device.RegisterTexture("));
|
|
|
|
// And the one resource is released.
|
|
Assert.Contains("public void Dispose() => _pipeline.Dispose();", source, StringComparison.Ordinal);
|
|
}
|
|
|
|
private static int CountOccurrences(string source, string needle)
|
|
{
|
|
int count = 0;
|
|
for (int i = source.IndexOf(needle, StringComparison.Ordinal);
|
|
i >= 0;
|
|
i = source.IndexOf(needle, i + needle.Length, StringComparison.Ordinal))
|
|
{
|
|
count++;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
private static string FindRepoRoot()
|
|
{
|
|
DirectoryInfo? directory = new(AppContext.BaseDirectory);
|
|
while (directory is not null)
|
|
{
|
|
if (File.Exists(Path.Combine(directory.FullName, "AcDream.slnx")))
|
|
return directory.FullName;
|
|
directory = directory.Parent;
|
|
}
|
|
|
|
throw new DirectoryNotFoundException("Could not find AcDream.slnx.");
|
|
}
|
|
}
|