Stellarium 0.12.3
StelQGLViewport.hpp
1 /*
2  * Stellarium
3  * Copyright (C) 2012 Ferdinand Majerech
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
18  */
19 
20 #ifndef _STELQGLVIEWPORT_HPP_
21 #define _STELQGLVIEWPORT_HPP_
22 
23 #include <QGLFramebufferObject>
24 #include <QGLWidget>
25 #include <QGraphicsView>
26 #include <QImage>
27 #include <QPainter>
28 
29 #include "StelUtils.hpp"
30 
34 class StelQGLWidget : public QGLWidget
35 {
36 public:
37  StelQGLWidget(QGLContext* ctx, QWidget* parent) : QGLWidget(ctx, parent)
38  {
39  setAttribute(Qt::WA_PaintOnScreen);
40  setAttribute(Qt::WA_NoSystemBackground);
41  setAttribute(Qt::WA_OpaquePaintEvent);
42  //setAutoFillBackground(false);
43  setBackgroundRole(QPalette::Window);
44  }
45 
47  QPaintEngine::Type getPaintEngineType() const
48  {
49  return paintEngineType;
50  }
51 
52 protected:
53  virtual void initializeGL()
54  {
55  qDebug() << "OpenGL supported version: " << QString((char*)glGetString(GL_VERSION));
56 
57  QGLWidget::initializeGL();
58 
59  if (!format().stencil())
60  qWarning("Could not get stencil buffer; results will be suboptimal");
61  if (!format().depth())
62  qWarning("Could not get depth buffer; results will be suboptimal");
63  if (!format().doubleBuffer())
64  qWarning("Could not get double buffer; results will be suboptimal");
65 
66  QString paintEngineStr;
67  paintEngineType = paintEngine()->type();
68  switch (paintEngineType)
69  {
70  case QPaintEngine::OpenGL: paintEngineStr = "OpenGL"; break;
71  case QPaintEngine::OpenGL2: paintEngineStr = "OpenGL2"; break;
72  default: paintEngineStr = "Other";
73  }
74  qDebug() << "Qt GL paint engine is: " << paintEngineStr;
75  }
76 
78  QPaintEngine::Type paintEngineType;
79 };
80 
85 {
86 public:
91  StelQGLViewport(StelQGLWidget* const glWidget, QGraphicsView* const parent)
92  : viewportSize(QSize())
93  , glWidget(glWidget)
94  , painter(NULL)
95  , defaultPainter(NULL)
96  , backBufferPainter(NULL)
97  , frontBuffer(NULL)
98  , backBuffer(NULL)
99  , drawing(false)
100  , usingGLWidgetPainter(false)
101  , fboSupported(false)
102  , fboDisabled(false)
103  , nonPowerOfTwoTexturesSupported(false)
104  {
105  // Forces glWidget to initialize GL.
106  glWidget->updateGL();
107 
108 #ifdef _MSC_BUILD
109  glEnable(GL_MULTISAMPLE);
110  glEnable(GL_LINE_SMOOTH);
111 #endif
112 
113  // Qt GL2 paint engine does some FBO magic depending
114  // on glBlendFunc having a particular value. I wasn't able
115  // to figure out what value it was, so another workaround
116  // was to not use FBOs ourselves.
117  // Not sure _why exactly_ this helps though.
118  //
119  // TODO
120  // If Qt is newer than 4.0, try removing this if -
121  // FBOs are an important feature so we want to use it
122  // if the Qt people cleaned up their mess.
123  if(qtPaintEngineType() == QPaintEngine::OpenGL2)
124  {
125  fboDisabled = true;
126  }
127  parent->setViewport(glWidget);
128  invariant();
129  }
130 
133  {
134  invariant();
135  Q_ASSERT_X(NULL == this->painter, Q_FUNC_INFO,
136  "Painting is not disabled at destruction");
137 
138  destroyFBOs();
139  // No need to delete the GL widget, its parent widget will do that.
140  glWidget = NULL;
141  }
142 
148  void init(const bool npot, const QString& glVendor, const QString& glRenderer)
149  {
150  invariant();
151  this->nonPowerOfTwoTexturesSupported = npot;
152  // Prevent flickering on mac Leopard/Snow Leopard
153  glWidget->setAutoFillBackground(false);
154 
155  // In some virtual machines.
156  // Not all needed FBO functionality needed is supported.
157  if(glRenderer == "Chromium")
158  {
159  fboDisabled = true;
160  }
161  if(glVendor == "Tungsten Graphics, Inc")
162  {
163  if(glRenderer.contains("945") ||
164  glRenderer.contains("810") ||
165  glRenderer.contains("845") ||
166  glRenderer.contains("855") ||
167  glRenderer.contains("865") ||
168  glRenderer.contains("915") ||
169  glRenderer.contains("946") ||
170  glRenderer.contains("500") ||
171  glRenderer.contains("965") ||
172  glRenderer.contains("950") ||
173  glRenderer.contains("X3100") ||
174  glRenderer.contains("GM45") ||
175  glRenderer.contains("Ironlake") ||
176  glRenderer.contains("G33") ||
177  glRenderer.contains("G41") ||
178  glRenderer.contains("IGD"))
179  {
180  fboDisabled = true;
181  }
182  }
183 
184  fboSupported = QGLFramebufferObject::hasOpenGLFramebufferObjects();
185  invariant();
186  }
187 
189  QPaintEngine::Type qtPaintEngineType() const
190  {
191  return glWidget->getPaintEngineType();
192  }
193 
195  void viewportHasBeenResized(const QSize newSize)
196  {
197  invariant();
198  //Can't check this in invariant because Renderer is initialized before
199  //AppGraphicsWidget sets its viewport size
200  Q_ASSERT_X(newSize.isValid(), Q_FUNC_INFO, "Invalid scene size");
201  viewportSize = newSize;
202  //We'll need FBOs of different size so get rid of the current FBOs.
203  destroyFBOs();
204  invariant();
205  }
206 
214  void setDefaultPainter(QPainter* const painter, const QGLContext* const context)
215  {
216  invariant();
217 
218 #ifndef NDEBUG
219  if(NULL != painter)
220  {
221  Q_ASSERT_X(painter->paintEngine()->type() == QPaintEngine::OpenGL ||
222  painter->paintEngine()->type() == QPaintEngine::OpenGL2,
223  Q_FUNC_INFO,
224  "QGL StelRenderer backends need a QGLWidget to be set as the "
225  "viewport on the graphics view");
226  QGLWidget* widget = dynamic_cast<QGLWidget*>(painter->device());
227  Q_ASSERT_X(NULL != widget && widget->context() == context, Q_FUNC_INFO,
228  "Painter used with QGL StelRenderer backends needs to paint on a QGLWidget "
229  "with the same GL context as the one used by the renderer");
230  }
231 #else
232  Q_UNUSED(context);
233 #endif
234 
235  defaultPainter = painter;
236  invariant();
237  }
238 
240  QImage screenshot() const
241  {
242  invariant();
243  return glWidget->grabFrameBuffer();
244  }
245 
247  QSize getViewportSize() const
248  {
249  invariant();
250  return viewportSize;
251  }
252 
255 
257  bool useFBO() const
258  {
259  // Can't call invariant here as invariant calls useFBO
260  return fboSupported && !fboDisabled;
261  }
262 
266  void setFont(const QFont& font)
267  {
268  Q_ASSERT_X(NULL != painter, Q_FUNC_INFO,
269  "Trying to set text (painting) font but painting is disabled");
270  painter->setFont(font);
271  }
272 
274  void startFrame()
275  {
276  invariant();
277  if (useFBO())
278  {
279  //Draw to backBuffer.
280  initFBOs();
281  backBuffer->bind();
282  backBufferPainter = new QPainter(backBuffer);
283  enablePainting(backBufferPainter);
284  }
285  else
286  {
287  enablePainting();
288  }
289  drawing = true;
290  invariant();
291  }
292 
297  void suspendFrame()
298  {
299  Q_ASSERT_X(useFBO(), Q_FUNC_INFO, "Partial rendering only works with FBOs");
300  finishFrame(false);
301  }
302 
304  void finishFrame(const bool swapBuffers = true)
305  {
306  invariant();
307  drawing = false;
308 
309  disablePainting();
310 
311  if (useFBO())
312  {
313  //Release the backbuffer.
314  delete backBufferPainter;
315  backBufferPainter = NULL;
316 
317  backBuffer->release();
318  //Swap buffers if finishing, don't swap yet if suspending.
319  if(swapBuffers){swapFBOs();}
320  }
321  invariant();
322  }
323 
326  {
327  invariant();
328  //Put the result of drawing to the FBO on the screen, applying an effect.
329  if (useFBO())
330  {
331  Q_ASSERT_X(!backBuffer->isBound() && !frontBuffer->isBound(), Q_FUNC_INFO,
332  "Framebuffer objects weren't released before drawing the result");
333  }
334  }
335 
338  {
339  invariant();
340 
341  if(usingGLWidgetPainter)
342  {
343  delete painter;
344  usingGLWidgetPainter = false;
345  }
346  painter = NULL;
347  invariant();
348  }
349 
352  {
353  invariant();
354  enablePainting(defaultPainter);
355  invariant();
356  }
357 
359  QPainter* getPainter()
360  {
361  return painter;
362  }
363 
364 private:
366  QSize viewportSize;
368  StelQGLWidget* glWidget;
369 
371  QPainter* painter;
373  QPainter* defaultPainter;
375  QPainter* backBufferPainter;
376 
378  QGLFramebufferObject* frontBuffer;
380  QGLFramebufferObject* backBuffer;
381 
383  bool drawing;
384 
386  bool usingGLWidgetPainter;
387 
389  bool fboSupported;
394  bool fboDisabled;
395 
397  bool nonPowerOfTwoTexturesSupported;
398 
400  void invariant() const
401  {
402 #ifndef NDEBUG
403  Q_ASSERT_X(NULL != glWidget, Q_FUNC_INFO, "Destroyed StelQGLViewport");
404  Q_ASSERT_X(glWidget->isValid(), Q_FUNC_INFO,
405  "Invalid glWidget (maybe there is no OpenGL support?)");
406 
407  const bool fbo = useFBO();
408  Q_ASSERT_X(NULL == backBufferPainter || fbo, Q_FUNC_INFO,
409  "We have a backbuffer painter even though we're not using FBO");
410  Q_ASSERT_X(drawing && fbo ? backBufferPainter != NULL : true, Q_FUNC_INFO,
411  "We're drawing and using FBOs, but the backBufferPainter is NULL");
412  Q_ASSERT_X(NULL == backBuffer || fbo, Q_FUNC_INFO,
413  "We have a backbuffer even though we're not using FBO");
414  Q_ASSERT_X(NULL == frontBuffer || fbo, Q_FUNC_INFO,
415  "We have a frontbuffer even though we're not using FBO");
416  Q_ASSERT_X(drawing && fbo ? backBuffer != NULL : true, Q_FUNC_INFO,
417  "We're drawing and using FBOs, but the backBuffer is NULL");
418  Q_ASSERT_X(drawing && fbo ? frontBuffer != NULL : true, Q_FUNC_INFO,
419  "We're drawing and using FBOs, but the frontBuffer is NULL");
420 #endif
421  }
422 
424  void enablePainting(QPainter* painter)
425  {
426  invariant();
427 
428  // If no painter specified, create a default one painting to the glWidget.
429  if(painter == NULL)
430  {
431  this->painter = new QPainter(glWidget);
432  usingGLWidgetPainter = true;
433  return;
434  }
435  this->painter = painter;
436  invariant();
437  }
438 
440  void initFBOs()
441  {
442  invariant();
443  Q_ASSERT_X(useFBO(), Q_FUNC_INFO, "We're not using FBO");
444  if (NULL == backBuffer)
445  {
446  Q_ASSERT_X(NULL == frontBuffer, Q_FUNC_INFO,
447  "frontBuffer is not null even though backBuffer is");
448 
449  // If non-power-of-two textures are supported,
450  // FBOs must have power of two size large enough to fit the viewport.
451  const QSize bufferSize = nonPowerOfTwoTexturesSupported
453  : viewportSize;
454 
455  // We want a depth and stencil buffer attached to our FBO if possible.
456  const QGLFramebufferObject::Attachment attachment =
457  QGLFramebufferObject::CombinedDepthStencil;
458  backBuffer = new QGLFramebufferObject(bufferSize, attachment);
459  frontBuffer = new QGLFramebufferObject(bufferSize, attachment);
460 
461  Q_ASSERT_X(backBuffer->isValid() && frontBuffer->isValid(),
462  Q_FUNC_INFO, "Framebuffer objects failed to initialize");
463  }
464  invariant();
465  }
466 
468  void swapFBOs()
469  {
470  invariant();
471  Q_ASSERT_X(useFBO(), Q_FUNC_INFO, "We're not using FBO");
472  QGLFramebufferObject* tmp = backBuffer;
473  backBuffer = frontBuffer;
474  frontBuffer = tmp;
475  invariant();
476  }
477 
479  void destroyFBOs()
480  {
481  invariant();
482  // Destroy framebuffers
483  if(NULL != frontBuffer)
484  {
485  delete frontBuffer;
486  frontBuffer = NULL;
487  }
488  if(NULL != backBuffer)
489  {
490  delete backBuffer;
491  backBuffer = NULL;
492  }
493  invariant();
494  }
495 };
496 
497 #endif // _STELQGLVIEWPORT_HPP_