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