1 /++
2  + Authors: Avaxar <avaxar@nekkl.org>
3  + Copyright: Copyright © 2023, Avaxar
4  + License: $(LINK2 https://mit-license.org, MIT License)
5  +/
6 
7 module dsdl2.gl;
8 @safe:
9 
10 import bindbc.sdl;
11 import dsdl2.sdl;
12 import dsdl2.window;
13 
14 import core.memory : GC;
15 import std.conv : to;
16 import std.string : toStringz;
17 
18 /++
19  + Wraps `SDL_GL_LoadLibrary` which loads OpenGL from the given path of the library
20  +
21  + Params:
22  +   path = path to the OpenGL library; `null` to load the default
23  + Throws: `dsdl2.SDLException` if unable to load the library
24  +/
25 void loadGL(string path = null) @trusted {
26     if (SDL_GL_LoadLibrary(path is null ? null : path.toStringz()) != 0) {
27         throw new SDLException;
28     }
29 }
30 
31 /++
32  + Wraps `SDL_GL_GetProcAddress` which gets the pointer to a certain OpenGL function
33  +
34  + Params:
35  +   proc = symbol name of the requested OpenGL function
36  + Returns: function pointer of the requested OpenGL function, otherwise `null` if not found
37  +/
38 void* getGLProcAddress(string proc) @system {
39     return SDL_GL_GetProcAddress(proc.toStringz());
40 }
41 
42 /++
43  + Wraps `SDL_GL_UnloadLibrary` which unloads the loaded OpenGL library from `dsdl2.loadGL`
44  +/
45 void unloadGL() @trusted {
46     SDL_GL_UnloadLibrary();
47 }
48 
49 /++
50  + Wraps `SDL_GL_ExtensionSupported` which checks whether an OpenGL extension is supported
51  +
52  + Params:
53  +   extension = name of the OpenGL extension
54  + Returns: `true` if the extension is supported, otherwise `false`
55  +/
56 bool isGLExtensionSupported(string extension) @trusted {
57     return SDL_GL_ExtensionSupported(extension.toStringz()) == SDL_TRUE;
58 }
59 
60 /++
61  + D enum that wraps `SDL_GLattr` defining OpenGL initialization attributes
62  +/
63 enum GLAttribute {
64     /++
65      + Wraps `SDL_GL_*` enumeration constants for `SDL_GLattr`
66      +/
67     redSize = SDL_GL_RED_SIZE,
68     greenSize = SDL_GL_GREEN_SIZE, /// ditto
69     blueSize = SDL_GL_BLUE_SIZE, /// ditto
70     alphaSize = SDL_GL_ALPHA_SIZE, /// ditto
71     bufferSize = SDL_GL_BUFFER_SIZE, /// ditto
72     doubleBuffer = SDL_GL_DOUBLEBUFFER, /// ditto
73     depthSize = SDL_GL_DEPTH_SIZE, /// ditto
74     stencilSize = SDL_GL_STENCIL_SIZE, /// ditto
75     accumRedSize = SDL_GL_ACCUM_RED_SIZE, /// ditto
76     accumGreenSize = SDL_GL_ACCUM_GREEN_SIZE, /// ditto
77     accumBlueSize = SDL_GL_ACCUM_BLUE_SIZE, /// ditto
78     accumAlphaSize = SDL_GL_ACCUM_ALPHA_SIZE, /// ditto
79     stereo = SDL_GL_STEREO, /// ditto
80     multiSampleBuffers = SDL_GL_MULTISAMPLEBUFFERS, /// ditto
81     multiSampleSamples = SDL_GL_MULTISAMPLESAMPLES, /// ditto
82     acceleratedVisual = SDL_GL_ACCELERATED_VISUAL, /// ditto
83     contextMajorVersion = SDL_GL_CONTEXT_MAJOR_VERSION, /// ditto
84     contextMinorVersion = SDL_GL_CONTEXT_MINOR_VERSION, /// ditto
85     // contextFlags = SDL_GL_CONTEXT_FLAGS, /// ditto
86     contextProfileMask = SDL_GL_CONTEXT_PROFILE_MASK, /// ditto
87     shareWithCurrentContext = SDL_GL_SHARE_WITH_CURRENT_CONTEXT /// ditto
88     // framebufferSRGBCapable = SDL_GL_FRAMEBUFFER_SRGB_CAPABLE, /// ditto
89     // contextReleaseBehavior = SDL_GL_CONTEXT_RELEASE_BEHAVIOR /// ditto
90 }
91 
92 /++
93  + D enum that wraps `SDL_GLprofile` defining OpenGL profiles
94  +/
95 enum GLProfile : uint {
96     /++
97      + Wraps `SDL_GL_CONTEXT_*` enumeration constants
98      +/
99     core = SDL_GL_CONTEXT_PROFILE_CORE,
100     compatibility = SDL_GL_CONTEXT_PROFILE_COMPATIBILITY, /// ditto
101     es = SDL_GL_CONTEXT_PROFILE_ES /// ditto
102 }
103 
104 /++
105  + Wraps `SDL_GL_SetAttribute` which sets OpenGL attributes for initialization
106  +
107  + Params:
108  +   attribute = the `dsdl2.GLAttribute` to set
109  +   value = requested value for the attribute to be set as
110  + Throws: `dsdl2.SDLException` if unable to set the attribute
111  +/
112 void setGLAttribute(GLAttribute attribute, uint value) @trusted {
113     if (SDL_GL_SetAttribute(attribute, value.to!int) != 0) {
114         throw new SDLException;
115     }
116 }
117 
118 /++
119  + Wraps `SDL_GL_GetAttribute` which gets previously set OpenGL attributes for initialization
120  +
121  + Params:
122  +   attribute = the `dsdl2.GLAttribute` whose value to get
123  + Returns: value of the requested attribute
124  + Throws: `dsdl2.SDLException` if unable to get the attribute
125  +/
126 uint getGLAttribute(GLAttribute attribute) @trusted {
127     uint value = void;
128     if (SDL_GL_GetAttribute(attribute, cast(int*)&value) != 0) {
129         throw new SDLException;
130     }
131 
132     return value;
133 }
134 
135 static if (sdlSupport >= SDLSupport.v2_0_2) {
136     /++
137      + Wraps `SDL_GL_ResetAttributes` (from SDL 2.0.2) which resets all OpenGL attributes previously set to default
138      +/
139     void resetGLAttributes() @trusted
140     in {
141         assert(getVersion() >= Version(2, 0, 2));
142     }
143     do {
144         SDL_GL_ResetAttributes();
145     }
146 }
147 
148 /++
149  + Wraps `SDL_GL_GetCurrentWindow` which gets the current target window for OpenGL
150  +
151  + This function is marked as `@system` due to the potential of referencing invalid memory.
152  +
153  + Returns: `dsdl2.Window` proxy to the target window for OpenGL
154  + Throws: `dsdl2.SDLException` if unable to get the window
155  +/
156 Window getCurrentGLWindow() @system {
157     if (SDL_Window* sdlWindow = SDL_GL_GetCurrentWindow()) {
158         return new Window(sdlWindow, false);
159     }
160     else {
161         throw new SDLException;
162     }
163 }
164 
165 /++
166  + Wraps `SDL_GL_GetCurrentContext` which gets the current OpenGL context used by SDL
167  +
168  + This function is marked as `@system` due to the potential of referencing invalid memory.
169  +
170  + Returns: `dsdl2.GLContext` proxy to the OpenGL context used by SDL
171  + Throws: `dsdl2.SDLException` if unable to get the context
172  +/
173 GLContext getCurrentGLContext() @system {
174     if (SDL_GLContext sdlGLContext = SDL_GL_GetCurrentContext()) {
175         return new GLContext(sdlGLContext, false);
176     }
177     else {
178         throw new SDLException;
179     }
180 }
181 
182 /++
183  + D enum that defines swap intervals for OpenGL
184  +/
185 enum GLSwapInterval {
186     immediate = 0, /// No vertical retrace synchronization
187     syncWithVerticalRetrace = 1, /// The buffer swap is synchronized with the vertical retrace
188     adaptiveVSync = -1 /// Late swaps happen immediately instead of waiting for the next retrace
189 }
190 
191 /++
192  + Wraps `SDL_GL_SetSwapInterval` which gets the swap interval method for OpenGL
193  +
194  + Params:
195  +   interval = the `dsdl2.GLSwapInterval` method to use
196  + Throws: `dsdl2.SDLException` if unable to set the swap interval
197  +/
198 void setGLSwapInterval(GLSwapInterval interval) @trusted {
199     if (SDL_GL_SetSwapInterval(cast(int) interval) != 0) {
200         throw new SDLException;
201     }
202 }
203 
204 /++
205  + Wraps `SDL_GL_GetSwapInterval` which gets the currently-used swap interval method for OpenGL
206  +
207  + Returns: the currently-used `dsdl2.GLSwapInterval` method
208  + Throws: `dsdl2.SDLException` if unable to get the swap interval
209  +/
210 GLSwapInterval getGLSwapInterval() @trusted {
211     // NOTE: This function is able to return an error. But the docs aren't clear when.
212     return cast(GLSwapInterval) SDL_GL_GetSwapInterval();
213 }
214 
215 /++
216  + D class that wraps `SDL_GLContext` enclosing an OpenGL context used for SDL
217  +/
218 class GLContext {
219     private bool isOwner = true;
220     private void* userRef = null;
221 
222     @system SDL_GLContext sdlGLContext = null; /// Internal `SDL_GLContext`
223 
224     /++
225      + Constructs a `dsdl2.GLContext` from a vanilla `SDL_GLContext` from bindbc-sdl
226      +
227      + Params:
228      +   sdlGLContext = the `SDL_GLContext` to manage
229      +   isOwner = whether the instance owns the given `SDL_GLContext` and should destroy it on its own
230      +   userRef = optional pointer to maintain reference link, avoiding GC cleanup
231      +/
232     this(SDL_GLContext sdlGLContext, bool isOwner = true, void* userRef = null) @system
233     in {
234         assert(sdlGLContext !is null);
235     }
236     do {
237         this.sdlGLContext = sdlGLContext;
238         this.isOwner = isOwner;
239         this.userRef = userRef;
240     }
241 
242     /++
243      + Creates an OpenGL context to use by SDL, which wraps `SDL_GL_CreateContext`
244      +
245      + Params:
246      +   window = the default OpenGL `dsdl2.Window` to be set current as the rendering target for the context
247      + Throws: `dsdl2.SDLException` if OpenGL context creation failed
248      +/
249     this(Window window) @trusted
250     in {
251         assert(window !is null);
252     }
253     do {
254         this.sdlGLContext = SDL_GL_CreateContext(window.sdlWindow);
255         if (this.sdlGLContext is null) {
256             throw new SDLException;
257         }
258     }
259 
260     ~this() @trusted {
261         if (this.isOwner) {
262             SDL_GL_DeleteContext(this.sdlGLContext);
263         }
264     }
265 
266     @trusted invariant { // @suppress(dscanner.trust_too_much)
267         // Instance might be in an invalid state due to holding a non-owned externally-freed object when
268         // destructed in an unpredictable order.
269         if (!this.isOwner && GC.inFinalizer) {
270             return;
271         }
272 
273         assert(this.sdlGLContext !is null);
274     }
275 
276     /++
277      + Wraps `SDL_GL_MakeCurrent` which makes a window current as the rendering target for OpenGL rendering
278      +
279      + Params:
280      +   window = new OpenGL `dsdl2.Window` to be set current as the rendering target for the context
281      + Throws: `dsdl2.SDLException` if failed to set the new window current
282      +/
283     void makeCurrent(Window window) @trusted
284     in {
285         assert(window !is null);
286     }
287     do {
288         if (SDL_GL_MakeCurrent(window.sdlWindow, this.sdlGLContext) != 0) {
289             throw new SDLException;
290         }
291     }
292 }