-
Notifications
You must be signed in to change notification settings - Fork 399
/
Copy pathSDL2OpenGLContext.cs
284 lines (245 loc) · 8.21 KB
/
SDL2OpenGLContext.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
// #define DEBUG_OPENGL
using System.IO;
#if DEBUG_OPENGL
using System.Runtime.InteropServices;
#endif
using Silk.NET.OpenGL;
using BizHawk.Common;
using BizHawk.Common.StringExtensions;
using static SDL2.SDL;
#pragma warning disable BHI1007 // target-typed Exception TODO don't
namespace BizHawk.Bizware.Graphics
{
/// <summary>
/// Wraps an SDL2 OpenGL context
/// </summary>
public class SDL2OpenGLContext : IDisposable
{
static SDL2OpenGLContext()
{
if (OSTailoredCode.IsUnixHost)
{
// make sure that Linux uses the x11 video driver
// we need this as mono winforms uses x11
// and the user could potentially try to force the wayland video driver via env vars
SDL_SetHint("SDL_VIDEODRIVER", "x11");
// try to use EGL if it is available
// GLX is the old API, and is the more or less "deprecated" at this point, and potentially more buggy with some drivers
// we do need to a bit more work, in case EGL is not actually available or potentially doesn't have desktop GL support
if (!Directory.Exists("/nix")/* this is just for me --yoshi */) SDL_SetHint(SDL_HINT_VIDEO_X11_FORCE_EGL, "1");
}
// init SDL video
if (SDL_Init(SDL_INIT_VIDEO) != 0)
{
throw new($"Could not init SDL video! SDL Error: {SDL_GetError()}");
}
if (OSTailoredCode.IsUnixHost)
{
// if we fail to load EGL, we'll just try again with GLX...
var loadGlx = SDL_GL_LoadLibrary(null) != 0;
if (!loadGlx)
{
try
{
// check if we can actually create a desktop GL context
using var glContext = new SDL2OpenGLContext(3, 2, true);
using var gl = GL.GetApi(GetGLProcAddress);
var versionString = gl.GetStringS(StringName.Version);
if (versionString.ContainsOrdinal("OpenGL ES"))
{
// driver ended up creating a GL ES context regardless
// hopefully GLX works here
loadGlx = true;
}
else
{
// check if we did in fact get at least GL 3.2
var versionParts = versionString!.Split('.');
var major = int.Parse(versionParts[0]);
var minor = int.Parse(versionParts[1][0].ToString());
loadGlx = major * 10 + minor < 32;
}
}
catch
{
// failed to create a context, fallback to GLX
loadGlx = true;
}
}
if (loadGlx)
{
SDL_SetHint(SDL_HINT_VIDEO_X11_FORCE_EGL, "0");
if (SDL_GL_LoadLibrary(null) != 0)
{
throw new($"Could not load default OpenGL library! SDL Error: {SDL_GetError()}");
}
}
}
else
{
// load the default OpenGL library
if (SDL_GL_LoadLibrary(null) != 0)
{
throw new($"Could not load default OpenGL library! SDL Error: {SDL_GetError()}");
}
}
// we will be turning a foreign window into an SDL window
// we need this so it knows that it is capable of using OpenGL functions
SDL_SetHint(SDL_HINT_VIDEO_FOREIGN_WINDOW_OPENGL, "1");
// don't allow windows events to be pumped
// it's not needed and can be dangerous in some rare cases
SDL_SetHint(SDL_HINT_WINDOWS_ENABLE_MESSAGELOOP, "0");
}
#if DEBUG_OPENGL
private static readonly DebugProc _debugProc = DebugCallback;
private static void DebugCallback(GLEnum source, GLEnum type, int id, GLEnum severity, int length, IntPtr message, IntPtr userParam)
=> Console.WriteLine($"{source} {type} {severity}: {Marshal.PtrToStringAnsi(message, length)}");
#endif
private IntPtr _sdlWindow;
private IntPtr _glContext;
private static void SetAttributes(int majorVersion, int minorVersion, bool coreProfile, bool shareContext)
{
// set some sensible defaults
SDL_GL_ResetAttributes();
if (SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_RED_SIZE, 8) is not 0
|| SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_GREEN_SIZE, 8) is not 0
|| SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_BLUE_SIZE, 8) is not 0
|| SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_ALPHA_SIZE, 0) is not 0
|| SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_DOUBLEBUFFER, 1) is not 0)
{
throw new($"Could not set GL attributes! SDL Error: {SDL_GetError()}");
}
if (SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_CONTEXT_MAJOR_VERSION, majorVersion) != 0)
{
throw new($"Could not set GL Major Version! SDL Error: {SDL_GetError()}");
}
if (SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_CONTEXT_MINOR_VERSION, minorVersion) != 0)
{
throw new($"Could not set GL Minor Version! SDL Error: {SDL_GetError()}");
}
#if DEBUG_OPENGL
if (SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_CONTEXT_FLAGS, (int)SDL_GLcontext.SDL_GL_CONTEXT_DEBUG_FLAG) != 0)
{
throw new($"Could not set GL Debug Flag! SDL Error: {SDL_GetError()}");
}
#endif
var profile = coreProfile
? SDL_GLprofile.SDL_GL_CONTEXT_PROFILE_CORE
: SDL_GLprofile.SDL_GL_CONTEXT_PROFILE_COMPATIBILITY;
if (SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_CONTEXT_PROFILE_MASK, profile) != 0)
{
throw new($"Could not set GL profile! SDL Error: {SDL_GetError()}");
}
if (SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_SHARE_WITH_CURRENT_CONTEXT, shareContext ? 1 : 0) != 0)
{
throw new($"Could not set share context attribute! SDL Error: {SDL_GetError()}");
}
}
private void CreateContext()
{
_glContext = SDL_GL_CreateContext(_sdlWindow);
if (_glContext == IntPtr.Zero)
{
throw new($"Could not create GL Context! SDL Error: {SDL_GetError()}");
}
#if DEBUG_OPENGL
if (GetGLProcAddress("glDebugMessageCallback") != IntPtr.Zero)
{
using var gl = GL.GetApi(GetGLProcAddress);
unsafe
{
gl.DebugMessageCallback(_debugProc, null);
}
}
#endif
}
public SDL2OpenGLContext(IntPtr nativeWindowhandle, int majorVersion, int minorVersion, bool coreProfile)
{
// Controls are not shared, they are the sharees
SetAttributes(majorVersion, minorVersion, coreProfile, shareContext: false);
_sdlWindow = SDL_CreateWindowFrom(nativeWindowhandle);
if (_sdlWindow == IntPtr.Zero)
{
throw new($"Could not create SDL Window! SDL Error: {SDL_GetError()}");
}
try
{
CreateContext();
}
catch
{
Dispose();
throw;
}
}
public SDL2OpenGLContext(int majorVersion, int minorVersion, bool coreProfile)
{
// 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
SetAttributes(majorVersion, minorVersion, coreProfile, shareContext: true);
_sdlWindow = SDL_CreateWindow(null, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 1, 1,
SDL_WindowFlags.SDL_WINDOW_OPENGL | SDL_WindowFlags.SDL_WINDOW_HIDDEN);
if (_sdlWindow == IntPtr.Zero)
{
throw new($"Could not create SDL Window! SDL Error: {SDL_GetError()}");
}
try
{
CreateContext();
}
catch
{
Dispose();
throw;
}
}
public void Dispose()
{
if (_glContext != IntPtr.Zero)
{
SDL_GL_DeleteContext(_glContext);
_glContext = IntPtr.Zero;
}
if (_sdlWindow != IntPtr.Zero)
{
SDL_DestroyWindow(_sdlWindow);
_sdlWindow = IntPtr.Zero;
}
}
public bool IsCurrent => SDL_GL_GetCurrentContext() == _glContext;
public void MakeContextCurrent()
{
// no-op if already current
if (SDL_GL_MakeCurrent(_sdlWindow, _glContext) != 0)
{
throw new($"Failed to set context to current! SDL error: {SDL_GetError()}");
}
}
public static void MakeNoneCurrent()
{
// no-op if nothing is current
if (SDL_GL_MakeCurrent(IntPtr.Zero, IntPtr.Zero) != 0)
{
throw new($"Failed to clear current context! SDL error: {SDL_GetError()}");
}
}
public static IntPtr GetGLProcAddress(string proc)
=> SDL_GL_GetProcAddress(proc);
public void SetVsync(bool state)
{
if (!IsCurrent)
{
throw new InvalidOperationException("Tried to set Vsync on non-active context");
}
_ = SDL_GL_SetSwapInterval(state ? 1 : 0);
}
public void SwapBuffers()
{
if (!IsCurrent)
{
throw new InvalidOperationException("Tried to swap buffers on non-active context");
}
SDL_GL_SwapWindow(_sdlWindow);
}
}
}