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