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