1 module gbaid.render.gl20;
2 
3 import std.stdio : writeln;
4 import std.conv : to;
5 import std.string : toStringz;
6 import std.container : RedBlackTree, make;
7 import std.variant : Variant;
8 import std.regex : ctRegex, replaceFirst;
9 import std.algorithm.comparison : min;
10 import std.algorithm.sorting : sort;
11 
12 import derelict.sdl2.sdl;
13 import derelict.opengl3.gl3;
14 
15 import gbaid.util;
16 
17 import gbaid.render.gl;
18 
19 /**
20  * An OpenGL 2.0 implementation of {@link org.spout.renderer.api.gl.Context}.
21  *
22  * @see org.spout.renderer.api.gl.Context
23  */
24 public class GL20Context : Context {
25     private string title = "";
26     private uint width = 100;
27     private uint height = 100;
28     private bool resizable = false;
29     private bool fullScreen = false;
30     private bool useVsync = false;
31     private SDL_Window* window;
32     private SDL_GLContext glContext;
33 
34     public override void create() {
35         checkNotCreated();
36         // Load the bindings if needed
37         if (!DerelictSDL2.isLoaded) {
38             DerelictSDL2.load();
39         }
40         if (!DerelictGL3.isLoaded) {
41             DerelictGL3.load();
42         }
43         // Initialize SDL video if needed
44         if (!SDL_WasInit(SDL_INIT_VIDEO)) {
45             if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0) {
46                 throw new Exception("Failed to initialize the SDL video system: " ~ toDString(SDL_GetError()));
47             }
48         }
49         // Configure the context
50         setContextAttributes();
51         // Attempt to create the window
52         int actualWidth, actualHeight;
53         getActualWindowSize(&actualWidth, &actualHeight);
54         int flags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN
55                 | (resizable ? SDL_WINDOW_RESIZABLE : 0) | (fullScreen ? SDL_WINDOW_FULLSCREEN : 0);
56         window = SDL_CreateWindow(toStringz(title), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
57                 actualWidth, actualHeight, flags);
58         if (!window) {
59             throw new Exception("Failed to create an SDL window: " ~ toDString(SDL_GetError()));
60         }
61         // Attempt to create the OpenGL context
62         glContext = SDL_GL_CreateContext(window);
63         if (glContext is null) {
64             throw new Exception("Failed to create OpenGL context: " ~ toDString(SDL_GetError()));
65         }
66         // Set the swap interval
67         if (SDL_GL_SetSwapInterval(useVsync ? 1 : 0) < 0) {
68             throw new Exception("Failed to enable VSYNC: " ~ toDString(SDL_GetError()));
69         }
70         // Load the GL1.1+ features if needed
71         if (DerelictGL3.loadedVersion == derelict.opengl3.types.GLVersion.GL11) {
72             DerelictGL3.reload();
73         }
74         // Check for errors
75         checkForGLError();
76         // Update the state
77         super.create();
78     }
79 
80     /**
81      * Created new context attributes for the version.
82      *
83      * @return The context attributes
84      */
85     protected void setContextAttributes() {
86         if (SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2) < 0) {
87             throw new Exception("Failed to set OpenGL major version: " ~ toDString(SDL_GetError()));
88         }
89         if (SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0) < 0) {
90             throw new Exception("Failed to set OpenGL minor version: " ~ toDString(SDL_GetError()));
91         }
92     }
93 
94     public override void destroy() {
95         checkCreated();
96         // Display goes after else there's no context in which to check for an error
97         checkForGLError();
98         SDL_GL_DeleteContext(glContext);
99         SDL_DestroyWindow(window);
100         super.destroy();
101     }
102 
103     public override FrameBuffer newFrameBuffer() {
104         return new GL20FrameBuffer();
105     }
106 
107     public override Program newProgram() {
108         return new GL20Program();
109     }
110 
111     public override RenderBuffer newRenderBuffer() {
112         return new GL20RenderBuffer();
113     }
114 
115     public override Shader newShader() {
116         return new GL20Shader();
117     }
118 
119     public override Texture newTexture() {
120         return new GL20Texture();
121     }
122 
123     public override VertexArray newVertexArray() {
124         return new GL20VertexArray();
125     }
126 
127     public override string getWindowTitle() {
128         return title;
129     }
130 
131     public override void setWindowTitle(string title) {
132         this.title = title;
133         if (isCreated()) {
134             SDL_SetWindowTitle(window, toStringz(title));
135         }
136     }
137 
138     public override void setWindowSize(uint width, uint height) {
139         this.width = width;
140         this.height = height;
141         if (isCreated()) {
142             int actualWidth, actualHeight;
143             getActualWindowSize(&actualWidth, &actualHeight);
144             SDL_SetWindowSize(window, actualWidth, actualHeight);
145         }
146     }
147 
148     public override void getActualWindowSize(int* width, int* height) {
149         SDL_Rect maxSize;
150         if (fullScreen) {
151             // In full screen the window size if the full display size
152             if (SDL_GetDisplayBounds(0, &maxSize) < 0) {
153                 throw new Exception("Failed to get display bounds: " ~ toDString(SDL_GetError()));
154             }
155             if (width != null) {
156                 *width = maxSize.w;
157             }
158             if (height != null) {
159                 *height = maxSize.h;
160             }
161         } else {
162             // In window mode, we limit the size to the maximum usable area
163             if (SDL_GetDisplayUsableBounds(0, &maxSize) < 0) {
164                 throw new Exception("Failed to get usable display bounds: " ~ toDString(SDL_GetError()));
165             }
166             if (width != null) {
167                 *width = min(this.width, maxSize.w);
168             }
169             if (height != null) {
170                 *height = min(this.height, maxSize.h);
171             }
172         }
173     }
174 
175     public override void setResizable(bool resizable) {
176         this.resizable = resizable;
177         if (isCreated()) {
178             SDL_SetWindowResizable(window, resizable);
179         }
180     }
181 
182     public override void setFullScreen(bool fullScreen) {
183         this.fullScreen = fullScreen;
184         if (isCreated()) {
185             // We need to read the actual size before, because full screen changes it
186             int actualWidth, actualHeight;
187             getActualWindowSize(&actualWidth, &actualHeight);
188             // Set to full screen
189             if (SDL_SetWindowFullscreen(window, fullScreen ? SDL_WINDOW_FULLSCREEN : 0) < 0) {
190                 throw new Exception("Failed to set window to full screen: " ~ toDString(SDL_GetError()));
191             }
192             // Set the window size to the full screen, or restore it to the original
193             SDL_SetWindowSize(window, actualWidth, actualHeight);
194             // Update the resizable state when exiting full screen
195             if (!fullScreen) {
196                 SDL_SetWindowResizable(window, resizable);
197             }
198         }
199     }
200 
201     public override void getWindowSize(int* width, int* height) {
202         if (isCreated()) {
203             SDL_GetWindowSize(window, width, height);
204         } else {
205             if (width != null) {
206                 *width = this.width;
207             }
208             if (height != null) {
209                 *height = this.height;
210             }
211         }
212     }
213 
214     public override void enableVsync(bool useVsync) {
215         this.useVsync = useVsync;
216         if (isCreated()) {
217             if (SDL_GL_SetSwapInterval(useVsync ? 1 : 0) < 0) {
218                 throw new Exception("Failed to enable VSYNC: " ~ toDString(SDL_GetError()));
219             }
220         }
221     }
222 
223     public override uint getWindowWidth() {
224         int w;
225         getWindowSize(&w, null);
226         return w;
227     }
228 
229     public override uint getWindowHeight() {
230         int h;
231         getWindowSize(null, &h);
232         return h;
233     }
234 
235     public override void updateDisplay() {
236         checkCreated();
237         SDL_GL_SwapWindow(window);
238     }
239 
240     public override void setClearColor(float red, float green, float blue, float alpha) {
241         checkCreated();
242         glClearColor(red, green, blue, alpha);
243         // Check for errors
244         checkForGLError();
245     }
246 
247     public override void clearCurrentBuffer() {
248         checkCreated();
249         glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
250         // Check for errors
251         checkForGLError();
252     }
253 
254     public override void enableCapability(Capability capability) {
255         checkCreated();
256         glEnable(capability.getGLConstant());
257         // Check for errors
258         checkForGLError();
259     }
260 
261     public override void disableCapability(Capability capability) {
262         checkCreated();
263         glDisable(capability.getGLConstant());
264         // Check for errors
265         checkForGLError();
266     }
267 
268     public override void setDepthMask(bool enabled) {
269         checkCreated();
270         glDepthMask(enabled);
271         // Check for errors
272         checkForGLError();
273     }
274 
275     public override void setBlendingFunctions(int bufferIndex, BlendFunction source, BlendFunction destination) {
276         checkCreated();
277         glBlendFunc(source.getGLConstant(), destination.getGLConstant());
278         // Check for errors
279         checkForGLError();
280     }
281 
282     public override void setViewPort(uint x, uint y, uint width, uint height) {
283         checkCreated();
284         glViewport(x, y, width, height);
285         // Check for errors
286         checkForGLError();
287     }
288 
289     public override ubyte[] readFrame(uint x, uint y, uint width, uint height, InternalFormat format) {
290         checkCreated();
291         // Create the image buffer
292         ubyte[] buffer = new ubyte[width * height * format.getBytes()];
293         // Read from the front buffer
294         glReadBuffer(GL_FRONT);
295         // Use byte alignment
296         glPixelStorei(GL_PACK_ALIGNMENT, 1);
297         // Read the pixels
298         glReadPixels(x, y, width, height, format.getFormat().getGLConstant(), format.getComponentType().getGLConstant(), buffer.ptr);
299         // Check for errors
300         checkForGLError();
301         return buffer;
302     }
303 
304     public override bool isWindowCloseRequested() {
305         SDL_PumpEvents();
306 
307         SDL_Event event;
308         if (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_QUIT, SDL_QUIT) < 0) {
309             throw new Exception("Failed to peep events: " ~ toDString(SDL_GetError()));
310         }
311 
312         if (event.type == SDL_QUIT) {
313             return true;
314         }
315 
316         SDL_FlushEvents(SDL_FIRSTEVENT, SDL_LASTEVENT);
317         return false;
318     }
319 
320     public gbaid.render.gl.GLVersion getGLVersion() {
321         return GL20;
322     }
323 }
324 
325 /**
326  * An OpenGL 2.0 implementation of {@link FrameBuffer} using EXT.
327  *
328  * @see FrameBuffer
329  */
330 public class GL20FrameBuffer : FrameBuffer {
331     private RedBlackTree!uint outputBuffers = make!(RedBlackTree!uint);
332 
333     /**
334      * Constructs a new frame buffer for OpenGL 2.0. If no EXT extension for frame buffers is available, an exception is thrown.
335      *
336      * @throws UnsupportedOperationException If the hardware doesn't support EXT frame buffers
337      */
338     public this() {
339         if (!EXT_framebuffer_object) {
340             throw new Exception("Frame buffers are not supported by this hardware");
341         }
342     }
343 
344     public override void create() {
345         checkNotCreated();
346         // Generate and bind the frame buffer
347         glGenFramebuffersEXT(1, &id);
348         glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, id);
349         // Disable input buffers
350         glReadBuffer(GL_NONE);
351         // Unbind the frame buffer
352         glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
353         // Update the state
354         super.create();
355         // Check for errors
356         checkForGLError();
357     }
358 
359     public override void destroy() {
360         checkCreated();
361         // Delete the frame buffer
362         glDeleteFramebuffersEXT(1, &id);
363         // Clear output buffers
364         outputBuffers.clear();
365         // Update the state
366         super.destroy();
367         // Check for errors
368         checkForGLError();
369     }
370 
371     public override void attach(AttachmentPoint point, Texture texture) {
372         checkCreated();
373         texture.checkCreated();
374         // Bind the frame buffer
375         glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, id);
376         // Attach the texture
377         glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, point.getGLConstant(), GL_TEXTURE_2D, texture.getID(), 0);
378         // Add it to the color outputs if it's a color type
379         if (point.isColor()) {
380             outputBuffers.insert(point.getGLConstant());
381         }
382         // Update the list of output buffers
383         updateOutputBuffers();
384         // Unbind the frame buffer
385         glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
386         // Check for errors
387         checkForGLError();
388     }
389 
390     public override void attach(AttachmentPoint point, RenderBuffer buffer) {
391         checkCreated();
392         buffer.checkCreated();
393         // Bind the frame buffer
394         glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, id);
395         // Attach the render buffer
396         glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, point.getGLConstant(), GL_RENDERBUFFER_EXT, buffer.getID());
397         // Add it to the color outputs if it's a color type
398         if (point.isColor()) {
399             outputBuffers.insert(point.getGLConstant());
400         }
401         // Update the list of output buffers
402         updateOutputBuffers();
403         // Unbind the frame buffer
404         glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
405         // Check for errors
406         checkForGLError();
407     }
408 
409     public override void detach(AttachmentPoint point) {
410         checkCreated();
411         // Bind the frame buffer
412         glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, id);
413         // Detach the render buffer or texture
414         glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, point.getGLConstant(), GL_RENDERBUFFER_EXT, 0);
415         // Remove it from the color outputs if it's a color type
416         if (point.isColor()) {
417             outputBuffers.removeKey(point.getGLConstant());
418         }
419         // Update the list of output buffers
420         updateOutputBuffers();
421         // Unbind the frame buffer
422         glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
423         // Check for errors
424         checkForGLError();
425     }
426 
427     private void updateOutputBuffers() {
428         // Set the output to the proper buffers
429         uint[] outputBuffersArray;
430         if (outputBuffers.empty) {
431             outputBuffersArray = [GL_NONE];
432         } else {
433             // Keep track of the buffers to output
434             outputBuffersArray = new uint[outputBuffers.length];
435             uint i = 0;
436             foreach (buffer; outputBuffers[]) {
437                 outputBuffersArray[i++] = buffer;
438             }
439             // Sorting the array ensures that attachments are in order n, n + 1, n + 2...
440             // This is important!
441             sort(outputBuffersArray);
442         }
443         glDrawBuffers(cast(uint) outputBuffersArray.length, outputBuffersArray.ptr);
444     }
445 
446     public override bool isComplete() {
447         checkCreated();
448         // Bind the frame buffer
449         glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, id);
450         // Fetch the status and compare to the complete enum value
451         bool complete = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT;
452         // Unbind the frame buffer
453         glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
454         // Check for errors
455         checkForGLError();
456         return complete;
457     }
458 
459     public override void bind() {
460         checkCreated();
461         // Bind the frame buffer
462         glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, id);
463         // Check for errors
464         checkForGLError();
465     }
466 
467     public override void unbind() {
468         checkCreated();
469         // Unbind the frame buffer
470         glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
471         // Check for errors
472         checkForGLError();
473     }
474 
475     public gbaid.render.gl.GLVersion getGLVersion() {
476         return GL20;
477     }
478 }
479 
480 /**
481  * An OpenGL 2.0 implementation of {@link Program}.
482  *
483  * @see Program
484  */
485 public class GL20Program : Program {
486     // Represents an unset value for a uniform
487     private static immutable Object UNSET = new immutable(Object)();
488     // Regex to remove the array notation from attribute names
489     private static auto ATTRIBUTE_ARRAY_NOTATION_PATTERN = ctRegex!("\\[\\d+\\]", "g");
490     // Set of all shaders in this program
491     private bool[Shader] shaders;
492     // Map of the attribute names to their vao index (optional for GL30 as they can be defined in the shader instead)
493     private uint[string] attributeLayouts;
494     // Map of the texture units to their names
495     private string[uint] textureLayouts;
496     // Map of the uniform names to their locations
497     private uint[string] uniforms;
498     // Map of the uniform names to their values
499     private Variant[string] uniformValues;
500 
501     public override void create() {
502         checkNotCreated();
503         // Create program
504         id = glCreateProgram();
505         // Update the state
506         super.create();
507     }
508 
509     public override void destroy() {
510         checkCreated();
511         // Delete the program
512         glDeleteProgram(id);
513         // Check for errors
514         checkForGLError();
515         // Clear the data
516         shaders = null;
517         attributeLayouts = null;
518         textureLayouts = null;
519         uniforms = null;
520         uniformValues = null;
521         // Update the state
522         super.destroy();
523     }
524 
525     public override void attachShader(Shader shader) {
526         checkCreated();
527         // Attach the shader
528         glAttachShader(id, shader.getID());
529         // Check for errors
530         checkForGLError();
531         // Add the shader to the set
532         shaders[shader] = true;
533         // Add all attribute and texture layouts
534         addAll!(string, uint)(attributeLayouts, shader.getAttributeLayouts());
535         addAll!(uint, string)(textureLayouts, shader.getTextureLayouts());
536     }
537 
538     public override void detachShader(Shader shader) {
539         checkCreated();
540         // Attach the shader
541         glDetachShader(id, shader.getID());
542         // Check for errors
543         checkForGLError();
544         // Remove the shader from the set
545         shaders.remove(shader);
546         // Remove all attribute and texture layouts
547         removeAll!(string, uint)(attributeLayouts, shader.getAttributeLayouts());
548         removeAll!(uint, string)(textureLayouts, shader.getTextureLayouts());
549     }
550 
551     public override void link() {
552         checkCreated();
553         // Add the attribute layouts to the program state
554         foreach (layout; attributeLayouts.byKey()) {
555             // Bind the index to the name
556             glBindAttribLocation(id, attributeLayouts[layout], toStringz(layout));
557         }
558         // Link program
559         glLinkProgram(id);
560         // Check program link status
561         int status;
562         glGetProgramiv(id, GL_LINK_STATUS, &status);
563         if (status == GL_FALSE) {
564             throw new Exception("Program could not be linked\n" ~ getInfoLog());
565         }
566         if (DEBUG_ENABLED) {
567             // Validate program
568             glValidateProgram(id);
569             // Check program validation status
570             glGetProgramiv(id, GL_VALIDATE_STATUS, &status);
571             if (status == GL_FALSE) {
572                 writeln("Program validation failed. This doesn't mean it won't work, so you maybe able to ignore it\n" ~ getInfoLog());
573             }
574         }
575         // Load uniforms
576         uniforms = null;
577         int uniformCount;
578         glGetProgramiv(id, GL_ACTIVE_UNIFORMS, &uniformCount);
579         int maxLength;
580         glGetProgramiv(id, GL_ACTIVE_UNIFORM_MAX_LENGTH, &maxLength);
581         int length;
582         int size;
583         uint type;
584         char[] name = new char[maxLength];
585         foreach (uint i; 0 .. uniformCount) {
586             glGetActiveUniform(id, i, maxLength, &length, &size, &type, name.ptr);
587             // Simplify array names
588             string nameString = toDString(name);
589             replaceFirst(nameString, ATTRIBUTE_ARRAY_NOTATION_PATTERN, "");
590             uniforms[nameString] = glGetUniformLocation(id, name.ptr);
591             uniformValues[nameString] = UNSET;
592         }
593         // Check for errors
594         checkForGLError();
595     }
596 
597     private string getInfoLog() {
598         static immutable uint maxLength = 1024;
599         int length;
600         char[maxLength] log = new char[maxLength];
601         glGetProgramInfoLog(id, cast(uint) maxLength, &length, log.ptr);
602         return gbaid.util.toDString(log);
603     }
604 
605     public override void use() {
606         checkCreated();
607         // Bind the program
608         glUseProgram(id);
609         // Check for errors
610         checkForGLError();
611     }
612 
613     public override void bindSampler(uint unit) {
614         if (unit !in textureLayouts) {
615             throw new Exception("No texture layout has been set for the unit: " ~ to!string(unit));
616         }
617         setUniform(textureLayouts[unit], unit);
618     }
619 
620     public override void setUniform(string name, bool b) {
621         checkCreated();
622         Variant var = b;
623         if (!isDirty(name, var)) {
624             return;
625         }
626         glUniform1i(uniforms[name], b ? 1 : 0);
627         uniformValues[name] = var;
628         checkForGLError();
629     }
630 
631     public override void setUniform(string name, int i) {
632         checkCreated();
633         Variant var = i;
634         if (!isDirty(name, var)) {
635             return;
636         }
637         glUniform1i(uniforms[name], i);
638         uniformValues[name] = var;
639         checkForGLError();
640     }
641 
642     public override void setUniform(string name, float f) {
643         checkCreated();
644         Variant var = f;
645         if (!isDirty(name, var)) {
646             return;
647         }
648         glUniform1f(uniforms[name], f);
649         uniformValues[name] = var;
650         checkForGLError();
651     }
652 
653     public override void setUniform(string name, float[] fs) {
654         checkCreated();
655         Variant var = fs;
656         if (!isDirty(name, var)) {
657             return;
658         }
659         glUniform1fv(uniforms[name], cast(uint) fs.length, fs.ptr);
660         uniformValues[name] = var;
661         checkForGLError();
662     }
663 
664     public override void setUniform(string name, float x, float y) {
665         checkCreated();
666         Vector2f v;
667         v.x = x;
668         v.y = y;
669         Variant var = v;
670         if (!isDirty(name, var)) {
671             return;
672         }
673         glUniform2f(uniforms[name], x, y);
674         uniformValues[name] = var;
675         checkForGLError();
676     }
677 
678     private struct Vector2f {
679         float x, y;
680     }
681 
682     public override void setUniform(string name, float x, float y, float z) {
683         checkCreated();
684         Vector3f v;
685         v.x = x;
686         v.y = y;
687         v.z = z;
688         Variant var = v;
689         if (!isDirty(name, var)) {
690             return;
691         }
692         glUniform3f(uniforms[name], x, y, z);
693         uniformValues[name] = var;
694         checkForGLError();
695     }
696 
697     private struct Vector3f {
698         float x, y, z;
699     }
700 
701     public override void setUniform(string name, float x, float y, float z, float w) {
702         checkCreated();
703         Vector4f v;
704         v.x = x;
705         v.y = y;
706         v.z = z;
707         v.w = w;
708         Variant var = v;
709         if (!isDirty(name, var)) {
710             return;
711         }
712         glUniform4f(uniforms[name], x, y, z, w);
713         uniformValues[name] = var;
714         checkForGLError();
715     }
716 
717     private struct Vector4f {
718         float x, y, z, w;
719     }
720 
721     public override void setUniform(string name, ref float[4] m) {
722         checkCreated();
723         Variant var = m.dup;
724         if (!isDirty(name, var)) {
725             return;
726         }
727         glUniformMatrix2fv(uniforms[name], 1, false, m.ptr);
728         uniformValues[name] = var;
729         checkForGLError();
730     }
731 
732     public override void setUniform(string name, ref float[9] m) {
733         checkCreated();
734         Variant var = m.dup;
735         if (!isDirty(name, var)) {
736             return;
737         }
738         glUniformMatrix3fv(uniforms[name], 1, false, m.ptr);
739         uniformValues[name] = var;
740         checkForGLError();
741     }
742 
743     public override void setUniform(string name, ref float[16] m) {
744         checkCreated();
745         Variant var = m.dup;
746         if (!isDirty(name, var)) {
747             return;
748         }
749         glUniformMatrix4fv(uniforms[name], 1, false, m.ptr);
750         uniformValues[name] = var;
751         checkForGLError();
752     }
753 
754     private bool isDirty(string name, Variant newValue) {
755         return name in uniformValues && uniformValues[name] != newValue;
756     }
757 
758     public override Shader[] getShaders() {
759         return shaders.keys;
760     }
761 
762     public override string[] getUniformNames() {
763         return uniforms.keys;
764     }
765 
766     public gbaid.render.gl.GLVersion getGLVersion() {
767         return GL20;
768     }
769 }
770 
771 /**
772  * An OpenGL 2.0 implementation of {@link RenderBuffer} using EXT.
773  *
774  * @see RenderBuffer
775  */
776 public class GL20RenderBuffer : RenderBuffer {
777     // The render buffer storage format
778     private InternalFormat format;
779     // The storage dimensions
780     private uint width = 1;
781     private uint height = 1;
782 
783     /**
784      * Constructs a new render buffer for OpenGL 2.0. If no EXT extension for render buffers is available, an exception is thrown.
785      *
786      * @throws UnsupportedOperationException If the hardware doesn't support EXT render buffers.
787      */
788     public this() {
789         if (!EXT_framebuffer_object) {
790             throw new Exception("Render buffers are not supported by this hardware");
791         }
792     }
793 
794     public override void create() {
795         checkNotCreated();
796         // Generate the render buffer
797         glGenRenderbuffersEXT(1, &id);
798         // Update the state
799         super.create();
800         // Check for errors
801         checkForGLError();
802     }
803 
804     public override void destroy() {
805         checkCreated();
806         // Delete the render buffer
807         glDeleteRenderbuffersEXT(1, &id);
808         // Update state
809         super.destroy();
810         // Check for errors
811         checkForGLError();
812     }
813 
814     public override void setStorage(InternalFormat format, uint width, uint height) {
815         checkCreated();
816         if (format is null) {
817             throw new Exception("Format cannot be null");
818         }
819         this.format = format;
820         this.width = width;
821         this.height = height;
822         // Bind the render buffer
823         glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, id);
824         // Set the storage format and size
825         glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, format.getGLConstant(), width, height);
826         // Unbind the render buffer
827         glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);
828         // Check for errors
829         checkForGLError();
830     }
831 
832     public override InternalFormat getFormat() {
833         return format;
834     }
835 
836     public override uint getWidth() {
837         return width;
838     }
839 
840     public override uint getHeight() {
841         return height;
842     }
843 
844     public override void bind() {
845         checkCreated();
846         // Unbind the render buffer
847         glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, id);
848         // Check for errors
849         checkForGLError();
850     }
851 
852     public override void unbind() {
853         checkCreated();
854         // Bind the render buffer
855         glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);
856         // Check for errors
857         checkForGLError();
858     }
859 
860     public gbaid.render.gl.GLVersion getGLVersion() {
861         return GL20;
862     }
863 }
864 
865 /**
866  * An OpenGL 2.0 implementation of {@link Shader}.
867  *
868  * @see Shader
869  */
870 public class GL20Shader : Shader {
871     private ShaderType type;
872     // Map of the attribute names to their vao index (optional for GL30 as they can be defined in the shader instead)
873     private uint[string] attributeLayouts;
874     // Map of the texture units to their names
875     private string[uint] textureLayouts;
876 
877     public override void create() {
878         checkNotCreated();
879         // Update the state
880         super.create();
881     }
882 
883     public override void destroy() {
884         checkCreated();
885         // Delete the shader
886         glDeleteShader(id);
887         // Clear the data
888         type = null;
889         attributeLayouts = null;
890         textureLayouts = null;
891         // Update the state
892         super.destroy();
893         // Check for errors
894         checkForGLError();
895     }
896 
897     public override void setSource(ShaderSource source) {
898         checkCreated();
899         if (source is null) {
900             throw new Exception("Shader source cannot be null");
901         }
902         if (!source.isComplete()) {
903             throw new Exception("Shader source isn't complete");
904         }
905         // If we don't have a previous shader or the type isn't the same, we need to create a new one
906         ShaderType type = source.getType();
907         if (id == 0 || this.type != type) {
908             // Delete the old shader
909             glDeleteShader(id);
910             // Create a shader of the correct type
911             id = glCreateShader(type.getGLConstant());
912             // Store the current type
913             this.type = type;
914         }
915         // Upload the new source
916         immutable(char)* src = toStringz(source.getSource());
917         glShaderSource(id, 1, &src, null);
918         // Set the layouts from the source
919         attributeLayouts = source.getAttributeLayouts().dup;
920         textureLayouts = source.getTextureLayouts().dup;
921         // Check for errors
922         checkForGLError();
923     }
924 
925     public override void compile() {
926         checkCreated();
927         // Compile the shader
928         glCompileShader(id);
929         // Get the shader compile status property, check it's false and fail if that's the case
930         int status;
931         glGetShaderiv(id, GL_COMPILE_STATUS, &status);
932         if (status == GL_FALSE) {
933             throw new Exception("OPEN GL ERROR: Could not compile shader\n" ~ getInfoLog());
934         }
935         // Check for errors
936         checkForGLError();
937     }
938 
939     private string getInfoLog() {
940         static immutable uint maxLength = 1024;
941         int length;
942         char[maxLength] log = new char[maxLength];
943         glGetShaderInfoLog(id, cast(uint) maxLength, &length, log.ptr);
944         return toDString(log);
945     }
946 
947     public override ShaderType getType() {
948         return type;
949     }
950 
951     public override uint[string] getAttributeLayouts() {
952         return attributeLayouts;
953     }
954 
955     public override string[uint] getTextureLayouts() {
956         return textureLayouts;
957     }
958 
959     public override void setAttributeLayout(string attribute, uint layout) {
960         attributeLayouts[attribute] = layout;
961     }
962 
963     public override void setTextureLayout(uint unit, string sampler) {
964         textureLayouts[unit] = sampler;
965     }
966 
967     public gbaid.render.gl.GLVersion getGLVersion() {
968         return GL20;
969     }
970 }
971 
972 /**
973  * An OpenGL 2.0 implementation of {@link Texture}.
974  *
975  * @see Texture
976  */
977 public class GL20Texture : Texture {
978     // The format
979     protected Format format = RGB;
980     protected InternalFormat internalFormat = null;
981     // The min filter, to check if we need mip maps
982     protected FilterMode minFilter = NEAREST_MIPMAP_LINEAR;
983     // Texture image dimensions
984     protected uint width = 1;
985     protected uint height = 1;
986 
987     public override void create() {
988         checkNotCreated();
989         // Generate the texture
990         glGenTextures(1, &id);
991         // Update the state
992         super.create();
993         // Check for errors
994         checkForGLError();
995     }
996 
997     public override void destroy() {
998         checkCreated();
999         // Delete the texture
1000         glDeleteTextures(1, &id);
1001         // Reset the data
1002         super.destroy();
1003         // Check for errors
1004         checkForGLError();
1005     }
1006 
1007     public override void setFormat(Format format, InternalFormat internalFormat) {
1008         if (format is null) {
1009             throw new Exception("Format cannot be null");
1010         }
1011         this.format = format;
1012         this.internalFormat = internalFormat;
1013     }
1014 
1015     public override Format getFormat() {
1016         return format;
1017     }
1018 
1019     public override InternalFormat getInternalFormat() {
1020         return internalFormat;
1021     }
1022 
1023     public override void setAnisotropicFiltering(float value) {
1024         checkCreated();
1025         if (value <= 0) {
1026             throw new Exception("Anisotropic filtering value must be greater than zero");
1027         }
1028         // Bind the texture
1029         glBindTexture(GL_TEXTURE_2D, id);
1030         // Set the anisotropic filtering value
1031         glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, value);
1032         // Unbind the texture
1033         glBindTexture(GL_TEXTURE_2D, 0);
1034         // Check for errors
1035         checkForGLError();
1036     }
1037 
1038     public override void setWraps(WrapMode horizontalWrap, WrapMode verticalWrap) {
1039         checkCreated();
1040         if (horizontalWrap is null) {
1041             throw new Exception("Horizontal wrap cannot be null");
1042         }
1043         if (verticalWrap is null) {
1044             throw new Exception("Vertical wrap cannot be null");
1045         }
1046         // Bind the texture
1047         glBindTexture(GL_TEXTURE_2D, id);
1048         // Set the vertical and horizontal texture wraps
1049         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, horizontalWrap.getGLConstant());
1050         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, verticalWrap.getGLConstant());
1051         // Unbind the texture
1052         glBindTexture(GL_TEXTURE_2D, 0);
1053         // Check for errors
1054         checkForGLError();
1055     }
1056 
1057     public override void setFilters(FilterMode minFilter, FilterMode magFilter) {
1058         checkCreated();
1059         if (minFilter is null) {
1060             throw new Exception("Min filter cannot be null");
1061         }
1062         if (magFilter is null) {
1063             throw new Exception("Mag filter cannot be null");
1064         }
1065         if (magFilter.needsMipMaps()) {
1066             throw new Exception("Mag filter cannot require mipmaps");
1067         }
1068         this.minFilter = minFilter;
1069         // Bind the texture
1070         glBindTexture(GL_TEXTURE_2D, id);
1071         // Set the min and max texture filters
1072         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter.getGLConstant());
1073         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter.getGLConstant());
1074         // Unbind the texture
1075         glBindTexture(GL_TEXTURE_2D, 0);
1076         // Check for errors
1077         checkForGLError();
1078     }
1079 
1080     public override void setCompareMode(CompareMode compareMode) {
1081         checkCreated();
1082         if (compareMode is null) {
1083             throw new Exception("Compare mode cannot be null");
1084         }
1085         // Bind the texture
1086         glBindTexture(GL_TEXTURE_2D, id);
1087         // Note: GL14.GL_COMPARE_R_TO_TEXTURE and GL30.GL_COMPARE_REF_TO_TEXTURE are the same, just a different name
1088         // No need for a different call in the GL30 implementation
1089         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
1090         // Set the compare mode
1091         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, compareMode.getGLConstant());
1092         // Unbind the texture
1093         glBindTexture(GL_TEXTURE_2D, 0);
1094         // Check for errors
1095         checkForGLError();
1096     }
1097 
1098     public override void setBorderColor(float red, float green, float blue, float alpha) {
1099         checkCreated();
1100         // Bind the texture
1101         glBindTexture(GL_TEXTURE_2D, id);
1102         // Set the border color
1103         float[4] color = [red, green, blue, alpha];
1104         glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, color.ptr);
1105         // Unbind the texture
1106         glBindTexture(GL_TEXTURE_2D, 0);
1107         // Check for errors
1108         checkForGLError();
1109     }
1110 
1111     public override void setImageData(ubyte[] imageData, uint width, uint height) {
1112         checkCreated();
1113         // Use byte alignment
1114         glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
1115         // Bind the texture
1116         glBindTexture(GL_TEXTURE_2D, id);
1117         // back up the old values
1118         uint oldWidth = this.width;
1119         uint oldHeight = this.height;
1120         // update the texture width and height
1121         this.width = width;
1122         this.height = height;
1123         // check if we can only upload without reallocating
1124         bool hasInternalFormat = internalFormat !is null;
1125         if (width == oldWidth && height == oldHeight) {
1126             glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format.getGLConstant(),
1127                     hasInternalFormat ? internalFormat.getComponentType().getGLConstant() : UNSIGNED_BYTE.getGLConstant(), imageData.ptr);
1128         } else {
1129             // reallocate and upload the texture to the GPU
1130             //if (minFilter.needsMipMaps() && imageData !is null) {
1131                 // Build mipmaps if using mip mapped filters
1132                 //gluBuild2DMipmaps(GL_TEXTURE_2D, hasInternalFormat ? internalFormat.getGLConstant() : format.getGLConstant(), width, height, format.getGLConstant(),
1133                 //        hasInternalFormat ? internalFormat.getComponentType().getGLConstant() : DataType.UNSIGNED_BYTE.getGLConstant(), imageData);
1134                 //} else {
1135                 // Else just make it a normal texture
1136                 // Upload the image
1137             glTexImage2D(GL_TEXTURE_2D, 0, hasInternalFormat ? internalFormat.getGLConstant() : format.getGLConstant(), width, height, 0, format.getGLConstant(),
1138                     hasInternalFormat ? internalFormat.getComponentType().getGLConstant() : UNSIGNED_BYTE.getGLConstant(), imageData.ptr);
1139             //}
1140         }
1141         // Unbind the texture
1142         glBindTexture(GL_TEXTURE_2D, 0);
1143         // Check for errors
1144         checkForGLError();
1145     }
1146 
1147     public override ubyte[] getImageData(InternalFormat format) {
1148         checkCreated();
1149         // Bind the texture
1150         glBindTexture(GL_TEXTURE_2D, id);
1151         // Create the image buffer
1152         bool formatNotNull = format !is null;
1153         ubyte[] imageData = new ubyte[width * height * (formatNotNull ? format.getBytes() : this.format.getComponentCount() * UNSIGNED_BYTE.getByteSize())];
1154         // Use byte alignment
1155         glPixelStorei(GL_PACK_ALIGNMENT, 1);
1156         // Get the image data
1157         glGetTexImage(GL_TEXTURE_2D, 0, formatNotNull ? format.getFormat().getGLConstant() : this.format.getGLConstant(),
1158                 formatNotNull ? format.getComponentType().getGLConstant() : UNSIGNED_BYTE.getGLConstant(), imageData.ptr);
1159         // Unbind the texture
1160         glBindTexture(GL_TEXTURE_2D, 0);
1161         // Check for errors
1162         checkForGLError();
1163         return imageData;
1164     }
1165 
1166     public override uint getWidth() {
1167         return width;
1168     }
1169 
1170     public override uint getHeight() {
1171         return height;
1172     }
1173 
1174     public override void bind(int unit) {
1175         checkCreated();
1176         if (unit != -1) {
1177             // Activate the texture unit
1178             glActiveTexture(GL_TEXTURE0 + unit);
1179         }
1180         // Bind the texture
1181         glBindTexture(GL_TEXTURE_2D, id);
1182         // Check for errors
1183         checkForGLError();
1184     }
1185 
1186     public override void unbind() {
1187         checkCreated();
1188         // Unbind the texture
1189         glBindTexture(GL_TEXTURE_2D, 0);
1190         // Check for errors
1191         checkForGLError();
1192     }
1193 
1194     public gbaid.render.gl.GLVersion getGLVersion() {
1195         return GL20;
1196     }
1197 }
1198 
1199 /**
1200  * An OpenGL 2.0 implementation of {@link VertexArray}.
1201  * <p/>
1202  * Vertex arrays will be used if the ARB or APPLE extension is supported by the hardware. Else, since core OpenGL doesn't support them until 3.0, the vertex attributes will have to be redefined on
1203  * each render call.
1204  *
1205  * @see VertexArray
1206  */
1207 public class GL20VertexArray : VertexArray {
1208     private static immutable uint[0] EMPTY_ARRAY = [];
1209     // Buffers IDs
1210     private uint indicesBufferID = 0;
1211     private uint[] attributeBufferIDs = EMPTY_ARRAY;
1212     // Size of the attribute buffers
1213     private uint[] attributeBufferSizes = EMPTY_ARRAY;
1214     // Amount of indices to render
1215     private uint indicesCount = 0;
1216     private uint indicesDrawCount = 0;
1217     // First and last index to render
1218     private uint indicesOffset = 0;
1219     // Drawing mode
1220     private DrawingMode drawingMode = TRIANGLES;
1221     // Polygon mode
1222     private PolygonMode polygonMode = FILL;
1223     // The pointers to the functions for the available vertex extension
1224     private bool hasVertexArrayExtension;
1225     private da_glGenVertexArrays genVertexArrays;
1226     private da_glBindVertexArray bindVertexArray;
1227     private da_glDeleteVertexArrays deleteVertexArrays;
1228     // Attribute properties for when we don't have a vao extension
1229     private uint[] attributeSizes;
1230     private uint[] attributeTypes;
1231     private bool[] attributeNormalizing;
1232 
1233     public this() {
1234         if (ARB_vertex_array_object) {
1235             hasVertexArrayExtension = true;
1236             genVertexArrays = glGenVertexArrays;
1237             bindVertexArray = glBindVertexArray;
1238             deleteVertexArrays = glDeleteVertexArrays;
1239         } else if (APPLE_vertex_array_object) {
1240             hasVertexArrayExtension = true;
1241             genVertexArrays = glGenVertexArraysAPPLE;
1242             bindVertexArray = glBindVertexArrayAPPLE;
1243             deleteVertexArrays = glDeleteVertexArraysAPPLE;
1244         } else {
1245             hasVertexArrayExtension = false;
1246             genVertexArrays = null;
1247             bindVertexArray = null;
1248             deleteVertexArrays = null;
1249         }
1250     }
1251 
1252     public override void create() {
1253         checkNotCreated();
1254         if (hasVertexArrayExtension) {
1255             // Generate the vao
1256             genVertexArrays(1, &id);
1257         }
1258         // Update state
1259         super.create();
1260         // Check for errors
1261         checkForGLError();
1262     }
1263 
1264     public override void destroy() {
1265         checkCreated();
1266         // Delete the indices buffer
1267         glDeleteBuffers(1, &indicesBufferID);
1268         // Delete the attribute buffers
1269         glDeleteBuffers(cast(uint) attributeBufferIDs.length, attributeBufferIDs.ptr);
1270         if (hasVertexArrayExtension) {
1271             // Delete the vao
1272             deleteVertexArrays(1, &id);
1273         } else {
1274             // Else delete the attribute properties
1275             attributeSizes = null;
1276             attributeTypes = null;
1277             attributeNormalizing = null;
1278         }
1279         // Reset the IDs and data
1280         indicesBufferID = 0;
1281         attributeBufferIDs = EMPTY_ARRAY.dup;
1282         attributeBufferSizes = EMPTY_ARRAY.dup;
1283         // Update the state
1284         super.destroy();
1285         // Check for errors
1286         checkForGLError();
1287     }
1288 
1289     public override void setData(VertexData vertexData) {
1290         checkCreated();
1291         // Generate a new indices buffer if we don't have one yet
1292         if (indicesBufferID == 0) {
1293             glGenBuffers(1, &indicesBufferID);
1294         }
1295         // Bind the indices buffer
1296         glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indicesBufferID);
1297         // Get the new count of indices
1298         uint newIndicesCount = vertexData.getIndicesCount();
1299         // If the new count is greater than or 50% smaller than the old one, we'll reallocate the memory
1300         // In the first case because we need more space, in the other to save space
1301         ubyte[] indicesBuffer = vertexData.getIndicesBuffer();
1302         if (newIndicesCount > indicesCount || newIndicesCount <= indicesCount * 0.5) {
1303             glBufferData(GL_ELEMENT_ARRAY_BUFFER, indicesBuffer.length, indicesBuffer.ptr, GL_STATIC_DRAW);
1304         } else {
1305             // Else, we replace the data with the new one, but we don't resize, so some old data might be left trailing in the buffer
1306             glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, indicesBuffer.length, indicesBuffer.ptr);
1307         }
1308         // Unbind the indices buffer
1309         glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
1310         // Update the count to the new one
1311         indicesCount = newIndicesCount;
1312         indicesDrawCount = indicesCount;
1313         // Ensure that the indices offset and count fits inside the valid part of the buffer
1314         indicesOffset = min(indicesOffset, indicesCount - 1);
1315         indicesDrawCount = indicesDrawCount - indicesOffset;
1316         // Bind the vao
1317         if (hasVertexArrayExtension) {
1318             bindVertexArray(id);
1319         }
1320         // Create a new array of attribute buffers ID of the correct size
1321         uint attributeCount = vertexData.getAttributeCount();
1322         uint[] newAttributeBufferIDs = new uint[attributeCount];
1323         // Copy all the old buffer IDs that will fit in the new array so we can reuse them
1324         size_t length = min(attributeBufferIDs.length, newAttributeBufferIDs.length);
1325         newAttributeBufferIDs[0 .. length] = attributeBufferIDs[0 .. length];
1326         // Delete any buffers that we don't need (new array is smaller than the previous one)
1327         int difference = cast(uint) (attributeBufferIDs.length - newAttributeBufferIDs.length);
1328         if (difference > 0) {
1329             glDeleteBuffers(difference, attributeBufferIDs[newAttributeBufferIDs.length .. attributeBufferIDs.length].ptr);
1330         } else if (difference < 0) {
1331             glGenBuffers(-difference, newAttributeBufferIDs[attributeBufferIDs.length .. newAttributeBufferIDs.length].ptr);
1332         }
1333         // Copy the old valid attribute buffer sizes
1334         uint[] newAttributeBufferSizes = new uint[attributeCount];
1335         length = min(attributeBufferSizes.length, newAttributeBufferSizes.length);
1336         newAttributeBufferSizes[0 .. length] = attributeBufferSizes[0 .. length];
1337         // If we don't have a vao, we have to save the properties manually
1338         if (!hasVertexArrayExtension) {
1339             attributeSizes = new uint[attributeCount];
1340             attributeTypes = new uint[attributeCount];
1341             attributeNormalizing = new bool[attributeCount];
1342         }
1343         // Upload the new vertex data
1344         foreach (uint i; 0 .. attributeCount) {
1345             VertexAttribute attribute = vertexData.getAttribute(i);
1346             ubyte[] attributeData = attribute.getData();
1347             // Get the current buffer size
1348             uint bufferSize = newAttributeBufferSizes[i];
1349             // Get the new buffer size
1350             uint newBufferSize = cast(uint) attributeData.length;
1351             // Bind the target buffer
1352             glBindBuffer(GL_ARRAY_BUFFER, newAttributeBufferIDs[i]);
1353             // If the new count is greater than or 50% smaller than the old one, we'll reallocate the memory
1354             if (newBufferSize > bufferSize || newBufferSize <= bufferSize * 0.5) {
1355                 glBufferData(GL_ARRAY_BUFFER, attributeData.length, attributeData.ptr, GL_STATIC_DRAW);
1356             } else {
1357                 // Else, we replace the data with the new one, but we don't resize, so some old data might be left trailing in the buffer
1358                 glBufferSubData(GL_ARRAY_BUFFER, 0, attributeData.length, attributeData.ptr);
1359             }
1360             // Update the buffer size to the new one
1361             newAttributeBufferSizes[i] = newBufferSize;
1362             // Next, we add the pointer to the data in the vao
1363             if (hasVertexArrayExtension) {
1364                 // As a float, normalized or not
1365                 glVertexAttribPointer(i, attribute.getSize(), attribute.getType().getGLConstant(), attribute.getUploadMode().normalize(), 0, null);
1366                 // Enable the attribute
1367                 glEnableVertexAttribArray(i);
1368             } else {
1369                 // Else we save the properties for rendering
1370                 attributeSizes[i] = attribute.getSize();
1371                 attributeTypes[i] = attribute.getType().getGLConstant();
1372                 attributeNormalizing[i] = attribute.getUploadMode().normalize();
1373             }
1374         }
1375         // Unbind the last vbo
1376         glBindBuffer(GL_ARRAY_BUFFER, 0);
1377         // Unbind the vao
1378         if (hasVertexArrayExtension) {
1379             bindVertexArray(0);
1380         }
1381         // Update the attribute buffer IDs to the new ones
1382         attributeBufferIDs = newAttributeBufferIDs;
1383         // Update the attribute buffer sizes to the new ones
1384         attributeBufferSizes = newAttributeBufferSizes;
1385         // Check for errors
1386         checkForGLError();
1387     }
1388 
1389     public override void setDrawingMode(DrawingMode mode) {
1390         if (mode is null) {
1391             throw new Exception("Drawing mode cannot be null");
1392         }
1393         this.drawingMode = mode;
1394     }
1395 
1396     public override void setPolygonMode(PolygonMode mode) {
1397         if (mode is null) {
1398             throw new Exception("Polygon mode cannot be null");
1399         }
1400         polygonMode = mode;
1401     }
1402 
1403     public override void setIndicesOffset(uint offset) {
1404         indicesOffset = min(offset, indicesCount - 1);
1405         indicesDrawCount = min(indicesDrawCount, indicesCount - indicesOffset);
1406     }
1407 
1408     public override void setIndicesCount(uint count) {
1409         if (count < 0) {
1410             indicesDrawCount = indicesCount;
1411         } else {
1412             indicesDrawCount = count;
1413         }
1414         indicesDrawCount = min(indicesDrawCount, indicesCount - indicesOffset);
1415     }
1416 
1417     public override void draw() {
1418         checkCreated();
1419         if (hasVertexArrayExtension) {
1420             // Bind the vao
1421             bindVertexArray(id);
1422         } else {
1423             // Enable the vertex attributes
1424             foreach (uint i; 0 .. cast(uint) attributeBufferIDs.length) {
1425                 // Bind the buffer
1426                 glBindBuffer(GL_ARRAY_BUFFER, attributeBufferIDs[i]);
1427                 // Define the attribute
1428                 glVertexAttribPointer(i, attributeSizes[i], attributeTypes[i], attributeNormalizing[i], 0, null);
1429                 // Enable it
1430                 glEnableVertexAttribArray(i);
1431             }
1432             // Unbind the last buffer
1433             glBindBuffer(GL_ARRAY_BUFFER, 0);
1434         }
1435         // Bind the index buffer
1436         glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indicesBufferID);
1437         // Set the polygon mode
1438         glPolygonMode(GL_FRONT_AND_BACK, polygonMode.getGLConstant());
1439         // Draw all indices with the provided mode
1440         glDrawElements(drawingMode.getGLConstant(), indicesDrawCount, GL_UNSIGNED_INT, cast(void*) (indicesOffset * INT.getByteSize()));
1441         // Unbind the indices buffer
1442         glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
1443         // Check for errors
1444         checkForGLError();
1445     }
1446 
1447     public gbaid.render.gl.GLVersion getGLVersion() {
1448         return GL20;
1449     }
1450 }