TinyGL-C++
Single-header, cross-platform, OpenGL 4.0 2D drawing framework
tinygl-cpp.h
1 #ifndef tinygl_cpp_H_
2 #define tinygl_cpp_H_
3 
4 #include <stdio.h>
5 #include <iostream>
6 #include <vector>
7 #include <map>
8 #include <cmath>
9 
10 #if ( (defined(__MACH__)) && (defined(__APPLE__)) )
11 #define GLFW_INCLUDE_GLCOREARB
12 #include <GLFW/glfw3.h>
13 #include <OpenGL/gl3ext.h>
14 #else
15 #include <GL/glew.h>
16 #include <GLFW/glfw3.h>
17 #endif
18 #define STB_IMAGE_IMPLEMENTATION
19 #include "stb/stb_image.h"
20 
21 namespace tinygl {
22 
23 static void error_callback(int error, const char* description) {
24  fputs("\n", stderr);
25  fputs(description, stderr);
26  fputs("\n", stderr);
27 }
28 
29 const GLchar* vertexShader[] =
30 {
31 "#version 400\n"
32 "uniform vec4 inColor;"
33 "uniform vec3 inSize;"
34 "uniform vec3 inPos;"
35 "uniform float inScreenWidth;"
36 "uniform float inScreenHeight;"
37 "in vec3 VertexPosition;"
38 "out vec2 uv;"
39 "out vec4 color;"
40 "void main() {"
41 " color = inColor;"
42 " uv = (VertexPosition + 0.5).xy;"
43 " vec4 x = vec4(2.0 / inScreenWidth, 0.0, 0.0, 0.0);"
44 " vec4 y = vec4(0.0, 2.0 / inScreenHeight, 0.0, 0.0);"
45 " vec4 z = vec4(0.0, 0.0, -2.0 / 2000, 0.0);"
46 " vec4 d = vec4(-1.0, -1.0, 0.0, 1.0);"
47 " mat4 projection = mat4(x, y, z, d);"
48 " vec3 pos = inSize * VertexPosition + inPos;"
49 " gl_Position = projection * vec4(pos, 1.0);"
50 "}"
51 };
52 
53 const GLchar* fragmentShader[] =
54 {
55 "#version 400\n"
56 "uniform sampler2D inImage;"
57 "in vec4 color;"
58 "in vec2 uv;"
59 "out vec4 FragColor;"
60 "void main() { FragColor = color * texture(inImage, uv); }"
61 };
62 
63 
64 static void PrintShaderErrors(GLuint id, const std::string& label)
65 {
66  std::cerr << label << " failed\n";
67  GLint logLen;
68  glGetShaderiv(id, GL_INFO_LOG_LENGTH, &logLen);
69  if (logLen > 0)
70  {
71  char* log = (char*)malloc(logLen);
72  GLsizei written;
73  glGetShaderInfoLog(id, logLen, &written, log);
74  std::cerr << "Shader log: " << log << std::endl;
75  free(log);
76  }
77 }
78 static class Window* theInstance = 0;
79 
101 class Window {
102  public:
110  Window(int width, int height) :
111  _windowWidth(width),
112  _windowHeight(height),
113  _elapsedTime(0.0),
114  _dt(-1.0) {
115 
116  theInstance = this;
117  glfwSetErrorCallback(error_callback);
118 
119  if (!glfwInit()) {
120  fprintf(stderr, "ERROR: Cannot initialize GLFW\n");
121  return;
122  }
123 
124  // Set the GLFW window creation hints - these are optional
125  glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
126  glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
127  glfwWindowHint(GLFW_SAMPLES, 4); // Request 4x antialiasing
128  glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
129  glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
130 
131  _window = glfwCreateWindow(_windowWidth, _windowHeight, "TinyGL C++ Window", 0, 0);
132  if (!_window) {
133  fprintf(stderr, "ERROR: Cannot initialize GLFW window\n");
134  glfwTerminate();
135  return;
136  }
137 
138  glfwMakeContextCurrent(_window);
139  glfwSetKeyCallback(_window, Window::onKeyboardCb);
140  glfwSetMouseButtonCallback(_window, Window::onMouseButtonCb);
141  glfwSetCursorPosCallback(_window, Window::onMouseMotionCb);
142  glfwSetScrollCallback(_window, Window::onScrollCb);
143 
144 #ifndef APPLE
145  if (glewInit() != GLEW_OK)
146  {
147  std::cout << "Cannot initialize GLEW\n";
148  return;
149  }
150 #endif
151 
152  // Initialize openGL and set default values
153  glEnable(GL_DEPTH_TEST);
154  glDepthFunc(GL_ALWAYS);
155  glEnable(GL_CULL_FACE);
156  glEnable(GL_BLEND);
157  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Alpha blend
158  glCullFace(GL_BACK);
159 
160  background(0, 0, 0);
161 
162  const float triangle[] =
163  {
164  -0.5, -0.5, 0,
165  0.5, -0.5, 0,
166  0.0, 0.5, 0
167  };
168 
169  const float square[] =
170  {
171  -0.5, -0.5, 0,
172  0.5, -0.5, 0,
173  0.5, 0.5, 0,
174 
175  -0.5, -0.5, 0,
176  0.5, 0.5, 0,
177  -0.5, 0.5, 0
178  };
179 
180  // numVerts = numTris + 2 -> Set numTris = 16
181  float* circle = new float[3*(numTris+2)]; // numFloats = numVerts * 3 = 36
182  circle[0] = circle[1] = circle[2] = 0.0;
183  float deltaAngle = 2.0f * 3.1415926535897932384626433832795f / numTris;
184  for (int i = 1; i < numTris+2; i++) {
185  float angle = (i-1) * deltaAngle;
186  circle[i*3+0] = cos(angle);
187  circle[i*3+1] = sin(angle);
188  circle[i*3+2] = 0;
189  }
190 
191  // default texture
192  glEnable(GL_TEXTURE0);
193  glActiveTexture(GL_TEXTURE0);
194  GLuint texId;
195  glGenTextures(1, &texId);
196  _textures["default"] = Texture{texId, 1, 1};
197  unsigned char defaultTex[4] = { 255,255,255,255 };
198  glBindTexture(GL_TEXTURE_2D, texId);
199  glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1);
200  glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1,
201  GL_RGBA, GL_UNSIGNED_BYTE, defaultTex);
202  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
203  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
204 
205  GLuint vboId;
206  glGenBuffers(1, &vboId);
207  glBindBuffer(GL_ARRAY_BUFFER, vboId);
208  glBufferData(GL_ARRAY_BUFFER, 9 * sizeof(float), triangle, GL_STATIC_DRAW);
209 
210  glGenVertexArrays(1, &_triVao);
211  glBindVertexArray(_triVao);
212  glEnableVertexAttribArray(0); // 0 -> activate sending VertexPositions to the active shader
213  glBindBuffer(GL_ARRAY_BUFFER, vboId); // as a habit -> always bind before setting data
214  glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (GLubyte*)NULL);
215 
216  glGenBuffers(1, &vboId);
217  glBindBuffer(GL_ARRAY_BUFFER, vboId);
218  glBufferData(GL_ARRAY_BUFFER, 24 * sizeof(float), square, GL_STATIC_DRAW);
219 
220  glGenVertexArrays(1, &_squareVao);
221  glBindVertexArray(_squareVao);
222  glEnableVertexAttribArray(0); // 0 -> activate sending VertexPositions to the active shader
223  glBindBuffer(GL_ARRAY_BUFFER, vboId); // as a habit -> always bind before setting data
224  glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (GLubyte*)NULL);
225 
226  glGenBuffers(1, &vboId);
227  glBindBuffer(GL_ARRAY_BUFFER, vboId);
228  glBufferData(GL_ARRAY_BUFFER, 3*(numTris+2) * sizeof(float), circle, GL_STATIC_DRAW);
229 
230  glGenVertexArrays(1, &_circleVao);
231  glBindVertexArray(_circleVao);
232  glEnableVertexAttribArray(0); // 0 -> activate sending VertexPositions to the active shader
233  glBindBuffer(GL_ARRAY_BUFFER, vboId); // as a habit -> always bind before setting data
234  glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (GLubyte*)NULL);
235  delete[] circle;
236 
237  GLint result;
238  GLuint vshaderId = glCreateShader(GL_VERTEX_SHADER);
239  glShaderSource(vshaderId, 1, vertexShader, NULL);
240  glCompileShader(vshaderId);
241  glGetShaderiv(vshaderId, GL_COMPILE_STATUS, &result);
242  if (result == GL_FALSE)
243  {
244  PrintShaderErrors(vshaderId, "Vertex shader");
245  }
246 
247  GLuint fshaderId = glCreateShader(GL_FRAGMENT_SHADER);
248  glShaderSource(fshaderId, 1, fragmentShader, NULL);
249  glCompileShader(fshaderId);
250  glGetShaderiv(fshaderId, GL_COMPILE_STATUS, &result);
251  if (result == GL_FALSE)
252  {
253  PrintShaderErrors(fshaderId, "Fragment shader");
254  }
255 
256  GLuint shaderId = glCreateProgram();
257  glAttachShader(shaderId, vshaderId);
258  glAttachShader(shaderId, fshaderId);
259  glLinkProgram(shaderId);
260  glGetShaderiv(shaderId, GL_LINK_STATUS, &result);
261  if (result == GL_FALSE)
262  {
263  PrintShaderErrors(shaderId, "Shader link");
264  }
265 
266  glUseProgram(shaderId);
267  _colorUniform = glGetUniformLocation(shaderId, "inColor");
268  _sizeUniform = glGetUniformLocation(shaderId, "inSize");
269  _posUniform = glGetUniformLocation(shaderId, "inPos");
270 
271  GLuint widthUniform = glGetUniformLocation(shaderId, "inScreenWidth");
272  glUniform1f(widthUniform, _windowWidth);
273 
274  GLuint heightUniform = glGetUniformLocation(shaderId, "inScreenHeight");
275  glUniform1f(heightUniform, _windowHeight);
276 
277  _imageUniform = glGetUniformLocation(shaderId, "inImage");
278  glUniform1i(_imageUniform, 0); // texture slot 0
279  }
280 
281  virtual ~Window() {
282  glfwTerminate();
283  }
284 
293  void run() {
294  if (!_window) return; // window wasn't initialized
295  setup();
296 
297  while (!glfwWindowShouldClose(_window)) {
298  float time = glfwGetTime();
299  _dt = time - _elapsedTime;
300  _elapsedTime = time;
301 
302  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
303 
304  draw(); // user function
305  glfwPollEvents();
306  glfwSwapBuffers(_window);
307  }
308  }
309 
310  protected:
311 
314 
323  void loadSprite(const std::string& name, const std::string& filename) {
324 
325  int w, h, n;
326  unsigned char* data = stbi_load(filename.c_str(), &w, &h, &n, 4);
327 
328  if (!data) {
329  std::cout << "ERROR: Cannot load texture " << filename<< std::endl;
330  return;
331  }
332 
333  glEnable(GL_TEXTURE0);
334  glActiveTexture(GL_TEXTURE0);
335 
336  GLuint texId;
337  if (_textures.count(name) == 0) {
338  glGenTextures(1, &texId);
339  _textures[name] = Texture{texId, w, h};
340  } else {
341  texId = _textures[name].texId;
342  }
343 
344  glBindTexture(GL_TEXTURE_2D, texId);
345  glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, w, h);
346  glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h,
347  GL_RGBA, GL_UNSIGNED_BYTE, data);
348 
349  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
350  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
351  stbi_image_free(data);
352  }
353 
370  void sprite(const std::string& textureName, float x, float y, float scale = 1) {
371 
372  assert(_textures.count(textureName) > 0);
373 
374  Texture& tex = _textures[textureName];
375  glActiveTexture(GL_TEXTURE0);
376  glBindTexture(GL_TEXTURE_2D, tex.texId);
377 
378  glUniform1i(_imageUniform, 0);
379  glUniform3f(_posUniform, x, y, 0);
380  glUniform3f(_sizeUniform, tex.width*scale, tex.height*scale, 1);
381  glBindVertexArray(_squareVao);
382  glDrawArrays(GL_TRIANGLES, 0, 6);
383  }
384 
393  void square(float x, float y, float width, float height) {
394  Texture& tex = _textures["default"];
395  glBindTexture(GL_TEXTURE_2D, tex.texId);
396 
397  glUniform3f(_posUniform, x, y, 0);
398  glUniform3f(_sizeUniform, width, height, 1);
399  glBindVertexArray(_squareVao);
400  glDrawArrays(GL_TRIANGLES, 0, 6);
401  }
402 
411  void triangle(int x, int y, float width, float height) {
412  Texture& tex = _textures["default"];
413  glBindTexture(GL_TEXTURE_2D, tex.texId);
414  glUniform3f(_posUniform, x, y, 0);
415  glUniform3f(_sizeUniform, width, height, 1);
416  glBindVertexArray(_triVao);
417  glDrawArrays(GL_TRIANGLES, 0, 3);
418  }
419 
427  void circle(int x, int y, float diameter) {
428  ellipsoid(x, y, diameter, diameter);
429  }
430 
439  void ellipsoid(int x, int y, float width, float height) {
440  Texture& tex = _textures["default"];
441  glBindTexture(GL_TEXTURE_2D, tex.texId);
442  glUniform3f(_posUniform, x, y, 0);
443  glUniform3f(_sizeUniform, width, height, 1);
444  glBindVertexArray(_circleVao);
445  glDrawArrays(GL_TRIANGLE_FAN, 0, numTris+2);
446  }
447 
455  void color(float r, float g, float b, float a = 1.0f) {
456  glUniform4f(_colorUniform, r, g, b, a);
457  }
458 
460 
461 
464 
469  virtual void setup() {}
470 
474  virtual void draw() {}
475 
486  virtual void mouseMotion(int x, int y, int dx, int dy) {}
487 
497  virtual void mouseDown(int button, int mods) {}
498 
505  virtual void mouseUp(int button, int mods) {}
506 
514  virtual void scroll(float dx, float dy) {}
515 
526  virtual void keyUp(int key, int mods) {}
527 
537  virtual void keyDown(int key, int mods) {}
538 
541 
550  bool keyIsDown(int key) const {
551  int state = glfwGetKey(_window, key);
552  return (state == GLFW_PRESS);
553  }
554 
563  bool mouseIsDown(int button) const {
564  int state = glfwGetMouseButton(_window, button);
565  return (state == GLFW_PRESS);
566  }
567 
571  float mouseX() const {
572  double xpos, ypos;
573  glfwGetCursorPos(_window, &xpos, &ypos);
574  return static_cast<float>(xpos);
575  }
576 
580  float mouseY() const {
581  double xpos, ypos;
582  glfwGetCursorPos(_window, &xpos, &ypos);
583  return static_cast<float>(ypos);
584  }
585 
592  float dt() const {
593  return _dt;
594  }
595 
599  float elapsedTime() const {
600  return _elapsedTime;
601  }
602 
606  float height() const {
607  return static_cast<float>(_windowHeight);
608  }
609 
613  float width() const {
614  return static_cast<float>(_windowWidth);
615  }
617 
624  void noLoop() {
625  glfwSetWindowShouldClose(_window, GL_TRUE);
626  }
627 
634  void background(float r, float g, float b) {
635  glClearColor(r, g, b, 1.0f);
636  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
637  }
638 
639  private:
640  static void onScrollCb(GLFWwindow* w, double xoffset, double yoffset) {
641  theInstance->scroll(
642  static_cast<float>(xoffset),
643  static_cast<float>(yoffset));
644  }
645  static void onMouseMotionCb(GLFWwindow* w, double x, double y) {
646  theInstance->onMouseMotion(static_cast<int>(x), static_cast<int>(y));
647  }
648 
649  static void onMouseButtonCb(GLFWwindow* w, int button, int action, int mods) {
650  theInstance->onMouseButton(button, action, mods);
651  }
652 
653  static void onKeyboardCb(GLFWwindow* w, int key, int code, int action, int mods) {
654  theInstance->onKeyboard(key, code, action, mods);
655  }
656 
657  void onMouseMotion(int x, int y) {
658  int dx = mouseX() - _lastx;
659  int dy = mouseY() - _lasty;
660  mouseMotion(x, height()-y, dx, -dy); // user hook
661  }
662 
663  void onMouseButton(int button, int action, int mods) {
664  double xpos, ypos;
665  glfwGetCursorPos(_window, &xpos, &ypos);
666 
667  if (action == GLFW_PRESS) {
668  _lastx = xpos;
669  _lasty = ypos;
670  mouseDown(button, mods);
671  } else if (action == GLFW_RELEASE) {
672  mouseUp(button, mods);
673  }
674 
675  onMouseMotion(static_cast<float>(xpos), static_cast<float>(ypos));
676  }
677 
678  void onKeyboard(int key, int scancode, int action, int mods) {
679  if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) {
680  glfwSetWindowShouldClose(_window, GL_TRUE);
681  }
682 
683  if (action == GLFW_PRESS) {
684  keyDown(key, mods);
685  } else if (action == GLFW_RELEASE) {
686  keyUp(key, mods);
687  }
688  }
689 
690  private:
691  int _windowWidth, _windowHeight;
692  float _elapsedTime;
693  float _dt;
694  float _lastx, _lasty;
695  struct GLFWwindow* _window = 0;
696  GLuint _triVao, _squareVao, _circleVao;
697  GLuint _colorUniform, _sizeUniform, _posUniform, _imageUniform;
698  const int numTris = 16; // for circles
699 
700  // textures
701  struct Texture {
702  GLuint texId;
703  int width;
704  int height;
705  };
706  std::map<std::string, Texture> _textures;
707 
708  protected:
709  inline GLFWwindow* window() const { return _window; }
710 };
711 
712 } // namespace tinygl
713 
714 #endif
tinygl::Window::square
void square(float x, float y, float width, float height)
Draws a square with the current color in pixel coordinates.
Definition: tinygl-cpp.h:393
tinygl::Window::elapsedTime
float elapsedTime() const
Return the amount of time since the setup() was called (in seconds)
Definition: tinygl-cpp.h:599
tinygl::Window::height
float height() const
Return the window height in pixels.
Definition: tinygl-cpp.h:606
tinygl::Window::triangle
void triangle(int x, int y, float width, float height)
Draw a triangle with the current color using pixel coordinates.
Definition: tinygl-cpp.h:411
tinygl::Window::width
float width() const
Return the window width in pixels.
Definition: tinygl-cpp.h:613
tinygl::Window::run
void run()
Opens the window and starts the main application loop.
Definition: tinygl-cpp.h:293
tinygl::Window::sprite
void sprite(const std::string &textureName, float x, float y, float scale=1)
Draws a sprite with the current color in pixel coordinates.
Definition: tinygl-cpp.h:370
tinygl::Window::keyUp
virtual void keyUp(int key, int mods)
Override this method to respond to key presses (button up)
Definition: tinygl-cpp.h:526
tinygl::Window::dt
float dt() const
Return the amount of time since the previous frame (in seconds)
Definition: tinygl-cpp.h:592
tinygl::Window::loadSprite
void loadSprite(const std::string &name, const std::string &filename)
Loads a texture to be shown on a sprite.
Definition: tinygl-cpp.h:323
tinygl::Window::draw
virtual void draw()
Override this method to draw.
Definition: tinygl-cpp.h:474
tinygl::Window::circle
void circle(int x, int y, float diameter)
Draws a circle with the current color in pixel coordinates.
Definition: tinygl-cpp.h:427
tinygl::Window::mouseY
float mouseY() const
Return the current mouse position Y coordinate (in screen coordinates)
Definition: tinygl-cpp.h:580
tinygl::Window::mouseIsDown
bool mouseIsDown(int button) const
Query whether the given mouse button is down.
Definition: tinygl-cpp.h:563
tinygl::Window::noLoop
void noLoop()
Stop the main application loop.
Definition: tinygl-cpp.h:624
tinygl::Window::mouseMotion
virtual void mouseMotion(int x, int y, int dx, int dy)
Override this method to respond to mouse movement.
Definition: tinygl-cpp.h:486
tinygl::Window::keyDown
virtual void keyDown(int key, int mods)
Override this method to respond to key presses (button down)
Definition: tinygl-cpp.h:537
tinygl::Window::color
void color(float r, float g, float b, float a=1.0f)
Set the current RGB color.
Definition: tinygl-cpp.h:455
tinygl::Window::ellipsoid
void ellipsoid(int x, int y, float width, float height)
Draws an ellipse with the current color in pixel coordinates.
Definition: tinygl-cpp.h:439
tinygl::Window::mouseDown
virtual void mouseDown(int button, int mods)
Override this method to respond to mouse press (button down)
Definition: tinygl-cpp.h:497
tinygl::Window::Window
Window(int width, int height)
Constructor.
Definition: tinygl-cpp.h:110
tinygl::Window::mouseUp
virtual void mouseUp(int button, int mods)
Override this method to respond to mouse press (button up)
Definition: tinygl-cpp.h:505
tinygl::Window::mouseX
float mouseX() const
Return the current mouse position X coordinate (in screen coordinates)
Definition: tinygl-cpp.h:571
tinygl::Window::setup
virtual void setup()
Override this method to perform setup before the main application loop.
Definition: tinygl-cpp.h:469
tinygl::Window::background
void background(float r, float g, float b)
Set the background color.
Definition: tinygl-cpp.h:634
tinygl::Window
Manages the window and user input.
Definition: tinygl-cpp.h:101
tinygl::Window::scroll
virtual void scroll(float dx, float dy)
Override this method to respond to scrolling the middle mouse button.
Definition: tinygl-cpp.h:514
tinygl::Window::keyIsDown
bool keyIsDown(int key) const
Query whether the given key is down @key The key to test.
Definition: tinygl-cpp.h:550