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