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