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:
Erik 2026-07-29 02:19:53 +02:00
parent b70b9832ff
commit 8a7a0837e1
121 changed files with 1243 additions and 19840 deletions

View file

@ -1,525 +0,0 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using AcDream.App.Rendering;
using Silk.NET.Input;
using Silk.NET.GLFW;
using Silk.NET.OpenGL;
using Silk.NET.Windowing;
namespace AcDream.App.Platform;
internal sealed record GraphicalFramebufferCapabilities(
int RedBits,
int GreenBits,
int BlueBits,
int AlphaBits,
int DepthBits,
int StencilBits,
int SampleBuffers,
int Samples,
bool FramebufferSrgbApi);
internal sealed record GraphicalInputCapabilities(
int KeyboardCount,
int MouseCount,
int GamepadCount,
int JoystickCount);
internal sealed record GraphicalWindowCapabilities(
int LogicalWidth,
int LogicalHeight,
int FramebufferWidth,
int FramebufferHeight,
string MonitorName,
double RefreshRateHz,
bool VSync);
internal sealed record GraphicalAudioCapabilities(
bool Requested,
bool Available,
bool PlaybackSubmitted,
bool DisposalComplete,
string Backend);
internal sealed record GraphicalSmokeLifecycleCapabilities(
int OwnedWindowCount,
int OwnedGlApiCount,
int OwnedInputContextCount,
int OwnedAudioEngineCount,
bool ShutdownComplete);
internal sealed record GraphicalFunctionProbeResult(
bool BindlessTexture,
bool ShaderDrawParameters,
bool MultiDrawIndirect,
bool ShaderStorageBuffer,
bool TimerQuery,
bool SrgbFramebuffer,
bool? PersistentBufferStorage,
IReadOnlyList<string> Failures)
{
internal static GraphicalFunctionProbeResult NotRun { get; } = new(
false,
false,
false,
false,
false,
false,
null,
["active OpenGL function probe did not run"]);
}
internal sealed record GraphicalCapabilityRecord(
DateTimeOffset CapturedAtUtc,
string RuntimeIdentifier,
GraphicalHostOperatingSystem OperatingSystem,
GraphicalDisplayProtocol RequestedDisplayProtocol,
GraphicalDisplayProtocol ActiveDisplayProtocol,
string DisplaySelectionReason,
string WindowBackend,
string WindowBackendVersion,
string GlVendor,
string GlRenderer,
string GlVersion,
string GlslVersion,
int GlMajorVersion,
int GlMinorVersion,
int ContextProfileMask,
int ContextFlags,
bool HasBindlessTexture,
bool HasShaderDrawParameters,
bool HasMultiDrawIndirect,
bool HasShaderStorageBuffer,
bool HasBufferStorage,
bool HasTimerQuery,
int MaximumShaderStorageBufferBindings,
int MaximumUniformBufferBindings,
int MaximumTextureSize,
int MaximumArrayTextureLayers,
int MaximumCombinedTextureImageUnits,
GraphicalFramebufferCapabilities Framebuffer,
GraphicalInputCapabilities Input,
GraphicalWindowCapabilities Window,
GraphicalAudioCapabilities Audio,
GraphicalSmokeLifecycleCapabilities Lifecycle,
IReadOnlyList<string> Extensions,
GraphicalFunctionProbeResult FunctionProbe,
IReadOnlyList<string> SupportFailures)
{
internal bool IsSupported => SupportFailures.Count == 0;
}
internal static class GraphicalCapabilityRequirements
{
internal static IReadOnlyList<string> Evaluate(
GraphicalCapabilityRecord capabilities)
{
ArgumentNullException.ThrowIfNull(capabilities);
var failures = new List<string>();
if (capabilities.GlMajorVersion < 4
|| capabilities.GlMajorVersion == 4
&& capabilities.GlMinorVersion < 3)
{
failures.Add(
"OpenGL 4.3 core is required; the active context reports " +
$"{capabilities.GlMajorVersion}.{capabilities.GlMinorVersion}.");
}
if (!capabilities.HasBindlessTexture)
failures.Add("GL_ARB_bindless_texture is required.");
if (!capabilities.HasShaderDrawParameters)
failures.Add("GL_ARB_shader_draw_parameters is required.");
if (!capabilities.HasMultiDrawIndirect)
failures.Add("multi-draw indirect support is required.");
if (!capabilities.HasShaderStorageBuffer)
failures.Add("shader-storage buffers are required.");
if (!capabilities.HasTimerQuery)
failures.Add("OpenGL timer queries are required.");
if (capabilities.Framebuffer.DepthBits < 24)
{
failures.Add(
"the default framebuffer must provide at least 24 depth bits.");
}
if (capabilities.Framebuffer.StencilBits < 8)
{
failures.Add(
"the default framebuffer must provide at least 8 stencil bits.");
}
if (!capabilities.Framebuffer.FramebufferSrgbApi)
failures.Add("framebuffer sRGB support is required.");
if (capabilities.Input.KeyboardCount < 1)
failures.Add("the graphical input backend exposed no keyboard.");
if (capabilities.Input.MouseCount < 1)
failures.Add("the graphical input backend exposed no mouse.");
if (capabilities.FunctionProbe.Failures.Count != 0)
{
failures.AddRange(
capabilities.FunctionProbe.Failures.Select(
failure => $"OpenGL function probe: {failure}"));
}
else
{
if (!capabilities.FunctionProbe.BindlessTexture)
failures.Add("the bindless texture call probe did not pass.");
if (!capabilities.FunctionProbe.ShaderDrawParameters)
{
failures.Add(
"the shader draw-parameters compile probe did not pass.");
}
if (!capabilities.FunctionProbe.MultiDrawIndirect)
failures.Add("the multi-draw indirect call probe did not pass.");
if (!capabilities.FunctionProbe.ShaderStorageBuffer)
failures.Add("the shader-storage buffer call probe did not pass.");
if (!capabilities.FunctionProbe.TimerQuery)
failures.Add("the timer-query call probe did not pass.");
if (!capabilities.FunctionProbe.SrgbFramebuffer)
failures.Add("the sRGB framebuffer call probe did not pass.");
if (capabilities.HasBufferStorage
&& capabilities.FunctionProbe.PersistentBufferStorage != true)
{
failures.Add(
"GL_ARB_buffer_storage was advertised but the persistent " +
"mapping call probe did not pass.");
}
}
return failures;
}
}
internal static class GraphicalCapabilityProbe
{
private const int GlSampleBuffers = 0x80A8;
private const int GlSamples = 0x80A9;
private const int GlMaxShaderStorageBufferBindings = 0x90DD;
private const int GlMaxUniformBufferBindings = 0x8A2F;
private const int GlMaxCombinedTextureImageUnits = 0x8B4D;
internal static GraphicalCapabilityRecord Capture(
GL gl,
IWindow window,
IInputContext input,
GraphicalHostPlatformServices platform)
{
ArgumentNullException.ThrowIfNull(gl);
ArgumentNullException.ThrowIfNull(window);
ArgumentNullException.ThrowIfNull(input);
ArgumentNullException.ThrowIfNull(platform);
int major = gl.GetInteger(GetPName.MajorVersion);
int minor = gl.GetInteger(GetPName.MinorVersion);
bool openGl43 = major > 4 || major == 4 && minor >= 3;
bool timerQuery =
major > 3
|| major == 3 && minor >= 3
|| gl.IsExtensionPresent("GL_ARB_timer_query");
bool framebufferSrgb =
major > 3
|| major == 3 && minor >= 0
|| gl.IsExtensionPresent("GL_ARB_framebuffer_sRGB")
|| gl.IsExtensionPresent("GL_EXT_framebuffer_sRGB");
IReadOnlyList<string> extensions = ReadExtensions(gl);
IMonitor? monitor = window.Monitor;
Silk.NET.Windowing.VideoMode mode =
monitor?.VideoMode ?? Silk.NET.Windowing.VideoMode.Default;
var framebuffer = window.FramebufferSize;
var logical = window.Size;
var captured = new GraphicalCapabilityRecord(
DateTimeOffset.UtcNow,
platform.RuntimeIdentifier,
platform.OperatingSystem,
platform.WindowBackend.RequestedProtocol,
GlfwNativePlatformProbe.GetActiveProtocol(
platform.OperatingSystem),
platform.WindowBackend.Reason,
"Silk.NET.Windowing.Glfw",
GlfwNativePlatformProbe.GetVersion(
platform.OperatingSystem),
gl.GetStringS(GLEnum.Vendor),
gl.GetStringS(GLEnum.Renderer),
gl.GetStringS(GLEnum.Version),
gl.GetStringS(GLEnum.ShadingLanguageVersion),
major,
minor,
GetInteger(gl, (GetPName)GLEnum.ContextProfileMask),
GetInteger(gl, (GetPName)GLEnum.ContextFlags),
gl.IsExtensionPresent("GL_ARB_bindless_texture"),
gl.IsExtensionPresent("GL_ARB_shader_draw_parameters"),
openGl43
|| gl.IsExtensionPresent("GL_ARB_multi_draw_indirect"),
openGl43
|| gl.IsExtensionPresent("GL_ARB_shader_storage_buffer_object"),
major > 4
|| major == 4 && minor >= 4
|| gl.IsExtensionPresent("GL_ARB_buffer_storage"),
timerQuery,
GetInteger(
gl,
(GetPName)GlMaxShaderStorageBufferBindings),
GetInteger(gl, (GetPName)GlMaxUniformBufferBindings),
GetInteger(gl, GetPName.MaxTextureSize),
GetInteger(gl, GetPName.MaxArrayTextureLayers),
GetInteger(
gl,
(GetPName)GlMaxCombinedTextureImageUnits),
new GraphicalFramebufferCapabilities(
GetFramebufferAttachmentInteger(
gl,
(FramebufferAttachment)GLEnum.BackLeft,
FramebufferAttachmentParameterName.RedSize),
GetFramebufferAttachmentInteger(
gl,
(FramebufferAttachment)GLEnum.BackLeft,
FramebufferAttachmentParameterName.GreenSize),
GetFramebufferAttachmentInteger(
gl,
(FramebufferAttachment)GLEnum.BackLeft,
FramebufferAttachmentParameterName.BlueSize),
GetFramebufferAttachmentInteger(
gl,
(FramebufferAttachment)GLEnum.BackLeft,
FramebufferAttachmentParameterName.AlphaSize),
GetFramebufferAttachmentInteger(
gl,
(FramebufferAttachment)GLEnum.Depth,
FramebufferAttachmentParameterName.DepthSize),
GetFramebufferAttachmentInteger(
gl,
(FramebufferAttachment)GLEnum.Stencil,
FramebufferAttachmentParameterName.StencilSize),
GetInteger(gl, (GetPName)GlSampleBuffers),
GetInteger(gl, (GetPName)GlSamples),
framebufferSrgb),
new GraphicalInputCapabilities(
input.Keyboards.Count,
input.Mice.Count,
input.Gamepads.Count,
input.Joysticks.Count),
new GraphicalWindowCapabilities(
logical.X,
logical.Y,
framebuffer.X,
framebuffer.Y,
monitor?.Name ?? "unknown",
mode.RefreshRate ?? 0,
window.VSync),
new GraphicalAudioCapabilities(
Requested: false,
Available: false,
PlaybackSubmitted: false,
DisposalComplete: true,
Backend: "not requested"),
new GraphicalSmokeLifecycleCapabilities(
OwnedWindowCount: 1,
OwnedGlApiCount: 1,
OwnedInputContextCount: 1,
OwnedAudioEngineCount: 0,
ShutdownComplete: false),
extensions,
GraphicalFunctionProbeResult.NotRun,
[]);
return captured with
{
SupportFailures =
GraphicalCapabilityRequirements.Evaluate(captured),
};
}
internal static GraphicalCapabilityRecord WithFunctionProbe(
GraphicalCapabilityRecord capabilities,
GraphicalFunctionProbeResult functionProbe)
{
ArgumentNullException.ThrowIfNull(capabilities);
ArgumentNullException.ThrowIfNull(functionProbe);
GraphicalCapabilityRecord updated = capabilities with
{
FunctionProbe = functionProbe,
SupportFailures = [],
};
return updated with
{
SupportFailures =
GraphicalCapabilityRequirements.Evaluate(updated),
};
}
private static int GetInteger(GL gl, GetPName name)
{
return GlResourceCommand.Execute(
gl,
$"query OpenGL integer 0x{(uint)name:X}",
() =>
{
gl.GetInteger(name, out int value);
return value;
});
}
private static int GetFramebufferAttachmentInteger(
GL gl,
FramebufferAttachment attachment,
FramebufferAttachmentParameterName name)
{
return GlResourceCommand.Execute(
gl,
$"query default framebuffer attachment {attachment}/{name}",
() =>
{
gl.GetFramebufferAttachmentParameter(
FramebufferTarget.Framebuffer,
attachment,
name,
out int value);
return value;
});
}
private static IReadOnlyList<string> ReadExtensions(GL gl)
{
int count = GetInteger(gl, GetPName.NumExtensions);
var extensions = new string[Math.Max(0, count)];
for (uint index = 0; index < extensions.Length; index++)
extensions[index] = gl.GetStringS(GLEnum.Extensions, index);
Array.Sort(extensions, StringComparer.Ordinal);
return extensions;
}
}
internal static class GraphicalCapabilityGuard
{
internal static GraphicalCapabilityRecord CaptureVerifyAndWrite(
GL gl,
IWindow window,
IInputContext input,
GraphicalHostPlatformServices platform,
string reportPath)
{
GraphicalCapabilityRecord passive =
GraphicalCapabilityProbe.Capture(
gl,
window,
input,
platform);
GraphicalFunctionProbeResult functions =
GraphicalGlFunctionProbe.Run(gl, passive);
GraphicalCapabilityRecord verified =
GraphicalCapabilityProbe.WithFunctionProbe(
passive,
functions);
GraphicalCapabilityReportWriter.Write(reportPath, verified);
return verified;
}
internal static void ThrowIfUnsupported(
GraphicalCapabilityRecord capabilities,
string reportPath)
{
ArgumentNullException.ThrowIfNull(capabilities);
if (!capabilities.IsSupported)
{
throw new NotSupportedException(
FormatUnsupportedMessage(capabilities, reportPath));
}
}
internal static string FormatUnsupportedMessage(
GraphicalCapabilityRecord capabilities,
string reportPath)
{
ArgumentNullException.ThrowIfNull(capabilities);
ArgumentException.ThrowIfNullOrWhiteSpace(reportPath);
return
"acdream's mandatory modern renderer is unsupported by the " +
"active graphical backend.\n" +
$"Platform: {capabilities.RuntimeIdentifier}, " +
$"{capabilities.ActiveDisplayProtocol}, " +
$"{capabilities.GlVendor} / {capabilities.GlRenderer}, " +
$"{capabilities.GlVersion}\n" +
string.Join(
"\n",
capabilities.SupportFailures.Select(
failure => $" - {failure}")) +
$"\nFull capability report: {Path.GetFullPath(reportPath)}";
}
}
internal static class GraphicalCapabilityReportWriter
{
private static readonly JsonSerializerOptions Options = new()
{
WriteIndented = true,
Converters =
{
new JsonStringEnumConverter(),
},
};
internal static void Write(
string path,
GraphicalCapabilityRecord capabilities)
{
ArgumentException.ThrowIfNullOrWhiteSpace(path);
ArgumentNullException.ThrowIfNull(capabilities);
string fullPath = Path.GetFullPath(path);
string? directory = Path.GetDirectoryName(fullPath);
if (!string.IsNullOrEmpty(directory))
Directory.CreateDirectory(directory);
string temporaryPath = fullPath + ".tmp";
File.WriteAllText(
temporaryPath,
JsonSerializer.Serialize(capabilities, Options));
File.Move(temporaryPath, fullPath, overwrite: true);
}
}
internal static unsafe class GlfwNativePlatformProbe
{
private const int GlfwWin32Platform = 0x00060001;
private const int GlfwWaylandPlatform = 0x00060003;
private const int GlfwX11Platform = 0x00060004;
internal static GraphicalDisplayProtocol GetActiveProtocol(
GraphicalHostOperatingSystem operatingSystem)
{
if (operatingSystem == GraphicalHostOperatingSystem.Windows)
return GraphicalDisplayProtocol.Windows;
if (!GraphicalWindowBackendConfigurator.TryGetConfiguredApi(
out Glfw? glfw)
|| glfw is null
|| !glfw.Context.TryGetProcAddress(
"glfwGetPlatform",
out nint export))
{
return GraphicalDisplayProtocol.Unknown;
}
int platform =
((delegate* unmanaged[Cdecl]<int>)export)();
return platform switch
{
GlfwX11Platform => GraphicalDisplayProtocol.X11,
GlfwWaylandPlatform => GraphicalDisplayProtocol.Wayland,
GlfwWin32Platform => GraphicalDisplayProtocol.Windows,
_ => GraphicalDisplayProtocol.Unknown,
};
}
internal static string GetVersion(
GraphicalHostOperatingSystem operatingSystem)
{
if (!GraphicalWindowBackendConfigurator.TryGetConfiguredApi(
out Glfw? glfw)
|| glfw is null)
{
return "unknown";
}
return glfw.GetVersionString() ?? "unknown";
}
}

View file

@ -1,513 +0,0 @@
using AcDream.App.Rendering;
using AcDream.App.Rendering.Wb;
using Silk.NET.OpenGL;
namespace AcDream.App.Platform;
/// <summary>
/// Executes one minimal, ownership-balanced call path for every OpenGL feature
/// the mandatory renderer relies upon. Advertisement alone is insufficient:
/// several Linux driver failures present an extension string while returning
/// a missing entry point, invalid bindless handle, or unusable framebuffer.
/// </summary>
internal static unsafe class GraphicalGlFunctionProbe
{
private const string VertexSource = """
#version 430 core
#extension GL_ARB_shader_draw_parameters : require
void main()
{
float identity =
float(gl_DrawIDARB + gl_BaseInstanceARB) * 0.0;
gl_Position = vec4(identity, 0.0, 0.0, 1.0);
}
""";
private const string FragmentSource = """
#version 430 core
layout(location = 0) out vec4 outColor;
void main()
{
outColor = vec4(1.0);
}
""";
internal static GraphicalFunctionProbeResult Run(
GL gl,
GraphicalCapabilityRecord capabilities)
{
ArgumentNullException.ThrowIfNull(gl);
ArgumentNullException.ThrowIfNull(capabilities);
var failures = new List<string>();
bool bindless = capabilities.HasBindlessTexture
&& Attempt(
"bindless texture handle/residency",
() => ProbeBindless(gl),
failures);
bool shaderDraw = capabilities.HasShaderDrawParameters
&& Attempt(
"shader draw parameters and multi-draw indirect",
() => ProbeShaderDrawAndMultiDraw(gl),
failures);
bool multiDraw = shaderDraw
&& capabilities.HasMultiDrawIndirect;
bool shaderStorage = capabilities.HasShaderStorageBuffer
&& Attempt(
"shader-storage buffer",
() => ProbeShaderStorageBuffer(gl),
failures);
bool timer = capabilities.HasTimerQuery
&& Attempt(
"timer query",
() => ProbeTimerQuery(gl),
failures);
bool srgb = capabilities.Framebuffer.FramebufferSrgbApi
&& Attempt(
"sRGB depth/stencil framebuffer",
() => ProbeSrgbFramebuffer(gl),
failures);
bool? persistent = capabilities.HasBufferStorage
? Attempt(
"persistent coherent buffer storage",
() => ProbePersistentBufferStorage(gl),
failures)
: null;
if (!capabilities.HasBindlessTexture)
failures.Add("GL_ARB_bindless_texture was not advertised.");
if (!capabilities.HasShaderDrawParameters)
failures.Add("GL_ARB_shader_draw_parameters was not advertised.");
if (!capabilities.HasMultiDrawIndirect)
failures.Add("multi-draw indirect was not advertised.");
if (!capabilities.HasShaderStorageBuffer)
failures.Add("shader-storage buffers were not advertised.");
if (!capabilities.HasTimerQuery)
failures.Add("timer queries were not advertised.");
if (!capabilities.Framebuffer.FramebufferSrgbApi)
failures.Add("framebuffer sRGB was not advertised.");
return new GraphicalFunctionProbeResult(
bindless,
shaderDraw,
multiDraw,
shaderStorage,
timer,
srgb,
persistent,
failures);
}
private static bool Attempt(
string name,
Action action,
List<string> failures)
{
try
{
action();
return true;
}
catch (Exception error)
{
failures.Add($"{name}: {error.GetType().Name}: {error.Message}");
return false;
}
}
private static void ProbeBindless(GL gl)
{
if (!BindlessSupport.TryCreate(gl, out BindlessSupport? bindless)
|| bindless is null)
{
throw new NotSupportedException(
"the ARB bindless extension object could not be loaded.");
}
uint texture = GlResourceCommand.CreateTexture(
gl,
"graphical capability bindless texture");
ulong handle = 0;
try
{
byte* pixel = stackalloc byte[4] { 255, 255, 255, 255 };
GlResourceCommand.Execute(
gl,
"initialize graphical capability bindless texture",
() =>
{
gl.BindTexture(TextureTarget.Texture2D, texture);
gl.TexImage2D(
TextureTarget.Texture2D,
0,
InternalFormat.Rgba8,
1,
1,
0,
PixelFormat.Rgba,
PixelType.UnsignedByte,
pixel);
gl.TexParameter(
TextureTarget.Texture2D,
TextureParameterName.TextureMinFilter,
(int)TextureMinFilter.Nearest);
gl.TexParameter(
TextureTarget.Texture2D,
TextureParameterName.TextureMagFilter,
(int)TextureMagFilter.Nearest);
});
handle = bindless.GetResidentHandle(texture);
bindless.MakeNonResident(handle);
handle = 0;
}
finally
{
if (handle != 0)
bindless.MakeNonResident(handle);
gl.BindTexture(TextureTarget.Texture2D, 0);
GlResourceCommand.DeleteTexture(
gl,
texture,
"delete graphical capability bindless texture");
}
}
private static void ProbeShaderDrawAndMultiDraw(GL gl)
{
uint program = ShaderProgramConstruction.Build(
new GlShaderProgramBuildApi(gl),
VertexSource,
FragmentSource);
uint vertexArray = 0;
uint elementBuffer = 0;
uint indirectBuffer = 0;
try
{
vertexArray = GlResourceCommand.CreateName(
gl,
"graphical capability vertex array",
gl.GenVertexArray,
gl.DeleteVertexArray);
elementBuffer = GlResourceCommand.CreateName(
gl,
"graphical capability element buffer",
gl.GenBuffer,
gl.DeleteBuffer);
indirectBuffer = GlResourceCommand.CreateName(
gl,
"graphical capability indirect buffer",
gl.GenBuffer,
gl.DeleteBuffer);
uint* index = stackalloc uint[1] { 0 };
uint* command = stackalloc uint[5]
{
0,
1,
0,
0,
0,
};
GLHelpers.ThrowOnResourceError(
gl,
"execute graphical capability multi-draw indirect call " +
"(precondition)");
gl.UseProgram(program);
gl.BindVertexArray(vertexArray);
gl.BindBuffer(
BufferTargetARB.ElementArrayBuffer,
elementBuffer);
gl.BufferData(
BufferTargetARB.ElementArrayBuffer,
(nuint)sizeof(uint),
index,
BufferUsageARB.StaticDraw);
gl.BindBuffer(
BufferTargetARB.DrawIndirectBuffer,
indirectBuffer);
gl.BufferData(
BufferTargetARB.DrawIndirectBuffer,
(nuint)(sizeof(uint) * 5),
command,
BufferUsageARB.StaticDraw);
gl.MultiDrawElementsIndirect(
PrimitiveType.Triangles,
DrawElementsType.UnsignedInt,
null,
1,
0);
GLHelpers.ThrowOnResourceError(
gl,
"execute graphical capability multi-draw indirect call");
}
finally
{
gl.UseProgram(0);
gl.BindVertexArray(0);
gl.BindBuffer(BufferTargetARB.DrawIndirectBuffer, 0);
if (indirectBuffer != 0)
{
GlResourceCommand.DeleteBuffer(
gl,
indirectBuffer,
"delete graphical capability indirect buffer");
}
if (elementBuffer != 0)
{
GlResourceCommand.DeleteBuffer(
gl,
elementBuffer,
"delete graphical capability element buffer");
}
if (vertexArray != 0)
{
GlResourceCommand.DeleteVertexArray(
gl,
vertexArray,
"delete graphical capability vertex array");
}
GlResourceCommand.DeleteProgram(
gl,
program,
"delete graphical capability shader program");
}
}
private static void ProbeShaderStorageBuffer(GL gl)
{
uint buffer = GlResourceCommand.CreateName(
gl,
"graphical capability shader-storage buffer",
gl.GenBuffer,
gl.DeleteBuffer);
try
{
uint* value = stackalloc uint[1] { 0xACD0_0001u };
GLHelpers.ThrowOnResourceError(
gl,
"bind graphical capability shader-storage buffer " +
"(precondition)");
gl.BindBuffer(BufferTargetARB.ShaderStorageBuffer, buffer);
gl.BufferData(
BufferTargetARB.ShaderStorageBuffer,
(nuint)sizeof(uint),
value,
BufferUsageARB.StaticDraw);
gl.BindBufferBase(
BufferTargetARB.ShaderStorageBuffer,
0,
buffer);
gl.BindBufferBase(
BufferTargetARB.ShaderStorageBuffer,
0,
0);
GLHelpers.ThrowOnResourceError(
gl,
"bind graphical capability shader-storage buffer");
}
finally
{
gl.BindBuffer(BufferTargetARB.ShaderStorageBuffer, 0);
GlResourceCommand.DeleteBuffer(
gl,
buffer,
"delete graphical capability shader-storage buffer");
}
}
private static void ProbeTimerQuery(GL gl)
{
uint query = GlResourceCommand.CreateName(
gl,
"graphical capability timer query",
gl.GenQuery,
gl.DeleteQuery);
try
{
GlResourceCommand.Execute(
gl,
"execute graphical capability timer query",
() =>
{
gl.BeginQuery(QueryTarget.TimeElapsed, query);
gl.EndQuery(QueryTarget.TimeElapsed);
});
}
finally
{
GlResourceCommand.Execute(
gl,
"delete graphical capability timer query",
() => gl.DeleteQuery(query));
}
}
private static void ProbeSrgbFramebuffer(GL gl)
{
uint texture = 0;
uint depthStencil = 0;
uint framebuffer = 0;
try
{
texture = GlResourceCommand.CreateTexture(
gl,
"graphical capability sRGB color texture");
depthStencil = GlResourceCommand.CreateName(
gl,
"graphical capability depth/stencil renderbuffer",
gl.GenRenderbuffer,
gl.DeleteRenderbuffer);
framebuffer = GlResourceCommand.CreateName(
gl,
"graphical capability framebuffer",
gl.GenFramebuffer,
gl.DeleteFramebuffer);
GlResourceCommand.Execute(
gl,
"configure graphical capability sRGB framebuffer",
() =>
{
gl.BindTexture(TextureTarget.Texture2D, texture);
gl.TexImage2D(
TextureTarget.Texture2D,
0,
InternalFormat.Srgb8Alpha8,
2,
2,
0,
PixelFormat.Rgba,
PixelType.UnsignedByte,
null);
gl.BindRenderbuffer(
RenderbufferTarget.Renderbuffer,
depthStencil);
gl.RenderbufferStorage(
RenderbufferTarget.Renderbuffer,
InternalFormat.Depth24Stencil8,
2,
2);
gl.BindFramebuffer(
FramebufferTarget.Framebuffer,
framebuffer);
gl.FramebufferTexture2D(
FramebufferTarget.Framebuffer,
FramebufferAttachment.ColorAttachment0,
TextureTarget.Texture2D,
texture,
0);
gl.FramebufferRenderbuffer(
FramebufferTarget.Framebuffer,
FramebufferAttachment.DepthStencilAttachment,
RenderbufferTarget.Renderbuffer,
depthStencil);
if (gl.CheckFramebufferStatus(
FramebufferTarget.Framebuffer)
!= GLEnum.FramebufferComplete)
{
throw new InvalidOperationException(
"the sRGB depth/stencil framebuffer is incomplete.");
}
gl.Enable(EnableCap.FramebufferSrgb);
gl.Clear(
ClearBufferMask.ColorBufferBit
| ClearBufferMask.DepthBufferBit
| ClearBufferMask.StencilBufferBit);
gl.Disable(EnableCap.FramebufferSrgb);
});
}
finally
{
gl.Disable(EnableCap.FramebufferSrgb);
gl.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
gl.BindRenderbuffer(RenderbufferTarget.Renderbuffer, 0);
gl.BindTexture(TextureTarget.Texture2D, 0);
if (framebuffer != 0)
{
GlResourceCommand.Execute(
gl,
"delete graphical capability framebuffer",
() => gl.DeleteFramebuffer(framebuffer));
}
if (depthStencil != 0)
{
GlResourceCommand.Execute(
gl,
"delete graphical capability depth/stencil renderbuffer",
() => gl.DeleteRenderbuffer(depthStencil));
}
if (texture != 0)
{
GlResourceCommand.DeleteTexture(
gl,
texture,
"delete graphical capability sRGB texture");
}
}
}
private static void ProbePersistentBufferStorage(GL gl)
{
uint buffer = GlResourceCommand.CreateName(
gl,
"graphical capability persistent buffer",
gl.GenBuffer,
gl.DeleteBuffer);
void* mapped = null;
try
{
GlResourceCommand.Execute(
gl,
"allocate graphical capability persistent buffer",
() =>
{
gl.BindBuffer(BufferTargetARB.ArrayBuffer, buffer);
gl.BufferStorage(
GLEnum.ArrayBuffer,
64,
null,
(uint)(
BufferStorageMask.MapWriteBit
| BufferStorageMask.MapPersistentBit
| BufferStorageMask.MapCoherentBit
| BufferStorageMask.DynamicStorageBit));
mapped = gl.MapBufferRange(
BufferTargetARB.ArrayBuffer,
0,
64,
MapBufferAccessMask.WriteBit
| MapBufferAccessMask.PersistentBit
| MapBufferAccessMask.CoherentBit);
if (mapped is null)
{
throw new InvalidOperationException(
"persistent mapping returned a null pointer.");
}
*((byte*)mapped) = 0x5A;
});
}
finally
{
if (mapped is not null)
{
GlResourceCommand.Execute(
gl,
"unmap graphical capability persistent buffer",
() => gl.UnmapBuffer(BufferTargetARB.ArrayBuffer));
}
gl.BindBuffer(BufferTargetARB.ArrayBuffer, 0);
GlResourceCommand.DeleteBuffer(
gl,
buffer,
"delete graphical capability persistent buffer");
}
}
}

View file

@ -187,3 +187,58 @@ internal static class GraphicalWindowBackendConfigurator
DefaultPathResolver.BaseDirectoryResolver);
}
}
/// <summary>
/// Reads the GLFW platform actually selected at runtime (as opposed to the one
/// requested — GLFW's <c>Automatic</c> hint can resolve to either X11 or
/// Wayland). Moved here from the deleted (Campaign V slice V11) raw-GL
/// <c>GraphicalCapabilityRecord.cs</c>: <see cref="VulkanGraphicsContext"/>
/// depends on this for its own capability report, so it survived the GL arm
/// that used to sit alongside it.
/// </summary>
internal static unsafe class GlfwNativePlatformProbe
{
private const int GlfwWin32Platform = 0x00060001;
private const int GlfwWaylandPlatform = 0x00060003;
private const int GlfwX11Platform = 0x00060004;
internal static GraphicalDisplayProtocol GetActiveProtocol(
GraphicalHostOperatingSystem operatingSystem)
{
if (operatingSystem == GraphicalHostOperatingSystem.Windows)
return GraphicalDisplayProtocol.Windows;
if (!GraphicalWindowBackendConfigurator.TryGetConfiguredApi(
out Glfw? glfw)
|| glfw is null
|| !glfw.Context.TryGetProcAddress(
"glfwGetPlatform",
out nint export))
{
return GraphicalDisplayProtocol.Unknown;
}
int platform =
((delegate* unmanaged[Cdecl]<int>)export)();
return platform switch
{
GlfwX11Platform => GraphicalDisplayProtocol.X11,
GlfwWaylandPlatform => GraphicalDisplayProtocol.Wayland,
GlfwWin32Platform => GraphicalDisplayProtocol.Windows,
_ => GraphicalDisplayProtocol.Unknown,
};
}
internal static string GetVersion(
GraphicalHostOperatingSystem operatingSystem)
{
if (!GraphicalWindowBackendConfigurator.TryGetConfiguredApi(
out Glfw? glfw)
|| glfw is null)
{
return "unknown";
}
return glfw.GetVersionString() ?? "unknown";
}
}