1 /* Open GL Area
2  *
3  * Gtk::GLArea is a widget that allows custom drawing using OpenGL calls.
4  *
5  */
6 
7 #include <iostream>
8 #include <string>
9 #include <vector>
10 
11 #include <gtkmm.h>
12 #include <giomm/resource.h>
13 #include <epoxy/gl.h>
14 
15 using std::cerr;
16 using std::endl;
17 using std::string;
18 
19 enum {
20   X_AXIS,
21   Y_AXIS,
22   Z_AXIS,
23 
24   N_AXIS
25 };
26 
27 static const GLfloat vertex_data[] = {
28   0.f,   0.5f,   0.f, 1.f,
29   0.5f, -0.366f, 0.f, 1.f,
30  -0.5f, -0.366f, 0.f, 1.f,
31 };
32 
33 class Example_GLArea : public Gtk::Window
34 {
35 public:
36   Example_GLArea();
37   ~Example_GLArea() override;
38 
39 protected:
40   Gtk::Box m_VBox {Gtk::ORIENTATION_VERTICAL, false};
41   Gtk::GLArea m_GLArea;
42   Gtk::Box m_Controls {Gtk::ORIENTATION_VERTICAL, false};
43   Gtk::Button m_Button {"Quit"};
44 
45   GLuint m_Vao {0};
46   GLuint m_Buffer {0};
47   GLuint m_Program {0};
48   GLuint m_Mvp {0};
49 
50   std::vector<float> m_RotationAngles;
51 
52   void on_axis_value_change(int axis, const Glib::RefPtr<Gtk::Adjustment>& adj);
53 
54   void realize();
55   void unrealize();
56   bool render(const Glib::RefPtr<Gdk::GLContext>& context);
57 
58   Gtk::Box* create_axis_slider_box(int axis);
59   void init_buffers();
60   void init_shaders();
61   void draw_triangle();
62 };
63 
do_glarea()64 Gtk::Window* do_glarea()
65 {
66   return new Example_GLArea();
67 }
68 
Example_GLArea()69 Example_GLArea::Example_GLArea() : m_RotationAngles(N_AXIS, 0.0f)
70 {
71   set_title("GL Area");
72   set_default_size(400, 600);
73   set_border_width(12);
74 
75   m_VBox.set_spacing(6);
76   add(m_VBox);
77 
78   m_GLArea.set_hexpand(true);
79   m_GLArea.set_vexpand(true);
80   m_GLArea.set_auto_render(true);
81   m_VBox.add(m_GLArea);
82 
83   // Connect gl area signals
84   m_GLArea.signal_realize().connect(sigc::mem_fun(*this, &Example_GLArea::realize));
85   // Important that the unrealize signal calls our handler to clean up
86   // GL resources _before_ the default unrealize handler is called (the "false")
87   m_GLArea.signal_unrealize().connect(sigc::mem_fun(*this, &Example_GLArea::unrealize), false);
88   m_GLArea.signal_render().connect(sigc::mem_fun(*this, &Example_GLArea::render));
89 
90   m_VBox.add(m_Controls);
91   m_Controls.set_hexpand(true);
92 
93   for(int i = 0 ; i < N_AXIS ; ++i)
94   {
95     auto sliderBox = create_axis_slider_box(i);
96     m_Controls.add(*sliderBox);
97   }
98 
99   m_Button.set_hexpand(true);
100   m_VBox.add(m_Button);
101   // Connect clicked to close of window
102   m_Button.signal_clicked().connect(sigc::mem_fun(*this, &Gtk::Window::close));
103 
104   show_all();
105 }
106 
~Example_GLArea()107 Example_GLArea::~Example_GLArea()
108 {
109 }
110 
on_axis_value_change(int axis,const Glib::RefPtr<Gtk::Adjustment> & adj)111 void Example_GLArea::on_axis_value_change(int axis, const Glib::RefPtr<Gtk::Adjustment>& adj)
112 {
113   m_RotationAngles[axis] = adj->get_value();
114   m_GLArea.queue_draw();
115 }
116 
realize()117 void Example_GLArea::realize()
118 {
119   m_GLArea.make_current();
120   try
121   {
122     m_GLArea.throw_if_error();
123     init_buffers();
124     init_shaders();
125   }
126   catch(const Gdk::GLError& gle)
127   {
128     cerr << "An error occured making the context current during realize:" << endl;
129     cerr << gle.domain() << "-" << gle.code() << "-" << gle.what() << endl;
130   }
131 }
132 
unrealize()133 void Example_GLArea::unrealize()
134 {
135   m_GLArea.make_current();
136   try
137   {
138     m_GLArea.throw_if_error();
139 
140     // Delete buffers and program
141     glDeleteBuffers(1, &m_Vao);
142     glDeleteProgram(m_Program);
143   }
144   catch(const Gdk::GLError& gle)
145   {
146     cerr << "An error occured making the context current during unrealize" << endl;
147     cerr << gle.domain() << "-" << gle.code() << "-" << gle.what() << endl;
148   }
149 }
150 
render(const Glib::RefPtr<Gdk::GLContext> &)151 bool Example_GLArea::render(const Glib::RefPtr<Gdk::GLContext>& /* context */)
152 {
153   try
154   {
155     m_GLArea.throw_if_error();
156     glClearColor(0.5, 0.5, 0.5, 1.0);
157     glClear(GL_COLOR_BUFFER_BIT);
158 
159     draw_triangle();
160 
161     glFlush();
162 
163     return true;
164   }
165   catch(const Gdk::GLError& gle)
166   {
167     cerr << "An error occurred in the render callback of the GLArea" << endl;
168     cerr << gle.domain() << "-" << gle.code() << "-" << gle.what() << endl;
169     return false;
170   }
171 }
172 
create_axis_slider_box(int axis)173 Gtk::Box* Example_GLArea::create_axis_slider_box(int axis)
174 {
175   auto box = Gtk::manage(new Gtk::Box{Gtk::ORIENTATION_HORIZONTAL, false});
176 
177   const char* text;
178 
179   switch(axis)
180   {
181     case X_AXIS:
182     {
183       text = "X axis";
184       break;
185     }
186     case Y_AXIS:
187     {
188       text = "Y axis";
189       break;
190     }
191     case Z_AXIS:
192     {
193       text = "Z axis";
194       break;
195     }
196     default:
197     {
198       g_assert_not_reached();
199     }
200   }
201 
202   auto label = Gtk::manage(new Gtk::Label{text});
203   box->add(*label);
204   label->show();
205 
206   auto adj = Gtk::Adjustment::create(0.0, 0.0, 360.0, 1.0, 12.0, 0.0);
207 
208   adj->signal_value_changed().connect(
209     sigc::bind(sigc::mem_fun(*this, &Example_GLArea::on_axis_value_change), axis, adj)
210                                       );
211   auto slider = Gtk::manage(new Gtk::Scale{adj, Gtk::ORIENTATION_HORIZONTAL});
212   box->add(*slider);
213   slider->set_hexpand(true);
214   slider->show();
215 
216   box->show();
217 
218   return box;
219 }
220 
init_buffers()221 void Example_GLArea::init_buffers()
222 {
223   glGenVertexArrays(1, &m_Vao);
224   glBindVertexArray(m_Vao);
225 
226   glGenBuffers(1, &m_Buffer);
227   glBindBuffer(GL_ARRAY_BUFFER, m_Buffer);
228   glBufferData(GL_ARRAY_BUFFER, sizeof(vertex_data), vertex_data, GL_STATIC_DRAW);
229   glBindBuffer(GL_ARRAY_BUFFER, 0);
230 }
231 
create_shader(int type,const char * src)232 static GLuint create_shader(int type, const char *src)
233 {
234   auto shader = glCreateShader(type);
235   glShaderSource(shader, 1, &src, nullptr);
236   glCompileShader(shader);
237 
238   int status;
239   glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
240   if(status == GL_FALSE)
241   {
242     int log_len;
243     glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_len);
244 
245     string log_space(log_len+1, ' ');
246     glGetShaderInfoLog(shader, log_len, nullptr, (GLchar*)log_space.c_str());
247 
248     cerr << "Compile failure in " <<
249       (type == GL_VERTEX_SHADER ? "vertex" : "fragment") <<
250       " shader: " << log_space << endl;
251 
252     glDeleteShader(shader);
253 
254     return 0;
255   }
256 
257   return shader;
258 }
259 
init_shaders()260 void Example_GLArea::init_shaders()
261 {
262   auto vshader_bytes = Gio::Resource::lookup_data_global("/glarea/glarea-vertex.glsl");
263   if(!vshader_bytes)
264   {
265     cerr << "Failed fetching vertex shader resource" << endl;
266     m_Program = 0;
267     return;
268   }
269   gsize vshader_size {vshader_bytes->get_size()};
270   auto vertex = create_shader(GL_VERTEX_SHADER,
271                          (const char*)vshader_bytes->get_data(vshader_size));
272 
273   if(vertex == 0)
274   {
275     m_Program = 0;
276     return;
277   }
278 
279   auto fshader_bytes = Gio::Resource::lookup_data_global("/glarea/glarea-fragment.glsl");
280   if(!fshader_bytes)
281   {
282     cerr << "Failed fetching fragment shader resource" << endl;
283     glDeleteShader(vertex);
284     m_Program = 0;
285     return;
286   }
287   gsize fshader_size {fshader_bytes->get_size()};
288   auto fragment = create_shader(GL_FRAGMENT_SHADER,
289                            (const char*)fshader_bytes->get_data(fshader_size));
290 
291   if(fragment == 0)
292   {
293     glDeleteShader(vertex);
294     m_Program = 0;
295     return;
296   }
297 
298   m_Program = glCreateProgram();
299   glAttachShader(m_Program, vertex);
300   glAttachShader(m_Program, fragment);
301 
302   glLinkProgram(m_Program);
303 
304   int status;
305   glGetProgramiv(m_Program, GL_LINK_STATUS, &status);
306   if(status == GL_FALSE)
307   {
308     int log_len;
309     glGetProgramiv(m_Program, GL_INFO_LOG_LENGTH, &log_len);
310 
311     string log_space(log_len+1, ' ');
312     glGetProgramInfoLog(m_Program, log_len, nullptr, (GLchar*)log_space.c_str());
313 
314     cerr << "Linking failure: " << log_space << endl;
315 
316     glDeleteProgram(m_Program);
317     m_Program = 0;
318   }
319   else
320   {
321     /* Get the location of the "mvp" uniform */
322     m_Mvp = glGetUniformLocation(m_Program, "mvp");
323 
324     glDetachShader(m_Program, vertex);
325     glDetachShader(m_Program, fragment);
326   }
327   glDeleteShader(vertex);
328   glDeleteShader(fragment);
329 }
330 
compute_mvp(float * res,float phi,float theta,float psi)331 static void compute_mvp(float *res,
332                         float phi,
333                         float theta,
334                         float psi)
335 {
336   float x       {phi * ((float)G_PI / 180.f)};
337   float y       {theta * ((float)G_PI / 180.f)};
338   float z       {psi * ((float)G_PI / 180.f)};
339   float c1      {cosf (x)};
340   float s1      {sinf (x)};
341   float c2      {cosf (y)};
342   float s2      {sinf (y)};
343   float c3      {cosf (z)};
344   float s3      {sinf (z)};
345   float c3c2    {c3 * c2};
346   float s3c1    {s3 * c1};
347   float c3s2s1  {c3 * s2 * s1};
348   float s3s1    {s3 * s1};
349   float c3s2c1  {c3 * s2 * c1};
350   float s3c2    {s3 * c2};
351   float c3c1    {c3 * c1};
352   float s3s2s1  {s3 * s2 * s1};
353   float c3s1    {c3 * s1};
354   float s3s2c1  {s3 * s2 * c1};
355   float c2s1    {c2 * s1};
356   float c2c1    {c2 * c1};
357 
358   /* apply all three rotations using the three matrices:
359    *
360    * ⎡  c3 s3 0 ⎤ ⎡ c2  0 -s2 ⎤ ⎡ 1   0  0 ⎤
361    * ⎢ -s3 c3 0 ⎥ ⎢  0  1   0 ⎥ ⎢ 0  c1 s1 ⎥
362    * ⎣   0  0 1 ⎦ ⎣ s2  0  c2 ⎦ ⎣ 0 -s1 c1 ⎦
363    */
364   res[0] = c3c2;  res[4] = s3c1 + c3s2s1;  res[8] = s3s1 - c3s2c1; res[12] = 0.f;
365   res[1] = -s3c2; res[5] = c3c1 - s3s2s1;  res[9] = c3s1 + s3s2c1; res[13] = 0.f;
366   res[2] = s2;    res[6] = -c2s1;         res[10] = c2c1;          res[14] = 0.f;
367   res[3] = 0.f;   res[7] = 0.f;           res[11] = 0.f;           res[15] = 1.f;
368 }
369 
draw_triangle()370 void Example_GLArea::draw_triangle()
371 {
372   float mvp[16];
373 
374   compute_mvp(mvp,
375               m_RotationAngles[X_AXIS],
376               m_RotationAngles[Y_AXIS],
377               m_RotationAngles[Z_AXIS]);
378 
379   glUseProgram(m_Program);
380 
381   glUniformMatrix4fv(m_Mvp, 1, GL_FALSE, &mvp[0]);
382 
383   glBindBuffer(GL_ARRAY_BUFFER, m_Vao);
384   glEnableVertexAttribArray(0);
385   glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, nullptr);
386 
387   glDrawArrays(GL_TRIANGLES, 0, 3);
388 
389   glDisableVertexAttribArray(0);
390   glBindBuffer(GL_ARRAY_BUFFER, 0);
391   glUseProgram(0);
392 }
393