1 module openglexample;
2 
3 import dlangui;
4 
5 mixin APP_ENTRY_POINT;
6 
7 /// entry point for dlangui based application
8 extern (C) int UIAppMain(string[] args) {
9     // embed resources listed in views/resources.list into executable
10     embeddedResourceList.addResources(embedResourcesFromList!("resources.list")());
11 
12     // the old API example works on old context because of OpenGL deprecation model
13     // otherwise there will be GL_INVALID_OPERATION error
14     // new API functions on a modern graphics card will work even if you set context version to 1.0
15     Platform.instance.GLVersionMajor = 2;
16     Platform.instance.GLVersionMinor = 1;
17 
18     // Enable multisampling
19     Platform.instance.multisamples = 16;
20 
21     // create window
22     Window window = Platform.instance.createWindow("DlangUI OpenGL Example", null, WindowFlag.Resizable, 800, 700);
23 
24     static if (ENABLE_OPENGL) {
25         window.mainWidget = new MyOpenglWidget();
26     } else {
27         window.mainWidget = new TextWidget(null, "DlangUI is built with OpenGL support disabled"d);
28     }
29 
30     window.windowIcon = drawableCache.getImage("dlangui-logo1");
31 
32     window.show();
33     // run message loop
34     return Platform.instance.enterMessageLoop();
35 }
36 
37 static if (ENABLE_OPENGL):
38 
39 import bindbc.opengl;
40 import dlangui.graphics.glsupport;
41 import dlangui.graphics.gldrawbuf;
42 
43 class MyOpenglWidget : VerticalLayout {
44     this() {
45         super("OpenGLView");
46         layoutWidth = FILL_PARENT;
47         layoutHeight = FILL_PARENT;
48         alignment = Align.Center;
49         // add some UI on top of OpenGL drawable
50         Widget w = parseML(q{
51             VerticalLayout {
52                 alignment: center
53                 layoutWidth: fill; layoutHeight: fill
54                 // background for window - tiled texture
55                 backgroundImageId: "tx_fabric.tiled"
56                 VerticalLayout {
57                     // child widget - will draw using OpenGL here
58                     id: glView
59                     margins: 20
60                     padding: 20
61                     layoutWidth: fill; layoutHeight: fill
62 
63                     TextWidget { text: "DlangUI OpenGL custom drawable example"; textColor: "red"; fontSize: 150%; fontWeight: 800; fontFace: "Arial" }
64 
65                     TextWidget { text: "Choose OpenGL drawable:" }
66                     VerticalLayout {
67                         RadioButton { id: rbExample1; text: "Shaders based example - Cube"; checked: true }
68                         RadioButton { id: rbExample2; text: "Legacy OpenGL API example - glxGears" }
69                     }
70 
71                     TextWidget { text: "Some controls to draw on top of OpenGL scene"; textColor: "red"; fontSize: 150%; fontWeight: 800; fontFace: "Arial" }
72 
73                     // arrange controls as form - table with two columns
74                     TableLayout {
75                         colCount: 2
76                         TextWidget { text: "param 1" }
77                         EditLine { id: edit1; text: "some text" }
78                         TextWidget { text: "param 2" }
79                         EditLine { id: edit2; text: "some text for param2" }
80                         TextWidget { text: "some radio buttons" }
81                         // arrange some radio buttons vertically
82                         VerticalLayout {
83                             RadioButton { id: rb1; text: "Item 1" }
84                             RadioButton { id: rb2; text: "Item 2" }
85                             RadioButton { id: rb3; text: "Item 3" }
86                         }
87                         TextWidget { text: "and checkboxes" }
88                         // arrange some checkboxes horizontally
89                         HorizontalLayout {
90                             CheckBox { id: cb1; text: "checkbox 1" }
91                             CheckBox { id: cb2; text: "checkbox 2" }
92                         }
93                     }
94                     VSpacer { layoutWeight: 10 }
95                     HorizontalLayout {
96                         Button { id: btnOk; text: "Ok" }
97                         Button { id: btnCancel; text: "Cancel" }
98                     }
99                 }
100             }
101         });
102         // assign OpenGL drawable to child widget background
103         w.childById("glView").backgroundDrawable = DrawableRef(new OpenGLDrawable(&doDraw));
104 
105         w.childById("rbExample1").click = delegate(Widget w) {
106             _exampleIndex = 0; // new API
107             return true;
108         };
109         w.childById("rbExample2").click = delegate(Widget w) {
110             _exampleIndex = 1; // old API
111             return true;
112         };
113 
114         addChild(w);
115     }
116 
117     int _exampleIndex = 0;
118 
119     /// returns true is widget is being animated - need to call animate() and redraw
120     @property override bool animating() { return true; }
121     /// animates window; interval is time left from previous draw, in hnsecs (1/10000000 of second)
122     override void animate(long interval) {
123         if (_exampleIndex == 1) {
124             // animate legacy API example
125             // rotate gears
126             angle += interval * 0.000002f;
127         } else {
128             // TODO: some other animation for new API example
129             angle += interval * 0.000002f;
130         }
131         invalidate();
132     }
133 
134     /// this is OpenGLDrawableDelegate implementation
135     private void doDraw(Rect windowRect, Rect rc) {
136         if (!openglEnabled) {
137             Log.v("GlGears: OpenGL is disabled");
138             return;
139         }
140         bool canUseOldApi = !!glLightfv;
141         bool canUseNewApi = true;
142         if (_exampleIndex == 0 || !canUseOldApi)
143             drawUsingNewAPI(windowRect, rc);
144         else if (_exampleIndex == 1 || !canUseNewApi)
145             drawUsingOldAPI(windowRect, rc);
146     }
147 
148     /// Legacy API example (glBegin/glEnd) - glxGears
149     void drawUsingOldAPI(Rect windowRect, Rect rc) {
150         static bool _initCalled;
151         if (!_initCalled) {
152             Log.d("GlGears: calling init()");
153             _initCalled = true;
154             glxgears_init();
155         }
156         glxgears_reshape(rc);
157         glEnable(GL_LIGHTING);
158         glEnable(GL_LIGHT0);
159         glEnable(GL_DEPTH_TEST);
160         glxgears_draw();
161         glDisable(GL_LIGHTING);
162         glDisable(GL_LIGHT0);
163         glDisable(GL_DEPTH_TEST);
164     }
165 
166     ~this() {
167         if (_program)
168             destroy(_program);
169         if (_vao)
170             destroy(_vao);
171         if (_vbo)
172             destroy(_vbo);
173         if (_tx)
174             destroy(_tx);
175     }
176 
177     MyGLProgram _program;
178     GLTexture _tx;
179     VAO _vao;
180     VBO _vbo;
181 
182     /// New API example (OpenGL3+, shaders)
183     void drawUsingNewAPI(Rect windowRect, Rect rc) {
184         if (!_program) {
185             _program = new MyGLProgram;
186             _program.compile();
187             createMesh();
188             auto buf = _program.createBuffers(vertices, colors, texcoords);
189             _vao = buf[0];
190             _vbo = buf[1];
191         }
192         if (!_program.check())
193             return;
194         if (!_tx.isValid) {
195             Log.e("Invalid texture");
196             return;
197         }
198 
199         checkgl!glEnable(GL_MULTISAMPLE);
200         checkgl!glEnable(GL_POLYGON_SMOOTH);
201 
202         checkgl!glEnable(GL_CULL_FACE);
203         checkgl!glEnable(GL_DEPTH_TEST);
204         checkgl!glCullFace(GL_BACK);
205 
206         // ======== Projection Matrix ==================
207         mat4 projectionMatrix;
208         float aspectRatio = cast(float)rc.width / cast(float)rc.height;
209         projectionMatrix.setPerspective(45.0f, aspectRatio, 0.1f, 100.0f);
210 
211         // ======== View Matrix ==================
212         mat4 viewMatrix;
213         viewMatrix.translate(0, 0, -6);
214         viewMatrix.rotatex(-15.0f);
215         //viewMatrix.lookAt(vec3(-10, 0, 0), vec3(0, 0, 0), vec3(0, 1, 0));//translation(0.0f, 0.0f, 4.0f).rotatez(angle);
216 
217         // ======== Model Matrix ==================
218         mat4 modelMatrix;
219         modelMatrix.scale(1.5f);
220         modelMatrix.rotatez(30.0f + angle * 0.3456778);
221         modelMatrix.rotatey(angle);
222         modelMatrix.rotatez(angle * 1.98765f);
223 
224         // ======= PMV matrix =====================
225         mat4 projectionViewModelMatrix = projectionMatrix * viewMatrix * modelMatrix;
226 
227         _program.execute(_vao, cast(int)vertices.length / 3, _tx.texture, true, projectionViewModelMatrix.m);
228 
229         checkgl!glDisable(GL_CULL_FACE);
230         checkgl!glDisable(GL_DEPTH_TEST);
231     }
232 
233     // Cube mesh
234     float[] vertices;
235     float[] texcoords;
236     float[4*6*6] colors;
237     void createMesh() {
238         if (!_tx)
239             _tx = new GLTexture("crate");
240 
241         // define Cube mesh
242         auto p000 = [-1.0f, -1.0f, -1.0f];
243         auto p100 = [ 1.0f, -1.0f, -1.0f];
244         auto p010 = [-1.0f,  1.0f, -1.0f];
245         auto p110 = [ 1.0f,  1.0f, -1.0f];
246         auto p001 = [-1.0f, -1.0f,  1.0f];
247         auto p101 = [ 1.0f, -1.0f,  1.0f];
248         auto p011 = [-1.0f,  1.0f,  1.0f];
249         auto p111 = [ 1.0f,  1.0f,  1.0f];
250         vertices = p000 ~ p010 ~ p110 ~  p110 ~ p100 ~ p000 // front face
251                  ~ p101 ~ p111 ~ p011 ~  p011 ~ p001 ~ p101 // back face
252                  ~ p100 ~ p110 ~ p111 ~  p111 ~ p101 ~ p100 // right face
253                  ~ p001 ~ p011 ~ p010 ~  p010 ~ p000 ~ p001 // left face
254                  ~ p010 ~ p011 ~ p111 ~  p111 ~ p110 ~ p010 // top face
255                  ~ p001 ~ p000 ~ p100 ~  p100 ~ p101 ~ p001 // bottom face
256             ;
257         // texture coordinates
258         float[2] uv = _tx.uv;
259         float tx0 = 0.0f;
260         float tx1 = uv[0];
261         float ty0 = 0.0f;
262         float ty1 = uv[1];
263         float[12] facetx = [tx1, ty1, // triangle 1
264                             tx0, ty0,
265                             tx0, ty1,
266                             tx0, ty1, // triangle 2
267                             tx1, ty0,
268                             tx1, ty1];
269         texcoords = facetx ~ facetx ~ facetx ~ facetx ~ facetx ~ facetx;
270         // init with white color (1, 1, 1, 1)
271         foreach(ref cl; colors)
272             cl = 1.0f;
273     }
274 }
275 
276 // ====================================================================================
277 // Shaders based example
278 
279 // Simple texture + color shader
280 class MyGLProgram : GLProgram {
281     @property override string vertexSource() {
282         return q{
283             in vec4 vertex;
284             in vec4 colAttr;
285             in vec4 texCoord;
286             out vec4 col;
287             out vec4 texc;
288             uniform mat4 matrix;
289             void main(void)
290             {
291                 gl_Position = matrix * vertex;
292                 col = colAttr;
293                 texc = texCoord;
294             }
295         };
296 
297     }
298     @property override string fragmentSource() {
299         return q{
300             uniform sampler2D tex;
301             in vec4 col;
302             in vec4 texc;
303             out vec4 outColor;
304             void main(void)
305             {
306                 outColor = texture(tex, texc.st) * col;
307             }
308         };
309     }
310 
311     // attribute locations
312     protected GLint matrixLocation;
313     protected GLint vertexLocation;
314     protected GLint colAttrLocation;
315     protected GLint texCoordLocation;
316 
317     override bool initLocations() {
318         matrixLocation = getUniformLocation("matrix");
319         vertexLocation = getAttribLocation("vertex");
320         colAttrLocation = getAttribLocation("colAttr");
321         texCoordLocation = getAttribLocation("texCoord");
322         return matrixLocation >= 0 && vertexLocation >= 0 && colAttrLocation >= 0 && texCoordLocation >= 0;
323     }
324 
325     import std.typecons : Tuple, tuple;
326     Tuple!(VAO, VBO) createBuffers(float[] vertices, float[] colors, float[] texcoords) {
327 
328         VBO vbo = new VBO;
329         vbo.fill([vertices, colors, texcoords]);
330 
331         VAO vao = new VAO;
332         glVertexAttribPointer(vertexLocation, 3, GL_FLOAT, GL_FALSE, 0, cast(void*) 0);
333         glVertexAttribPointer(colAttrLocation, 4, GL_FLOAT, GL_FALSE, 0, cast(void*) (vertices.length * vertices[0].sizeof));
334         glVertexAttribPointer(texCoordLocation, 2, GL_FLOAT, GL_FALSE, 0, cast(void*) (vertices.length * vertices[0].sizeof + colors.length * colors[0].sizeof));
335 
336         glEnableVertexAttribArray(vertexLocation);
337         glEnableVertexAttribArray(colAttrLocation);
338         glEnableVertexAttribArray(texCoordLocation);
339 
340         return tuple(vao, vbo);
341     }
342 
343     void execute(VAO vao, int vertsCount, Tex2D texture, bool linear, float[16] matrix) {
344 
345         bind();
346         checkgl!glUniformMatrix4fv(matrixLocation, 1, false, matrix.ptr);
347 
348         texture.setup();
349         texture.setSamplerParams(linear);
350 
351         vao.bind();
352         checkgl!glDrawArrays(GL_TRIANGLES, 0, vertsCount);
353 
354         texture.unbind();
355         unbind();
356     }
357 }
358 
359 
360 
361 
362 //=====================================================================================
363 // Legacy OpenGL API example
364 // GlxGears
365 //=====================================================================================
366 
367 import std.math;
368 static __gshared GLfloat view_rotx = 20.0, view_roty = 30.0, view_rotz = 0.0;
369 static __gshared GLint gear1, gear2, gear3;
370 static __gshared GLfloat angle = 0.0;
371 alias M_PI = std.math.PI;
372 
373 /*
374 *
375 *  Draw a gear wheel.  You'll probably want to call this function when
376 *  building a display list since we do a lot of trig here.
377 *
378 *  Input:  inner_radius - radius of hole at center
379 *          outer_radius - radius at center of teeth
380 *          width - width of gear
381 *          teeth - number of teeth
382 *          tooth_depth - depth of tooth
383 */
384 static void
385     gear(GLfloat inner_radius, GLfloat outer_radius, GLfloat width,
386             GLint teeth, GLfloat tooth_depth)
387 {
388     GLint i;
389     GLfloat r0, r1, r2;
390     GLfloat angle, da;
391     GLfloat u, v, len;
392 
393     r0 = inner_radius;
394     r1 = outer_radius - tooth_depth / 2.0;
395     r2 = outer_radius + tooth_depth / 2.0;
396 
397     da = 2.0 * M_PI / teeth / 4.0;
398 
399     glShadeModel(GL_FLAT);
400 
401     glNormal3f(0.0, 0.0, 1.0);
402 
403     /* draw front face */
404     glBegin(GL_QUAD_STRIP);
405     for (i = 0; i <= teeth; i++) {
406         angle = i * 2.0 * M_PI / teeth;
407         glVertex3f(r0 * cos(angle), r0 * sin(angle), width * 0.5);
408         glVertex3f(r1 * cos(angle), r1 * sin(angle), width * 0.5);
409         if (i < teeth) {
410             glVertex3f(r0 * cos(angle), r0 * sin(angle), width * 0.5);
411             glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
412                         width * 0.5);
413         }
414     }
415     glEnd();
416 
417     /* draw front sides of teeth */
418     glBegin(GL_QUADS);
419     da = 2.0 * M_PI / teeth / 4.0;
420     for (i = 0; i < teeth; i++) {
421         angle = i * 2.0 * M_PI / teeth;
422 
423         glVertex3f(r1 * cos(angle), r1 * sin(angle), width * 0.5);
424         glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), width * 0.5);
425         glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da),
426                     width * 0.5);
427         glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
428                     width * 0.5);
429     }
430     glEnd();
431 
432     glNormal3f(0.0, 0.0, -1.0);
433 
434     /* draw back face */
435     glBegin(GL_QUAD_STRIP);
436     for (i = 0; i <= teeth; i++) {
437         angle = i * 2.0 * M_PI / teeth;
438         glVertex3f(r1 * cos(angle), r1 * sin(angle), -width * 0.5);
439         glVertex3f(r0 * cos(angle), r0 * sin(angle), -width * 0.5);
440         if (i < teeth) {
441             glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
442                         -width * 0.5);
443             glVertex3f(r0 * cos(angle), r0 * sin(angle), -width * 0.5);
444         }
445     }
446     glEnd();
447 
448     /* draw back sides of teeth */
449     glBegin(GL_QUADS);
450     da = 2.0 * M_PI / teeth / 4.0;
451     for (i = 0; i < teeth; i++) {
452         angle = i * 2.0 * M_PI / teeth;
453 
454         glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
455                     -width * 0.5);
456         glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da),
457                     -width * 0.5);
458         glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), -width * 0.5);
459         glVertex3f(r1 * cos(angle), r1 * sin(angle), -width * 0.5);
460     }
461     glEnd();
462 
463     /* draw outward faces of teeth */
464     glBegin(GL_QUAD_STRIP);
465     for (i = 0; i < teeth; i++) {
466         angle = i * 2.0 * M_PI / teeth;
467 
468         glVertex3f(r1 * cos(angle), r1 * sin(angle), width * 0.5);
469         glVertex3f(r1 * cos(angle), r1 * sin(angle), -width * 0.5);
470         u = r2 * cos(angle + da) - r1 * cos(angle);
471         v = r2 * sin(angle + da) - r1 * sin(angle);
472         len = sqrt(u * u + v * v);
473         u /= len;
474         v /= len;
475         glNormal3f(v, -u, 0.0);
476         glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), width * 0.5);
477         glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), -width * 0.5);
478         glNormal3f(cos(angle), sin(angle), 0.0);
479         glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da),
480                     width * 0.5);
481         glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da),
482                     -width * 0.5);
483         u = r1 * cos(angle + 3 * da) - r2 * cos(angle + 2 * da);
484         v = r1 * sin(angle + 3 * da) - r2 * sin(angle + 2 * da);
485         glNormal3f(v, -u, 0.0);
486         glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
487                     width * 0.5);
488         glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
489                     -width * 0.5);
490         glNormal3f(cos(angle), sin(angle), 0.0);
491     }
492 
493     glVertex3f(r1 * cos(0.0), r1 * sin(0.0), width * 0.5);
494     glVertex3f(r1 * cos(0.0), r1 * sin(0.0), -width * 0.5);
495 
496     glEnd();
497 
498     glShadeModel(GL_SMOOTH);
499 
500     /* draw inside radius cylinder */
501     glBegin(GL_QUAD_STRIP);
502     for (i = 0; i <= teeth; i++) {
503         angle = i * 2.0 * M_PI / teeth;
504         glNormal3f(-cos(angle), -sin(angle), 0.0);
505         glVertex3f(r0 * cos(angle), r0 * sin(angle), -width * 0.5);
506         glVertex3f(r0 * cos(angle), r0 * sin(angle), width * 0.5);
507     }
508     glEnd();
509 }
510 
511 
512 static void glxgears_draw()
513 {
514     glPushMatrix();
515     glRotatef(view_rotx, 1.0, 0.0, 0.0);
516     glRotatef(view_roty, 0.0, 1.0, 0.0);
517     glRotatef(view_rotz, 0.0, 0.0, 1.0);
518 
519     glPushMatrix();
520     glTranslatef(-3.0, -2.0, 0.0);
521     glRotatef(angle, 0.0, 0.0, 1.0);
522     glCallList(gear1);
523     glPopMatrix();
524 
525     glPushMatrix();
526     glTranslatef(3.1, -2.0, 0.0);
527     glRotatef(-2.0 * angle - 9.0, 0.0, 0.0, 1.0);
528     glCallList(gear2);
529     glPopMatrix();
530 
531     glPushMatrix();
532     glTranslatef(-3.1, 4.2, 0.0);
533     glRotatef(-2.0 * angle - 25.0, 0.0, 0.0, 1.0);
534     glCallList(gear3);
535     glPopMatrix();
536 
537     glPopMatrix();
538 }
539 
540 
541 /* new window size or exposure */
542 static void
543     glxgears_reshape(Rect rc)
544 {
545     GLfloat h = cast(GLfloat) rc.height / cast(GLfloat) rc.width;
546     glMatrixMode(GL_PROJECTION);
547     glLoadIdentity();
548     glFrustum(-1.0, 1.0, -h, h, 5.0, 60.0);
549     glMatrixMode(GL_MODELVIEW);
550     glLoadIdentity();
551     glTranslatef(0.0, 0.0, -40.0);
552 }
553 
554 
555 static void glxgears_init()
556 {
557     static GLfloat[4] pos = [ 5.0, 5.0, 10.0, 0.0 ];
558     static GLfloat[4] red = [ 0.8, 0.1, 0.0, 1.0 ];
559     static GLfloat[4] green = [ 0.0, 0.8, 0.2, 1.0 ];
560     static GLfloat[4] blue = [ 0.2, 0.2, 1.0, 1.0 ];
561 
562     glLightfv(GL_LIGHT0, GL_POSITION, pos.ptr);
563     glEnable(GL_CULL_FACE);
564     glEnable(GL_LIGHTING);
565     glEnable(GL_LIGHT0);
566     glEnable(GL_DEPTH_TEST);
567 
568     /* make the gears */
569     gear1 = glGenLists(1);
570     glNewList(gear1, GL_COMPILE);
571     glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, red.ptr);
572     gear(1.0, 4.0, 1.0, 20, 0.7);
573     glEndList();
574 
575     gear2 = glGenLists(1);
576     glNewList(gear2, GL_COMPILE);
577     glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, green.ptr);
578     gear(0.5, 2.0, 2.0, 10, 0.7);
579     glEndList();
580 
581     gear3 = glGenLists(1);
582     glNewList(gear3, GL_COMPILE);
583     glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, blue.ptr);
584     gear(1.3, 2.0, 0.5, 10, 0.7);
585     glEndList();
586 
587     glEnable(GL_NORMALIZE);
588 }