1 /*
2     Inochi2D Part
3 
4     Copyright © 2020, Inochi2D Project
5     Distributed under the 2-Clause BSD License, see LICENSE file.
6     
7     Authors: Luna Nielsen
8 */
9 module inochi2d.core.nodes.part;
10 import inochi2d.integration;
11 import inochi2d.fmt;
12 import inochi2d.core.nodes.drawable;
13 import inochi2d.core;
14 import inochi2d.math;
15 import bindbc.opengl;
16 import std.exception;
17 import std.algorithm.mutation : copy;
18 public import inochi2d.core.nodes.common;
19 import std.math : isNaN;
20 
21 public import inochi2d.core.meshdata;
22 
23 
24 package(inochi2d) {
25     private {
26         Shader partShader;
27         Shader partMaskShader;
28 
29         /* GLSL Uniforms (Normal) */
30         GLint mvp;
31         GLint offset;
32         GLint gopacity;
33         GLint gMultColor;
34         GLint gScreenColor;
35 
36         /* GLSL Uniforms (Masks) */
37         GLint mmvp;
38         GLint mthreshold;
39 
40         GLuint sVertexBuffer;
41         GLuint sUVBuffer;
42         GLuint sElementBuffer;
43     }
44 
45     void inInitPart() {
46         inRegisterNodeType!Part;
47 
48         version(InDoesRender) {
49             partShader = new Shader(import("basic/basic.vert"), import("basic/basic.frag"));
50             partMaskShader = new Shader(import("basic/basic.vert"), import("basic/basic-mask.frag"));
51 
52             mvp = partShader.getUniformLocation("mvp");
53             offset = partShader.getUniformLocation("offset");
54             gopacity = partShader.getUniformLocation("opacity");
55             gMultColor = partShader.getUniformLocation("multColor");
56             gScreenColor = partShader.getUniformLocation("screenColor");
57             
58             mmvp = partMaskShader.getUniformLocation("mvp");
59             mthreshold = partMaskShader.getUniformLocation("threshold");
60             
61             glGenBuffers(1, &sVertexBuffer);
62             glGenBuffers(1, &sUVBuffer);
63             glGenBuffers(1, &sElementBuffer);
64         }
65     }
66 }
67 
68 
69 /**
70     Creates a simple part that is sized after the texture given
71     part is created based on file path given.
72     Supported file types are: png, tga and jpeg
73 
74     This is unoptimal for normal use and should only be used
75     for real-time use when you want to add/remove parts on the fly
76 */
77 Part inCreateSimplePart(string file, Node parent = null) {
78     return inCreateSimplePart(ShallowTexture(file), parent, file);
79 }
80 
81 /**
82     Creates a simple part that is sized after the texture given
83 
84     This is unoptimal for normal use and should only be used
85     for real-time use when you want to add/remove parts on the fly
86 */
87 Part inCreateSimplePart(ShallowTexture texture, Node parent = null, string name = "New Part") {
88 	return inCreateSimplePart(new Texture(texture), parent, name);
89 }
90 
91 /**
92     Creates a simple part that is sized after the texture given
93 
94     This is unoptimal for normal use and should only be used
95     for real-time use when you want to add/remove parts on the fly
96 */
97 Part inCreateSimplePart(Texture tex, Node parent = null, string name = "New Part") {
98 	MeshData data = MeshData([
99 		vec2(-(tex.width/2), -(tex.height/2)),
100 		vec2(-(tex.width/2), tex.height/2),
101 		vec2(tex.width/2, -(tex.height/2)),
102 		vec2(tex.width/2, tex.height/2),
103 	], 
104 	[
105 		vec2(0, 0),
106 		vec2(0, 1),
107 		vec2(1, 0),
108 		vec2(1, 1),
109 	],
110 	[
111 		0, 1, 2,
112 		2, 1, 3
113 	]);
114 	Part p = new Part(data, [tex], parent);
115 	p.name = name;
116     return p;
117 }
118 
119 /**
120     Dynamic Mesh Part
121 */
122 @TypeId("Part")
123 class Part : Drawable {
124 private:
125     
126     GLuint uvbo;
127 
128     void updateUVs() {
129         version(InDoesRender) {
130             glBindBuffer(GL_ARRAY_BUFFER, uvbo);
131             glBufferData(GL_ARRAY_BUFFER, data.uvs.length*vec2.sizeof, data.uvs.ptr, GL_STATIC_DRAW);
132         }
133     }
134 
135     /*
136         RENDERING
137     */
138 
139     void drawSelf(bool isMask = false)() {
140 
141         // In some cases this may happen
142         if (textures.length == 0) return;
143 
144         // Bind the vertex array
145         incDrawableBindVAO();
146 
147         static if (isMask) {
148             partMaskShader.use();
149             partMaskShader.setUniform(offset, data.origin);
150             partMaskShader.setUniform(mmvp, inGetCamera().matrix * transform.matrix());
151             partMaskShader.setUniform(mthreshold, clamp(offsetMaskThreshold + maskAlphaThreshold, 0, 1));
152             glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
153         } else {
154             partShader.use();
155             partShader.setUniform(offset, data.origin);
156             partShader.setUniform(mvp, inGetCamera().matrix * transform.matrix());
157             partShader.setUniform(gopacity, clamp(offsetOpacity * opacity, 0, 1));
158             
159             vec3 clampedColor = tint;
160             if (!offsetTint.x.isNaN) clampedColor.x = clamp(tint.x*offsetTint.x, 0, 1);
161             if (!offsetTint.y.isNaN) clampedColor.y = clamp(tint.y*offsetTint.y, 0, 1);
162             if (!offsetTint.z.isNaN) clampedColor.z = clamp(tint.z*offsetTint.z, 0, 1);
163             partShader.setUniform(gMultColor, clampedColor);
164 
165             clampedColor = screenTint;
166             if (!offsetScreenTint.x.isNaN) clampedColor.x = clamp(screenTint.x+offsetScreenTint.x, 0, 1);
167             if (!offsetScreenTint.y.isNaN) clampedColor.y = clamp(screenTint.y+offsetScreenTint.y, 0, 1);
168             if (!offsetScreenTint.z.isNaN) clampedColor.z = clamp(screenTint.z+offsetScreenTint.z, 0, 1);
169             partShader.setUniform(gScreenColor, clampedColor);
170             inSetBlendMode(blendingMode);
171 
172             // TODO: EXT MODE
173         }
174 
175         // Bind the texture
176         textures[0].bind();
177 
178         // Enable points array
179         glEnableVertexAttribArray(0);
180         glBindBuffer(GL_ARRAY_BUFFER, vbo);
181         glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, null);
182 
183         // Enable UVs array
184         glEnableVertexAttribArray(1); // uvs
185         glBindBuffer(GL_ARRAY_BUFFER, uvbo);
186         glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, null);
187 
188         // Enable deform array
189         glEnableVertexAttribArray(2); // deforms
190         glBindBuffer(GL_ARRAY_BUFFER, dbo);
191         glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, null);
192 
193         // Bind index buffer
194         this.bindIndex();
195 
196         // Disable the vertex attribs after use
197         glDisableVertexAttribArray(0);
198         glDisableVertexAttribArray(1);
199         glDisableVertexAttribArray(2);
200     }
201 
202 protected:
203     override
204     void renderMask(bool dodge = false) {
205         
206         // Enable writing to stencil buffer and disable writing to color buffer
207         glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
208         glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
209         glStencilFunc(GL_ALWAYS, dodge ? 0 : 1, 0xFF);
210         glStencilMask(0xFF);
211 
212         // Draw ourselves to the stencil buffer
213         drawSelf!true();
214 
215         // Disable writing to stencil buffer and enable writing to color buffer
216         glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
217     }
218 
219     override
220     string typeId() { return "Part"; }
221 
222     /**
223         Allows serializing self data (with pretty serializer)
224     */
225     override
226     void serializeSelf(ref InochiSerializer serializer) {
227         super.serializeSelf(serializer);
228         version (InDoesRender) {
229             if (inIsINPMode()) {
230                 serializer.putKey("textures");
231                 auto state = serializer.arrayBegin();
232                     foreach(texture; textures) {
233                         ptrdiff_t index = puppet.getTextureSlotIndexFor(texture);
234                         if (index >= 0) {
235                             serializer.elemBegin;
236                             serializer.putValue(cast(size_t)index);
237                         }
238                     }
239                 serializer.arrayEnd(state);
240             } else {
241                 serializer.putKey("textures");
242                 auto state = serializer.arrayBegin();
243                     serializer.elemBegin;
244                     serializer.putValue(name);
245                 serializer.arrayEnd(state);
246             }
247         }
248 
249         serializer.putKey("blend_mode");
250         serializer.serializeValue(blendingMode);
251         
252         serializer.putKey("tint");
253         tint.serialize(serializer);
254 
255         serializer.putKey("screenTint");
256         screenTint.serialize(serializer);
257 
258         if (masks.length > 0) {
259             serializer.putKey("masks");
260             auto state = serializer.arrayBegin();
261                 foreach(m; masks) {
262                     serializer.elemBegin;
263                     serializer.serializeValue(m);
264                 }
265             serializer.arrayEnd(state);
266         }
267 
268         serializer.putKey("mask_threshold");
269         serializer.putValue(maskAlphaThreshold);
270 
271         serializer.putKey("opacity");
272         serializer.putValue(opacity);
273     }
274 
275     /**
276         Allows serializing self data (with compact serializer)
277     */
278     override
279     void serializeSelf(ref InochiSerializerCompact serializer) {
280         super.serializeSelf(serializer);
281         
282         if (inIsINPMode()) {
283             serializer.putKey("textures");
284             auto state = serializer.arrayBegin();
285                 foreach(texture; textures) {
286                     ptrdiff_t index = puppet.getTextureSlotIndexFor(texture);
287                     if (index >= 0) {
288                         serializer.elemBegin;
289                         serializer.putValue(cast(size_t)index);
290                     }
291                 }
292             serializer.arrayEnd(state);
293         } else {
294             serializer.putKey("textures");
295             auto state = serializer.arrayBegin();
296                 serializer.elemBegin;
297                 serializer.putValue(name);
298             serializer.arrayEnd(state);
299         }
300 
301         serializer.putKey("blend_mode");
302         serializer.serializeValue(blendingMode);
303 
304         serializer.putKey("tint");
305         tint.serialize(serializer);
306 
307         serializer.putKey("screenTint");
308         screenTint.serialize(serializer);
309 
310         if (masks.length > 0) {
311             serializer.putKey("masks");
312             auto state = serializer.arrayBegin();
313                 foreach(m; masks) {
314                     serializer.elemBegin;
315                     serializer.serializeValue(m);
316                 }
317             serializer.arrayEnd(state);
318         }
319 
320 
321         serializer.putKey("mask_threshold");
322         serializer.putValue(maskAlphaThreshold);
323 
324         serializer.putKey("opacity");
325         serializer.putValue(opacity);
326 
327     }
328 
329     override
330     SerdeException deserializeFromFghj(Fghj data) {
331         super.deserializeFromFghj(data);
332 
333     
334         version(InRenderless) {
335             if (inIsINPMode()) {
336                 foreach(texElement; data["textures"].byElement) {
337                     uint textureId;
338                     texElement.deserializeValue(textureId);
339                     textureIds ~= textureId;
340                 }
341             } else {
342                 assert(0, "Raw Inochi2D JSON not supported in renderless mode");
343             }
344             
345             // Do nothing in this instance
346         } else {
347             if (inIsINPMode()) {
348 
349                 foreach(texElement; data["textures"].byElement) {
350                     uint textureId;
351                     texElement.deserializeValue(textureId);
352                     textureIds ~= textureId;
353                     this.textures ~= inGetTextureFromId(textureId);
354                 }
355             } else {
356 
357                 // TODO: Index textures by ID
358                 string texName;
359                 auto elements = data["textures"].byElement;
360                 if (!elements.empty) {
361                     if (auto exc = elements.front.deserializeValue(texName)) return exc;
362                     this.textures = [new Texture(texName)];
363                 }
364             }
365         }
366 
367         data["opacity"].deserializeValue(this.opacity);
368         data["mask_threshold"].deserializeValue(this.maskAlphaThreshold);
369 
370         // Older models may not have tint
371         if (!data["tint"].isEmpty) deserialize(tint, data["tint"]);
372 
373         // Older models may not have screen tint
374         if (!data["screenTint"].isEmpty) deserialize(screenTint, data["screenTint"]);
375 
376         // Older models may not have blend mode
377         if (!data["blend_mode"].isEmpty) data["blend_mode"].deserializeValue(this.blendingMode);
378 
379         if (!data["masked_by"].isEmpty) {
380             MaskingMode mode;
381             data["mask_mode"].deserializeValue(mode);
382 
383             // Go every masked part
384             foreach(imask; data["masked_by"].byElement) {
385                 uint uuid;
386                 if (auto exc = imask.deserializeValue(uuid)) return exc;
387                 this.masks ~= MaskBinding(uuid, mode, null);
388             }
389         }
390 
391         if (!data["masks"].isEmpty) {
392             data["masks"].deserializeValue(this.masks);
393         }
394 
395         // Update indices and vertices
396         this.updateUVs();
397         return null;
398     }
399 
400     //
401     //      PARAMETER OFFSETS
402     //
403     float offsetMaskThreshold = 0;
404     float offsetOpacity = 1;
405     vec3 offsetTint = vec3(0);
406     vec3 offsetScreenTint = vec3(0);
407 
408     // TODO: Cache this
409     size_t maskCount() {
410         size_t c;
411         foreach(m; masks) if (m.mode == MaskingMode.Mask) c++;
412         return c;
413     }
414 
415     size_t dodgeCount() {
416         size_t c;
417         foreach(m; masks) if (m.mode == MaskingMode.DodgeMask) c++;
418         return c;
419     }
420 
421 public:
422     /**
423         List of textures this part can use
424 
425         TODO: use more than texture 0
426     */
427     Texture[] textures;
428 
429     /**
430         List of texture IDs
431     */
432     int[] textureIds;
433 
434     /**
435         List of masks to apply
436     */
437     MaskBinding[] masks;
438 
439     /**
440         Blending mode
441     */
442     BlendMode blendingMode = BlendMode.Normal;
443     
444     /**
445         Alpha Threshold for the masking system, the higher the more opaque pixels will be discarded in the masking process
446     */
447     float maskAlphaThreshold = 0.5;
448 
449     /**
450         Opacity of the mesh
451     */
452     float opacity = 1;
453 
454     /**
455         Multiplicative tint color
456     */
457     vec3 tint = vec3(1, 1, 1);
458 
459     /**
460         Screen tint color
461     */
462     vec3 screenTint = vec3(0, 0, 0);
463 
464     /**
465         Gets the active texture
466     */
467     Texture activeTexture() {
468         return textures[0];
469     }
470 
471     /**
472         Constructs a new part
473     */
474     this(MeshData data, Texture[] textures, Node parent = null) {
475         this(data, textures, inCreateUUID(), parent);
476     }
477 
478     /**
479         Constructs a new part
480     */
481     this(Node parent = null) {
482         super(parent);
483         
484         version(InDoesRender) glGenBuffers(1, &uvbo);
485     }
486 
487     /**
488         Constructs a new part
489     */
490     this(MeshData data, Texture[] textures, uint uuid, Node parent = null) {
491         super(data, uuid, parent);
492         this.textures = textures;
493 
494         version(InDoesRender) {
495             glGenBuffers(1, &uvbo);
496 
497             mvp = partShader.getUniformLocation("mvp");
498             gopacity = partShader.getUniformLocation("opacity");
499             
500             mmvp = partMaskShader.getUniformLocation("mvp");
501             mthreshold = partMaskShader.getUniformLocation("threshold");
502         }
503 
504         this.updateUVs();
505     }
506 
507     override
508     bool hasParam(string key) {
509         if (super.hasParam(key)) return true;
510 
511         switch(key) {
512             case "alphaThreshold":
513             case "opacity":
514             case "tint.r":
515             case "tint.g":
516             case "tint.b":
517             case "screenTint.r":
518             case "screenTint.g":
519             case "screenTint.b":
520                 return true;
521             default:
522                 return false;
523         }
524     }
525 
526     override
527     float getDefaultValue(string key) {
528         // Skip our list of our parent already handled it
529         float def = super.getDefaultValue(key);
530         if (!isNaN(def)) return def;
531 
532         switch(key) {
533             case "alphaThreshold":
534                 return 0;
535             case "opacity":
536             case "tint.r":
537             case "tint.g":
538             case "tint.b":
539                 return 1;
540             case "screenTint.r":
541             case "screenTint.g":
542             case "screenTint.b":
543                 return 0;
544             default: return float();
545         }
546     }
547 
548     override
549     bool setValue(string key, float value) {
550         
551         // Skip our list of our parent already handled it
552         if (super.setValue(key, value)) return true;
553 
554         switch(key) {
555             case "alphaThreshold":
556                 offsetMaskThreshold = value;
557                 return true;
558             case "opacity":
559                 offsetOpacity = value;
560                 return true;
561             case "tint.r":
562                 offsetTint.x = value;
563                 return true;
564             case "tint.g":
565                 offsetTint.y = value;
566                 return true;
567             case "tint.b":
568                 offsetTint.z = value;
569                 return true;
570             case "screenTint.r":
571                 offsetScreenTint.x = value;
572                 return true;
573             case "screenTint.g":
574                 offsetScreenTint.y = value;
575                 return true;
576             case "screenTint.b":
577                 offsetScreenTint.z = value;
578                 return true;
579             default: return false;
580         }
581     }
582 
583     bool isMaskedBy(Drawable drawable) {
584         foreach(mask; masks) {
585             if (mask.maskSrc.uuid == drawable.uuid) return true;
586         }
587         return false;
588     }
589 
590     ptrdiff_t getMaskIdx(Drawable drawable) {
591         if (drawable is null) return -1;
592         foreach(i, ref mask; masks) {
593             if (mask.maskSrc.uuid == drawable.uuid) return i;
594         }
595         return -1;
596     }
597 
598     ptrdiff_t getMaskIdx(uint uuid) {
599         foreach(i, ref mask; masks) {
600             if (mask.maskSrc.uuid == uuid) return i;
601         }
602         return -1;
603     }
604 
605     override
606     void beginUpdate() {
607         offsetMaskThreshold = 0;
608         offsetOpacity = 1;
609         offsetTint = vec3(1, 1, 1);
610         offsetScreenTint = vec3(0, 0, 0);
611         super.beginUpdate();
612     }
613     
614     override
615     void rebuffer(ref MeshData data) {
616         super.rebuffer(data);
617         this.updateUVs();
618     }
619 
620     override
621     void draw() {
622         if (!enabled) return;
623         this.drawOne();
624 
625         foreach(child; children) {
626             child.draw();
627         }
628     }
629 
630     override
631     void drawOne() {
632         version (InDoesRender) {
633             if (!enabled) return;
634             if (!data.isReady) return; // Yeah, don't even try
635             
636             size_t cMasks = maskCount;
637 
638             if (masks.length > 0) {
639                 import std.stdio : writeln;
640                 inBeginMask(cMasks > 0);
641 
642                 foreach(ref mask; masks) {
643                     mask.maskSrc.renderMask(mask.mode == MaskingMode.DodgeMask);
644                 }
645 
646                 inBeginMaskContent();
647 
648                 // We are the content
649                 this.drawSelf();
650 
651                 inEndMask();
652                 return;
653             }
654 
655             this.drawSelf();
656         }
657         super.drawOne();
658     }
659 
660     override
661     void drawOneDirect(bool forMasking) {
662         if (forMasking) this.drawSelf!true();
663         else this.drawSelf!false();
664     }
665 
666     override
667     void finalize() {
668         super.finalize();
669         foreach(i; 0..masks.length) {
670             if (Drawable nMask = puppet.find!Drawable(masks[i].maskSrcUUID)) {
671                 masks[i].maskSrc = nMask;
672             }
673         }
674     }
675 }
676 
677 /**
678     Draws a texture at the transform of the specified part
679 */
680 void inDrawTextureAtPart(Texture texture, Part part) {
681     const float texWidthP = texture.width()/2;
682     const float texHeightP = texture.height()/2;
683 
684     // Bind the vertex array
685     incDrawableBindVAO();
686 
687     partShader.use();
688     partShader.setUniform(mvp, 
689         inGetCamera().matrix * 
690         mat4.translation(vec3(part.transform.matrix() * vec4(1, 1, 1, 1)))
691     );
692     partShader.setUniform(gopacity, part.opacity);
693     partShader.setUniform(gMultColor, part.tint);
694     partShader.setUniform(gScreenColor, part.screenTint);
695     
696     // Bind the texture
697     texture.bind();
698 
699     // Enable points array
700     glEnableVertexAttribArray(0);
701     glBindBuffer(GL_ARRAY_BUFFER, sVertexBuffer);
702     glBufferData(GL_ARRAY_BUFFER, 4*vec2.sizeof, [
703         -texWidthP, -texHeightP,
704         texWidthP, -texHeightP,
705         -texWidthP, texHeightP,
706         texWidthP, texHeightP,
707     ].ptr, GL_STATIC_DRAW);
708     glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, null);
709 
710     // Enable UVs array
711     glEnableVertexAttribArray(1); // uvs
712     glBindBuffer(GL_ARRAY_BUFFER, sUVBuffer);
713     glBufferData(GL_ARRAY_BUFFER, 4*vec2.sizeof, [
714         0, 0,
715         1, 0,
716         0, 1,
717         1, 1,
718     ].ptr, GL_STATIC_DRAW);
719     glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, null);
720 
721     // Bind element array and draw our mesh
722     glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, sElementBuffer);
723     glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6*ushort.sizeof, (cast(ushort[])[
724         0u, 1u, 2u,
725         2u, 1u, 3u
726     ]).ptr, GL_STATIC_DRAW);
727     glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, null);
728 
729     // Disable the vertex attribs after use
730     glDisableVertexAttribArray(0);
731     glDisableVertexAttribArray(1);
732 }
733 
734 /**
735     Draws a texture at the transform of the specified part
736 */
737 void inDrawTextureAtPosition(Texture texture, vec2 position, float opacity = 1, vec3 color = vec3(1, 1, 1), vec3 screenColor = vec3(1, 1, 1)) {
738     const float texWidthP = texture.width()/2;
739     const float texHeightP = texture.height()/2;
740 
741     // Bind the vertex array
742     incDrawableBindVAO();
743 
744     partShader.use();
745     partShader.setUniform(mvp, 
746         inGetCamera().matrix * 
747         mat4.scaling(1, 1, 1) * 
748         mat4.translation(vec3(position, 0))
749     );
750     partShader.setUniform(gopacity, opacity);
751     partShader.setUniform(gMultColor, color);
752     partShader.setUniform(gScreenColor, screenColor);
753     
754     // Bind the texture
755     texture.bind();
756 
757     // Enable points array
758     glEnableVertexAttribArray(0);
759     glBindBuffer(GL_ARRAY_BUFFER, sVertexBuffer);
760     glBufferData(GL_ARRAY_BUFFER, 4*vec2.sizeof, [
761         -texWidthP, -texHeightP,
762         texWidthP, -texHeightP,
763         -texWidthP, texHeightP,
764         texWidthP, texHeightP,
765     ].ptr, GL_STATIC_DRAW);
766     glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, null);
767 
768     // Enable UVs array
769     glEnableVertexAttribArray(1); // uvs
770     glBindBuffer(GL_ARRAY_BUFFER, sUVBuffer);
771     glBufferData(GL_ARRAY_BUFFER, 4*vec2.sizeof, (cast(float[])[
772         0, 0,
773         1, 0,
774         0, 1,
775         1, 1,
776     ]).ptr, GL_STATIC_DRAW);
777     glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, null);
778 
779     // Bind element array and draw our mesh
780     glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, sElementBuffer);
781     glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6*ushort.sizeof, (cast(ushort[])[
782         0u, 1u, 2u,
783         2u, 1u, 3u
784     ]).ptr, GL_STATIC_DRAW);
785     glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, null);
786 
787     // Disable the vertex attribs after use
788     glDisableVertexAttribArray(0);
789     glDisableVertexAttribArray(1);
790 }