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 &center,
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