1 /*
2 * Copyright (C) 2014-2018 Christopho, Solarus - http://www.solarus-games.org
3 *
4 * Solarus Quest Editor is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * Solarus Quest Editor is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17 #include <solarus/graphics/VertexArray.h>
18
19 // Workaround for conflicting declaration of GLsizeiptrARB on Windows.
20 #ifndef GL_ARB_vertex_buffer_object
21 #define GL_ARB_vertex_buffer_object 1
22 #endif
23
24 #include "widgets/shader_previewer.h"
25 #include "quest.h"
26 #include "shader_model.h"
27 #include "view_settings.h"
28
29 #define GLM_FORCE_INLINE
30 #include <solarus/graphics/DefaultShaders.h>
31 #include <solarus/graphics/Shader.h>
32
33 #include <QMatrix3x3>
34 #include <QMouseEvent>
35 #include <QOpenGLContext>
36 #include <QOpenGLFunctions>
37 #include <QWheelEvent>
38
39
40 namespace SolarusEditor {
41
42 constexpr const char* SWIPE_VERTEX_SHADER =
43 R"(
44 #if __VERSION__ >= 130
45 #define COMPAT_VARYING out
46 #define COMPAT_ATTRIBUTE in
47 #else
48 #define COMPAT_VARYING varying
49 #define COMPAT_ATTRIBUTE attribute
50 #endif
51
52 #ifdef GL_ES
53 precision mediump float;
54 #define COMPAT_PRECISION mediump
55 #else
56 #define COMPAT_PRECISION
57 #endif
58
59 uniform mat4 sol_mvp_matrix;
60 uniform mat3 sol_uv_matrix;
61 COMPAT_ATTRIBUTE vec2 sol_vertex;
62 COMPAT_ATTRIBUTE vec2 sol_tex_coord;
63 COMPAT_ATTRIBUTE vec4 sol_color;
64
65 COMPAT_VARYING vec2 sol_vtex_coord;
66 COMPAT_VARYING vec4 sol_vcolor;
67 COMPAT_VARYING float vfactor;
68
69 void main() {
70 gl_Position = sol_mvp_matrix * vec4(sol_vertex,0,1);
71 vfactor = (gl_Position.x + 1.0)*0.5;
72 sol_vcolor = sol_color;
73 sol_vtex_coord = (sol_uv_matrix * vec3(sol_tex_coord,1)).xy;
74 }
75 )";
76
77 constexpr const char* SWIPE_FRAGMENT_SHADER =
78 R"(
79 #if __VERSION__ >= 130
80 #define COMPAT_VARYING in
81 #define COMPAT_TEXTURE texture
82 out vec4 FragColor;
83 #else
84 #define COMPAT_VARYING varying
85 #define FragColor gl_FragColor
86 #define COMPAT_TEXTURE texture2D
87 #endif
88
89 #ifdef GL_ES
90 precision mediump float;
91 #define COMPAT_PRECISION mediump
92 #else
93 #define COMPAT_PRECISION
94 #endif
95
96 uniform float factor;
97 uniform sampler2D input_tex;
98 uniform sampler2D output_tex;
99 uniform vec2 sol_output_size;
100 COMPAT_VARYING vec2 sol_vtex_coord;
101 COMPAT_VARYING vec4 sol_vcolor;
102 COMPAT_VARYING float vfactor;
103
104 void main() {
105 vec4 tex_i = COMPAT_TEXTURE(input_tex, sol_vtex_coord);
106 vec4 tex_o = COMPAT_TEXTURE(output_tex, vec2(sol_vtex_coord.x,1.0-sol_vtex_coord.y));
107 if(vfactor<factor) {
108 FragColor = tex_i;
109 } else {
110 FragColor = tex_o;
111 }
112 }
113 )";
114
115 /**
116 * @brief Creates a shader previewer.
117 * @param parent The parent object or nullptr.
118 */
ShaderPreviewer(QWidget * parent)119 ShaderPreviewer::ShaderPreviewer(QWidget *parent) :
120 QOpenGLWidget(parent),
121 program(this),
122 model(nullptr),
123 preview_mode(ShaderPreviewMode::SIDE_BY_SIDE)
124 #ifdef SOLARUSEDITOR_DEBUG_GL
125 ,gl_logger(this)
126 #endif
127 {
128
129 QSurfaceFormat format;
130 format.setProfile(QSurfaceFormat::CoreProfile);
131 #ifdef SOLARUSEDITOR_DEBUG_GL
132 format.setMajorVersion(3);
133 format.setMinorVersion(2);
134 format.setOption(QSurfaceFormat::DebugContext);
135 #endif
136 setFormat(format);
137
138 // Setup cursors
139 grab_cursor.setShape(Qt::ClosedHandCursor);
140 hover_cursor.setShape(Qt::OpenHandCursor);
141
142 setCursor(hover_cursor);
143 setMouseTracking(true);
144
145 connect(&fps_timer,&QTimer::timeout,[this]{
146 update();
147 });
148 fps_timer.setInterval(10);
149 fps_timer.start();
150 }
151
152 /**
153 * @brief handle mouse movement, moving scene if grabbed
154 * @param event
155 */
mouseMoveEvent(QMouseEvent * event)156 void ShaderPreviewer::mouseMoveEvent(QMouseEvent* event) {
157 Q_UNUSED(event);
158 if (grabbing) {
159 QPointF d = event->localPos() - last_mouse_pos;
160 d.setY(-d.y());
161 translation += QVector2D(d) / (zoom*pixelFactor());
162 last_mouse_pos = event->localPos();
163 event->accept();
164 }
165 update();
166 }
167
168 /**
169 * @brief handle mouse press
170 * @param event
171 */
mousePressEvent(QMouseEvent * event)172 void ShaderPreviewer::mousePressEvent(QMouseEvent* event) {
173 Q_UNUSED(event);
174 if (event->button() == Qt::LeftButton ||
175 event->button() == Qt::MiddleButton) {
176 grabbing = true;
177 last_mouse_pos = event->localPos();
178 setCursor(grab_cursor);
179 event->accept();
180 }
181 }
182
183 /**
184 * @brief handle mouse release
185 * @param event
186 */
mouseReleaseEvent(QMouseEvent * event)187 void ShaderPreviewer::mouseReleaseEvent(QMouseEvent* event) {
188 Q_UNUSED(event);
189 if (event->button() == Qt::LeftButton ||
190 event->button() == Qt::MiddleButton) {
191 grabbing = false;
192 setCursor(hover_cursor);
193 event->accept();
194 }
195 }
196
197
198 /**
199 * @brief get displacement to the frame center
200 * @param mouse_position
201 * @return
202 */
to_frame_center(const QPoint & mouse_position) const203 QVector2D ShaderPreviewer::to_frame_center(const QPoint& mouse_position) const {
204 auto center = [&]()->QVector2D{
205 switch(preview_mode) {
206 case ShaderPreviewMode::SIDE_BY_SIDE:
207 {
208 float y = frameSize().height() / 2.f;
209 QSize qsize = model->get_quest().get_properties().get_normal_quest_size();
210 qsize.setWidth(qsize.width()*2);
211 QSize letterb = get_letter_box(qsize,frameSize());
212 int rx = (frameSize().width() - letterb.width()) / 2;
213 if(mouse_position.x() < frameSize().width() / 2) {
214 return {rx+letterb.width()/4.f,y};
215 } else {
216 return {rx+(letterb.width()*3.f)/4.f,y};
217 }
218 }
219 default:
220 {
221 QSize fsize = frameSize();
222 return {fsize.width()/2.f,fsize.height()/2.f};
223 }
224 };
225 };
226 return (center() - QVector2D(mouse_position))*QVector2D(1,-1);
227 }
228
229 /**
230 * @brief Receives a mouse wheel event.
231 * @param event The event to handle.
232 */
wheelEvent(QWheelEvent * event)233 void ShaderPreviewer::wheelEvent(QWheelEvent* event) {
234
235 float old_zoom = zoom;
236 if (event->delta() > 0) {
237 zoom_in();
238 if(zoom != old_zoom) {
239 translation += (to_frame_center(event->pos())/zoom) / pixelFactor();
240 }
241 }
242 else {
243 zoom_out();
244 if(zoom != old_zoom) {
245 translation -= 0.5*(to_frame_center(event->pos())/zoom) / pixelFactor();
246 }
247 }
248
249 }
250
251 /**
252 * @brief Scales the view by a factor of 2.
253 *
254 * Zooming will be anchored at the mouse position.
255 * The maximum zoom value is 4.0: this function does nothing if you try to
256 * zoom more.
257 */
zoom_in()258 void ShaderPreviewer::zoom_in() {
259
260 if (view_settings == nullptr) {
261 return;
262 }
263
264 view_settings->set_zoom(view_settings->get_zoom() * 2.0);
265 }
266
267 /**
268 * @brief Scales the view by a factor of 0.5.
269 *
270 * Zooming will be anchored at the mouse position.
271 * The maximum zoom value is 0.25: this function does nothing if you try to
272 * zoom less.
273 */
zoom_out()274 void ShaderPreviewer::zoom_out() {
275
276 if (view_settings == nullptr) {
277 return;
278 }
279
280 view_settings->set_zoom(view_settings->get_zoom() / 2.0);
281 }
282
283 /**
284 * @brief Sets the zoom level of the view from the settings.
285 *
286 * Zooming will be anchored at the mouse position.
287 * The zoom value will be clamped between 1 and 4.0.
288 */
289
update_zoom()290 void ShaderPreviewer::update_zoom() {
291
292 if (view_settings == nullptr) {
293 return;
294 }
295
296 float zoom = static_cast<float>(view_settings->get_zoom());
297 zoom = qMin(4.0f, qMax(1.f, zoom));
298 view_settings->set_zoom(zoom);
299
300 if (zoom == this->zoom) {
301 return;
302 }
303
304
305 this->zoom = zoom;
306 update();
307 }
308
309 /**
310 * @brief compute letter boxed size
311 * @param qsize size of the box to fit
312 * @param basesize size of the enclosing box
313 * @return qsize scaled to fit in basesize
314 */
get_letter_box(const QSize & qsize,const QSize & basesize) const315 QSize ShaderPreviewer::get_letter_box(const QSize& qsize, const QSize& basesize) const {
316 float qratio = qsize.width() / (float) qsize.height();
317 float wratio = basesize.width() / (float) basesize.height();
318 if (qratio > wratio) {
319 return QSize(basesize.width(), basesize.width() / qratio);
320 } else {
321 return QSize(basesize.height() * qratio, basesize.height());
322 }
323 }
324
325 /**
326 * @brief ShaderPreviewer::sanitizeShaderCode
327 * @param code
328 * @return
329 */
sanitizeShaderCode(const QString & code) const330 QString ShaderPreviewer::sanitizeShaderCode(const QString& code) const {
331 if(code.contains("#version")) {
332 return code;
333 } else {
334 return glsl_version + code;
335 }
336 }
337
338 /**
339 * @brief return the factor between inputframebuffer pixels and screen pixels
340 * @return
341 */
pixelFactor() const342 float ShaderPreviewer::pixelFactor() const {
343 QSize qsize = model->get_quest().get_properties().get_normal_quest_size();
344 switch(preview_mode) {
345 case ShaderPreviewMode::SIDE_BY_SIDE: {
346 qsize.setWidth(qsize.width()*2);
347 QSize lb = get_letter_box(qsize,frameSize());
348 return 0.5f*lb.width() / (float) input_fb->width();
349 }
350 default: {
351 QSize lb = get_letter_box(qsize,frameSize());
352 return lb.width() / (float) input_fb->width();
353 }
354 }
355 }
356
357 /**
358 * @brief Sets the shader model.
359 * @param model The shader model, or nullptr to remove any model.
360 * This class does not take ownership on the model.
361 * The model can be deleted safely.
362 */
set_model(ShaderModel * model)363 void ShaderPreviewer::set_model(ShaderModel* model) {
364
365 if (this->model != nullptr) {
366 this->model->disconnect(this);
367 this->model = nullptr;
368 }
369
370 this->model = model;
371
372 if (model != nullptr) {
373 connect(model, &ShaderModel::fragment_file_changed,
374 this, &ShaderPreviewer::on_source_changed);
375 connect(model, &ShaderModel::vertex_file_changed,
376 this, &ShaderPreviewer::on_source_changed);
377 connect(model, &ShaderModel::scaling_factor_changed,
378 this, &ShaderPreviewer::on_scaling_factor_changed);
379 on_source_changed();
380 }
381 }
382
383 /**
384 * @brief Sets the view settings for this view.
385 *
386 * When they change, the view is updated accordingly.
387 *
388 * @param view_settings The settings to watch.
389 */
set_view_settings(ViewSettings & view_settings)390 void ShaderPreviewer::set_view_settings(ViewSettings& view_settings) {
391
392 this->view_settings = &view_settings;
393
394 connect(&view_settings, &ViewSettings::zoom_changed,
395 this, &ShaderPreviewer::update_zoom);
396 update_zoom();
397 }
398
399 /**
400 * @brief Returns the current preview mode.
401 * @return The preview mode.
402 */
get_preview_mode() const403 ShaderPreviewMode ShaderPreviewer::get_preview_mode() const {
404 return preview_mode;
405 }
406
407 /**
408 * @brief Changes the preview mode.
409 * @param preview_mode The new mode to set.
410 */
set_preview_mode(ShaderPreviewMode preview_mode)411 void ShaderPreviewer::set_preview_mode(ShaderPreviewMode preview_mode) {
412 this->preview_mode = preview_mode;
413 update();
414 }
415
416 /**
417 * @brief re-create framebuffer with appropriate size
418 * @param output_size size of the output buffer
419 */
setup_framebuffers(const QSize & output_size)420 void ShaderPreviewer::setup_framebuffers(const QSize& output_size) {
421 makeCurrent();
422 delete input_fb;
423 delete output_fb;
424
425 QSize size = model->get_quest().get_properties().get_normal_quest_size();
426 input_fb = new QOpenGLFramebufferObject(size);
427 if (!input_fb->isValid()) {
428 qWarning() << "Failed to create input frame buffer with size" << size;
429 }
430 output_fb = new QOpenGLFramebufferObject(output_size);
431 if (!output_fb->isValid()) {
432 qWarning() << "Failed to create output frame buffer with size" << output_size;
433 }
434 }
435
436 /**
437 * @brief Render input to input buffer and rerender it with shader to output buffer
438 */
render_fbs()439 void ShaderPreviewer::render_fbs() {
440 if (input_texture == nullptr) {
441 return;
442 }
443 QOpenGLFunctions* gl = context()->functions();
444 { //Render input to simulate quest surface
445 input_fb->bind();
446 QMatrix4x4 mvp;
447 //TODO mouse control
448 mvp.ortho(0, input_fb->width(), input_fb->height(), 0, -1, 1);
449 //mvp.scale(zoom);
450 mvp.translate(floor(translation.x()), floor(translation.y()), 0);
451 mvp.scale(input_texture->width(), input_texture->height(), 1);
452 QMatrix3x3 uvm;
453 gl->glClearColor(0, 0, 0, 1.f);
454 gl->glViewport(0, 0, input_fb->width(), input_fb->height());
455 gl->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
456 simple_program.bind();
457 simple_program.setUniformValue(Solarus::Shader::UV_MATRIX_NAME, uvm);
458 simple_program.setUniformValue(Solarus::Shader::MVP_MATRIX_NAME, mvp);
459 const char* texture_name = Solarus::Shader::TEXTURE_NAME;
460 render_quad(simple_program, {{texture_name, input_texture->textureId()}});
461 input_fb->release();
462 }
463 { // Render output to simulate screen
464 output_fb->bind();
465 QMatrix4x4 mvp;
466 mvp.translate(-1, -1, 0);
467 mvp.scale(2);
468 QMatrix3x3 uvm;
469 gl->glClearColor(0, 0, 0, 0);
470 gl->glViewport(0, 0, output_fb->width(), output_fb->height());
471 gl->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
472 program.bind();
473 program.setUniformValue(Solarus::Shader::UV_MATRIX_NAME,uvm);
474 program.setUniformValue(Solarus::Shader::MVP_MATRIX_NAME,mvp);
475 QSize isize = model->get_quest().get_properties().get_normal_quest_size();
476 program.setUniformValue(
477 Solarus::Shader::INPUT_SIZE_NAME,
478 QVector2D(isize.width(), isize.height()));
479 program.setUniformValue(
480 Solarus::Shader::OUTPUT_SIZE_NAME,
481 QVector2D(output_fb->width(), output_fb->height()));
482 int time_loc = program.uniformLocation(Solarus::Shader::TIME_NAME);
483 int ms = QTime::currentTime().msecsSinceStartOfDay();
484 gl->glUniform1i(time_loc, ms);
485 const char* texture_name = Solarus::Shader::TEXTURE_NAME;
486 render_quad(program, {{texture_name, input_fb->texture()}});
487 output_fb->release();
488 }
489
490 }
491
492 /**
493 * @brief render a quad with the given shader and textures
494 * @param shader a valid Solarus-Style shader
495 * @param textures a vector of name-handle pairs
496 */
render_quad(QOpenGLShaderProgram & shader,const Textures & textures)497 void ShaderPreviewer::render_quad(QOpenGLShaderProgram& shader, const Textures& textures) {
498 shader.bind();
499 vao->bind();
500 vertex_buffer->bind();
501 QOpenGLFunctions* gl = context()->functions();
502 int pos_loc = shader.attributeLocation(Solarus::Shader::POSITION_NAME);
503 if (pos_loc > -1) {
504 gl->glEnableVertexAttribArray(pos_loc);
505 gl->glVertexAttribPointer(pos_loc, 2, GL_FLOAT, GL_FALSE, sizeof(Solarus::Vertex), (void*) offsetof(Solarus::Vertex, position));
506 }
507 int uv_loc = shader.attributeLocation(Solarus::Shader::TEXCOORD_NAME);
508 if (uv_loc > -1) {
509 gl->glEnableVertexAttribArray(uv_loc);
510 gl->glVertexAttribPointer(uv_loc, 2, GL_FLOAT, GL_FALSE, sizeof(Solarus::Vertex), (void*) offsetof(Solarus::Vertex, texcoords));
511 }
512 int color_loc = shader.attributeLocation(Solarus::Shader::COLOR_NAME);
513 if (color_loc > -1) {
514 gl->glEnableVertexAttribArray(color_loc);
515 gl->glVertexAttribPointer(color_loc, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(Solarus::Vertex), (void*) offsetof(Solarus::Vertex, color));
516 }
517
518 for (size_t i = 0; i < textures.size(); ++i) {
519 int tex_loc = shader.uniformLocation(textures[i].first);
520 gl->glUniform1i(tex_loc,i);
521 gl->glActiveTexture(GL_TEXTURE0 + i);
522 gl->glBindTexture(GL_TEXTURE_2D, textures[i].second);
523 gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
524 gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
525 }
526 gl->glDrawArrays(GL_TRIANGLES, 0, 6);
527 vertex_buffer->release();
528 vao->release();
529 shader.release();
530 }
531
532 /**
533 * @brief render the framebuffers in a swipe fashion
534 * @param factor position of the vertical split [0,1]
535 */
render_swipe(float factor)536 void ShaderPreviewer::render_swipe(float factor) {
537 swipe_program.bind();
538 QMatrix4x4 mvp;
539 mvp.scale(zoom);
540 mvp.translate(-1, -1, 0);
541 mvp.scale(2);
542 QMatrix3x3 uvm;
543 swipe_program.setUniformValue(Solarus::Shader::UV_MATRIX_NAME, uvm);
544 swipe_program.setUniformValue(Solarus::Shader::MVP_MATRIX_NAME, mvp);
545 swipe_program.setUniformValue("factor", factor);
546 QSize vp = get_letter_box(model->get_quest().get_properties().get_normal_quest_size(), frameSize());
547 swipe_program.setUniformValue(
548 Solarus::Shader::OUTPUT_SIZE_NAME,
549 QVector2D(vp.width(),vp.height()));
550 render_quad(swipe_program, {{"input_tex", input_fb->texture()}, {"output_tex", output_fb->texture()}});
551 }
552
553 /**
554 * @brief render the framebuffers in a Side by Side fashion
555 */
render_sbs()556 void ShaderPreviewer::render_sbs() {
557 QSize qsize = model->get_quest().get_properties().get_normal_quest_size();
558 qsize.setWidth(qsize.width()*2);
559 QSize letterb = get_letter_box(qsize,frameSize());
560 auto render_side = [&](GLuint tex, bool invert) {
561 QMatrix4x4 mvp;
562 mvp.scale(zoom);
563 mvp.translate(-1, -1, 0);
564 mvp.scale(2);
565 QMatrix3x3 uvm;
566 if (invert) {
567 uvm.data()[4] = -1;
568 uvm.data()[7] = 1;
569 }
570 simple_program.bind();
571 simple_program.setUniformValue(Solarus::Shader::UV_MATRIX_NAME, uvm);
572 simple_program.setUniformValue(Solarus::Shader::MVP_MATRIX_NAME, mvp);
573 const char* name = Solarus::Shader::TEXTURE_NAME;
574 render_quad(simple_program, {{name, tex}});
575 };
576 QOpenGLFunctions* gl = context()->functions();
577 int x = (frameSize().width() - letterb.width()) / 2;
578 int y = (frameSize().height() - letterb.height()) / 2;
579 gl->glViewport(x, y, letterb.width() / 2, letterb.height());
580 render_side(input_fb->texture(), false);
581 gl->glViewport(x + letterb.width() / 2, y, letterb.width()/2, letterb.height());
582 render_side(output_fb->texture(), true);
583 }
584
585 /**
586 * @brief QOpengl pain event all drawing happens here
587 */
paintGL()588 void ShaderPreviewer::paintGL() {
589 QOpenGLFunctions* gl = context()->functions();
590 gl->glClearColor(0.3f, 0.3f, 0.3f, 1);
591 gl->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
592 if (model == nullptr) {
593 return;
594 }
595 if (should_recompile) {
596 compile_program();
597 }
598 render_fbs();
599 QSize letterb = get_letter_box(model->get_quest().get_properties().get_normal_quest_size(), frameSize());
600 int x = (frameSize().width() - letterb.width()) / 2;
601 int y = (frameSize().height() - letterb.height()) / 2;
602 gl->glViewport(x, y, letterb.width(), letterb.height());
603 switch (preview_mode) {
604 case ShaderPreviewMode::INPUT:
605 render_swipe(1.f);
606 break;
607 case ShaderPreviewMode::OUTPUT:
608 render_swipe(0.f);
609 break;
610 case ShaderPreviewMode::SIDE_BY_SIDE:
611 render_sbs();
612 break;
613 case ShaderPreviewMode::SWIPE:
614 {
615 QPoint p = mapFromGlobal(QCursor::pos());
616 float factor = (p.x() - x) / (float) letterb.width();
617 render_swipe(factor); // TODO pass actual factor
618 break;
619 }
620 default:
621 break;
622 }
623 }
624
625 /**
626 * @brief QOpengl initialisation event, initialize as much as possible
627 */
initializeGL()628 void ShaderPreviewer::initializeGL() {
629
630 #ifdef SOLARUSEDITOR_DEBUG_GL
631 gl_logger.initialize();
632 gl_logger.startLogging(QOpenGLDebugLogger::SynchronousLogging);
633 connect(&gl_logger,&QOpenGLDebugLogger::messageLogged, this, &ShaderPreviewer::on_gl_log);
634 #endif
635 QSurfaceFormat format = context()->format();
636
637 auto make_number = [&](int major,int minor) -> QString {
638 switch(major*10+minor){
639 case 20:
640 return "110";
641 case 21:
642 return "120";
643 case 30:
644 return "130";
645 case 31:
646 return "140";
647 case 32:
648 return "150";
649 default:
650 if(major*10+minor >= 33) {
651 return QString::number(major*100+minor*10);
652 } else {
653 return "110";
654 }
655 }
656 };
657
658 glsl_version = QString("#version %1\n").arg(make_number(format.majorVersion(),format.minorVersion()));
659
660 // Setup quad
661 QOpenGLFunctions* gl = context()->functions();
662 gl->initializeOpenGLFunctions();
663 gl->glClearColor(0.3f,0.3f,0.3f,1);
664 gl->glDisable(GL_DEPTH_TEST);
665 gl->glDisable(GL_CULL_FACE);
666 vertex_buffer = new QOpenGLBuffer();
667 if (!vertex_buffer->create()) {
668 qWarning() << "Failed to create glbuffer!"; // TODO fail gracefully
669 }
670 Solarus::VertexArray array;
671 array.add_quad(Solarus::Rectangle(0, 0, 1, 1),
672 Solarus::Rectangle(0, 1, 1, -1),
673 Solarus::Color::white);
674 vertex_buffer->bind();
675 vertex_buffer->allocate(array.data(), array.vertex_count() * sizeof(Solarus::Vertex));
676 vertex_buffer->release();
677 // Create empty vao for core profiles
678 vao = new QOpenGLVertexArrayObject();
679 vao->create();
680
681 // Create simple shader
682 simple_program.addShaderFromSourceCode(
683 QOpenGLShader::Vertex,
684 Solarus::DefaultShaders::get_default_vertex_source().c_str());
685 simple_program.addShaderFromSourceCode(
686 QOpenGLShader::Fragment,
687 Solarus::DefaultShaders::get_default_fragment_source().c_str());
688 if (!simple_program.link()) {
689 emit shader_error("Failed to link basic shader program:\n" + simple_program.log());
690 }
691
692 // Create swipe shader
693 swipe_program.addShaderFromSourceCode(
694 QOpenGLShader::Vertex,
695 sanitizeShaderCode(SWIPE_VERTEX_SHADER));
696
697 swipe_program.addShaderFromSourceCode(
698 QOpenGLShader::Fragment,
699 sanitizeShaderCode(SWIPE_FRAGMENT_SHADER));
700 if (!swipe_program.link()) {
701 emit shader_error("Failed to link swipe shader program:\n" + swipe_program.log());
702 }
703
704 // OpenGL is now ready, build the texture if it was already set.
705 build_preview_texture();
706 }
707
708 /**
709 * @brief QOpenGL resize event, framebuffer are recreated at the right size
710 * @param w width
711 * @param h height
712 */
resizeGL(int w,int h)713 void ShaderPreviewer::resizeGL(int w, int h) {
714 Q_UNUSED(w);
715 Q_UNUSED(h);
716 if (model != nullptr) {
717 if (model->get_scaling_factor() > 0) {
718 // Intermediate surface with fixed size
719 setup_framebuffers(
720 model->get_scaling_factor() * model->get_quest().get_properties().get_normal_quest_size());
721 } else {
722 QSize letterb = get_letter_box(model->get_quest().get_properties().get_normal_quest_size(), frameSize());
723 setup_framebuffers(letterb);
724 }
725 }
726 }
727
728 /**
729 * @brief notify change of the model scaling factor
730 * @param factor
731 */
on_scaling_factor_changed(double factor)732 void ShaderPreviewer::on_scaling_factor_changed(double factor) {
733 Q_UNUSED(factor);
734 /*setup_framebuffers(
735 factor * model->get_quest().get_properties().get_normal_quest_size());*/
736 resizeGL(0,0);
737 update();
738 }
739
740 /**
741 * @brief notify change of the model sources
742 */
on_source_changed()743 void ShaderPreviewer::on_source_changed() {
744 should_recompile = true;
745 update();
746 }
747
748 /**
749 * @brief compile the model shader
750 */
compile_program()751 void ShaderPreviewer::compile_program() {
752 program.removeAllShaders();
753 emit shader_compilation_started(model->get_shader_id());
754 auto check_warnings = [this]{
755 if(program.log().size()) {
756 emit shader_warning(program.log());
757 }
758 };
759 auto fail = [this](const QString& s){
760 emit shader_error(s);
761 should_recompile = false;
762 };
763
764 auto readFile = [](const QString& path) -> QString {
765 QFile file(path);
766 file.open(QIODevice::ReadOnly);
767
768 QTextStream s1(&file);
769 return s1.readAll();
770 };
771
772 QString vertex_source;
773 QString fragment_source;
774
775 QString vertex_file_path = model->get_quest().get_shader_code_file_path(model->get_vertex_file());
776 if (!model->get_quest().exists(vertex_file_path) ||
777 !model->get_quest().is_shader_code_file(vertex_file_path)) {
778 vertex_file_path = QString();
779 }
780 if (!vertex_file_path.isEmpty()) {
781 vertex_source = readFile(vertex_file_path);
782 } else {
783 vertex_source = QString::fromStdString(Solarus::DefaultShaders::get_default_vertex_source());
784 }
785
786 QString fragment_file_path = model->get_quest().get_shader_code_file_path(model->get_fragment_file());
787 if (!model->get_quest().exists(fragment_file_path) ||
788 !model->get_quest().is_shader_code_file(fragment_file_path)) {
789 fragment_file_path = QString();
790 }
791 if (!fragment_file_path.isEmpty()) {
792 fragment_source = readFile(fragment_file_path);
793 } else {
794 fragment_source = QString::fromStdString(Solarus::DefaultShaders::get_default_fragment_source());
795 }
796
797 if (!program.addShaderFromSourceCode(
798 QOpenGLShader::Vertex,
799 sanitizeShaderCode(vertex_source))) {
800 return fail("Failed to compile vertex shader:\n" + program.log());
801 }
802 if (!program.addShaderFromSourceCode(
803 QOpenGLShader::Fragment,
804 sanitizeShaderCode(fragment_source))) {
805 return fail("Failed to compile fragment shader:\n" + program.log());
806 }
807
808 if (!program.link()) {
809 return fail("Failed to link shader program:\n" + program.log());
810 } else {
811 check_warnings();
812 }
813
814 should_recompile = false;
815 emit shader_compilation_finished(model->get_shader_id());
816 }
817
818 #ifdef SOLARUSEDITOR_DEBUG_GL
819 /**
820 * @brief OpenGL error log slot
821 * @param message
822 */
on_gl_log(const QOpenGLDebugMessage & message)823 void ShaderPreviewer::on_gl_log(const QOpenGLDebugMessage& message) {
824 qDebug() << message;
825 }
826 #endif
827
828 /**
829 * @brief Changes the image to be displayed in the preview widget.
830 * @param image The new image to show.
831 */
set_preview_image(QImage image)832 void ShaderPreviewer::set_preview_image(QImage image) {
833
834 if (image == this->preview_image) {
835 return;
836 }
837 this->preview_image = image;
838 build_preview_texture();
839 }
840
841 /**
842 * @brief Creates the OpenGL texture to be displayed.
843 */
build_preview_texture()844 void ShaderPreviewer::build_preview_texture() {
845
846 if (vertex_buffer == nullptr) {
847 // OpenGL is not initialized yet.
848 return;
849 }
850
851 // TODO manage memory
852 makeCurrent();
853 delete input_texture;
854 input_texture = nullptr;
855
856 if (!preview_image.isNull()) {
857 input_texture = new QOpenGLTexture(preview_image, QOpenGLTexture::MipMapGeneration::DontGenerateMipMaps);
858 }
859 zoom = 1;
860 translation = QVector2D();
861
862 update();
863 }
864
865 }
866