From 210f146d12ca0b8104cb63bceb84314a7358a1a1 Mon Sep 17 00:00:00 2001
From: Joseph Walton-Rivers <joseph@walton-rivers.uk>
Date: Sun, 11 Sep 2022 20:47:26 +0100
Subject: [PATCH] work on unifying asset loading

---
 demo/data/rollball.yml                |   5 +
 demo/data/topdown.yml                 |   5 +
 fggl/entity/loader/loader.cpp         |  13 +-
 fggl/entity/module.cpp                |   8 +-
 fggl/gfx/ogl/renderer.cpp             |  10 +-
 fggl/gfx/ogl/shader.cpp               | 488 +++++++++++++-------------
 fggl/gfx/ogl4/canvas.cpp              |  20 +-
 fggl/gfx/ogl4/debug.cpp               |  13 +-
 fggl/gfx/ogl4/models.cpp              | 129 ++++---
 fggl/gfx/ogl4/module.cpp              |  89 ++++-
 fggl/gfx/ogl4/setup.cpp               |   2 +-
 fggl/gfx/window.cpp                   |   8 +-
 include/fggl/assets/loader.hpp        |   2 +-
 include/fggl/assets/manager.hpp       |  55 ++-
 include/fggl/assets/types.hpp         |   5 +-
 include/fggl/data/model.hpp           |   8 +-
 include/fggl/display/window.hpp       |   9 +-
 include/fggl/entity/loader/loader.hpp |   9 +-
 include/fggl/gfx/ogl/renderer.hpp     |   4 +-
 include/fggl/gfx/ogl/shader.hpp       |  14 +-
 include/fggl/gfx/ogl/types.hpp        |   6 +-
 include/fggl/gfx/ogl4/canvas.hpp      |   6 +-
 include/fggl/gfx/ogl4/debug.hpp       |   4 +-
 include/fggl/gfx/ogl4/models.hpp      |  46 ++-
 include/fggl/gfx/ogl4/module.hpp      |   6 +-
 include/fggl/gfx/ogl4/setup.hpp       |  17 +-
 include/fggl/gfx/setup.hpp            |  36 +-
 include/fggl/phys/null.hpp            |   5 +-
 28 files changed, 616 insertions(+), 406 deletions(-)

diff --git a/demo/data/rollball.yml b/demo/data/rollball.yml
index bdf74de..015c226 100644
--- a/demo/data/rollball.yml
+++ b/demo/data/rollball.yml
@@ -13,6 +13,7 @@ prefabs:
       Transform:
       StaticMesh:
         pipeline: redbook/debug
+        shape_id: "rb_wall_x"
         shape:
           type: box
           scale: [1.0, 5.0, 41]
@@ -28,6 +29,7 @@ prefabs:
       Transform:
       StaticMesh:
         pipeline: redbook/debug
+        shape_id: "rb_wall_z"
         shape:
           type: box
           scale: [39, 5, 1]
@@ -42,6 +44,7 @@ prefabs:
       Transform:
       StaticMesh:
         pipeline: redbook/debug
+        shape_id: "rb_floor"
         shape:
           type: box # we don't (currently) support planes...
           scale: [39, 0.5, 39]
@@ -55,6 +58,7 @@ prefabs:
       Transform:
       StaticMesh:
         pipeline: redbook/debug
+        shape_id: "rb_player"
         shape:
           type: sphere
       gfx::material:
@@ -71,6 +75,7 @@ prefabs:
       Transform:
       StaticMesh:
         pipeline: redbook/debug
+        shape_id: "rb_collect"
         shape:
           type: box
       gfx::material:
diff --git a/demo/data/topdown.yml b/demo/data/topdown.yml
index 0b1c8c5..bf6f533 100644
--- a/demo/data/topdown.yml
+++ b/demo/data/topdown.yml
@@ -5,6 +5,7 @@ prefabs:
       Transform:
       StaticMesh:
         pipeline: redbook/debug
+        shape_id: td_wall_x
         shape:
           type: box
           scale: [1.0, 5.0, 41]
@@ -24,6 +25,7 @@ prefabs:
       Transform:
       StaticMesh:
         pipeline: redbook/debug
+        shape_id: td_wall_y
         shape:
           type: box
           scale: [39, 5, 1]
@@ -42,6 +44,7 @@ prefabs:
       Transform:
       StaticMesh:
         pipeline: redbook/debug
+        shape_id: td_floor
         shape:
           type: box # we don't (currently) support planes...
           scale: [39, 0.5, 39]
@@ -60,6 +63,7 @@ prefabs:
       Transform:
       StaticMesh:
         pipeline: redbook/lighting
+        shape_id: td_player
         shape:
           type: sphere
       gfx::material:
@@ -76,6 +80,7 @@ prefabs:
       Transform:
       StaticMesh:
         pipeline: redbook/lighting
+        shape_id: td_collect
         shape:
           type: box
       gfx::material:
diff --git a/fggl/entity/loader/loader.cpp b/fggl/entity/loader/loader.cpp
index 316aae5..c38715b 100644
--- a/fggl/entity/loader/loader.cpp
+++ b/fggl/entity/loader/loader.cpp
@@ -21,7 +21,7 @@
 
 namespace fggl::entity {
 
-	assets::AssetRefRaw load_prototype(EntityFactory *factory, const assets::AssetGUID &guid, assets::AssetData data) {
+	assets::AssetRefRaw load_prototype(assets::Loader* loader, EntityFactory *factory, const assets::AssetGUID &guid, assets::AssetData data) {
 		auto *filePath = std::get<assets::AssetPath *>(data);
 
 		// We need to process the prototypes, and load them into the asset system.
@@ -31,10 +31,7 @@ namespace fggl::entity {
 
 			for (const auto &prefab : prefabs) {
 				auto name = prefab["name"].as<fggl::util::GUID>();
-
-				#ifndef NDEBUG
-				debug::info("found prefab: {}", fggl::util::guidToString(name));
-				#endif
+				debug::info("found prefab: {}", name);
 
 				// set up the components
 				EntitySpec entity{};
@@ -48,11 +45,7 @@ namespace fggl::entity {
 					entity.components[compId] = compSpec;
 					entity.ordering.push_back(compId);
 
-					#ifndef NDEBUG
-					debug::trace("{} has component {}",
-								 fggl::util::guidToString(name),
-								 fggl::util::guidToString(compId));
-					#endif
+					debug::trace("prefab {} has component {}", name, compId);
 				}
 
 				factory->define(name, entity);
diff --git a/fggl/entity/module.cpp b/fggl/entity/module.cpp
index c2936f4..752b108 100644
--- a/fggl/entity/module.cpp
+++ b/fggl/entity/module.cpp
@@ -21,7 +21,7 @@
 
 namespace fggl::entity {
 
-	void make_transform(const entity::ComponentSpec &spec, EntityManager &manager, const entity::EntityID entity) {
+	void make_transform(const entity::ComponentSpec &spec, EntityManager &manager, const entity::EntityID entity, modules::Services &/*svc*/) {
 		auto &transform = manager.add<math::Transform>(entity);
 		transform.origin(spec.get<math::vec3>("origin", math::VEC3_ZERO));
 		transform.euler(spec.get<math::vec3>("rotation", math::VEC3_ZERO));
@@ -36,13 +36,13 @@ namespace fggl::entity {
 
 	bool ECS::factory(modules::ModuleService service, modules::Services &services) {
 		if (service == EntityFactory::service) {
-			auto *factory = services.create<EntityFactory>();
+			auto *factory = services.create<EntityFactory>(services);
 			install_component_factories(factory);
 
 			// we are responsible for prefabs...
 			auto *assetLoader = services.get<assets::Loader>();
-			assetLoader->setFactory(PROTOTYPE_ASSET, [factory](const assets::AssetGUID &a, assets::AssetData b) {
-				return load_prototype(factory, a, b);
+			assetLoader->setFactory(PROTOTYPE_ASSET, [factory](assets::Loader* loader, const assets::AssetGUID &a, assets::AssetData b) {
+				return load_prototype(loader, factory, a, b);
 			}, assets::LoadType::PATH);
 
 			return true;
diff --git a/fggl/gfx/ogl/renderer.cpp b/fggl/gfx/ogl/renderer.cpp
index b92d80e..e95da19 100644
--- a/fggl/gfx/ogl/renderer.cpp
+++ b/fggl/gfx/ogl/renderer.cpp
@@ -139,8 +139,8 @@ namespace fggl::gfx {
 	using data::Mesh2D;
 	using data::Vertex2D;
 
-	OpenGL4Backend::OpenGL4Backend(data::Storage *storage, gui::FontLibrary *fonts)
-		: fggl::gfx::Graphics(), m_canvasPipeline(INVALID_SHADER_ID), m_storage(storage) {
+	OpenGL4Backend::OpenGL4Backend(data::Storage *storage, gui::FontLibrary *fonts, assets::AssetManager *assets)
+		: fggl::gfx::Graphics(), m_canvasPipeline(nullptr), m_storage(storage) {
 		// initialise GLEW, or fail
 		// FIXME this binds the graphics stack to GLFW :'(
 		int version = gladLoadGLLoader((GLADloadproc) glfwGetProcAddress);
@@ -169,7 +169,7 @@ namespace fggl::gfx {
 		// setup 2D rendering system
 		ShaderConfig shader2DConfig = ShaderConfig::named("canvas");
 		m_canvasPipeline = m_cache->load(shader2DConfig);
-		if (m_canvasPipeline == INVALID_SHADER_ID) {
+		if (m_canvasPipeline == nullptr) {
 			debug::error("failed to load shader2D - using fallback");
 			m_canvasPipeline = m_cache->get(ogl4::FALLBACK_CANVAS_PIPELINE);
 		}
@@ -181,7 +181,7 @@ namespace fggl::gfx {
 
 		// rendering helpers
 		m_canvasRenderer = std::make_unique<ogl4::CanvasRenderer>(fonts);
-		m_modelRenderer = std::make_unique<ogl4::StaticModelRenderer>(m_cache.get());
+		m_modelRenderer = std::make_unique<ogl4::StaticModelRenderer>(m_cache.get(), assets);
 
 		m_debugRenderer = std::make_unique<ogl4::DebugRenderer>(m_cache->getOrLoad(ShaderConfig::named("debug")));
 		if (m_debugRenderer) {
@@ -199,7 +199,7 @@ namespace fggl::gfx {
 			return;
 		}
 
-		m_canvasRenderer->render(m_canvasPipeline, paint);
+		m_canvasRenderer->render(*m_canvasPipeline, paint);
 	}
 
 	void OpenGL4Backend::drawScene(entity::EntityManager &world) {
diff --git a/fggl/gfx/ogl/shader.cpp b/fggl/gfx/ogl/shader.cpp
index 953751d..2d95c2d 100644
--- a/fggl/gfx/ogl/shader.cpp
+++ b/fggl/gfx/ogl/shader.cpp
@@ -20,325 +20,329 @@
 #include <vector>
 #include <spdlog/spdlog.h>
 
-using namespace fggl::gfx;
-
-bool ShaderCache::compileShaderFromSource(const std::string &source, GLuint sid) {
-	// upload and compile shader
-	const char *src = source.c_str();
-	glShaderSource(sid, 1, &src, nullptr);
-	glCompileShader(sid);
-
-	// check it worked
-	GLint compiled = GL_FALSE;
-	glGetShaderiv(sid, GL_COMPILE_STATUS, &compiled);
-	if (compiled == GL_FALSE) {
-
-		GLint maxLength = 0;
-		glGetShaderiv(sid, GL_INFO_LOG_LENGTH, &maxLength);
-		char *infoLog = new char[maxLength];
-		glGetShaderInfoLog(sid, maxLength, &maxLength, infoLog);
-
-		spdlog::warn("could not compile shader source: {}", infoLog);
-		delete[] infoLog;
-		return false;
+namespace fggl::gfx {
+
+	bool ShaderCache::compileShaderFromSource(const std::string &source, GLuint sid) {
+		// upload and compile shader
+		const char *src = source.c_str();
+		glShaderSource(sid, 1, &src, nullptr);
+		glCompileShader(sid);
+
+		// check it worked
+		GLint compiled = GL_FALSE;
+		glGetShaderiv(sid, GL_COMPILE_STATUS, &compiled);
+		if (compiled == GL_FALSE) {
+
+			GLint maxLength = 0;
+			glGetShaderiv(sid, GL_INFO_LOG_LENGTH, &maxLength);
+			char *infoLog = new char[maxLength];
+			glGetShaderInfoLog(sid, maxLength, &maxLength, infoLog);
+
+			spdlog::warn("could not compile shader source: {}", infoLog);
+			delete[] infoLog;
+			return false;
+		}
+
+		return true;
 	}
 
-	return true;
-}
+	bool ShaderCache::readAndCompileShader(const std::string &filename, GLuint shader) {
+		std::string source;
+		bool result = m_storage->load(fggl::data::Data, filename, &source);
+		if (!result) {
+			spdlog::warn("could not load shader source from disk: {}", filename.c_str());
+			return false;
+		}
 
-bool ShaderCache::readAndCompileShader(const std::string &filename, GLuint shader) {
-	std::string source;
-	bool result = m_storage->load(fggl::data::Data, filename, &source);
-	if (!result) {
-		spdlog::warn("could not load shader source from disk: {}", filename.c_str());
-		return false;
+		return compileShaderFromSource(source, shader);
 	}
 
-	return compileShaderFromSource(source, shader);
-}
+	ShaderCache::ShaderCache(fggl::data::Storage *storage) : m_storage(storage), m_shaders(), m_binary(true) {
 
-ShaderCache::ShaderCache(fggl::data::Storage *storage) : m_storage(storage), m_shaders(), m_binary(true) {
+		if (!GLAD_GL_ARB_get_program_binary) {
+			spdlog::warn("the graphics card doesn support shader caching, disabling");
+			m_binary = false;
+		} else {
+			// debug - disable shader cache
+			m_binary = false;
+		}
 
-	if (!GLAD_GL_ARB_get_program_binary) {
-		spdlog::warn("the graphics card doesn support shader caching, disabling");
-		m_binary = false;
-	} else {
-		// debug - disable shader cache
-		m_binary = false;
+		if (GLAD_GL_ARB_shading_language_include) {
+			setupIncludes();
+		}
+		initFallbackPipelines();
 	}
 
-	if (GLAD_GL_ARB_shading_language_include) {
-		setupIncludes();
-	}
-	initFallbackPipelines();
-}
+	void ShaderCache::setupIncludes() {
+		auto root = m_storage->resolvePath(data::StorageType::Data, "include");
+		auto paths = m_storage->findResources(root, ".glsl");
 
-void ShaderCache::setupIncludes() {
-	auto root = m_storage->resolvePath(data::StorageType::Data, "include");
-	auto paths = m_storage->findResources(root, ".glsl");
+		for (auto &path : paths) {
+			std::string source;
+			m_storage->load(fggl::data::Data, path.string(), &source);
 
-	for (auto &path : paths) {
-		std::string source;
-		m_storage->load(fggl::data::Data, path.string(), &source);
+			auto relPath = std::filesystem::relative(path, root);
+			const auto relPathStr = "/" + relPath.string();
+			glNamedStringARB(GL_SHADER_INCLUDE_ARB, -1, relPathStr.c_str(), -1, source.c_str());
+		}
 
-		auto relPath = std::filesystem::relative(path, root);
-		const auto relPathStr = "/" + relPath.string();
-		glNamedStringARB(GL_SHADER_INCLUDE_ARB, -1, relPathStr.c_str(), -1, source.c_str());
 	}
 
-}
+	bool ShaderCache::loadFromDisk(GLuint pid, const std::string &pipelineName) {
 
-bool ShaderCache::loadFromDisk(GLuint pid, const std::string &pipelineName) {
+		BinaryCache cache;
+		auto fname = "shader_" + pipelineName + ".bin";
+		bool status = m_storage->load(fggl::data::Cache, fname, &cache);
 
-	BinaryCache cache;
-	auto fname = "shader_" + pipelineName + ".bin";
-	bool status = m_storage->load(fggl::data::Cache, fname, &cache);
+		if (!status) {
+			spdlog::info("cached shader '{}' could not be loaded from disk", pipelineName);
+			return false;
+		}
 
-	if (!status) {
-		spdlog::info("cached shader '{}' could not be loaded from disk", pipelineName);
-		return false;
+		bool result = cacheLoad(pid, &cache);
+		std::free(cache.data);
+		return result;
 	}
 
-	bool result = cacheLoad(pid, &cache);
-	std::free(cache.data);
-	return result;
-}
-
-void ShaderCache::saveToDisk(GLuint pid, const std::string &pipelineName) {
-	BinaryCache cache;
-	cacheSave(pid, &cache);
+	void ShaderCache::saveToDisk(GLuint pid, const std::string &pipelineName) {
+		BinaryCache cache;
+		cacheSave(pid, &cache);
 
-	auto fname = "shader_" + pipelineName + ".bin";
-	m_storage->save(fggl::data::Cache, fname, &cache);
-}
+		auto fname = "shader_" + pipelineName + ".bin";
+		m_storage->save(fggl::data::Cache, fname, &cache);
+	}
 
-GLuint ShaderCache::getOrLoad(const ShaderConfig &config) {
-	try {
-		return m_shaders.at(config.name);
-	} catch (std::out_of_range &e) {
-		return load(config);
+	ShaderCache::ShaderPtr ShaderCache::getOrLoad(const ShaderConfig &config) {
+		try {
+			return m_shaders.at(config.name);
+		} catch (std::out_of_range &e) {
+			return load(config);
+		}
 	}
-}
 
-GLuint ShaderCache::get(const std::string &name) {
-	return m_shaders.at(name);
-}
+	ShaderCache::ShaderPtr ShaderCache::get(const std::string &name) {
+		return m_shaders.at(name);
+	}
 
-GLuint ShaderCache::load(const ShaderConfig &config) {
+	ShaderCache::ShaderPtr ShaderCache::load(const ShaderConfig &config) {
+		spdlog::debug("starting shader program generation for {}", config.name);
 
-	spdlog::debug("starting shader program generation for {}", config.name);
+		GLuint pid = glCreateProgram();
 
-	GLuint pid = glCreateProgram();
+		if (m_binary) {
+			// if we have support for shader cache, give that a go
+			bool worked = loadFromDisk(pid, config.name);
+			if (worked) {
+				auto shader = std::make_shared<ogl::Shader>(pid);
+				m_shaders[config.name] = shader;
+				return shader;
+			}
 
-	if (m_binary) {
-		// if we have support for shader cache, give that a go
-		bool worked = loadFromDisk(pid, config.name);
-		if (worked) {
-			m_shaders[config.name] = pid;
-			return pid;
+			spdlog::debug("could not use cached shader for '{}', doing full compile.", config.name);
 		}
 
-		spdlog::debug("could not use cached shader for '{}', doing full compile.", config.name);
-	}
+		// TODO actual shader loading
+		GLuint vertShader = glCreateShader(GL_VERTEX_SHADER);
+		readAndCompileShader(config.vertex, vertShader);
+		glAttachShader(pid, vertShader);
 
-	// TODO actual shader loading
-	GLuint vertShader = glCreateShader(GL_VERTEX_SHADER);
-	readAndCompileShader(config.vertex, vertShader);
-	glAttachShader(pid, vertShader);
+		GLuint fragShader = glCreateShader(GL_FRAGMENT_SHADER);
+		readAndCompileShader(config.fragment, fragShader);
+		glAttachShader(pid, fragShader);
 
-	GLuint fragShader = glCreateShader(GL_FRAGMENT_SHADER);
-	readAndCompileShader(config.fragment, fragShader);
-	glAttachShader(pid, fragShader);
+		GLuint geomShader = 0;
+		if (config.hasGeom) {
+			geomShader = glCreateShader(GL_GEOMETRY_SHADER);
+			readAndCompileShader(config.geometry, geomShader);
+			glAttachShader(pid, geomShader);
+		}
 
-	GLuint geomShader = 0;
-	if (config.hasGeom) {
-		geomShader = glCreateShader(GL_GEOMETRY_SHADER);
-		readAndCompileShader(config.geometry, geomShader);
-		glAttachShader(pid, geomShader);
-	}
+		glLinkProgram(pid);
+		glDetachShader(pid, vertShader);
+		glDetachShader(pid, fragShader);
 
-	glLinkProgram(pid);
-	glDetachShader(pid, vertShader);
-	glDetachShader(pid, fragShader);
+		if (config.hasGeom) {
+			glDetachShader(pid, geomShader);
+		}
 
-	if (config.hasGeom) {
-		glDetachShader(pid, geomShader);
-	}
+		GLint linked = GL_FALSE;
+		glGetProgramiv(pid, GL_LINK_STATUS, &linked);
+		if (linked == GL_FALSE) {
 
-	GLint linked = GL_FALSE;
-	glGetProgramiv(pid, GL_LINK_STATUS, &linked);
-	if (linked == GL_FALSE) {
+			// get the error
+			std::array<char, 512> infoLog;
+			glGetProgramInfoLog(pid, infoLog.size(), nullptr, infoLog.data());
+			spdlog::warn("linking shader program '{}' failed: {}", config.name, infoLog.data());
 
-		// get the error
-		std::array<char, 512> infoLog;
-		glGetProgramInfoLog(pid, infoLog.size(), nullptr, infoLog.data());
-		spdlog::warn("linking shader program '{}' failed: {}", config.name, infoLog.data());
+			// cleanup
+			glDeleteProgram(pid);
+			glDeleteShader(vertShader);
+			glDeleteShader(fragShader);
+			if (config.hasGeom) {
+				glDeleteShader(geomShader);
+			}
 
-		// cleanup
-		glDeleteProgram(pid);
-		glDeleteShader(vertShader);
-		glDeleteShader(fragShader);
-		if (config.hasGeom) {
-			glDeleteShader(geomShader);
+			return nullptr;
 		}
 
-		return INVALID_SHADER_ID;
-	}
+		if (m_binary) {
+			saveToDisk(pid, config.name);
+		}
 
-	if (m_binary) {
-		saveToDisk(pid, config.name);
+		// update the cache and return
+		auto shaderPtr = std::make_shared<ogl::Shader>(pid);
+		m_shaders[config.name] = shaderPtr;
+		return shaderPtr;
 	}
 
-	// update the cache and return
-	m_shaders[config.name] = pid;
-	return pid;
-}
+	ShaderCache::ShaderPtr ShaderCache::load(const ShaderSources &sources, bool allowBinaryCache) {
 
-GLuint ShaderCache::load(const ShaderSources &sources, bool allowBinaryCache) {
+		spdlog::debug("starting shader program generation for {}", sources.name);
 
-	spdlog::debug("starting shader program generation for {}", sources.name);
+		GLuint pid = glCreateProgram();
 
-	GLuint pid = glCreateProgram();
+		if (m_binary && allowBinaryCache) {
+			// if we have support for shader cache, give that a go
+			bool worked = loadFromDisk(pid, sources.name);
+			if (worked) {
+				auto shader = std::make_shared<ogl::Shader>(pid);
+				m_shaders[sources.name] = shader;
+				return shader;
+			}
 
-	if (m_binary && allowBinaryCache) {
-		// if we have support for shader cache, give that a go
-		bool worked = loadFromDisk(pid, sources.name);
-		if (worked) {
-			m_shaders[sources.name] = pid;
-			return pid;
+			spdlog::debug("could not use cached shader for '{}', doing full compile.", sources.name);
 		}
 
-		spdlog::debug("could not use cached shader for '{}', doing full compile.", sources.name);
-	}
+		// TODO actual shader loading
+		GLuint vertShader = glCreateShader(GL_VERTEX_SHADER);
+		compileShaderFromSource(sources.vertexSource, vertShader);
+		glAttachShader(pid, vertShader);
 
-	// TODO actual shader loading
-	GLuint vertShader = glCreateShader(GL_VERTEX_SHADER);
-	compileShaderFromSource(sources.vertexSource, vertShader);
-	glAttachShader(pid, vertShader);
+		GLuint fragShader = glCreateShader(GL_FRAGMENT_SHADER);
+		compileShaderFromSource(sources.fragmentSource, fragShader);
+		glAttachShader(pid, fragShader);
 
-	GLuint fragShader = glCreateShader(GL_FRAGMENT_SHADER);
-	compileShaderFromSource(sources.fragmentSource, fragShader);
-	glAttachShader(pid, fragShader);
+		GLuint geomShader = INVALID_SHADER_ID;
+		if (!sources.geometrySource.empty()) {
+			geomShader = glCreateShader(GL_GEOMETRY_SHADER);
+			compileShaderFromSource(sources.geometrySource, geomShader);
+			glAttachShader(pid, geomShader);
+		}
 
-	GLuint geomShader = INVALID_SHADER_ID;
-	if (!sources.geometrySource.empty()) {
-		geomShader = glCreateShader(GL_GEOMETRY_SHADER);
-		compileShaderFromSource(sources.geometrySource, geomShader);
-		glAttachShader(pid, geomShader);
-	}
+		glLinkProgram(pid);
+		glDetachShader(pid, vertShader);
+		glDetachShader(pid, fragShader);
 
-	glLinkProgram(pid);
-	glDetachShader(pid, vertShader);
-	glDetachShader(pid, fragShader);
+		if (geomShader != INVALID_SHADER_ID) {
+			glDetachShader(pid, geomShader);
+		}
 
-	if (geomShader != INVALID_SHADER_ID) {
-		glDetachShader(pid, geomShader);
-	}
+		GLint linked = GL_FALSE;
+		glGetProgramiv(pid, GL_LINK_STATUS, &linked);
+		if (linked == GL_FALSE) {
 
-	GLint linked = GL_FALSE;
-	glGetProgramiv(pid, GL_LINK_STATUS, &linked);
-	if (linked == GL_FALSE) {
+			// get the error
+			std::array<char, 512> infoLog{};
+			glGetProgramInfoLog(pid, infoLog.size(), nullptr, infoLog.data());
+			spdlog::warn("linking shader program '{}' failed: {}", sources.name, infoLog.data());
 
-		// get the error
-		std::array<char, 512> infoLog;
-		glGetProgramInfoLog(pid, infoLog.size(), nullptr, infoLog.data());
-		spdlog::warn("linking shader program '{}' failed: {}", sources.name, infoLog.data());
+			// cleanup
+			glDeleteProgram(pid);
+			glDeleteShader(vertShader);
+			glDeleteShader(fragShader);
+			if (geomShader != INVALID_SHADER_ID) {
+				glDeleteShader(geomShader);
+			}
 
-		// cleanup
-		glDeleteProgram(pid);
-		glDeleteShader(vertShader);
-		glDeleteShader(fragShader);
-		if (geomShader != INVALID_SHADER_ID) {
-			glDeleteShader(geomShader);
+			return nullptr;
 		}
 
-		return INVALID_SHADER_ID;
-	}
+		if (m_binary && allowBinaryCache) {
+			saveToDisk(pid, sources.name);
+		}
 
-	if (m_binary && allowBinaryCache) {
-		saveToDisk(pid, sources.name);
+		// update the cache and return
+		m_shaders[sources.name] = std::make_shared<ogl::Shader>(pid);
+		return m_shaders[sources.name];
 	}
 
-	// update the cache and return
-	m_shaders[sources.name] = pid;
-	return pid;
-}
+	void ShaderCache::cacheSave(GLuint pid, BinaryCache *cache) {
+		GLsizei length;
+		glGetProgramiv(pid, GL_PROGRAM_BINARY_LENGTH, &length);
 
-void ShaderCache::cacheSave(GLuint pid, BinaryCache *cache) {
-	GLsizei length;
-	glGetProgramiv(pid, GL_PROGRAM_BINARY_LENGTH, &length);
+		cache->data = std::malloc(length);
+		cache->size = length;
 
-	cache->data = std::malloc(length);
-	cache->size = length;
+		glGetProgramBinary(pid, length, &cache->size, &cache->format, cache->data);
+	}
 
-	glGetProgramBinary(pid, length, &cache->size, &cache->format, cache->data);
-}
+	bool ShaderCache::cacheLoad(GLuint pid, const BinaryCache *cache) {
+		if (!m_binary) {
+			return false;
+		}
+		glProgramBinary(pid, cache->format, cache->data, cache->size);
 
-bool ShaderCache::cacheLoad(GLuint pid, const BinaryCache *cache) {
-	if (!m_binary) {
-		return false;
+		// check it loaded correctly
+		GLint status = GL_FALSE;
+		glGetProgramiv(pid, GL_LINK_STATUS, &status);
+		return status == GL_TRUE;
 	}
-	glProgramBinary(pid, cache->format, cache->data, cache->size);
 
-	// check it loaded correctly
-	GLint status = GL_FALSE;
-	glGetProgramiv(pid, GL_LINK_STATUS, &status);
-	return status == GL_TRUE;
-}
+	void ShaderCache::initFallbackPipelines() {
+		// canvas fallback pipeline
+		load({
+				 .name = ogl4::FALLBACK_CANVAS_PIPELINE,
+				 .vertexSource = ogl4::FALLBACK_CANVAS_VERTEX_SHADER,
+				 .fragmentSource = ogl4::FALLBACK_CANVAS_FRAGMENT_SHADER,
+				 .geometrySource = ""
+			 }, false);
+	}
 
-void ShaderCache::initFallbackPipelines() {
-	// canvas fallback pipeline
-	load({
-			 .name = ogl4::FALLBACK_CANVAS_PIPELINE,
-			 .vertexSource = ogl4::FALLBACK_CANVAS_VERTEX_SHADER,
-			 .fragmentSource = ogl4::FALLBACK_CANVAS_FRAGMENT_SHADER,
-			 .geometrySource = ""
-		 }, false);
 }
 
-template<>
-bool fggl::data::fggl_deserialize(std::filesystem::path &data, fggl::gfx::BinaryCache *out) {
-	auto f =
-		#ifdef _MSC_VER
-		_wfopen(data.c_str(), L"r");
-		#else
-		std::fopen(data.c_str(), "r");
-	#endif
+	template<>
+	bool fggl::data::fggl_deserialize(std::filesystem::path &data, fggl::gfx::BinaryCache *out) {
+		auto f =
+			#ifdef _MSC_VER
+			_wfopen(data.c_str(), L"r");
+			#else
+			std::fopen(data.c_str(), "r");
+		#endif
+
+		if (f == nullptr) {
+			spdlog::warn("could not load cached shader, fp was null");
+			return false;
+		}
 
-	if (f == nullptr) {
-		spdlog::warn("could not load cached shader, fp was null");
-		return false;
-	}
+		auto rsize = std::fread(&out->format, sizeof(GLenum), 1, f);
+		if (rsize != 1) {
+			spdlog::warn("could not load cached shader: type read failed");
+			std::fclose(f);
+			return false;
+		}
 
-	auto rsize = std::fread(&out->format, sizeof(GLenum), 1, f);
-	if (rsize != 1) {
-		spdlog::warn("could not load cached shader: type read failed");
-		std::fclose(f);
-		return false;
-	}
+		out->size = 0;
+		rsize = std::fread(&out->size, sizeof(GLsizei), 1, f);
+		if (rsize != 1) {
+			spdlog::warn("could not load cached shader: size read failed");
+			std::fclose(f);
+			return false;
+		}
 
-	out->size = 0;
-	rsize = std::fread(&out->size, sizeof(GLsizei), 1, f);
-	if (rsize != 1) {
-		spdlog::warn("could not load cached shader: size read failed");
-		std::fclose(f);
-		return false;
-	}
+		out->data = std::malloc(out->size);
+		auto readSize = std::fread(out->data, out->size, 1, f);
 
-	out->data = std::malloc(out->size);
-	auto readSize = std::fread(out->data, out->size, 1, f);
+		auto result = true;
+		if (readSize != 1) {
+			spdlog::warn("could not load cached shader: reading failed!");
+			std::free(out->data);
+			result = false;
+		}
 
-	auto result = true;
-	if (readSize != 1) {
-		spdlog::warn("could not load cached shader: reading failed!");
-		std::free(out->data);
-		result = false;
+		std::fclose(f);
+		return result;
 	}
 
-	std::fclose(f);
-	return result;
-}
-
 #include <iostream>
 #include <fstream>
 
diff --git a/fggl/gfx/ogl4/canvas.cpp b/fggl/gfx/ogl4/canvas.cpp
index 750e884..ca3f430 100644
--- a/fggl/gfx/ogl4/canvas.cpp
+++ b/fggl/gfx/ogl4/canvas.cpp
@@ -114,7 +114,7 @@ namespace fggl::gfx::ogl4 {
 		glBindVertexArray(0);
 	}
 
-	void CanvasRenderer::renderShapes(const gfx::Paint &paint, GLuint shader) {
+	void CanvasRenderer::renderShapes(const gfx::Paint &paint, ogl::Shader& shader) {
 		data::Mesh2D mesh;
 		convert_to_mesh(paint, mesh);
 
@@ -132,12 +132,11 @@ namespace fggl::gfx::ogl4 {
 		glDisable(GL_DEPTH_TEST);
 		glDisable(GL_CULL_FACE);
 
-		// FIXME: this should be abstracted into the shader class
-		glUseProgram(shader);
 		auto projMat = glm::ortho(m_bounds.left, m_bounds.right, m_bounds.bottom, m_bounds.top);
-		glUniformMatrix4fv(glGetUniformLocation(shader, "projection"), 1, GL_FALSE,
-						   glm::value_ptr(projMat));
+		shader.use();
+		shader.setUniformMtx(shader.uniform("projection"), projMat);
 
+		// draw elements
 		m_vao.drawElements(m_indexList, ogl::Primative::TRIANGLE, mesh.indexList.size());
 
 		// cleanup
@@ -147,7 +146,7 @@ namespace fggl::gfx::ogl4 {
 	}
 
 	// slow version
-	void CanvasRenderer::renderText(const Paint &paint, GLuint shader) {
+	void CanvasRenderer::renderText(const Paint &paint, ogl::Shader& shader) {
 		if (paint.textCmds().empty()) {
 			return;
 		}
@@ -161,10 +160,9 @@ namespace fggl::gfx::ogl4 {
 		}
 
 		// setup the shader
-		glUseProgram(shader);
+		shader.use();
 		auto projMat = glm::ortho(0.0f, 1920.0f, 1080.0f, 0.f);
-		glUniformMatrix4fv(glGetUniformLocation(shader, "projection"), 1, GL_FALSE,
-						   glm::value_ptr(projMat));
+		shader.setUniformMtx(shader.uniform("projection"), projMat);
 
 		// bind the vbo we'll use for writing
 		m_vao.bind();
@@ -196,7 +194,7 @@ namespace fggl::gfx::ogl4 {
 				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
 
 				m_fontTex.bind(1);
-				glUniform1i(glGetUniformLocation(shader, "tex"), 1);
+				shader.setUniformI(shader.uniform("tex"), 1);
 
 				// this is why this is called the slow version, we render each quad as a single call
 				auto &metrics = face->metrics(letter);
@@ -277,7 +275,7 @@ namespace fggl::gfx::ogl4 {
 		glUseProgram( 0 );
 	}*/
 
-	void CanvasRenderer::render(GLuint shader, const gfx::Paint &paint) {
+	void CanvasRenderer::render(ogl::Shader& shader, const gfx::Paint &paint) {
 		renderShapes(paint, shader);
 		renderText(paint, shader);
 	}
diff --git a/fggl/gfx/ogl4/debug.cpp b/fggl/gfx/ogl4/debug.cpp
index e84a000..cf0d8cc 100644
--- a/fggl/gfx/ogl4/debug.cpp
+++ b/fggl/gfx/ogl4/debug.cpp
@@ -21,13 +21,14 @@
 #include "fggl/gfx/ogl4/debug.hpp"
 
 #include <cassert>
+#include <utility>
 
 namespace fggl::gfx::ogl4 {
 
-	DebugRenderer::DebugRenderer(GLuint shader) :
-		mvpMatrix(1.0f),
-		m_lineShader(shader),
-		m_lineShaderMVP(m_lineShader.uniform("u_MvpMatrix")) {
+	DebugRenderer::DebugRenderer(std::shared_ptr<ogl::Shader> shader) :
+		mvpMatrix(1.0F),
+		m_lineShader(std::move(shader)),
+		m_lineShaderMVP(m_lineShader->uniform("u_MvpMatrix")) {
 		// define our attributes
 		auto posAttr = ogl::attribute<dd::DrawVertex, math::vec3>(0);
 		auto colAttr = ogl::attribute<dd::DrawVertex, math::vec3>(sizeof(float) * 3);
@@ -42,8 +43,8 @@ namespace fggl::gfx::ogl4 {
 		assert(count > 0 && count <= DEBUG_DRAW_VERTEX_BUFFER_SIZE);
 
 		m_lineVao.bind();
-		m_lineShader.use();
-		m_lineShader.setUniformMtx(m_lineShaderMVP, mvpMatrix);
+		m_lineShader->use();
+		m_lineShader->setUniformMtx(m_lineShaderMVP, mvpMatrix);
 
 		if (depthEnabled) {
 			glEnable(GL_DEPTH_TEST);
diff --git a/fggl/gfx/ogl4/models.cpp b/fggl/gfx/ogl4/models.cpp
index 1095ac8..3849fb7 100644
--- a/fggl/gfx/ogl4/models.cpp
+++ b/fggl/gfx/ogl4/models.cpp
@@ -27,7 +27,7 @@
 namespace fggl::gfx::ogl4 {
 
 	static std::shared_ptr<ogl::ArrayBuffer> setupArrayBuffer(std::shared_ptr<ogl::VertexArray> &vao,
-															  std::vector<data::Vertex> &data) {
+															  const std::vector<data::Vertex> &data) {
 		auto buff = std::make_shared<ogl::ArrayBuffer>();
 		buff->write(data.size() * sizeof(data::Vertex), data.data(), ogl::BufUsage::STATIC_DRAW);
 
@@ -43,7 +43,7 @@ namespace fggl::gfx::ogl4 {
 	}
 
 	static std::shared_ptr<ogl::ElementBuffer> setupIndexBuffer(std::shared_ptr<ogl::VertexArray> &vao,
-																std::vector<uint32_t> &data) {
+																const std::vector<uint32_t> &data) {
 		auto elementBuffer = std::make_shared<ogl::ElementBuffer>();
 		elementBuffer->write(data.size() * sizeof(uint32_t),
 							 data.data(), ogl::BufUsage::STATIC_DRAW);
@@ -64,53 +64,95 @@ namespace fggl::gfx::ogl4 {
 		modelComp.drawType = ogl::Primative::TRIANGLE;
 	}
 
-	void StaticModelRenderer::resolveModels(entity::EntityManager &world) {
-		// FIXME: this needs something reactive or performance will suck.
-		auto renderables = world.find<data::StaticMesh>();
-		for (const auto &renderable : renderables) {
-			auto *currModel = world.tryGet<StaticModel>(renderable);
-			if (currModel != nullptr) {
-				continue;
-			}
+	StaticModel* StaticModelRenderer::uploadMesh(assets::AssetGUID guid, const data::Mesh &mesh) {
+		assert( m_assets != nullptr );
 
-			auto &meshComp = world.get<data::StaticMesh>(renderable);
-			auto &modelComp = world.add<StaticModel>(renderable);
+		// if the asset has already been uploaded, we don't need to do anything
+		if ( m_assets->has(guid) ) {
+			m_assets->require(guid);
+			return m_assets->get<StaticModel>(guid);
+		}
 
-			auto shader = m_phong;
-			try {
-				shader = std::make_shared<ogl::Shader>(m_shaders->get(meshComp.pipeline));
-			} catch (std::out_of_range &e) {
-				debug::log(debug::Level::warning, "Could not find shader: {}", meshComp.pipeline);
-			}
+		// the asset does not exist, we need to upload it
+		auto* modelAsset = new StaticModel();
+		modelAsset->vao = std::make_shared<ogl::VertexArray>();
+		modelAsset->vertexData = setupArrayBuffer(modelAsset->vao, mesh.vertexList());
+		modelAsset->elements = setupIndexBuffer(modelAsset->vao, mesh.indexList());
+		modelAsset->elementCount = mesh.indexCount();
+		modelAsset->drawType = ogl::Primative::TRIANGLE;
+		m_assets->set(guid, modelAsset);
+
+		return modelAsset;
+	}
+
+	StaticModelGPU* StaticModelRenderer::uploadMesh2(const assets::AssetGUID& meshName, const data::Mesh &mesh) {
+		assert( m_assets != nullptr );
 
-			setupComponent(modelComp, shader, meshComp.mesh);
-			debug::log(debug::Level::info, "Added static mesh to {}", (uint64_t) renderable);
+		if ( m_assets->has(meshName) ) {
+			// we've already uploaded it...
+			return m_assets->get<StaticModelGPU>(meshName);
 		}
 
-		// terrain
-		auto terrain = world.find<data::HeightMap>();
-		for (auto &renderable : terrain) {
-			auto currModel = world.tryGet<StaticModel>(renderable);
-			if (currModel != nullptr) {
-				continue;
+		auto* modelAsset = new StaticModelGPU();
+		modelAsset->vao = std::make_shared<ogl::VertexArray>();
+		modelAsset->vertices = setupArrayBuffer(modelAsset->vao, mesh.vertexList());
+		modelAsset->elements = setupIndexBuffer(modelAsset->vao, mesh.indexList());
+		modelAsset->elementCount = mesh.indexCount();
+		modelAsset->drawType = ogl::Primative::TRIANGLE;
+
+		return m_assets->set(meshName, modelAsset);
+	}
+
+	#ifdef FGGL_ALLOW_DEFERRED_UPLOAD
+		void StaticModelRenderer::resolveModels(entity::EntityManager &world) {
+			// FIXME: this needs something reactive or performance will suck.
+			auto renderables = world.find<data::StaticMesh>();
+			for (const auto &renderable : renderables) {
+				auto *currModel = world.tryGet<StaticModel>(renderable);
+				if (currModel != nullptr) {
+					continue;
+				}
+
+				auto &meshComp = world.get<data::StaticMesh>(renderable);
+
+				// model loading (should be via asset system)
+				auto loadedModel = uploadMesh("DEFER_ENT_"+ std::to_string((uint64_t)renderable), meshComp.mesh);
+				auto loadedShader = m_shaders->get(meshComp.pipeline);
+
+				// splice the loaded asset into the ecs
+				auto& entityModel = world.add<StaticModel>(renderable);
+				entityModel = *loadedModel;
+				entityModel.pipeline = loadedShader;
+
+				// let the user know we just did this...
+				debug::log(debug::Level::info, "Added static mesh to {}, pipeline was: {}", (uint64_t) renderable, meshComp.pipeline);
 			}
 
-			auto &heightmap = world.get<data::HeightMap>(renderable);
-			data::Mesh heightMapMesh{};
-			data::generateHeightMesh(heightmap, heightMapMesh);
+			// terrain
+			auto terrain = world.find<data::HeightMap>();
+			for (auto &renderable : terrain) {
+				auto currModel = world.tryGet<StaticModel>(renderable);
+				if (currModel != nullptr) {
+					continue;
+				}
+
+				auto &heightmap = world.get<data::HeightMap>(renderable);
+				data::Mesh heightMapMesh{};
+				data::generateHeightMesh(heightmap, heightMapMesh);
 
-			auto &modelComp = world.add<StaticModel>(renderable);
-			setupComponent(modelComp, m_phong, heightMapMesh);
+				auto &modelComp = world.add<StaticModel>(renderable);
+				setupComponent(modelComp, m_phong, heightMapMesh);
 
-			// we know this is a triangle strip with a restart vertex...
-			// FIXME the model should be telling us this...
-			modelComp.drawType = ogl::Primative::TRIANGLE_STRIP;
-			modelComp.restartIndex = heightMapMesh.restartVertex;
+				// we know this is a triangle strip with a restart vertex...
+				// FIXME the model should be telling us this...
+				modelComp.drawType = ogl::Primative::TRIANGLE_STRIP;
+				modelComp.restartIndex = heightMapMesh.restartVertex;
 
-			// no active model, we need to resolve/load one.
-			debug::info("looks like {} needs a static mesh", (uint64_t)renderable);
+				// no active model, we need to resolve/load one.
+				debug::info("looks like {} needs a static mesh", (uint64_t)renderable);
+			}
 		}
-	}
+	#endif
 
 	void StaticModelRenderer::renderModelsForward(const entity::EntityManager &world) {
 
@@ -144,7 +186,10 @@ namespace fggl::gfx::ogl4 {
 
 			// TODO lighting needs to not be this...
 			math::vec3 lightPos{0.0f, 10.0f, 0.0f};
+
 			std::shared_ptr<ogl::Shader> shader = nullptr;
+			ogl::Location mvpMatrixUniform = 0;
+			ogl::Location mvMatrixUniform = 0;
 
 			auto renderables = world.find<StaticModel>();
 			for (const auto &entity : renderables) {
@@ -157,7 +202,7 @@ namespace fggl::gfx::ogl4 {
 				}
 
 				// check if we switched shaders
-				if (shader != model.pipeline) {
+				if (shader == nullptr || shader->shaderID() != model.pipeline->shaderID()) {
 					// new shader - need to re-send the view and projection matrices
 					shader = model.pipeline;
 					shader->use();
@@ -165,12 +210,14 @@ namespace fggl::gfx::ogl4 {
 						shader->setUniformMtx(shader->uniform("view"), viewMatrix);
 						shader->setUniformMtx(shader->uniform("projection"), projectionMatrix);
 					}
+					mvpMatrixUniform = shader->uniform("MVPMatrix");
+					mvMatrixUniform = shader->uniform("MVMatrix");
 				}
 
 				// set model transform
 				const auto &transform = world.get<math::Transform>(entity);
-				shader->setUniformMtx(shader->uniform("MVPMatrix"), projectionMatrix * viewMatrix * transform.model());
-				shader->setUniformMtx(shader->uniform("MVMatrix"), viewMatrix * transform.model());
+				shader->setUniformMtx(mvpMatrixUniform, projectionMatrix * viewMatrix * transform.model());
+				shader->setUniformMtx(mvMatrixUniform, viewMatrix * transform.model());
 
 				auto normalMatrix = glm::mat3(glm::transpose(inverse(transform.model())));
 				shader->setUniformMtx(shader->uniform("NormalMatrix"), normalMatrix);
diff --git a/fggl/gfx/ogl4/module.cpp b/fggl/gfx/ogl4/module.cpp
index dc6767b..5c5289d 100644
--- a/fggl/gfx/ogl4/module.cpp
+++ b/fggl/gfx/ogl4/module.cpp
@@ -20,6 +20,7 @@
 #include "fggl/gfx/phong.hpp"
 #include "fggl/data/procedural.hpp"
 
+#include "fggl/assets/loader.hpp"
 #include <string>
 
 namespace fggl::gfx {
@@ -52,28 +53,84 @@ namespace fggl::gfx {
 		}
 	}
 
-	void attach_mesh(const entity::ComponentSpec &spec, entity::EntityManager &manager, const entity::EntityID &id) {
-		auto &meshComp = manager.add<data::StaticMesh>(id);
-		meshComp.pipeline = spec.get<std::string>("pipeline", "");
+	void attach_mesh(const entity::ComponentSpec &spec, entity::EntityManager &manager, const entity::EntityID &id, modules::Services &services) {
 
+		// check for the asset service
+		auto* assetService = services.get<assets::AssetManager>();
+		auto* assetLoader = services.get<assets::Loader>();
+		if ( assetService == nullptr || assetLoader == nullptr ) {
+			// no asset service, give up
+			return;
+		}
+
+		// asset is a procedural mesh description
 		if (spec.has("shape")) {
-			// procedural mesh
-			data::Mesh mesh;
-			if (spec.config["shape"].IsSequence()) {
-				for (const auto &node : spec.config["shape"]) {
-					process_shape(node, mesh);
+			data::Mesh* meshAsset = nullptr;
+			auto pipeline = spec.get<std::string>("pipeline", "");
+
+			// check if we had previously loaded this asset
+			const auto shapeName = spec.get<std::string>("shape_id", "");
+			if ( !shapeName.empty() ) {
+				meshAsset = assetService->get<data::Mesh>(shapeName);
+			}
+
+			// we've not loaded this before - generate mesh
+			if ( meshAsset == nullptr ) {
+				// procedural meshes are build as static meshes first
+				auto* meshTmp = new data::Mesh();
+
+				if (spec.config["shape"].IsSequence()) {
+					for (const auto &node : spec.config["shape"]) {
+						process_shape(node, *meshTmp);
+					}
+				} else {
+					process_shape(spec.config["shape"], *meshTmp);
+				}
+				meshTmp->removeDups();
+
+				if (!shapeName.empty()) {
+					meshAsset = assetService->set(shapeName, meshTmp);
+				} else {
+					meshAsset = meshTmp;
 				}
-			} else {
-				process_shape(spec.config["shape"], mesh);
 			}
-			mesh.removeDups();
-			meshComp.mesh = mesh;
+
+			// TODO we need to trigger an upload to the GPU (somehow)
+			#ifdef FGGL_ALLOW_DEFERRED_UPLOAD
+				// the graphics stack can detect static meshes without a rendering proxy at runtime and fix it but this
+				// requires loading the whole model into the ECS (and should be removed in the future). instead we should
+				// be triggering the upload at this point (if needed).
+				auto &entityMesh = manager.add<data::StaticMesh>(id);
+				entityMesh.pipeline = pipeline;
+				entityMesh.mesh = *meshAsset;
+				debug::warning("HACKY: Triggered proc mesh - using deferred upload");
+			#endif
+
+			return;
+		}
+
+		// asset is a model from the resource cache
+		if ( spec.has("model") ) {
+			// figure out what model we want
+			auto assetStr = spec.get<std::string>("model", "");
+			auto* asset = assetService->get<ogl4::StaticModel>(assetStr);
+
+			if ( asset == nullptr ) {
+				// the asset is not loaded/does not exist
+				debug::error("requested model {} but it was not loaded.", assetStr);
+				return;
+			}
+
+			// copy the asset to the model
+			auto& model = manager.add<ogl4::StaticModel>(id);
+			model = *asset;
 		}
 	}
 
 	void attach_material(const entity::ComponentSpec &spec,
 						 entity::EntityManager &manager,
-						 const entity::EntityID &id) {
+						 const entity::EntityID &id,
+						 modules::Services& services ) {
 		auto &mat = manager.add<gfx::PhongMaterial>(id);
 		mat.ambient = spec.get<math::vec3>("ambient", gfx::DEFAULT_AMBIENT);
 		mat.diffuse = spec.get<math::vec3>("diffuse", gfx::DEFAULT_DIFFUSE);
@@ -81,7 +138,7 @@ namespace fggl::gfx {
 		mat.shininess = spec.get<float>("ambient", gfx::DEFAULT_SHININESS);
 	}
 
-	void attach_light(const entity::ComponentSpec &spec, entity::EntityManager &manager, const entity::EntityID &id) {
+	void attach_light(const entity::ComponentSpec &spec, entity::EntityManager &manager, const entity::EntityID &id, modules::Services& services) {
 		auto &light = manager.add<gfx::Light>(id);
 	}
 
@@ -90,7 +147,9 @@ namespace fggl::gfx {
 			// setup the thing responsible for graphics
 			auto *storage = services.get<data::Storage>();
 			auto *fontLibrary = services.get<gui::FontLibrary>();
-			services.bind<WindowGraphics, ogl4::WindowGraphics>(storage, fontLibrary);
+			auto *assets = services.get<assets::AssetManager>();
+
+			services.bind<WindowGraphics, ogl4::WindowGraphics>(storage, fontLibrary, assets);
 
 			// register as responsible for creating rendering components
 			auto *entityFactory = services.get<entity::EntityFactory>();
diff --git a/fggl/gfx/ogl4/setup.cpp b/fggl/gfx/ogl4/setup.cpp
index cf1a07e..ba1a948 100644
--- a/fggl/gfx/ogl4/setup.cpp
+++ b/fggl/gfx/ogl4/setup.cpp
@@ -21,7 +21,7 @@
 namespace fggl::gfx::ogl4 {
 
 	Graphics *WindowGraphics::create(display::Window &window) {
-		return new OpenGL4Backend(m_storage, m_fonts);
+		return new OpenGL4Backend(m_storage, m_fonts, m_assets);
 	}
 
 }
\ No newline at end of file
diff --git a/fggl/gfx/window.cpp b/fggl/gfx/window.cpp
index b0e86d1..a71a273 100644
--- a/fggl/gfx/window.cpp
+++ b/fggl/gfx/window.cpp
@@ -99,7 +99,7 @@ namespace fggl::display::glfw {
 
 		auto &gamepadCtl = input.gamepads();
 		for (int jid = 0; jid < GLFW_JOYSTICK_LAST; jid++) {
-			if (glfwJoystickPresent(jid)) {
+			if (glfwJoystickPresent(jid) == GLFW_TRUE) {
 				fggl_update_joystick(gamepadCtl, jid);
 			} else {
 				gamepadCtl.setActive(jid, false);
@@ -184,12 +184,16 @@ namespace fggl::display::glfw {
 
 		// bind the graphics API
 		glfwMakeContextCurrent(m_window);
-		m_graphics = std::unique_ptr<gfx::Graphics>(graphics->create(*this));
+		m_graphics = graphics->createMain(*this);
 
 		spdlog::debug("[glfw] window creation complete");
 	}
 
 	Window::~Window() {
+		if ( m_graphics != nullptr ) {
+			m_graphics = nullptr;
+		}
+
 		if (m_window != nullptr) {
 			// prevent dangling pointers
 			glfwSetWindowUserPointer(m_window, nullptr);
diff --git a/include/fggl/assets/loader.hpp b/include/fggl/assets/loader.hpp
index a0cd15d..48fd853 100644
--- a/include/fggl/assets/loader.hpp
+++ b/include/fggl/assets/loader.hpp
@@ -81,7 +81,7 @@ namespace fggl::assets {
 					// TODO we load the data into temp memory and give a pointer to it.
 					debug::log(debug::Level::error, "Tried to load staged asset - no one wrote that yet!");
 					break;
-				case LoadType::PATH: config.first(guid, AssetData(&path));
+				case LoadType::PATH: config.first(this, guid, AssetData(&path));
 					break;
 				}
 			}
diff --git a/include/fggl/assets/manager.hpp b/include/fggl/assets/manager.hpp
index 0872282..fef3c4e 100644
--- a/include/fggl/assets/manager.hpp
+++ b/include/fggl/assets/manager.hpp
@@ -29,10 +29,23 @@
 
 namespace fggl::assets {
 
+	struct AssetBox {
+		virtual ~AssetBox() = default;
+		virtual void release() = 0;
+	};
+
+	template<typename T>
+	struct AssetBoxT : public AssetBox {
+		T* asset = nullptr;
+
+		inline void release() override {
+			asset = nullptr;
+		}
+	};
+
 	class AssetManager {
 		public:
 			constexpr const static modules::ModuleService service = modules::make_service("fggl::assets::Manager");
-			using AssetGUID = std::string;
 
 			AssetManager() = default;
 			virtual ~AssetManager() = default;
@@ -43,36 +56,40 @@ namespace fggl::assets {
 			AssetManager(AssetManager &&) = delete;
 			AssetManager &operator=(AssetManager &&) = delete;
 
-			inline AssetRefRaw getRaw(const AssetGUID &guid) const {
-				return m_registry.at(guid).asset;
+			template<typename T>
+			T* get(const AssetGUID &guid) const {
+				try {
+					const auto &assetRecord = m_registry.at(guid);
+					std::shared_ptr<AssetBoxT<T>> casted = std::dynamic_pointer_cast<AssetBoxT<T>>(assetRecord);
+					return casted->asset;
+				} catch (std::out_of_range& e) {
+					return nullptr;
+				}
 			}
 
 			template<typename T>
-			AssetRef<T> get(const AssetGUID &guid) const {
-				return std::dynamic_pointer_cast<T>(getRaw(guid));
+			T* set(const AssetGUID &guid, T* assetRef) {
+				auto ptr = std::make_shared<AssetBoxT<T>>();
+				ptr->asset = assetRef;
+
+				m_registry[guid] = ptr;
+				return (*ptr).asset;
 			}
 
 			inline void require(const AssetGUID &guid) {
-				m_registry.at(guid).refCount++;
+				//m_registry.at(guid).refCount++;
+			}
+
+			inline bool has(const AssetGUID &guid) {
+				return m_registry.contains(guid);
 			}
 
 			inline void release(const AssetGUID &guid) {
-				m_registry.at(guid).refCount--;
+				//m_registry.at(guid).refCount--;
 			}
 
 		private:
-			struct AssetRecord {
-				std::shared_ptr<Asset> asset = nullptr;
-				std::size_t refCount;
-
-				inline ~AssetRecord() {
-					if (asset != nullptr) {
-						asset->release();
-					}
-				}
-			};
-
-			std::map<AssetGUID, AssetRecord> m_registry;
+			std::map<AssetGUID, std::shared_ptr<AssetBox>> m_registry;
 	};
 
 } // namespace fggl::assets
diff --git a/include/fggl/assets/types.hpp b/include/fggl/assets/types.hpp
index f31cd1b..7d84ddf 100644
--- a/include/fggl/assets/types.hpp
+++ b/include/fggl/assets/types.hpp
@@ -41,13 +41,14 @@ namespace fggl::assets {
 		std::size_t size;
 	};
 
-	using AssetRefRaw = std::shared_ptr<Asset>;
+	using AssetRefRaw = std::shared_ptr<void>;
 
 	template<typename T>
 	using AssetRef = std::shared_ptr<T>;
 
+	class Loader;
 	using AssetData = std::variant<MemoryBlock, AssetPath *, FILE *>;
-	using Checkin = std::function<AssetRefRaw(const AssetGUID &, const AssetData &)>;
+	using Checkin = std::function<AssetRefRaw(Loader* loader, const AssetGUID &, const AssetData &)>;
 }
 
 #endif //FGGL_ASSETS_TYPES_HPP
diff --git a/include/fggl/data/model.hpp b/include/fggl/data/model.hpp
index 900b1e6..61cc4b5 100644
--- a/include/fggl/data/model.hpp
+++ b/include/fggl/data/model.hpp
@@ -134,19 +134,19 @@ namespace fggl::data {
 			 */
 			IndexType indexOf(Vertex vert);
 
-			inline std::vector<Vertex> &vertexList() {
+			inline const std::vector<Vertex> &vertexList() const {
 				return m_verts;
 			}
 
-			inline std::size_t vertexCount() {
+			inline std::size_t vertexCount() const {
 				return m_verts.size();
 			}
 
-			inline std::vector<IndexType> &indexList() {
+			inline const std::vector<IndexType> &indexList() const {
 				return m_index;
 			}
 
-			inline std::size_t indexCount() {
+			inline std::size_t indexCount() const {
 				return m_index.size();
 			}
 
diff --git a/include/fggl/display/window.hpp b/include/fggl/display/window.hpp
index 194ffaf..1d0f9f7 100644
--- a/include/fggl/display/window.hpp
+++ b/include/fggl/display/window.hpp
@@ -21,6 +21,7 @@
 
 #include "fggl/modules/module.hpp"
 #include "fggl/math/types.hpp"
+
 #include "fggl/gfx/interfaces.hpp"
 
 namespace fggl::display {
@@ -30,12 +31,6 @@ namespace fggl::display {
 			virtual ~Window() = default;
 			virtual void activate() const = 0;
 
-			template<typename T, typename ...Args>
-			void make_graphics(Args... args) {
-				activate();
-				m_graphics = std::make_unique<T>(*this, args...);
-			}
-
 			// window-related getters
 			[[nodiscard]]
 			virtual math::vec2i frameSize() const = 0;
@@ -59,7 +54,7 @@ namespace fggl::display {
 			virtual bool isFullscreen() const = 0;
 
 		protected:
-			std::unique_ptr<gfx::Graphics> m_graphics;
+			gfx::Graphics* m_graphics;
 	};
 
 	class WindowService {
diff --git a/include/fggl/entity/loader/loader.hpp b/include/fggl/entity/loader/loader.hpp
index d7d0703..2ba0775 100644
--- a/include/fggl/entity/loader/loader.hpp
+++ b/include/fggl/entity/loader/loader.hpp
@@ -32,7 +32,7 @@
 namespace fggl::entity {
 
 	constexpr auto PROTOTYPE_ASSET = assets::AssetType::make("entity_prototype");
-	using FactoryFunc = std::function<void(const ComponentSpec &config, EntityManager &, const EntityID &)>;
+	using FactoryFunc = std::function<void(const ComponentSpec &config, EntityManager &, const EntityID &, modules::Services &svc)>;
 	using CustomiseFunc = std::function<void(EntityManager &, const EntityID &)>;
 
 	struct FactoryInfo {
@@ -44,6 +44,8 @@ namespace fggl::entity {
 		public:
 			constexpr static const modules::ModuleService service = modules::make_service("fggl::entity:Factory");
 
+			inline EntityFactory(modules::Services &services) : m_services(services) {}
+
 			EntityID create(const EntityType &spec, EntityManager &manager, const CustomiseFunc &customise = nullptr) {
 				debug::warning("creating {}", spec);
 				assert( m_prototypes.contains(spec) && "asked to create undefined prototype!" );
@@ -97,6 +99,7 @@ namespace fggl::entity {
 			}
 
 		private:
+			modules::Services m_services;
 			std::map<ComponentID, FactoryInfo> m_factories;
 			std::map<EntityType, EntitySpec> m_prototypes;
 
@@ -125,7 +128,7 @@ namespace fggl::entity {
 							loadedComps.push_back(component);
 
 							auto &info = m_factories.at(component);
-							info.factory(data, manager, entity);
+							info.factory(data, manager, entity, m_services);
 
 							if (info.finalise != nullptr) {
 								finishers.push_back(info.finalise);
@@ -156,7 +159,7 @@ namespace fggl::entity {
 			}
 	};
 
-	assets::AssetRefRaw load_prototype(EntityFactory *factory, const assets::AssetGUID &guid, assets::AssetData data);
+	assets::AssetRefRaw load_prototype(assets::Loader* loader, EntityFactory *factory, const assets::AssetGUID &guid, assets::AssetData data);
 
 } // namespace fggl::entity
 
diff --git a/include/fggl/gfx/ogl/renderer.hpp b/include/fggl/gfx/ogl/renderer.hpp
index 3928154..fd88e2d 100644
--- a/include/fggl/gfx/ogl/renderer.hpp
+++ b/include/fggl/gfx/ogl/renderer.hpp
@@ -55,7 +55,7 @@ namespace fggl::gfx {
 	 */
 	class OpenGL4Backend : public Graphics {
 		public:
-			explicit OpenGL4Backend(data::Storage *storage, gui::FontLibrary *fonts);
+			explicit OpenGL4Backend(data::Storage *storage, gui::FontLibrary *fonts, assets::AssetManager *assets);
 			~OpenGL4Backend() override = default;
 
 			// copy bad
@@ -107,7 +107,7 @@ namespace fggl::gfx {
 			std::unique_ptr<ogl4::CanvasRenderer> m_canvasRenderer;
 			std::unique_ptr<ogl4::DebugRenderer> m_debugRenderer;
 			std::unique_ptr<ShaderCache> m_cache;
-			GLuint m_canvasPipeline;
+			std::shared_ptr<ogl::Shader> m_canvasPipeline;
 			data::Storage *m_storage;
 			gui::FontLibrary *m_fontLibrary;
 	};
diff --git a/include/fggl/gfx/ogl/shader.hpp b/include/fggl/gfx/ogl/shader.hpp
index 3828f43..28d690f 100644
--- a/include/fggl/gfx/ogl/shader.hpp
+++ b/include/fggl/gfx/ogl/shader.hpp
@@ -17,6 +17,8 @@
 
 #include <cstdio>
 #include <fggl/gfx/ogl/backend.hpp>
+#include "fggl/gfx/ogl/types.hpp"
+
 #include <fggl/data/storage.hpp>
 
 #include <filesystem>
@@ -63,15 +65,17 @@ namespace fggl::gfx {
 
 	class ShaderCache {
 		public:
+			using ShaderPtr = std::shared_ptr<ogl::Shader>;
+
 			ShaderCache(fggl::data::Storage *storage);
 			~ShaderCache() = default;
 
-			GLuint load(const ShaderConfig &config);
-			GLuint load(const ShaderSources &sources, bool allowBinaryCache);
+			ShaderPtr load(const ShaderConfig &config);
+			ShaderPtr load(const ShaderSources &sources, bool allowBinaryCache);
 
-			GLuint getOrLoad(const ShaderConfig &config);
+			ShaderPtr getOrLoad(const ShaderConfig &config);
 
-			GLuint get(const std::string &name);
+			ShaderPtr get(const std::string &name);
 
 			/**
 			 * Fallback pipelines.
@@ -83,7 +87,7 @@ namespace fggl::gfx {
 
 		private:
 			fggl::data::Storage* m_storage;
-			std::unordered_map<std::string, GLuint> m_shaders;
+			std::unordered_map<std::string, ShaderPtr> m_shaders;
 
 			// extensions
 			void setupIncludes();
diff --git a/include/fggl/gfx/ogl/types.hpp b/include/fggl/gfx/ogl/types.hpp
index 832cda4..2d082d2 100644
--- a/include/fggl/gfx/ogl/types.hpp
+++ b/include/fggl/gfx/ogl/types.hpp
@@ -68,11 +68,15 @@ namespace fggl::gfx::ogl {
 			inline Location uniform(const std::string_view &name) const {
 				auto location = glGetUniformLocation(m_obj, name.data());
 				if (location == -1) {
-					std::cerr << "error: " << name << " does not exist" << std::endl;
+					debug::warning("uniform {} does not exist", name);
 				}
 				return location;
 			}
 
+			inline GLuint shaderID() {
+				return m_obj;
+			}
+
 			// primatives
 			inline void setUniformF(Location name, GLfloat value) {
 				glProgramUniform1f(m_obj, name, value);
diff --git a/include/fggl/gfx/ogl4/canvas.hpp b/include/fggl/gfx/ogl4/canvas.hpp
index f546a0a..e1fb245 100644
--- a/include/fggl/gfx/ogl4/canvas.hpp
+++ b/include/fggl/gfx/ogl4/canvas.hpp
@@ -30,7 +30,7 @@ namespace fggl::gfx::ogl4 {
 	class CanvasRenderer {
 		public:
 			CanvasRenderer(gui::FontLibrary *fonts);
-			void render(GLuint shader, const gfx::Paint &paint);
+			void render(ogl::Shader& shader, const gfx::Paint &paint);
 
 			inline gfx::Bounds bounds() const {
 				return m_bounds;
@@ -44,8 +44,8 @@ namespace fggl::gfx::ogl4 {
 			gui::FontLibrary *m_fonts;
 			ogl::Texture m_fontTex;
 
-			void renderShapes(const Paint &paint, GLuint shader);
-			void renderText(const Paint &, GLuint shader);
+			void renderShapes(const Paint &paint, ogl::Shader& shader);
+			void renderText(const Paint &, ogl::Shader& shader);
 	};
 
 } // namespace fggl::gfx::ogl4
diff --git a/include/fggl/gfx/ogl4/debug.hpp b/include/fggl/gfx/ogl4/debug.hpp
index b4b1f19..feaf378 100644
--- a/include/fggl/gfx/ogl4/debug.hpp
+++ b/include/fggl/gfx/ogl4/debug.hpp
@@ -25,13 +25,13 @@ namespace fggl::gfx::ogl4 {
 
 	class DebugRenderer : public dd::RenderInterface {
 		public:
-			explicit DebugRenderer(GLuint shader);
+			explicit DebugRenderer(std::shared_ptr<ogl::Shader> shader);
 			~DebugRenderer() override = default;
 			void drawLineList(const dd::DrawVertex *lines, int count, bool depthEnabled) override;
 
 			math::mat4 mvpMatrix;
 		private:
-			ogl::Shader m_lineShader;
+			std::shared_ptr<ogl::Shader> m_lineShader;
 			ogl::Location m_lineShaderMVP;
 			ogl::VertexArray m_lineVao;
 			ogl::ArrayBuffer m_lineVbo;
diff --git a/include/fggl/gfx/ogl4/models.hpp b/include/fggl/gfx/ogl4/models.hpp
index 9f3175a..1306735 100644
--- a/include/fggl/gfx/ogl4/models.hpp
+++ b/include/fggl/gfx/ogl4/models.hpp
@@ -26,10 +26,31 @@
 #include "fggl/gfx/ogl/backend.hpp"
 #include "fggl/gfx/ogl/types.hpp"
 
+#include "fggl/data/model.hpp"
+#include "fggl/assets/manager.hpp"
+
+#define FGGL_ALLOW_DEFERRED_UPLOAD
+
 namespace fggl::gfx::ogl4 {
 
 	const std::size_t NO_RESTART_IDX = 0;
 
+	struct StaticModelGPU {
+		std::shared_ptr<ogl::VertexArray> vao;
+		std::shared_ptr<ogl::ElementBuffer> elements;
+		std::shared_ptr<ogl::ArrayBuffer> vertices;
+		std::size_t elementCount;
+		ogl::Primative drawType = ogl::Primative::TRIANGLE;
+		std::size_t restartIndex = NO_RESTART_IDX;
+	};
+
+	struct StaticModelInstance {
+		constexpr static auto name = "ogl::static::model";
+		std::string modelName;
+		StaticModelGPU* model = nullptr;
+		std::shared_ptr<ogl::Shader> pipeline;
+	};
+
 	struct StaticModel {
 		constexpr static auto name = "StaticModel";
 
@@ -47,9 +68,9 @@ namespace fggl::gfx::ogl4 {
 
 	class StaticModelRenderer {
 		public:
-			inline StaticModelRenderer(gfx::ShaderCache *cache)
-				: m_shaders(cache), m_phong(nullptr), m_vao(), m_vertexList(), m_indexList() {
-				m_phong = std::make_shared<ogl::Shader>(cache->get("redbook/debug"));
+			inline StaticModelRenderer(gfx::ShaderCache *cache, assets::AssetManager *assets)
+				: m_assets(assets), m_shaders(cache), m_phong(nullptr), m_vao(), m_vertexList(), m_indexList() {
+				m_phong = cache->get("redbook/debug");
 			}
 
 			~StaticModelRenderer() = default;
@@ -60,22 +81,31 @@ namespace fggl::gfx::ogl4 {
 			StaticModelRenderer &operator=(const StaticModelRenderer &other) = delete;
 			StaticModelRenderer &operator=(StaticModelRenderer &&other) = delete;
 
+			StaticModel* uploadMesh(assets::AssetGUID guid, const data::Mesh& mesh);
+			StaticModelGPU* uploadMesh2(const assets::AssetGUID& meshName, const data::Mesh& mesh);
+
 			void render(entity::EntityManager &world) {
-				resolveModels(world);
+				#ifdef FGGL_ALLOW_DEFERRED_UPLOAD
+					resolveModels(world);
+				#endif
 				renderModelsForward(world);
 			}
 
 		private:
-			/**
-			 * Attach any missing rendering components to models.
-			 */
-			void resolveModels(entity::EntityManager &world);
+
+			#ifdef FGGL_ALLOW_DEFERRED_UPLOAD
+				/**
+				 * Attach any missing rendering components to models.
+				 */
+				void resolveModels(entity::EntityManager &world);
+			#endif
 
 			/**
 			 * Render all visible objects according to their render tokens.
 			 */
 			void renderModelsForward(const entity::EntityManager &world);
 
+			assets::AssetManager *m_assets;
 			gfx::ShaderCache *m_shaders;
 			std::shared_ptr<ogl::Shader> m_phong;
 			ogl::VertexArray m_vao;
diff --git a/include/fggl/gfx/ogl4/module.hpp b/include/fggl/gfx/ogl4/module.hpp
index 47d1dfa..9de8e79 100644
--- a/include/fggl/gfx/ogl4/module.hpp
+++ b/include/fggl/gfx/ogl4/module.hpp
@@ -20,7 +20,10 @@
 #define FGGL_GFX_OGL4_MODULE_HPP
 
 #include <array>
+
 #include "fggl/modules/module.hpp"
+
+#include "fggl/assets/manager.hpp"
 #include "fggl/entity/loader/loader.hpp"
 
 #include "fggl/gfx/interfaces.hpp"
@@ -36,8 +39,9 @@ namespace fggl::gfx {
 		constexpr static const std::array<modules::ModuleService, 1> provides = {
 			WindowGraphics::service
 		};
-		constexpr static const std::array<modules::ModuleService, 3> depends = {
+		constexpr static const std::array<modules::ModuleService, 4> depends = {
 			data::Storage::service,
+			assets::AssetManager::service,
 			gui::FontLibrary::service,
 			entity::EntityFactory::service
 		};
diff --git a/include/fggl/gfx/ogl4/setup.hpp b/include/fggl/gfx/ogl4/setup.hpp
index d6ab931..30d26aa 100644
--- a/include/fggl/gfx/ogl4/setup.hpp
+++ b/include/fggl/gfx/ogl4/setup.hpp
@@ -24,28 +24,29 @@
 
 namespace fggl::gfx::ogl4 {
 
-	constexpr GraphicsDetails openGL4Details{
-		GraphicsAPI::OpenGL,
-		4,
-		3,
-		false
-	};
+	constexpr GraphicsDetails OPENGL_4_3_PRODUCTION { GraphicsAPI::OpenGL, 4, 3, false };
+	constexpr GraphicsDetails OPENGL_4_3_DEBUG { GraphicsAPI::OpenGL, 4, 3, true };
 
 	class WindowGraphics : public gfx::WindowGraphics {
 		public:
-			WindowGraphics(data::Storage *storage, gui::FontLibrary *fonts) : m_storage(storage), m_fonts(fonts) {};
+			WindowGraphics(data::Storage *storage, gui::FontLibrary *fonts, assets::AssetManager* assets) : m_storage(storage), m_fonts(fonts), m_assets(assets) {};
 			virtual ~WindowGraphics() = default;
 
 			fggl::gfx::Graphics *create(display::Window &window) override;
 
 			[[nodiscard]]
 			inline GraphicsDetails config() const override {
-				return openGL4Details;
+				#ifdef NDEBUG
+					return OPENGL_4_3_PRODUCTION;
+				#else
+					return OPENGL_4_3_DEBUG;
+				#endif
 			}
 
 		private:
 			data::Storage *m_storage;
 			gui::FontLibrary *m_fonts;
+			assets::AssetManager *m_assets;
 	};
 
 } // namespace fggl::gfx::ogl4
diff --git a/include/fggl/gfx/setup.hpp b/include/fggl/gfx/setup.hpp
index 908d02d..9f1a65c 100644
--- a/include/fggl/gfx/setup.hpp
+++ b/include/fggl/gfx/setup.hpp
@@ -39,8 +39,42 @@ namespace fggl::gfx {
 		public:
 			constexpr const static modules::ModuleService service = modules::make_service("fggl::gfx::WindowGraphics");
 
+			WindowGraphics() = default;
+			virtual ~WindowGraphics() = default;
+
+			// no copy
+			WindowGraphics(const WindowGraphics& ) = delete;
+			WindowGraphics& operator=(const WindowGraphics&) = delete;
+
+			// no move
+			WindowGraphics(WindowGraphics&&) = delete;
+			WindowGraphics& operator=(WindowGraphics&&) = delete;
+
+			[[nodiscard]]
 			virtual GraphicsDetails config() const = 0;
-			virtual Graphics *create(display::Window &window) = 0;
+
+			[[nodiscard]]
+			virtual Graphics* create(display::Window &window) = 0;
+
+			inline Graphics* createMain(display::Window& window) {
+				assert( m_graphics == nullptr );
+				m_graphics = create(window);
+				return m_graphics;
+			}
+
+			inline void release() {
+				if ( m_graphics != nullptr ) {
+					delete m_graphics;
+					m_graphics = nullptr;
+				}
+			}
+
+			[[nodiscard]] inline Graphics* get() const {
+				return m_graphics;
+			}
+
+		private:
+			Graphics* m_graphics = nullptr;
 	};
 
 } // namespace fggl::gfx
diff --git a/include/fggl/phys/null.hpp b/include/fggl/phys/null.hpp
index 79c296e..276e22f 100644
--- a/include/fggl/phys/null.hpp
+++ b/include/fggl/phys/null.hpp
@@ -25,8 +25,9 @@
 namespace fggl::phys {
 
 	inline void build_noop(const entity::ComponentSpec & /*config*/,
-						   entity::EntityManager & manager,
-						   const entity::EntityID & entity) {
+						   entity::EntityManager &manager,
+						   const entity::EntityID &entity,
+						   modules::Services &/*services*/) {
 		manager.add<fggl::phys::RigidBody>(entity);
 		manager.add<fggl::phys::Dynamics>(entity);
 	}
-- 
GitLab