├── LICENSE ├── README.md ├── main.cpp ├── screenshot1.png ├── screenshot2.png └── texture.png /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Tomasz Czajęcki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 2d-opengl-renderer 2 | Minimal, fast OpenGL renderer for 2D sprites 3 | 4 | ![Screenshot 1](https://raw.githubusercontent.com/Tchayen/2d_opengl_renderer/master/screenshot1.png) 5 | 6 | Loads one texture and uses it to render several sprites multiple thousand times each. Allowing for up to 20k on mobile Intel GPU in 60 fps (see [Screenshot 2](https://raw.githubusercontent.com/Tchayen/2d_opengl_renderer/master/screenshot2.png)). 7 | 8 | # TODO: 9 | - [x] basic logic of buffers etc. 10 | - [x] updating vertex buffer, allowing to move objects 11 | 12 | # License and dependencies 13 | MIT license (see file LICENSE for copy). 14 | Project uses GLFW, GLEW and SOIL but non of them are included here. [Fruit icons](http://www.flaticon.com/packs/gastronomy-set). 15 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | // needed for printf(), and rand(), they are not required 2 | #include 3 | #include 4 | #include 5 | 6 | // you will need your own OpenGL bindings, GLFW (or other lib capable of 7 | // creating window and OpenGL context) and SOIL (or, again, other lib loading 8 | // images in format of your choice) 9 | #include "glew-1.9.0\glew.h" 10 | #include "glfw-3.2\glfw3.h" 11 | #include "soil-1.16\SOIL.h" 12 | 13 | // not necessary but makes my life much easier when I can just see the number 14 | // of bits and I dont have to write 'unsigned' as a separate word 15 | typedef short int16; 16 | typedef int int32; 17 | typedef unsigned short uint16; 18 | typedef unsigned int uint32; 19 | 20 | void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode); 21 | GLuint getShaderProgramId(const char *vertexFile, const char *fragmentFile); 22 | GLuint compileShader(const GLchar *source, GLuint shaderType); 23 | 24 | GLFWwindow* window; 25 | 26 | const uint32 27 | WINDOW_WIDTH = 1440, 28 | WINDOW_HEIGHT = 810, 29 | // object count is static number in this example, but it can be made a bit 30 | // more flexible (however remember that you will never have much freedom 31 | // with constant block of memory which is required by targeted performance) 32 | OBJECT_COUNT = 100; 33 | 34 | GLuint shaderProgramId, vao, vbo, ubo, textureId; 35 | 36 | // in order to avoid sending information to shaders screen resolution is hard 37 | // coded (precisely, half of it in each dimension), but it should be 38 | // unnoticable in performance after extracting to shader uniform 39 | const char *vertexShader = 40 | "#version 330\n" 41 | "layout (location = 0) in vec2 vert;\n" 42 | "layout (location = 1) in vec2 _uv;\n" 43 | "out vec2 uv;\n" 44 | "void main()\n" 45 | "{\n" 46 | " uv = _uv;\n" 47 | " gl_Position = vec4(vert.x / 720.0 - 1.0, vert.y / 405.0 - 1.0, 0.0, 1.0);\n" 48 | "}\n"; 49 | 50 | const char *fragmentShader = 51 | "#version 330\n" 52 | "out vec4 color;\n" 53 | "in vec2 uv;\n" 54 | "uniform sampler2D tex;\n" 55 | "void main()\n" 56 | "{\n" 57 | " color = texture(tex, uv);\n" 58 | "}\n"; 59 | 60 | struct Texture { uint16 width, height; float u1, v1, u2, v2; }; 61 | struct Object { int16 x, y; Texture texture; }; 62 | 63 | Texture 64 | watermelon = { 64, 64, 0.0f, 0.0f, 0.5f, 0.5f }, 65 | pineapple = { 64, 64, 0.5f, 0.0f, 1.0f, 0.5f }, 66 | orange = { 32, 32, 0.0f, 0.5f, 0.25f, 0.75f }, 67 | grapes = { 32, 32, 0.25f, 0.5f, 0.5f, 0.75f }, 68 | pear = { 32, 32, 0.0f, 0.75f, 0.25f, 1.0f }, 69 | banana = { 32, 32, 0.25f, 0.75f, 0.5f, 1.0f }, 70 | strawberry = { 16, 16, 0.5f, 0.5f, 0.625f, 0.625f }, 71 | raspberry = { 16, 16, 0.625f, 0.5f, 0.75f, 0.625f }, 72 | cherries = { 16, 16, 0.5f, 0.625f, 0.625f, 0.75f }; 73 | 74 | Texture textures[9] = 75 | { 76 | watermelon, 77 | pineapple, 78 | orange, 79 | grapes, 80 | pear, 81 | banana, 82 | strawberry, 83 | cherries, 84 | raspberry 85 | }; 86 | 87 | static Object objects[OBJECT_COUNT]; 88 | 89 | static int16 vertices[OBJECT_COUNT * 12]; 90 | static float uvs[OBJECT_COUNT * 12]; 91 | 92 | void updateObject(int i) 93 | { 94 | // top right 95 | vertices[i * 12] = objects[i].x + objects[i].texture.width; 96 | vertices[i * 12 + 1] = objects[i].y; 97 | 98 | // bottom right 99 | vertices[i * 12 + 2] = objects[i].x + objects[i].texture.width; 100 | vertices[i * 12 + 3] = objects[i].y + objects[i].texture.height; 101 | 102 | // top left 103 | vertices[i * 12 + 4] = objects[i].x; 104 | vertices[i * 12 + 5] = objects[i].y; 105 | 106 | // bottom right 107 | vertices[i * 12 + 6] = objects[i].x + objects[i].texture.width; 108 | vertices[i * 12 + 7] = objects[i].y + objects[i].texture.height; 109 | 110 | // bottom left 111 | vertices[i * 12 + 8] = objects[i].x; 112 | vertices[i * 12 + 9] = objects[i].y + objects[i].texture.height; 113 | 114 | // top left 115 | vertices[i * 12 + 10] = objects[i].x; 116 | vertices[i * 12 + 11] = objects[i].y; 117 | } 118 | 119 | void update() 120 | { 121 | // dancing fruits =) 122 | for (uint32 i = 0; i < OBJECT_COUNT; i++) 123 | { 124 | objects[i].x += rand() % 5 - 2; 125 | objects[i].y += rand() % 5 - 2; 126 | 127 | updateObject(i); 128 | } 129 | 130 | // if you had to unbind vbo for whatever reason, bind it again now 131 | // glBindBuffer(GL_ARRAY_BUFFER, vbo); 132 | glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices); 133 | 134 | } 135 | 136 | void render() 137 | { 138 | glClearColor(0.2f, 0.25f, 0.3f, 1.0f); 139 | glClear(GL_COLOR_BUFFER_BIT); 140 | 141 | // if you had to unbind vao for whatever reason, bind it again now 142 | // glBindVertexArray(vao); 143 | glDrawArrays(GL_TRIANGLES, 0, OBJECT_COUNT * 6); 144 | 145 | glfwSwapBuffers(window); 146 | } 147 | 148 | int32 main() 149 | { 150 | // not perfect but good enough randomness 151 | srand(time(0)); 152 | 153 | // initialize GLFW 154 | glfwInit(); 155 | glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); 156 | glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); 157 | glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); 158 | glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); 159 | 160 | window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "", 0, 0); 161 | glfwMakeContextCurrent(window); 162 | glfwSetKeyCallback(window, key_callback); 163 | 164 | // initialize GLEW 165 | glewExperimental = GL_TRUE; 166 | glewInit(); 167 | 168 | // OpenGL setup 169 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 170 | glEnable(GL_CULL_FACE); 171 | glFrontFace(GL_CCW); 172 | glEnable(GL_BLEND); 173 | glDisable(GL_DEPTH_TEST); 174 | glDisable(GL_SCISSOR_TEST); 175 | 176 | // viewport setup 177 | { 178 | int32 width, height; 179 | glfwGetFramebufferSize(window, &width, &height); 180 | glViewport(0, 0, width, height); 181 | } 182 | 183 | // initialize shader 184 | shaderProgramId = getShaderProgramId(vertexShader, fragmentShader); 185 | 186 | // texture 187 | glGenTextures(1, &textureId); 188 | glBindTexture(GL_TEXTURE_2D, textureId); 189 | 190 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); 191 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); 192 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 193 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 194 | 195 | // load image using SOIL and store it on GPU 196 | { 197 | int32 width, height; 198 | unsigned char* image = SOIL_load_image( 199 | "texture.png", 200 | &width, 201 | &height, 202 | 0, 203 | SOIL_LOAD_RGBA); 204 | 205 | glTexImage2D( 206 | GL_TEXTURE_2D, 207 | 0, 208 | GL_RGBA, 209 | width, 210 | height, 211 | 0, 212 | GL_RGBA, 213 | GL_UNSIGNED_BYTE, 214 | image); 215 | 216 | if (image == 0) 217 | { 218 | printf("Failed to load texture image.\n"); 219 | exit(1); 220 | } 221 | else 222 | { 223 | SOIL_free_image_data(image); 224 | } 225 | } 226 | 227 | for (uint32 i = 0; i < OBJECT_COUNT; i++) 228 | { 229 | Texture t = textures[rand() % 9]; 230 | objects[i] = 231 | { 232 | (int16)(rand() % (WINDOW_WIDTH - t.width)), 233 | (int16)(rand() % (WINDOW_HEIGHT - t.height)), 234 | t 235 | }; 236 | 237 | // vertices 238 | { 239 | // top right 240 | vertices[i * 12] = objects[i].x + objects[i].texture.width; 241 | vertices[i * 12 + 1] = objects[i].y; 242 | 243 | // bottom right 244 | vertices[i * 12 + 2] = objects[i].x + objects[i].texture.width; 245 | vertices[i * 12 + 3] = objects[i].y + objects[i].texture.height; 246 | 247 | // top left 248 | vertices[i * 12 + 4] = objects[i].x; 249 | vertices[i * 12 + 5] = objects[i].y; 250 | 251 | // bottom right 252 | vertices[i * 12 + 6] = objects[i].x + objects[i].texture.width; 253 | vertices[i * 12 + 7] = objects[i].y + objects[i].texture.height; 254 | 255 | // bottom left 256 | vertices[i * 12 + 8] = objects[i].x; 257 | vertices[i * 12 + 9] = objects[i].y + objects[i].texture.height; 258 | 259 | // top left 260 | vertices[i * 12 + 10] = objects[i].x; 261 | vertices[i * 12 + 11] = objects[i].y; 262 | } 263 | 264 | // uvs 265 | { 266 | // top right 267 | uvs[i * 12] = objects[i].texture.u2; 268 | uvs[i * 12 + 1] = objects[i].texture.v2; 269 | 270 | // bottom right 271 | uvs[i * 12 + 2] = objects[i].texture.u2; 272 | uvs[i * 12 + 3] = objects[i].texture.v1; 273 | 274 | // top left 275 | uvs[i * 12 + 4] = objects[i].texture.u1; 276 | uvs[i * 12 + 5] = objects[i].texture.v2; 277 | 278 | // bottom right 279 | uvs[i * 12 + 6] = objects[i].texture.u2; 280 | uvs[i * 12 + 7] = objects[i].texture.v1; 281 | 282 | // bottom left 283 | uvs[i * 12 + 8] = objects[i].texture.u1; 284 | uvs[i * 12 + 9] = objects[i].texture.v1; 285 | 286 | // top left 287 | uvs[i * 12 + 10] = objects[i].texture.u1; 288 | uvs[i * 12 + 11] = objects[i].texture.v2; 289 | } 290 | } 291 | 292 | // initialize OpenGL buffers 293 | glGenVertexArrays(1, &vao); 294 | glGenBuffers(1, &vbo); 295 | glGenBuffers(1, &ubo); 296 | glBindVertexArray(vao); 297 | 298 | glBindBuffer(GL_ARRAY_BUFFER, vbo); 299 | glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_DYNAMIC_DRAW); 300 | glVertexAttribPointer(0, 2, GL_SHORT, GL_FALSE, 2 * sizeof(int16), 0); 301 | 302 | glBindBuffer(GL_ARRAY_BUFFER, ubo); 303 | glBufferData(GL_ARRAY_BUFFER, sizeof(uvs), uvs, GL_STATIC_DRAW); 304 | glVertexAttribPointer(1, 2, GL_FLOAT, GL_TRUE, 2 * sizeof(GLfloat), 0); 305 | 306 | glEnableVertexAttribArray(0); 307 | glEnableVertexAttribArray(1); 308 | 309 | glBindBuffer(GL_ARRAY_BUFFER, 0); 310 | glBindVertexArray(0); 311 | 312 | // those usually go to render(), but this example uses just one shader, vao 313 | // and texture so it is enough to set them once 314 | glUseProgram(shaderProgramId); 315 | glActiveTexture(GL_TEXTURE0); 316 | glBindTexture(GL_TEXTURE_2D, textureId); 317 | glBindVertexArray(vao); 318 | 319 | // main loop 320 | double t = 0.0; 321 | const double dt = 0.01; 322 | 323 | double currentTime = glfwGetTime(); 324 | double lastPrinted = currentTime; 325 | double accumulator = 0.0; 326 | 327 | while (!glfwWindowShouldClose(window)) 328 | { 329 | double newTime = glfwGetTime(); 330 | double frameTime = newTime - currentTime; 331 | currentTime = newTime; 332 | 333 | // print frame time (if you want to get rid of that remember to also 334 | // delete lastPrinted variable above 335 | if (currentTime - lastPrinted > 1.0) 336 | { 337 | printf("%fms\n", frameTime * 1000.0); 338 | lastPrinted = currentTime; 339 | } 340 | 341 | accumulator += frameTime; 342 | 343 | while (accumulator >= dt) 344 | { 345 | glfwPollEvents(); 346 | update(); 347 | accumulator -= dt; 348 | t += dt; 349 | } 350 | 351 | render(); 352 | } 353 | 354 | // cleanup 355 | glDeleteVertexArrays(1, &vao); 356 | glDeleteBuffers(1, &vbo); 357 | glDeleteBuffers(1, &ubo); 358 | glDeleteTextures(1, &textureId); 359 | glDeleteProgram(shaderProgramId); 360 | 361 | glfwTerminate(); 362 | return 0; 363 | } 364 | 365 | void key_callback( 366 | GLFWwindow* window, 367 | int key, 368 | int32 scancode, 369 | int32 action, 370 | int32 mode) 371 | { 372 | if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) 373 | glfwSetWindowShouldClose(window, GL_TRUE); 374 | } 375 | 376 | GLuint getShaderProgramId(const char *vertexFile, const char *fragmentFile) 377 | { 378 | GLuint programId, vertexHandler, fragmentHandler; 379 | 380 | vertexHandler = compileShader(vertexFile, GL_VERTEX_SHADER); 381 | fragmentHandler = compileShader(fragmentFile, GL_FRAGMENT_SHADER); 382 | 383 | programId = glCreateProgram(); 384 | glAttachShader(programId, vertexHandler); 385 | glAttachShader(programId, fragmentHandler); 386 | glLinkProgram(programId); 387 | 388 | GLint success; 389 | GLchar infoLog[512]; 390 | glGetProgramiv(programId, GL_LINK_STATUS, &success); 391 | if (!success) 392 | { 393 | glGetProgramInfoLog(programId, 512, 0, infoLog); 394 | printf("Error in linking of shaders:\n%s\n", infoLog); 395 | exit(1); 396 | } 397 | 398 | glDeleteShader(vertexHandler); 399 | glDeleteShader(fragmentHandler); 400 | 401 | return programId; 402 | } 403 | 404 | GLuint compileShader(const GLchar *source, GLuint shaderType) 405 | { 406 | GLuint shaderHandler; 407 | 408 | shaderHandler = glCreateShader(shaderType); 409 | glShaderSource(shaderHandler, 1, &source, 0); 410 | glCompileShader(shaderHandler); 411 | 412 | GLint success; 413 | GLchar infoLog[512]; 414 | glGetShaderiv(shaderHandler, GL_COMPILE_STATUS, &success); 415 | if (!success) 416 | { 417 | glGetShaderInfoLog(shaderHandler, 512, 0, infoLog); 418 | printf("Error in compilation of shader:\n%s\n", infoLog); 419 | exit(1); 420 | }; 421 | 422 | return shaderHandler; 423 | } 424 | -------------------------------------------------------------------------------- /screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tchayen/2d-opengl-rendering/3645aae0b45aef1fea9699879d73bd864baf82dc/screenshot1.png -------------------------------------------------------------------------------- /screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tchayen/2d-opengl-rendering/3645aae0b45aef1fea9699879d73bd864baf82dc/screenshot2.png -------------------------------------------------------------------------------- /texture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tchayen/2d-opengl-rendering/3645aae0b45aef1fea9699879d73bd864baf82dc/texture.png --------------------------------------------------------------------------------