From 0ba44abcf461ad917c449635e20db7de8f44e0e0 Mon Sep 17 00:00:00 2001
From: Joseph Walton-Rivers <joseph@walton-rivers.uk>
Date: Sun, 17 Apr 2022 13:53:36 +0100
Subject: [PATCH] try to cleanup some of the api

---
 demo/main.cpp                       |  69 +---
 fggl/CMakeLists.txt                 |   1 -
 fggl/app.cpp                        |   1 +
 fggl/fggl.cpp                       |   0
 fggl/fggl.hpp                       |   6 -
 fggl/gfx/input.cpp                  |   4 +-
 fggl/gfx/ogl/renderer.cpp           | 584 ++++++++++++++++------------
 fggl/gfx/window.cpp                 |  79 +---
 include/fggl/app.hpp                |   8 +-
 include/fggl/gfx/compat.hpp         |  10 +-
 include/fggl/gfx/ogl/renderer.hpp   |   1 +
 include/fggl/gfx/window.hpp         |  12 +-
 include/fggl/gfx/windowing.hpp      |   1 +
 include/fggl/math/triangulation.hpp |  23 +-
 include/fggl/util/ownership.hpp     |  14 +
 include/fggl/util/service.h         |   3 +-
 16 files changed, 410 insertions(+), 406 deletions(-)
 delete mode 100644 fggl/fggl.cpp
 delete mode 100644 fggl/fggl.hpp
 create mode 100644 include/fggl/util/ownership.hpp

diff --git a/demo/main.cpp b/demo/main.cpp
index 60d60a9..820e848 100644
--- a/demo/main.cpp
+++ b/demo/main.cpp
@@ -366,15 +366,8 @@ int main(int argc, const char* argv[]) {
 
     // FIXME: janky API(s)
     auto& locator = fggl::util::ServiceLocator::instance();
-    
-    auto inputs = std::make_shared<fggl::input::Input>();
-    locator.supply<fggl::input::Input>(inputs);
-
-    auto storage = std::make_shared<fggl::data::Storage>();
-    locator.supply<fggl::data::Storage>(storage);
-
-    std::vector< fggl::gfx::ImageAtlas<char>::SubImage > images;
-    auto atlas = fggl::gfx::ImageAtlas<char>::pack(images);
+	auto inputs = locator.supply<fggl::input::Input>(std::make_shared<fggl::input::Input>());
+	auto storage = locator.supply<fggl::data::Storage>(std::make_shared<fggl::data::Storage>());
 
     // Would be nice to not take args like this, it messes with lifetimes
     auto& windowing = app.use<fggl::gfx::ecsGlfwModule>(inputs);
@@ -385,62 +378,16 @@ int main(int argc, const char* argv[]) {
     window->fullscreen( true );
     app.setWindow( std::move(window) );
 
-    // and now our states
-    auto menu = app.add_state<fggl::scenes::BasicMenu>("menu");
+	// atlas testing
+	std::vector< fggl::gfx::ImageAtlas<char>::SubImage > images;
+	auto *atlas = fggl::gfx::ImageAtlas<char>::pack(images);
+
+	// and now our states
+    auto *menu = app.add_state<fggl::scenes::BasicMenu>("menu");
     menu->add("start", [&app]() { app.change_state("game"); });
 
     // game state
     app.add_state<GameScene>("game");
 
-
-	// debug layer
-	//std::shared_ptr<fggl::debug::DebugUI> debug = std::make_shared<fggl::debug::DebugUI>(window);
-    //locator.supply<fggl::debug::DebugUI>(debug);
-    //debug->addWindow("gamepad", gamepadDebug);
-    //debug->addWindow("imgui-demo", ImGui::ShowDemoWindow);
-    //debug->addWindow("imgui-about", ImGui::ShowAboutWindow);
-    //debug->addWindow("imgui-help", [](bool* val) { ImGui::ShowUserGuide(); } );
-	//debug->visible(true);
-
-    // Scene management
-    //auto scenes = std::make_shared<fggl::scenes::SceneManager>();
-    //locator.supply<fggl::scenes::SceneManager>(scenes);
-    //scenes->create("main_menu", std::make_shared<MenuScene>(inputs));
-    //scenes->create("game", std::make_shared<GameScene>(ecs, inputs));
-    //scenes->activate("main_menu");
-
-    /*
-        // Main game/event loop
-        fggl::util::Timer time{};
-        time.frequency( glfwGetTimerFrequency() );
-        time.setup( glfwGetTimerValue() );
-
-        while( !window->closeRequested() ) {
-            //
-            // Setup setup
-            //
-            time.tick( glfwGetTimerValue() );
-            inputs->frame( time.delta() );
-
-            glfwModule->context.pollEvents();
-            debug->frameStart();
-
-            //
-            // update step
-            //
-            scenes->update();
-
-            // render the scene
-            window->activate();
-            glModule->ogl.clear();
-
-            // allow the scene to do stuff, then actually render
-            scenes->render();
-
-            debug->draw();
-            window->swap();
-        }
-    */
-
 	return app.run(argc, argv);
 }
diff --git a/fggl/CMakeLists.txt b/fggl/CMakeLists.txt
index 5cefcc8..244f3bf 100644
--- a/fggl/CMakeLists.txt
+++ b/fggl/CMakeLists.txt
@@ -8,7 +8,6 @@ endif()
 
 target_sources(${PROJECT_NAME}
   PRIVATE
-    fggl.cpp
     app.cpp
     ecs/ecs.cpp
     data/model.cpp
diff --git a/fggl/app.cpp b/fggl/app.cpp
index 8f3e3d6..c74cea5 100644
--- a/fggl/app.cpp
+++ b/fggl/app.cpp
@@ -18,6 +18,7 @@ namespace fggl {
         m_running(true),
         m_types(std::make_unique<ecs3::TypeRegistry>()),
         m_modules(std::make_unique<ecs3::ModuleManager>(*m_types)),
+		m_window(nullptr),
         m_states() {}
 
     int App::run(int argc, const char** argv) {
diff --git a/fggl/fggl.cpp b/fggl/fggl.cpp
deleted file mode 100644
index e69de29..0000000
diff --git a/fggl/fggl.hpp b/fggl/fggl.hpp
deleted file mode 100644
index d734444..0000000
--- a/fggl/fggl.hpp
+++ /dev/null
@@ -1,6 +0,0 @@
-#ifndef FGGL_H
-#define FGGL_H
-
-#include <fggl/gfx/display.hpp>
-
-#endif
diff --git a/fggl/gfx/input.cpp b/fggl/gfx/input.cpp
index 8be1ecb..ef7329f 100644
--- a/fggl/gfx/input.cpp
+++ b/fggl/gfx/input.cpp
@@ -23,8 +23,8 @@ void Input::clear() {
 	for ( auto& pad : m_pad_curr ) {
 		pad.present = false;
 		pad.buttons.reset();
-		for (int i=0; i<6; i++){
-			pad.axes[i] = 0.0f;
+		for (float & axe : pad.axes){
+			axe = 0.0F;
 		}
 	}
 	m_pad_last = m_pad_curr;
diff --git a/fggl/gfx/ogl/renderer.cpp b/fggl/gfx/ogl/renderer.cpp
index 4a0f570..b4d18e8 100644
--- a/fggl/gfx/ogl/renderer.cpp
+++ b/fggl/gfx/ogl/renderer.cpp
@@ -12,6 +12,88 @@
 #include <glm/gtc/type_ptr.hpp>
 #include <memory>
 
+extern "C" {
+
+/**
+ * Convert an OpenGL source enum to a string.
+ *
+ * list of sources taken from table 20.1, GL Spec 4.5
+ * @param source
+ * @return string representing the source
+ */
+constexpr auto fggl_ogl_source(GLenum source) -> const char * {
+	switch (source) {
+	case GL_DEBUG_SOURCE_API: return "GL";
+	case GL_DEBUG_SOURCE_SHADER_COMPILER: return "GLSL compiler";
+	case GL_DEBUG_SOURCE_WINDOW_SYSTEM: return "Windowing System";
+	case GL_DEBUG_SOURCE_THIRD_PARTY: return "Third Party";
+	case GL_DEBUG_SOURCE_APPLICATION: return "Application";
+	default:
+	case GL_DEBUG_SOURCE_OTHER: return "Other";
+	}
+	assert(false);
+	return "unknown";
+}
+
+/**
+ * Convert an OpenGL type enum to a string.
+ *
+ * list of sources taken from table 20.2, GL Spec 4.5
+ * @param type
+ * @return string representing the type
+ */
+constexpr auto static fggl_ogl_type(GLenum type) -> const char * {
+	switch (type) {
+	case GL_DEBUG_TYPE_ERROR: return "Error";
+		break;
+	case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: return "Deprecated Behaviour";
+		break;
+	case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: return "Undefined Behaviour";
+		break;
+	case GL_DEBUG_TYPE_PERFORMANCE: return "Performance";
+		break;
+	case GL_DEBUG_TYPE_PORTABILITY: return "Portability";
+		break;
+	case GL_DEBUG_TYPE_MARKER: return "Marker";
+		break;
+	case GL_DEBUG_TYPE_PUSH_GROUP: return "Push Group";
+		break;
+	case GL_DEBUG_TYPE_POP_GROUP: return "Pop Group";
+		break;
+	default:
+	case GL_DEBUG_TYPE_OTHER: return "Other";
+		break;
+	}
+	assert(false);
+	return "unknown";
+}
+
+#pragma clang diagnostic push
+#pragma ide diagnostic ignored "bugprone-easily-swappable-parameters"
+void static fggl_ogl_to_spdlog(GLenum source, GLenum type, unsigned int msgId, GLenum severity, GLsizei  /*length*/,
+							   const char *message, const void * /*userParam*/) {
+	std::string fmt = "[GL] {}, {}: [{}]: {}";
+
+	const auto *const sourceStr = fggl_ogl_source(source);
+	const auto *const typeStr = fggl_ogl_type(type);
+
+	// table 20.3, GL spec 4.5
+	switch (severity) {
+	case GL_DEBUG_SEVERITY_HIGH: spdlog::error(fmt, sourceStr, typeStr, msgId, message);
+		break;
+	default:
+	case GL_DEBUG_SEVERITY_MEDIUM: spdlog::warn(fmt, sourceStr, typeStr, msgId, message);
+		break;
+	case GL_DEBUG_SEVERITY_LOW: spdlog::info(fmt, sourceStr, typeStr, msgId, message);
+		break;
+	case GL_DEBUG_SEVERITY_NOTIFICATION: spdlog::debug(fmt, sourceStr, typeStr, msgId, message);
+		break;
+	}
+}
+#pragma clang diagnostic pop
+
+}
+
 /**
  * Future optimisations:
  *   recommended approach is to group stuff in to as few vao as possible - this
@@ -28,245 +110,271 @@
  */
 namespace fggl::gfx {
 
-using data::Mesh2D;
-using data::Vertex2D;
-
-GlRenderToken setupVertex2D() {
-  GlRenderToken token{};
-  glGenVertexArrays(1, &token.vao);
-  glBindVertexArray(token.vao);
-
-  glGenBuffers(2, token.buffs);
-
-  glBindBuffer(GL_ARRAY_BUFFER, token.buffs[0]);
-  glEnableVertexAttribArray(0);
-  glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex2D),
-                        reinterpret_cast<void*>(offsetof(Vertex2D, position)));
-  glEnableVertexAttribArray(1);
-  glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex2D),
-                        reinterpret_cast<void*>(offsetof(Vertex2D, colour)));
-
-  glBindVertexArray(0);
-  return token;
-}
-
-OpenGL4Backend::OpenGL4Backend(const Window& owner) : fggl::gfx::Graphics() {
-  // initialise GLEW, or fail
-  GLenum err = glewInit();
-  if (GLEW_OK != err) {
-    throw std::runtime_error("couldn't init glew");
-  }
-
-  // setup the viewport based on the window's framebuffer
-  auto fbSize = owner.frameSize();
-  glViewport(0, 0, fbSize.x, fbSize.y);
-
-  // setup the shader cache
-  auto& locator = util::ServiceLocator::instance();
-  auto storage = locator.get<data::Storage>();
-  m_cache = std::make_unique<ShaderCache>(storage);
-
-  // setup 2D rendering system
-  m_token2D = setupVertex2D();
-  ShaderConfig shader2DConfig = ShaderFromName("shader2D");
-  m_cache->load(shader2DConfig);
-};
-
-void OpenGL4Backend::clear() {
-  glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
-  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
-}
-
-static void generateMesh(const gfx::Paint& paint, Mesh2D& mesh) {
-  for (auto& cmd : paint.cmds()) {
-    auto path = cmd.path;
-
-    std::vector<data::Vertex2D> verts;
-    math::vec3 colour{1.0f, 1.0f, 1.0f};
-    auto idx = 0;
-    auto colourIdx = 0;
-
-    for (auto& type : path.m_types) {
-      if (type == PathType::PATH) {
-        verts.push_back({.position = path.m_points[idx++], .colour = colour});
-      } else if (type == PathType::MOVE) {
-        // polygon finished
-        if (verts.size() < 3) {
-          // empty, point, or line
-        } else if (verts.size() == 3) {
-          // triangle
-        } else {
-          // polygon
-          math::fanTriangulation(verts, mesh);
-        }
-
-        verts.clear();
-        verts.push_back({.position = path.m_points[idx++], .colour = colour});
-      } else if (type == PathType::COLOUR) {
-        colour = path.m_colours[colourIdx++];
-      } else {
-        // unsupported type
-      }
-    }
-
-    if (!verts.empty()) {
-      math::fanTriangulation(verts, mesh);
-    }
-  }
-}
-
-void OpenGL4Backend::draw2D(const gfx::Paint& paint) {
-  // generate the mesh from a paint command list
-  data::Mesh2D mesh;
-  generateMesh(paint, mesh);
-
-  // render the resulting mesh
-  auto shader2D = m_cache->get("shader2D");
-
-  glUseProgram(shader2D);
-
-  auto projMat = glm::ortho(0.0f, 1920.0f, 0.0f, 1080.f);
-  glUniformMatrix4fv(glGetUniformLocation(shader2D, "projection"), 1, GL_FALSE,
-                     glm::value_ptr(projMat));
-
-  glBindVertexArray(m_token2D.vao);
-
-  glBindBuffer(GL_ARRAY_BUFFER, m_token2D.buffs[0]);
-  glBufferData(GL_ARRAY_BUFFER, mesh.vertexList.size() * sizeof(Vertex2D),
-               mesh.vertexList.data(), GL_DYNAMIC_DRAW);
-
-  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_token2D.buffs[1]);
-  glBufferData(GL_ELEMENT_ARRAY_BUFFER,
-               mesh.indexList.size() * sizeof(uint32_t), mesh.indexList.data(),
-               GL_DYNAMIC_DRAW);
-
-  glDrawElements(GL_TRIANGLES, mesh.indexList.size(), GL_UNSIGNED_INT,
-                 (GLvoid*)0);
-
-  glBindVertexArray(0);
-  glUseProgram(0);
-  // glDisable(GL_PRIMITIVE_RESTART);
-}
-
-template <typename T>
-static GLuint createArrayBuffer(std::vector<T>& vertexData) {
-  GLuint buffId;
-  glGenBuffers(1, &buffId);
-  glBindBuffer(GL_ARRAY_BUFFER, buffId);
-  glBufferData(GL_ARRAY_BUFFER, vertexData.size() * sizeof(T),
-               vertexData.data(), GL_STATIC_DRAW);
-  return buffId;
-}
-
-static GLuint createIndexBuffer(std::vector<uint32_t>& indexData) {
-  GLuint buffId;
-  glGenBuffers(1, &buffId);
-  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffId);
-  glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexData.size() * sizeof(uint32_t),
-               indexData.data(), GL_STATIC_DRAW);
-  return buffId;
-}
-
-GlRenderToken MeshRenderer::upload(fggl::data::Mesh& mesh) {
-  GlRenderToken token{};
-  glGenVertexArrays(1, &token.vao);
-  glBindVertexArray(token.vao);
-
-  token.buffs[0] = createArrayBuffer<fggl::data::Vertex>(mesh.vertexList());
-  glEnableVertexAttribArray(0);
-  glVertexAttribPointer(
-      0, 3, GL_FLOAT, GL_FALSE, sizeof(fggl::data::Vertex),
-      reinterpret_cast<void*>(offsetof(fggl::data::Vertex, posititon)));
-  glEnableVertexAttribArray(1);
-  glVertexAttribPointer(
-      1, 3, GL_FLOAT, GL_FALSE, sizeof(fggl::data::Vertex),
-      reinterpret_cast<void*>(offsetof(fggl::data::Vertex, normal)));
-
-  token.buffs[1] = createIndexBuffer(mesh.indexList());
-  token.idxOffset = 0;
-  token.idxSize = mesh.indexCount();
-  token.restartVertex = mesh.restartVertex;
-  glBindVertexArray(0);
-
-  return token;
-}
+	using data::Mesh2D;
+	using data::Vertex2D;
+
+	GlRenderToken setupVertex2D() {
+		GlRenderToken token{};
+		glGenVertexArrays(1, &token.vao);
+		glBindVertexArray(token.vao);
+
+		glGenBuffers(2, token.buffs);
+
+		glBindBuffer(GL_ARRAY_BUFFER, token.buffs[0]);
+		glEnableVertexAttribArray(0);
+		glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex2D),
+							  reinterpret_cast<void *>(offsetof(Vertex2D, position)));
+		glEnableVertexAttribArray(1);
+		glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex2D),
+							  reinterpret_cast<void *>(offsetof(Vertex2D, colour)));
+
+		glBindVertexArray(0);
+		return token;
+	}
+
+	OpenGL4Backend::OpenGL4Backend(const Window &owner) : fggl::gfx::Graphics() {
+		// initialise GLEW, or fail
+		GLenum err = glewInit();
+		if (GLEW_OK != err) {
+			throw std::runtime_error("couldn't init glew");
+		}
+
+		// OpenGL debug Support
+		GLint flags = 0;
+		glGetIntegerv(GL_CONTEXT_FLAGS, &flags);
+		if ( (flags & GL_CONTEXT_FLAG_DEBUG_BIT) == GL_CONTEXT_FLAG_DEBUG_BIT) { // NOLINT(hicpp-signed-bitwise)
+			spdlog::info("enabling OpenGL debug output");
+			glEnable(GL_DEBUG_OUTPUT);
+			glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
+			glDebugMessageCallback(fggl_ogl_to_spdlog, nullptr);
+			glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE);
+		}
+
+		// setup the viewport based on the window's framebuffer
+		auto fbSize = owner.frameSize();
+		glViewport(0, 0, fbSize.x, fbSize.y);
+
+		// setup the shader cache
+		auto &locator = util::ServiceLocator::instance();
+		auto storage = locator.get<data::Storage>();
+		m_cache = std::make_unique<ShaderCache>(storage);
+
+		// setup 2D rendering system
+		m_token2D = setupVertex2D();
+		ShaderConfig shader2DConfig = ShaderFromName("shader2D");
+		m_cache->load(shader2DConfig);
+
+	};
+
+	void OpenGL4Backend::clear() {
+		glClearColor(0.0F, 0.0F, 0.0F, 0.0F);
+		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+	}
+
+	inline static void add_mesh_triangle(data::Mesh2D& mesh, const std::vector<data::Vertex2D>& verts) {
+		assert( verts.size() == 3);
+		for( const auto& vert : verts ) {
+			auto idx = mesh.add_vertex(vert);
+			mesh.add_index(idx);
+		}
+	}
+
+	static void generateMesh(const gfx::Paint &paint, Mesh2D &mesh) {
+		for (const auto &cmd : paint.cmds()) {
+			auto path = cmd.path;
+
+			std::vector<data::Vertex2D> verts;
+			math::vec3 colour{1.0F, 1.0F, 1.0F};
+			auto idx = 0;
+			auto colourIdx = 0;
+
+			for (auto &type : path.m_types) {
+				if (type == PathType::PATH) {
+					verts.push_back({.position = path.m_points[idx++], .colour = colour});
+				} else if (type == PathType::MOVE) {
+					// polygon finished
+					if (verts.size() < 3) {
+						// empty, point, or line
+						// TODO deal with whatever I'm meant to do with this...
+					} else if (verts.size() == 3) {
+						// triangle
+						add_mesh_triangle(mesh, verts);
+					} else {
+						// polygon
+						math::fanTriangulation(verts, mesh);
+					}
+
+					verts.clear();
+					verts.push_back({.position = path.m_points[idx++], .colour = colour});
+				} else if (type == PathType::COLOUR) {
+					colour = path.m_colours[colourIdx++];
+				} else {
+					// unsupported type
+				}
+			}
+
+			if (!verts.empty()) {
+				math::fanTriangulation(verts, mesh);
+			}
+		}
+	}
+
+	void OpenGL4Backend::draw2D(const gfx::Paint &paint) {
+		// generate the mesh from a paint command list
+		data::Mesh2D mesh;
+		generateMesh(paint, mesh);
+
+		// render the resulting mesh
+		auto shader2D = m_cache->get("shader2D");
+
+		glUseProgram(shader2D);
+
+		auto projMat = glm::ortho(0.0f, 1920.0f, 0.0f, 1080.f);
+		glUniformMatrix4fv(glGetUniformLocation(shader2D, "projection"), 1, GL_FALSE,
+						   glm::value_ptr(projMat));
+
+		glBindVertexArray(m_token2D.vao);
+
+		glBindBuffer(GL_ARRAY_BUFFER, m_token2D.buffs[0]);
+		glBufferData(GL_ARRAY_BUFFER, mesh.vertexList.size() * sizeof(Vertex2D),
+					 mesh.vertexList.data(), GL_DYNAMIC_DRAW);
+
+		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_token2D.buffs[1]);
+		glBufferData(GL_ELEMENT_ARRAY_BUFFER,
+					 mesh.indexList.size() * sizeof(uint32_t), mesh.indexList.data(),
+					 GL_DYNAMIC_DRAW);
+
+		glDrawElements(GL_TRIANGLES, mesh.indexList.size(), GL_UNSIGNED_INT,
+					   (GLvoid *) 0);
+
+		glBindVertexArray(0);
+		glUseProgram(0);
+		// glDisable(GL_PRIMITIVE_RESTART);
+	}
+
+	void OpenGL4Backend::resize(int width, int height) {
+		glViewport(0, 0, width, height);
+	}
+
+	template<typename T>
+	static GLuint createArrayBuffer(std::vector<T> &vertexData) {
+		GLuint buffId;
+		glGenBuffers(1, &buffId);
+		glBindBuffer(GL_ARRAY_BUFFER, buffId);
+		glBufferData(GL_ARRAY_BUFFER, vertexData.size() * sizeof(T),
+					 vertexData.data(), GL_STATIC_DRAW);
+		return buffId;
+	}
+
+	static GLuint createIndexBuffer(std::vector<uint32_t> &indexData) {
+		GLuint buffId;
+		glGenBuffers(1, &buffId);
+		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffId);
+		glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexData.size() * sizeof(uint32_t),
+					 indexData.data(), GL_STATIC_DRAW);
+		return buffId;
+	}
+
+	GlRenderToken MeshRenderer::upload(fggl::data::Mesh &mesh) {
+		GlRenderToken token{};
+		glGenVertexArrays(1, &token.vao);
+		glBindVertexArray(token.vao);
+
+		token.buffs[0] = createArrayBuffer<fggl::data::Vertex>(mesh.vertexList());
+		glEnableVertexAttribArray(0);
+		glVertexAttribPointer(
+			0, 3, GL_FLOAT, GL_FALSE, sizeof(fggl::data::Vertex),
+			reinterpret_cast<void *>(offsetof(fggl::data::Vertex, posititon)));
+		glEnableVertexAttribArray(1);
+		glVertexAttribPointer(
+			1, 3, GL_FLOAT, GL_FALSE, sizeof(fggl::data::Vertex),
+			reinterpret_cast<void *>(offsetof(fggl::data::Vertex, normal)));
+
+		token.buffs[1] = createIndexBuffer(mesh.indexList());
+		token.idxOffset = 0;
+		token.idxSize = mesh.indexCount();
+		token.restartVertex = mesh.restartVertex;
+		glBindVertexArray(0);
+
+		return token;
+	}
 
 // TODO(webpigeon): this shouldn't be hard-coded
-constexpr glm::vec3 DEFAULT_LIGHTPOS = glm::vec3(20.0F, 20.0F, 15.0F);
-
-void MeshRenderer::render(fggl::ecs3::World& ecs, ecs3::entity_t camera,
-                          float dt) {
-  if (camera == ecs::NULL_ENTITY) {
-    spdlog::warn("tried to render a scene, but no camera exists!");
-    return;
-  }
-
-  auto entities = ecs.findMatching<GlRenderToken>();
-  if (entities.empty()) {
-    spdlog::warn("asked to render, but no entities are renderable");
-    return;
-  }
-
-  total += dt;
-
-  glEnable(GL_CULL_FACE);
-  glCullFace(GL_BACK);
-
-  glEnable(GL_DEPTH_TEST);
-
-  // camera logic
-  auto* const camTransform = ecs.get<math::Transform>(camera);
-  auto* const camComp = ecs.get<gfx::Camera>(camera);
-  glm::mat4 proj = glm::perspective(camComp->fov, camComp->aspectRatio,
-                                    camComp->nearPlane, camComp->farPlane);
-  glm::mat4 view =
-      glm::lookAt(camTransform->origin(), camComp->target, camTransform->up());
-
-  // lighting
-  glm::vec3 lightPos = DEFAULT_LIGHTPOS;
-
-  // TODO(webpigeon): better performance if grouped by vao first
-  // TODO(webpigeon): the nvidia performance presentation said I shouldn't use
-  // uniforms for large data
-  for (auto& entity : entities) {
-    const auto& transform = ecs.get<fggl::math::Transform>(entity);
-    const auto& mesh = ecs.get<GlRenderToken>(entity);
-
-    glm::mat4 model = transform->model();
-    //	    model = glm::rotate(model, glm::radians(total/2048.0f * 360.0f),
-    //glm::vec3(0.0f,1.0f,0.0f));
-
-    auto shader = mesh->pipeline;
-    glUseProgram(shader);
-
-    glUniformMatrix4fv(glGetUniformLocation(shader, "model"), 1, GL_FALSE,
-                       glm::value_ptr(model));
-    glUniformMatrix4fv(glGetUniformLocation(shader, "view"), 1, GL_FALSE,
-                       glm::value_ptr(view));
-    glUniformMatrix4fv(glGetUniformLocation(shader, "projection"), 1, GL_FALSE,
-                       glm::value_ptr(proj));
-
-    // lighting
-    GLint lightID = glGetUniformLocation(shader, "lightPos");
-    if (lightID != -1) {
-      glUniform3fv(lightID, 1, glm::value_ptr(lightPos));
-    }
-
-    glBindVertexArray(mesh->vao);
-
-    if (mesh->renderType == GlRenderType::triangle_strip) {
-      glEnable(GL_PRIMITIVE_RESTART);
-      glPrimitiveRestartIndex(mesh->restartVertex);
-    }
-
-    glDrawElements(mesh->renderType, mesh->idxSize, GL_UNSIGNED_INT,
-                   reinterpret_cast<void*>(mesh->idxOffset));
-    if (mesh->renderType == GlRenderType::triangle_strip) {
-      glDisable(GL_PRIMITIVE_RESTART);
-    }
-  }
-
-  glBindVertexArray(0);
-}
+	constexpr glm::vec3 DEFAULT_LIGHTPOS = glm::vec3(20.0F, 20.0F, 15.0F);
+
+	void MeshRenderer::render(fggl::ecs3::World &ecs, ecs3::entity_t camera,
+							  float dt) {
+		if (camera == ecs::NULL_ENTITY) {
+			spdlog::warn("tried to render a scene, but no camera exists!");
+			return;
+		}
+
+		auto entities = ecs.findMatching<GlRenderToken>();
+		if (entities.empty()) {
+			spdlog::warn("asked to render, but no entities are renderable");
+			return;
+		}
+
+		total += dt;
+
+		glEnable(GL_CULL_FACE);
+		glCullFace(GL_BACK);
+
+		glEnable(GL_DEPTH_TEST);
+
+		// camera logic
+		auto *const camTransform = ecs.get<math::Transform>(camera);
+		auto *const camComp = ecs.get<gfx::Camera>(camera);
+		glm::mat4 proj = glm::perspective(camComp->fov, camComp->aspectRatio,
+										  camComp->nearPlane, camComp->farPlane);
+		glm::mat4 view =
+			glm::lookAt(camTransform->origin(), camComp->target, camTransform->up());
+
+		// lighting
+		glm::vec3 lightPos = DEFAULT_LIGHTPOS;
+
+		// TODO(webpigeon): better performance if grouped by vao first
+		// TODO(webpigeon): the nvidia performance presentation said I shouldn't use
+		// uniforms for large data
+		for (auto &entity : entities) {
+			const auto &transform = ecs.get<fggl::math::Transform>(entity);
+			const auto &mesh = ecs.get<GlRenderToken>(entity);
+
+			glm::mat4 model = transform->model();
+			//	    model = glm::rotate(model, glm::radians(total/2048.0f * 360.0f),
+			//glm::vec3(0.0f,1.0f,0.0f));
+
+			auto shader = mesh->pipeline;
+			glUseProgram(shader);
+
+			glUniformMatrix4fv(glGetUniformLocation(shader, "model"), 1, GL_FALSE,
+							   glm::value_ptr(model));
+			glUniformMatrix4fv(glGetUniformLocation(shader, "view"), 1, GL_FALSE,
+							   glm::value_ptr(view));
+			glUniformMatrix4fv(glGetUniformLocation(shader, "projection"), 1, GL_FALSE,
+							   glm::value_ptr(proj));
+
+			// lighting
+			GLint lightID = glGetUniformLocation(shader, "lightPos");
+			if (lightID != -1) {
+				glUniform3fv(lightID, 1, glm::value_ptr(lightPos));
+			}
+
+			glBindVertexArray(mesh->vao);
+
+			if (mesh->renderType == GlRenderType::triangle_strip) {
+				glEnable(GL_PRIMITIVE_RESTART);
+				glPrimitiveRestartIndex(mesh->restartVertex);
+			}
+
+			glDrawElements(mesh->renderType, mesh->idxSize, GL_UNSIGNED_INT,
+						   reinterpret_cast<void *>(mesh->idxOffset));
+			if (mesh->renderType == GlRenderType::triangle_strip) {
+				glDisable(GL_PRIMITIVE_RESTART);
+			}
+		}
+
+		glBindVertexArray(0);
+	}
 
 }  // namespace fggl::gfx
diff --git a/fggl/gfx/window.cpp b/fggl/gfx/window.cpp
index a3cd411..0b973be 100644
--- a/fggl/gfx/window.cpp
+++ b/fggl/gfx/window.cpp
@@ -1,3 +1,4 @@
+#include <utility>
 #include <fggl/gfx/window.hpp>
 #include <fggl/gfx/window_input.hpp>
 
@@ -10,68 +11,19 @@
 
 using namespace fggl::gfx;
 
-void APIENTRY glDebugOutput(GLenum source,
-                            GLenum type,
-                            unsigned int id,
-                            GLenum severity,
-                            GLsizei length,
-                            const char *message,
-                            const void *userParam)
-{
-    // ignore non-significant error/warning codes
-    if(id == 131169 || id == 131185 || id == 131218 || id == 131204) return;
-
-    std::string fmt = "[GL] {}, {}: [{}]: {}";
-    std::string sourceStr = "unknown";
-    std::string typeStr = "unknown";
-
-    switch (source)
-    {
-        case GL_DEBUG_SOURCE_API:             sourceStr = "API"; break;
-        case GL_DEBUG_SOURCE_WINDOW_SYSTEM:   sourceStr = "Window System"; break;
-        case GL_DEBUG_SOURCE_SHADER_COMPILER: sourceStr = "Shader Compiler"; break;
-        case GL_DEBUG_SOURCE_THIRD_PARTY:     sourceStr = "Third Party"; break;
-        case GL_DEBUG_SOURCE_APPLICATION:     sourceStr = "Application"; break;
-        case GL_DEBUG_SOURCE_OTHER:           sourceStr = "Other"; break;
-    }
-
-    switch (type)
-    {
-        case GL_DEBUG_TYPE_ERROR:               typeStr = "Error"; break;
-        case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: typeStr = "Deprecated Behaviour"; break;
-        case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:  typeStr = "Undefined Behaviour"; break;
-        case GL_DEBUG_TYPE_PORTABILITY:         typeStr = "Portability"; break;
-        case GL_DEBUG_TYPE_PERFORMANCE:         typeStr = "Performance"; break;
-        case GL_DEBUG_TYPE_MARKER:              typeStr = "Marker"; break;
-        case GL_DEBUG_TYPE_PUSH_GROUP:          typeStr = "Push Group"; break;
-        case GL_DEBUG_TYPE_POP_GROUP:           typeStr = "Pop Group"; break;
-        case GL_DEBUG_TYPE_OTHER:               typeStr = "Other"; break;
-    }
-
-    switch (severity)
-    {
-        case GL_DEBUG_SEVERITY_HIGH:         spdlog::error(fmt, sourceStr, typeStr, id, message); break;
-        case GL_DEBUG_SEVERITY_MEDIUM:       spdlog::warn(fmt, sourceStr, typeStr, id, message); break;
-        case GL_DEBUG_SEVERITY_LOW:          spdlog::info(fmt, sourceStr, typeStr, id, message); break;
-        case GL_DEBUG_SEVERITY_NOTIFICATION: spdlog::debug(fmt, sourceStr, typeStr, id, message); break;
-    }
-}
 
 static void glfw_error(int code, const char* description) {
     spdlog::warn("[GLFW error] {}: {}", code, description);
 }
 
 static void framebuffer_resize(GLFWwindow* window, int width, int height) {
-	glfwMakeContextCurrent( window );
-	glViewport(0, 0, width, height);
-
-	auto fgglWindow = reinterpret_cast<GlfwWindow*>(glfwGetWindowUserPointer( window ));
+	auto *fgglWindow = static_cast<GlfwWindow*>(glfwGetWindowUserPointer( window ));
 	fgglWindow->framesize( width, height );
 }
 
 static void fggl_input_cursor(GLFWwindow* window, double x, double y) {
 	auto& input = GlfwInputManager::instance();
-	auto fgglWin = (GlfwWindow*)glfwGetWindowUserPointer(window);
+	auto *fgglWin = static_cast<GlfwWindow*>(glfwGetWindowUserPointer(window));
 
 	#ifndef FGGL_INPUT_SCREEN_COORDS
 		// convert to nice ranges...
@@ -190,45 +142,36 @@ void GlfwContext::pollEvents() {
 	fggl_joystick_poll();
 }
 
-GlfwWindow::GlfwWindow() : m_window(nullptr), m_framesize() {
+GlfwWindow::GlfwWindow(std::shared_ptr<GlfwContext> context) : m_context(std::move(context)), m_window(nullptr), m_framesize() {
     spdlog::debug("[glfw] creating window");
+
+	// FIXME - this ties the graphics API before window creation
 	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
 	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
 	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
-	glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, true);
+	glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE);
 
 	m_window = glfwCreateWindow(1920, 1080, "main", nullptr, nullptr);
 	if ( m_window == nullptr ) {
 		return;
 	}
 
-	activate();
-	auto result = glewInit();
-    spdlog::debug("[glfw] glew result: {}", result == GLEW_OK);
-
-	m_framesize = glm::vec2(1920, 1080);
 	glfwSetWindowUserPointer(m_window, this);
-	glfwSetFramebufferSizeCallback( m_window, framebuffer_resize );
 
-	// input functions
+	// setup callback wrappers (will invoke the methods via the user ptr above)
+	glfwSetFramebufferSizeCallback( m_window, framebuffer_resize );
 	glfwSetScrollCallback(m_window, fggl_input_scroll);
 	glfwSetCursorPosCallback(m_window, fggl_input_cursor);
 	glfwSetMouseButtonCallback(m_window, fggl_input_mouse_btn);
 	glfwSetKeyCallback(m_window, fggl_input_keyboard);
 
-	int flags; glGetIntegerv(GL_CONTEXT_FLAGS, &flags);
-	if (flags & GL_CONTEXT_FLAG_DEBUG_BIT) {
-		glEnable(GL_DEBUG_OUTPUT);
-		glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
-		glDebugMessageCallback(glDebugOutput, "DEBUG MODE");
-		glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE);
-	}
-
     spdlog::debug("[glfw] window creation complete");
 }
 
 GlfwWindow::~GlfwWindow() {
 	if ( m_window != nullptr ) {
+		// prevent dangling pointers
+		glfwSetWindowUserPointer(m_window, nullptr);
 		glfwDestroyWindow(m_window);
 		m_window = nullptr;
 	}
diff --git a/include/fggl/app.hpp b/include/fggl/app.hpp
index 6b9732a..14fb958 100644
--- a/include/fggl/app.hpp
+++ b/include/fggl/app.hpp
@@ -55,6 +55,7 @@ namespace fggl {
 			 * @param owner a non-owned reference to the owner of the state.
 			 */
 			explicit AppState(App &owner) : m_owner(owner) {}
+			virtual ~AppState() = default;
 
 			/**
 			 * Update the underlying model of this state.
@@ -92,9 +93,12 @@ namespace fggl {
 
 			// class is non copy-able
 			App(const App &app) = delete;
-			App &operator=(App other) = delete;
+			App(const App &&app) = delete;
 
-			inline void setWindow(std::unique_ptr<gfx::Window> &&window) {
+			App &operator=(const App& other) = delete;
+			App &operator=(App&& other) = delete;
+
+			inline void setWindow(std::unique_ptr<gfx::Window>&& window) {
 				m_window = std::move(window);
 			}
 
diff --git a/include/fggl/gfx/compat.hpp b/include/fggl/gfx/compat.hpp
index 32b766d..0dfdb9d 100644
--- a/include/fggl/gfx/compat.hpp
+++ b/include/fggl/gfx/compat.hpp
@@ -22,21 +22,21 @@ namespace fggl::gfx {
 	// fake module support - allows us to still RAII
 	//
 	struct ecsGlfwModule : ecs3::Module {
-		GlfwContext context;
+		std::shared_ptr<GlfwContext> context;
 
 		inline explicit
-		ecsGlfwModule(std::shared_ptr<fggl::input::Input> inputs) : context(std::move(inputs)) {
+		ecsGlfwModule(std::shared_ptr<fggl::input::Input> inputs) : context( std::make_shared<GlfwContext>(std::move(inputs))) {
 		}
 
 		inline
 		std::unique_ptr<GlfwWindow> createWindow(const std::string &title) {
-			auto window = std::make_unique<GlfwWindow>();
+			std::unique_ptr<GlfwWindow> window = std::make_unique<GlfwWindow>(context);
 			window->title(title);
-			return window;
+			return std::move(window);
 		}
 
 		void onUpdate() override {
-			context.pollEvents();
+			context->pollEvents();
 		}
 
 		[[nodiscard]]
diff --git a/include/fggl/gfx/ogl/renderer.hpp b/include/fggl/gfx/ogl/renderer.hpp
index b4fbb92..c88e9f3 100644
--- a/include/fggl/gfx/ogl/renderer.hpp
+++ b/include/fggl/gfx/ogl/renderer.hpp
@@ -45,6 +45,7 @@ namespace fggl::gfx {
 			~OpenGL4Backend() override = default;
 
 			void clear() override;
+			void resize(int width, int height) override;
 
 			void draw2D(const Paint &paint) override;
 
diff --git a/include/fggl/gfx/window.hpp b/include/fggl/gfx/window.hpp
index 3b24e29..36327b7 100644
--- a/include/fggl/gfx/window.hpp
+++ b/include/fggl/gfx/window.hpp
@@ -13,12 +13,17 @@
 
 namespace fggl::gfx {
 
+	class GlfwWindow;
+
 	class GlfwContext {
 		public:
 			explicit GlfwContext(std::shared_ptr<fggl::input::Input> input);
 			~GlfwContext();
 
 			void pollEvents();
+
+		private:
+			std::vector<std::unique_ptr<GlfwWindow>> m_windows;
 	};
 
 	enum MutWindowHint {
@@ -44,8 +49,9 @@ namespace fggl::gfx {
 
 	class GlfwWindow : public Window {
 		public:
-			GlfwWindow();
+			GlfwWindow(std::shared_ptr<GlfwContext> context);
 			~GlfwWindow();
+
 			GlfwWindow(Window &) = delete;
 			GlfwWindow(Window &&) = delete;
 
@@ -71,6 +77,9 @@ namespace fggl::gfx {
 
 			inline void framesize(int width, int height) {
 				m_framesize = math::vec2(width, height);
+				if ( m_graphics != nullptr )  {
+					m_graphics->resize( width, height );
+				}
 			}
 
 			// window manager stuff
@@ -136,6 +145,7 @@ namespace fggl::gfx {
 			}
 
 		private:
+			std::shared_ptr<GlfwContext> m_context;
 			GLFWwindow *m_window;
 			math::vec2 m_framesize;
 
diff --git a/include/fggl/gfx/windowing.hpp b/include/fggl/gfx/windowing.hpp
index 33e9f07..a307896 100644
--- a/include/fggl/gfx/windowing.hpp
+++ b/include/fggl/gfx/windowing.hpp
@@ -11,6 +11,7 @@ namespace fggl::gfx {
 		public:
 			virtual ~Graphics() = default;
 			virtual void clear() = 0;
+			virtual void resize(int width, int height) = 0;
 
 			virtual void draw2D(const Paint &paint) = 0;
 	};
diff --git a/include/fggl/math/triangulation.hpp b/include/fggl/math/triangulation.hpp
index fa7a20f..11578c8 100644
--- a/include/fggl/math/triangulation.hpp
+++ b/include/fggl/math/triangulation.hpp
@@ -119,33 +119,14 @@ namespace fggl::math {
 	/**
 	 * Fast Triangulation for convex polygons.
 	 */
-	void fanTriangulation(const Polygon &polygon, data::Mesh2D &mesh) {
-		assert(polygon.size() >= 3);
-
-		// add the first two points to the mesh
-		auto firstIdx = mesh.add_vertex(pointToVertex(polygon[0]));
-		auto prevIdx = mesh.add_vertex(pointToVertex(polygon[1]));
-
-		// deal with the indicies
-		const auto nTris = polygon.size() - 2;
-		for (auto i = 0; i < nTris; i++) {
-			mesh.add_index(firstIdx);
-			mesh.add_index(prevIdx);
-
-			auto currIdx = mesh.add_vertex(pointToVertex(polygon[i + 2]));
-			mesh.add_index(currIdx);
-			prevIdx = currIdx;
-		}
-	}
-
-	void fanTriangulation(const PolygonVertex polygon, data::Mesh2D &mesh) {
+	void fanTriangulation(const PolygonVertex& polygon, data::Mesh2D &mesh) {
 		assert(polygon.size() >= 3);
 
 		// add the first two points to the mesh
 		auto firstIdx = mesh.add_vertex(polygon[0]);
 		auto prevIdx = mesh.add_vertex(polygon[1]);
 
-		// deal with the indicies
+		// deal with the indices
 		const auto nTris = polygon.size() - 2;
 		for (auto i = 0; i < nTris; i++) {
 			mesh.add_index(firstIdx);
diff --git a/include/fggl/util/ownership.hpp b/include/fggl/util/ownership.hpp
new file mode 100644
index 0000000..b11056d
--- /dev/null
+++ b/include/fggl/util/ownership.hpp
@@ -0,0 +1,14 @@
+//
+// Created by webpigeon on 17/04/22.
+//
+
+#ifndef FGGL_UTIL_OWNERSHIP_HPP
+#define FGGL_UTIL_OWNERSHIP_HPP
+
+#include <memory>
+
+namespace fggl::util {
+
+} // namespace fggl::util
+
+#endif //FGGL_UTIL_OWNERSHIP_HPP
diff --git a/include/fggl/util/service.h b/include/fggl/util/service.h
index d7f6044..d80b3bd 100644
--- a/include/fggl/util/service.h
+++ b/include/fggl/util/service.h
@@ -29,9 +29,10 @@ namespace fggl::util {
 			}
 
 			template<typename T>
-			void supply(std::shared_ptr<T> ptr) {
+			std::shared_ptr<T> supply(std::shared_ptr<T> ptr) {
 				auto info = std::type_index(typeid(T));
 				m_services[info] = ptr;
+				return ptr;
 			}
 
 			template<typename T>
-- 
GitLab