Stellarium 0.12.2
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  // Qt GL2 paint engine does some FBO magic depending
109  // on glBlendFunc having a particular value. I wasn't able
110  // to figure out what value it was, so another workaround
111  // was to not use FBOs ourselves.
112  // Not sure _why exactly_ this helps though.
113  //
114  // TODO
115  // If Qt is newer than 4.0, try removing this if -
116  // FBOs are an important feature so we want to use it
117  // if the Qt people cleaned up their mess.
118  if(qtPaintEngineType() == QPaintEngine::OpenGL2)
119  {
120  fboDisabled = true;
121  }
122  parent->setViewport(glWidget);
123  invariant();
124  }
125 
128  {
129  invariant();
130  Q_ASSERT_X(NULL == this->painter, Q_FUNC_INFO,
131  "Painting is not disabled at destruction");
132 
133  destroyFBOs();
134  // No need to delete the GL widget, its parent widget will do that.
135  glWidget = NULL;
136  }
137 
143  void init(const bool npot, const QString& glVendor, const QString& glRenderer)
144  {
145  invariant();
146  this->nonPowerOfTwoTexturesSupported = npot;
147  // Prevent flickering on mac Leopard/Snow Leopard
148  glWidget->setAutoFillBackground(false);
149 
150  // In some virtual machines.
151  // Not all needed FBO functionality needed is supported.
152  if(glRenderer == "Chromium")
153  {
154  fboDisabled = true;
155  }
156  if(glVendor == "Tungsten Graphics, Inc")
157  {
158  if(glRenderer.contains("945") ||
159  glRenderer.contains("810") ||
160  glRenderer.contains("845") ||
161  glRenderer.contains("855") ||
162  glRenderer.contains("865") ||
163  glRenderer.contains("915") ||
164  glRenderer.contains("946") ||
165  glRenderer.contains("500") ||
166  glRenderer.contains("965") ||
167  glRenderer.contains("950") ||
168  glRenderer.contains("X3100") ||
169  glRenderer.contains("GM45") ||
170  glRenderer.contains("Ironlake") ||
171  glRenderer.contains("G33") ||
172  glRenderer.contains("G41") ||
173  glRenderer.contains("IGD"))
174  {
175  fboDisabled = true;
176  }
177  }
178 
179  fboSupported = QGLFramebufferObject::hasOpenGLFramebufferObjects();
180  invariant();
181  }
182 
184  QPaintEngine::Type qtPaintEngineType() const
185  {
186  return glWidget->getPaintEngineType();
187  }
188 
190  void viewportHasBeenResized(const QSize newSize)
191  {
192  invariant();
193  //Can't check this in invariant because Renderer is initialized before
194  //AppGraphicsWidget sets its viewport size
195  Q_ASSERT_X(newSize.isValid(), Q_FUNC_INFO, "Invalid scene size");
196  viewportSize = newSize;
197  //We'll need FBOs of different size so get rid of the current FBOs.
198  destroyFBOs();
199  invariant();
200  }
201 
209  void setDefaultPainter(QPainter* const painter, const QGLContext* const context)
210  {
211  invariant();
212 
213 #ifndef NDEBUG
214  if(NULL != painter)
215  {
216  Q_ASSERT_X(painter->paintEngine()->type() == QPaintEngine::OpenGL ||
217  painter->paintEngine()->type() == QPaintEngine::OpenGL2,
218  Q_FUNC_INFO,
219  "QGL StelRenderer backends need a QGLWidget to be set as the "
220  "viewport on the graphics view");
221  QGLWidget* widget = dynamic_cast<QGLWidget*>(painter->device());
222  Q_ASSERT_X(NULL != widget && widget->context() == context, Q_FUNC_INFO,
223  "Painter used with QGL StelRenderer backends needs to paint on a QGLWidget "
224  "with the same GL context as the one used by the renderer");
225  }
226 #else
227  Q_UNUSED(context);
228 #endif
229 
230  defaultPainter = painter;
231  invariant();
232  }
233 
235  QImage screenshot() const
236  {
237  invariant();
238  return glWidget->grabFrameBuffer();
239  }
240 
242  QSize getViewportSize() const
243  {
244  invariant();
245  return viewportSize;
246  }
247 
250 
252  bool useFBO() const
253  {
254  // Can't call invariant here as invariant calls useFBO
255  return fboSupported && !fboDisabled;
256  }
257 
261  void setFont(const QFont& font)
262  {
263  Q_ASSERT_X(NULL != painter, Q_FUNC_INFO,
264  "Trying to set text (painting) font but painting is disabled");
265  painter->setFont(font);
266  }
267 
269  void startFrame()
270  {
271  invariant();
272  if (useFBO())
273  {
274  //Draw to backBuffer.
275  initFBOs();
276  backBuffer->bind();
277  backBufferPainter = new QPainter(backBuffer);
278  enablePainting(backBufferPainter);
279  }
280  else
281  {
282  enablePainting();
283  }
284  drawing = true;
285  invariant();
286  }
287 
292  void suspendFrame()
293  {
294  Q_ASSERT_X(useFBO(), Q_FUNC_INFO, "Partial rendering only works with FBOs");
295  finishFrame(false);
296  }
297 
299  void finishFrame(const bool swapBuffers = true)
300  {
301  invariant();
302  drawing = false;
303 
304  disablePainting();
305 
306  if (useFBO())
307  {
308  //Release the backbuffer.
309  delete backBufferPainter;
310  backBufferPainter = NULL;
311 
312  backBuffer->release();
313  //Swap buffers if finishing, don't swap yet if suspending.
314  if(swapBuffers){swapFBOs();}
315  }
316  invariant();
317  }
318 
321  {
322  invariant();
323  //Put the result of drawing to the FBO on the screen, applying an effect.
324  if (useFBO())
325  {
326  Q_ASSERT_X(!backBuffer->isBound() && !frontBuffer->isBound(), Q_FUNC_INFO,
327  "Framebuffer objects weren't released before drawing the result");
328  }
329  }
330 
333  {
334  invariant();
335 
336  if(usingGLWidgetPainter)
337  {
338  delete painter;
339  usingGLWidgetPainter = false;
340  }
341  painter = NULL;
342  invariant();
343  }
344 
347  {
348  invariant();
349  enablePainting(defaultPainter);
350  invariant();
351  }
352 
354  QPainter* getPainter()
355  {
356  return painter;
357  }
358 
359 private:
361  QSize viewportSize;
363  StelQGLWidget* glWidget;
364 
366  QPainter* painter;
368  QPainter* defaultPainter;
370  QPainter* backBufferPainter;
371 
373  QGLFramebufferObject* frontBuffer;
375  QGLFramebufferObject* backBuffer;
376 
378  bool drawing;
379 
381  bool usingGLWidgetPainter;
382 
384  bool fboSupported;
389  bool fboDisabled;
390 
392  bool nonPowerOfTwoTexturesSupported;
393 
395  void invariant() const
396  {
397 #ifndef NDEBUG
398  Q_ASSERT_X(NULL != glWidget, Q_FUNC_INFO, "Destroyed StelQGLViewport");
399  Q_ASSERT_X(glWidget->isValid(), Q_FUNC_INFO,
400  "Invalid glWidget (maybe there is no OpenGL support?)");
401 
402  const bool fbo = useFBO();
403  Q_ASSERT_X(NULL == backBufferPainter || fbo, Q_FUNC_INFO,
404  "We have a backbuffer painter even though we're not using FBO");
405  Q_ASSERT_X(drawing && fbo ? backBufferPainter != NULL : true, Q_FUNC_INFO,
406  "We're drawing and using FBOs, but the backBufferPainter is NULL");
407  Q_ASSERT_X(NULL == backBuffer || fbo, Q_FUNC_INFO,
408  "We have a backbuffer even though we're not using FBO");
409  Q_ASSERT_X(NULL == frontBuffer || fbo, Q_FUNC_INFO,
410  "We have a frontbuffer even though we're not using FBO");
411  Q_ASSERT_X(drawing && fbo ? backBuffer != NULL : true, Q_FUNC_INFO,
412  "We're drawing and using FBOs, but the backBuffer is NULL");
413  Q_ASSERT_X(drawing && fbo ? frontBuffer != NULL : true, Q_FUNC_INFO,
414  "We're drawing and using FBOs, but the frontBuffer is NULL");
415 #endif
416  }
417 
419  void enablePainting(QPainter* painter)
420  {
421  invariant();
422 
423  // If no painter specified, create a default one painting to the glWidget.
424  if(painter == NULL)
425  {
426  this->painter = new QPainter(glWidget);
427  usingGLWidgetPainter = true;
428  return;
429  }
430  this->painter = painter;
431  invariant();
432  }
433 
435  void initFBOs()
436  {
437  invariant();
438  Q_ASSERT_X(useFBO(), Q_FUNC_INFO, "We're not using FBO");
439  if (NULL == backBuffer)
440  {
441  Q_ASSERT_X(NULL == frontBuffer, Q_FUNC_INFO,
442  "frontBuffer is not null even though backBuffer is");
443 
444  // If non-power-of-two textures are supported,
445  // FBOs must have power of two size large enough to fit the viewport.
446  const QSize bufferSize = nonPowerOfTwoTexturesSupported
448  : viewportSize;
449 
450  // We want a depth and stencil buffer attached to our FBO if possible.
451  const QGLFramebufferObject::Attachment attachment =
452  QGLFramebufferObject::CombinedDepthStencil;
453  backBuffer = new QGLFramebufferObject(bufferSize, attachment);
454  frontBuffer = new QGLFramebufferObject(bufferSize, attachment);
455 
456  Q_ASSERT_X(backBuffer->isValid() && frontBuffer->isValid(),
457  Q_FUNC_INFO, "Framebuffer objects failed to initialize");
458  }
459  invariant();
460  }
461 
463  void swapFBOs()
464  {
465  invariant();
466  Q_ASSERT_X(useFBO(), Q_FUNC_INFO, "We're not using FBO");
467  QGLFramebufferObject* tmp = backBuffer;
468  backBuffer = frontBuffer;
469  frontBuffer = tmp;
470  invariant();
471  }
472 
474  void destroyFBOs()
475  {
476  invariant();
477  // Destroy framebuffers
478  if(NULL != frontBuffer)
479  {
480  delete frontBuffer;
481  frontBuffer = NULL;
482  }
483  if(NULL != backBuffer)
484  {
485  delete backBuffer;
486  backBuffer = NULL;
487  }
488  invariant();
489  }
490 };
491 
492 #endif // _STELQGLVIEWPORT_HPP_