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