1 /*
2 src/glutil.cpp -- Convenience classes for accessing OpenGL >= 3.x
3
4 NanoGUI was developed by Wenzel Jakob <wenzel.jakob@epfl.ch>.
5 The widget drawing code is based on the NanoVG demo application
6 by Mikko Mononen.
7
8 All rights reserved. Use of this source code is governed by a
9 BSD-style license that can be found in the LICENSE.txt file.
10 */
11
12 #include <nanogui/glutil.h>
13 #include <iostream>
14 #include <fstream>
15
NAMESPACE_BEGIN(nanogui)16 NAMESPACE_BEGIN(nanogui)
17
18 static GLuint createShader_helper(GLint type, const std::string &name,
19 const std::string &defines,
20 std::string shader_string) {
21 if (shader_string.empty())
22 return (GLuint) 0;
23
24 if (!defines.empty()) {
25 if (shader_string.length() > 8 && shader_string.substr(0, 8) == "#version") {
26 std::istringstream iss(shader_string);
27 std::ostringstream oss;
28 std::string line;
29 std::getline(iss, line);
30 oss << line << std::endl;
31 oss << defines;
32 while (std::getline(iss, line))
33 oss << line << std::endl;
34 shader_string = oss.str();
35 } else {
36 shader_string = defines + shader_string;
37 }
38 }
39
40 GLuint id = glCreateShader(type);
41 const char *shader_string_const = shader_string.c_str();
42 glShaderSource(id, 1, &shader_string_const, nullptr);
43 glCompileShader(id);
44
45 GLint status;
46 glGetShaderiv(id, GL_COMPILE_STATUS, &status);
47
48 if (status != GL_TRUE) {
49 char buffer[512];
50 std::cerr << "Error while compiling ";
51 if (type == GL_VERTEX_SHADER)
52 std::cerr << "vertex shader";
53 else if (type == GL_FRAGMENT_SHADER)
54 std::cerr << "fragment shader";
55 else if (type == GL_GEOMETRY_SHADER)
56 std::cerr << "geometry shader";
57 std::cerr << " \"" << name << "\":" << std::endl;
58 std::cerr << shader_string << std::endl << std::endl;
59 glGetShaderInfoLog(id, 512, nullptr, buffer);
60 std::cerr << "Error: " << std::endl << buffer << std::endl;
61 throw std::runtime_error("Shader compilation failed!");
62 }
63
64 return id;
65 }
66
initFromFiles(const std::string & name,const std::string & vertex_fname,const std::string & fragment_fname,const std::string & geometry_fname)67 bool GLShader::initFromFiles(
68 const std::string &name,
69 const std::string &vertex_fname,
70 const std::string &fragment_fname,
71 const std::string &geometry_fname) {
72 auto file_to_string = [](const std::string &filename) -> std::string {
73 if (filename.empty())
74 return "";
75 std::ifstream t(filename);
76 return std::string((std::istreambuf_iterator<char>(t)),
77 std::istreambuf_iterator<char>());
78 };
79
80 return init(name,
81 file_to_string(vertex_fname),
82 file_to_string(fragment_fname),
83 file_to_string(geometry_fname));
84 }
85
init(const std::string & name,const std::string & vertex_str,const std::string & fragment_str,const std::string & geometry_str)86 bool GLShader::init(const std::string &name,
87 const std::string &vertex_str,
88 const std::string &fragment_str,
89 const std::string &geometry_str) {
90 std::string defines;
91 for (auto def : mDefinitions)
92 defines += std::string("#define ") + def.first + std::string(" ") + def.second + "\n";
93
94 glGenVertexArrays(1, &mVertexArrayObject);
95 mName = name;
96 mVertexShader =
97 createShader_helper(GL_VERTEX_SHADER, name, defines, vertex_str);
98 mGeometryShader =
99 createShader_helper(GL_GEOMETRY_SHADER, name, defines, geometry_str);
100 mFragmentShader =
101 createShader_helper(GL_FRAGMENT_SHADER, name, defines, fragment_str);
102
103 if (!mVertexShader || !mFragmentShader)
104 return false;
105 if (!geometry_str.empty() && !mGeometryShader)
106 return false;
107
108 mProgramShader = glCreateProgram();
109
110 glAttachShader(mProgramShader, mVertexShader);
111 glAttachShader(mProgramShader, mFragmentShader);
112
113 if (mGeometryShader)
114 glAttachShader(mProgramShader, mGeometryShader);
115
116 glLinkProgram(mProgramShader);
117
118 GLint status;
119 glGetProgramiv(mProgramShader, GL_LINK_STATUS, &status);
120
121 if (status != GL_TRUE) {
122 char buffer[512];
123 glGetProgramInfoLog(mProgramShader, 512, nullptr, buffer);
124 std::cerr << "Linker error (" << mName << "): " << std::endl << buffer << std::endl;
125 mProgramShader = 0;
126 throw std::runtime_error("Shader linking failed!");
127 }
128
129 return true;
130 }
131
bind()132 void GLShader::bind() {
133 glUseProgram(mProgramShader);
134 glBindVertexArray(mVertexArrayObject);
135 }
136
attrib(const std::string & name,bool warn) const137 GLint GLShader::attrib(const std::string &name, bool warn) const {
138 GLint id = glGetAttribLocation(mProgramShader, name.c_str());
139 if (id == -1 && warn)
140 std::cerr << mName << ": warning: did not find attrib " << name << std::endl;
141 return id;
142 }
143
setUniform(const std::string & name,const GLUniformBuffer & buf,bool warn)144 void GLShader::setUniform(const std::string &name, const GLUniformBuffer &buf, bool warn) {
145 GLuint blockIndex = glGetUniformBlockIndex(mProgramShader, name.c_str());
146 if (blockIndex == GL_INVALID_INDEX) {
147 if (warn)
148 std::cerr << mName << ": warning: did not find uniform buffer " << name << std::endl;
149 return;
150 }
151 glUniformBlockBinding(mProgramShader, blockIndex, buf.getBindingPoint());
152 }
153
uniform(const std::string & name,bool warn) const154 GLint GLShader::uniform(const std::string &name, bool warn) const {
155 GLint id = glGetUniformLocation(mProgramShader, name.c_str());
156 if (id == -1 && warn)
157 std::cerr << mName << ": warning: did not find uniform " << name << std::endl;
158 return id;
159 }
160
uploadAttrib(const std::string & name,size_t size,int dim,uint32_t compSize,GLuint glType,bool integral,const void * data,int version)161 void GLShader::uploadAttrib(const std::string &name, size_t size, int dim,
162 uint32_t compSize, GLuint glType, bool integral,
163 const void *data, int version) {
164 int attribID = 0;
165 if (name != "indices") {
166 attribID = attrib(name);
167 if (attribID < 0)
168 return;
169 }
170
171 GLuint bufferID;
172 auto it = mBufferObjects.find(name);
173 if (it != mBufferObjects.end()) {
174 Buffer &buffer = it->second;
175 bufferID = it->second.id;
176 buffer.version = version;
177 buffer.size = size;
178 buffer.compSize = compSize;
179 } else {
180 glGenBuffers(1, &bufferID);
181 Buffer buffer;
182 buffer.id = bufferID;
183 buffer.glType = glType;
184 buffer.dim = dim;
185 buffer.compSize = compSize;
186 buffer.size = size;
187 buffer.version = version;
188 mBufferObjects[name] = buffer;
189 }
190 size_t totalSize = size * (size_t) compSize;
191
192 if (name == "indices") {
193 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufferID);
194 glBufferData(GL_ELEMENT_ARRAY_BUFFER, totalSize, data, GL_DYNAMIC_DRAW);
195 } else {
196 glBindBuffer(GL_ARRAY_BUFFER, bufferID);
197 glBufferData(GL_ARRAY_BUFFER, totalSize, data, GL_DYNAMIC_DRAW);
198 if (size == 0) {
199 glDisableVertexAttribArray(attribID);
200 } else {
201 glEnableVertexAttribArray(attribID);
202 glVertexAttribPointer(attribID, dim, glType, integral, 0, 0);
203 }
204 }
205 }
206
downloadAttrib(const std::string & name,size_t size,int,uint32_t compSize,GLuint,void * data)207 void GLShader::downloadAttrib(const std::string &name, size_t size, int /* dim */,
208 uint32_t compSize, GLuint /* glType */, void *data) {
209 auto it = mBufferObjects.find(name);
210 if (it == mBufferObjects.end())
211 throw std::runtime_error("downloadAttrib(" + mName + ", " + name + ") : buffer not found!");
212
213 const Buffer &buf = it->second;
214 if (buf.size != size || buf.compSize != compSize)
215 throw std::runtime_error(mName + ": downloadAttrib: size mismatch!");
216
217 size_t totalSize = size * (size_t) compSize;
218
219 if (name == "indices") {
220 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buf.id);
221 glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, totalSize, data);
222 } else {
223 glBindBuffer(GL_ARRAY_BUFFER, buf.id);
224 glGetBufferSubData(GL_ARRAY_BUFFER, 0, totalSize, data);
225 }
226 }
227
shareAttrib(const GLShader & otherShader,const std::string & name,const std::string & _as)228 void GLShader::shareAttrib(const GLShader &otherShader, const std::string &name, const std::string &_as) {
229 std::string as = _as.length() == 0 ? name : _as;
230 auto it = otherShader.mBufferObjects.find(name);
231 if (it == otherShader.mBufferObjects.end())
232 throw std::runtime_error("shareAttribute(" + otherShader.mName + ", " + name + "): attribute not found!");
233 const Buffer &buffer = it->second;
234
235 if (name != "indices") {
236 int attribID = attrib(as);
237 if (attribID < 0)
238 return;
239 glEnableVertexAttribArray(attribID);
240 glBindBuffer(GL_ARRAY_BUFFER, buffer.id);
241 glVertexAttribPointer(attribID, buffer.dim, buffer.glType, buffer.compSize == 1 ? GL_TRUE : GL_FALSE, 0, 0);
242 } else {
243 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.id);
244 }
245 }
246
invalidateAttribs()247 void GLShader::invalidateAttribs() {
248 for (auto &buffer : mBufferObjects)
249 buffer.second.version = -1;
250 }
251
freeAttrib(const std::string & name)252 void GLShader::freeAttrib(const std::string &name) {
253 auto it = mBufferObjects.find(name);
254 if (it != mBufferObjects.end()) {
255 glDeleteBuffers(1, &it->second.id);
256 mBufferObjects.erase(it);
257 }
258 }
259
drawIndexed(int type,uint32_t offset_,uint32_t count_)260 void GLShader::drawIndexed(int type, uint32_t offset_, uint32_t count_) {
261 if (count_ == 0)
262 return;
263 size_t offset = offset_;
264 size_t count = count_;
265
266 switch (type) {
267 case GL_TRIANGLES: offset *= 3; count *= 3; break;
268 case GL_LINES: offset *= 2; count *= 2; break;
269 }
270
271 glDrawElements(type, (GLsizei) count, GL_UNSIGNED_INT,
272 (const void *)(offset * sizeof(uint32_t)));
273 }
274
drawArray(int type,uint32_t offset,uint32_t count)275 void GLShader::drawArray(int type, uint32_t offset, uint32_t count) {
276 if (count == 0)
277 return;
278
279 glDrawArrays(type, offset, count);
280 }
281
free()282 void GLShader::free() {
283 for (auto &buf: mBufferObjects)
284 glDeleteBuffers(1, &buf.second.id);
285 mBufferObjects.clear();
286
287 if (mVertexArrayObject) {
288 glDeleteVertexArrays(1, &mVertexArrayObject);
289 mVertexArrayObject = 0;
290 }
291
292 glDeleteProgram(mProgramShader); mProgramShader = 0;
293 glDeleteShader(mVertexShader); mVertexShader = 0;
294 glDeleteShader(mFragmentShader); mFragmentShader = 0;
295 glDeleteShader(mGeometryShader); mGeometryShader = 0;
296 }
297
298 // ----------------------------------------------------
299
init()300 void GLUniformBuffer::init() {
301 glGenBuffers(1, &mID);
302 }
303
bind(int bindingPoint)304 void GLUniformBuffer::bind(int bindingPoint) {
305 mBindingPoint = bindingPoint;
306 glBindBufferBase(GL_UNIFORM_BUFFER, mBindingPoint, mID);
307 }
308
release()309 void GLUniformBuffer::release() {
310 glBindBufferBase(GL_UNIFORM_BUFFER, mBindingPoint, 0);
311 }
312
free()313 void GLUniformBuffer::free() {
314 glDeleteBuffers(1, &mID);
315 mID = 0;
316 }
317
update(const std::vector<uint8_t> & data)318 void GLUniformBuffer::update(const std::vector<uint8_t> &data) {
319 glBindBuffer(GL_UNIFORM_BUFFER, mID);
320 glBufferData(GL_UNIFORM_BUFFER, data.size(), data.data(), GL_DYNAMIC_DRAW);
321 glBindBuffer(GL_UNIFORM_BUFFER, 0);
322 }
323
324 // ----------------------------------------------------
325
init(const Vector2i & size,int nSamples)326 void GLFramebuffer::init(const Vector2i &size, int nSamples) {
327 mSize = size;
328 mSamples = nSamples;
329
330 glGenRenderbuffers(1, &mColor);
331 glBindRenderbuffer(GL_RENDERBUFFER, mColor);
332
333 if (nSamples <= 1)
334 glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, size.x(), size.y());
335 else
336 glRenderbufferStorageMultisample(GL_RENDERBUFFER, nSamples, GL_RGBA8, size.x(), size.y());
337
338 glGenRenderbuffers(1, &mDepth);
339 glBindRenderbuffer(GL_RENDERBUFFER, mDepth);
340
341 if (nSamples <= 1)
342 glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, size.x(), size.y());
343 else
344 glRenderbufferStorageMultisample(GL_RENDERBUFFER, nSamples, GL_DEPTH24_STENCIL8, size.x(), size.y());
345
346 glGenFramebuffers(1, &mFramebuffer);
347 glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
348
349 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, mColor);
350 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, mDepth);
351 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, mDepth);
352
353 glDrawBuffer(GL_COLOR_ATTACHMENT0);
354 glReadBuffer(GL_COLOR_ATTACHMENT0);
355
356 GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
357 if (status != GL_FRAMEBUFFER_COMPLETE)
358 throw std::runtime_error("Could not create framebuffer object!");
359
360 release();
361 }
362
free()363 void GLFramebuffer::free() {
364 glDeleteRenderbuffers(1, &mColor);
365 glDeleteRenderbuffers(1, &mDepth);
366 mColor = mDepth = 0;
367 }
368
bind()369 void GLFramebuffer::bind() {
370 glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
371 if (mSamples > 1)
372 glEnable(GL_MULTISAMPLE);
373 }
374
release()375 void GLFramebuffer::release() {
376 if (mSamples > 1)
377 glDisable(GL_MULTISAMPLE);
378 glBindFramebuffer(GL_FRAMEBUFFER, 0);
379 }
380
blit()381 void GLFramebuffer::blit() {
382 glBindFramebuffer(GL_READ_FRAMEBUFFER, mFramebuffer);
383 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
384 glDrawBuffer(GL_BACK);
385
386 glBlitFramebuffer(0, 0, mSize.x(), mSize.y(), 0, 0, mSize.x(), mSize.y(),
387 GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, GL_NEAREST);
388
389 glBindFramebuffer(GL_FRAMEBUFFER, 0);
390 }
391
downloadTGA(const std::string & filename)392 void GLFramebuffer::downloadTGA(const std::string &filename) {
393 uint8_t *temp = new uint8_t[mSize.prod() * 4];
394
395 std::cout << "Writing \"" << filename << "\" (" << mSize.x() << "x" << mSize.y() << ") .. ";
396 std::cout.flush();
397 glPixelStorei(GL_PACK_ALIGNMENT, 1);
398 glBindFramebuffer(GL_READ_FRAMEBUFFER, mFramebuffer);
399 glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
400 glReadPixels(0, 0, mSize.x(), mSize.y(), GL_BGRA, GL_UNSIGNED_BYTE, temp);
401 glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
402
403 uint32_t rowSize = mSize.x() * 4;
404 uint32_t halfHeight = mSize.y() / 2;
405 uint8_t *line = (uint8_t *) alloca(rowSize);
406 for (uint32_t i=0, j=mSize.y()-1; i<halfHeight; ++i) {
407 memcpy(line, temp + i * rowSize, rowSize);
408 memcpy(temp + i * rowSize, temp + j * rowSize, rowSize);
409 memcpy(temp + j * rowSize, line, rowSize);
410 j--;
411 }
412
413 FILE *tga = fopen(filename.c_str(), "wb");
414 if (tga == nullptr)
415 throw std::runtime_error("GLFramebuffer::downloadTGA(): Could not open output file");
416 fputc(0, tga); /* ID */
417 fputc(0, tga); /* Color map */
418 fputc(2, tga); /* Image type */
419 fputc(0, tga); fputc(0, tga); /* First entry of color map (unused) */
420 fputc(0, tga); fputc(0, tga); /* Length of color map (unused) */
421 fputc(0, tga); /* Color map entry size (unused) */
422 fputc(0, tga); fputc(0, tga); /* X offset */
423 fputc(0, tga); fputc(0, tga); /* Y offset */
424 fputc(mSize.x() % 256, tga); /* Width */
425 fputc(mSize.x() / 256, tga); /* continued */
426 fputc(mSize.y() % 256, tga); /* Height */
427 fputc(mSize.y() / 256, tga); /* continued */
428 fputc(32, tga); /* Bits per pixel */
429 fputc(0x20, tga); /* Scan from top left */
430 fwrite(temp, mSize.prod() * 4, 1, tga);
431 fclose(tga);
432
433 delete[] temp;
434 std::cout << "done." << std::endl;
435 }
436
437 // ----------------------------------------------------
438
project(const Eigen::Vector3f & obj,const Eigen::Matrix4f & model,const Eigen::Matrix4f & proj,const Vector2i & viewportSize)439 Eigen::Vector3f project(const Eigen::Vector3f &obj,
440 const Eigen::Matrix4f &model,
441 const Eigen::Matrix4f &proj,
442 const Vector2i &viewportSize) {
443 Eigen::Vector4f tmp;
444 tmp << obj, 1;
445
446 tmp = model * tmp;
447
448 tmp = proj * tmp;
449
450 tmp = tmp.array() / tmp(3);
451 tmp = tmp.array() * 0.5f + 0.5f;
452 tmp(0) = tmp(0) * viewportSize.x();
453 tmp(1) = tmp(1) * viewportSize.y();
454
455 return tmp.head(3);
456 }
457
unproject(const Eigen::Vector3f & win,const Eigen::Matrix4f & model,const Eigen::Matrix4f & proj,const Vector2i & viewportSize)458 Eigen::Vector3f unproject(const Eigen::Vector3f &win,
459 const Eigen::Matrix4f &model,
460 const Eigen::Matrix4f &proj,
461 const Vector2i &viewportSize) {
462 Eigen::Matrix4f Inverse = (proj * model).inverse();
463
464 Eigen::Vector4f tmp;
465 tmp << win, 1;
466 tmp(0) = tmp(0) / viewportSize.x();
467 tmp(1) = tmp(1) / viewportSize.y();
468 tmp = tmp.array() * 2.0f - 1.0f;
469
470 Eigen::Vector4f obj = Inverse * tmp;
471 obj /= obj(3);
472
473 return obj.head(3);
474 }
475
lookAt(const Eigen::Vector3f & eye,const Eigen::Vector3f & center,const Eigen::Vector3f & up)476 Eigen::Matrix4f lookAt(const Eigen::Vector3f &eye,
477 const Eigen::Vector3f ¢er,
478 const Eigen::Vector3f &up) {
479 Eigen::Vector3f f = (center - eye).normalized();
480 Eigen::Vector3f s = f.cross(up).normalized();
481 Eigen::Vector3f u = s.cross(f);
482
483 Eigen::Matrix4f Result = Eigen::Matrix4f::Identity();
484 Result(0, 0) = s(0);
485 Result(0, 1) = s(1);
486 Result(0, 2) = s(2);
487 Result(1, 0) = u(0);
488 Result(1, 1) = u(1);
489 Result(1, 2) = u(2);
490 Result(2, 0) = -f(0);
491 Result(2, 1) = -f(1);
492 Result(2, 2) = -f(2);
493 Result(0, 3) = -s.transpose() * eye;
494 Result(1, 3) = -u.transpose() * eye;
495 Result(2, 3) = f.transpose() * eye;
496 return Result;
497 }
498
ortho(const float left,const float right,const float bottom,const float top,const float zNear,const float zFar)499 Eigen::Matrix4f ortho(const float left, const float right, const float bottom,
500 const float top, const float zNear, const float zFar) {
501 Eigen::Matrix4f Result = Eigen::Matrix4f::Identity();
502 Result(0, 0) = 2.0f / (right - left);
503 Result(1, 1) = 2.0f / (top - bottom);
504 Result(2, 2) = -2.0f / (zFar - zNear);
505 Result(0, 3) = -(right + left) / (right - left);
506 Result(1, 3) = -(top + bottom) / (top - bottom);
507 Result(2, 3) = -(zFar + zNear) / (zFar - zNear);
508 return Result;
509 }
510
frustum(const float left,const float right,const float bottom,const float top,const float nearVal,const float farVal)511 Eigen::Matrix4f frustum(const float left, const float right, const float bottom,
512 const float top, const float nearVal,
513 const float farVal) {
514 Eigen::Matrix4f Result = Eigen::Matrix4f::Zero();
515 Result(0, 0) = (2.0f * nearVal) / (right - left);
516 Result(1, 1) = (2.0f * nearVal) / (top - bottom);
517 Result(0, 2) = (right + left) / (right - left);
518 Result(1, 2) = (top + bottom) / (top - bottom);
519 Result(2, 2) = -(farVal + nearVal) / (farVal - nearVal);
520 Result(3, 2) = -1.0f;
521 Result(2, 3) = -(2.0f * farVal * nearVal) / (farVal - nearVal);
522 return Result;
523 }
524
scale(const Eigen::Matrix4f & m,const Eigen::Vector3f & v)525 Eigen::Matrix4f scale(const Eigen::Matrix4f &m, const Eigen::Vector3f &v) {
526 Eigen::Matrix4f Result;
527 Result.col(0) = m.col(0).array() * v(0);
528 Result.col(1) = m.col(1).array() * v(1);
529 Result.col(2) = m.col(2).array() * v(2);
530 Result.col(3) = m.col(3);
531 return Result;
532 }
533
translate(const Eigen::Matrix4f & m,const Eigen::Vector3f & v)534 Eigen::Matrix4f translate(const Eigen::Matrix4f &m, const Eigen::Vector3f &v) {
535 Eigen::Matrix4f Result = m;
536 Result.col(3) = m.col(0).array() * v(0) + m.col(1).array() * v(1) +
537 m.col(2).array() * v(2) + m.col(3).array();
538 return Result;
539 }
540
541 NAMESPACE_END(nanogui)
542