Rework OpenGL version support checking to be more robust, slightly rework OpenGL context creation request (they can now explicitly request the core profile)

This commit is contained in:
CasualPokePlayer 2023-07-31 22:32:30 -07:00
parent a2ea86fae0
commit 67e5505899
8 changed files with 105 additions and 57 deletions

View File

@ -38,6 +38,8 @@ namespace BizHawk.Bizware.Graphics
{ {
public EDispMethod DispMethodEnum => EDispMethod.OpenGL; public EDispMethod DispMethodEnum => EDispMethod.OpenGL;
private static readonly bool _supportsOpenGL3 = OpenGLVersion.SupportsVersion(3, 0);
private readonly GL GL; private readonly GL GL;
private readonly ExtFramebufferObject EXT; private readonly ExtFramebufferObject EXT;
@ -50,15 +52,17 @@ namespace BizHawk.Bizware.Graphics
// this IGL either requires at least OpenGL 3.0, or OpenGL 2.0 + the EXT_framebuffer_object or ARB_framebuffer_object extension present // this IGL either requires at least OpenGL 3.0, or OpenGL 2.0 + the EXT_framebuffer_object or ARB_framebuffer_object extension present
private static readonly Lazy<bool> _available = new(() => private static readonly Lazy<bool> _available = new(() =>
{ {
switch (SDL2OpenGLContext.Version) if (_supportsOpenGL3)
{ {
case >= 300: return true;
return true;
case < 200:
return false;
} }
using (new SDL2OpenGLContext(2, 0, false)) if (!OpenGLVersion.SupportsVersion(2, 0))
{
return false;
}
using (new SDL2OpenGLContext(2, 0, false, false))
{ {
using var gl = GL.GetApi(SDL2OpenGLContext.GetGLProcAddress); using var gl = GL.GetApi(SDL2OpenGLContext.GetGLProcAddress);
return gl.IsExtensionPresent("EXT_framebuffer_object") || gl.IsExtensionPresent("ARB_framebuffer_object"); return gl.IsExtensionPresent("EXT_framebuffer_object") || gl.IsExtensionPresent("ARB_framebuffer_object");
@ -77,9 +81,9 @@ namespace BizHawk.Bizware.Graphics
GL = GL.GetApi(SDL2OpenGLContext.GetGLProcAddress); GL = GL.GetApi(SDL2OpenGLContext.GetGLProcAddress);
// might need to use EXT if < OpenGL 3.0 and ARB_framebuffer_object is unavailable // might need to use EXT if < OpenGL 3.0 and ARB_framebuffer_object is unavailable
if (SDL2OpenGLContext.Version < 300) if (!_supportsOpenGL3)
{ {
using (new SDL2OpenGLContext(2, 0, false)) using (new SDL2OpenGLContext(2, 0, false, false))
{ {
// ARB_framebuffer_object entrypoints are identical to standard OpenGL 3.0 ones // ARB_framebuffer_object entrypoints are identical to standard OpenGL 3.0 ones
// EXT_framebuffer_object has differently named entrypoints so needs a separate object // EXT_framebuffer_object has differently named entrypoints so needs a separate object
@ -166,7 +170,7 @@ namespace BizHawk.Bizware.Graphics
public IGraphicsControl Internal_CreateGraphicsControl() public IGraphicsControl Internal_CreateGraphicsControl()
{ {
var ret = new OpenGLControl(ContextChangeCallback); var ret = new OpenGLControl(_supportsOpenGL3, ContextChangeCallback);
ret.CreateControl(); // DisplayManager relies on this context being active for creating the GuiRenderer ret.CreateControl(); // DisplayManager relies on this context being active for creating the GuiRenderer
return ret; return ret;
} }

View File

@ -8,6 +8,7 @@ namespace BizHawk.Bizware.Graphics
{ {
internal class OpenGLControl : Control, IGraphicsControl internal class OpenGLControl : Control, IGraphicsControl
{ {
private readonly bool _openGL3;
private readonly Action _contextChangeCallback; private readonly Action _contextChangeCallback;
public SDL2OpenGLContext Context { get; private set; } public SDL2OpenGLContext Context { get; private set; }
@ -18,8 +19,9 @@ namespace BizHawk.Bizware.Graphics
set => throw new NotImplementedException(); set => throw new NotImplementedException();
} }
public OpenGLControl(Action contextChangeCallback) public OpenGLControl(bool openGL3, Action contextChangeCallback)
{ {
_openGL3 = openGL3;
_contextChangeCallback = contextChangeCallback; _contextChangeCallback = contextChangeCallback;
// according to OpenTK, these are the styles we want to set // according to OpenTK, these are the styles we want to set
@ -52,7 +54,7 @@ namespace BizHawk.Bizware.Graphics
protected override void OnHandleCreated(EventArgs e) protected override void OnHandleCreated(EventArgs e)
{ {
base.OnHandleCreated(e); base.OnHandleCreated(e);
Context = new(Handle, 2, 0, false); Context = new(Handle, _openGL3 ? 3 : 2, 0, false, false);
_contextChangeCallback(); _contextChangeCallback();
} }

View File

@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using BizHawk.Common.CollectionExtensions;
using Silk.NET.OpenGL.Legacy;
using static SDL2.SDL;
namespace BizHawk.Bizware.Graphics
{
/// <summary>
/// Wraps checking OpenGL versions
/// </summary>
public static class OpenGLVersion
{
// TODO: make this a ref struct once we're c# 10 (parameterless struct ctor)
private class SavedOpenGLContext : IDisposable
{
private readonly IntPtr _sdlWindow, _glContext;
public SavedOpenGLContext()
{
_sdlWindow = SDL_GL_GetCurrentWindow();
_glContext = SDL_GL_GetCurrentContext();
}
public void Dispose()
{
_ = SDL_GL_MakeCurrent(_sdlWindow, _glContext);
}
}
private static readonly IDictionary<int, bool> _glSupport = new Dictionary<int, bool>();
private static int PackGLVersion(int major, int minor)
=> major * 10 + minor;
private static bool CheckVersion(int requestedMajor, int requestedMinor)
{
using (new SavedOpenGLContext())
{
try
{
using (new SDL2OpenGLContext(requestedMajor, requestedMinor, true, false))
{
using var gl = GL.GetApi(SDL2OpenGLContext.GetGLProcAddress);
var versionString = gl.GetStringS(StringName.Version);
var versionParts = versionString!.Split('.');
var major = int.Parse(versionParts[0]);
var minor = int.Parse(versionParts[1][0].ToString());
return PackGLVersion(major, minor) >= PackGLVersion(requestedMajor, requestedMinor);
}
}
catch
{
return false;
}
}
}
public static bool SupportsVersion(int major, int minor)
=> _glSupport.GetValueOrPut(PackGLVersion(major, minor),
static version => CheckVersion(version / 10, version % 10));
}
}

View File

@ -44,35 +44,10 @@ namespace BizHawk.Bizware.Graphics
SDL_SetHint(SDL_HINT_WINDOWS_ENABLE_MESSAGELOOP, "0"); SDL_SetHint(SDL_HINT_WINDOWS_ENABLE_MESSAGELOOP, "0");
} }
private static readonly Lazy<int> _version = new(() =>
{
var prevWindow = SDL_GL_GetCurrentWindow();
var prevContext = SDL_GL_GetCurrentContext();
try
{
using (new SDL2OpenGLContext(2, 0, false))
{
using var gl = GL.GetApi(GetGLProcAddress);
var versionString = gl.GetStringS(StringName.Version);
var versionParts = versionString!.Split('.');
var major = int.Parse(versionParts[0]);
var minor = int.Parse(versionParts[1][0].ToString());
return major * 100 + minor * 10;
}
}
finally
{
SDL_GL_MakeCurrent(prevWindow, prevContext);
}
});
public static int Version => _version.Value;
private IntPtr _sdlWindow; private IntPtr _sdlWindow;
private IntPtr _glContext; private IntPtr _glContext;
private void CreateContext(int majorVersion, int minorVersion, bool forwardCompatible) private void CreateContext(int majorVersion, int minorVersion, bool coreProfile, bool forwardCompatible)
{ {
if (SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_CONTEXT_MAJOR_VERSION, majorVersion) != 0) if (SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_CONTEXT_MAJOR_VERSION, majorVersion) != 0)
{ {
@ -84,17 +59,16 @@ namespace BizHawk.Bizware.Graphics
throw new($"Could not set GL Minor Version! SDL Error: {SDL_GetError()}"); throw new($"Could not set GL Minor Version! SDL Error: {SDL_GetError()}");
} }
// TODO: Debug flag / debug callback with DEBUG build
if (SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_CONTEXT_FLAGS, forwardCompatible if (SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_CONTEXT_FLAGS, forwardCompatible
? (int)SDL_GLcontext.SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG : 0) != 0) ? (int)SDL_GLcontext.SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG : 0) != 0)
{ {
throw new($"Could not set GL Context Flags! SDL Error: {SDL_GetError()}"); throw new($"Could not set GL Context Flags! SDL Error: {SDL_GetError()}");
} }
// if we're requesting OpenGL 3.3+, get the core profile var profile = coreProfile
// profiles don't exist otherwise
var profile = majorVersion * 10 + minorVersion >= 33
? SDL_GLprofile.SDL_GL_CONTEXT_PROFILE_CORE ? SDL_GLprofile.SDL_GL_CONTEXT_PROFILE_CORE
: 0; : SDL_GLprofile.SDL_GL_CONTEXT_PROFILE_COMPATIBILITY;
if (SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_CONTEXT_PROFILE_MASK, profile) != 0) if (SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_CONTEXT_PROFILE_MASK, profile) != 0)
{ {
@ -108,7 +82,7 @@ namespace BizHawk.Bizware.Graphics
} }
} }
public SDL2OpenGLContext(IntPtr nativeWindowhandle, int majorVersion, int minorVersion, bool forwardCompatible) public SDL2OpenGLContext(IntPtr nativeWindowhandle, int majorVersion, int minorVersion, bool coreProfile, bool forwardCompatible)
{ {
_sdlWindow = SDL_CreateWindowFrom(nativeWindowhandle); _sdlWindow = SDL_CreateWindowFrom(nativeWindowhandle);
if (_sdlWindow == IntPtr.Zero) if (_sdlWindow == IntPtr.Zero)
@ -122,10 +96,10 @@ namespace BizHawk.Bizware.Graphics
throw new($"Could not set share context attribute! SDL Error: {SDL_GetError()}"); throw new($"Could not set share context attribute! SDL Error: {SDL_GetError()}");
} }
CreateContext(majorVersion, minorVersion, forwardCompatible); CreateContext(majorVersion, minorVersion, coreProfile, forwardCompatible);
} }
public SDL2OpenGLContext(int majorVersion, int minorVersion, bool forwardCompatible) public SDL2OpenGLContext(int majorVersion, int minorVersion, bool coreProfile, bool forwardCompatible)
{ {
_sdlWindow = SDL_CreateWindow(null, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 1, 1, _sdlWindow = SDL_CreateWindow(null, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 1, 1,
SDL_WindowFlags.SDL_WINDOW_OPENGL | SDL_WindowFlags.SDL_WINDOW_HIDDEN); SDL_WindowFlags.SDL_WINDOW_OPENGL | SDL_WindowFlags.SDL_WINDOW_HIDDEN);
@ -135,14 +109,13 @@ namespace BizHawk.Bizware.Graphics
} }
// offscreen contexts are shared (as we want to send texture from it over to our control's context) // offscreen contexts are shared (as we want to send texture from it over to our control's context)
// make sure to set the current graphics' control context before creating this context // make sure to set the current graphics control context before creating this context
// (if no context is set, i.e. first IGL, then this won't do anything)
if (SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1) != 0) if (SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1) != 0)
{ {
throw new($"Could not set share context attribute! SDL Error: {SDL_GetError()}"); throw new($"Could not set share context attribute! SDL Error: {SDL_GetError()}");
} }
CreateContext(majorVersion, minorVersion, forwardCompatible); CreateContext(majorVersion, minorVersion, coreProfile, forwardCompatible);
} }
public void Dispose() public void Dispose()

View File

@ -10,10 +10,11 @@ namespace BizHawk.Client.EmuHawk
/// </summary> /// </summary>
public class OpenGLProvider : IOpenGLProvider public class OpenGLProvider : IOpenGLProvider
{ {
public int GLVersion => SDL2OpenGLContext.Version; public bool SupportsGLVersion(int major, int minor)
=> OpenGLVersion.SupportsVersion(major, minor);
public object RequestGLContext(int major, int minor, bool forwardCompatible) public object RequestGLContext(int major, int minor, bool coreProfile, bool forwardCompatible)
=> new SDL2OpenGLContext(major, minor, forwardCompatible); => new SDL2OpenGLContext(major, minor, coreProfile, forwardCompatible);
public void ReleaseGLContext(object context) public void ReleaseGLContext(object context)
=> ((SDL2OpenGLContext)context).Dispose(); => ((SDL2OpenGLContext)context).Dispose();

View File

@ -235,7 +235,7 @@ namespace BizHawk.Client.EmuHawk
} }
var igl = new IGL_OpenGL(); var igl = new IGL_OpenGL();
// need to have a context active for checking renderer, will be disposed afterwards // need to have a context active for checking renderer, will be disposed afterwards
using (new SDL2OpenGLContext(2, 0, false)) using (new SDL2OpenGLContext(OpenGLVersion.SupportsVersion(3, 0) ? 3 : 2, 0, false, false))
{ {
return CheckRenderer(igl); return CheckRenderer(igl);
} }

View File

@ -8,16 +8,19 @@ namespace BizHawk.Emulation.Common
public interface IOpenGLProvider public interface IOpenGLProvider
{ {
/// <summary> /// <summary>
/// OpenGL version, using major.minor.build format with decimal points ommitted (e.g. 1.2.3 turns into 123) /// Checks if specified OpenGL version is supported
/// The current context will be preserved
/// </summary> /// </summary>
public int GLVersion { get; } public bool SupportsGLVersion(int major, int minor);
/// <summary> /// <summary>
/// Requests an OpenGL context with specified major / minor / forwardCompatible /// Requests an OpenGL context with specified major / minor
/// The core profile can be requested (otherwise, the compatibility profile will be used)
/// The forward compatible bit can also be requested
/// The requested OpenGL context will be shared with the current context /// The requested OpenGL context will be shared with the current context
/// Note: creating a context implicitly makes that created context current /// Note: creating a context implicitly makes that created context current
/// </summary> /// </summary>
public object RequestGLContext(int major, int minor, bool forwardCompatible); public object RequestGLContext(int major, int minor, bool coreProfile, bool forwardCompatible);
/// <summary> /// <summary>
/// Frees this OpenGL context /// Frees this OpenGL context

View File

@ -64,7 +64,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.N3DS
_configCallbackInterface.GetString = GetStringSettingCallback; _configCallbackInterface.GetString = GetStringSettingCallback;
_openGLProvider = lp.Comm.OpenGLProvider; _openGLProvider = lp.Comm.OpenGLProvider;
_supportsOpenGL43 = _openGLProvider.GLVersion >= 430; _supportsOpenGL43 = _openGLProvider.SupportsGLVersion(4, 3);
if (!_supportsOpenGL43/* && _syncSettings.GraphicsApi == CitraSyncSettings.EGraphicsApi.OpenGL*/) if (!_supportsOpenGL43/* && _syncSettings.GraphicsApi == CitraSyncSettings.EGraphicsApi.OpenGL*/)
{ {
throw new("OpenGL 4.3 is required, but it is not supported on this machine"); throw new("OpenGL 4.3 is required, but it is not supported on this machine");
@ -161,7 +161,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.N3DS
private IntPtr RequestGLContextCallback() private IntPtr RequestGLContextCallback()
{ {
var context = _openGLProvider.RequestGLContext(4, 3, false); var context = _openGLProvider.RequestGLContext(4, 3, true, false);
_glContexts.Add(context); _glContexts.Add(context);
var handle = GCHandle.Alloc(context, GCHandleType.Weak); var handle = GCHandle.Alloc(context, GCHandleType.Weak);
return GCHandle.ToIntPtr(handle); return GCHandle.ToIntPtr(handle);