1 /*
2  * Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
3  *
4  * This software is provided 'as-is', without any express or implied
5  * warranty.  In no event will the authors be held liable for any damages
6  * arising from the use of this software.
7  * Permission is granted to anyone to use this software for any purpose,
8  * including commercial applications, and to alter it and redistribute it
9  * freely, subject to the following restrictions:
10  * 1. The origin of this software must not be misrepresented; you must not
11  *    claim that you wrote the original software. If you use this software
12  *    in a product, an acknowledgment in the product documentation would be
13  *    appreciated but is not required.
14  * 2. Altered source versions must be plainly marked as such, and must not be
15  *    misrepresented as being the original software.
16  * 3. This notice may not be removed or altered from any source distribution.
17  */
18 module imgui.gl3_renderer;
19 
20 import core.stdc.stdlib;
21 import core.stdc.string;
22 
23 import std.math;
24 import std.stdio;
25 
26 version(imgui_gl_glad)
27 {
28     import glad.gl.all;
29     import glad.gl.loader;
30 }
31 version(imgui_gl_derelict)
32 {
33     import derelict.opengl3.gl3;
34 }
35 
36 import imgui.api;
37 import imgui.engine;
38 import imgui.stdb_truetype;
39 
40 private:
41 // Draw up to 65536 unicode glyphs.  What this will actually do is draw *only glyphs the
42 // font supports* until it will run out of glyphs or texture space (determined by
43 // g_font_texture_size).  The actual number of glyphs will be in thousands (ASCII is
44 // guaranteed, the rest will depend mainly on what the font supports, e.g. if it
45 // supports common European characters such as á or š they will be there because they
46 // are "early" in Unicode)
47 //
48 // Note that g_cdata uses memory of stbtt_bakedchar.sizeof * MAX_CHARACTER_COUNT which
49 // at the moment is 20 * 65536 or 1.25 MiB.
50 enum MAX_CHARACTER_COUNT = 1024 * 16 * 4;
51 enum FIRST_CHARACTER     = 32;
52 
53 
54 
55 /** Globals start. */
56 
57 // A 1024x1024 font texture takes 1MiB of memory, and should be enough for thousands of
58 // glyphs (at the fixed 15.0f size imgui uses).
59 //
60 // Some examples:
61 //
62 // =================================================== ============ =============================
63 // Font                                                Texture size Glyps fit
64 // =================================================== ============ =============================
65 // GentiumPlus-R                                       512x512      2550 (all glyphs in the font)
66 // GentiumPlus-R                                       256x256      709
67 // DroidSans (the small version included for examples) 512x512      903 (all glyphs in the font)
68 // DroidSans (the small version included for examples) 256x256      497
69 // =================================================== ============ =============================
70 //
71 // This was measured after the optimization to reuse null character glyph, which is in
72 // BakeFontBitmap in stdb_truetype.d
73 __gshared uint g_font_texture_size = 1024;
74 __gshared float g_tempCoords[TEMP_COORD_COUNT * 2];
75 __gshared float g_tempNormals[TEMP_COORD_COUNT * 2];
76 __gshared float g_tempVertices[TEMP_COORD_COUNT * 12 + (TEMP_COORD_COUNT - 2) * 6];
77 __gshared float g_tempTextureCoords[TEMP_COORD_COUNT * 12 + (TEMP_COORD_COUNT - 2) * 6];
78 __gshared float g_tempColors[TEMP_COORD_COUNT * 24 + (TEMP_COORD_COUNT - 2) * 12];
79 __gshared float g_circleVerts[CIRCLE_VERTS * 2];
80 __gshared uint g_max_character_count = MAX_CHARACTER_COUNT;
81 __gshared stbtt_bakedchar[MAX_CHARACTER_COUNT] g_cdata;
82 __gshared GLuint g_ftex     = 0;
83 __gshared GLuint g_whitetex = 0;
84 __gshared GLuint g_vao      = 0;
85 __gshared GLuint[3] g_vbos  = [0, 0, 0];
86 __gshared GLuint g_program = 0;
87 __gshared GLuint g_programViewportLocation = 0;
88 __gshared GLuint g_programTextureLocation  = 0;
89 
90 /** Globals end. */
91 
92 enum TEMP_COORD_COUNT = 100;
93 enum int CIRCLE_VERTS = 8 * 4;
94 immutable float[4] g_tabStops = [150, 210, 270, 330];
95 
96 package:
97 
98 uint maxCharacterCount() @trusted nothrow @nogc
99 {
100     return g_max_character_count;
101 }
102 
103 void imguifree(void* ptr, void* /*userptr*/)
104 {
105     free(ptr);
106 }
107 
108 void* imguimalloc(size_t size, void* /*userptr*/)
109 {
110     return malloc(size);
111 }
112 
113 uint toPackedRGBA(RGBA color)
114 {
115     return (color.r) | (color.g << 8) | (color.b << 16) | (color.a << 24);
116 }
117 
118 void drawPolygon(const(float)* coords, uint numCoords, float r, uint col)
119 {
120     if (numCoords > TEMP_COORD_COUNT)
121         numCoords = TEMP_COORD_COUNT;
122 
123     for (uint i = 0, j = numCoords - 1; i < numCoords; j = i++)
124     {
125         const(float)* v0 = &coords[j * 2];
126         const(float)* v1 = &coords[i * 2];
127         float dx        = v1[0] - v0[0];
128         float dy        = v1[1] - v0[1];
129         float d         = sqrt(dx * dx + dy * dy);
130 
131         if (d > 0)
132         {
133             d   = 1.0f / d;
134             dx *= d;
135             dy *= d;
136         }
137         g_tempNormals[j * 2 + 0] = dy;
138         g_tempNormals[j * 2 + 1] = -dx;
139     }
140 
141     const float[4] colf      = [cast(float)(col & 0xff) / 255.0, cast(float)((col >> 8) & 0xff) / 255.0, cast(float)((col >> 16) & 0xff) / 255.0, cast(float)((col >> 24) & 0xff) / 255.0];
142     const float[4] colTransf = [cast(float)(col & 0xff) / 255.0, cast(float)((col >> 8) & 0xff) / 255.0, cast(float)((col >> 16) & 0xff) / 255.0, 0];
143 
144     for (uint i = 0, j = numCoords - 1; i < numCoords; j = i++)
145     {
146         float dlx0 = g_tempNormals[j * 2 + 0];
147         float dly0 = g_tempNormals[j * 2 + 1];
148         float dlx1 = g_tempNormals[i * 2 + 0];
149         float dly1 = g_tempNormals[i * 2 + 1];
150         float dmx  = (dlx0 + dlx1) * 0.5f;
151         float dmy  = (dly0 + dly1) * 0.5f;
152         float dmr2 = dmx * dmx + dmy * dmy;
153 
154         if (dmr2 > 0.000001f)
155         {
156             float scale = 1.0f / dmr2;
157 
158             if (scale > 10.0f)
159                 scale = 10.0f;
160             dmx *= scale;
161             dmy *= scale;
162         }
163         g_tempCoords[i * 2 + 0] = coords[i * 2 + 0] + dmx * r;
164         g_tempCoords[i * 2 + 1] = coords[i * 2 + 1] + dmy * r;
165     }
166 
167     int vSize  = numCoords * 12 + (numCoords - 2) * 6;
168     int uvSize = numCoords * 2 * 6 + (numCoords - 2) * 2 * 3;
169     int cSize  = numCoords * 4 * 6 + (numCoords - 2) * 4 * 3;
170     float* v   = g_tempVertices.ptr;
171     float* uv  = g_tempTextureCoords.ptr;
172     memset(uv, 0, uvSize * float.sizeof);
173     float* c = g_tempColors.ptr;
174     memset(c, 1, cSize * float.sizeof);
175 
176     float* ptrV = v;
177     float* ptrC = c;
178 
179     for (uint i = 0, j = numCoords - 1; i < numCoords; j = i++)
180     {
181         *ptrV       = coords[i * 2];
182         *(ptrV + 1) = coords[i * 2 + 1];
183         ptrV       += 2;
184         *ptrV       = coords[j * 2];
185         *(ptrV + 1) = coords[j * 2 + 1];
186         ptrV       += 2;
187         *ptrV       = g_tempCoords[j * 2];
188         *(ptrV + 1) = g_tempCoords[j * 2 + 1];
189         ptrV       += 2;
190         *ptrV       = g_tempCoords[j * 2];
191         *(ptrV + 1) = g_tempCoords[j * 2 + 1];
192         ptrV       += 2;
193         *ptrV       = g_tempCoords[i * 2];
194         *(ptrV + 1) = g_tempCoords[i * 2 + 1];
195         ptrV       += 2;
196         *ptrV       = coords[i * 2];
197         *(ptrV + 1) = coords[i * 2 + 1];
198         ptrV       += 2;
199 
200         *ptrC       = colf[0];
201         *(ptrC + 1) = colf[1];
202         *(ptrC + 2) = colf[2];
203         *(ptrC + 3) = colf[3];
204         ptrC       += 4;
205         *ptrC       = colf[0];
206         *(ptrC + 1) = colf[1];
207         *(ptrC + 2) = colf[2];
208         *(ptrC + 3) = colf[3];
209         ptrC       += 4;
210         *ptrC       = colTransf[0];
211         *(ptrC + 1) = colTransf[1];
212         *(ptrC + 2) = colTransf[2];
213         *(ptrC + 3) = colTransf[3];
214         ptrC       += 4;
215         *ptrC       = colTransf[0];
216         *(ptrC + 1) = colTransf[1];
217         *(ptrC + 2) = colTransf[2];
218         *(ptrC + 3) = colTransf[3];
219         ptrC       += 4;
220         *ptrC       = colTransf[0];
221         *(ptrC + 1) = colTransf[1];
222         *(ptrC + 2) = colTransf[2];
223         *(ptrC + 3) = colTransf[3];
224         ptrC       += 4;
225         *ptrC       = colf[0];
226         *(ptrC + 1) = colf[1];
227         *(ptrC + 2) = colf[2];
228         *(ptrC + 3) = colf[3];
229         ptrC       += 4;
230     }
231 
232     for (uint i = 2; i < numCoords; ++i)
233     {
234         *ptrV       = coords[0];
235         *(ptrV + 1) = coords[1];
236         ptrV       += 2;
237         *ptrV       = coords[(i - 1) * 2];
238         *(ptrV + 1) = coords[(i - 1) * 2 + 1];
239         ptrV       += 2;
240         *ptrV       = coords[i * 2];
241         *(ptrV + 1) = coords[i * 2 + 1];
242         ptrV       += 2;
243 
244         *ptrC       = colf[0];
245         *(ptrC + 1) = colf[1];
246         *(ptrC + 2) = colf[2];
247         *(ptrC + 3) = colf[3];
248         ptrC       += 4;
249         *ptrC       = colf[0];
250         *(ptrC + 1) = colf[1];
251         *(ptrC + 2) = colf[2];
252         *(ptrC + 3) = colf[3];
253         ptrC       += 4;
254         *ptrC       = colf[0];
255         *(ptrC + 1) = colf[1];
256         *(ptrC + 2) = colf[2];
257         *(ptrC + 3) = colf[3];
258         ptrC       += 4;
259     }
260 
261     glBindTexture(GL_TEXTURE_2D, g_whitetex);
262 
263     glBindVertexArray(g_vao);
264     glBindBuffer(GL_ARRAY_BUFFER, g_vbos[0]);
265     glBufferData(GL_ARRAY_BUFFER, vSize * float.sizeof, v, GL_STATIC_DRAW);
266     glBindBuffer(GL_ARRAY_BUFFER, g_vbos[1]);
267     glBufferData(GL_ARRAY_BUFFER, uvSize * float.sizeof, uv, GL_STATIC_DRAW);
268     glBindBuffer(GL_ARRAY_BUFFER, g_vbos[2]);
269     glBufferData(GL_ARRAY_BUFFER, cSize * float.sizeof, c, GL_STATIC_DRAW);
270     glDrawArrays(GL_TRIANGLES, 0, (numCoords * 2 + numCoords - 2) * 3);
271 }
272 
273 void drawRect(float x, float y, float w, float h, float fth, uint col)
274 {
275     const float verts[4 * 2] =
276     [
277         x + 0.5f, y + 0.5f,
278         x + w - 0.5f, y + 0.5f,
279         x + w - 0.5f, y + h - 0.5f,
280         x + 0.5f, y + h - 0.5f,
281     ];
282     drawPolygon(verts.ptr, 4, fth, col);
283 }
284 
285 /*
286    void drawEllipse(float x, float y, float w, float h, float fth, uint col)
287    {
288         float verts[CIRCLE_VERTS*2];
289         const(float)* cverts = g_circleVerts;
290         float* v = verts;
291 
292         for (int i = 0; i < CIRCLE_VERTS; ++i)
293         {
294  * v++ = x + cverts[i*2]*w;
295  * v++ = y + cverts[i*2+1]*h;
296         }
297 
298         drawPolygon(verts, CIRCLE_VERTS, fth, col);
299    }
300  */
301 
302 void drawRoundedRect(float x, float y, float w, float h, float r, float fth, uint col)
303 {
304     const uint n = CIRCLE_VERTS / 4;
305     float verts[(n + 1) * 4 * 2];
306     const(float)* cverts = g_circleVerts.ptr;
307     float* v = verts.ptr;
308 
309     for (uint i = 0; i <= n; ++i)
310     {
311         *v++ = x + w - r + cverts[i * 2] * r;
312         *v++ = y + h - r + cverts[i * 2 + 1] * r;
313     }
314 
315     for (uint i = n; i <= n * 2; ++i)
316     {
317         *v++ = x + r + cverts[i * 2] * r;
318         *v++ = y + h - r + cverts[i * 2 + 1] * r;
319     }
320 
321     for (uint i = n * 2; i <= n * 3; ++i)
322     {
323         *v++ = x + r + cverts[i * 2] * r;
324         *v++ = y + r + cverts[i * 2 + 1] * r;
325     }
326 
327     for (uint i = n * 3; i < n * 4; ++i)
328     {
329         *v++ = x + w - r + cverts[i * 2] * r;
330         *v++ = y + r + cverts[i * 2 + 1] * r;
331     }
332 
333     *v++ = x + w - r + cverts[0] * r;
334     *v++ = y + r + cverts[1] * r;
335 
336     drawPolygon(verts.ptr, (n + 1) * 4, fth, col);
337 }
338 
339 void drawLine(float x0, float y0, float x1, float y1, float r, float fth, uint col)
340 {
341     float dx = x1 - x0;
342     float dy = y1 - y0;
343     float d  = sqrt(dx * dx + dy * dy);
344 
345     if (d > 0.0001f)
346     {
347         d   = 1.0f / d;
348         dx *= d;
349         dy *= d;
350     }
351     float nx = dy;
352     float ny = -dx;
353     float verts[4 * 2];
354     r -= fth;
355     r *= 0.5f;
356 
357     if (r < 0.01f)
358         r = 0.01f;
359     dx *= r;
360     dy *= r;
361     nx *= r;
362     ny *= r;
363 
364     verts[0] = x0 - dx - nx;
365     verts[1] = y0 - dy - ny;
366 
367     verts[2] = x0 - dx + nx;
368     verts[3] = y0 - dy + ny;
369 
370     verts[4] = x1 + dx + nx;
371     verts[5] = y1 + dy + ny;
372 
373     verts[6] = x1 + dx - nx;
374     verts[7] = y1 + dy - ny;
375 
376     drawPolygon(verts.ptr, 4, fth, col);
377 }
378 
379 bool imguiRenderGLInit(const(char)[] fontpath, const uint fontTextureSize)
380 {
381     for (int i = 0; i < CIRCLE_VERTS; ++i)
382     {
383         float a = cast(float)i / cast(float)CIRCLE_VERTS * PI * 2;
384         g_circleVerts[i * 2 + 0] = cos(a);
385         g_circleVerts[i * 2 + 1] = sin(a);
386     }
387 
388     // Load font.
389     auto file = File(cast(string)fontpath, "rb");
390     g_font_texture_size = fontTextureSize;
391     FILE* fp = file.getFP();
392 
393     if (!fp)
394         return false;
395     fseek(fp, 0, SEEK_END);
396     size_t size = cast(size_t)ftell(fp);
397     fseek(fp, 0, SEEK_SET);
398 
399     ubyte* ttfBuffer = cast(ubyte*)malloc(size);
400 
401     if (!ttfBuffer)
402     {
403         return false;
404     }
405 
406     fread(ttfBuffer, 1, size, fp);
407     // fclose(fp);
408     fp = null;
409 
410     ubyte* bmap = cast(ubyte*)malloc(g_font_texture_size * g_font_texture_size);
411 
412     if (!bmap)
413     {
414         free(ttfBuffer);
415         return false;
416     }
417 
418     const result = stbtt_BakeFontBitmap(ttfBuffer, 0, 15.0f, bmap,
419                                         g_font_texture_size, g_font_texture_size,
420                                         FIRST_CHARACTER, g_max_character_count, g_cdata.ptr);
421     // If result is negative, we baked less than max characters so update the max
422     // character count.
423     if(result < 0)
424     {
425         g_max_character_count = -result;
426     }
427 
428     // can free ttf_buffer at this point
429     glGenTextures(1, &g_ftex);
430     glBindTexture(GL_TEXTURE_2D, g_ftex);
431     glTexImage2D(GL_TEXTURE_2D, 0, GL_RED,
432                  g_font_texture_size, g_font_texture_size,
433                  0, GL_RED, GL_UNSIGNED_BYTE, bmap);
434     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
435     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
436 
437     // can free ttf_buffer at this point
438     ubyte white_alpha = 255;
439     glGenTextures(1, &g_whitetex);
440     glBindTexture(GL_TEXTURE_2D, g_whitetex);
441     glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, 1, 1, 0, GL_RED, GL_UNSIGNED_BYTE, &white_alpha);
442     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
443     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
444 
445     glGenVertexArrays(1, &g_vao);
446     glGenBuffers(3, g_vbos.ptr);
447 
448     glBindVertexArray(g_vao);
449     glEnableVertexAttribArray(0);
450     glEnableVertexAttribArray(1);
451     glEnableVertexAttribArray(2);
452 
453     glBindBuffer(GL_ARRAY_BUFFER, g_vbos[0]);
454     glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, GL_FLOAT.sizeof * 2, null);
455     glBufferData(GL_ARRAY_BUFFER, 0, null, GL_STATIC_DRAW);
456     glBindBuffer(GL_ARRAY_BUFFER, g_vbos[1]);
457     glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, GL_FLOAT.sizeof * 2, null);
458     glBufferData(GL_ARRAY_BUFFER, 0, null, GL_STATIC_DRAW);
459     glBindBuffer(GL_ARRAY_BUFFER, g_vbos[2]);
460     glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, GL_FLOAT.sizeof * 4, null);
461     glBufferData(GL_ARRAY_BUFFER, 0, null, GL_STATIC_DRAW);
462     g_program = glCreateProgram();
463 
464     string vs =
465         "#version 150\n"
466         "uniform vec2 Viewport;\n"
467         "in vec2 VertexPosition;\n"
468         "in vec2 VertexTexCoord;\n"
469         "in vec4 VertexColor;\n"
470         "out vec2 texCoord;\n"
471         "out vec4 vertexColor;\n"
472         "void main(void)\n"
473         "{\n"
474         "    vertexColor = VertexColor;\n"
475         "    texCoord = VertexTexCoord;\n"
476         "    gl_Position = vec4(VertexPosition * 2.0 / Viewport - 1.0, 0.f, 1.0);\n"
477         "}\n";
478     GLuint vso = glCreateShader(GL_VERTEX_SHADER);
479     auto vsPtr = vs.ptr;
480     glShaderSource(vso, 1, &vsPtr, null);
481     glCompileShader(vso);
482     glAttachShader(g_program, vso);
483 
484     string fs =
485         "#version 150\n"
486         "in vec2 texCoord;\n"
487         "in vec4 vertexColor;\n"
488         "uniform sampler2D Texture;\n"
489         "out vec4  Color;\n"
490         "void main(void)\n"
491         "{\n"
492         "    float alpha = texture(Texture, texCoord).r;\n"
493         "    Color = vec4(vertexColor.rgb, vertexColor.a * alpha);\n"
494         "}\n";
495     GLuint fso = glCreateShader(GL_FRAGMENT_SHADER);
496 
497     auto fsPtr = fs.ptr;
498     glShaderSource(fso, 1, &fsPtr, null);
499     glCompileShader(fso);
500     glAttachShader(g_program, fso);
501 
502     glBindAttribLocation(g_program, 0, "VertexPosition");
503     glBindAttribLocation(g_program, 1, "VertexTexCoord");
504     glBindAttribLocation(g_program, 2, "VertexColor");
505     glBindFragDataLocation(g_program, 0, "Color");
506     glLinkProgram(g_program);
507     glDetachShader(g_program, vso);
508     glDetachShader(g_program, fso);
509     glDeleteShader(vso);
510     glDeleteShader(fso);
511 
512     glUseProgram(g_program);
513     g_programViewportLocation = glGetUniformLocation(g_program, "Viewport");
514     g_programTextureLocation  = glGetUniformLocation(g_program, "Texture");
515 
516     glUseProgram(0);
517 
518     free(ttfBuffer);
519     free(bmap);
520 
521     return true;
522 }
523 
524 void imguiRenderGLDestroy()
525 {
526     if (g_ftex)
527     {
528         glDeleteTextures(1, &g_ftex);
529         g_ftex = 0;
530     }
531     if (g_whitetex)
532     {
533         glDeleteTextures(1, &g_whitetex);
534         g_whitetex = 0;
535     }
536 
537     if (g_vao)
538     {
539         glDeleteVertexArrays(1, &g_vao);
540         glDeleteBuffers(3, g_vbos.ptr);
541         g_vao = 0;
542     }
543 
544     if (g_program)
545     {
546         glDeleteProgram(g_program);
547         g_program = 0;
548     }
549 }
550 
551 
552 void getBakedQuad(stbtt_bakedchar* chardata, int pw, int ph, int char_index,
553                          float* xpos, float* ypos, stbtt_aligned_quad* q)
554 {
555     stbtt_bakedchar* b = chardata + char_index;
556     int round_x        = STBTT_ifloor(*xpos + b.xoff);
557     int round_y        = STBTT_ifloor(*ypos - b.yoff);
558 
559     q.x0 = cast(float)round_x;
560     q.y0 = cast(float)round_y;
561     q.x1 = cast(float)round_x + b.x1 - b.x0;
562     q.y1 = cast(float)round_y - b.y1 + b.y0;
563 
564     q.s0 = b.x0 / cast(float)pw;
565     q.t0 = b.y0 / cast(float)pw;
566     q.s1 = b.x1 / cast(float)ph;
567     q.t1 = b.y1 / cast(float)ph;
568 
569     *xpos += b.xadvance;
570 }
571 
572 float getTextLength(stbtt_bakedchar* chardata, const(char)[] text)
573 {
574     float xpos = 0;
575     float len  = 0;
576 
577     // The cast(string) is only there for UTF-8 decoding.
578     foreach (dchar c; cast(string)text)
579     {
580         if (c == '\t')
581         {
582             for (int i = 0; i < 4; ++i)
583             {
584                 if (xpos < g_tabStops[i])
585                 {
586                     xpos = g_tabStops[i];
587                     break;
588                 }
589             }
590         }
591         else if (cast(int)c >= FIRST_CHARACTER && cast(int)c < FIRST_CHARACTER + g_max_character_count)
592         {
593             stbtt_bakedchar* b = chardata + c - FIRST_CHARACTER;
594             int round_x        = STBTT_ifloor((xpos + b.xoff) + 0.5);
595             len   = round_x + b.x1 - b.x0 + 0.5f;
596             xpos += b.xadvance;
597         }
598     }
599 
600     return len;
601 }
602 
603 float getTextLength(const(char)[] text)
604 {
605     return getTextLength(g_cdata.ptr, text);
606 }
607 
608 void drawText(float x, float y, const(char)[] text, int align_, uint col)
609 {
610     if (!g_ftex)
611         return;
612 
613     if (!text)
614         return;
615 
616     if (align_ == TextAlign.center)
617         x -= getTextLength(g_cdata.ptr, text) / 2;
618     else if (align_ == TextAlign.right)
619         x -= getTextLength(g_cdata.ptr, text);
620 
621     float r = cast(float)(col & 0xff) / 255.0;
622     float g = cast(float)((col >> 8) & 0xff) / 255.0;
623     float b = cast(float)((col >> 16) & 0xff) / 255.0;
624     float a = cast(float)((col >> 24) & 0xff) / 255.0;
625 
626     // assume orthographic projection with units = screen pixels, origin at top left
627     glBindTexture(GL_TEXTURE_2D, g_ftex);
628 
629     const float ox = x;
630 
631     // The cast(string) is only there for UTF-8 decoding.
632     foreach (dchar c; cast(string)text)
633     {
634         if (c == '\t')
635         {
636             for (int i = 0; i < 4; ++i)
637             {
638                 if (x < g_tabStops[i] + ox)
639                 {
640                     x = g_tabStops[i] + ox;
641                     break;
642                 }
643             }
644         }
645         else if (c >= FIRST_CHARACTER && c < FIRST_CHARACTER + g_max_character_count)
646         {
647             stbtt_aligned_quad q;
648             getBakedQuad(g_cdata.ptr, g_font_texture_size, g_font_texture_size,
649                          c - FIRST_CHARACTER, &x, &y, &q);
650 
651             float v[12] = [
652                 q.x0, q.y0,
653                 q.x1, q.y1,
654                 q.x1, q.y0,
655                 q.x0, q.y0,
656                 q.x0, q.y1,
657                 q.x1, q.y1,
658             ];
659             float uv[12] = [
660                 q.s0, q.t0,
661                 q.s1, q.t1,
662                 q.s1, q.t0,
663                 q.s0, q.t0,
664                 q.s0, q.t1,
665                 q.s1, q.t1,
666             ];
667             float cArr[24] = [
668                 r, g, b, a,
669                 r, g, b, a,
670                 r, g, b, a,
671                 r, g, b, a,
672                 r, g, b, a,
673                 r, g, b, a,
674             ];
675             glBindVertexArray(g_vao);
676             glBindBuffer(GL_ARRAY_BUFFER, g_vbos[0]);
677             glBufferData(GL_ARRAY_BUFFER, 12 * float.sizeof, v.ptr, GL_STATIC_DRAW);
678             glBindBuffer(GL_ARRAY_BUFFER, g_vbos[1]);
679             glBufferData(GL_ARRAY_BUFFER, 12 * float.sizeof, uv.ptr, GL_STATIC_DRAW);
680             glBindBuffer(GL_ARRAY_BUFFER, g_vbos[2]);
681             glBufferData(GL_ARRAY_BUFFER, 24 * float.sizeof, cArr.ptr, GL_STATIC_DRAW);
682             glDrawArrays(GL_TRIANGLES, 0, 6);
683         }
684     }
685 
686     // glEnd();
687     // glDisable(GL_TEXTURE_2D);
688 }
689 
690 void imguiRenderGLDraw(int width, int height)
691 {
692     const imguiGfxCmd* q = imguiGetRenderQueue();
693     int nq = imguiGetRenderQueueSize();
694 
695     const float s = 1.0f / 8.0f;
696 
697     glViewport(0, 0, width, height);
698     glUseProgram(g_program);
699     glActiveTexture(GL_TEXTURE0);
700     glUniform2f(g_programViewportLocation, cast(float)width, cast(float)height);
701     glUniform1i(g_programTextureLocation, 0);
702 
703     glDisable(GL_SCISSOR_TEST);
704 
705     for (int i = 0; i < nq; ++i)
706     {
707         auto cmd = &q[i];
708 
709         if (cmd.type == IMGUI_GFXCMD_RECT)
710         {
711             if (cmd.rect.r == 0)
712             {
713                 drawRect(cast(float)cmd.rect.x * s + 0.5f, cast(float)cmd.rect.y * s + 0.5f,
714                          cast(float)cmd.rect.w * s - 1, cast(float)cmd.rect.h * s - 1,
715                          1.0f, cmd.col);
716             }
717             else
718             {
719                 drawRoundedRect(cast(float)cmd.rect.x * s + 0.5f, cast(float)cmd.rect.y * s + 0.5f,
720                                 cast(float)cmd.rect.w * s - 1, cast(float)cmd.rect.h * s - 1,
721                                 cast(float)cmd.rect.r * s, 1.0f, cmd.col);
722             }
723         }
724         else if (cmd.type == IMGUI_GFXCMD_LINE)
725         {
726             drawLine(cmd.line.x0 * s, cmd.line.y0 * s, cmd.line.x1 * s, cmd.line.y1 * s, cmd.line.r * s, 1.0f, cmd.col);
727         }
728         else if (cmd.type == IMGUI_GFXCMD_TRIANGLE)
729         {
730             if (cmd.flags == 1)
731             {
732                 const float verts[3 * 2] =
733                 [
734                     cast(float)cmd.rect.x * s + 0.5f, cast(float)cmd.rect.y * s + 0.5f,
735                     cast(float)cmd.rect.x * s + 0.5f + cast(float)cmd.rect.w * s - 1, cast(float)cmd.rect.y * s + 0.5f + cast(float)cmd.rect.h * s / 2 - 0.5f,
736                     cast(float)cmd.rect.x * s + 0.5f, cast(float)cmd.rect.y * s + 0.5f + cast(float)cmd.rect.h * s - 1,
737                 ];
738                 drawPolygon(verts.ptr, 3, 1.0f, cmd.col);
739             }
740 
741             if (cmd.flags == 2)
742             {
743                 const float verts[3 * 2] =
744                 [
745                     cast(float)cmd.rect.x * s + 0.5f, cast(float)cmd.rect.y * s + 0.5f + cast(float)cmd.rect.h * s - 1,
746                     cast(float)cmd.rect.x * s + 0.5f + cast(float)cmd.rect.w * s / 2 - 0.5f, cast(float)cmd.rect.y * s + 0.5f,
747                     cast(float)cmd.rect.x * s + 0.5f + cast(float)cmd.rect.w * s - 1, cast(float)cmd.rect.y * s + 0.5f + cast(float)cmd.rect.h * s - 1,
748                 ];
749                 drawPolygon(verts.ptr, 3, 1.0f, cmd.col);
750             }
751         }
752         else if (cmd.type == IMGUI_GFXCMD_TEXT)
753         {
754             drawText(cmd.text.x, cmd.text.y, cmd.text.text, cmd.text.align_, cmd.col);
755         }
756         else if (cmd.type == IMGUI_GFXCMD_SCISSOR)
757         {
758             if (cmd.flags)
759             {
760                 glEnable(GL_SCISSOR_TEST);
761                 glScissor(cmd.rect.x, cmd.rect.y, cmd.rect.w, cmd.rect.h);
762             }
763             else
764             {
765                 glDisable(GL_SCISSOR_TEST);
766             }
767         }
768     }
769 
770     glDisable(GL_SCISSOR_TEST);
771     glUseProgram(0);
772 }