acdream/src/AcDream.App/Platform/GraphicalGlFunctionProbe.cs

513 lines
17 KiB
C#

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");
}
}
}