Add tinyktx.
Add workaround for OpenGL3 context creation in `basic` example.
diff --git a/LICENSE.tinyktx b/LICENSE.tinyktx
new file mode 100644
index 0000000..56a715e
--- /dev/null
+++ b/LICENSE.tinyktx
@@ -0,0 +1,25 @@
+BSD 2-Clause License
+
+Copyright (c) 2019, DeanoC
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
index 3d5dc79..f2f9e1e 100644
--- a/README.md
+++ b/README.md
@@ -7,6 +7,7 @@
 
 ## Status
 
+ - v2.3.0 release(Support loading KTX image through tiny_ktx)
  - v2.2.0 release(Support loading 16bit PNG. Sparse accessor support)
  - v2.1.0 release(Draco support)
  - v2.0.0 release(22 Aug, 2018)!
@@ -52,7 +53,8 @@
   * [x] Image load
   * [x] Image save
 * Extensions
-  * [x] Draco mesh decoding
+  * [x] Draco mesh decoding(`TINYGLTF_ENABLE_DRACO` required)
+  * [x] KTX image support(`TINYGLTF_ENABLE_KTX` required)
 
 ## Examples
 
@@ -143,6 +145,7 @@
 * `TINYGLTF_NO_INCLUDE_JSON `: Disable including `json.hpp` from within `tiny_gltf.h` because it has been already included before or you want to include it using custom path before including `tiny_gltf.h`.
 * `TINYGLTF_NO_INCLUDE_STB_IMAGE `: Disable including `stb_image.h` from within `tiny_gltf.h` because it has been already included before or you want to include it using custom path before including `tiny_gltf.h`.
 * `TINYGLTF_NO_INCLUDE_STB_IMAGE_WRITE `: Disable including `stb_image_write.h` from within `tiny_gltf.h` because it has been already included before or you want to include it using custom path before including `tiny_gltf.h`.
+* `TINYGLTF_ENABLE_KTX` : Enable loading KTX images( https://www.khronos.org/opengles/sdk/tools/KTX/ ) using `tiny_ktx.h`. Supported MIME is `image/ktx` ( https://github.com/KhronosGroup/glTF/issues/835 ). See `models/Cube-KTX` for details. Application also defined `TINYKTX_IMPLEMENTATION` in **one** .cc file.
 
 
 ### Saving gltTF 2.0 model
@@ -186,3 +189,4 @@
 * json.hpp : Licensed under the MIT License <http://opensource.org/licenses/MIT>. Copyright (c) 2013-2017 Niels Lohmann <http://nlohmann.me>.
 * stb_image : Public domain.
 * catch : Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved. Distributed under the Boost Software License, Version 1.0.
+* tiny_ktx : Copyright (c) 2019, DeanoC. Licensed under 2 clause BSD license.
diff --git a/examples/basic/main.cpp b/examples/basic/main.cpp
index 6a4c545..03fb67c 100644
--- a/examples/basic/main.cpp
+++ b/examples/basic/main.cpp
@@ -1,365 +1,397 @@
-#include <fstream>

-#include <iostream>

-

-#include <GL/glew.h>

-#include <GLFW/glfw3.h>

-#include <glm/gtc/matrix_transform.hpp>

-

-#include "shaders.h"

-#include "window.h"

-

-#define TINYGLTF_IMPLEMENTATION

-#define STB_IMAGE_IMPLEMENTATION

-#define STB_IMAGE_WRITE_IMPLEMENTATION

-#define TINYGLTF_NOEXCEPTION

-#define JSON_NOEXCEPTION

-#include "../../tiny_gltf.h"

-

-#define BUFFER_OFFSET(i) ((char *)NULL + (i))

-

-bool loadModel(tinygltf::Model &model, const char *filename) {

-  tinygltf::TinyGLTF loader;

-  std::string err;

-  std::string warn;

-

-  bool res = loader.LoadASCIIFromFile(&model, &err, &warn, filename);

-  if (!warn.empty()) {

-    std::cout << "WARN: " << warn << std::endl;

-  }

-

-  if (!err.empty()) {

-    std::cout << "ERR: " << err << std::endl;

-  }

-

-  if (!res)

-    std::cout << "Failed to load glTF: " << filename << std::endl;

-  else

-    std::cout << "Loaded glTF: " << filename << std::endl;

-

-  return res;

-}

-

-std::map<int, GLuint> bindMesh(std::map<int, GLuint> vbos,

-                               tinygltf::Model &model, tinygltf::Mesh &mesh) {

-  for (size_t i = 0; i < model.bufferViews.size(); ++i) {

-    const tinygltf::BufferView &bufferView = model.bufferViews[i];

-    if (bufferView.target == 0) {  // TODO impl drawarrays

-      std::cout << "WARN: bufferView.target is zero" << std::endl;

-      continue;  // Unsupported bufferView.

-                 /*

-                   From spec2.0 readme:

-                   https://github.com/KhronosGroup/glTF/tree/master/specification/2.0

-                            ... drawArrays function should be used with a count equal to

-                   the count            property of any of the accessors referenced by the

-                   attributes            property            (they are all equal for a given

-                   primitive).

-                 */

-    }

-

-    tinygltf::Buffer buffer = model.buffers[bufferView.buffer];

-    std::cout << "bufferview.target " << bufferView.target << std::endl;

-

-    GLuint vbo;

-    glGenBuffers(1, &vbo);

-    vbos[i] = vbo;

-    glBindBuffer(bufferView.target, vbo);

-

-    std::cout << "buffer.data.size = " << buffer.data.size()

-              << ", bufferview.byteOffset = " << bufferView.byteOffset

-              << std::endl;

-

-    glBufferData(bufferView.target, bufferView.byteLength,

-                 &buffer.data.at(0) + bufferView.byteOffset, GL_STATIC_DRAW);

-  }

-

-  for (size_t i = 0; i < mesh.primitives.size(); ++i) {

-    tinygltf::Primitive primitive = mesh.primitives[i];

-    tinygltf::Accessor indexAccessor = model.accessors[primitive.indices];

-

-    for (auto &attrib : primitive.attributes) {

-      tinygltf::Accessor accessor = model.accessors[attrib.second];

-      int byteStride =

-          accessor.ByteStride(model.bufferViews[accessor.bufferView]);

-      glBindBuffer(GL_ARRAY_BUFFER, vbos[accessor.bufferView]);

-

-      int size = 1;

-      if (accessor.type != TINYGLTF_TYPE_SCALAR) {

-        size = accessor.type;

-      }

-

-      int vaa = -1;

-      if (attrib.first.compare("POSITION") == 0) vaa = 0;

-      if (attrib.first.compare("NORMAL") == 0) vaa = 1;

-      if (attrib.first.compare("TEXCOORD_0") == 0) vaa = 2;

-      if (vaa > -1) {

-        glEnableVertexAttribArray(vaa);

-        glVertexAttribPointer(vaa, size, accessor.componentType,

-                              accessor.normalized ? GL_TRUE : GL_FALSE,

-                              byteStride, BUFFER_OFFSET(accessor.byteOffset));

-      } else

-        std::cout << "vaa missing: " << attrib.first << std::endl;

-    }

-

-    GLuint texid;

-    glGenTextures(1, &texid);

-

-    tinygltf::Texture &tex = model.textures[0];

-    tinygltf::Image &image = model.images[tex.source];

-

-    glBindTexture(GL_TEXTURE_2D, texid);

-    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

-    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

-    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);

-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

-

-    GLenum format = GL_RGBA;

-

-    if (image.component == 1) {

-      format = GL_RED;

-    } else if (image.component == 2) {

-      format = GL_RG;

-    } else if (image.component == 3) {

-      format = GL_RGB;

-    } else {

-      // ???

-    }

-

-    GLenum type = GL_UNSIGNED_BYTE;

-    if (image.bits == 8) {

-      // ok

-    } else if (image.bits == 16) {

-      type = GL_UNSIGNED_SHORT;

-    } else {

-      // ???

-    }

-

-    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width, image.height, 0,

-                 format, type, &image.image.at(0));

-  }

-

-  return vbos;

-}

-

-// bind models

-void bindModelNodes(std::map<int, GLuint> vbos, tinygltf::Model &model,

-                    tinygltf::Node &node) {

-  bindMesh(vbos, model, model.meshes[node.mesh]);

-  for (size_t i = 0; i < node.children.size(); i++) {

-    bindModelNodes(vbos, model, model.nodes[node.children[i]]);

-  }

-}

-GLuint bindModel(tinygltf::Model &model) {

-  std::map<int, GLuint> vbos;

-  GLuint vao;

-  glGenVertexArrays(1, &vao);

-  glBindVertexArray(vao);

-

-  const tinygltf::Scene &scene = model.scenes[model.defaultScene];

-  for (size_t i = 0; i < scene.nodes.size(); ++i) {

-    bindModelNodes(vbos, model, model.nodes[scene.nodes[i]]);

-  }

-

-  glBindVertexArray(0);

-  // cleanup vbos

-  for (size_t i = 0; i < vbos.size(); ++i) {

-    glDeleteBuffers(1, &vbos[i]);

-  }

-

-  return vao;

-}

-

-void drawMesh(tinygltf::Model &model, tinygltf::Mesh &mesh) {

-  for (size_t i = 0; i < mesh.primitives.size(); ++i) {

-    tinygltf::Primitive primitive = mesh.primitives[i];

-    tinygltf::Accessor indexAccessor = model.accessors[primitive.indices];

-

-    glDrawElements(primitive.mode, indexAccessor.count,

-                   indexAccessor.componentType,

-                   BUFFER_OFFSET(indexAccessor.byteOffset));

-  }

-}

-

-// recursively draw node and children nodes of model

-void drawModelNodes(tinygltf::Model &model, tinygltf::Node &node) {

-  drawMesh(model, model.meshes[node.mesh]);

-  for (size_t i = 0; i < node.children.size(); i++) {

-    drawModelNodes(model, model.nodes[node.children[i]]);

-  }

-}

-void drawModel(GLuint vao, tinygltf::Model &model) {

-  glBindVertexArray(vao);

-

-  const tinygltf::Scene &scene = model.scenes[model.defaultScene];

-  for (size_t i = 0; i < scene.nodes.size(); ++i) {

-    drawModelNodes(model, model.nodes[scene.nodes[i]]);

-  }

-

-  glBindVertexArray(0);

-}

-

-void dbgModel(tinygltf::Model &model) {

-  for (auto &mesh : model.meshes) {

-    std::cout << "mesh : " << mesh.name << std::endl;

-    for (auto &primitive : mesh.primitives) {

-      const tinygltf::Accessor &indexAccessor =

-          model.accessors[primitive.indices];

-

-      std::cout << "indexaccessor: count " << indexAccessor.count << ", type "

-                << indexAccessor.componentType << std::endl;

-

-      tinygltf::Material &mat = model.materials[primitive.material];

-      for (auto &mats : mat.values) {

-        std::cout << "mat : " << mats.first.c_str() << std::endl;

-      }

-

-      for (auto &image : model.images) {

-        std::cout << "image name : " << image.uri << std::endl;

-        std::cout << "  size : " << image.image.size() << std::endl;

-        std::cout << "  w/h : " << image.width << "/" << image.height

-                  << std::endl;

-      }

-

-      std::cout << "indices : " << primitive.indices << std::endl;

-      std::cout << "mode     : "

-                << "(" << primitive.mode << ")" << std::endl;

-

-      for (auto &attrib : primitive.attributes) {

-        std::cout << "attribute : " << attrib.first.c_str() << std::endl;

-      }

-    }

-  }

-}

-

-glm::mat4 genView(glm::vec3 pos, glm::vec3 lookat) {

-  // Camera matrix

-  glm::mat4 view = glm::lookAt(

-      pos,                // Camera in World Space

-      lookat,             // and looks at the origin

-      glm::vec3(0, 1, 0)  // Head is up (set to 0,-1,0 to look upside-down)

-  );

-

-  return view;

-}

-

-glm::mat4 genMVP(glm::mat4 view_mat, glm::mat4 model_mat, float fov, int w,

-                 int h) {

-  glm::mat4 Projection =

-      glm::perspective(glm::radians(fov), (float)w / (float)h, 0.01f, 1000.0f);

-

-  // Or, for an ortho camera :

-  // glm::mat4 Projection = glm::ortho(-10.0f,10.0f,-10.0f,10.0f,0.0f,100.0f);

-  // // In world coordinates

-

-  glm::mat4 mvp = Projection * view_mat * model_mat;

-

-  return mvp;

-}

-

-void displayLoop(Window &window, const std::string &filename) {

-  Shaders shader = Shaders();

-  glUseProgram(shader.pid);

-

-  // grab uniforms to modify

-  GLuint MVP_u = glGetUniformLocation(shader.pid, "MVP");

-  GLuint sun_position_u = glGetUniformLocation(shader.pid, "sun_position");

-  GLuint sun_color_u = glGetUniformLocation(shader.pid, "sun_color");

-

-  tinygltf::Model model;

-  if (!loadModel(model, filename.c_str())) return;

-

-  GLuint vao = bindModel(model);

-  // dbgModel(model); return;

-

-  // Model matrix : an identity matrix (model will be at the origin)

-  glm::mat4 model_mat = glm::mat4(1.0f);

-  glm::mat4 model_rot = glm::mat4(1.0f);

-  glm::vec3 model_pos = glm::vec3(-3, 0, -3);

-

-  // generate a camera view, based on eye-position and lookAt world-position

-  glm::mat4 view_mat = genView(glm::vec3(2, 2, 20), model_pos);

-

-  glm::vec3 sun_position = glm::vec3(3.0, 10.0, -5.0);

-  glm::vec3 sun_color = glm::vec3(1.0);

-

-  while (!window.Close()) {

-    window.Resize();

-

-    glClearColor(0.2, 0.2, 0.2, 1.0);

-    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

-

-    glm::mat4 trans =

-        glm::translate(glm::mat4(1.0f), model_pos);  // reposition model

-    model_rot = glm::rotate(model_rot, glm::radians(0.8f),

-                            glm::vec3(0, 1, 0));  // rotate model on y axis

-    model_mat = trans * model_rot;

-

-    // build a model-view-projection

-    GLint w, h;

-    glfwGetWindowSize(window.window, &w, &h);

-    glm::mat4 mvp = genMVP(view_mat, model_mat, 45.0f, w, h);

-    glUniformMatrix4fv(MVP_u, 1, GL_FALSE, &mvp[0][0]);

-

-    glUniform3fv(sun_position_u, 1, &sun_position[0]);

-    glUniform3fv(sun_color_u, 1, &sun_color[0]);

-

-    drawModel(vao, model);

-    glfwSwapBuffers(window.window);

-    glfwPollEvents();

-  }

-}

-

-static void error_callback(int error, const char *description) {

-  (void)error;

-  fprintf(stderr, "Error: %s\n", description);

-}

-

-int main(int argc, char **argv) {

-  std::string filename = "../../models/Cube/Cube.gltf";

-

-  if (argc > 1) {

-    filename = argv[1];

-  }

-

-  glfwSetErrorCallback(error_callback);

-

-  if (!glfwInit()) return -1;

-

-  // Force create OpenGL 3.3

-  // NOTE(syoyo): Linux + NVIDIA driver segfaults for some reason? commenting out glfwWindowHint will work.

-  glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);

-  glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);

-  glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

-#ifdef __APPLE__

-  glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);

-#endif

-#endif

-

-  Window window = Window(800, 600, "TinyGLTF basic example");

-  glfwMakeContextCurrent(window.window);

-

-#ifdef __APPLE__

-  // https://stackoverflow.com/questions/50192625/openggl-segmentation-fault

-  glewExperimental = GL_TRUE;

-#endif

-

-  glewInit();

-  std::cout << glGetString(GL_RENDERER) << ", " << glGetString(GL_VERSION)

-            << std::endl;

-

-  if (!GLEW_VERSION_3_3) {

-    std::cerr << "OpenGL 3.3 is required to execute this app." << std::endl;

-    return EXIT_FAILURE;

-  }

-

-  glEnable(GL_DEPTH_TEST);

-  glDepthFunc(GL_LESS);

-

-  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

-  glEnable(GL_BLEND);

-

-  displayLoop(window, filename);

-

-  glfwTerminate();

-  return 0;

-}

+#include <fstream>
+#include <iostream>
+
+#include <GL/glew.h>
+#include <GLFW/glfw3.h>
+#include <glm/gtc/matrix_transform.hpp>
+
+#include "shaders.h"
+#include "window.h"
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Weverything"
+#endif
+
+// Inlude tinyktx.h before tiny_gltf.h
+#define TINYKTX_IMPLEMENTATION
+#include "../../tinyktx.h"
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+#define TINYGLTF_IMPLEMENTATION
+#define STB_IMAGE_IMPLEMENTATION
+#define STB_IMAGE_WRITE_IMPLEMENTATION
+#define TINYGLTF_NOEXCEPTION
+#define JSON_NOEXCEPTION
+#define TINYGLTF_ENABLE_KTX
+#define TINYGLTF_NO_INCLUDE_TINY_KTX
+#include "../../tiny_gltf.h"
+
+//#define BUFFER_OFFSET(i) ((char *)nullptr + (i))
+#define BUFFER_OFFSET(i) \
+  (reinterpret_cast<void *>(i))  // TODO(syoyo): Is this right way?
+
+bool loadModel(tinygltf::Model &model, const char *filename) {
+  tinygltf::TinyGLTF loader;
+  std::string err;
+  std::string warn;
+
+  bool res = loader.LoadASCIIFromFile(&model, &err, &warn, filename);
+  if (!warn.empty()) {
+    std::cout << "WARN: " << warn << std::endl;
+  }
+
+  if (!err.empty()) {
+    std::cout << "ERR: " << err << std::endl;
+  }
+
+  if (!res)
+    std::cout << "Failed to load glTF: " << filename << std::endl;
+  else
+    std::cout << "Loaded glTF: " << filename << std::endl;
+
+  return res;
+}
+
+std::map<int, GLuint> bindMesh(std::map<int, GLuint> vbos,
+                               tinygltf::Model &model, tinygltf::Mesh &mesh) {
+  for (size_t i = 0; i < model.bufferViews.size(); ++i) {
+    const tinygltf::BufferView &bufferView = model.bufferViews[i];
+    if (bufferView.target == 0) {  // TODO impl drawarrays
+      std::cout << "WARN: bufferView.target is zero" << std::endl;
+      continue;  // Unsupported bufferView.
+                 /*
+                   From spec2.0 readme:
+                   https://github.com/KhronosGroup/glTF/tree/master/specification/2.0
+                            ... drawArrays function should be used with a count equal to
+                   the count            property of any of the accessors referenced by the
+                   attributes            property            (they are all equal for a
+                   given            primitive).
+                 */
+    }
+
+    tinygltf::Buffer buffer = model.buffers[bufferView.buffer];
+    std::cout << "bufferview.target " << bufferView.target << std::endl;
+
+    GLuint vbo;
+    glGenBuffers(1, &vbo);
+    vbos[i] = vbo;
+    glBindBuffer(bufferView.target, vbo);
+
+    std::cout << "buffer.data.size = " << buffer.data.size()
+              << ", bufferview.byteOffset = " << bufferView.byteOffset
+              << std::endl;
+
+    glBufferData(bufferView.target, bufferView.byteLength,
+                 &buffer.data.at(0) + bufferView.byteOffset, GL_STATIC_DRAW);
+  }
+
+  for (size_t i = 0; i < mesh.primitives.size(); ++i) {
+    tinygltf::Primitive primitive = mesh.primitives[i];
+    tinygltf::Accessor indexAccessor = model.accessors[primitive.indices];
+
+    for (auto &attrib : primitive.attributes) {
+      tinygltf::Accessor accessor = model.accessors[attrib.second];
+      int byteStride =
+          accessor.ByteStride(model.bufferViews[accessor.bufferView]);
+      glBindBuffer(GL_ARRAY_BUFFER, vbos[accessor.bufferView]);
+
+      int size = 1;
+      if (accessor.type != TINYGLTF_TYPE_SCALAR) {
+        size = accessor.type;
+      }
+
+      int vaa = -1;
+      if (attrib.first.compare("POSITION") == 0) vaa = 0;
+      if (attrib.first.compare("NORMAL") == 0) vaa = 1;
+      if (attrib.first.compare("TEXCOORD_0") == 0) vaa = 2;
+      if (vaa > -1) {
+        glEnableVertexAttribArray(vaa);
+        glVertexAttribPointer(vaa, size, accessor.componentType,
+                              accessor.normalized ? GL_TRUE : GL_FALSE,
+                              byteStride, BUFFER_OFFSET(accessor.byteOffset));
+      } else
+        std::cout << "vaa missing: " << attrib.first << std::endl;
+    }
+
+    GLuint texid;
+    glGenTextures(1, &texid);
+
+    tinygltf::Texture &tex = model.textures[0];
+    tinygltf::Image &image = model.images[tex.source];
+
+    glBindTexture(GL_TEXTURE_2D, texid);
+    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+
+    GLenum format = GL_RGBA;
+
+    if (image.component == 1) {
+      format = GL_RED;
+    } else if (image.component == 2) {
+      format = GL_RG;
+    } else if (image.component == 3) {
+      format = GL_RGB;
+    } else {
+      // ???
+    }
+
+    GLenum type = GL_UNSIGNED_BYTE;
+    if (image.bits == 8) {
+      // ok
+    } else if (image.bits == 16) {
+      type = GL_UNSIGNED_SHORT;
+    } else {
+      // ???
+    }
+
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width, image.height, 0,
+                 format, type, &image.image.at(0));
+  }
+
+  return vbos;
+}
+
+// bind models
+void bindModelNodes(std::map<int, GLuint> vbos, tinygltf::Model &model,
+                    tinygltf::Node &node) {
+  bindMesh(vbos, model, model.meshes[node.mesh]);
+  for (size_t i = 0; i < node.children.size(); i++) {
+    bindModelNodes(vbos, model, model.nodes[node.children[i]]);
+  }
+}
+GLuint bindModel(tinygltf::Model &model) {
+  std::map<int, GLuint> vbos;
+  GLuint vao;
+  glGenVertexArrays(1, &vao);
+  glBindVertexArray(vao);
+
+  const tinygltf::Scene &scene = model.scenes[model.defaultScene];
+  for (size_t i = 0; i < scene.nodes.size(); ++i) {
+    bindModelNodes(vbos, model, model.nodes[scene.nodes[i]]);
+  }
+
+  glBindVertexArray(0);
+  // cleanup vbos
+  for (size_t i = 0; i < vbos.size(); ++i) {
+    glDeleteBuffers(1, &vbos[i]);
+  }
+
+  return vao;
+}
+
+void drawMesh(tinygltf::Model &model, tinygltf::Mesh &mesh) {
+  for (size_t i = 0; i < mesh.primitives.size(); ++i) {
+    tinygltf::Primitive primitive = mesh.primitives[i];
+    tinygltf::Accessor indexAccessor = model.accessors[primitive.indices];
+
+    glDrawElements(primitive.mode, indexAccessor.count,
+                   indexAccessor.componentType,
+                   BUFFER_OFFSET(indexAccessor.byteOffset));
+  }
+}
+
+// recursively draw node and children nodes of model
+void drawModelNodes(tinygltf::Model &model, tinygltf::Node &node) {
+  drawMesh(model, model.meshes[node.mesh]);
+  for (size_t i = 0; i < node.children.size(); i++) {
+    drawModelNodes(model, model.nodes[node.children[i]]);
+  }
+}
+void drawModel(GLuint vao, tinygltf::Model &model) {
+  glBindVertexArray(vao);
+
+  const tinygltf::Scene &scene = model.scenes[model.defaultScene];
+  for (size_t i = 0; i < scene.nodes.size(); ++i) {
+    drawModelNodes(model, model.nodes[scene.nodes[i]]);
+  }
+
+  glBindVertexArray(0);
+}
+
+void dbgModel(tinygltf::Model &model) {
+  for (auto &mesh : model.meshes) {
+    std::cout << "mesh : " << mesh.name << std::endl;
+    for (auto &primitive : mesh.primitives) {
+      const tinygltf::Accessor &indexAccessor =
+          model.accessors[primitive.indices];
+
+      std::cout << "indexaccessor: count " << indexAccessor.count << ", type "
+                << indexAccessor.componentType << std::endl;
+
+      tinygltf::Material &mat = model.materials[primitive.material];
+      for (auto &mats : mat.values) {
+        std::cout << "mat : " << mats.first.c_str() << std::endl;
+      }
+
+      for (auto &image : model.images) {
+        std::cout << "image name : " << image.uri << std::endl;
+        std::cout << "  size : " << image.image.size() << std::endl;
+        std::cout << "  w/h : " << image.width << "/" << image.height
+                  << std::endl;
+      }
+
+      std::cout << "indices : " << primitive.indices << std::endl;
+      std::cout << "mode     : "
+                << "(" << primitive.mode << ")" << std::endl;
+
+      for (auto &attrib : primitive.attributes) {
+        std::cout << "attribute : " << attrib.first.c_str() << std::endl;
+      }
+    }
+  }
+}
+
+glm::mat4 genView(glm::vec3 pos, glm::vec3 lookat) {
+  // Camera matrix
+  glm::mat4 view = glm::lookAt(
+      pos,                // Camera in World Space
+      lookat,             // and looks at the origin
+      glm::vec3(0, 1, 0)  // Head is up (set to 0,-1,0 to look upside-down)
+  );
+
+  return view;
+}
+
+glm::mat4 genMVP(glm::mat4 view_mat, glm::mat4 model_mat, float fov, int w,
+                 int h) {
+  glm::mat4 Projection =
+      glm::perspective(glm::radians(fov), (float)w / (float)h, 0.01f, 1000.0f);
+
+  // Or, for an ortho camera :
+  // glm::mat4 Projection = glm::ortho(-10.0f,10.0f,-10.0f,10.0f,0.0f,100.0f);
+  // // In world coordinates
+
+  glm::mat4 mvp = Projection * view_mat * model_mat;
+
+  return mvp;
+}
+
+void displayLoop(Window &window, const std::string &filename) {
+  Shaders shader = Shaders();
+  glUseProgram(shader.pid);
+
+  // grab uniforms to modify
+  GLuint MVP_u = glGetUniformLocation(shader.pid, "MVP");
+  GLuint sun_position_u = glGetUniformLocation(shader.pid, "sun_position");
+  GLuint sun_color_u = glGetUniformLocation(shader.pid, "sun_color");
+
+  tinygltf::Model model;
+  if (!loadModel(model, filename.c_str())) return;
+
+  GLuint vao = bindModel(model);
+  // dbgModel(model); return;
+
+  // Model matrix : an identity matrix (model will be at the origin)
+  glm::mat4 model_mat = glm::mat4(1.0f);
+  glm::mat4 model_rot = glm::mat4(1.0f);
+  glm::vec3 model_pos = glm::vec3(-3, 0, -3);
+
+  // generate a camera view, based on eye-position and lookAt world-position
+  glm::mat4 view_mat = genView(glm::vec3(2, 2, 20), model_pos);
+
+  glm::vec3 sun_position = glm::vec3(3.0, 10.0, -5.0);
+  glm::vec3 sun_color = glm::vec3(1.0);
+
+  while (!window.Close()) {
+    window.Resize();
+
+    glClearColor(0.2, 0.2, 0.2, 1.0);
+    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+    glm::mat4 trans =
+        glm::translate(glm::mat4(1.0f), model_pos);  // reposition model
+    model_rot = glm::rotate(model_rot, glm::radians(0.8f),
+                            glm::vec3(0, 1, 0));  // rotate model on y axis
+    model_mat = trans * model_rot;
+
+    // build a model-view-projection
+    GLint w, h;
+    glfwGetWindowSize(window.window, &w, &h);
+    glm::mat4 mvp = genMVP(view_mat, model_mat, 45.0f, w, h);
+    glUniformMatrix4fv(MVP_u, 1, GL_FALSE, &mvp[0][0]);
+
+    glUniform3fv(sun_position_u, 1, &sun_position[0]);
+    glUniform3fv(sun_color_u, 1, &sun_color[0]);
+
+    drawModel(vao, model);
+    glfwSwapBuffers(window.window);
+    glfwPollEvents();
+  }
+}
+
+static void error_callback(int error, const char *description) {
+  (void)error;
+  fprintf(stderr, "Error: %s\n", description);
+}
+
+int main(int argc, char **argv) {
+  std::string filename = "../../models/Cube/Cube.gltf";
+
+  if (argc > 1) {
+    filename = argv[1];
+  }
+
+  glfwSetErrorCallback(error_callback);
+
+  if (!glfwInit()) return -1;
+
+    // NOTE(syoyo): For some reason, Linux + NVIDIA driver + apt-installed
+    // glew(1.13) cannot initialize some ARB functions when CONTEXT_VERSION are
+    // explicitly given. Proably we need to compile app with recent glfw and
+    // glew(or use glad) package
+#if !defined(__linux__)
+  // Try to create OpenGL 3.3 context on Windows and macOS
+  glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
+  glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
+#endif
+
+  // glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
+#ifdef __APPLE__
+  glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
+  glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
+#endif
+
+  Window window = Window(800, 600, "TinyGLTF basic example");
+  glfwMakeContextCurrent(window.window);
+
+#ifdef __APPLE__
+  // https://stackoverflow.com/questions/50192625/openggl-segmentation-fault
+  glewExperimental = GL_TRUE;
+#endif
+
+  if (glewInit() != GLEW_OK) {
+    std::cerr << "Failed to initialie glew." << std::endl;
+    return EXIT_FAILURE;
+  }
+
+  std::cout << glGetString(GL_RENDERER) << ", " << glGetString(GL_VERSION)
+            << std::endl;
+
+  if (!GLEW_ARB_vertex_array_object) {
+    std::cerr << "GLEW_ARB_vertex_array_object was not available." << std::endl;
+    return EXIT_FAILURE;
+  }
+
+  if (!GLEW_VERSION_3_3) {
+    std::cerr << "OpenGL 3.3 is required to execute this app." << std::endl;
+    return EXIT_FAILURE;
+  }
+
+  glEnable(GL_DEPTH_TEST);
+  glDepthFunc(GL_LESS);
+
+  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+  glEnable(GL_BLEND);
+
+  displayLoop(window, filename);
+
+  glfwTerminate();
+  return 0;
+}