Stellarium 0.12.4
StelQGL2Renderer.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 _STELQGL2RENDERER_HPP_
21 #define _STELQGL2RENDERER_HPP_
22 
23 
24 #include <QGLShader>
25 #include <QGLShaderProgram>
26 #include <QVector>
27 
28 #include "StelApp.hpp"
29 #include "StelCore.hpp"
30 #include "StelQGLGLSLShader.hpp"
31 #include "StelQGLRenderer.hpp"
32 #include "StelProjector.hpp"
33 #include "StelProjectorClasses.hpp"
34 #include "StelQGL2InterleavedArrayVertexBufferBackend.hpp"
35 
36 
41 {
42 public:
48  StelQGL2Renderer(QGraphicsView* parent, bool pvrSupported)
49  : StelQGLRenderer(parent, pvrSupported)
50  , initialized(false)
51  , floatTexturesDisabled(true)
52  , builtinShaders()
53  , customShader(NULL)
54  {
55  }
56 
57  virtual ~StelQGL2Renderer()
58  {
59  // Can't check the invariant here, as the renderer will be destroyed even
60  // if it's initialization has failed.
61 
62  foreach(StelQGLGLSLShader *shader, builtinShaders)
63  {
64  delete shader;
65  }
66 
67  initialized = false;
68  }
69 
70  virtual bool init()
71  {
72  Q_ASSERT_X(!initialized, Q_FUNC_INFO,
73  "StelQGL2Renderer is already initialized");
74 
75  // Using this instead of makeGLContextCurrent() to avoid invariant
76  // as we're not in valid state (getGLContext() isn't public - it doesn't call invariant)
77  getGLContext()->makeCurrent();
78 
79  // GL2 drawing doesn't work with GL1 paint engine .
80  //
81  // Not checking for GL2 as there could also be e.g. X11, or GL3 in future, etc;
82  // where GL2 might work fine.
83  if(qtPaintEngineType() == QPaintEngine::OpenGL)
84  {
85  qWarning() << "StelQGL2Renderer::init : Failed because Qt paint engine is not OpenGL2 \n"
86  "If paint engine is OpenGL3 or higher, this code needs to be updated";
87  return false;
88  }
89 
90  if(!QGLFormat::openGLVersionFlags().testFlag(QGLFormat::OpenGL_Version_2_1) &&
91  !QGLFormat::openGLVersionFlags().testFlag(QGLFormat::OpenGL_ES_Version_2_0))
92  {
93  qWarning() << "StelQGL2Renderer::init : StelQGL2Renderer needs OpenGL 2.1 or OpenGL ES 2.0";
94  return false;
95  }
96 
97  // Check for features required for the GL2 backend.
98  if(!gl.hasOpenGLFeature(QGLFunctions::Shaders))
99  {
100  qWarning() << "StelQGL2Renderer::init : Required feature not supported: shaders";
101  return false;
102  }
103  if(!gl.hasOpenGLFeature(QGLFunctions::Buffers))
104  {
105  qWarning() << "StelQGL2Renderer::init : Required feature not supported: VBOs/IBOs";
106  return false;
107  }
108  // We don't want to check for multitexturing everywhere.
109  // Also, GL2 requires multitexturing so this shouldn't even happen.
110  // (GL1 works fine without multitexturing)
111  if(!gl.hasOpenGLFeature(QGLFunctions::Multitexture))
112  {
113  qWarning() << "StelQGL2Renderer::init : Required feature not supported: Multitexturing";
114  return false;
115  }
116 
117  if(!StelQGLRenderer::init())
118  {
119  qWarning() << "StelQGL2Renderer::init : parent init failed";
120  return false;
121  }
122 
123  // Each shader here handles a specific combination of vertex attribute
124  // interpretations. E.g. vertex-color-texcoord .
125 
126  // Notes about uniform/attribute names.
127  //
128  // Projection matrix is always specified (and must be declared in the shader),
129  // as "uniform mat4 projectionMatrix"
130  //
131  // For shaders that have no vertex color attribute,
132  // "uniform vec4 globalColor" must be declared.
133  //
134  // Furthermore, each vertex attribute interpretation has its name that
135  // must be used. These are specified in attributeGLSLName function in
136  // StelGLUtilityFunctions.cpp . These are:
137  //
138  // - "vertex" for vertex position
139  // - "texCoord" for texture coordinate
140  // - "normal" for vertex normal
141  // - "color" for vertex color
142  //
143  // GLSLShader can also require unprojected position attribute,
144  // "unprojectedVertex", but this is only specified if the shader requires it.
145  //
146 
147  // All vertices have the same color.
148  plainShader = loadBuiltinShader
149  (
150  "plainShader"
151  ,
152  "vec4 project(in vec4 v);\n"
153  "attribute mediump vec4 vertex;\n"
154  "uniform mediump mat4 projectionMatrix;\n"
155  "void main(void)\n"
156  "{\n"
157  " gl_Position = projectionMatrix * project(vertex);\n"
158  "}\n"
159  ,
160  "uniform mediump vec4 globalColor;\n"
161  "void main(void)\n"
162  "{\n"
163  " gl_FragColor = globalColor;\n"
164  "}\n"
165  );
166  if(NULL == plainShader)
167  {
168  qWarning() << "StelQGL2Renderer::init failed to load plain shader";
169  return false;
170  }
171  builtinShaders.append(plainShader);
172 
173  // Vertices with per-vertex color.
174  colorShader = loadBuiltinShader
175  (
176  "colorShader"
177  ,
178  "vec4 project(in vec4 v);\n"
179  "attribute highp vec4 vertex;\n"
180  "attribute mediump vec4 color;\n"
181  "uniform mediump mat4 projectionMatrix;\n"
182  "varying mediump vec4 outColor;\n"
183  "void main(void)\n"
184  "{\n"
185  " outColor = color;\n"
186  " gl_Position = projectionMatrix * project(vertex);\n"
187  "}\n"
188  ,
189  "varying mediump vec4 outColor;\n"
190  "void main(void)\n"
191  "{\n"
192  " gl_FragColor = outColor;\n"
193  "}\n"
194  );
195  if(NULL == colorShader)
196  {
197  qWarning() << "StelQGL2Renderer::init failed to load color shader program";
198  return false;
199  }
200  builtinShaders.append(colorShader);
201 
202  // Textured mesh.
203  textureShader = loadBuiltinShader
204  (
205  "textureShader"
206  ,
207  "vec4 project(in vec4 v);\n"
208  "attribute highp vec4 vertex;\n"
209  "attribute mediump vec2 texCoord;\n"
210  "uniform mediump mat4 projectionMatrix;\n"
211  "varying mediump vec2 texc;\n"
212  "void main(void)\n"
213  "{\n"
214  " gl_Position = projectionMatrix * project(vertex);\n"
215  " texc = texCoord;\n"
216  "}\n"
217  ,
218  "varying mediump vec2 texc;\n"
219  "uniform sampler2D tex;\n"
220  "uniform mediump vec4 globalColor;\n"
221  "void main(void)\n"
222  "{\n"
223  " gl_FragColor = texture2D(tex, texc) * globalColor;\n"
224  "}\n"
225  );
226  if(NULL == textureShader)
227  {
228  qWarning() << "StelQGL2Renderer::init failed to load texture shader";
229  return false;
230  }
231  builtinShaders.append(textureShader);
232 
233  // Textured mesh interpolated with per-vertex color.
234  colorTextureShader = loadBuiltinShader
235  (
236  "colorTextureShader"
237  ,
238  "vec4 project(in vec4 v);\n"
239  "attribute highp vec4 vertex;\n"
240  "attribute mediump vec2 texCoord;\n"
241  "attribute mediump vec4 color;\n"
242  "uniform mediump mat4 projectionMatrix;\n"
243  "varying mediump vec2 texc;\n"
244  "varying mediump vec4 outColor;\n"
245  "void main(void)\n"
246  "{\n"
247  " gl_Position = projectionMatrix * project(vertex);\n"
248  " texc = texCoord;\n"
249  " outColor = color;\n"
250  "}\n"
251  ,
252  "varying mediump vec2 texc;\n"
253  "varying mediump vec4 outColor;\n"
254  "uniform sampler2D tex;\n"
255  "void main(void)\n"
256  "{\n"
257  " gl_FragColor = texture2D(tex, texc) * outColor;\n"
258  "}\n"
259  );
260  if(NULL == colorTextureShader)
261  {
262  qWarning() << "StelQGL2Renderer::init failed to load colorTexture shader";
263  return false;
264  }
265  builtinShaders.append(colorTextureShader);
266 
267  // Float texture support blacklist
268  // (mainly open source drivers, which don't support them due to patents on
269  // float textures)
270  floatTexturesDisabled =
271  glVendorString == "nouveau" || // Open source NVidia drivers
272  glVendorString == "DRI R300 Project" || // Open source ATI R300 (Radeon 9000, X000 and X1000)
273  glVendorString == "Intel" || // Intel drivers
274  glVendorString == "Mesa Project" || // Mesa software rasterizer
275  glVendorString == "Microsoft Corporation" || // Microsoft builtin GL (this doesn't even support GL2, though)
276  glVendorString == "Tungsten Graphics, Inc" || // Some Gallium3D drivers
277  glVendorString == "X.Org R300 Project" || // More open source ATI R300
278  glVendorString == "X.Org" || // Gallium3D on AMD GPUs
279  glVendorString == "VMware, Inc." // More Gallium3D, mainly llvmpipe, softpipe
280  ;
281 
282  initialized = true;
283  invariant();
284  return true;
285  }
286 
287  virtual bool areFloatTexturesSupported() const
288  {
289  return QGLFormat::openGLVersionFlags().testFlag(QGLFormat::OpenGL_Version_3_0)
290  && !floatTexturesDisabled;
291  }
292 
293  virtual bool areNonPowerOfTwoTexturesSupported() const {return true;}
294 
296  {
297  return new StelQGLGLSLShader(this, false);
298  }
299 
300  virtual bool isGLSLSupported() const {return true;}
301 
310  StelQGLGLSLShader* getShader(const StelVertexAttribute* const attributes, const int attributeCount)
311  {
312  invariant();
313 
314  // A custom shader (StelGLSLShader - in this case StelQGLGLSLShader)
315  // overrides builtin shaders.
316  if(NULL != customShader) {return customShader;}
317 
318  // Determine which vertex attributes are used.
319  bool position, texCoord, normal, color;
320  position = texCoord = normal = color = false;
321  for(int attrib = 0; attrib < attributeCount; ++attrib)
322  {
323  const StelVertexAttribute& attribute(attributes[attrib]);
324  switch(attribute.interpretation)
325  {
326  case AttributeInterpretation_Position: position = true; break;
327  case AttributeInterpretation_TexCoord: texCoord = true; break;
328  case AttributeInterpretation_Normal: normal = true; break;
329  case AttributeInterpretation_Color: color = true; break;
330  default:
331  Q_ASSERT_X(false, Q_FUNC_INFO, "Unknown vertex interpretation");
332  }
333  }
334 
335  Q_ASSERT_X(position, Q_FUNC_INFO,
336  "Vertex formats without vertex position are not supported");
337 
338  invariant();
339  // There are possible combinations - 4 are implemented right now.
340  if(!texCoord && !normal && !color) {return plainShader;}
341  if(!texCoord && !normal && color) {return colorShader;}
342  if(texCoord && !normal && !color) {return textureShader;}
343  if(texCoord && !normal && color) {return colorTextureShader;}
344 
345  // If we reach here, the vertex format has no shader implemented so we
346  // least inform the user about what vertex format fails (so it can
347  // be changed or a shader can be implemented for it)
348  qDebug() << "position: " << position << " texCoord: " << texCoord
349  << " normal: " << normal << " color: " << color;
350 
351  Q_ASSERT_X(false, Q_FUNC_INFO,
352  "Shader for vertex format not (yet) implemented");
353 
354  invariant();
355  // Prevents GCC from complaining about exiting a non-void function:
356  return NULL;
357  }
358 
368  {
369  Q_ASSERT_X(NULL == customShader, Q_FUNC_INFO,
370  "Trying to bind() a shader without releasing the previous shader.");
371 
372  customShader = shader;
373  }
374 
383  {
384 #ifdef NDEBUG
385  Q_UNUSED(shader);
386 #endif
387  Q_ASSERT_X(shader == customShader, Q_FUNC_INFO,
388  "Trying to release() a shader when no shader or a different shader is bound.");
389 
390  customShader = NULL;
391  }
392 
393 protected:
395  (const PrimitiveType primitiveType, const QVector<StelVertexAttribute>& attributes)
396  {
397  invariant();
398  statistics[VERTEX_BUFFERS_CREATED] += 1.0;
399  return new StelQGL2InterleavedArrayVertexBufferBackend(primitiveType, attributes);
400  }
401 
403  StelIndexBuffer* indexBuffer = NULL,
404  StelProjector* projector = NULL,
405  bool dontProject = false)
406  {
407  invariant();
408 
410  dynamic_cast<StelQGL2InterleavedArrayVertexBufferBackend*>(vertexBuffer);
411  Q_ASSERT_X(backend != NULL, Q_FUNC_INFO,
412  "StelQGL2Renderer: Vertex buffer created by different renderer backend "
413  "or uninitialized");
414 
415  StelQGLIndexBuffer* glIndexBuffer = NULL;
416  if(indexBuffer != NULL)
417  {
418  glIndexBuffer = dynamic_cast<StelQGLIndexBuffer*>(indexBuffer);
419  Q_ASSERT_X(glIndexBuffer != NULL, Q_FUNC_INFO,
420  "StelQGL2Renderer: Index buffer created by different renderer "
421  "backend or uninitialized");
422  if(indexBuffer->length() == 0)
423  {
424  statistics[EMPTY_BATCHES] += 1.0;
425  return;
426  }
427  }
428  else if(backend->length() == 0)
429  {
430  statistics[EMPTY_BATCHES] += 1.0;
431  return;
432  }
433 
434  // We don't own this - we just have a pointer to it.
435  StelProjector::GLSLProjector* glslProjector = NULL;
436  StelQGLGLSLShader* shader =
437  getShader(backend->attributes.attributes, backend->attributes.count);
438 
439  // Internal shader can't be bound by the user so we bind/release it here.
440  if(NULL == customShader) {shader->bind();}
441 
442  // We need a shared pointer when we're getting the projector ourselves (the 2D case),
443  // to prevent destructor of returned shared pointer from destroying it
444  // before we can use it.
445  StelProjectorP projector2DDummy =
446  (NULL != projector ? StelProjectorP(NULL)
447  : StelApp::getInstance().getCore()->getProjection2d());
448 
449  projector = (NULL != projector ? projector : &(*(projector2DDummy)));
450  // XXX: we should use a more generic way to test whether or not to do the projection.
451  if(!dontProject && (NULL == dynamic_cast<StelProjector2d*>(projector)))
452  {
453  // Try to do projection in GLSL, use projectVertices() as fallback.
454  glslProjector = projector->getGLSLProjector();
455  if(NULL == glslProjector || !glslProjector->init(shader))
456  {
457  backend->projectVertices(projector, glIndexBuffer);
458  glslProjector = NULL;
459  statistics[BATCH_PROJECTIONS_CPU_TOTAL] += 1.0;
460  statistics[BATCH_PROJECTIONS_CPU] += 1.0;
461  }
462  else
463  {
464  statistics[BATCH_PROJECTIONS_GPU_TOTAL] += 1.0;
465  statistics[BATCH_PROJECTIONS_GPU] += 1.0;
466  }
467  }
468  else
469  {
470  statistics[BATCH_PROJECTIONS_NONE_TOTAL] += 1.0;
471  statistics[BATCH_PROJECTIONS_NONE] += 1.0;
472  }
473 
474  if(!shader->getProgram().bind())
475  {
476  Q_ASSERT_X(false, Q_FUNC_INFO, "Failed to bind shader program");
477  }
478  if(NULL != glslProjector)
479  {
480  shader->pushUniformStorage();
481  glslProjector->preDraw(shader);
482  }
483 
484  // Instead of setting GL state when functions such as setDepthTest() or setCulledFaces()
485  // are called, we only set it before drawing and reset after drawing to avoid
486  setupGLState(projector);
487 
488  // Need to transpose the matrix for QGL.
489  const Mat4f& projectionMatrix = projector->getProjectionMatrix();
490 
491  // We might relink the shaders here, so we can only upload uniforms after the relinking.
492  shader->uploadUniforms();
493 
494  // Set up viewport for the projector.
495  const Vec4i viewXywh = projector->getViewportXywh();
496  glViewport(viewXywh[0], viewXywh[1], viewXywh[2], viewXywh[3]);
497 
498  updateDrawStatistics(backend, glIndexBuffer);
499  backend->draw(*this, projectionMatrix, glIndexBuffer, shader);
500 
501  // Restore default state to avoid interfering with Qt OpenGL drawing.
502  restoreGLState(projector);
503 
504  shader->getProgram().release();
505 
506  if(NULL != glslProjector)
507  {
508  shader->popUniformStorage();
509  glslProjector->postDraw(shader);
510  }
511 
512  if(NULL == customShader) {shader->release();}
513 
514  invariant();
515  }
516 
518  {
519  // Called at initialization, so can't call invariant
520  int textureUnitCount;
521  // GL1 version should use GL_MAX_TEXTURE_UNITS instead.
522  glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &textureUnitCount);
523  return std::max(textureUnitCount, STELQGLRENDERER_MAX_TEXTURE_UNITS);
524  }
525 
526 #ifndef NDEBUG
527  virtual void invariant() const
528  {
529  Q_ASSERT_X(initialized, Q_FUNC_INFO, "uninitialized StelQGL2Renderer");
531  }
532 #endif
533 
534 private:
536  bool initialized;
537 
539  bool floatTexturesDisabled;
540 
544  QVector<StelQGLGLSLShader *> builtinShaders;
545 
547  StelQGLGLSLShader* plainShader;
549  StelQGLGLSLShader* colorShader;
551  StelQGLGLSLShader* textureShader;
553  StelQGLGLSLShader* colorTextureShader;
554 
558  StelQGLGLSLShader* customShader;
559 
560  //Note:
561  //We don't keep handles to shader variable locations.
562  //Instead, we access them when used through QGLShaderProgram.
563  //That should only change if profiling shows that it wastes
564  //too much time (which is unlikely)
565 
566 
574  StelQGLGLSLShader *loadBuiltinShader(const char* const name,
575  const char* const vSrc, const char* const fSrc)
576  {
577  // No invariants, as this is called from init - before the Renderer is
578  // in fully valid state.
579  StelQGLGLSLShader* result =
580  dynamic_cast<StelQGLGLSLShader*>(new StelQGLGLSLShader(this, true));
581 
582  // Compile and add vertex shader.
583  if(!result->addVertexShader(QString(vSrc)))
584  {
585  qWarning() << "Failed to compile vertex shader of builtin shader \""
586  << name << "\" : " << result->log();
587  delete result;
588  return NULL;
589  }
590  // Compile and add fragment shader.
591  if(!result->addFragmentShader(QString(fSrc)))
592  {
593  qWarning() << "Failed to compile fragment shader of builtin shader \""
594  << name << "\" : " << result->log();
595  delete result;
596  return NULL;
597  }
598  // Link the shader program.
599  if(!result->build())
600  {
601  qWarning() << "Failed to link builtin shader \""
602  << name << "\" : " << result->log();
603  delete result;
604  return NULL;
605  }
606  if(!result->log().isEmpty())
607  {
608  qWarning() << "Log of the compilation of builtin shader \"" << name
609  << "\" :" << result->log();
610  }
611 
612  return result;
613  }
614 };
615 
616 #endif // _STELQGL2RENDERER_HPP_