A game framework written with osu! in mind.
1// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
2// See the LICENCE file in the repository root for full licence text.
3
4using System;
5using System.Diagnostics;
6using System.Linq;
7using System.Reflection;
8using System.Runtime.InteropServices;
9using osu.Framework.Graphics.OpenGL;
10using osu.Framework.Logging;
11using osuTK.Graphics;
12using osuTK.Graphics.ES30;
13
14namespace osu.Framework.Platform
15{
16 /// <summary>
17 /// Implementation of <see cref="IGraphicsBackend"/> that force-loads OpenGL
18 /// endpoints into osuTK's bindings.
19 /// </summary>
20 public abstract class PassthroughGraphicsBackend : IGraphicsBackend
21 {
22 internal IntPtr Context;
23
24 internal Version GLVersion { get; private set; }
25
26 internal Version GLSLVersion { get; private set; }
27
28 internal bool IsEmbedded { get; private set; }
29
30 public abstract bool VerticalSync { get; set; }
31
32 protected abstract IntPtr CreateContext();
33 protected abstract void MakeCurrent(IntPtr context);
34 protected abstract IntPtr GetProcAddress(string symbol);
35
36 public abstract void SwapBuffers();
37
38 public virtual void Initialise(IWindow window)
39 {
40 Context = CreateContext();
41
42 MakeCurrent(Context);
43
44 loadTKBindings();
45
46 string version = GL.GetString(StringName.Version);
47 string versionNumberSubstring = getVersionNumberSubstring(version);
48
49 GLVersion = new Version(versionNumberSubstring);
50
51 // As defined by https://www.khronos.org/registry/OpenGL-Refpages/es2.0/xhtml/glGetString.xml
52 IsEmbedded = version.Contains("OpenGL ES");
53 GLWrapper.IsEmbedded = IsEmbedded;
54
55 version = GL.GetString(StringName.ShadingLanguageVersion);
56
57 if (!string.IsNullOrEmpty(version))
58 {
59 try
60 {
61 GLSLVersion = new Version(versionNumberSubstring);
62 }
63 catch (Exception e)
64 {
65 Logger.Error(e, $@"couldn't set GLSL version using string '{version}'");
66 }
67 }
68
69 if (GLSLVersion == null)
70 GLSLVersion = new Version();
71
72 Logger.Log($@"GL Initialized
73 GL Version: {GL.GetString(StringName.Version)}
74 GL Renderer: {GL.GetString(StringName.Renderer)}
75 GL Shader Language version: {GL.GetString(StringName.ShadingLanguageVersion)}
76 GL Vendor: {GL.GetString(StringName.Vendor)}
77 GL Extensions: {GL.GetString(StringName.Extensions)}");
78
79 // We need to release the context in this thread, since Windows locks it and prevents
80 // the draw thread from taking it. macOS seems to gracefully ignore this.
81 MakeCurrent(IntPtr.Zero);
82 }
83
84 public void MakeCurrent() => MakeCurrent(Context);
85
86 public void ClearCurrent() => MakeCurrent(IntPtr.Zero);
87
88 private void loadTKBindings()
89 {
90 loadEntryPoints(new osuTK.Graphics.OpenGL.GL());
91 loadEntryPoints(new osuTK.Graphics.OpenGL4.GL());
92 loadEntryPoints(new osuTK.Graphics.ES11.GL());
93 loadEntryPoints(new osuTK.Graphics.ES20.GL());
94 loadEntryPoints(new GL());
95 }
96
97 private unsafe void loadEntryPoints(GraphicsBindingsBase bindings)
98 {
99 var type = bindings.GetType();
100 var pointsInfo = type.GetRuntimeFields().First(x => x.Name == "_EntryPointsInstance");
101 var namesInfo = type.GetRuntimeFields().First(x => x.Name == "_EntryPointNamesInstance");
102 var offsetsInfo = type.GetRuntimeFields().First(x => x.Name == "_EntryPointNameOffsetsInstance");
103
104 var entryPointsInstance = (IntPtr[])pointsInfo.GetValue(bindings);
105 var entryPointNamesInstance = (byte[])namesInfo.GetValue(bindings);
106 var entryPointNameOffsetsInstance = (int[])offsetsInfo.GetValue(bindings);
107
108 Debug.Assert(entryPointsInstance != null);
109 Debug.Assert(entryPointNameOffsetsInstance != null);
110
111 fixed (byte* name = entryPointNamesInstance)
112 {
113 for (int i = 0; i < entryPointsInstance.Length; i++)
114 {
115 var ptr = name + entryPointNameOffsetsInstance[i];
116 var str = Marshal.PtrToStringAnsi(new IntPtr(ptr));
117 entryPointsInstance[i] = GetProcAddress(str);
118 }
119 }
120
121 pointsInfo.SetValue(bindings, entryPointsInstance);
122 }
123
124 private string getVersionNumberSubstring(string version)
125 {
126 string result = version.Split(' ').FirstOrDefault(s => char.IsDigit(s, 0));
127 if (result != null) return result;
128
129 throw new ArgumentException($"Invalid version string: \"{version}\"", nameof(version));
130 }
131 }
132}