فهرست منبع

Merge branch 'manager_init_virtual' into test_catchup

* manager_init_virtual:
  Cleanup dead code.
  Move texture initialization into correct places.
  Migrate "material uniform id" into the driver-impl.
  Migrate the activation of materials into renderer. TODO: Have manager/renderer code take ownership of certain characteristics such as uniform_id, and consider caching the outcomes of glUniformLocation() call(s).
  Step 3: Convert shader and shader_program to use the new manager::compile function instead of the graphics::shaders::init free function. (1/2)
  Step 2: Extract opengl_manager and opengl_renderer to a private header file.
  Sprint: Extract manager "create texture/shader/program" code. Step 1: Create interface and OpenGL override class.
  Re-arrange functions in graphics::manager. Rename manager_impl to manager_cache. Add comments to graphics::manager.
Sam Jaffe 6 سال پیش
والد
کامیت
67498ba5ec

+ 12 - 8
graphics/graphics.xcodeproj/project.pbxproj

@@ -16,17 +16,18 @@
 		CD3AC7191D2C0950002B4BB0 /* shader_program.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CD3AC7171D2C0950002B4BB0 /* shader_program.cpp */; };
 		CD3AC7261D2C0C63002B4BB0 /* object.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CD3AC7241D2C0C63002B4BB0 /* object.cpp */; settings = {COMPILER_FLAGS = "-fvisibility=default"; }; };
 		CD62FCF72290DC9000376440 /* helper.hpp in Headers */ = {isa = PBXBuildFile; fileRef = CD62FCF52290DC9000376440 /* helper.hpp */; };
-		CD62FCF82290DC9000376440 /* helper.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD62FCF62290DC9000376440 /* helper.cxx */; };
+		CD62FCF82290DC9000376440 /* opengl_manager.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD62FCF62290DC9000376440 /* opengl_manager.cxx */; };
 		CD62FCFA2290E2E500376440 /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CD62FCF92290E2E500376440 /* OpenGL.framework */; };
 		CD62FD062291970F00376440 /* libgameutils.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = CD62FD052291970F00376440 /* libgameutils.dylib */; };
 		CD62FD1E2292412900376440 /* renderer.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD62FD1C2292412900376440 /* renderer.cxx */; settings = {COMPILER_FLAGS = "-fvisibility=default"; }; };
 		CD62FD222292C76B00376440 /* renderer_impl.hpp in Headers */ = {isa = PBXBuildFile; fileRef = CD62FD202292C76B00376440 /* renderer_impl.hpp */; };
-		CD62FD232292C76B00376440 /* renderer_impl.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD62FD212292C76B00376440 /* renderer_impl.cxx */; };
+		CD62FD232292C76B00376440 /* opengl_renderer.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD62FD212292C76B00376440 /* opengl_renderer.cxx */; };
 		CDA34D9A22517A3D008036A7 /* libmath.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = CDA34D9922517A3D008036A7 /* libmath.dylib */; };
 		CDED9C1922A2D6CE00AE5CE5 /* libgraphics.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = CD3AC6E21D2C0364002B4BB0 /* libgraphics.dylib */; };
 		CDED9C4322A2FACB00AE5CE5 /* renderer_test.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CDED9C4222A2FACB00AE5CE5 /* renderer_test.cxx */; };
 		CDED9C4622A2FCA100AE5CE5 /* GoogleMock.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CD62FCDF22904AD100376440 /* GoogleMock.framework */; };
 		CDED9C4722A308AE00AE5CE5 /* libmath.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = CDA34D9922517A3D008036A7 /* libmath.dylib */; };
+		CDED9C5422A465DB00AE5CE5 /* opengl_renderer.h in Headers */ = {isa = PBXBuildFile; fileRef = CDED9C5322A465DB00AE5CE5 /* opengl_renderer.h */; settings = {ATTRIBUTES = (Private, ); }; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -87,17 +88,18 @@
 		CD3AC7241D2C0C63002B4BB0 /* object.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = object.cpp; sourceTree = "<group>"; };
 		CD62FCD722904AD100376440 /* GoogleMock.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = GoogleMock.xcodeproj; path = "../../gmock-xcode-master/GoogleMock.xcodeproj"; sourceTree = "<group>"; };
 		CD62FCF52290DC9000376440 /* helper.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = helper.hpp; sourceTree = "<group>"; };
-		CD62FCF62290DC9000376440 /* helper.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = helper.cxx; sourceTree = "<group>"; };
+		CD62FCF62290DC9000376440 /* opengl_manager.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = opengl_manager.cxx; sourceTree = "<group>"; };
 		CD62FCF92290E2E500376440 /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; };
 		CD62FD052291970F00376440 /* libgameutils.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; path = libgameutils.dylib; sourceTree = BUILT_PRODUCTS_DIR; };
 		CD62FD1C2292412900376440 /* renderer.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = renderer.cxx; sourceTree = "<group>"; };
 		CD62FD202292C76B00376440 /* renderer_impl.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = renderer_impl.hpp; sourceTree = "<group>"; };
-		CD62FD212292C76B00376440 /* renderer_impl.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = renderer_impl.cxx; sourceTree = "<group>"; };
+		CD62FD212292C76B00376440 /* opengl_renderer.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = opengl_renderer.cxx; sourceTree = "<group>"; };
 		CDA34D86225171AA008036A7 /* game */ = {isa = PBXFileReference; lastKnownFileType = folder; name = game; path = include/game; sourceTree = "<group>"; };
 		CDA34D9922517A3D008036A7 /* libmath.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; path = libmath.dylib; sourceTree = BUILT_PRODUCTS_DIR; };
 		CDED9C1422A2D6CD00AE5CE5 /* graphics-test.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "graphics-test.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
 		CDED9C1822A2D6CE00AE5CE5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		CDED9C4222A2FACB00AE5CE5 /* renderer_test.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = renderer_test.cxx; sourceTree = "<group>"; };
+		CDED9C5322A465DB00AE5CE5 /* opengl_renderer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = opengl_renderer.h; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -127,8 +129,9 @@
 		CD1C84052299B72C00825C4E /* openGL */ = {
 			isa = PBXGroup;
 			children = (
-				CD62FD212292C76B00376440 /* renderer_impl.cxx */,
-				CD62FCF62290DC9000376440 /* helper.cxx */,
+				CDED9C5322A465DB00AE5CE5 /* opengl_renderer.h */,
+				CD62FD212292C76B00376440 /* opengl_renderer.cxx */,
+				CD62FCF62290DC9000376440 /* opengl_manager.cxx */,
 				CD1C840D2299B81500825C4E /* error_formatter.cxx */,
 			);
 			path = openGL;
@@ -236,6 +239,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				CD62FD222292C76B00376440 /* renderer_impl.hpp in Headers */,
+				CDED9C5422A465DB00AE5CE5 /* opengl_renderer.h in Headers */,
 				CD62FCF72290DC9000376440 /* helper.hpp in Headers */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
@@ -397,8 +401,8 @@
 				CD3AC6F81D2C0518002B4BB0 /* texture.cpp in Sources */,
 				CD3AC7261D2C0C63002B4BB0 /* object.cpp in Sources */,
 				CD1C83E922998E2600825C4E /* manager.cxx in Sources */,
-				CD62FCF82290DC9000376440 /* helper.cxx in Sources */,
-				CD62FD232292C76B00376440 /* renderer_impl.cxx in Sources */,
+				CD62FCF82290DC9000376440 /* opengl_manager.cxx in Sources */,
+				CD62FD232292C76B00376440 /* opengl_renderer.cxx in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};

+ 6 - 0
graphics/include/game/graphics/graphics_fwd.h

@@ -22,7 +22,13 @@ namespace graphics {
   struct shader;
   struct shader_program;
   class texture;
+  namespace materials {
+    enum class uniform : unsigned int;
+  }
   namespace shaders {
     enum class type : unsigned int;
   }
+  namespace textures {
+    enum class format : unsigned int;
+  }
 }

+ 49 - 1
graphics/include/game/graphics/manager.hpp

@@ -20,23 +20,71 @@ namespace graphics {
     manager();
     ~manager();
 
+    /**
+     * @brief Load a material - from either cache or by fetching data from the
+     * given arguments - and return its identifier.
+     * A material is a linkage of shader(s) and texture(s), that desribe the
+     * needed details to draw it on a page.
+     * @param program The shader program that should be used to paint this
+     * material.
+     * @param texture The path to an image file for this material
+     * @param uniform The name of the uniform to use as a fallback texture
+     */
     identity<material> get(identity<shader_program> program,
                            std::string const & texture,
                            std::string const & uniform) const;
+    /**
+     * @brief Load a shader - from either cache or by fetching data from the
+     * given arguments - and return its identifier.
+     * A shader is a type of program that is run on the GPU by a library like
+     * OpenGL.
+     * @param type A shader type describe what it draws...
+     * @param path The path to the shader's source code file.
+     */
     identity<shader> get(shaders::type type, std::string const & path) const;
+    /**
+     * @brief Load a shader_program - from either cache or by fetching data from
+     * the given arguments - and return its identifier.
+     * @param fragment The file path to the fragment shader code.
+     * @param vertex The file path to the vertex shader code.
+     */
     identity<shader_program> get(std::string const & fragment,
                                  std::string const & vertex) const;
+    /**
+     * @brief Load a texture - from either cache or by fetching data from the
+     * given arguments - and return its identifier.
+     * @param path The file path to an imagefile that contains one or more
+     * drawings of the object to be rendered.
+     */
     identity<texture> get(std::string const & path) const;
 
+    // TODO: This is kinda dumb...
     object create_object(identity<material> fromMaterial, math::vec2 atPosition,
                          math::vec2 frameWidth, float scale) const;
+
+    /**
+     * @brief Translate a material identity into an actual object.
+     * Used for internal linkage with the implementation code.
+     */
     material const & get(identity<material> identity) const;
+    /**
+     * @brief Translate a texture identity into an actual object.
+     * Used for internal linkage with the implementation code.
+     */
     texture const & get(identity<texture> identity) const;
 
   private:
+    void prepare_uniforms() const;
     identity<texture> texture_or_uniform(std::string const & path,
                                          std::string const & uniform) const;
 
-    std::unique_ptr<struct manager_impl> pimpl_;
+    virtual shader compile(shaders::type type,
+                           std::string const & path) const = 0;
+    virtual shader_program compile(identity<shader> fragment,
+                                   identity<shader> vertex) const = 0;
+    virtual texture compile(textures::format color, math::vec2i size,
+                            void const * buffer) const = 0;
+
+    std::unique_ptr<struct manager_cache> pcache_;
   };
 }

+ 2 - 3
graphics/include/game/graphics/material.hpp

@@ -16,15 +16,14 @@
 namespace graphics {
   struct uniform {
     identity<texture> texture;
-    int uniform_id; // TODO (sjaffe): use an enum and hide remapping?
+    materials::uniform
+        uniform_id; // TODO (sjaffe): use an enum and hide remapping?
   };
 
   struct material : public identity<material> {
     material(identity<shader_program> const & sp, math::vec2i size,
              std::vector<uniform> const & uniforms);
 
-    void activate() const;
-
     identity<shader_program> program;
     math::vec2i size;
     std::vector<uniform> uniforms;

+ 1 - 1
graphics/include/game/graphics/shader.hpp

@@ -13,7 +13,7 @@
 
 namespace graphics {
   struct shader : public identity<shader> {
-    shader(shaders::type, std::string const &);
+    shader(unsigned int id, shaders::type, std::string const &);
 
     shaders::type type;
     std::string path;

+ 1 - 1
graphics/include/game/graphics/shader_program.hpp

@@ -13,7 +13,7 @@
 
 namespace graphics {
   struct shader_program : public identity<shader_program> {
-    shader_program(identity<shader>, identity<shader>);
+    shader_program(unsigned int id, identity<shader>, identity<shader>);
 
     void activate() const;
 

+ 2 - 12
graphics/include/game/graphics/texture.hpp

@@ -12,19 +12,9 @@
 #include "vector/vector.hpp"
 
 namespace graphics {
-  class texture : public identity<texture> {
-  public:
-    texture(std::string const & str);
+  struct texture : public identity<texture> {
+    texture(unsigned int id, math::vec2i const & size);
 
-    static texture const & WHITE();
-    static texture const & DARK_YELLOW();
-    static texture const & LIGHT_BLUE();
-
-  private:
-    texture(std::pair<unsigned int, math::vec2i>);
-    texture(char const *, math::vec2i);
-
-  public:
     math::vec2i const size;
   };
 }

+ 16 - 11
graphics/src/helper.hpp

@@ -13,25 +13,24 @@
 
 #include "game/graphics/graphics_fwd.h"
 #include "game/math/math_fwd.hpp"
+#include "vector/vector.hpp"
 
 namespace graphics {
-  struct uniform;
   namespace textures {
-    enum class format { RGB, RGBA };
-    unsigned int init(format, math::vec2i, void const *);
+    enum class format : unsigned int { RGB, RGBA };
+    struct external_data {
+      external_data(std::string const & abs_path);
+      ~external_data();
+      format color;
+      math::vec2i size;
+      void * buffer;
+    };
   }
   namespace shaders {
     enum class type : unsigned int { FRAGMENT, VERTEX };
-    unsigned int init(type, std::string const &);
-    unsigned int init(identity<shader> const & fragmentShader,
-                      identity<shader> const & vertexShader);
-    void activate(identity<shader_program> id);
-    int uniform_location(identity<shader_program> id,
-                         std::string const & uniform);
   }
   namespace materials {
-    void activate(identity<shader_program> program,
-                  std::vector<uniform> const & uniforms);
+    enum class uniform : unsigned int { NORMAL, DIFFUSE, SPECULAR };
   }
 }
 
@@ -41,4 +40,10 @@ namespace std {
       return std::hash<unsigned int>()(static_cast<unsigned int>(tp));
     }
   };
+
+  template <> struct hash<graphics::materials::uniform> {
+    std::size_t operator()(graphics::materials::uniform uf) const {
+      return std::hash<unsigned int>()(static_cast<unsigned int>(uf));
+    }
+  };
 }

+ 63 - 43
graphics/src/manager.cxx

@@ -45,90 +45,70 @@ identity<T> cache<T>::emplace(key_t<T> const & key, T && value) {
   return values.emplace(key, std::forward<T>(value)).first->second;
 }
 
-struct graphics::manager_impl {
+struct graphics::manager_cache {
   cache<material> materials;
   cache<shader> shaders;
   cache<shader_program> programs;
   cache<texture> textures;
 };
 
-manager::manager() : pimpl_(new manager_impl) {}
+manager::manager() : pcache_(new manager_cache) {}
 manager::~manager() {}
 
+materials::uniform uniform_id(std::string const & uniform) {
+  if (uniform == "u_normalMap") {
+    return materials::uniform::NORMAL;
+  } else if (uniform == "u_specularMap") {
+    return materials::uniform::SPECULAR;
+  } else if (uniform == "u_diffuseMap") {
+    return materials::uniform::DIFFUSE;
+  }
+  throw;
+}
+
 identity<material> manager::get(identity<shader_program> program,
                                 std::string const & texture,
                                 std::string const & uniform) const {
   auto key = std::make_tuple(program, texture, uniform);
-  auto & cache = pimpl_->materials;
+  auto & cache = pcache_->materials;
   auto found = cache.values.find(key);
   if (found != cache.values.end()) { return found->second; }
 
   std::vector<struct uniform> uniforms;
-  uniforms.push_back({texture_or_uniform(texture, uniform),
-                      shaders::uniform_location(program, uniform)});
+  uniforms.push_back(
+      {texture_or_uniform(texture, uniform), uniform_id(uniform)});
   math::vec2i size = get(uniforms[0].texture).size;
   //  if (!uniforms.empty()) { size = ...; }
   return cache.emplace(key, material(program, size, uniforms));
 }
 
-material const & manager::get(identity<material> identity) const {
-  auto & cache = pimpl_->materials;
-  return cache.values.find(cache.keys.find(identity)->second)->second;
-}
-
-texture const & manager::get(identity<texture> identity) const {
-  auto & cache = pimpl_->textures;
-  auto it = cache.keys.find(identity);
-  if (it == cache.keys.end()) { return texture::WHITE(); }
-  return cache.values.find(it->second)->second;
-}
-
 identity<shader> manager::get(shaders::type type,
                               std::string const & path) const {
   auto key = std::make_pair(type, path);
-  auto & cache = pimpl_->shaders;
+  auto & cache = pcache_->shaders;
   auto found = cache.values.find(key);
   if (found != cache.values.end()) { return found->second; }
-  return cache.emplace(key, shader(type, path));
-}
-
-identity<texture>
-manager::texture_or_uniform(std::string const & path,
-                            std::string const & uniform) const {
-  if (!path.empty()) {
-    try {
-      return get(path);
-    } catch (std::exception const & e) {
-      // TODO: Logging
-    }
-  }
-  if (uniform == "u_normalMap") {
-    return texture::LIGHT_BLUE();
-  } else if (uniform == "u_specularMap") {
-    return texture::DARK_YELLOW();
-  } else if (uniform == "u_diffuseMap") {
-    return texture::WHITE();
-  }
-  throw;
+  return cache.emplace(key, compile(type, path));
 }
 
 identity<shader_program> manager::get(std::string const & fragment,
                                       std::string const & vertex) const {
   auto key = std::make_pair(fragment, vertex);
-  auto & cache = pimpl_->programs;
+  auto & cache = pcache_->programs;
   auto found = cache.values.find(key);
   if (found != cache.values.end()) { return found->second; }
   auto fragment_shader = get(shaders::type::FRAGMENT, fragment);
   auto vertex_shader = get(shaders::type::VERTEX, vertex);
 
-  return cache.emplace(key, shader_program(fragment_shader, vertex_shader));
+  return cache.emplace(key, compile(fragment_shader, vertex_shader));
 }
 
 identity<texture> manager::get(std::string const & path) const {
-  auto & cache = pimpl_->textures;
+  auto & cache = pcache_->textures;
   auto found = cache.values.find(path);
   if (found != cache.values.end()) { return found->second; }
-  return cache.emplace(path, texture(path));
+  textures::external_data data(path);
+  return cache.emplace(path, compile(data.color, data.size, data.buffer));
 }
 
 object manager::create_object(identity<material> fromMaterial,
@@ -138,3 +118,43 @@ object manager::create_object(identity<material> fromMaterial,
                                                (scale ? scale : 1.f)};
   return {bounds, bounds, fromMaterial, {make_vector(0.f, 0.f), frameWidth}};
 }
+
+material const & manager::get(identity<material> identity) const {
+  auto & cache = pcache_->materials;
+  return cache.values.find(cache.keys.find(identity)->second)->second;
+}
+
+texture const & manager::get(identity<texture> identity) const {
+  auto & cache = pcache_->textures;
+  auto it = cache.keys.find(identity);
+  //  if (it == cache.keys.end()) { return texture::WHITE(); }
+  return cache.values.find(it->second)->second;
+}
+
+void manager::prepare_uniforms() const {
+  // Initialize the three default uniform-textures immediately
+  auto & cache = pcache_->textures;
+  if (cache.values.count("u_normalMap") != 0) return;
+  auto RGBA = textures::format::RGBA;
+  cache.emplace("u_normalMap", compile(RGBA, {{1, 1}}, "\x80\x80\xFF\xFF"));
+  cache.emplace("u_specularMap", compile(RGBA, {{1, 1}}, "\x80\x80\x00\xFF"));
+  cache.emplace("u_diffuseMap", compile(RGBA, {{1, 1}}, "\xFF\xFF\xFF\xFF"));
+}
+
+identity<texture>
+manager::texture_or_uniform(std::string const & path,
+                            std::string const & uniform) const {
+  if (!path.empty()) {
+    try {
+      return get(path);
+    } catch (std::exception const & e) {
+      // TODO: Logging
+    }
+  }
+  prepare_uniforms();
+  // The uniform is primed into the cache already.
+  auto & cache = pcache_->textures;
+  auto found = cache.values.find(path);
+  if (found != cache.values.end()) { return found->second; }
+  throw;
+}

+ 0 - 2
graphics/src/material.cpp

@@ -24,5 +24,3 @@ material::material(identity<shader_program> const & sp, math::vec2i size,
                    std::vector<uniform> const & uniforms)
     : identity<material>(next_id()), program(sp), size(size),
       uniforms(uniforms) {}
-
-void material::activate() const { materials::activate(program, uniforms); }

+ 0 - 265
graphics/src/openGL/helper.cxx

@@ -1,265 +0,0 @@
-//
-//  helper.cxx
-//  graphics
-//
-//  Created by Sam Jaffe on 5/18/19.
-//  Copyright © 2019 Sam Jaffe. All rights reserved.
-//
-
-#include "helper.hpp"
-
-#include <iostream>
-#include <memory>
-#include <sstream>
-#ifdef __APPLE__
-#include <OpenGL/gl3.h>
-#endif
-
-#include "scope_guard/scope_guard.hpp"
-#include "vector/vector.hpp"
-
-#include "game/graphics/material.hpp"
-#include "game/graphics/shader.hpp"
-#include "game/util/env.hpp"
-#include "game/util/files.hpp"
-#include "game/util/time.hpp"
-
-namespace graphics {
-  extern void print_shader_error(GLuint, std::string const &);
-  extern void print_shader_program_error(GLuint, std::string const &,
-                                         std::string const &);
-}
-
-namespace graphics { namespace textures {
-  static int glfmt(format color_fmt) {
-    switch (color_fmt) {
-    case format::RGB:
-      return GL_RGB;
-    case format::RGBA:
-      return GL_RGBA;
-    }
-  }
-
-  unsigned int init(format color_fmt, math::vec2i dimension,
-                    void const * data) {
-    unsigned int id;
-    // Enable texturings
-    //    glEnable( GL_TEXTURE_2D );
-
-    // Tell OpenGL that our pixel data is single-byte aligned
-    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
-
-    // Ask OpenGL for an unused texName (ID number) to use for this texture
-    glGenTextures(1, &id);
-
-    // Tell OpenGL to bind (set) this as the currently active texture
-    glBindTexture(GL_TEXTURE_2D, id);
-    // Set texture clamp vs. wrap (repeat)
-
-    // one of: GL_CLAMP_TO_EDGE, GL_REPEAT, GL_MIRRORED_REPEAT,
-    // GL_MIRROR_CLAMP_TO_EDGE, ...
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
-
-    // the format our source pixel data is currently in; any of: GL_RGB,
-    // GL_RGBA, GL_LUMINANCE, GL_LUMINANCE_ALPHA, ...
-    int bufferFormat = glfmt(color_fmt);
-
-    // the format we want the texture to me on the card; allows us to translate
-    // into a different texture format as we upload to OpenGL
-    int internalFormat = bufferFormat;
-
-    /* glTexImage2D: Load a 2d texture image
-     * target: Creating this as a 2d texture.
-     * level: Which mipmap level to use as the "root" (0 = the highest-quality,
-     *  full-res image), if mipmaps are enabled.
-     * internalFormat: Type of texel format we want OpenGL to use for this
-     *  texture internally on the video card.
-     * width: Texel-width of image; for maximum compatibility, use 2^N + 2^B,
-     *  where N is some integer in the range [3,10], and B is the border
-     *  thickness [0,1]
-     * height: Texel-height of image; for maximum compatibility, use 2^M + 2^B,
-     *  where M is some integer in the range [3,10], and B is the border
-     *  thickness [0,1]
-     * border: Border size, in texels (must be 0 or 1)
-     * format: Pixel format describing the composition of the pixel data in
-     *  buffer
-     * type: Pixel color components are unsigned bytes (one byte per color/alpha
-     *  channel)
-     * pixels: Location of the actual pixel data bytes/buffer
-     */
-    glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, dimension.x(), dimension.y(),
-                 0, bufferFormat, GL_UNSIGNED_BYTE, data);
-
-    glGenerateMipmap(GL_TEXTURE_2D);
-
-    // Set magnification (texel > pixel) and minification (texel < pixel)
-    // filters one of: GL_NEAREST, GL_LINEAR
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
-    // one of: GL_NEAREST, GL_LINEAR, GL_NEAREST_MIPMAP_NEAREST,
-    // GL_NEAREST_MIPMAP_LINEAR, GL_LINEAR_MIPMAP_NEAREST,
-    // GL_LINEAR_MIPMAP_LINEAR
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
-                    GL_NEAREST_MIPMAP_LINEAR);
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 5);
-
-    return id;
-  }
-}}
-
-namespace graphics { namespace shaders {
-  struct file_read_error : std::runtime_error {
-    using std::runtime_error::runtime_error;
-  };
-
-  struct compilation_error : std::runtime_error {
-    using std::runtime_error::runtime_error;
-  };
-
-  struct linker_error : std::runtime_error {
-    using std::runtime_error::runtime_error;
-  };
-
-  static int gltype(type tp) {
-    switch (tp) {
-    case type::FRAGMENT:
-      return GL_FRAGMENT_SHADER;
-    case type::VERTEX:
-      return GL_VERTEX_SHADER;
-    }
-  }
-
-  unsigned int init(type tp, std::string const & path) {
-    std::unique_ptr<char const[]> buffer;
-
-    // 1. Load the vertex shader code (text file) to a new memory buffer
-    std::string const abs_path = env::resource_file(path);
-    if (!(buffer = files::load(abs_path))) {
-      throw file_read_error("Could not load shader file " + abs_path);
-    }
-
-    // 2. Create a new shader ID
-    // GL_VERTEX_SHADER or GL_FRAGMENT_SHADER
-    unsigned int id = glCreateShader(gltype(tp));
-
-    // 3. Associate the shader code with the new shader ID
-    char const * buffer_ptr = buffer.get();
-    glShaderSource(id, 1, &buffer_ptr, NULL);
-
-    // 4. Compile the shader (the shader compiler is built in to your graphics
-    //    card driver)
-    glCompileShader(id);
-
-    // 5. Check for compile errors
-    int return_code;
-    glGetShaderiv(id, GL_COMPILE_STATUS, &return_code);
-
-    if (return_code != GL_TRUE) {
-      print_shader_error(id, abs_path);
-      throw compilation_error("Could not compile the shader file");
-    }
-    return id;
-  }
-
-  unsigned int init(identity<shader> const & fragmentShader,
-                    identity<shader> const & vertexShader) {
-    // 9. Create a new shader program ID
-    unsigned int id = glCreateProgram();
-
-    // 10. Attach the vertex and fragment shaders to the new shader program
-    glAttachShader(id, vertexShader.id);
-    glAttachShader(id, fragmentShader.id);
-
-    // 11. Do some other advanced stuff we'll do later on (like setting generic
-    // vertex attrib locations)
-    glBindAttribLocation(id, 0, "a_position");
-    glBindAttribLocation(id, 1, "a_color");
-    glBindAttribLocation(id, 2, "a_texCoords");
-    glBindAttribLocation(id, 3, "a_normal");
-
-    // 12. Link the program
-    glLinkProgram(id);
-
-    // 13. Check for link errors
-    int return_code;
-    glGetProgramiv(id, GL_LINK_STATUS, &return_code);
-
-    if (return_code != GL_TRUE) {
-      //      print_shader_program_error(id, fragmentShader.actual().path,
-      //                                 vertexShader.actual().path);
-      throw linker_error("Could not link shader program");
-    }
-    return id;
-  }
-
-  void activate(identity<shader_program> program) {
-    // 100. Use the shader program ID to "turn it on" for all subsequent drawing
-    glUseProgram(program.id);
-
-    /*
-     // 101. Enable texturing and Bind texture(s) to GPU texture units
-     glActiveTexture(GL_TEXTURE3);
-     glEnable(GL_TEXTURE_2D);
-     glBindTexture(GL_TEXTURE_2D, emissiveTextureID);
-
-     glActiveTexture(GL_TEXTURE2);
-     glEnable(GL_TEXTURE_2D);
-     glBindTexture(GL_TEXTURE_2D, specularTextureID);
-
-     glActiveTexture(GL_TEXTURE1);
-     glEnable(GL_TEXTURE_2D);
-     glBindTexture(GL_TEXTURE_2D, normalTextureID);
-
-     glActiveTexture(GL_TEXTURE0);
-     glEnable(GL_TEXTURE_2D);
-     glBindTexture(GL_TEXTURE_2D, diffuseTextureID);
-     */
-
-    // 102. Get the location # of each named uniform you wish to pass in to the
-    // shader
-    int timeUniformLocation = glGetUniformLocation(program.id, "u_time");
-    int scale = glGetUniformLocation(program.id, "Scale");
-    int diffuseMap = glGetUniformLocation(program.id, "u_diffuseMap");
-    int normalMap = glGetUniformLocation(program.id, "u_normalMap");
-    int specularMap = glGetUniformLocation(program.id, "u_specularMap");
-    int emissiveMap = glGetUniformLocation(program.id, "u_emissiveMap");
-    int debugWave = glGetUniformLocation(program.id, "g_debugWave");
-
-    // 103. Set the uniform values, including the texture unit numbers for
-    // texture (sampler) uniforms
-    // Env::GetCurrentTimeSeconds()
-    glUniform1f(timeUniformLocation, env::clock::current_time<float>());
-    glUniform1f(scale, 2.f);
-    glUniform1i(debugWave, 1); // TODO: m_waveEffectOn in ShaderProgram??
-    // for GL_TEXTURE0, texture unit 0
-    glUniform1i(diffuseMap, 0);
-    // for GL_TEXTURE1, texture unit 1
-    glUniform1i(normalMap, 1);
-    // for GL_TEXTURE2, texture unit 2
-    glUniform1i(specularMap, 2);
-    // for GL_TEXTURE3, texture unit 3
-    glUniform1i(emissiveMap, 3);
-  }
-
-  int uniform_location(identity<shader_program> program,
-                       std::string const & uniform) {
-    return glGetUniformLocation(program.id, uniform.c_str());
-  }
-}}
-
-namespace graphics { namespace materials {
-  void activate(identity<shader_program> program,
-                std::vector<uniform> const & uniforms) {
-    glUseProgram(program.id);
-
-    for (unsigned int i = 0; i < uniforms.size(); i++) {
-      const uniform & uniform = uniforms[i];
-      glActiveTexture(i + GL_TEXTURE0);
-      //      glEnable(GL_TEXTURE_2D);
-      glBindTexture(GL_TEXTURE_2D, uniform.texture.id);
-      glUniform1i(uniform.uniform_id, i);
-    }
-
-    glActiveTexture(GL_TEXTURE0);
-  }
-}}

+ 216 - 0
graphics/src/openGL/opengl_manager.cxx

@@ -0,0 +1,216 @@
+//
+//  helper.cxx
+//  graphics
+//
+//  Created by Sam Jaffe on 5/18/19.
+//  Copyright © 2019 Sam Jaffe. All rights reserved.
+//
+
+#include "helper.hpp"
+#include "opengl_renderer.h"
+
+#include <iostream>
+#include <memory>
+#include <sstream>
+#ifdef __APPLE__
+#include <OpenGL/gl3.h>
+#endif
+
+#include "scope_guard/scope_guard.hpp"
+#include "vector/vector.hpp"
+
+#include "game/graphics/material.hpp"
+#include "game/graphics/shader.hpp"
+#include "game/graphics/shader_program.hpp"
+#include "game/graphics/texture.hpp"
+#include "game/util/env.hpp"
+#include "game/util/files.hpp"
+#include "game/util/time.hpp"
+
+namespace graphics {
+  extern void print_shader_error(GLuint, std::string const &);
+  extern void print_shader_program_error(GLuint, std::string const &,
+                                         std::string const &);
+
+  struct file_read_error : std::runtime_error {
+    using std::runtime_error::runtime_error;
+  };
+
+  struct compilation_error : std::runtime_error {
+    using std::runtime_error::runtime_error;
+  };
+
+  struct linker_error : std::runtime_error {
+    using std::runtime_error::runtime_error;
+  };
+}
+
+using namespace graphics;
+
+namespace {
+  int glfmt(textures::format color_fmt) {
+    switch (color_fmt) {
+    case textures::format::RGB:
+      return GL_RGB;
+    case textures::format::RGBA:
+      return GL_RGBA;
+    }
+  }
+
+  int gltype(shaders::type tp) {
+    switch (tp) {
+    case shaders::type::FRAGMENT:
+      return GL_FRAGMENT_SHADER;
+    case shaders::type::VERTEX:
+      return GL_VERTEX_SHADER;
+    }
+  }
+}
+
+texture opengl_manager::compile(textures::format color, math::vec2i size,
+                                void const * buffer) const {
+  //  textures::external_data data(env::resource_file(path));
+  //  unsigned int init(format color_fmt, math::vec2i dimension,
+  //                    void const * data) {
+  unsigned int id;
+  // Enable texturings
+  //    glEnable( GL_TEXTURE_2D );
+
+  // Tell OpenGL that our pixel data is single-byte aligned
+  glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+  // Ask OpenGL for an unused texName (ID number) to use for this texture
+  glGenTextures(1, &id);
+
+  // Tell OpenGL to bind (set) this as the currently active texture
+  glBindTexture(GL_TEXTURE_2D, id);
+  // Set texture clamp vs. wrap (repeat)
+
+  // one of: GL_CLAMP_TO_EDGE, GL_REPEAT, GL_MIRRORED_REPEAT,
+  // GL_MIRROR_CLAMP_TO_EDGE, ...
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+  // the format our source pixel data is currently in; any of: GL_RGB,
+  // GL_RGBA, GL_LUMINANCE, GL_LUMINANCE_ALPHA, ...
+  int bufferFormat = glfmt(color);
+
+  // the format we want the texture to me on the card; allows us to translate
+  // into a different texture format as we upload to OpenGL
+  int internalFormat = bufferFormat;
+
+  /* glTexImage2D: Load a 2d texture image
+   * target: Creating this as a 2d texture.
+   * level: Which mipmap level to use as the "root" (0 = the highest-quality,
+   *  full-res image), if mipmaps are enabled.
+   * internalFormat: Type of texel format we want OpenGL to use for this
+   *  texture internally on the video card.
+   * width: Texel-width of image; for maximum compatibility, use 2^N + 2^B,
+   *  where N is some integer in the range [3,10], and B is the border
+   *  thickness [0,1]
+   * height: Texel-height of image; for maximum compatibility, use 2^M + 2^B,
+   *  where M is some integer in the range [3,10], and B is the border
+   *  thickness [0,1]
+   * border: Border size, in texels (must be 0 or 1)
+   * format: Pixel format describing the composition of the pixel data in
+   *  buffer
+   * type: Pixel color components are unsigned bytes (one byte per color/alpha
+   *  channel)
+   * pixels: Location of the actual pixel data bytes/buffer
+   */
+  glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, size.x(), size.y(), 0,
+               bufferFormat, GL_UNSIGNED_BYTE, buffer);
+
+  glGenerateMipmap(GL_TEXTURE_2D);
+
+  // Set magnification (texel > pixel) and minification (texel < pixel)
+  // filters one of: GL_NEAREST, GL_LINEAR
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+  // one of: GL_NEAREST, GL_LINEAR, GL_NEAREST_MIPMAP_NEAREST,
+  // GL_NEAREST_MIPMAP_LINEAR, GL_LINEAR_MIPMAP_NEAREST,
+  // GL_LINEAR_MIPMAP_LINEAR
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
+                  GL_NEAREST_MIPMAP_LINEAR);
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 5);
+
+  return texture{id, size};
+}
+
+shader opengl_manager::compile(shaders::type tp,
+                               std::string const & path) const {
+  std::unique_ptr<char const[]> buffer;
+
+  // 1. Load the vertex shader code (text file) to a new memory buffer
+  std::string const abs_path = env::resource_file(path);
+  if (!(buffer = files::load(abs_path))) {
+    throw file_read_error("Could not load shader file " + abs_path);
+  }
+
+  // 2. Create a new shader ID
+  // GL_VERTEX_SHADER or GL_FRAGMENT_SHADER
+  unsigned int id = glCreateShader(gltype(tp));
+
+  // 3. Associate the shader code with the new shader ID
+  char const * buffer_ptr = buffer.get();
+  glShaderSource(id, 1, &buffer_ptr, NULL);
+
+  // 4. Compile the shader (the shader compiler is built in to your graphics
+  //    card driver)
+  glCompileShader(id);
+
+  // 5. Check for compile errors
+  int return_code;
+  glGetShaderiv(id, GL_COMPILE_STATUS, &return_code);
+
+  if (return_code != GL_TRUE) {
+    print_shader_error(id, abs_path);
+    throw compilation_error("Could not compile the shader file");
+  }
+
+  return shader{id, tp, path};
+}
+
+shader_program opengl_manager::compile(identity<shader> fragmentShader,
+                                       identity<shader> vertexShader) const {
+  // 9. Create a new shader program ID
+  unsigned int id = glCreateProgram();
+
+  // 10. Attach the vertex and fragment shaders to the new shader program
+  glAttachShader(id, vertexShader.id);
+  glAttachShader(id, fragmentShader.id);
+
+  // 11. Do some other advanced stuff we'll do later on (like setting generic
+  // vertex attrib locations)
+  glBindAttribLocation(id, 0, "a_position");
+  glBindAttribLocation(id, 1, "a_color");
+  glBindAttribLocation(id, 2, "a_texCoords");
+  glBindAttribLocation(id, 3, "a_normal");
+
+  // 12. Link the program
+  glLinkProgram(id);
+
+  // 13. Check for link errors
+  int return_code;
+  glGetProgramiv(id, GL_LINK_STATUS, &return_code);
+
+  if (return_code != GL_TRUE) {
+    //      print_shader_program_error(id, fragmentShader.actual().path,
+    //                                 vertexShader.actual().path);
+    throw linker_error("Could not link shader program");
+  }
+  return shader_program{id, fragmentShader, vertexShader};
+}
+
+opengl_uniform_data & opengl_manager::data(identity<shader_program> program) {
+  auto & data = data_[program];
+  // TODO: perform this in compile()
+  if (data.uniform_id.empty()) {
+    glUseProgram(program.id);
+    using materials::uniform;
+    data.uniform_id = {
+        {uniform::NORMAL, glGetUniformLocation(program.id, "u_normalMap")},
+        {uniform::DIFFUSE, glGetUniformLocation(program.id, "u_diffuseMap")},
+        {uniform::SPECULAR, glGetUniformLocation(program.id, "u_specularMap")}};
+  }
+  return data;
+}

+ 18 - 26
graphics/src/openGL/renderer_impl.cxx

@@ -6,7 +6,7 @@
 //  Copyright © 2019 Sam Jaffe. All rights reserved.
 //
 
-#include "renderer_impl.hpp"
+#include "opengl_renderer.h"
 
 #ifdef __APPLE__
 #include <OpenGL/gl3.h>
@@ -23,31 +23,8 @@
 
 using namespace graphics;
 
-class opengl_renderer : public renderer_impl {
-public:
-  opengl_renderer();
-  ~opengl_renderer();
-
-  void draw(identity<material>, math::matr4 const &,
-            std::vector<vertex> const &) override;
-  void clear() override;
-  void flush() override;
-
-  std::shared_ptr<class manager const> manager() const override { return mgr; }
-
-private:
-  const math::matr4 identity{math::matrix::identity<float, 4>()};
-
-  std::shared_ptr<class manager> mgr;
-  unsigned int active_material;
-
-  math::matr4 world_to_clip{identity};
-  double current_time{0.0};
-  unsigned int vertex_array_object{0}, vertex_buffer_object{0};
-};
-
 opengl_renderer::opengl_renderer()
-    : mgr(new class manager), active_material(0) {
+    : manager_(new opengl_manager), active_material(0) {
   glGenVertexArrays(1, &vertex_array_object);
   glBindVertexArray(vertex_array_object);
   glGenBuffers(1, &vertex_buffer_object);
@@ -85,7 +62,7 @@ void opengl_renderer::draw(::identity<material> material_id,
                            math::matr4 const & object_to_world,
                            std::vector<vertex> const & vertices) {
   material const & mat = manager()->get(material_id);
-  if (material_id != active_material) { mat.activate(); }
+  activate(mat);
   // TODO: Attatch shader-id to material-id
   unsigned int const id = mat.program.id;
 
@@ -122,6 +99,21 @@ void opengl_renderer::draw(::identity<material> material_id,
   glDisableVertexAttribArray(texCoordsLocation);
 }
 
+void opengl_renderer::activate(material const & mat) {
+  if (mat.id == active_material) return;
+  glUseProgram(mat.program.id);
+
+  for (unsigned int i = 0; i < mat.uniforms.size(); i++) {
+    const uniform & uniform = mat.uniforms[i];
+    glActiveTexture(i + GL_TEXTURE0);
+    //      glEnable(GL_TEXTURE_2D);
+    glBindTexture(GL_TEXTURE_2D, uniform.texture.id);
+    glUniform1i(manager_->data(mat.program)[uniform.uniform_id], i);
+  }
+
+  glActiveTexture(GL_TEXTURE0);
+}
+
 template <> renderer_impl * graphics::get_renderer_impl<driver::openGL>() {
   static opengl_renderer impl;
   return &impl;

+ 66 - 0
graphics/src/openGL/opengl_renderer.h

@@ -0,0 +1,66 @@
+//
+//  opengl_renderer.h
+//  graphics
+//
+//  Created by Sam Jaffe on 6/2/19.
+//  Copyright © 2019 Sam Jaffe. All rights reserved.
+//
+
+#pragma once
+
+#include "../helper.hpp"
+#include "game/graphics/manager.hpp"
+#include "game/util/identity.hpp"
+#include "renderer_impl.hpp"
+
+#include "matrix/matrix.hpp"
+#include "matrix/matrix_helpers.hpp"
+
+namespace graphics {
+  struct opengl_uniform_data {
+    unsigned int operator[](materials::uniform id) { return uniform_id[id]; }
+    std::unordered_map<materials::uniform, unsigned int> uniform_id;
+  };
+
+  class opengl_manager : public manager {
+  public:
+    shader compile(shaders::type type, std::string const & path) const override;
+    shader_program compile(identity<shader> fragment,
+                           identity<shader> vertex) const override;
+    texture compile(textures::format color, math::vec2i size,
+                    void const * buffer) const override;
+
+    opengl_uniform_data & data(identity<shader_program> id);
+
+  private:
+    std::unordered_map<identity<shader_program>, opengl_uniform_data> data_;
+  };
+
+  class opengl_renderer : public renderer_impl {
+  public:
+    opengl_renderer();
+    ~opengl_renderer();
+
+    void draw(identity<material>, math::matr4 const &,
+              std::vector<vertex> const &) override;
+    void clear() override;
+    void flush() override;
+
+    std::shared_ptr<class manager const> manager() const override {
+      return manager_;
+    }
+
+  private:
+    void activate(material const & mat);
+
+  private:
+    const math::matr4 identity{math::matrix::identity<float, 4>()};
+
+    std::shared_ptr<opengl_manager> manager_;
+    unsigned int active_material;
+
+    math::matr4 world_to_clip{identity};
+    double current_time{0.0};
+    unsigned int vertex_array_object{0}, vertex_buffer_object{0};
+  };
+}

+ 2 - 4
graphics/src/shader.cpp

@@ -7,9 +7,7 @@
 
 #include "game/graphics/shader.hpp"
 
-#include "helper.hpp"
-
 using namespace graphics;
 
-shader::shader(shaders::type type, std::string const & path)
-    : identity<shader>(shaders::init(type, path)), type(type), path(path) {}
+shader::shader(unsigned int id, shaders::type type, std::string const & path)
+    : identity<shader>(id), type(type), path(path) {}

+ 4 - 4
graphics/src/shader_program.cpp

@@ -7,13 +7,13 @@
 
 #include "game/graphics/shader_program.hpp"
 
-#include "game/graphics/shader.hpp"
 #include "helper.hpp"
 
 using namespace graphics;
 
-shader_program::shader_program(identity<shader> frag, identity<shader> vert)
-    : identity<shader_program>(shaders::init(frag, vert)),
-      fragment_shader(frag), vertex_shader(vert) {}
+shader_program::shader_program(unsigned int id, identity<shader> frag,
+                               identity<shader> vert)
+    : identity<shader_program>(id), fragment_shader(frag), vertex_shader(vert) {
+}
 
 void shader_program::activate() const { shaders::activate(id); }

+ 13 - 28
graphics/src/texture.cpp

@@ -25,44 +25,29 @@ unsigned char * stbi_load(char const *, int *, int *, int *, int);
 void stbi_image_free(void *);
 
 using namespace graphics;
+using namespace textures;
 
-static textures::format format(int comps) {
+static format as_format(int comps) {
   switch (comps) {
   case 3:
-    return textures::format::RGB;
+    return format::RGB;
   case 4:
-    return textures::format::RGBA;
+    return format::RGBA;
   default:
     throw;
   }
 }
 
-std::pair<unsigned int, math::vec2i> create(std::string const & path) {
+external_data::external_data(std::string const & rel_path) {
+  std::string abs_path = env::resource_file(rel_path);
   int components = 0;
-  math::vec2i size;
-  std::string file = env::resource_file(path);
-  auto data = stbi_load(file.c_str(), &size.x(), &size.y(), &components, 0);
-  scope(exit) { stbi_image_free(data); };
-  return {textures::init(format(components), size, data), size};
+  buffer = stbi_load(abs_path.c_str(), &size.x(), &size.y(), &components, 0);
+  color = as_format(components);
 }
 
-texture::texture(std::string const & path) : texture(create(path)) {}
-
-texture::texture(std::pair<unsigned int, math::vec2i> pair)
-    : identity<texture>(pair.first), size(pair.second) {}
-
-texture::texture(char const * data, math::vec2i size)
-    : identity<texture>(textures::init(format(4), size, data)), size(size) {}
-
-texture const & texture::WHITE() {
-  static auto t = texture("\xFF\xFF\xFF\xFF", {{1, 1}});
-  return t;
-}
-texture const & texture::DARK_YELLOW() {
-  static auto t = texture("\x80\x80\x00\xFF", {{1, 1}});
-  return t;
-}
-texture const & texture::LIGHT_BLUE() {
-  static auto t = texture("\x80\x80\xFF\xFF", {{1, 1}});
-  return t;
+external_data::~external_data() {
+  if (buffer) { stbi_image_free(buffer); }
 }
+
+texture::texture(unsigned int id, math::vec2i const & size)
+    : identity<texture>(id), size(size) {}