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