feat(linux): gate graphical backends and capabilities
This commit is contained in:
parent
07bb1c5a74
commit
11501d52ca
13 changed files with 1924 additions and 32 deletions
513
src/AcDream.App/Platform/GraphicalGlFunctionProbe.cs
Normal file
513
src/AcDream.App/Platform/GraphicalGlFunctionProbe.cs
Normal file
|
|
@ -0,0 +1,513 @@
|
|||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue