513 lines
17 KiB
C#
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");
|
|
}
|
|
}
|
|
}
|