1 /*
2 * Stellarium
3 * Copyright (C) 2016 Florian Schaukowitsch
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 #include "StelOpenGLArray.hpp"
21 #include "StelOBJ.hpp"
22
23 #include <QElapsedTimer>
24 #include <QOpenGLFunctions>
25
26 Q_LOGGING_CATEGORY(stelOpenGLArray,"stel.OpenGLArray")
27
28 QOpenGLFunctions* StelOpenGLArray::gl = Q_NULLPTR;
29 bool StelOpenGLArray::s_vaosSupported = false;
30 bool StelOpenGLArray::s_intElementBuffersSupported = false;
31
32 //static function
initGL()33 void StelOpenGLArray::initGL()
34 {
35 QOpenGLContext* ctx = QOpenGLContext::currentContext();
36 gl = ctx->functions();
37
38 //disable VAOs on Intel because of serious bugs in their implemenation...
39 QString vendor(reinterpret_cast<const char*>(gl->glGetString(GL_VENDOR)));
40 if(vendor.contains("Intel",Qt::CaseInsensitive))
41 {
42 s_vaosSupported = false;
43 qCDebug(stelOpenGLArray)<<"Disabling VAO usage because of Intel driver bugs";
44 }
45 else
46 {
47 //find out if VAOs are supported, simplest way is just trying to create one
48 //Qt has the necessary checks inside the create method
49 QOpenGLVertexArrayObject testObj;
50 s_vaosSupported = testObj.create();
51 testObj.destroy();
52 }
53
54 if( s_vaosSupported )
55 {
56 qCDebug(stelOpenGLArray)<<"Vertex Array Objects are supported";
57 }
58 else
59 {
60 qCDebug(stelOpenGLArray)<<"Vertex Array Objects are not supported on your hardware (this is not an error)";
61 }
62
63 //check if we can enable int index buffers
64 if(ctx->isOpenGLES())
65 {
66 //query for extension
67 if(ctx->hasExtension("GL_OES_element_index_uint"))
68 {
69 s_intElementBuffersSupported = true;
70 }
71 }
72 else
73 {
74 //we are on Desktop, so int is always supported
75 s_intElementBuffersSupported = true;
76 }
77
78 if(!s_intElementBuffersSupported)
79 {
80 qCWarning(stelOpenGLArray)<<"Your hardware does not support integer indices. Large models may not load.";
81 }
82 }
83
StelOpenGLArray()84 StelOpenGLArray::StelOpenGLArray()
85 : m_vertexBuffer(QOpenGLBuffer::VertexBuffer),
86 m_indexBuffer(QOpenGLBuffer::IndexBuffer),
87 m_indexBufferType(GL_UNSIGNED_INT),
88 m_indexBufferTypeSize(sizeof(GLuint)),
89 m_indexCount(0),
90 m_memoryUsage(0),
91 m_offsets(),
92 m_sizes(),
93 m_types(),
94 m_strides()
95 {
96 }
97
~StelOpenGLArray()98 StelOpenGLArray::~StelOpenGLArray()
99 {
100 //release is done by the Qt class destructors automatically
101 }
102
clear()103 void StelOpenGLArray::clear()
104 {
105 //QOpenGLBuffer has no good copy constructor/assignment operator
106 //so we can't just do *this = StelOpenGLArray() here
107 if(m_vao.isCreated())
108 m_vao.destroy();
109 m_vertexBuffer.destroy();
110 m_indexBuffer.destroy();
111
112 m_indexBufferType = GL_UNSIGNED_INT;
113 m_indexBufferTypeSize = sizeof(GLuint);
114 m_indexCount = 0;
115 m_memoryUsage = 0;
116
117 for (int i =0;i<ATTLOC_SIZE;++i)
118 {
119 m_offsets[i] = 0;
120 m_sizes[i] = 0;
121 m_types[i] = 0;
122 m_strides[i] = 0;
123 }
124 }
125
load(const StelOBJ * obj,bool useTangents)126 bool StelOpenGLArray::load(const StelOBJ* obj, bool useTangents)
127 {
128 clear();
129
130 QElapsedTimer timer;
131 timer.start();
132
133 if(s_vaosSupported)
134 {
135 //create vao, also already bind it to be core-profile compatible
136 if(!m_vao.create())
137 {
138 qCCritical(stelOpenGLArray)<<"Could not create OpenGL vertex array object!";
139 clear();
140 return false;
141 }
142 m_vao.bind();
143 }
144
145 if(! (m_vertexBuffer.create() && m_indexBuffer.create()) )
146 {
147 qCCritical(stelOpenGLArray)<<"Could not create OpenGL buffers!";
148 clear();
149 return false;
150 }
151
152 if(m_vertexBuffer.bind())
153 {
154 //! The data is used for static drawing
155 m_vertexBuffer.setUsagePattern(QOpenGLBuffer::StaticDraw);
156 //perform data upload
157 const StelOBJ::VertexList& vertices = obj->getVertexList();
158 if(useTangents)
159 {
160 //we can simply upload the whole vertex data in a single call
161 m_vertexBuffer.allocate(vertices.constData(), static_cast<int>(sizeof(StelOBJ::Vertex)) * vertices.size());
162 m_memoryUsage+=sizeof(StelOBJ::Vertex) * static_cast<size_t>(vertices.size());
163
164 //setup vtx format
165 for (int i = 0;i<=ATTLOC_BITANGENT;++i)
166 {
167 //all strides are the same, and only float is used
168 m_strides[i] = sizeof(StelOBJ::Vertex);
169 m_types[i] = GL_FLOAT;
170 }
171 m_offsets[ATTLOC_VERTEX] = offsetof(StelOBJ::Vertex,position);
172 m_sizes[ATTLOC_VERTEX] = 3;
173 m_offsets[ATTLOC_NORMAL] = offsetof(StelOBJ::Vertex,normal);
174 m_sizes[ATTLOC_NORMAL] = 3;
175 m_offsets[ATTLOC_TEXCOORD] = offsetof(StelOBJ::Vertex,texCoord);
176 m_sizes[ATTLOC_TEXCOORD] = 2;
177 m_offsets[ATTLOC_TANGENT] = offsetof(StelOBJ::Vertex,tangent);
178 m_sizes[ATTLOC_TANGENT] = 4;
179 m_offsets[ATTLOC_BITANGENT] = offsetof(StelOBJ::Vertex,bitangent);
180 m_sizes[ATTLOC_BITANGENT] = 3;
181 }
182 else
183 {
184 //we have to convert to a simpler vertex format
185 //we use the first 8 floats of each vertex
186 const int vtxSize = sizeof(GLfloat) * 8;
187 //alloc data but dont upload
188 m_vertexBuffer.allocate(vertices.size() * vtxSize);
189 m_memoryUsage+=static_cast<size_t>(vertices.size()) * vtxSize;
190 for(int i =0;i<vertices.size();++i)
191 {
192 //copy the first 8 floats from each vertex
193 m_vertexBuffer.write(i * vtxSize, &vertices.at(i), vtxSize);
194 }
195 for (int i = 0;i<=ATTLOC_NORMAL;++i)
196 {
197 //all strides are the same, and only float is used
198 m_strides[i] = vtxSize;
199 m_types[i] = GL_FLOAT;
200 }
201 m_offsets[ATTLOC_VERTEX] = offsetof(StelOBJ::Vertex,position);
202 m_sizes[ATTLOC_VERTEX] = 3;
203 m_offsets[ATTLOC_NORMAL] = offsetof(StelOBJ::Vertex,normal);
204 m_sizes[ATTLOC_NORMAL] = 3;
205 m_offsets[ATTLOC_TEXCOORD] = offsetof(StelOBJ::Vertex,texCoord);
206 m_sizes[ATTLOC_TEXCOORD] = 2;
207 }
208 //dont forget to release
209 m_vertexBuffer.release();
210 }
211 else
212 {
213 qCCritical(stelOpenGLArray)<<"Could not bind vertex buffer";
214 clear();
215 return false;
216 }
217
218 if(m_indexBuffer.bind())
219 {
220 m_indexBuffer.setUsagePattern(QOpenGLBuffer::StaticDraw);
221 //determine the index type to use
222 if(obj->canUseShortIndices())
223 {
224 m_indexBufferType = GL_UNSIGNED_SHORT;
225 m_indexBufferTypeSize = sizeof(GLushort);
226 const StelOBJ::ShortIndexList idxList = obj->getShortIndexList();
227 m_indexBuffer.allocate(idxList.constData(),static_cast<int>(m_indexBufferTypeSize) * idxList.size());
228 }
229 else
230 {
231 if(!s_intElementBuffersSupported)
232 {
233 qCCritical(stelOpenGLArray)<<"Can not use int element indices on this hardware, but StelOBJ requires it!";
234 m_indexBuffer.release();
235 clear();
236 return false;
237 }
238
239 m_indexBufferType = GL_UNSIGNED_INT;
240 m_indexBufferTypeSize = sizeof(GLuint);
241
242 const StelOBJ::IndexList& idxList = obj->getIndexList();
243 m_indexBuffer.allocate(idxList.constData(),static_cast<int>(m_indexBufferTypeSize) * idxList.size());
244 }
245 m_indexCount = obj->getIndexList().size();
246 m_memoryUsage+= static_cast<size_t>(m_indexCount) * m_indexBufferTypeSize;
247 m_indexBuffer.release();
248 }
249 else
250 {
251 qCCritical(stelOpenGLArray)<<"Could not bind index buffer";
252 clear();
253 return false;
254 }
255
256 if(m_vao.isCreated())
257 {
258 //let the VAO remember the bindBuffers state
259 m_vao.bind();
260 bindBuffers();
261 m_vao.release();
262 releaseBuffers();
263 }
264
265 qCDebug(stelOpenGLArray)<<"Loaded StelOBJ data into OpenGL in"<<timer.elapsed()<<"ms ("<<(m_memoryUsage / 1024.0f)<<"kb GL memory)";
266 return true;
267 }
268
bind()269 void StelOpenGLArray::bind()
270 {
271 if(m_vao.isCreated())
272 m_vao.bind(); //all we have to do
273 else
274 bindBuffers(); //we have to bind manually
275 }
276
release()277 void StelOpenGLArray::release()
278 {
279 if(m_vao.isCreated())
280 m_vao.release(); //all we have to do
281 else
282 releaseBuffers(); //we have to release manually
283 }
284
bindBuffers()285 void StelOpenGLArray::bindBuffers()
286 {
287 m_vertexBuffer.bind();
288
289 // using Qt wrapper classes here is not possible because the only implementation is in QOpenGLShaderProgram, which requires a shader program instance
290 // this is a bit incorrect, because the following is global state that does not depend on a shader
291 // (but may be stored in a VAO to enable faster binding/unbinding)
292
293 for(GLuint i = 0; i<ATTLOC_SIZE;++i)
294 {
295 if(m_sizes[i]>0) //zero size disables the location
296 {
297 gl->glEnableVertexAttribArray(i);
298 gl->glVertexAttribPointer(i, m_sizes[i], m_types[i], GL_FALSE, m_strides[i], reinterpret_cast<const void *>(m_offsets[i]) );
299 }
300 }
301
302 //vertex buffer does not need to remain bound, because the binding is stored by glVertexAttribPointer
303 m_vertexBuffer.release();
304
305 //index buffer must remain bound
306 m_indexBuffer.bind();
307 }
308
releaseBuffers()309 void StelOpenGLArray::releaseBuffers()
310 {
311 m_indexBuffer.release();
312
313 for(GLuint i = 0; i<ATTLOC_SIZE;++i)
314 {
315 if(m_sizes[i]>0) //zero size disables the location
316 {
317 gl->glDisableVertexAttribArray(i);
318 }
319 }
320 }
321