diff --git a/demo/main.cpp b/demo/main.cpp
index 263624ae07b76cf94c4c040737c3dac8cc5edf32..3aaa99b9288314fa327232faa562edc11ce4172a 100644
--- a/demo/main.cpp
+++ b/demo/main.cpp
@@ -228,9 +228,13 @@ int main(int argc, char* argv[]) {
 
 	// create ECS
 	fggl::ecs2::World world;
+
+	// load the modules
 	world.import<fggl::components::Math>();
 	world.import<fggl::components::Gfx>();
 
+	world.import<fggl::systems::OpenGL>();
+
 	// make camera
 	auto camEnt = world.create("camera");
 	{
@@ -295,56 +299,6 @@ int main(int argc, char* argv[]) {
 	}
 
 
-	// rendering systems - Game/OpenGL
-	auto matSys = world.ecs().system<const fggl::components::Transform, fggl::components::GfxMat>()
-		.iter([&world](flecs::iter& it, const fggl::components::Transform* t, fggl::components::GfxMat* mats) {
-				// there is probably a more ECS-friendly way of doing this...
-				auto cam = world.ecs().lookup("camera");
-				const auto camComp = cam.get< fggl::components::Camera >();
-				const auto camTrans = cam.get< fggl::components::Transform >();
-
-				// build the two matrices from the camera
-				const fggl::math::mat4 proj = glm::perspective( camComp->fov, camComp->aspectRatio, camComp->nearPlane, camComp->farPlane);
-				const fggl::math::mat4 view = glm::lookAt( camTrans->origin(), camComp->target, camTrans->up() );
-
-				// splat component data into mesh data
-				for ( auto i : it ) {
-				mats[i].proj = proj;
-				mats[i].view = view;
-				mats[i].model = t[i].model();
-				}
-				});
-
-	glm::vec3 lightPos(20.0f, 20.0f, 15.0f);
-	auto drawSys = world.ecs().system<>("DrawSys", "gfx.mat,SHARED:gfx.token")
-		.iter([lightPos](flecs::iter& it) {
-
-				auto t = it.column<const fggl::components::GfxMat>(1);
-				auto mesh = it.column<const fggl::components::GfxToken>(2);
-
-				glEnable(GL_CULL_FACE);
-				glCullFace(GL_BACK);
-
-				glEnable(GL_DEPTH_TEST);
-
-				for ( auto i : it ) {
-				auto shader = mesh->pipeline;
-				glUseProgram( shader );
-
-				glUniformMatrix4fv( glGetUniformLocation(shader, "model"), 1, GL_FALSE, glm::value_ptr( t[i].model ) );
-				glUniformMatrix4fv( glGetUniformLocation(shader, "view"), 1, GL_FALSE, glm::value_ptr( t[i].view ) );
-				glUniformMatrix4fv( glGetUniformLocation(shader, "projection"), 1, GL_FALSE, glm::value_ptr( t[i].proj ) );
-
-				// lighting
-				GLint lightID = glGetUniformLocation(shader, "lightPos");
-				if ( lightID != -1 )
-					glUniform3fv( lightID, 1, glm::value_ptr( lightPos ) );
-
-				glBindVertexArray( mesh->vao );
-				glDrawElements( GL_TRIANGLES, mesh->idxSize, GL_UNSIGNED_INT, reinterpret_cast<void*>(mesh->idxOffset) );
-				}
-		});
-
 	fggl::gfx::Input& input = fggl::gfx::Input::instance();
 
 	world.ecs().system<fggl::components::Transform, fggl::components::Camera>()
@@ -489,10 +443,6 @@ int main(int argc, char* argv[]) {
 
 		world.tick(dt);
 
-		// rendering systems
-		matSys.run(dt);
-		drawSys.run(dt);
-
 		// render using real shader
 		debug.draw();
 		win.swap();
diff --git a/fggl/gfx/ecs.hpp b/fggl/gfx/ecs.hpp
index 46b6ed391da89f52d4041c311da984d5e5a76c1c..6201de7ab076f22186983baaf8f117a313117d07 100644
--- a/fggl/gfx/ecs.hpp
+++ b/fggl/gfx/ecs.hpp
@@ -4,6 +4,7 @@
 #include <fggl/ecs2/ecs.hpp>
 #include <fggl/math/types.hpp>
 
+#include <fggl/math/ecs.hpp>
 #include <GL/gl.h>
 
 namespace fggl::components {
@@ -41,4 +42,27 @@ namespace fggl::components {
 	};
 };
 
+namespace fggl::systems {
+
+
+	void fglPopulateVertex(flecs::iter& it, const fggl::components::Transform* t, fggl::components::GfxMat* mats);
+	void fglDrawMeshes(flecs::iter& it);
+
+	class OpenGL {
+		public:
+			inline OpenGL(ecs2::impl::world& world) {
+				world.module<OpenGL>("ogl");
+
+				world.system<const fggl::components::Transform, fggl::components::GfxMat>("VertPrep")
+					.kind(flecs::OnStore)
+					.iter(fglPopulateVertex);
+
+				world.system<>("DrawSys", "gfx.mat,SHARED:gfx.token")
+					.kind(flecs::OnStore)
+					.iter(fglDrawMeshes);
+			}
+	};
+
+}
+
 #endif
diff --git a/fggl/gfx/renderer.cpp b/fggl/gfx/renderer.cpp
index dde4510d0a5a4b9d59b0f0277a093e96e4c5c7a7..d288dd30460730f1ccd721440fbf5e2b44b690d5 100644
--- a/fggl/gfx/renderer.cpp
+++ b/fggl/gfx/renderer.cpp
@@ -2,6 +2,9 @@
 #include <fggl/gfx/rendering.hpp>
 #include <fggl/gfx/camera.hpp>
 
+#include <fggl/math/ecs.hpp>
+#include <fggl/gfx/ecs.hpp>
+
 #include <fggl/data/model.hpp>
 
 #include <glm/ext/matrix_transform.hpp>
@@ -121,3 +124,53 @@ void MeshRenderer::render(const Window& window, const fggl::ecs::ECS& ecs, const
 
 }
 
+namespace fggl::systems {
+	void fglPopulateVertex(flecs::iter& it, const fggl::components::Transform* t, fggl::components::GfxMat* mats) {
+		auto cam = it.world().lookup("camera");
+		const auto camComp = cam.get< fggl::components::Camera >();
+		const auto camTrans = cam.get< fggl::components::Transform >();
+
+		// build the two matrices from the camera
+		const fggl::math::mat4 proj = glm::perspective( camComp->fov, camComp->aspectRatio, camComp->nearPlane, camComp->farPlane);
+		const fggl::math::mat4 view = glm::lookAt( camTrans->origin(), camComp->target, camTrans->up() );
+
+		// splat component data into mesh data
+		for ( auto i : it ) {
+			mats[i].proj = proj;
+			mats[i].view = view;
+			mats[i].model = t[i].model();
+		}
+	}
+
+	void fglDrawMeshes(flecs::iter& it) {
+		// FIXME get lighting data from scene
+    		glm::vec3 lightPos(20.0f, 20.0f, 15.0f);
+
+		auto t = it.column<const fggl::components::GfxMat>(1);
+		auto mesh = it.column<const fggl::components::GfxToken>(2);
+
+		glEnable(GL_CULL_FACE);
+		glCullFace(GL_BACK);
+
+		glEnable(GL_DEPTH_TEST);
+
+		for ( auto i : it ) {
+			auto shader = mesh->pipeline;
+			glUseProgram( shader );
+
+			glUniformMatrix4fv( glGetUniformLocation(shader, "model"), 1, GL_FALSE, glm::value_ptr( t[i].model ) );
+			glUniformMatrix4fv( glGetUniformLocation(shader, "view"), 1, GL_FALSE, glm::value_ptr( t[i].view ) );
+			glUniformMatrix4fv( glGetUniformLocation(shader, "projection"), 1, GL_FALSE, glm::value_ptr( t[i].proj ) );
+
+			// lighting
+			GLint lightID = glGetUniformLocation(shader, "lightPos");
+			if ( lightID != -1 )
+				glUniform3fv( lightID, 1, glm::value_ptr( lightPos ) );
+
+			glBindVertexArray( mesh->vao );
+			glDrawElements( GL_TRIANGLES, mesh->idxSize, GL_UNSIGNED_INT, reinterpret_cast<void*>(mesh->idxOffset) );
+		}
+	}
+
+};
+
diff --git a/fggl/math/ecs.hpp b/fggl/math/ecs.hpp
index 00a706d6c0e81132e5151f1ea84dcc9870179994..f141521fe7a49c84e69b7a6211263d46b870866a 100644
--- a/fggl/math/ecs.hpp
+++ b/fggl/math/ecs.hpp
@@ -1,3 +1,6 @@
+#ifndef FGGL_MATH_ECS_H
+#define FGGL_MATH_ECS_H
+
 #include <fggl/ecs2/ecs.hpp>
 #include <fggl/math/types.hpp>
 
@@ -13,3 +16,5 @@ namespace fggl::components {
 			}
 	};
 }
+
+#endif