commit 5444b48a0c2bced7eb200b56e274ad7de79fe000
parent 1a35612b18f3291266b0c4e84095c7e98d503cc9
Author: William Casarin <jb55@jb55.com>
Date:   Sun, 26 Sep 2021 12:38:22 -0700
shader var overhaul
Diffstat:
58 files changed, 1915 insertions(+), 1483 deletions(-)
diff --git a/Makefile b/Makefile
@@ -7,24 +7,17 @@ DEFS= -DGLFW_INCLUDE_NONE -DDEBUG
 
 # CFLAGS = $(DEFS) -ggdb -O0 -I src -Wall -Wextra -std=c99 \
 
-BASE_CFLAGS = $(DEFS) -O2 -g -I src -Wall -Werror -Wextra -std=c99  \
-	      	-DSTBI_ONLY_TGA \
-		-Wno-unused-function \
-		-Wno-unused-parameter \
-		-Wno-unused-variable \
-		-Wmissing-field-initializers \
-		-Wno-cast-align \
-		-Wno-padded \
-		$(shell pkg-config --cflags sdl2 gl)
-
-CFLAGS = $(BASE_CFLAGS)
-EM_CFLAGS = $(BASE_CFLAGS) -s STANDALONE_WASM -mbulk-memory 
-
-LDFLAGS = $(shell pkg-config --libs sdl2 gl) -lm
+CFLAGS = $(DEFS) -O1 -g -I src -Wall -Werror -Wextra -std=c99  \
+						-Wno-unused-function \
+						-Wno-unused-parameter \
+						-Wno-unused-variable \
+						-Wmissing-field-initializers \
+						-Wno-cast-align \
+						-Wno-padded
+LDFLAGS = -lSDL2 -lGL -lm
 SRC=src
 
 SRCS=$(wildcard $(SRC)/*.c)
-HEADERS=$(wildcard $(SRC)/*.h)
 PLYS=$(wildcard data/models/*.ply)
 
 MODELS=$(PLYS:.ply=.mdl)
@@ -74,14 +67,14 @@ $(BIN): main.o $(OBJS)
 	@echo "link $@"
 	@$(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@
 
-$(BIN).wasm: main.c $(SRCS)
-	emcc $(EM_CFLAGS) $^ -s WASM=1 -s ERROR_ON_UNDEFINED_SYMBOLS=0 -o $@
-
 install: $(BIN)
 	install -d $(PREFIX)/bin
 	install $(BIN) $(PREFIX)/bin
 
+TAGS:
+	etags $(SRCS)
+
 tags:
 	ctags $(SRCS) $(HEADERS)
 
-.PHONY: TAGS tags
+.PHONY: TAGS
diff --git a/default.nix b/default.nix
@@ -14,7 +14,7 @@ stdenv.mkDerivation rec {
 
   makeFlags = "PREFIX=$(out)";
 
-  nativeBuildInputs = with pkgs; [ tinycc pkg-config gdb emscripten binaryen wabt ];
+  nativeBuildInputs = with pkgs; [ tinycc pkg-config gdb ];
 
   buildInputs = with pkgs; [ SDL2 mesa libglvnd ] ++ 
                 (with xorg; [ libX11 libxcb libXau libXdmcp libXext libXcursor 
diff --git a/etc/shaders/chess-piece.v.glsl b/etc/shaders/chess-piece.v.glsl
@@ -6,7 +6,7 @@ in vec3 normal;
 
 #include uniforms.glsl
 
-uniform bool is_white;
+uniform int is_white;
 
 out shader_data {
 #include shadervars.glsl
@@ -14,7 +14,7 @@ out shader_data {
 
 void main()
 {
-  vec3 color = is_white ? vec3(0.9, 0.9, 0.9) : vec3(0.3, 0.3, 0.3);
+  vec3 color = is_white == 1 ? vec3(0.9, 0.9, 0.9) : vec3(0.3, 0.3, 0.3);
 #include standard_vtxos.glsl
   gl_Position = mvp * v4_pos;
 }
diff --git a/etc/shaders/main.f.glsl b/etc/shaders/main.f.glsl
@@ -41,7 +41,7 @@ void main() {
   // vec3 color = reflect_env(vertex.color);
   vec3 color = pbr(vertex.color, normalize(V), vertex.normal);
 
-  if (fog_on) {
+  if (fog_on == 1) {
     vec3 fog = apply_fog(color, vertex.position, length(V), camera_position, V);
     color = fog;
   }
diff --git a/etc/shaders/standard_vtxos.glsl b/etc/shaders/standard_vtxos.glsl
@@ -2,8 +2,8 @@ vec4 v4_pos = vec4(position, 1.0);
 // data_out.normal = normal;
 // data_out.position = position;
 
-data_out.normal = mat3(transpose(inverse(model))) * normal;
-data_out.position = vec3(model * v4_pos);
+data_out.normal = mat3(transpose(inverse(model_view))) * normal;
+data_out.position = vec3(model_view * v4_pos);
 data_out.color_smooth = data_out.color = color;
 data_out.shadow_coord = depth_mvp * v4_pos;
 // TODO: move shadow coord calc from frag to here
diff --git a/etc/shaders/ui.v.glsl b/etc/shaders/ui.v.glsl
@@ -1,7 +1,6 @@
 #include profile
 
 in vec3 position;
-in vec3 color;
 in vec2 tex_coords;
 
 out vec3 v_color;
@@ -18,5 +17,5 @@ void main()
     gl_Position = mvp * v4_pos;
 
     v_tex_coords = tex_coords;
-    v_color = color + v4_pos.xyz;
+    v_color = v4_pos.xyz;
 }
diff --git a/etc/shaders/uniforms.glsl b/etc/shaders/uniforms.glsl
@@ -1,15 +1,13 @@
 
+precision highp int;
 // uniform bool diffuse_on;
-uniform bool fog_on;
+uniform int fog_on;
 uniform float sky_intensity;
 uniform float light_intensity;
 uniform mat4 depth_mvp;
-// uniform mat4 depth_vp;
-uniform mat4 model;
 uniform mat4 mvp;
+uniform mat4 model_view;
 uniform mat4 normal_matrix;
-// uniform float time;
-// uniform float ambient_str;
 uniform vec3 sun_color;
 uniform vec3 camera_position;
 uniform vec3 light_dir;
diff --git a/main.c b/main.c
@@ -19,12 +19,10 @@
 #include "update.h"
 #include "util.h"
 #include "window.h"
-
 #include "test_game.h"
+//#include "rogue/rogue.h"
 
 
-
-#include <stdio.h>
 #include <assert.h>
 #include <time.h>
 
@@ -46,16 +44,16 @@ int main(void)
 
     /* SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); */
     int width = 640;
-    int hello;
     int height = 480;
-    game_init(&engine, width, height);
 
-    init_test_game(&test_game, &engine.gpu, width, height);
+    game_init(&engine, width, height);
+    //rogue_game_init(&rogue_game);
 
+    init_test_game(&engine, &test_game);
     //reset_scene(&engine);
     default_scene(&test_game);
     //pbr_scene(&test_game);
-    //chess_scene(&game);
+    chess_scene(&engine, &test_game);
 
     check_gl();
     double last = hires_time_in_seconds();
@@ -77,6 +75,7 @@ int main(void)
 
 	// render our game frame here
 	test_game_frame(&engine, &test_game);
+	//rogue_frame(&engine, rogue_game);
 
         /* Swap front and back buffers */
         SDL_GL_SwapWindow(engine.window);
diff --git a/src/chess.c b/src/chess.c
@@ -1,233 +1,321 @@
 
 #include "chess.h"
 #include "node.h"
+#include "gpu.h"
+#include "shader.h"
+#include "hash.h"
+#include "test_game.h"
 
-//  v1------v0
-//  |       |
-//  |       |
-//  |       |
-//  v2------v3
+//	v1------v0
+//	|		|
+//	|		|
+//	|		|
+//	v2------v3
 
 static GLuint quad_indices[] = {0, 1, 2,  0, 2, 3};
 
 
 static GLfloat quad_normals[] = {
-  0, 0, 1,   0, 0, 1,   0, 0, 1,   0, 0, 1, // front
+  0, 0, 1,	 0, 0, 1,	0, 0, 1,   0, 0, 1, // front
 };
 
 static GLfloat quad_vertices[] = {
-  0.5, 0.5, 0.5,  -0.5, 0.5, 0.5,  -0.5,-0.5, 0.5,   0.5,-0.5, 0.5,    // v0-v1-v2-v3 front
+  0.5, 0.5, 0.5,  -0.5, 0.5, 0.5,  -0.5,-0.5, 0.5,	 0.5,-0.5, 0.5,    // v0-v1-v2-v3 front
 };
 
-
 static GLfloat quad_uvs[] =
-    {
-     1.0, 1.0, // v0
-     0.0, 1.0, // v1
-     0.0, 0.0, // v2
-     1.0, 0.0, // v3
-    };
+	{
+	 1.0, 1.0, // v0
+	 0.0, 1.0, // v1
+	 0.0, 0.0, // v2
+	 1.0, 0.0, // v3
+	};
 
 #define CELL_SIZE 10.0
 
+struct chess_piece {
+	int is_white;
+};
+
+#define CP_LENS(field, typ) { #field, offsetof(struct chess_piece, field), DATA_ID_##typ }
+static struct lens chess_piece_lenses[] = {
+	CP_LENS(is_white, INT),
+};
+
 void make_grid(float *verts, float vert_capacity,
-               u32 *indices, float index_capacity,
-               float *normals, float normal_capacity,
-               float *colors, float color_capacity,
-               int width, int height)
+			   u32 *indices, float index_capacity,
+			   float *normals, float normal_capacity,
+			   float *colors, float color_capacity,
+			   int width, int height)
 {
-    int c = 0;
-    for (int x = 0; x < width; x++) {
-    for (int y = 0; y < height; y++, c++) {
-        int grid_ind = y * height + x;
-        int vind = grid_ind * ARRAY_SIZE(quad_vertices);
-        int iind = grid_ind * ARRAY_SIZE(quad_indices);
-        int i = 0;
-
-        for (i = 0; i < 4; i++) {
-            int nv = i*3;
-            assert(vind+2+nv < vert_capacity);
-            assert(vind+2+nv < color_capacity);
-            assert(vind+2+nv < normal_capacity);
-            verts[vind+0+nv] = (quad_vertices[0+nv] + x) * CELL_SIZE;
-            verts[vind+1+nv] = (quad_vertices[1+nv] + y) * CELL_SIZE;
-            verts[vind+2+nv] = quad_vertices[2+nv];
-
-            // recenter
-            verts[vind+0+nv] -= (width * CELL_SIZE)/2.0 - 0.5 * CELL_SIZE;
-            verts[vind+1+nv] -= (height * CELL_SIZE)/2.0 - 0.5 * CELL_SIZE;
-            verts[vind+2+nv] -= 0.5;
-
-            normals[vind+0+nv] = quad_normals[0+nv];
-            normals[vind+1+nv] = quad_normals[1+nv];
-            normals[vind+2+nv] = quad_normals[2+nv];
-
-            float checkers = (x ^ y) & 1;
-            colors[vind+0+nv] = checkers;
-            colors[vind+1+nv] = checkers;
-            colors[vind+2+nv] = checkers;
-        }
-
-        for (i = 0; i < 2; i++) {
-            int nv = i*3;
-            assert(iind+2+nv < index_capacity);
-            indices[iind+0+nv] = quad_indices[0+nv] + 4*c;
-            indices[iind+1+nv] = quad_indices[1+nv] + 4*c;
-            indices[iind+2+nv] = quad_indices[2+nv] + 4*c;
-        }
-    }
-    }
+	int c = 0;
+	for (int x = 0; x < width; x++) {
+	for (int y = 0; y < height; y++, c++) {
+		int grid_ind = y * height + x;
+		int vind = grid_ind * ARRAY_SIZE(quad_vertices);
+		int iind = grid_ind * ARRAY_SIZE(quad_indices);
+		int i = 0;
+
+		for (i = 0; i < 4; i++) {
+			int nv = i*3;
+			assert(vind+2+nv < vert_capacity);
+			assert(vind+2+nv < color_capacity);
+			assert(vind+2+nv < normal_capacity);
+			verts[vind+0+nv] = (quad_vertices[0+nv] + x) * CELL_SIZE;
+			verts[vind+1+nv] = (quad_vertices[1+nv] + y) * CELL_SIZE;
+			verts[vind+2+nv] = quad_vertices[2+nv];
+
+			// recenter
+			verts[vind+0+nv] -= (width * CELL_SIZE)/2.0 - 0.5 * CELL_SIZE;
+			verts[vind+1+nv] -= (height * CELL_SIZE)/2.0 - 0.5 * CELL_SIZE;
+			verts[vind+2+nv] -= 0.5;
+
+			normals[vind+0+nv] = quad_normals[0+nv];
+			normals[vind+1+nv] = quad_normals[1+nv];
+			normals[vind+2+nv] = quad_normals[2+nv];
+
+			float checkers = (x ^ y) & 1;
+			colors[vind+0+nv] = checkers;
+			colors[vind+1+nv] = checkers;
+			colors[vind+2+nv] = checkers;
+		}
+
+		for (i = 0; i < 2; i++) {
+			int nv = i*3;
+			assert(iind+2+nv < index_capacity);
+			indices[iind+0+nv] = quad_indices[0+nv] + 4*c;
+			indices[iind+1+nv] = quad_indices[1+nv] + 4*c;
+			indices[iind+2+nv] = quad_indices[2+nv] + 4*c;
+		}
+	}
+	}
 }
 
 static void make_chessboard_geom(geometry_id *id)
 {
-    struct make_geometry mkgeom;
-    init_make_geometry(&mkgeom);
-
-    static float verts[12*8*8]; // array_size(quad) 12 * 8 * 8
-    static float colors[12*8*8]; // array_size(quad) 12 * 8 * 8
-    static float normals[12*8*8]; // array_size(quad) 12 * 8 * 8
-    static u32 indices[6*8*8]; // array_size(quad) 6 * 8 * 8
-
-    make_grid(verts, ARRAY_SIZE(verts),
-              indices, ARRAY_SIZE(indices),
-              normals, ARRAY_SIZE(normals),
-              colors, ARRAY_SIZE(colors),
-              8, 8);
-
-    mkgeom.indices  = indices;
-    mkgeom.vertices = verts;
-    mkgeom.normals  = normals;
-    mkgeom.colors   = colors;
-    mkgeom.num_indices = ARRAY_SIZE(indices);
-    mkgeom.num_verts = ARRAY_SIZE(verts)/3;
-    mkgeom.num_uv_components = 0;
-
-    init_id(id);
-    struct geometry *geom = new_geometry(id);
-    make_buffer_geometry(&mkgeom, geom);
-    check_gl();
+	struct make_geometry mkgeom;
+	init_make_geometry(&mkgeom);
+
+	static float verts[12*8*8]; // array_size(quad) 12 * 8 * 8
+	static float colors[12*8*8]; // array_size(quad) 12 * 8 * 8
+	static float normals[12*8*8]; // array_size(quad) 12 * 8 * 8
+	static u32 indices[6*8*8]; // array_size(quad) 6 * 8 * 8
+
+	make_grid(verts, ARRAY_SIZE(verts),
+			  indices, ARRAY_SIZE(indices),
+			  normals, ARRAY_SIZE(normals),
+			  colors, ARRAY_SIZE(colors),
+			  8, 8);
+
+	mkgeom.indices	= indices;
+	mkgeom.vertices = verts;
+	mkgeom.normals	= normals;
+	mkgeom.colors	= colors;
+	mkgeom.num_indices = ARRAY_SIZE(indices);
+	mkgeom.num_verts = ARRAY_SIZE(verts)/3;
+	mkgeom.num_uv_components = 0;
+
+	init_id(id);
+	struct geometry *geom = new_geometry(id);
+	make_buffer_geometry(&mkgeom, geom);
+	check_gl();
 }
 
 static void position_piece(struct entity *piece, int x, int y)
 {
-    assert(x < 8);
-    assert(y < 8);
+	assert(x < 8);
+	assert(y < 8);
 
-    struct node *node = get_node(&piece->node_id);
-    struct model *model = get_model(&piece->model_id);
-    struct geometry *geom = get_geometry(&model->geom_id);
-    assert(node);
+	struct node *node = get_node(&piece->node_id);
+	struct model *model = get_model(&piece->model_id);
+	struct geometry *geom = get_geometry(&model->geom_id);
+	assert(node);
 
-    // reset to a1
-    node->pos[0] = -3.5 * CELL_SIZE;
-    node->pos[1] = -3.5 * CELL_SIZE;
-    node->pos[2] = 0.0;
+	// reset to a1
+	node->pos[0] = -3.5 * CELL_SIZE;
+	node->pos[1] = -3.5 * CELL_SIZE;
+	node->pos[2] = 0.0;
 
-    node->pos[0] += x * CELL_SIZE;
-    node->pos[1] += y * CELL_SIZE;
+	node->pos[0] += x * CELL_SIZE;
+	node->pos[1] += y * CELL_SIZE;
 }
 
 static void setup_pieces(node_id *chessboard) {
-    struct model *pirate_officer;
-    get_model_by_name("pirate_officer", &pirate_officer);
-
-    // make pawn model
-    struct model_id pawn_model_id;
-    init_id(&pawn_model_id.id);
-    struct model *pawn_model = new_model(&pawn_model_id);
-    pawn_model->geom_id = pirate_officer->geom_id;
-    pawn_model->shader = CHESS_PIECE_PROGRAM;
-
-    // pawns
-    for (int i = 0; i < 16; i++) {
-        struct entity *pawn = new_entity(NULL);
-        struct node *pawn_node = get_node(&pawn->node_id);
-        pawn->model_id = pawn_model_id;
-        pawn->flags |= ENT_IS_WHITE | ENT_CASTS_SHADOWS;
-
-        node_scale(pawn_node, 5.0);
-        if (i >= 8) {
-            pawn->flags &= ~ENT_IS_WHITE;
-            node_rotate(pawn_node, V3(0.0,0.0,135.0));
-        }
-        node_attach(&pawn->node_id, chessboard);
-
-        position_piece(pawn, i % 8, i < 8 ? 1 : 6);
-    }
-
-    for (int i = 0; i < 4; i++) {
-        // rooks
-        struct entity *rook = new_entity(NULL);
-        struct model *rook_model;
-        struct node *rook_node = get_node(&rook->node_id);
-        rook->model_id = get_model_by_name("tower", &rook_model);
-        rook->flags |= ENT_CASTS_SHADOWS | ENT_IS_WHITE;
-        rook_model->shader = CHESS_PIECE_PROGRAM;
-        node_attach(&rook->node_id, chessboard);
-        /* node_rotate(rook_node, V3(0.0,0.0,20.0)); */
-        if (i >= 2) {
-            rook->flags &= ~ENT_IS_WHITE;
-            node_rotate(rook_node, V3(0.0,0.0,135.0));
-        }
-        position_piece(rook, i % 2 ? 0 : 7, i < 2 ? 0 : 7);
-        node_scale(rook_node, 0.25);
-    }
+	struct model *pirate_officer, *pawn_model, *tower_model, *rook_model;
+	struct model_id pawn_model_id, rook_model_id;
+	u32 ent_type;
+
+	ent_type = hash_str("chess-piece");
+	debug("chess piece ent_type %d\n", ent_type);
+
+	get_model_by_name("pirate_officer", &pirate_officer);
+	get_model_by_name("tower", &tower_model);
+
+	// make pawn model
+	init_id(&pawn_model_id.id);
+	init_id(&rook_model_id.id);
+
+	pawn_model = new_model(&pawn_model_id);
+	pawn_model->geom_id = pirate_officer->geom_id;
+	pawn_model->shader = CHESS_PIECE_PROGRAM;
+	pawn_model->name = "pawn";
+
+	rook_model = new_model(&rook_model_id);
+	rook_model->geom_id = tower_model->geom_id;
+	rook_model->shader = CHESS_PIECE_PROGRAM;
+	rook_model->name = "rook";
+
+	// pawns
+	for (int i = 0; i < 16; i++) {
+		struct entity *ent = new_entity(NULL);
+		struct node *pawn_node = get_node(&ent->node_id);
+		struct chess_piece *pawn = (struct chess_piece*)ent->data;
+
+		ent->type = ent_type;
+		ent->model_id = pawn_model_id;
+		ent->flags |= ENT_CASTS_SHADOWS;
+
+		node_set_label(pawn_node, "pawn");
+
+		pawn->is_white = 1;
+
+		node_scale(pawn_node, 5.0);
+
+		if (i >= 8) {
+			pawn->is_white = 0;
+			node_rotate(pawn_node, V3(0.0,0.0,135.0));
+		}
+
+		node_attach(&ent->node_id, chessboard);
+
+		position_piece(ent, i % 8, i < 8 ? 1 : 6);
+	}
+
+	for (int i = 0; i < 4; i++) {
+		// rooks
+		struct entity *ent = new_entity(NULL);
+		struct chess_piece *rook = (struct chess_piece*)ent->data;
+		struct node *rook_node = get_node(&ent->node_id);
 
+		ent->type = ent_type;
+		ent->model_id = rook_model_id;
+		ent->flags |= ENT_CASTS_SHADOWS;
 
+		rook->is_white = true;
+
+		node_attach(&ent->node_id, chessboard);
+		/* node_rotate(rook_node, V3(0.0,0.0,20.0)); */
+		if (i >= 2) {
+			rook->is_white = false;
+			node_rotate(rook_node, V3(0.0,0.0,135.0));
+		}
+		position_piece(ent, i % 2 ? 0 : 7, i < 2 ? 0 : 7);
+		node_scale(rook_node, 0.25);
+	}
+
+
+}
+
+static struct structure_binding *get_chess_piece_bindings(int *count)
+{
+	static struct structure_binding bindings[2];
+
+	bindings[0].lenses = get_common_lenses();
+	bindings[0].num_lenses = get_common_lenses_length();
+	assert(bindings[0].num_lenses > 1);
+
+	bindings[1].lenses = chess_piece_lenses;
+	bindings[1].num_lenses = ARRAY_SIZE(chess_piece_lenses);
+
+	*count = 2;
+
+	return bindings;
 }
 
+static struct gpu_program *make_chess_program(struct gpu *gpu)
+{
+	struct gpu_program *program, *default_program;
+	struct shader *fragment, chess_piece_vertex;
+	struct structure_binding *bindings;
+	int num_bindings;
+	int ok;
+
+	ok = make_shader(GL_VERTEX_SHADER, SHADER("chess-piece.v.glsl"), &chess_piece_vertex);
+	rtassert(ok, "chess-piece vertex shader");
+	check_gl();
+
+	default_program = get_gpu_program_by_name(gpu, "vertex-color");
+	rtassert(default_program, "failed to get default program");
+	check_gl();
+
+	fragment = get_program_shader(default_program, GL_FRAGMENT_SHADER);
+	rtassert(fragment, "failed to get default fragment shader");
+
+	bindings = get_chess_piece_bindings(&num_bindings);
+
+	program = &gpu->programs[CHESS_PIECE_PROGRAM];
+	ok = make_program("chess-piece", &chess_piece_vertex, fragment, program,
+			bindings, num_bindings);
+	rtassert(ok, "chess-piece program");
+	check_gl();
+
+	return program;
+}
 
-void chess_scene(struct test_game *game)
+void chess_scene(struct game *engine, struct test_game *game)
 {
-    struct entity *ent = new_entity(NULL);
-    struct node *node  = get_node(&ent->node_id); assert(node);
-    struct entity *player = get_entity(&game->player_id);
+	struct gpu_program *piece_program;
+	struct entity *ent = new_entity(NULL);
+	struct node *node  = get_node(&ent->node_id); assert(node);
+	struct entity *player = get_entity(&game->player_id);
+	struct shader chess_piece_vertex;
 
-    ent->model_id = get_model_by_name("icosphere", NULL);
-    node_set_label(node, "sphere");
+	ent->model_id = get_model_by_name("icosphere", NULL);
+	node_set_label(node, "sphere");
 
-    //
-    // setup chessboard
-    //
-    geometry_id chessboard_geom;
-    make_chessboard_geom(&chessboard_geom);
+	//
+	// setup chessboard
+	//
+	geometry_id chessboard_geom;
+	make_chessboard_geom(&chessboard_geom);
 
-    struct model_id chessboard_model_id;
-    init_id(&chessboard_model_id.id);
+	struct model_id chessboard_model_id;
+	init_id(&chessboard_model_id.id);
 
-    struct model *chessboard_model = new_model(&chessboard_model_id);
-    chessboard_model->shader = DEFAULT_PROGRAM;
-    chessboard_model->geom_id = chessboard_geom;
+	struct model *chessboard_model = new_model(&chessboard_model_id);
+	chessboard_model->name = "chessboard";
+	chessboard_model->shader = 0;
+	chessboard_model->geom_id = chessboard_geom;
 
-    player->model_id = chessboard_model_id;
-    player->flags &= ~ENT_CASTS_SHADOWS;
+	player->model_id = chessboard_model_id;
+	player->flags &= ~ENT_CASTS_SHADOWS;
 
-    //
-    // setup camera
-    //
-    struct spherical *coords = &game->orbit_camera.coords;
-    coords->radius = 72.0;
-    coords->inclination = 0.5;
-    coords->azimuth = -7.86;
+	//
+	// setup camera
+	//
+	struct spherical *coords = &game->orbit_camera.coords;
+	coords->radius = 72.0;
+	coords->inclination = 0.5;
+	coords->azimuth = -7.86;
 
-    struct node *pnode = get_node(&player->node_id);
-    node_translate(pnode, V3(240.0, 252.0, 373.0));
-    pnode->orientation[2] = -0.005;
-    pnode->orientation[3] = -1.0;
+	struct node *pnode = get_node(&player->node_id);
+	node_translate(pnode, V3(240.0, 252.0, 373.0));
+	pnode->orientation[2] = -0.005;
+	pnode->orientation[3] = -1.0;
 
-    node_id *cam_id = &game->orbit_camera.node_id;
-    struct node *cam_node = get_node(cam_id); assert(cam_node);
+	node_id *cam_id = &game->orbit_camera.node_id;
+	struct node *cam_node = get_node(cam_id); assert(cam_node);
 
-    setup_pieces(&player->node_id);
+	make_chess_program(&engine->gpu);
+	setup_pieces(&player->node_id);
 
-    node_recalc(pnode);
+	node_recalc(pnode);
 
-    //player 1284.539062 1111.104126 14.273574
+	//player 1284.539062 1111.104126 14.273574
 
-    // show terrain
-    //terrain_ent->flags |= ENT_INVISIBLE;
+	// show terrain
+	//terrain_ent->flags |= ENT_INVISIBLE;
 
-    // show player
-    /* player->flags |= ENT_INVISIBLE; */
+	// show player
+	/* player->flags |= ENT_INVISIBLE; */
 }
diff --git a/src/chess.h b/src/chess.h
@@ -3,7 +3,8 @@
 #define POLYADVENT_CHESS_H
 
 #include "test_game.h"
+#include "game.h"
 
-void chess_scene(struct test_game *game);
+void chess_scene(struct game *engine, struct test_game *game);
 
 #endif /* POLYADVENT_CHESS_H */
diff --git a/src/common.h b/src/common.h
@@ -2,6 +2,8 @@
 #ifndef POLYADVENT_COMMON_H
 #define POLYADVENT_COMMON_H
 
+#include <inttypes.h>
+
 typedef int bool;
 #define false 0
 #define true 1
@@ -22,8 +24,8 @@ typedef signed short s16;
 typedef unsigned int u32;
 typedef signed int s32;
 
-typedef unsigned long long u64;
-typedef signed long long s64;
+typedef uint64_t u64;
+typedef int64_t s64;
 
 struct point {
   double x, y;
diff --git a/src/dae.c b/src/dae.c
@@ -351,7 +351,7 @@ void dae_attr(struct xmlparser *x, const char *t, size_t tl,
     }
     else if (data->state == PARSING_NODE
              && streq(a, "name")) {
-        strncpy(data->current_name, v, sizeof(data->current_name)-1);
+        strncpy(data->current_name, v, sizeof(data->current_name));
     }
     else if (data->state == PARSING_NODE
              && streq(a, "type")
diff --git a/src/data_id.h b/src/data_id.h
@@ -0,0 +1,92 @@
+#pragma once
+
+#include "resource.h"
+#include <stddef.h>
+#include <assert.h>
+#include <string.h>
+
+enum data_id_type {
+	DATA_ID_RESOURCE = 1,
+	DATA_ID_RAWPTR,
+};
+
+enum data_id_datatype {
+	DATA_ID_FLOAT = 1,
+	DATA_ID_INT,
+	DATA_ID_VEC2,
+	DATA_ID_VEC3,
+	DATA_ID_VEC3P,
+	DATA_ID_MAT3,
+	DATA_ID_MAT4,
+	DATA_ID_MAT4P,
+};
+
+struct lens {
+	const char *name;
+	int offset;
+	enum data_id_datatype type;
+};
+
+static inline int data_id_get_ptr_data(void *ptr, enum data_id_datatype typ, void *out)
+{
+    assert(out);
+    assert(ptr);
+    switch (typ) {
+        case DATA_ID_FLOAT:
+            break;
+        case DATA_ID_INT:
+            *((int*)out) = *(int*)ptr;
+            break;
+        case DATA_ID_VEC2:
+        case DATA_ID_VEC3:
+        case DATA_ID_MAT3:
+        case DATA_ID_MAT4:
+            assert(0);
+            break;
+        case DATA_ID_MAT4P:
+        case DATA_ID_VEC3P:
+            *((float**)out) = *(float**)ptr;
+	    break;
+        default:
+            assert(0);
+    }
+
+    return 1;
+}
+
+
+static inline void *get_lens_offset_ptr(struct lens *lens, void *structure)
+{
+	return (void*)((unsigned char*)structure + lens->offset);
+}
+
+static inline float *get_lens_ptr(struct lens *lens, void *structure)
+{
+	assert(lens->type == DATA_ID_MAT4P || lens->type == DATA_ID_VEC3P);
+	float **p = (float**)get_lens_offset_ptr(lens, structure);
+	return *p;
+}
+
+static inline float *get_lens_float_array(struct lens *lens, void *structure)
+{
+	assert(lens->type == DATA_ID_MAT4 ||
+	       lens->type == DATA_ID_VEC3 ||
+	       lens->type == DATA_ID_VEC2 ||
+	       lens->type == DATA_ID_MAT3);
+	void *p = get_lens_offset_ptr(lens, structure);
+	return (float*)p;
+}
+
+static inline float get_lens_float(struct lens *lens, void *structure)
+{
+	assert(lens->type == DATA_ID_FLOAT);
+	void *p = get_lens_offset_ptr(lens, structure);
+	return *(float*)p;
+}
+
+static inline int get_lens_int(struct lens *lens, void *structure)
+{
+	assert(lens->type == DATA_ID_INT);
+	void *p = get_lens_offset_ptr(lens, structure);
+	return *(int*)p;
+}
diff --git a/src/debug.c b/src/debug.c
@@ -3,7 +3,21 @@
 #include <stdlib.h>
 #include <stdio.h>
 
-void show_info_log(GLuint shader) {
+void show_program_info_log(int program)
+{
+	GLint msgLen = 0;
+	glGetProgramiv(program, GL_INFO_LOG_LENGTH, &msgLen);
+
+	// The maxLength includes the NULL character
+	char *buffer = malloc(msgLen);
+	glGetProgramInfoLog(program, msgLen, &msgLen, buffer);
+
+	printf("program error: (%d) %.*s\n", msgLen, msgLen, buffer);
+
+	free(buffer);
+}
+
+void show_shader_info_log(GLuint shader) {
 	GLint msgLen = 0;
 	glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &msgLen);
 
@@ -11,9 +25,9 @@ void show_info_log(GLuint shader) {
 	char *buffer = malloc(msgLen);
 	glGetShaderInfoLog(shader, msgLen, &msgLen, buffer);
 
-  printf("shader error: (%d) %.*s\n", msgLen, msgLen, buffer);
+	printf("shader error: (%d) %.*s\n", msgLen, msgLen, buffer);
 
-  free(buffer);
+	free(buffer);
 }
 
 
diff --git a/src/debug.h b/src/debug.h
@@ -6,7 +6,8 @@
 
 #include <stdio.h>
 
-void show_info_log(GLuint shader);
+void show_shader_info_log(GLuint shader);
+void show_program_info_log(int program);
 
 // rtassert exists in release builds, use sparingly
 #define rtassert(cond, msg) if (!cond) { \
diff --git a/src/delaunay.c b/src/delaunay.c
@@ -513,11 +513,11 @@ static halfedge_t* del_valid_left( halfedge_t* b )
 
 	v	= b->next->pair->vertex;	/* pair(next(next(halfedge)) point */
 
-	if( v!=u && classify_point_seg(g, d, u) == ON_LEFT )
+	if( classify_point_seg(g, d, u) == ON_LEFT )
 	{
 		/* 3 points aren't colinear */
 		/* as long as the 4 points belong to the same circle, do the cleaning */
-		//assert( v != u && "1: floating point precision error");
+		assert( v != u && "1: floating point precision error");
 		while( v != d && v != g && in_circle(g, d, u, v) == INSIDE )
 		{
 			c	= b->next;
@@ -528,7 +528,7 @@ static halfedge_t* del_valid_left( halfedge_t* b )
 			v	= b->next->pair->vertex;
 		}
 
-		//assert( v != u && "2: floating point precision error");
+		assert( v != u && "2: floating point precision error");
 		if( v != d && v != g && in_circle(g, d, u, v) == ON_CIRCLE )
 		{
 			du	= du->prev;
@@ -559,9 +559,9 @@ static halfedge_t* del_valid_right( halfedge_t *b )
 
 	v	= b->prev->pair->vertex;
 
-	if( v!=u && classify_point_seg(lv, rv, u) == ON_LEFT )
+	if( classify_point_seg(lv, rv, u) == ON_LEFT )
 	{
-		//assert( v != u && "1: floating point precision error");
+		assert( v != u && "1: floating point precision error");
 		while( v != lv && v != rv && in_circle(lv, rv, u, v) == INSIDE )
 		{
 			c	= b->prev;
@@ -572,7 +572,7 @@ static halfedge_t* del_valid_right( halfedge_t *b )
 			v	= b->prev->pair->vertex;
 		}
 
-		//assert( v != u && "1: floating point precision error");
+		assert( v != u && "1: floating point precision error");
 		if( v != lv && v != rv && in_circle(lv, rv, u, v) == ON_CIRCLE )
 		{
 			du	= du->next;
diff --git a/src/entity.c b/src/entity.c
@@ -20,12 +20,10 @@ struct entity *get_all_entities(u32 *count, entity_id **ids) {
 }
 
 struct entity *init_entity(struct entity *ent, node_id *id) {
-    node_id new_id;
     init_id(&ent->model_id.id);
     if (id == NULL) {
-        init_id(&new_id);
-        new_node(&new_id);
-        ent->node_id = new_id;
+        init_id(&ent->node_id);
+        new_node(&ent->node_id);
         /* debug("init_entity with new node_id %llu\n", new_id.uuid); */
     }
     else {
@@ -43,11 +41,6 @@ static inline struct entity *new_uninitialized_entity(entity_id *id) {
     return new_resource(&esys, id);
 }
 
-struct entity *new_entity_(entity_id *id) {
-    return new_entity_with_node(id, NULL);
-}
-
-
 
 struct entity *new_entity_with_node(entity_id *id, node_id *node)
 {
diff --git a/src/entity.h b/src/entity.h
@@ -9,21 +9,23 @@
 #include <assert.h>
 
 #define MAX_ENTITIES 10048
+#define ENTITY_DATA_SIZE 128
 
 enum entity_flags {
-  ENT_IS_PLAYER     = 1 << 0,
-  ENT_INVISIBLE     = 1 << 1,
-  ENT_CASTS_SHADOWS = 1 << 2,
-  ENT_ON_GROUND     = 1 << 3,
-  ENT_IS_WHITE      = 1 << 4,
+  ENT_IS_PLAYER		= 1 << 0,
+  ENT_INVISIBLE		= 1 << 1,
+  ENT_CASTS_SHADOWS	= 1 << 2,
+  ENT_ON_GROUND		= 1 << 3,
 };
 
 struct entity {
-    node_id node_id;
-    struct model_id model_id;
-    u32 flags;
-    float velocity[3];
-    float accel[3];
+	node_id node_id;
+	struct model_id model_id;
+	u32 flags;
+	u32 type;
+	float velocity[3]; // move into entity components
+	float accel[3];
+	u8 data[ENTITY_DATA_SIZE];
 };
 
 typedef struct resource_id entity_id;
@@ -36,16 +38,19 @@ void init_entity_system();
 struct entity *get_entity(entity_id *);
 const char *entity_label(struct entity *);
 struct entity *get_all_entities(u32 *count, entity_id **ids);
-struct entity *new_entity_(entity_id *);
 struct entity *new_entity_with_node(entity_id *, node_id *);
 struct entity *new_debug_entity(entity_id *, float *pos);
 void destroy_entity_system();
 
+static inline struct entity *new_entity_(entity_id *id) {
+    return new_entity_with_node(id, NULL);
+}
+
 static inline struct entity *new_entity(entity_id *id)
 {
-    if (id)
-        assert((int)id->index == -1 && "error: " __FILE__ ":" STRIZE(__LINE__) " missing init_id or already initialized");
-    return new_entity_(id);
+	if (id)
+		assert((int)id->index == -1 && "error: " __FILE__ ":" STRIZE(__LINE__) " missing init_id or already initialized");
+	return new_entity_(id);
 }
 
 #endif /* ENTITY_H */
diff --git a/src/fbo.c b/src/fbo.c
@@ -3,8 +3,6 @@
 #include "fbo.h"
 #include "util.h"
 
-#include <stdio.h>
-
 void create_fbo(struct fbo *fbo, int width, int height) {
     glGenFramebuffers(1, &fbo->handle);
     glBindFramebuffer(GL_FRAMEBUFFER, fbo->handle);
diff --git a/src/file.c b/src/file.c
@@ -2,10 +2,8 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include "file.h"
-#include "debug.h"
 
 #include <sys/stat.h>
-#include <string.h>
 
 time_t file_mtime(const char *filename) {
 	// TODO: windows file_mtime
diff --git a/src/game.c b/src/game.c
@@ -30,7 +30,7 @@ bool was_key_pressed_this_frame(struct game *game, int scancode)
     return is_key_down_on_frame(&game->input, scancode, game->frame);
 }
 
-bool was_button_pressed_this_frame(struct game *game, int button)
+bool was_button_pressed_this_frame(struct game *game, SDL_GameControllerButton button)
 {
     return is_button_down_on_frame(&game->input, button, game->frame);
 }
@@ -43,7 +43,7 @@ static void init_user_settings(struct user_settings *settings) {
 
 static void init_sdl(SDL_Window **window, int width, int height)
 {
-    SDL_Init( 0);
+    SDL_Init( SDL_INIT_JOYSTICK );
 
     /* SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); */
 
@@ -65,11 +65,9 @@ void init_misc(struct game *game) {
     game->gpu.num_programs = 0;
 
 //debug("creating ui...\n");
-    //create_ui(&game->ui, width, height, &res->programs[UI_PROGRAM]);
     //check_gl();
 }
 
-/*
 void init_controller(struct input *input) {
     SDL_GameControllerAddMappingsFromFile("data/gamecontrollerdb.txt");
 
@@ -92,8 +90,6 @@ void init_controller(struct input *input) {
     }
 }
 
-*/
-
 void game_init(struct game *game, int width, int height) {
 	game->width = width;
 	game->height = height;
@@ -103,23 +99,20 @@ void game_init(struct game *game, int width, int height) {
     init_gl();
     debug("init entities...\n");
     init_entity_system();
-    debug("init geom...\n");
     init_geometry_manager();
-    debug("init modelman...\n");
     init_model_manager();
-    debug("init nodeman...\n");
     init_node_manager();
-    debug("init user settings...\n");
     init_user_settings(&game->user_settings);
 
     check_gl();
 
     game->wireframe = 0;
 
-    debug("init input...\n");
     init_input(&game->input);
+    init_controller(&game->input);
     //init_controller(&game->input);
     debug("init misc...\n");
     init_misc(game);
 
+    printf("gpu programs %d\n", game->gpu.num_programs);
 }
diff --git a/src/game.h b/src/game.h
@@ -16,8 +16,6 @@
 #include "ui.h"
 #include "gpu.h"
 
-#include <stdio.h>
-
 #define PLAYER_HEIGHT 1.73
 
 /*
@@ -48,7 +46,7 @@ void game_init(struct game *game, int width, int height);
 void quit_game(struct game *game);
 void should_update(struct game *game);
 bool was_key_pressed_this_frame(struct game *game, int scancode);
-bool was_button_pressed_this_frame(struct game *game, int button);
+bool was_button_pressed_this_frame(struct game *game, SDL_GameControllerButton button);
 int is_free_camera(struct game *game);
 
 #endif /* PA_GAME_H */
diff --git a/src/geometry.c b/src/geometry.c
@@ -9,204 +9,210 @@ struct resource_manager geom_manager;
 
 void
 destroy_buffer_geometry(geometry_id *geom_id) {
-    struct geometry *geom = get_geometry(geom_id);
-    gpu_addr buffers[MAX_VERTEX_ATTRS];
-
-    for (int i = 0; i < MAX_VERTEX_ATTRS; i++)
-        buffers[i] = geom->vbos[i].handle;
-    /* void glDeleteVertexArrays(GLsizei n, const GLuint *arrays); */
-    /* glDisableVertexAttribArray(geom->buffer.vertex_buffer.handle); */
-    /* check_gl(); */
-    /* glDisableVertexAttribArray(geom->buffer.normal_buffer.handle); */
-    /* check_gl(); */
-    /* glDisableVertexAttribArray(geom->buffer.index_buffer.handle); */
-    /* check_gl(); */
-    check_gl();
-    glDeleteBuffers(ARRAY_SIZE(buffers), buffers);
-    check_gl();
-
-
-    geom->has_vbos = 0;
+	struct geometry *geom = get_geometry(geom_id);
+	gpu_addr buffers[MAX_VERTEX_ATTRS];
+
+	for (int i = 0; i < MAX_VERTEX_ATTRS; i++)
+		buffers[i] = geom->vbos[i].handle;
+	/* void glDeleteVertexArrays(GLsizei n, const GLuint *arrays); */
+	/* glDisableVertexAttribArray(geom->buffer.vertex_buffer.handle); */
+	/* check_gl(); */
+	/* glDisableVertexAttribArray(geom->buffer.normal_buffer.handle); */
+	/* check_gl(); */
+	/* glDisableVertexAttribArray(geom->buffer.index_buffer.handle); */
+	/* check_gl(); */
+	check_gl();
+	glDeleteBuffers(ARRAY_SIZE(buffers), buffers);
+	check_gl();
+
+
+	geom->has_vbos = 0;
 }
 
-void bind_geometry(struct geometry *geom, gpu_addr *vertex_attrs) {
-    struct vbo *vbo;
-    for (int i = 0; i < MAX_VERTEX_ATTRS; i++) {
-        vbo = &geom->vbos[i];
-        if (vbo->handle && vertex_attrs[i] != 0xFFFFFFFF) {
-            bind_vbo(vbo, vertex_attrs[i], vbo->component_type);
-            check_gl();
-        }
-    }
+void bind_geometry(struct geometry *geom, struct gpu_program *program) {
+	struct vbo *vbo;
+	for (int i = 0; i < MAX_VERTEX_ATTRS; i++) {
+		if (i != va_index && !(program->active_attributes & (1 << i))) {
+			continue;
+		}
+		vbo = &geom->vbos[i];
+		assert(program->vertex_attrs[i] != 0xFFFFFFFF);
+		if (vbo->handle) {
+			bind_vbo(vbo, program->vertex_attrs[i],
+				vbo->component_type);
+			check_gl();
+		} else {
+			printf("%s: %s attribute not bound\n",
+					program->name,
+					vertex_attr_str(i));
+			assert(0);
+		}
+	}
 }
 
 
-void render_geometry(struct geometry *geom, gpu_addr *vertex_attrs,
-                     struct gpu_program *program)
+void render_geometry(struct geometry *geom, struct gpu_program *program)
 {
-    int type = GL_TRIANGLES;
-    bind_geometry(geom, vertex_attrs);
-    if (geom->num_indices) {
-        glDrawElements(type,
-                       geom->num_indices, /* count */
-                       GL_UNSIGNED_INT,    /* type */
-                       (void*)0            /* element array buffer offset */
-                       );
-        //printf("render_geometry %d %s\n", geom->num_indices, program->name);
-        check_gl();
-    }
-    else {
-        /* printf("nverts %d\n", geom->num_verts); */
-        glDrawArrays(type, 0, geom->num_verts);
-        check_gl();
-    }
+	int type = GL_TRIANGLES;
+	bind_geometry(geom, program);
+	if (geom->num_indices) {
+		glDrawElements(type,
+			geom->num_indices, /* count */
+			GL_UNSIGNED_INT,    /* type */
+			(void*)0			   /* element array buffer offset */
+		);
+		//printf("render_geometry %d %s\n", geom->num_indices, program->name);
+		check_gl();
+	}
+	else {
+		/* printf("nverts %d\n", geom->num_verts); */
+		glDrawArrays(type, 0, geom->num_verts);
+		check_gl();
+	}
 }
 
 void init_make_geometry(struct make_geometry *mkgeom) {
-    *mkgeom = (struct make_geometry){
-      .colors = NULL,
-      .normals = NULL,
-      .indices = NULL,
-      .vertices = NULL,
-      .tex_coords = NULL,
-      .joint_ids = NULL,
-      .joint_weights = NULL,
-      .num_uv_components = 2,
-      .num_verts = 0,
-      .num_indices = 0,
-    };
+	*mkgeom = (struct make_geometry){
+	  .colors = NULL,
+	  .normals = NULL,
+	  .indices = NULL,
+	  .vertices = NULL,
+	  .tex_coords = NULL,
+	  .joint_ids = NULL,
+	  .joint_weights = NULL,
+	  .num_uv_components = 2,
+	  .num_verts = 0,
+	  .num_indices = 0,
+	};
 }
 
 void init_geometry(struct geometry *geom) {
-    geom->has_vbos = 0;
+	geom->has_vbos = 0;
 
-    for (int i = 0; i < MAX_VERTEX_ATTRS; i++)
-        init_vbo(&geom->vbos[i]);
+	for (int i = 0; i < MAX_VERTEX_ATTRS; i++)
+		init_vbo(&geom->vbos[i]);
 
-    geom->num_uv_components = 2;
+	geom->num_uv_components = 2;
 }
 
 void make_buffer_geometry(struct make_geometry *mkgeom, struct geometry *geom)
 {
-    init_geometry(geom);
-
-    // VBOs
-    geom->num_uv_components = mkgeom->num_uv_components;
-    geom->num_verts = mkgeom->num_verts;
-    geom->num_indices = mkgeom->num_indices;
-
-    assert(mkgeom->num_verts);
-    assert(mkgeom->vertices);
-    /* assert(geom->normals); */
-    /* assert(geom->indices); */
-    /* assert(geom->num_indices >= 1); */
-
-    /* printf("making vertex buffer\n"); */
-    make_float_vertex_buffer(&geom->vbos[va_position],
-                             mkgeom->vertices,
-                             mk_num_elements(mkgeom->num_verts),
-                             mk_components(3)
-                             );
-
-    /* printf("making normal buffer\n"); */
-    // cube normals
-    if (mkgeom->normals) {
-        make_float_vertex_buffer(&geom->vbos[va_normal],
-                                 mkgeom->normals,
-                                 mk_num_elements(mkgeom->num_verts),
-                                 mk_components(3)
-                                 );
-    }
-    if (mkgeom->joint_ids) {
-        make_int_vertex_buffer(&geom->vbos[va_joint_ids],
-                               mkgeom->joint_ids,
-                               mk_num_elements(mkgeom->num_verts),
-                               mk_components(3)
-                               );
-    }
-
-    // vertex colors
-    if (mkgeom->colors) {
-        make_float_vertex_buffer(&geom->vbos[va_color],
-                                 mkgeom->colors,
-                                 mk_num_elements(mkgeom->num_verts),
-                                 mk_components(3)
-                                 );
-    }
-
-    if (mkgeom->tex_coords != NULL) {
-        assert(geom->num_uv_components);
-
-        make_uv_buffer(&geom->vbos[va_tex_coord],
-                       mkgeom->tex_coords,
-                       mk_num_elements(geom->num_verts),
-                       mk_components(geom->num_uv_components)
-                       );
-    }
-
-    /* printf("making index buffer\n"); */
-    // cube indices
-    if (mkgeom->indices)
-        make_index_buffer(&geom->vbos[va_index],
-                          mkgeom->indices,
-                          mk_num_elements(mkgeom->num_indices)
-                          );
-
-    geom->has_vbos = 1;
+	init_geometry(geom);
+
+	// VBOs
+	geom->num_uv_components = mkgeom->num_uv_components;
+	geom->num_verts = mkgeom->num_verts;
+	geom->num_indices = mkgeom->num_indices;
+
+	assert(mkgeom->num_verts);
+	assert(mkgeom->vertices);
+	/* assert(geom->normals); */
+	/* assert(geom->indices); */
+	/* assert(geom->num_indices >= 1); */
+
+	/* printf("making vertex buffer\n"); */
+	make_float_vertex_buffer(&geom->vbos[va_position],
+		mkgeom->vertices,
+		mk_num_elements(mkgeom->num_verts),
+		mk_components(3));
+
+	/* printf("making normal buffer\n"); */
+	// cube normals
+	if (mkgeom->normals) {
+		make_float_vertex_buffer(&geom->vbos[va_normal],
+			mkgeom->normals,
+			mk_num_elements(mkgeom->num_verts),
+			mk_components(3));
+	}
+	if (mkgeom->joint_ids) {
+		assert(0);
+		make_int_vertex_buffer(&geom->vbos[va_joint_ids],
+			mkgeom->joint_ids,
+			mk_num_elements(mkgeom->num_verts),
+			mk_components(3));
+	}
+
+	// vertex colors
+	if (mkgeom->colors) {
+		make_float_vertex_buffer(&geom->vbos[va_color],
+			mkgeom->colors,
+			mk_num_elements(mkgeom->num_verts),
+			mk_components(3));
+	}
+
+	if (mkgeom->tex_coords != NULL) {
+		assert(geom->num_uv_components);
+
+		make_uv_buffer(&geom->vbos[va_tex_coord],
+					   mkgeom->tex_coords,
+					   mk_num_elements(geom->num_verts),
+					   mk_components(geom->num_uv_components)
+					   );
+	}
+
+	/* printf("making index buffer\n"); */
+	// cube indices
+	if (mkgeom->indices)
+		make_index_buffer(&geom->vbos[va_index],
+						  mkgeom->indices,
+						  mk_num_elements(mkgeom->num_indices)
+						  );
+
+	geom->has_vbos = 1;
 }
 
 
 void geometry_centroid(struct geometry *geom, float *dest) {
-    vec3_subtract(geom->max, geom->min, dest);
-    vec3_scale(dest, 0.5, dest);
+	vec3_subtract(geom->max, geom->min, dest);
+	vec3_scale(dest, 0.5, dest);
 };
 
 
 void init_geometry_manager() {
-    init_resource_manager(&geom_manager, sizeof(struct geometry),
-                          DEF_NUM_GEOMETRY, MAX_GEOMETRY, "geometry");
+	init_resource_manager(&geom_manager, sizeof(struct geometry),
+						  DEF_NUM_GEOMETRY, MAX_GEOMETRY, "geometry");
 }
 
 struct geometry *get_geometry(geometry_id *geom_id) {
-    return get_resource(&geom_manager, geom_id);
+	return get_resource(&geom_manager, geom_id);
 }
 
 
 struct geometry *new_geometry(geometry_id *geom_id) {
-    struct geometry *geom = new_resource(&geom_manager, geom_id);
-    /* debug("new geometry %llu\n", geom_id->uuid); */
-    return geom;
+	struct geometry *geom = new_resource(&geom_manager, geom_id);
+	/* debug("new geometry %llu\n", geom_id->uuid); */
+	return geom;
 }
 
 struct geometry *get_all_geometry(u32 *count, geometry_id **ids) {
-    return get_all_resources(&geom_manager, count, ids);
+	return get_all_resources(&geom_manager, count, ids);
 }
 
 void destroy_geometry(geometry_id *geom_id)
 {
-    struct geometry *geom = get_geometry(geom_id); assert(geom);
-    struct vbo *vbo;
+	struct geometry *geom = get_geometry(geom_id); assert(geom);
+	struct vbo *vbo;
 
-    for (int i = 0; i < MAX_VERTEX_ATTRS; i++) {
-        vbo = &geom->vbos[i];
-        if (vbo->handle)
-            glDeleteBuffers(1, &vbo->handle);
-    }
+	for (int i = 0; i < MAX_VERTEX_ATTRS; i++) {
+		vbo = &geom->vbos[i];
+		if (vbo->handle)
+			glDeleteBuffers(1, &vbo->handle);
+	}
 
-    destroy_resource(&geom_manager, geom_id);
+	destroy_resource(&geom_manager, geom_id);
 }
 
 void free_make_geometry(struct make_geometry *mkgeom)
 {
-    if (mkgeom->vertices)
-        free(mkgeom->vertices);
+	if (mkgeom->vertices)
+		free(mkgeom->vertices);
 
-    if (mkgeom->normals)
-        free(mkgeom->normals);
+	if (mkgeom->normals)
+		free(mkgeom->normals);
 
-    if (mkgeom->colors)
-        free(mkgeom->colors);
+	if (mkgeom->colors)
+		free(mkgeom->colors);
 
-    if (mkgeom->indices)
-        free(mkgeom->indices);
+	if (mkgeom->indices)
+		free(mkgeom->indices);
 }
diff --git a/src/geometry.h b/src/geometry.h
@@ -47,9 +47,8 @@ struct geometry_manager {
     int num_geometry;
 };
 
-void render_geometry(struct geometry *geom, gpu_addr *vertex_attrs,
-                     struct gpu_program *current_program);
-void bind_geometry(struct geometry *geom, gpu_addr *vertex_attrs);
+void render_geometry(struct geometry *geom, struct gpu_program *current_program);
+void bind_geometry(struct geometry *geom, struct gpu_program *program);
 void init_geometry(struct geometry *geom);
 void init_make_geometry(struct make_geometry *mkgeom);
 void free_make_geometry(struct make_geometry *mkgeom);
diff --git a/src/gpu.c b/src/gpu.c
@@ -21,3 +21,19 @@ int try_reload_shaders(struct gpu *gpu) {
 	return ret;
 }
 
+struct gpu_program *get_gpu_program_by_name(struct gpu *gpu, const char *name)
+{
+	int i;
+	struct gpu_program *program;
+
+	printf("num_programs %d\n", gpu->num_programs);
+
+	for (i = 0; i < gpu->num_programs; i++) {
+		program = &gpu->programs[i];
+		if (!strcmp(program->name, name))
+			return program;
+	}
+
+	return NULL;
+}
+
diff --git a/src/gpu.h b/src/gpu.h
@@ -10,3 +10,4 @@ struct gpu {
 };
 
 int try_reload_shaders(struct gpu *gpu);
+struct gpu_program *get_gpu_program_by_name(struct gpu *gpu, const char *name);
diff --git a/src/hash.h b/src/hash.h
@@ -0,0 +1,20 @@
+
+// TODO: move hash to it's own file
+#define FNV_32_PRIME ((u32)0x01000193)
+
+static inline unsigned int hash_str(const char *str)
+{
+    unsigned char *s = (unsigned char *)str;	/* unsigned string */
+    unsigned int hval = 0;
+
+    /*
+     * FNV-1a hash each octet in the buffer
+     */
+    while (*s) {
+	hval ^= (u32)*s++;
+	hval *= FNV_32_PRIME;
+    }
+
+    /* return our new hash value */
+    return hval;
+}
diff --git a/src/hires.c b/src/hires.c
@@ -1,7 +1,7 @@
 
 #include <time.h>
 #include "common.h"
-#include <SDL.h>
+#include <SDL2/SDL.h>
 
 double hires_time_in_seconds() {
     double ticks_per_sec = (double)SDL_GetPerformanceFrequency();
diff --git a/src/input.c b/src/input.c
@@ -29,7 +29,7 @@ static void key_up(struct input *input, int scancode, u64 current_frame) {
 
 static void button_up(struct input *input, SDL_JoyButtonEvent *event, u64 current_frame)
 {
-    if (event->button >= MAX_CONTROLLER_BUTTONS) return;
+    if (event->button >= SDL_CONTROLLER_BUTTON_MAX) return;
     /* printf("button up %d\n", event->button); */
     struct input_edge *edge = &input->button_edge_states[event->button];
 
@@ -39,7 +39,7 @@ static void button_up(struct input *input, SDL_JoyButtonEvent *event, u64 curren
 
 static void button_down(struct input *input, SDL_JoyButtonEvent *event, u64 current_frame)
 {
-    if (event->button >= MAX_CONTROLLER_BUTTONS) return;
+    if (event->button >= SDL_CONTROLLER_BUTTON_MAX) return;
     printf("button down %d\n", event->button);
 
     struct input_edge *edge = &input->button_edge_states[event->button];
@@ -152,7 +152,7 @@ void init_input(struct input *input) {
   input->last_my = 0;
   input->resized_height = 0;
   input->resized_width = 0;
-  //input->controller = 0;
+  input->controller = 0;
   assert(input->keystates);
 }
 
@@ -163,7 +163,7 @@ bool is_key_down_on_frame(struct input *input, u8 scancode, u64 frame) {
     return edge->down_frame == frame && edge->is_down;
 }
 
-bool is_button_down_on_frame(struct input *input, int button, u64 frame)
+bool is_button_down_on_frame(struct input *input, SDL_GameControllerButton button, u64 frame)
 {
     struct input_edge *edge = &input->button_edge_states[button];
     return edge->down_frame == frame && edge->is_down;
@@ -183,14 +183,11 @@ int input_is_dragging(struct input *input, int mouse_button) {
     return input->mbuttons[mouse_button-1];
 }
 
-bool is_button_down(struct input *input, int button)
+bool is_button_down(struct input *input, SDL_GameControllerButton button)
 {
-	return false;
-	/*
     if (!input->controller) {
         return false;
     }
 
     return SDL_GameControllerGetButton(input->controller, button) == 1;
-    */
 }
diff --git a/src/input.h b/src/input.h
@@ -3,7 +3,7 @@
 #ifndef POLYADVENT_INPUT_H
 #define POLYADVENT_INPUT_H
 
-#include <SDL.h>
+#include <SDL2/SDL.h>
 #include "common.h"
 
 /* enum key_state { */
@@ -23,7 +23,6 @@
 #define MAX_AXIS_VALUE 32767
 #define MIN_AXIS_VALUE -32767
 #define MAX_CONTROLLERS
-#define MAX_CONTROLLER_BUTTONS 15
 
 struct input_edge {
     int is_down;
@@ -31,13 +30,11 @@ struct input_edge {
     u64 up_frame;
 };
 
-
-
 struct input {
   /* enum key_state keys[0x7F-0x1F]; */
     u8 const *keystates;
     SDL_Keymod modifiers;
-    //SDL_GameController *controller;
+    SDL_GameController *controller;
     int mx, my, last_mx, last_my;
     int axis[MAX_AXIS];
     int axis_min_input;
@@ -53,14 +50,14 @@ struct input {
     /* u8 frame_down_keys[KEY_BUFFER_SIZE]; */
     /* u8 frame_up_keys[KEY_BUFFER_SIZE]; */
     struct input_edge key_edge_states[SDL_NUM_SCANCODES];
-    struct input_edge button_edge_states[MAX_CONTROLLER_BUTTONS];
+    struct input_edge button_edge_states[SDL_CONTROLLER_BUTTON_MAX];
 };
 
 int input_is_dragging(struct input *input, int mouse_button);
 
-bool is_button_down(struct input *input, int button);
+bool is_button_down(struct input *input, SDL_GameControllerButton button);
 bool is_key_down_on_frame(struct input *input, u8 scancode, u64 frame);
-bool is_button_down_on_frame(struct input *input, int button, u64 frame);
+bool is_button_down_on_frame(struct input *input, SDL_GameControllerButton button, u64 frame);
 
 void init_input(struct input *input);
 void input_reset(struct input *input);
diff --git a/src/mdl.c b/src/mdl.c
@@ -41,7 +41,7 @@ void init_mdl_geometry(struct mdl_geometry *lgeom)
     vec3_set(V3(0,0,0), lgeom->max);
 }
 
-void save_mdl_fd(FILE *out, struct mdl_geometry *lgeom)
+void save_mdl_fd(FILE *out, struct model *model, struct mdl_geometry *lgeom)
 {
     struct make_geometry *mkgeom = &lgeom->mkgeom;
     assert(mkgeom->vertices);
@@ -140,7 +140,7 @@ static inline int read_int(FILE *stream)
     return val;
 }
 
-#define MAX_MDL_ARRAY 1280000 // only enforced at dev time for sanity
+#define MAX_MDL_ARRAY 128000 // only enforced at dev time for sanity
 
 static inline int read_nfloats(FILE *stream, int n, float *floats)
 {
@@ -184,7 +184,6 @@ void load_mdl_fd(FILE *in, struct model *model, struct mdl_geometry *lgeom)
         switch (tag) {
         case MDL_POSITION:
             mkgeom->num_verts = read_floats(in, &mkgeom->vertices) / 3;
-	    printf("mdl verts %d\n", mkgeom->num_verts);
             break;
 
         case MDL_NORMAL:
@@ -236,11 +235,11 @@ void load_mdl_fd(FILE *in, struct model *model, struct mdl_geometry *lgeom)
     }
 }
 
-void save_mdl(const char *filename, struct mdl_geometry *lgeom)
+void save_mdl(const char *filename, struct model *model, struct mdl_geometry *lgeom)
 {
     FILE *out = fopen(filename, "wb");
     assert(out);
-    save_mdl_fd(out, lgeom);
+    save_mdl_fd(out, model, lgeom);
     fclose(out);
 }
 
diff --git a/src/mdl.h b/src/mdl.h
@@ -4,7 +4,6 @@
 
 #include "geometry.h"
 #include "model.h"
-
 #include <stdio.h>
 
 enum mdl_tag {
@@ -27,8 +26,8 @@ struct mdl_geometry {
 };
 
 
-void save_mdl(const char *filename, struct mdl_geometry *geom);
-void save_mdl_fd(FILE *out, struct mdl_geometry *geom);
+void save_mdl(const char *filename, struct model *model, struct mdl_geometry *geom);
+void save_mdl_fd(FILE *out, struct model *model, struct mdl_geometry *geom);
 
 void load_mdl(const char *filename, struct model *model, struct mdl_geometry *geom);
 void load_mdl_fd(FILE *in, struct model *model, struct mdl_geometry *geom);
diff --git a/src/model.c b/src/model.c
@@ -3,6 +3,7 @@
 #include "ply.h"
 #include "resource.h"
 #include "debug.h"
+#include "hash.h"
 #include "util.h"
 #include <assert.h>
 
@@ -11,102 +12,87 @@
 static struct resource_manager dyn_modelman;
 
 struct model *init_model(struct model *model) {
-    init_id(&model->geom_id);
-    model->shader = DEFAULT_PROGRAM;
-    model->texture = 0;
-    model->nposes = 0;
-    for (u16 i = 0; i < ARRAY_SIZE(model->poses); i++) {
-        init_pose(model->poses);
-    }
-    return model;
+	init_id(&model->geom_id);
+	model->shader = 0;
+	model->texture = 0;
+	model->nposes = 0;
+	for (u16 i = 0; i < ARRAY_SIZE(model->poses); i++) {
+		init_pose(model->poses);
+	}
+	return model;
 }
 
 struct model *get_all_models(u32 *count, struct model_id **ids) {
-    return get_all_resources(&dyn_modelman, count, (struct resource_id **)ids);
+	return get_all_resources(&dyn_modelman, count, (struct resource_id **)ids);
 }
 
 static inline struct model *new_uninitialized_model(struct model_id *id) {
-    return new_resource(&dyn_modelman, &id->id);
+	return new_resource(&dyn_modelman, &id->id);
 }
 
-static struct model *new_model_resource(struct model_id *model_id)
+struct model *new_model(struct model_id *model_id)
 {
-    struct model *model = new_uninitialized_model(model_id);
-    /* debug("new model %llu\n", model_id->dyn_model_id.uuid); */
-    init_id(&model->geom_id);
-    new_geometry(&model->geom_id);
-    return model;
+	struct model *model = new_uninitialized_model(model_id);
+	/* debug("new model %llu\n", model_id->dyn_model_id.uuid); */
+	init_model(model);
+	init_id(&model->geom_id);
+	new_geometry(&model->geom_id);
+	return model;
 }
 
 void init_model_manager()
 {
-    init_resource_manager(&dyn_modelman, sizeof(struct model),
-                          DEF_DYNAMIC_MODELS, MAX_DYNAMIC_MODELS, "model");
+	init_resource_manager(&dyn_modelman, sizeof(struct model),
+		DEF_DYNAMIC_MODELS, MAX_DYNAMIC_MODELS, "model");
 }
 
 
-struct model *new_model(struct model_id *id)
-{
-    return new_model_resource(id);
-}
-
 struct model *get_model(struct model_id *model_id)
 {
-    return get_resource(&dyn_modelman, &model_id->id);
-}
-
-// TODO: move hash to it's own file
-#define FNV_32_PRIME ((u32)0x01000193)
-
-static u32 hash_str(const char *str)
-{
-    u8 *s = (u8 *)str;	/* unsigned string */
-    u32 hval = 0;
-
-    /*
-     * FNV-1a hash each octet in the buffer
-     */
-    while (*s) {
-	hval ^= (u32)*s++;
-	hval *= FNV_32_PRIME;
-    }
-
-    /* return our new hash value */
-    return hval;
+	return get_resource(&dyn_modelman, &model_id->id);
 }
 
 static struct model *load_model(const char *name, struct model_id *mid)
 {
-    static char path[128] = {0};
+	static char path[128] = {0};
+
+	struct model *model = new_model(mid);
+	struct geometry *geom = get_geometry(&model->geom_id);
+	struct mdl_geometry lgeom;
 
-    struct model *model = new_model_resource(mid);
-    struct geometry *geom = get_geometry(&model->geom_id);
-    struct mdl_geometry lgeom;
+	init_mdl_geometry(&lgeom);
+	init_geometry(geom);
 
-    init_mdl_geometry(&lgeom);
-    init_geometry(geom);
+	model->name = name;
+	model->name_hash = hash_str(name);
 
-    model->name = name;
-    model->name_hash = hash_str(name);
+	assert(!geom->has_vbos);
+
+	/*
+	if (geom->has_vbos) {
+		debug("model %s already loaded\n", name);
+		return model;
+	}
+	*/
 
-    if (geom->has_vbos) {
-        debug("model %s already loaded\n", name);
-        return model;
-    }
+	// Load mesh
+	debug("loading %s model uuid %" PRIu64 " with geom_id %" PRIu64 "\n", name,
+			mid->id.uuid, model->geom_id.uuid);
 
-    // Load mesh
-    debug("loading %s model with geom_id\n", name);
+	snprintf(path, 128, "data/models/%s.mdl", name);
+	load_mdl(path, model, &lgeom);
 
-    snprintf(path, 128, "data/models/%s.mdl", name);
-    load_mdl(path, model, &lgeom);
+	vec3_copy(lgeom.min, geom->min);
+	vec3_copy(lgeom.max, geom->max);
 
-    vec3_copy(lgeom.min, geom->min);
-    vec3_copy(lgeom.max, geom->max);
+	printf("%s min:%f,%f,%f max:%f,%f,%f\n", name,
+			geom->min[0], geom->min[1], geom->min[2],
+			geom->max[0], geom->max[1], geom->max[2]);
 
-    make_buffer_geometry(&lgeom.mkgeom, geom);
-    free_make_geometry(&lgeom.mkgeom);
+	make_buffer_geometry(&lgeom.mkgeom, geom);
+	free_make_geometry(&lgeom.mkgeom);
 
-    return model;
+	return model;
 }
 
 struct model_id get_model_by_name(const char *name, struct model **found)
@@ -129,6 +115,10 @@ struct model_id get_model_by_name(const char *name, struct model **found)
 		if (model->name_hash == name_hash) {
 			if (found)
 				*found = model;
+			/*
+			printf("found %s:%s model_id:%"PRIu64" geom_id:%"PRIu64"\n",
+					name, model->name, ids[i].id.uuid, model->geom_id.uuid);
+					*/
 			return ids[i];
 		}
 	}
@@ -142,10 +132,10 @@ struct model_id get_model_by_name(const char *name, struct model **found)
 
 void destroy_model(struct model_id *model_id)
 {
-    struct model *model = get_model(model_id);
+	struct model *model = get_model(model_id);
 
-    destroy_geometry(&model->geom_id);
-    destroy_resource(&dyn_modelman, &model_id->id);
+	destroy_geometry(&model->geom_id);
+	destroy_resource(&dyn_modelman, &model_id->id);
 }
 
 
diff --git a/src/model.h b/src/model.h
@@ -20,7 +20,7 @@ struct model_id {
 struct model {
     /* geometry_id geom_id; */
     geometry_id geom_id;
-    enum program_type shader;
+    int shader;
     struct pose poses[MAX_POSES]; // TODO: separate animated_model buffer?
     int nposes;
     u32 texture;
diff --git a/src/node.c b/src/node.c
@@ -32,7 +32,7 @@ void destroy_node(node_id *id)
 {
 #ifdef DEBUG
     struct node *node = get_node(id);
-    debug("destroying node %llu %s\n", id->uuid, node->label);
+    debug("destroying node %" PRIu64 " %s\n", id->uuid, node->label);
 #endif
 
     destroy_resource(&node_manager, id);
@@ -216,6 +216,9 @@ void node_attach(struct resource_id *node_id, struct resource_id *to_id)
     struct node *to   = get_node(to_id);
 
     assert(node);
+    if (to->n_children > MAX_NODE_CHILDREN) {
+	    debug("to->n_children %d\n", to->n_children);
+    }
     assert(to && to->n_children <= MAX_NODE_CHILDREN);
 
     node->parent_id = *to_id;
diff --git a/src/node.h b/src/node.h
@@ -4,7 +4,7 @@
 
 #include "resource.h"
 
-#define MAX_NODE_CHILDREN 20
+#define MAX_NODE_CHILDREN 32
 
 enum node_flags {
   NODE_IGNORE_RECALC = 1 << 0
diff --git a/src/orbit_util.c b/src/orbit_util.c
@@ -1,6 +1,7 @@
 
 #include "orbit.h"
 #include "input.h"
+#include "debug.h"
 #include "entity.h"
 
 void orbit_update_from_mouse(struct orbit *camera, struct input *input,
@@ -8,17 +9,20 @@ void orbit_update_from_mouse(struct orbit *camera, struct input *input,
                              float dt)
 {
     float target[3];
-    struct node *target_node     = get_node(&player->node_id);
-    struct node *cam_node        = get_node(&camera->node_id);
-    struct model *pmodel         = get_model(&player->model_id); assert(pmodel);
-    struct geometry *player_geom = get_geometry(&pmodel->geom_id); assert(player_geom);
+    struct node *target_node = get_node(&player->node_id);
+    struct node *cam_node    = get_node(&camera->node_id);
+    struct model *pmodel     = get_model(&player->model_id);
+    assert(pmodel);
+
+    struct geometry *player_geom = get_geometry(&pmodel->geom_id);
+    assert(player_geom);
 
     assert(target_node);
     assert(cam_node);
 
     node_recalc(target_node);
     vec3_copy(node_world(target_node), target);
-    assert(player_geom->max[2] != 0);
+    //assert(player_geom->max[2] != 0);
     vec3_add(target, V3(0.0, 0.0, player_geom->max[2]), target);
     /* vec3_add(target, V3(0.0, 0.0, 10.0), target); */
 
diff --git a/src/poisson.c b/src/poisson.c
@@ -5,8 +5,6 @@
 #include "common.h"
 #include "poisson.h"
 
-#include <stdio.h>
-
 
 struct grid_info {
   double size;
diff --git a/src/quickhull.c b/src/quickhull.c
@@ -998,7 +998,7 @@ qh_face_t* qh__build_tetrahedron(qh_context_t* context, float epsilon)
             qh_vertex_t* v;
             qh_face_t* dface = NULL;
 
-            if (vertices[0] == (long)i || vertices[1] == (long)i || vertices[2] == (long)i) {
+            if (vertices[0] == i || vertices[1] == i || vertices[2] == i) {
                 continue;
             }
 
@@ -1014,7 +1014,7 @@ qh_face_t* qh__build_tetrahedron(qh_context_t* context, float epsilon)
 
                 for (int j = 0; j < 3; ++j) {
                     qh_half_edge_t* e = context->edges + dface->edges[j];
-                    if ((long)i == e->to_vertex) {
+                    if (i == e->to_vertex) {
                         valid = 0;
                         break;
                     }
diff --git a/src/render.h b/src/render.h
@@ -21,28 +21,27 @@ void wireframe_mode_on();
 void wireframe_mode_off();
 
 static inline void uniform_3f(struct gpu_program *program,
-                              enum uniform_id id, const float *v3)
+                              int id, const float *v3)
 {
     glUniform3f(program->uniforms[id].location, v3[0], v3[1], v3[2]);
     check_gl();
 }
 
-static inline void uniform_1i(struct gpu_program *program,
-                             enum uniform_id id, int i)
+static inline void uniform_1i(struct gpu_program *program, int id, int i)
 {
     glUniform1i(program->uniforms[id].location, i);
     check_gl();
 }
 
 static inline void uniform_m4f(struct gpu_program *program,
-                               enum uniform_id id, float *m)
+                               int id, float *m)
 {
     glUniformMatrix4fv(program->uniforms[id].location, 1, 0, m);
     check_gl();
 }
 
 static inline void uniform_1f(struct gpu_program *program,
-                              enum uniform_id id, float f)
+                              int id, float f)
 {
     glUniform1f(program->uniforms[id].location, f);
     check_gl();
diff --git a/src/resource.c b/src/resource.c
@@ -11,217 +11,217 @@ static u64 resource_uuids = 0;
 
 static inline void *index_resource_(u8 *res, u32 elem_size, int i)
 {
-    assert(res);
-    return res + (i * elem_size);
+	assert(res);
+	return res + (i * elem_size);
 }
 
 static inline void *index_resource(struct resource_manager *r, int i)
 {
-    return index_resource_(r->resources, r->elem_size, i);
+	return index_resource_(r->resources, r->elem_size, i);
 }
 
 
 void *get_all_resources(struct resource_manager *r, u32 *count, struct resource_id **ids) {
-    if (count != 0)
-        *count = r->resource_count;
+	if (count != 0)
+		*count = r->resource_count;
 
-    if (ids != 0)
-        *ids = r->ids;
+	if (ids != 0)
+		*ids = r->ids;
 
-    return r->resources;
+	return r->resources;
 }
 
 void init_id(struct resource_id *id) {
-    id->index = -1;
-    id->generation = -1;
-    id->uuid = -1;
+	id->index = -1;
+	id->generation = -1;
+	id->uuid = -1;
 }
 
 void null_id(struct resource_id *id)
 {
-    id->generation = 0;
-    id->uuid = -1;
-    id->index = -1;
+	id->generation = 0;
+	id->uuid = -1;
+	id->index = -1;
 }
 
 void init_resource_manager(struct resource_manager *r, u32 elem_size,
-                           u32 initial_elements, u32 max_elements,
-                           const char *name)
+						   u32 initial_elements, u32 max_elements,
+						   const char *name)
 {
-    r->generation = 1;
-    r->resource_count = 0;
-    r->elem_size = elem_size;
-    r->max_capacity = max_elements;
-    r->current_capacity = initial_elements;
-    r->name = name;
+	r->generation = 1;
+	r->resource_count = 0;
+	r->elem_size = elem_size;
+	r->max_capacity = max_elements;
+	r->current_capacity = initial_elements;
+	r->name = name;
 
-    assert(initial_elements != 0);
+	assert(initial_elements != 0);
 
-    r->resources = calloc(r->current_capacity, elem_size);
-    r->ids = calloc(r->current_capacity, sizeof(struct resource_id));
+	r->resources = calloc(r->current_capacity, elem_size);
+	r->ids = calloc(r->current_capacity, sizeof(struct resource_id));
 }
 
 void destroy_resource_manager(struct resource_manager *r) {
-    free(r->ids);
-    free(r->resources);
+	free(r->ids);
+	free(r->resources);
 }
 
 static int refresh_id(struct resource_manager *r, struct resource_id *id,
-                      struct resource_id *new)
+					  struct resource_id *new)
 {
-    // rollover is ok
-    /* assert(->generation <= esys.generation); */
-    if (id->generation != r->generation) {
-        /* debug("id %llu gen %d != res gen %d, refreshing\n", */
-        /*       id->uuid, id->generation, r->generation); */
-        // try to find uuid in new memory layout
-        for (u32 i = 0; i < r->resource_count; i++) {
-            struct resource_id *newer_id = &r->ids[i];
-            if (newer_id->uuid == id->uuid) {
-                /* debug("found %llu, ind %d -> %d\n", new_id->uuid, new_id->index, new->index); */
-                new->index = newer_id->index;
-                new->generation = r->generation;
-                return REFRESHED_ID;
-            }
-        }
-
-        // entity was deleted
-        return RESOURCE_DELETED;
-    }
-
-    // doesn't need refreshed
-    return REFRESH_NOT_NEEDED;
+	// rollover is ok
+	/* assert(->generation <= esys.generation); */
+	if (id->generation != r->generation) {
+		/* debug("id %llu gen %d != res gen %d, refreshing\n", */
+		/*		 id->uuid, id->generation, r->generation); */
+		// try to find uuid in new memory layout
+		for (u32 i = 0; i < r->resource_count; i++) {
+			struct resource_id *newer_id = &r->ids[i];
+			if (newer_id->uuid == id->uuid) {
+				/* debug("found %llu, ind %d -> %d\n", new_id->uuid, new_id->index, new->index); */
+				new->index = newer_id->index;
+				new->generation = r->generation;
+				return REFRESHED_ID;
+			}
+		}
+
+		// entity was deleted
+		return RESOURCE_DELETED;
+	}
+
+	// doesn't need refreshed
+	return REFRESH_NOT_NEEDED;
 }
  
-int is_resource_destroyed(struct resource_id *id) {
-    return id->generation == 0;
+int is_resource_destroyed(struct resource_manager *r, struct resource_id *id) {
+	return refresh_id(r, id, id) == RESOURCE_DELETED;
 }
 
 static void new_id(struct resource_manager *r, struct resource_id *id)
 {
-    id->index = r->resource_count;
-    id->uuid  = ++resource_uuids;
-    id->generation = r->generation;
-    assert(id->generation);
+	id->index = r->resource_count;
+	id->uuid  = ++resource_uuids;
+	id->generation = r->generation;
+	assert(id->generation);
 }
 
 static void resize(struct resource_manager *r)
 {
-    debug("resizing %s resources, count %d+1 > current capacity %d\n",
-          r->name, r->resource_count, r->current_capacity);
-    void *new_mem;
-    u32 new_size = r->resource_count * 1.5;
-    if (new_size >= r->max_capacity)
-        new_size = r->max_capacity;
-
-    /* debug("resizing new_size %d\n", new_size); */
-
-    new_mem = realloc(r->resources, (new_size+1) * r->elem_size);
-    if (!new_mem) {
-        // yikes, out of memory, bail
-        assert(new_mem);
-        return;
-    }
-
-    r->resources = new_mem;
-    new_mem = realloc(r->ids, sizeof(struct resource_id) * (new_size+1));
-
-    if (!new_mem) {
-        // yikes, out of memory, bail
-        assert(new_mem);
-        return;
-    }
-    r->current_capacity = new_size;
-    r->ids = new_mem;
+	debug("resizing %s resources, count %d+1 > current capacity %d\n",
+		  r->name, r->resource_count, r->current_capacity);
+	void *new_mem;
+	u32 new_size = r->resource_count * 1.5;
+	if (new_size >= r->max_capacity)
+		new_size = r->max_capacity;
+
+	/* debug("resizing new_size %d\n", new_size); */
+
+	new_mem = realloc(r->resources, (new_size+1) * r->elem_size);
+	if (!new_mem) {
+		// yikes, out of memory, bail
+		assert(new_mem);
+		return;
+	}
+
+	r->resources = new_mem;
+	new_mem = realloc(r->ids, sizeof(struct resource_id) * (new_size+1));
+
+	if (!new_mem) {
+		// yikes, out of memory, bail
+		assert(new_mem);
+		return;
+	}
+	r->current_capacity = new_size;
+	r->ids = new_mem;
 }
 
 void print_id(struct resource_id *id, int nl)
 {
-    printf("id(u:%llu i:%d g:%d)%s",
-           id->uuid, id->index, id->generation, nl?"\n":"");
+	printf("id(u:%" PRIu64 " i:%d g:%d)%s",
+		   id->uuid, id->index, id->generation, nl?"\n":"");
 }
 
 
 void *new_resource(struct resource_manager *r, struct resource_id *id)
 {
-    assert(id);
-    assert(id->index == 0xFFFFFFFF && "res_id is uninitialized");
+	assert(id);
+	assert(id->index == 0xFFFFFFFF && "res_id is uninitialized");
 
-    struct resource_id *fresh_id;
+	struct resource_id *fresh_id;
 
-    if (r->resource_count + 1 > r->max_capacity) {
-        printf("new_resource: count %d > max cap %d\n", r->resource_count, r->max_capacity);
-        return NULL;
-    }
+	if (r->resource_count + 1 > r->max_capacity) {
+		printf("new_resource: count %d > max cap %d\n", r->resource_count, r->max_capacity);
+		return NULL;
+	}
 
-    if (r->resource_count + 1 > r->current_capacity)
-        resize(r);
+	if (r->resource_count + 1 > r->current_capacity)
+		resize(r);
 
-    fresh_id = &r->ids[r->resource_count];
-    new_id(r, fresh_id);
-    *id = *fresh_id;
+	fresh_id = &r->ids[r->resource_count];
+	new_id(r, fresh_id);
+	*id = *fresh_id;
 
-    return index_resource(r, r->resource_count++);
+	return index_resource(r, r->resource_count++);
 }
 
 
 void *get_resource(struct resource_manager *r, struct resource_id *id) {
-    assert((int64_t)id->generation != -1 && "id intialized but not allocated (needs new_ call)");
+	assert((int64_t)id->generation != -1 && "id intialized but not allocated (needs new_ call)");
 
-    if (id->generation == 0) {
-        /* unusual("getting already deleted resource %llu\n", id->uuid); */
-        return NULL;
-    }
+	if (id->generation == 0) {
+		/* unusual("getting already deleted resource %llu\n", id->uuid); */
+		return NULL;
+	}
 
-    enum refresh_status res = refresh_id(r, id, id);
+	enum refresh_status res = refresh_id(r, id, id);
 
-    if (res == RESOURCE_DELETED) {
-        /* unusual("getting deleted %s resource %llu\n", r->name, id->uuid); */
-        return NULL;
-    }
+	if (res == RESOURCE_DELETED) {
+		/* unusual("getting deleted %s resource %llu\n", r->name, id->uuid); */
+		return NULL;
+	}
 
-    return index_resource(r, id->index);
+	return index_resource(r, id->index);
 }
 
 
 void destroy_resource(struct resource_manager *r, struct resource_id *id) {
-    if (is_resource_destroyed(id)) {
-        unusual("trying to destroy resource %llu which was already destroyed\n", id->uuid);
-        return;
-    }
+	if (is_resource_destroyed(r, id)) {
+		unusual("trying to destroy resource %" PRIu64 " which was already destroyed\n", id->uuid);
+		return;
+	}
 
-    enum refresh_status res = refresh_id(r, id, id);
+	enum refresh_status res = refresh_id(r, id, id);
 
-    // entity already deleted
-    /* debug("refresh res %d uuid %llu gen %d index %d\n", res, */
-    /*       id->uuid, id->generation, id->index); */
+	// entity already deleted
+	/* debug("refresh res %d uuid %llu gen %d index %d\n", res, */
+	/*		 id->uuid, id->generation, id->index); */
 
-    if (res == RESOURCE_DELETED) {
-        unusual("trying to destroy resource %llu which was already destroyed (2)\n", id->uuid);
-        id->generation = 0;
-        return;
-    }
+	if (res == RESOURCE_DELETED) {
+		unusual("trying to destroy resource %" PRIu64 " which was already destroyed (2)\n", id->uuid);
+		id->generation = 0;
+		return;
+	}
 
-    /* debug("destroying %s resource %llu ind %d res_count %d\n", */
-    /*       r->name, id->uuid, id->index, r->resource_count); */
+	/* debug("destroying %s resource %llu ind %d res_count %d\n", */
+	/*		 r->name, id->uuid, id->index, r->resource_count); */
 
-    r->resource_count--;
-    r->generation++;
+	r->resource_count--;
+	r->generation++;
 
-    if (r->resource_count > 0) {
-	    assert((int)r->resource_count - (int)id->index >= 0);
+	if (r->resource_count > 0) {
+		assert((int)r->resource_count - (int)id->index >= 0);
 
-	    // TODO: we're copying OOB here
-	    memmove(index_resource(r, id->index),
-		    index_resource(r, id->index+1),
-		    r->elem_size * (r->resource_count - id->index));
+		// TODO: we're copying OOB here
+		memmove(index_resource(r, id->index),
+			index_resource(r, id->index+1),
+			r->elem_size * (r->resource_count - id->index));
 
-	    memmove(&r->ids[id->index],
-		    &r->ids[id->index+1],
-		    sizeof(struct resource_id) * (r->resource_count - id->index));
-    }
+		memmove(&r->ids[id->index],
+			&r->ids[id->index+1],
+			sizeof(struct resource_id) * (r->resource_count - id->index));
+	}
 
-    for (u32 i = id->index; i < r->resource_count; i++) {
-        r->ids[i].index--;
-    }
+	for (u32 i = id->index; i < r->resource_count; i++) {
+		r->ids[i].index--;
+	}
 }
diff --git a/src/resource.h b/src/resource.h
@@ -39,6 +39,7 @@ void destroy_resource_manager(struct resource_manager *);
 void *new_resource(struct resource_manager *, struct resource_id *id);
 void print_id(struct resource_id *, int nl);
 void null_id(struct resource_id *id);
+int is_resource_destroyed(struct resource_manager *r, struct resource_id *id);
 
 void init_resource_manager(struct resource_manager *r, u32 elem_size,
                            u32 initial_elements, u32 max_elements, const char *name);
diff --git a/src/scene.c b/src/scene.c
@@ -1 +0,0 @@
-
diff --git a/src/shader.c b/src/shader.c
@@ -20,319 +20,537 @@ static char *line_buff[MAX_LINES];
 static u32 line_lens[MAX_LINES];
 
 static char *cached_file_contents(const char *filename) {
-    size_t len;
-    for (int i = 0; i < file_buf_count; i++) {
-        if (memcmp(file_names[i], filename, file_name_lens[i]) == 0) {
-            return file_buffers[i];
-        }
-    }
-
-    return (char*)file_contents(filename, &len);
+	size_t len;
+	for (int i = 0; i < file_buf_count; i++) {
+		if (memcmp(file_names[i], filename, file_name_lens[i]) == 0) {
+			return file_buffers[i];
+		}
+	}
+
+	return (char*)file_contents(filename, &len);
 }
 
 static char *strsep(char **stringp, const char *delim) {
-    if (*stringp == NULL) { return NULL; }
-    char *token_start = *stringp;
-    *stringp = strpbrk(token_start, delim);
-    if (*stringp)
-        (*stringp)++;
-    return token_start;
+	if (*stringp == NULL) { return NULL; }
+	char *token_start = *stringp;
+	*stringp = strpbrk(token_start, delim);
+	if (*stringp)
+		(*stringp)++;
+	return token_start;
 }
 
 static char **resolve_imports(char *contents, int *nlines, int level) {
-    char *line;
-    int nline = 0;
-    char *resolved_contents;
-    char fname_buf[32] = {0};
-
-    while ((line = strsep(&contents, "\n"))) {
-        nline++;
-        int line_len = contents - line;
-        int size = sizeof("#include");
-        if (memcmp(line, "#include ", size) == 0) {
-            char *filename = line + size;
-            int file_name_len = line_len - size - 1;
-            snprintf(fname_buf, 32, SHADER("%.*s"), file_name_len, filename);
-            file_name_lens[file_buf_count] = file_name_len;
-            file_names[file_buf_count] = filename;
-
-            /* printf("got include %.*s at line %d level %d ind %d\n", */
-            /*         file_name_len, filename, nline, level, file_buf_count); */
-
-            // TODO (perf): cache file contents based on filename
-            resolved_contents = cached_file_contents(fname_buf);
-            file_buffers[file_buf_count] = resolved_contents;
-            file_buf_count++;
-            resolve_imports(resolved_contents, nlines, level + 1);
-        }
-        else {
-            line_buff[*nlines] = line;
-            line_lens[*nlines] = line_len;
-            (*nlines)++;
-            /* printf("%d %.*s", *nlines, line_len, line); */
-        }
-    }
-
-    return line_buff;
+	char *line;
+	int nline = 0;
+	char *resolved_contents;
+	char fname_buf[32] = {0};
+
+	while ((line = strsep(&contents, "\n"))) {
+		nline++;
+		int line_len = contents - line;
+		int size = sizeof("#include");
+		if (memcmp(line, "#include ", size) == 0) {
+			char *filename = line + size;
+			int file_name_len = line_len - size - 1;
+			snprintf(fname_buf, 32, SHADER("%.*s"), file_name_len, filename);
+			file_name_lens[file_buf_count] = file_name_len;
+			file_names[file_buf_count] = filename;
+
+			/* printf("got include %.*s at line %d level %d ind %d\n", */
+			/*		   file_name_len, filename, nline, level, file_buf_count); */
+
+			// TODO (perf): cache file contents based on filename
+			resolved_contents = cached_file_contents(fname_buf);
+			file_buffers[file_buf_count] = resolved_contents;
+			file_buf_count++;
+			resolve_imports(resolved_contents, nlines, level + 1);
+		}
+		else {
+			line_buff[*nlines] = line;
+			line_lens[*nlines] = line_len;
+			(*nlines)++;
+			/* printf("%d %.*s", *nlines, line_len, line); */
+		}
+	}
+
+	return line_buff;
 }
 
 
-struct shader *get_program_shader(struct gpu_program *program, GLenum type) {
-    struct shader *shader;
-    for (int i = 0; i < program->n_shaders; i++) {
-        shader = &program->shaders[i];
-        if (shader->type == type)
-            return shader;
-    }
+struct shader *get_program_shader(struct gpu_program *program, GLenum type)
+{
+	struct shader *shader;
+	for (int i = 0; i < program->n_shaders; i++) {
+		shader = &program->shaders[i];
+		if (shader->type == type)
+			return shader;
+	}
 
-    return NULL;
+	return NULL;
 }
 
-
 int make_shader(GLenum type, const char *filename, struct shader *shader) {
-  size_t length;
-  int nlines = 0;
-  GLchar *source = (GLchar *)file_contents(filename, &length);
-  if (!source)
-      return 0;
+	size_t length;
+	int nlines = 0;
+	GLchar *source = (GLchar *)file_contents(filename, &length);
+	if (!source)
+		return 0;
 
-  assert(file_buf_count == 0);
-  char **lines = resolve_imports(source, &nlines, 0);
+	assert(file_buf_count == 0);
+	char **lines = resolve_imports(source, &nlines, 0);
+	
+	GLint shader_ok;
+	
+	shader->n_includes = 0;
+	shader->filename = filename;
+	shader->type = type;
+	shader->handle = glCreateShader(type);
+	
+	glShaderSource(shader->handle, nlines, (const char**)lines,
+		(const int*)line_lens);
 
-  GLint shader_ok;
+  // shader dependency stuff
+	for (int i = 0; i < file_buf_count; ++i) {
+		assert(i < MAX_SHADER_INCLUDES);
+		char *p = shader->includes[shader->n_includes];
+		int name_len = file_name_lens[i];
+		/* printf("%.*s name len %d\n", name_len, file_names[i], name_len); */
+		snprintf(p, MAX_INCLUDE_FNAME_LEN, SHADER("%.*s"), name_len, file_names[i]);
+		assert(name_len < MAX_INCLUDE_FNAME_LEN);
+		shader->include_mtimes[shader->n_includes] = file_mtime(p);
+		/* printf("'%s' included in '%s'\n", p, shader->filename); */
+		shader->n_includes++;
+		free(file_buffers[i]);
+	}
+	free(source);
 
-  shader->n_includes = 0;
-  shader->filename = filename;
-  shader->type = type;
-  shader->handle = glCreateShader(type);
+	file_buf_count = 0;
 
-  glShaderSource(shader->handle, nlines, (const char**)lines,
-                 (const int*)line_lens);
+	glCompileShader(shader->handle);
+	glGetShaderiv(shader->handle, GL_COMPILE_STATUS, &shader_ok);
 
-  // shader dependency stuff
-  for (int i = 0; i < file_buf_count; ++i) {
-      assert(i < MAX_SHADER_INCLUDES);
-      char *p = shader->includes[shader->n_includes];
-      int name_len = file_name_lens[i];
-      /* printf("%.*s name len %d\n", name_len, file_names[i], name_len); */
-      snprintf(p, MAX_INCLUDE_FNAME_LEN, SHADER("%.*s"), name_len, file_names[i]);
-      assert(name_len < MAX_INCLUDE_FNAME_LEN);
-      shader->include_mtimes[shader->n_includes] = file_mtime(p);
-      /* printf("'%s' included in '%s'\n", p, shader->filename); */
-      shader->n_includes++;
-      free(file_buffers[i]);
-  }
-  free(source);
-
-  file_buf_count = 0;
-
-  glCompileShader(shader->handle);
-
-  glGetShaderiv(shader->handle, GL_COMPILE_STATUS, &shader_ok);
-
-  if (!shader_ok) {
-    fprintf(stderr, "Failed to compile %s:\n", filename);
-    show_info_log(shader->handle);
-    glDeleteShader(shader->handle);
-    return 0;
-  }
-
-  shader->load_mtime =
-	file_mtime(shader->filename);
-
-  return 1;
+	if (!shader_ok) {
+		fprintf(stderr, "Failed to compile %s:\n", filename);
+		show_shader_info_log(shader->handle);
+		glDeleteShader(shader->handle);
+		return 0;
+	}
+
+	shader->load_mtime = file_mtime(shader->filename);
+
+	return 1;
 }
 
 #define N_SHADERS 3
 
 void init_gpu_program(struct gpu_program *program) {
-    memset(program, 0, sizeof(*program));
+	memset(program, 0, sizeof(*program));
 }
 
-void add_attribute(struct gpu_program *program, const char *name,
-                   enum vertex_attr attr)
+void link_attribute(struct gpu_program *program, const char *name,
+				   enum vertex_attr attr)
 {
-    program->vertex_attrs[attr] =
-        (gpu_addr)glGetAttribLocation(program->handle, name);
-    check_gl();
+	program->vertex_attrs[attr] =
+		(gpu_addr)glGetAttribLocation(program->handle, name);
+	program->active_attributes |= 1 << attr;
+	check_gl();
 }
 
-void add_uniform(struct gpu_program *program,
-                 const char *name,
-                 enum uniform_id id)
+static inline struct lens *get_uniform_lens(struct gpu_program *program,
+		int binding_id, int lens_id)
 {
-    struct uniform *var = &program->uniforms[id];
-    var->name = name;
-    var->id = id;
-    var->location = glGetUniformLocation(program->handle, var->name);
-    if (var->location == -1) {
-        debug("warn: could not find uniform location: %s in program %s\n",
-              var->name, program->name);
-    }
-    /* assert(var->location != -1); */
-    check_gl();
+	struct structure_binding *binding =
+		&program->bindings[binding_id];
+
+	return &binding->lenses[lens_id];
 }
 
-void find_program_uniforms(struct gpu_program *program,
-                           struct gpu_program *programs)
+void add_uniform(struct gpu_program *program,
+		 int id,
+		 int binding_id,
+		 int lens_id)
 {
-    // Program variables
-    add_uniform(program, "camera_position", UNIFORM_CAMERA_POSITION);
-    add_uniform(program, "depth_mvp", UNIFORM_DEPTH_MVP);
-    add_uniform(program, "light_intensity", UNIFORM_LIGHT_INTENSITY);
-    add_uniform(program, "sky_intensity", UNIFORM_SKY_INTENSITY);
-    add_uniform(program, "time", UNIFORM_TIME);
-    add_uniform(program, "light_dir", UNIFORM_LIGHT_DIR);
-    add_uniform(program, "sun_color", UNIFORM_SUN_COLOR);
-    add_uniform(program, "fog_on", UNIFORM_FOG_ON);
-    add_uniform(program, "model", UNIFORM_MODEL);
-    add_uniform(program, "mvp", UNIFORM_MVP);
-    add_uniform(program, "normal_matrix", UNIFORM_NORMAL_MATRIX);
-
-    // Attributes
-    add_attribute(program, "normal", va_normal);
-    add_attribute(program, "position", va_position);
-    add_attribute(program, "color", va_color);
-
-    // chess stuff
-    if (program == &programs[CHESS_PIECE_PROGRAM]) {
-        add_uniform(program, "is_white",
-                    UNIFORM_IS_WHITE);
-    }
+	struct uniform *var = &program->uniforms[id];
+	struct lens *lens = get_uniform_lens(program, binding_id, lens_id);
+
+	var->location = glGetUniformLocation(program->handle, lens->name);
+	var->id = id;
+	var->binding_id = binding_id;
+	var->lens_id = lens_id;
+
+	if (var->location == -1) {
+		debug("warn: could not find uniform location: %s in program %s\n",
+			lens->name, program->name);
+	}
+	/* assert(var->location != -1); */
+	check_gl();
 }
 
-void find_uniforms(struct gpu_program *programs)
+static GLenum datatype_to_gl_type(enum data_id_datatype type)
 {
-    for (int i = 0; i < NUM_PROGRAMS; ++i) {
-        struct gpu_program *program = &programs[i];
-        if (program == NULL) {
-            debug("program %d is NULL\n", i);
-            continue;
-        }
+	switch (type) {
+	case DATA_ID_INT: return GL_INT;
+	case DATA_ID_FLOAT: return GL_FLOAT;
+	case DATA_ID_VEC2: return GL_FLOAT_VEC2;
+	case DATA_ID_VEC3: return GL_FLOAT_VEC3;
+	case DATA_ID_VEC3P: return GL_FLOAT_VEC3;
+	case DATA_ID_MAT3: return GL_FLOAT_MAT3;
+	case DATA_ID_MAT4: return GL_FLOAT_MAT4;
+	case DATA_ID_MAT4P: return GL_FLOAT_MAT4;
+	}
 
-        if (program->name == NULL) {
-            debug("program %d name is NULL, not initialized?\n", i);
-            continue;
-        }
+	return -1;
+}
 
-        find_program_uniforms(program, programs);
-    }
+static const char *gl_type_str(GLenum type)
+{
+	switch (type) {
+	case GL_BYTE: return "byte";
+	case GL_UNSIGNED_BYTE: return "ubyte";
+	case GL_SHORT: return "short";
+	case GL_UNSIGNED_SHORT: return "ushort";
+	case GL_INT: return "int";
+	case GL_UNSIGNED_INT: return "uint";
+	case GL_FLOAT: return "float";
+	case GL_FLOAT_VEC2: return "float_vec2";
+	case GL_FLOAT_VEC3: return "float_vec3";
+	case GL_FLOAT_MAT3: return "float_mat3";
+	case GL_FLOAT_MAT4: return "float_mat4";
+	case GL_DOUBLE: return "double";
+	case GL_TEXTURE_2D: return "texture2d";
+	case GL_TEXTURE_3D: return "texture3d";
+	case GL_SAMPLER_2D: return "sampler2d";
+	case GL_SAMPLER_CUBE: return "samplerCube";
+	default: return "unknown";
+	}
+}
 
+int vertex_attr_from_name(const char *name, enum vertex_attr *attr)
+{
+	if (!strcmp(name, "position")) {
+		*attr = va_position;
+		return 1;
+	} else if (!strcmp(name, "color")) {
+		*attr = va_color;
+		return 1;
+	} else if (!strcmp(name, "normal")) {
+		*attr = va_normal;
+		return 1;
+	} else if (!strcmp(name, "index")) {
+		*attr = va_index;
+		return 1;
+	} else if (!strncmp(name, "tex_coord", 9)) {
+		*attr = va_tex_coord;
+		return 1;
+	}
+	return 0;
 }
 
-#ifdef DEBUG
-int reload_program(struct gpu_program *program,
-                   struct gpu_program *programs) {
-	int ok;
+void create_attribute_bindings(struct gpu_program *program)
+{
+	int i, size, length, count = 0;
+	char name[64];
+	GLenum type;
+	enum vertex_attr attr;
 
-    int n_shaders = program->n_shaders;
-    struct gpu_program new_program;
-    struct shader new_shaders[n_shaders];
-    struct shader *new_shaders_p[n_shaders];
+	glGetProgramiv(program->handle, GL_ACTIVE_ATTRIBUTES, &count);
+	program->active_attributes = 0;
 
-    init_gpu_program(&new_program);
+	for (i = 0; i < count; i++) {
+		glGetActiveAttrib(program->handle, (GLuint)i, sizeof(name),
+				&length, &size, &type, name);
 
-    int changes[n_shaders];
+		if (!vertex_attr_from_name(name, &attr)) {
+			printf("unknown vertex attribute: '%s'\n", name);
+			rtassert(0, "");
+		}
 
-    for (int i = 0; i < n_shaders; i++) {
-        changes[i] = 0;
-        struct shader *shader = &program->shaders[i];
-        new_shaders_p[i] = shader;
+		debug("%s: linking %s to %d\n", program->name, name, attr);
+		link_attribute(program, name, attr);
+	}
+}
 
-        time_t mtime = file_mtime(shader->filename);
-        int changed = mtime != shader->load_mtime;
+void create_uniform_bindings(struct gpu_program *program,
+		struct structure_binding *bindings,
+		int num_bindings)
+{
+	int i, binding_id, lens_id, size, length, count = 0, unis = 0;
+	GLenum type, lens_type;
+	struct lens *lens;
+	struct structure_binding *binding;
+	unsigned char *p;
+	char name[64];
+
+	program->bindings = bindings;
+	program->num_bindings = num_bindings;
+
+	glGetProgramiv(program->handle, GL_ACTIVE_UNIFORMS, &count);
+
+	for (i = 0; i < count; i++) {
+		glGetActiveUniform(program->handle, (GLuint)i, sizeof(name),
+				&length, &size, &type, name);
+
+		debug("%s: var '%s' :: %s\n", program->name, name,
+				gl_type_str(type));
+		// TODO: bind sampler uniforms
+		if (type == GL_SAMPLER_2D || type == GL_SAMPLER_CUBE) {
+			debug("create_uniform_bindings: skipping %s :: %s\n",
+					name, gl_type_str(type));
+			continue;
+		}
+
+		for (binding_id = 0; binding_id < num_bindings; binding_id++) {
+			binding = &bindings[binding_id];
+		for (lens_id = 0; lens_id < binding->num_lenses; lens_id++) {
+			lens = &binding->lenses[lens_id];
+			if (!strcmp(name, lens->name)) {
+				lens_type = datatype_to_gl_type(lens->type);
+				if (type != lens_type) {
+					printf("shader var type mismatch: %s shader(%s) != struct(%s)\n",
+							name,
+							gl_type_str(type),
+							gl_type_str(lens_type));
+					assert(0);
+				}
+				assert(type == datatype_to_gl_type(lens->type));
+				add_uniform(program, unis++, binding_id, lens_id);
+				goto cont;
+			}
+		}
+		}
+		
+		printf("bind_uniforms: could not bind uniform '%s' in %s\n",
+				name, program->name);
+		assert(0);
+	cont:
+		;
+	}
 
-        for (int j = 0; j < shader->n_includes; ++j) {
-            time_t include_mtime = shader->include_mtimes[j];
-            time_t new_mtime = file_mtime(shader->includes[j]);
-            changed |= include_mtime != new_mtime;
-        }
+	program->num_uniforms = unis;
+}
 
-        changes[i] = changed;
+static void bind_uniform_lens(struct lens *lens, int location, void *structure)
+{
+	float f;
+	float *fs;
+	int i;
+
+	switch (lens->type) {
+	case DATA_ID_FLOAT:
+		f = get_lens_float(lens, structure);
+		glUniform1f(location, f);
+		check_gl();
+		break;
+
+	case DATA_ID_INT:
+		i = get_lens_int(lens, structure);
+		glUniform1i(location, i);
+		check_gl();
+		break;
+
+	case DATA_ID_VEC2:
+		fs = get_lens_float_array(lens, structure);
+		glUniform2f(location, fs[0], fs[1]);
+		check_gl();
+		break;
+
+	case DATA_ID_VEC3:
+		fs = get_lens_float_array(lens, structure);
+		glUniform3f(location, fs[0], fs[1], fs[2]);
+		check_gl();
+		break;
+
+	case DATA_ID_VEC3P:
+		fs = get_lens_ptr(lens, structure);
+		glUniform3f(location, fs[0], fs[1], fs[2]);
+		check_gl();
+		break;
+
+	case DATA_ID_MAT3:
+		fs = get_lens_float_array(lens, structure);
+		glUniformMatrix3fv(location, 1, 0, fs);
+		check_gl();
+		break;
+
+	case DATA_ID_MAT4P:
+		fs = get_lens_ptr(lens, structure);
+		glUniformMatrix4fv(location, 1, 0, fs);
+		check_gl();
+		break;
+
+	case DATA_ID_MAT4:
+		fs = get_lens_float_array(lens, structure);
+		glUniformMatrix4fv(location, 1, 0, fs);
+		check_gl();
+		break;
+	}
+}
 
-        if (changed) {
-            /* printf("making shader %s\n", shader->filename); */
-            ok = make_shader(shader->type, shader->filename, &new_shaders[i]);
-            if (!ok) {
-                // clean up all the new shaders we've created so far
-                for (int k = 0; k < i; k++)
-                    glDeleteShader(new_shaders[k].handle);
-                return 0;
-            }
-            new_shaders_p[i] = &new_shaders[i];
-        }
-    }
+void bind_uniforms(struct gpu_program *program, void **structures, int num_structs)
+{
+	int i;
+	struct uniform *uniform;
+	struct lens *lens;
+	struct structure_binding *binding;
+	void *structure;
+	void *data;
+
+	if (num_structs < program->num_bindings) {
+		debug("%s: num_structs(%d) < program->num_bindings(%d)\n",
+				program->name, num_structs, program->num_bindings);
+	}
+	assert(num_structs >= program->num_bindings);
+
+	assert(program->name);
+	/*
+	debug("bind_uniforms: %s num_uniforms %d\n",
+			program->name, program->num_uniforms);
+			*/
+	for (i = 0; i < program->num_uniforms; i++) {
+		uniform = &program->uniforms[i];
+		binding = &program->bindings[uniform->binding_id];
+		lens = &binding->lenses[uniform->lens_id];
+		/*
+		debug("%s: binding %d %s binding_id:%d lens_id:%d\n",
+				program->name, i,
+				lens->name, uniform->binding_id,
+				uniform->lens_id);
+				*/
+		structure = structures[uniform->binding_id];
+		bind_uniform_lens(lens, uniform->location, structure);
+	}
+}
 
-    int any_changes = 0;
-    for (int i = 0; i < n_shaders; i++)
-        any_changes |= changes[i];
+static void link_uniform(struct gpu_program *program, struct uniform *u)
+{
+	struct lens *lens = get_uniform_lens(program, u->binding_id, u->lens_id);
+	u->location = glGetUniformLocation(program->handle, lens->name);
+	if (u->location == -1) {
+		debug("warn: could not find uniform location: %s in program %s\n",
+			lens->name, program->name);
+	}
+	/* assert(u->location != -1); */
+	check_gl();
+}
 
-    if (!any_changes) {
-        return 2;
-    }
+int reload_program(struct gpu_program *program, struct gpu_program *programs)
+{
+	int ok;
 
-	ok = make_program_from_shaders(program->name, new_shaders_p,
-                                   n_shaders, &new_program);
+	int n_shaders = program->n_shaders;
+	struct gpu_program new_program;
+	struct shader new_shaders[n_shaders];
+	struct shader *new_shaders_p[n_shaders];
+
+	init_gpu_program(&new_program);
+
+	int changes[n_shaders];
+
+	for (int i = 0; i < n_shaders; i++) {
+		changes[i] = 0;
+		struct shader *shader = &program->shaders[i];
+		new_shaders_p[i] = shader;
+
+		time_t mtime = file_mtime(shader->filename);
+		int changed = mtime != shader->load_mtime;
+
+		for (int j = 0; j < shader->n_includes; ++j) {
+			time_t include_mtime = shader->include_mtimes[j];
+			time_t new_mtime = file_mtime(shader->includes[j]);
+			changed |= include_mtime != new_mtime;
+		}
+
+		changes[i] = changed;
+
+		if (changed) {
+			/* printf("making shader %s\n", shader->filename); */
+			ok = make_shader(shader->type, shader->filename, &new_shaders[i]);
+			if (!ok) {
+				// clean up all the new shaders we've created so far
+				for (int k = 0; k < i; k++)
+					glDeleteShader(new_shaders[k].handle);
+				return 0;
+			}
+			new_shaders_p[i] = &new_shaders[i];
+		}
+	}
+
+	int any_changes = 0;
+	for (int i = 0; i < n_shaders; i++)
+		any_changes |= changes[i];
 
-    if (!ok) {
-        for (int i = 0; i < n_shaders; i++) {
-            glDeleteShader(program->shaders[i].handle);
-        }
-        return 0;
-    }
+	if (!any_changes) {
+		return 2;
+	}
 
-    *program = new_program;
+	ok = make_program_from_shaders(program->name, new_shaders_p,
+				       n_shaders, &new_program,
+				       program->bindings,
+				       program->num_bindings);
+
+	if (!ok) {
+		for (int i = 0; i < n_shaders; i++) {
+			glDeleteShader(program->shaders[i].handle);
+		}
+		return 0;
+	}
 
-    find_program_uniforms(program, programs);
-    return 1;
+	*program = new_program;
+	return 1;
 }
-#endif
 
 int
-make_program(const char *name,
-             struct shader *vertex,
-             struct shader *fragment,
-	         struct gpu_program *program)
+make_program(const char *name, struct shader *vertex, struct shader *fragment,
+	     struct gpu_program *program, struct structure_binding *bindings,
+	     int num_bindings)
 {
-    struct shader *shaders[] = { vertex, fragment };
-    return make_program_from_shaders(name, shaders, 2, program);
+	struct shader *shaders[] = { vertex, fragment };
+	return make_program_from_shaders(name, shaders, 2, program, bindings,
+			num_bindings);
 }
 
 int make_program_from_shaders(const char *name, struct shader **shaders,
-                              int n_shaders, struct gpu_program *program)
+			      int n_shaders, struct gpu_program *program,
+			      struct structure_binding *bindings,
+			      int num_bindings)
 {
 	GLint program_ok;
 
 	// TODO: relax these constraints
 	program->handle = glCreateProgram();
-    check_gl();
-    program->n_shaders = n_shaders;
-    program->name = name;
-
-    assert(n_shaders <= MAX_SHADERS);
-    for (int i = 0; i < n_shaders; i++) {
-        struct shader *shader = shaders[i];
-        program->shaders[i] = *shader;
-        /* printf("attaching shader %s\n", shader->filename); */
-        /* for (int j = 0; j < shader->n_includes; ++j) { */
-        /*     printf("attaching shader %s dep %s \n", shader->filename, */
-        /*            shader->includes[j]); */
-        /* } */
-        glAttachShader(program->handle, shader->handle);
-        check_gl();
-    }
+	check_gl();
+	program->n_shaders = n_shaders;
+	program->name = name;
+
+	assert(n_shaders <= MAX_SHADERS);
+	for (int i = 0; i < n_shaders; i++) {
+		struct shader *shader = shaders[i];
+		program->shaders[i] = *shader;
+		/* printf("attaching shader %s\n", shader->filename); */
+		/* for (int j = 0; j < shader->n_includes; ++j) { */
+		/*	   printf("attaching shader %s dep %s \n", shader->filename, */
+		/*			  shader->includes[j]); */
+		/* } */
+		glAttachShader(program->handle, shader->handle);
+		check_gl();
+	}
 
 	glLinkProgram(program->handle);
-    check_gl();
+	check_gl();
 
 	glGetProgramiv(program->handle, GL_LINK_STATUS, &program_ok);
 
 	if (!program_ok) {
 		fprintf(stderr, "Failed to link shader program:\n");
-		show_info_log(program->handle);
+		show_program_info_log(program->handle);
 		glDeleteProgram(program->handle);
 		return 0;
 	}
 
+	if (num_bindings > 0)
+		create_uniform_bindings(program, bindings, num_bindings);
+
+	create_attribute_bindings(program);
+
 	return 1;
 }
diff --git a/src/shader.h b/src/shader.h
@@ -4,88 +4,76 @@
 #include <time.h>
 #include "gl.h"
 #include "vbo.h"
+#include "data_id.h"
 
 #define SHADER(f) "etc/shaders/" f
 
 #define MAX_SHADER_INCLUDES 16
 #define MAX_INCLUDE_FNAME_LEN 64
 #define MAX_SHADERS 5
+#define MAX_UNIFORMS 64
+#define MAX_SHADER_STRUCTS 4
+
+/* structure binding */
+struct structure_binding {
+	struct lens *lenses;
+	int num_lenses;
+};
 
 struct shader {
 	GLenum type;
 	GLuint handle;
-    int n_includes;
+	int n_includes;
 	const char *filename;
-    char includes[MAX_SHADER_INCLUDES][MAX_INCLUDE_FNAME_LEN];
-    time_t include_mtimes[MAX_SHADER_INCLUDES];
+	char includes[MAX_SHADER_INCLUDES][MAX_INCLUDE_FNAME_LEN];
+	time_t include_mtimes[MAX_SHADER_INCLUDES];
 	time_t load_mtime;
 };
 
-enum program_type {
-    DEFAULT_PROGRAM,
-    TERRAIN_PROGRAM,
-    UI_PROGRAM,
-    SKYBOX_PROGRAM,
-    CHESS_PIECE_PROGRAM,
-    NUM_PROGRAMS,
-};
-
-
-enum uniform_id {
-    UNIFORM_AMBIENT_STR,
-    UNIFORM_CAMERA_POSITION,
-    UNIFORM_DEPTH_MVP,
-    UNIFORM_DEPTH_VP,
-    UNIFORM_DIFFUSE_ON,
-    UNIFORM_FOG_ON,
-    UNIFORM_LIGHT_DIR,
-    UNIFORM_LIGHT_INTENSITY,
-    UNIFORM_MODEL,
-    UNIFORM_MODEL_VIEW,
-    UNIFORM_MVP,
-    UNIFORM_NORMAL_MATRIX,
-    UNIFORM_SKY_INTENSITY,
-    UNIFORM_SUN_COLOR,
-    UNIFORM_TIME,
-    UNIFORM_VIEW_PROJ,
-    UNIFORM_IS_WHITE,
-    MAX_UNIFORMS
-};
-
 struct uniform {
-    enum uniform_id id;
-    const char *name;
-    int location;
+	int id;
+	int location;
+	int binding_id;
+	int lens_id;
 };
 
 struct gpu_program {
 	struct shader shaders[MAX_SHADERS];
-    struct uniform uniforms[MAX_UNIFORMS];
+	struct uniform uniforms[MAX_UNIFORMS];
+	struct structure_binding *bindings;
+	int num_bindings;
 	gpu_addr vertex_attrs[MAX_VERTEX_ATTRS];
-    int n_shaders;
+	int n_shaders;
+	int num_uniforms;
+	int active_attributes;
 	GLuint handle;
-    const char *name;
+	const char *name;
 };
 
-void add_uniform(struct gpu_program *program, const char *name, enum uniform_id id);
-void add_attribute(struct gpu_program *program, const char *name, enum vertex_attr attr);
+void add_uniform(struct gpu_program *program, int id, int binding_id, int lens_id);
+
+void create_shader_bindings(struct gpu_program *program, struct structure_binding *bindings, int num_bindings);
 
 #define NO_GEOM_SHADER NULL
 
 int reload_program(struct gpu_program *program, struct gpu_program *programs);
 int make_shader(GLenum type, const char *filename, struct shader *shader);
 
-void find_uniforms(struct gpu_program *programs);
-void find_program_uniforms(struct gpu_program *program,
-                           struct gpu_program *programs);
+void bind_uniforms(struct gpu_program *program, void **structures, int num_structs);
+
 void init_gpu_program(struct gpu_program *program);
 int make_program_from_shaders(const char *name,
                               struct shader **shaders,
                               int n_shaders,
-                              struct gpu_program *program);
+                              struct gpu_program *program,
+			      struct structure_binding *bindings,
+			      int n_bindings
+			      );
 
 struct shader *get_program_shader(struct gpu_program *program, GLenum type);
 
-int make_program(const char *name, struct shader *vertex, struct shader *fragment, struct gpu_program *program);
+int make_program(const char *name, struct shader *vertex, struct shader *fragment,
+		struct gpu_program *program, struct structure_binding *bindings,
+		int n_bindings);
 
 #endif /* POLYADVENT_SHADER_H */
diff --git a/src/skybox.c b/src/skybox.c
@@ -3,8 +3,6 @@
 #include "util.h"
 #include "texture.h"
 
-#include <stdio.h>
-
 static GLfloat skybox_vertices[] = {
   1.0, 1.0, 1.0,  -1.0, 1.0, 1.0,  -1.0,-1.0, 1.0,   1.0,-1.0, 1.0,    // v0-v1-v2-v3 top
   1.0, 1.0,-1.0,   1.0, 1.0, 1.0,   1.0,-1.0, 1.0,   1.0,-1.0,-1.0,    // v5-v0-v3-v4 right
@@ -35,6 +33,16 @@ static u32 skybox_indices[] = {
 };
 
 
+#define SKY_LENS(field, typ) { #field, offsetof(struct skybox, field), DATA_ID_##typ }
+static struct lens skybox_lenses[] = {
+	SKY_LENS(mvp, MAT4P),
+};
+#undef SKY_LENS
+
+static struct structure_binding skybox_bindings[] = {
+    { skybox_lenses, ARRAY_SIZE(skybox_lenses) } 
+};
+
 void create_skybox(struct skybox *skybox, struct gpu_program *program) {
     struct shader vertex, frag;
     struct shader *shaders[] = {&vertex, &frag};
@@ -60,6 +68,7 @@ void create_skybox(struct skybox *skybox, struct gpu_program *program) {
     struct model *model = new_model(&skybox->model_id); assert(model);
     struct geometry *geom = get_geometry(&model->geom_id); assert(geom);
 
+    model->name = "skybox";
     make_buffer_geometry(&mkgeom, geom);
 
     static const char *faces[6] = {
@@ -82,25 +91,16 @@ void create_skybox(struct skybox *skybox, struct gpu_program *program) {
     /* }; */
 
     model->texture = create_cubemap(faces);
-    printf("cubemap texture %d\n", model->texture);
 
     make_shader(GL_VERTEX_SHADER, SHADER("skybox.v.glsl"), &vertex);
     check_gl();
     make_shader(GL_FRAGMENT_SHADER, SHADER("skybox.f.glsl"), &frag);
     check_gl();
 
-    ok = make_program("skybox", &vertex, &frag, skybox->program);
+    ok = make_program("skybox", &vertex, &frag, skybox->program,
+		    skybox_bindings, ARRAY_SIZE(skybox_bindings));
     assert(ok);
     check_gl();
-
-    skybox->uniforms.mvp =
-        glGetUniformLocation(skybox->program->handle, "mvp");
-
-    skybox->attrs[va_position] = (gpu_addr)
-        glGetAttribLocation(skybox->program->handle, "position");
-
-    skybox->attrs[va_tex_coord] = (gpu_addr)
-        glGetAttribLocation(skybox->program->handle, "tex_coord");
 }
 
 
@@ -108,6 +108,9 @@ void render_skybox(struct skybox *skybox, mat4 *camera) {
 
     struct model *model = get_model(&skybox->model_id); assert(model);
     struct geometry *geom = get_geometry(&model->geom_id); assert(geom);
+    void *structures[] = { skybox };
+
+    skybox->mvp = camera;
 
     glDepthFunc(GL_LEQUAL);
     glDepthMask(GL_FALSE);
@@ -116,13 +119,12 @@ void render_skybox(struct skybox *skybox, mat4 *camera) {
     glUseProgram(skybox->program->handle);
     check_gl();
 
-    glUniformMatrix4fv(skybox->uniforms.mvp, 1, 0, camera);
-    check_gl();
+    bind_uniforms(skybox->program, structures, ARRAY_SIZE(structures));
 
     glBindTexture(GL_TEXTURE_CUBE_MAP, model->texture);
     check_gl();
 
-    render_geometry(geom, skybox->attrs, skybox->program);
+    render_geometry(geom, skybox->program);
     check_gl();
 
     glDepthMask(GL_TRUE);
diff --git a/src/skybox.h b/src/skybox.h
@@ -8,13 +8,10 @@
 #include "mat4.h"
 
 struct skybox {
-    struct gpu_program *program;
-    struct model_id model_id;
-    gpu_addr attrs[MAX_VERTEX_ATTRS];
-    struct node node;
-    struct {
-        int mvp;
-    } uniforms;
+	struct gpu_program *program;
+	struct model_id model_id;
+	struct node node;
+	float *mvp;
 };
 
 void create_skybox(struct skybox *skybox, struct gpu_program *program);
diff --git a/src/terrain.c b/src/terrain.c
@@ -1,4 +1,5 @@
 
+
 #include "terrain.h"
 #include "util.h"
 #include "delaunay.h"
@@ -6,9 +7,6 @@
 #include "vec3.h"
 #include "perlin.h"
 #include "poisson.h"
-#include "mdl.h"
-
-static const double pdist = 24.0;
 
 static const float plane_verts[] = {
   -1,-1,0,  -1,1,0,  1,1,0,  1,-1,0
@@ -55,13 +53,6 @@ void reset_terrain(struct terrain *terrain, float size) {
     assert(ent);
 }
 
-double offset_fn(struct terrain* terrain, double x, double y) {
-    struct perlin_settings *perlin = &terrain->settings;
-    double ox = perlin->ox;
-    double oy = perlin->oy;
-    return old_noisy_boi(terrain, ox+x, oy+y);
-}
-
 void init_terrain(struct terrain *terrain, float size) {
     init_id(&terrain->entity_id);
 
@@ -70,9 +61,7 @@ void init_terrain(struct terrain *terrain, float size) {
     struct model *model = new_model(&ent->model_id); assert(model);
     /* struct model *model = init_model(&ent->model_id); assert(model); */
 
-    assert(terrain->entity_id.index == 0);
-
-    model->shader = TERRAIN_PROGRAM;
+    model->shader = 1;
     node_set_label(node, "terrain");
     ent->flags &= ~ENT_CASTS_SHADOWS;
 
@@ -80,22 +69,18 @@ void init_terrain(struct terrain *terrain, float size) {
     /* node->pos[2] = 20.0; */
 
     reset_terrain(terrain, size);
+}
 
-    terrain->fn = offset_fn;
-    terrain->cell_size = pdist;
-    terrain->n_cells = round(size / terrain->cell_size) + 1;
-    debug("n_cells %d\n", terrain->n_cells);
-
-    struct terrain_cell *grid =
-        calloc(terrain->n_cells * terrain->n_cells, sizeof(struct terrain_cell));
-
-    terrain->grid = grid;
-
+double offset_fn(struct terrain* terrain, double x, double y) {
+    struct perlin_settings *perlin = &terrain->settings;
+    double ox = perlin->ox;
+    double oy = perlin->oy;
+    return old_noisy_boi(terrain, ox+x, oy+y);
 }
 
 void gen_terrain_samples(struct terrain *terrain, float scale, const double pdist) {
 
-    debug("generating terrain samples...\n");
+    debug("generating terrain samples\n");
     if (terrain->samples)
         free(terrain->samples);
 
@@ -105,12 +90,9 @@ void gen_terrain_samples(struct terrain *terrain, float scale, const double pdis
     /* struct point *samples = */
     /*   uniform_samples(n_samples, game->terrain.size); */
 
-    
     struct point *samples =
         poisson_disk_samples(pdist, terrain->size, 30, &n_samples);
 
-    debug("done generating terrain samples\n");
-
     /* remap_samples(samples, n_samples, game->terrain.size); */
 
     /* draw_samples(samples, pdist, n_samples, game->terrain.size); */
@@ -123,6 +105,7 @@ void gen_terrain_samples(struct terrain *terrain, float scale, const double pdis
 static inline struct terrain_cell *index_terrain_cell(struct terrain *terrain, int x, int y)
 {
     if (x < 0 || y < 0 || x >= terrain->n_cells || y >= terrain->n_cells) {
+        assert(!"terrain oob");
         return NULL;
     }
 
@@ -133,11 +116,10 @@ static inline struct terrain_cell *query_terrain_cell(struct terrain *terrain,
                                                       float x, float y,
                                                       int *grid_x, int *grid_y)
 { 
-    if (x < 0) return NULL;
-    if(y < 0) return NULL;
-    if(x >= terrain->size) return NULL;
-    if(y >= terrain->size) return NULL;
-
+    assert(x >= 0);
+    assert(y >= 0);
+    assert(x < terrain->size);
+    assert(y < terrain->size);
     *grid_x = grid_index(terrain, x);
     *grid_y = grid_index(terrain, y);
 
@@ -147,7 +129,7 @@ static inline struct terrain_cell *query_terrain_cell(struct terrain *terrain,
 void query_terrain_grid(struct terrain *terrain, float x, float y,
                         struct terrain_cell *cells[9])
 {
-    int grid_x = 0, grid_y = 0;
+    int grid_x, grid_y;
 
     // middle
     cells[4] = query_terrain_cell(terrain, x, y, &grid_x, &grid_y);
@@ -218,94 +200,45 @@ static double distance_to_closest_edge(double size, double x, double y) {
     return min(top, min(left, min(right, bottom)));
 }
 
-static void compute_bounding(float *vertices, int num_verts, vec3 *min, vec3 *max)
-{
-	int i;
-	for (i = 0; i < num_verts; i++) {
-	}
-}
-
-void create_terrain_collision(struct terrain *terrain)
-{
-    double x, y;
-    int n;
-    u32 i;
-
-    for (i = 0; i < (u32)terrain->n_verts; i++) {
-        n = i*3;
-        /* double dx, dy; */
-
-        x = terrain->verts[n+0];
-        y = terrain->verts[n+1];
-
-        int grid_x = x / terrain->cell_size;
-        int grid_y = y / terrain->cell_size;
-
-        // clamp height at edge
-	printf("%f %f %f %d grid_x %d grid_y %f cell_size\n",
-			x, y, terrain->verts[n+2],
-			grid_x, grid_y, terrain->cell_size);
-
-        struct terrain_cell *cell =
-            index_terrain_cell(terrain, grid_x, grid_y);
-
-	insert_grid_vertex(cell, n);
-    }
-}
-
-
-void load_terrain(struct terrain *terrain)
-{
-	int seed;
-
-	assert(terrain->size > 0);
-	struct mdl_geometry terrain_mdl;
-	init_mdl_geometry(&terrain_mdl);
-	load_mdl("terrain.mdl", NULL, &terrain_mdl);
-
-	struct entity *ent = get_entity(&terrain->entity_id); assert(ent);
-	struct model *model = get_model(&ent->model_id); assert(model);
-	struct geometry *geom = get_geometry(&model->geom_id); assert(geom);
-
-	assert(terrain_mdl.mkgeom.joint_weights == 0);
-
-	make_buffer_geometry(&terrain_mdl.mkgeom, geom);
-
-	terrain->verts = terrain_mdl.mkgeom.vertices;
-	terrain->n_verts = terrain_mdl.mkgeom.num_verts;
-
-	//struct vert_tris *vert_tris = calloc(terrain->n_verts, sizeof(struct vert_tris));
-	//terrain->vtris = vert_tris;
-
-	//create_terrain_collision(terrain);
-}
-
 void create_terrain(struct terrain *terrain, float scale, int seed) {
     u32 i;
-    int n;
-    double x, y, z;
     const double size = terrain->size;
 
+    static const double pdist = 24.0;
     terrain->settings.seed = seed;
 
     float tmp1[3], tmp2[3];
     if (!terrain->n_samples) {
         gen_terrain_samples(terrain, scale, pdist);
-        //save_samples(terrain->samples, seed, terrain->n_samples);
+        /* save_samples(terrain->samples, seed, terrain->n_samples); */
     }
     assert(terrain->n_samples > 0);
 
     del_point2d_t *points = calloc(terrain->n_samples, sizeof(*points));
     float *verts = calloc(terrain->n_samples * 3, sizeof(*verts));
+    terrain->cell_size = pdist;
+    terrain->n_cells = round(size / terrain->cell_size) + 1;
+    debug("n_cells %d\n", terrain->n_cells);
+
+    struct terrain_cell *grid =
+        calloc(terrain->n_cells * terrain->n_cells, sizeof(struct terrain_cell));
+
+    terrain->grid = grid;
+
+    /* float *normals = calloc(terrain->n_samples * 3, sizeof(*verts)); */
+
+    terrain->fn = offset_fn;
 
+    // n random samples from our noise function
     for (i = 0; i < (u32)terrain->n_samples; i++) {
-        n = i*3;
+        int n = i*3;
+        double x, y;
+        /* double dx, dy; */
+
         x = terrain->samples[i].x;
         y = terrain->samples[i].y;
 
         double z = terrain->fn(terrain, x, y);
-        double d = distance_to_closest_edge(size, x, y);
-        z *= (d / (size/2.0)) * 2.0;
 
         points[i].x = x;
         points[i].y = y;
@@ -313,17 +246,28 @@ void create_terrain(struct terrain *terrain, float scale, int seed) {
         verts[n] = (float)x;
         verts[n+1] = (float)y;
 
+        int grid_x = verts[n] / terrain->cell_size;
+        int grid_y = verts[n+1] / terrain->cell_size;
+
+        // clamp height at edge
+
+        double d = distance_to_closest_edge(size, x, y);
+        z *= (d / (size/2.0)) * 2.0;
+
+        struct terrain_cell *cell =
+            index_terrain_cell(terrain, grid_x, grid_y);
+
+        assert(cell);
+
+        insert_grid_vertex(cell, n);
+
         static const double limit = 1.4;
         if (x < limit || x > size-limit || y < limit || y > size-limit)
-		verts[n+2] = 0;
+        verts[n+2] = 0;
         else
-		verts[n+2] = (float)z;
+        verts[n+2] = (float)z;
     }
 
-    /* float *normals = calloc(terrain->n_samples * 3, sizeof(*verts)); */
-
-    // n random samples from our noise function
-
     delaunay2d_t *del = delaunay2d_from(points, terrain->n_samples);
     tri_delaunay2d_t *tri = tri_delaunay2d_from(del);
 
@@ -388,27 +332,20 @@ void create_terrain(struct terrain *terrain, float scale, int seed) {
     struct entity *ent = get_entity(&terrain->entity_id);
     assert(ent);
 
-    struct mdl_geometry terrain_mdl;
-    init_mdl_geometry(&terrain_mdl);
-    
-    struct make_geometry *mkgeom = &terrain_mdl.mkgeom;
+    struct make_geometry mkgeom;
+    init_make_geometry(&mkgeom);
 
-    mkgeom->num_verts = num_verts;
-    mkgeom->vertices = (float*)del_verts;
-    mkgeom->normals = (float*)del_norms;
-    mkgeom->indices = (u32*)del_indices;
-    mkgeom->num_indices = num_verts;
+    mkgeom.num_verts = num_verts;
+    mkgeom.vertices = (float*)del_verts;
+    mkgeom.normals = (float*)del_norms;
+    mkgeom.indices = (u32*)del_indices;
+    mkgeom.num_indices = num_verts;
 
     struct model *model = get_model(&ent->model_id); assert(model);
     struct geometry *geom = get_geometry(&model->geom_id); assert(geom);
 
-    assert(mkgeom->joint_weights == 0);
-
-    make_buffer_geometry(mkgeom, geom);
-
-    compute_bounding(mkgeom->vertices, mkgeom->num_verts, terrain_mdl.min,
-		    terrain_mdl.max);
-    save_mdl("terrain.mdl", &terrain_mdl);
+    assert(mkgeom.joint_weights == 0);
+    make_buffer_geometry(&mkgeom, geom);
 
     delaunay2d_release(del);
     tri_delaunay2d_release(tri);
@@ -423,8 +360,6 @@ void create_terrain(struct terrain *terrain, float scale, int seed) {
     terrain->verts = verts;
     terrain->n_verts = num_verts;
 
-    create_terrain_collision(terrain);
-
     // we might need norms in memory eventually as well ?
     free(del_norms);
     free(del_indices);
diff --git a/src/terrain.h b/src/terrain.h
@@ -7,8 +7,8 @@
 #include "model.h"
 #include "debug.h"
 
-#define MAX_CELL_VERTS 16
-#define MAX_VERT_TRIS 32
+#define MAX_CELL_VERTS 4
+#define MAX_VERT_TRIS 20
 
 struct point;
 
@@ -68,7 +68,6 @@ void gen_terrain_samples(struct terrain *terrain, float scale, const double pdis
 void init_terrain(struct terrain *terrain, float size);
 void reset_terrain(struct terrain *terrain, float size);
 void create_terrain(struct terrain *terrain, float scale, int seed);
-void load_terrain(struct terrain *terrain);
 void destroy_terrain(struct terrain *terrain);
 void query_terrain_grid(struct terrain *terrain, float x, float y, struct terrain_cell *cells[9]);
 
diff --git a/src/terrain_collision.c b/src/terrain_collision.c
@@ -1,7 +1,6 @@
 
 #include "terrain_collision.h"
-
-#include <float.h>
+#include <stdint.h>
 
 struct grid_query {
     struct terrain_cell *cell;
@@ -23,8 +22,6 @@ static void get_closest_verts(struct terrain *terrain,
 {
     for (int i = 0; i < 9; i++) {
         struct terrain_cell *cell = cells[i];
-	if (!cell)
-		continue;
         for (int j = 0; j < cell->vert_count; j++) {
             vec3 *vpos = &terrain->verts[cell->verts_index[j]];
             float d = vec3_distsq(pos, vpos);
@@ -170,7 +167,7 @@ struct tri *collide_terrain(struct terrain *terrain, float *pos, float *move, fl
     struct terrain_cell *cells[9] = {0};
     struct grid_query queries[3];
     for (int i = 0; i < 3; i++) {
-        queries[i].distance = FLT_MAX;
+        queries[i].distance = 3.402823e+38;
         queries[i].cell = NULL;
         queries[i].cell_vert_index = -1;
     }
diff --git a/src/test_game.c b/src/test_game.c
@@ -6,9 +6,11 @@
 #include "game.h"
 #include "node.h"
 #include "render.h"
+#include "shader.h"
 #include "quat.h"
 #include "movement.h"
 #include "gpu.h"
+#include "util.h"
 #include "terrain_collision.h"
 #include "orbit_util.h"
 #include "mat_util.h"
@@ -21,8 +23,33 @@ static const float bias_matrix[] = {
   0.5, 0.5, 0.5, 1.0
 };
 
+#define CU_LENS(field, typ) { #field, offsetof(struct common_uniforms, field), DATA_ID_##typ }
+static struct lens common_lenses[] = {
+	CU_LENS(fog_on, INT),
+	CU_LENS(sky_intensity, FLOAT),
+	CU_LENS(light_intensity, FLOAT),
+	CU_LENS(depth_mvp, MAT4),
+	CU_LENS(mvp, MAT4),
+	CU_LENS(model_view, MAT4),
+	CU_LENS(normal_matrix, MAT4),
+	CU_LENS(sun_color, VEC3),
+	CU_LENS(camera_position, VEC3P),
+	CU_LENS(light_dir, VEC3),
+};
+#undef CU_LENS
+
+struct lens *get_common_lenses()
+{
+	return common_lenses;
+}
+
+int get_common_lenses_length()
+{
+	return ARRAY_SIZE(common_lenses);
+}
+
 static void resize_fbos(struct entity *player, struct fbo *shadow_buffer,
-                 float *m4_ortho, int width, int height)
+				 float *m4_ortho, int width, int height)
 {
 	if (shadow_buffer->handle) {
 		// TODO: remove once delete_fbo deletes attachments
@@ -37,15 +64,15 @@ static void resize_fbos(struct entity *player, struct fbo *shadow_buffer,
 	struct model *model   = get_model(&player->model_id); assert(model);
 	struct geometry *geom = get_geometry(&model->geom_id); assert(geom);
 
-	float left   = geom->min[0] - factor;
+	float left	 = geom->min[0] - factor;
 	float right  = geom->max[0] + factor;
 	float bottom = geom->min[1] - factor;
-	float top    = geom->max[1] + factor;
+	float top	 = geom->max[1] + factor;
 
-	/* float left   = -factor; */
-	/* float right  = factor; */
+	/* float left	= -factor; */
+	/* float right	= factor; */
 	/* float bottom = factor; */
-	/* float top    = -factor; */
+	/* float top	= -factor; */
 
 	const float near = -50.0;
 	const float far = 50.0;
@@ -55,7 +82,7 @@ static void resize_fbos(struct entity *player, struct fbo *shadow_buffer,
 
 	create_fbo(shadow_buffer, width, height );
 	/* fbo_attach_renderbuffer(&res->shadow_buffer, GL_DEPTH24_STENCIL8, */
-	/*                         GL_DEPTH_STENCIL_ATTACHMENT); */
+	/*						   GL_DEPTH_STENCIL_ATTACHMENT); */
 
 	/* fbo_attach_color_texture(&res->shadow_buffer); */
 	fbo_attach_depth_texture(shadow_buffer);
@@ -63,7 +90,7 @@ static void resize_fbos(struct entity *player, struct fbo *shadow_buffer,
 	check_fbo(shadow_buffer);
 
 	/* fbo_attach_texture(&res->shadow_buffer, GL_DEPTH_COMPONENT16, */
-	/*                    GL_DEPTH_COMPONENT, GL_DEPTH_ATTACHMENT); */
+	/*					  GL_DEPTH_COMPONENT, GL_DEPTH_ATTACHMENT); */
 }
 
 
@@ -80,217 +107,225 @@ static struct entity *get_terrain_entity(struct terrain *t) {
 }
 
 static void player_movement(struct game *engine, struct entity *player) {
-    /* if (player->flags & ENT_ON_GROUND) */
-    /*     entity_movement(game, player); */
+	/* if (player->flags & ENT_ON_GROUND) */
+	/*	   entity_movement(game, player); */
 	struct node *node = get_node(&player->node_id);
 	movement(engine, node, 2.0);
 }
 
 static void camera_keep_above_ground(struct terrain *terrain,
-                                     struct node *camera) {
-    if (get_entity(&terrain->entity_id)->flags & ENT_INVISIBLE)
-        return;
-    float move[3];
-    float *camworld = node_world(camera);
-    float pen = 0.0;
-    static const float penlim = 1.0;
-    struct tri *tri = collide_terrain(terrain, camworld, move, &pen);
-
-    if (!tri)
-        return;
-
-    if (pen < penlim) {
-        float dir[3], above[3];
-        vec3_normalize(move, dir);
-        vec3_scale(dir, pen < 0 ? penlim : -penlim, above);
-        vec3_add(camworld, move, camworld);
-        vec3_add(camworld, above, camworld);
-        /* vec3_add(move, above, move); */
-        /* vec3_add(camworld, move, camworld); */
-    }
+				     struct node *camera) {
+	if (get_entity(&terrain->entity_id)->flags & ENT_INVISIBLE)
+		return;
+	float move[3];
+	float *camworld = node_world(camera);
+	float pen = 0.0;
+	static const float penlim = 1.0;
+	struct tri *tri = collide_terrain(terrain, camworld, move, &pen);
+
+	if (!tri)
+		return;
+
+	if (pen < penlim) {
+		float dir[3], above[3];
+		vec3_normalize(move, dir);
+		vec3_scale(dir, pen < 0 ? penlim : -penlim, above);
+		vec3_add(camworld, move, camworld);
+		vec3_add(camworld, above, camworld);
+		/* vec3_add(move, above, move); */
+		/* vec3_add(camworld, move, camworld); */
+	}
 }
 
 static void entity_movement(struct game *game, struct entity *ent)
 {
-    static const float move_accel = 1.0f;
-    static const float max_speed = 10.0f;
-    struct node *node = get_node(&ent->node_id);
+	static const float move_accel = 1.0f;
+	static const float max_speed = 10.0f;
+	struct node *node = get_node(&ent->node_id);
 
-    float vel[3];
+	float vel[3];
 
-    float amt = 10.0 * game->dt;
+	float amt = 10.0 * game->dt;
 
-    if (game->input.keystates[SDL_SCANCODE_W]) {
-        vec3_forward(ent->velocity, node->orientation, V3(0,amt,0), vel);
-        if (vec3_lengthsq(vel) <= max_speed*max_speed)
-            vec3_copy(vel, ent->velocity);
-    }
+	if (game->input.keystates[SDL_SCANCODE_W]) {
+		vec3_forward(ent->velocity, node->orientation, V3(0,amt,0), vel);
+		if (vec3_lengthsq(vel) <= max_speed*max_speed)
+			vec3_copy(vel, ent->velocity);
+	}
 }
 
 
 static void player_terrain_collision(struct terrain *terrain, struct node *node) {
-    // player movement
-    static vec3 last_pos[3] = {0};
+	// player movement
+	static vec3 last_pos[3] = {0};
 
-    if (!vec3_approxeq(node->pos, last_pos)) {
-        float player_z = node->pos[2];
+	if (!vec3_approxeq(node->pos, last_pos)) {
+		float player_z = node->pos[2];
 
-        float terrain_z =
-            terrain->fn(terrain, node->pos[0], node->pos[1]);
+		float terrain_z =
+			terrain->fn(terrain, node->pos[0], node->pos[1]);
 
-        float inset =
-            min(0.0, player_z - terrain_z);
+		float inset =
+			min(0.0, player_z - terrain_z);
 
-        if (inset <= 0)
-            node_translate(node, V3(0.0, 0.0, -inset));
-    }
+		if (inset <= 0)
+			node_translate(node, V3(0.0, 0.0, -inset));
+	}
 
 }
 
 // TODO: match based on some real concept of time
 static void day_night_cycle(float time, struct test_game *res) {
-    float val = time * 0.0001;
-    float intensity = 1.0;//max(0.0, vec3_dot(res->light_dir, V3(0.0, 0.0, 1.0))); */
-    struct entity *player = get_player(res);
-    struct node *pnode = get_node(&player->node_id);
-    assert(pnode);
-    struct node *suncam = get_node(&res->sun_camera_id);
-    assert(suncam);
+	float val = time * 0.0001;
+	float intensity = 1.0;//max(0.0, vec3_dot(res->light_dir, V3(0.0, 0.0, 1.0))); */
+	struct entity *player = get_player(res);
+	struct node *pnode = get_node(&player->node_id);
+	assert(pnode);
+	struct node *suncam = get_node(&res->sun_camera_id);
+	assert(suncam);
+	struct common_uniforms *cvars = &res->common_vars;
 
-    float light_pos[3];
+	float light_pos[3];
 
-    float g = 0.6;
-    float b = 0.4;
-    res->sun_color[0] = 1.0;
-    res->sun_color[1] = g+intensity*(1.0-g);
-    res->sun_color[2] = b+intensity*(1.0-b);
+	float g = 0.6;
+	float b = 0.4;
+	cvars->sun_color[0] = 1.0;
+	cvars->sun_color[1] = g+intensity*(1.0-g);
+	cvars->sun_color[2] = b+intensity*(1.0-b);
 
-    /* res->sun_color[0] = 1.0; */
-    /* res->sun_color[1] = 1.0; */
-    /* res->sun_color[2] = 1.0; */
+	/* res->sun_color[0] = 1.0; */
+	/* res->sun_color[1] = 1.0; */
+	/* res->sun_color[2] = 1.0; */
 
-    /* vec3_scale(res->sun_color, res->light_intensity, gtmp); */
+	/* vec3_scale(res->sun_color, res->light_intensity, gtmp); */
 
-    /* float intensity = angle <= 0.5 */
-    /*     ? clamp(roots, darkest, 1.0) */
-    /*     : clamp(-roots * 0.4, darkest, 0.5); */
+	/* float intensity = angle <= 0.5 */
+	/*	   ? clamp(roots, darkest, 1.0) */
+	/*	   : clamp(-roots * 0.4, darkest, 0.5); */
 
-    res->light_intensity = intensity;
+	cvars->light_intensity = intensity;
 
-    /* vec3_normalize(res->light_intensity, res->light_intensity); */
+	/* vec3_normalize(res->light_intensity, res->light_intensity); */
 
-    res->light_dir[0] = 0.0;
-    /* res->light_dir[1] = sin(val); */
-    /* res->light_dir[2] = cos(val) + 1.0; */
-    res->light_dir[1] = 0.8;
-    res->light_dir[2] = 0.8;
+	cvars->light_dir[0] = 0.0;
+	/* res->light_dir[1] = sin(val); */
+	/* res->light_dir[2] = cos(val) + 1.0; */
+	cvars->light_dir[1] = 0.8;
+	cvars->light_dir[2] = 0.8;
 
-    vec3_normalize(res->light_dir, res->light_dir);
+	vec3_normalize(cvars->light_dir, cvars->light_dir);
 
-    /* printf("intensity %f(%f) n %f light_dir %f %f\n", roots, intensity, */
-    /*        n, res->light_dir[1], res->light_dir[2]); */
+	/* printf("intensity %f(%f) n %f light_dir %f %f\n", roots, intensity, */
+	/*		  n, res->light_dir[1], res->light_dir[2]); */
 
-    vec3_add(pnode->pos, res->light_dir, light_pos);
+	vec3_add(pnode->pos, cvars->light_dir, light_pos);
 
-    /* float target[3]; */
-    /* float hh = player->model.geom.max[2] / 2.0; */
-    /* vec3_copy(player->node.pos, target); */
-    /* target[2] += 2.0; */
+	/* float target[3]; */
+	/* float hh = player->model.geom.max[2] / 2.0; */
+	/* vec3_copy(player->node.pos, target); */
+	/* target[2] += 2.0; */
 
-    look_at(light_pos, pnode->pos, V3(0, 0, 1.0), suncam->mat);
-    /* look_at(light_pos, player->node.pos, V3(0, 0, 1.0), res->sun_camera.mat); */
+	look_at(light_pos, pnode->pos, V3(0, 0, 1.0), suncam->mat);
+	/* look_at(light_pos, player->node.pos, V3(0, 0, 1.0), res->sun_camera.mat); */
 }
 
 static void player_update(struct game *engine, struct test_game *game, struct entity *player)
 {
-    struct orbit *camera = &game->orbit_camera;
-    struct node *node = get_node(&player->node_id);
-    struct node *cam_node = get_node(&game->camera_node_id);
-    assert(node);
-    assert(cam_node);
-
-    orbit_update_from_mouse(camera, &engine->input,engine->user_settings.mouse_sens,
-				player, engine->dt);
-
-    camera_keep_above_ground(&game->terrain, cam_node);
-
-    // move player camera toward camera orientation
-    if (input_is_dragging(&engine->input, SDL_BUTTON_RIGHT)) {
-        float yaw = game->orbit_camera.coords.azimuth;
-        quat_axis_angle(V3(0.0, 0.0, 1.0), -yaw - RAD(90), node->orientation);
-    }
-
-    struct terrain *terrain = &game->terrain;
-
-    player_terrain_collision(terrain, node);
-
-    float move[3];
-    float pos[3];
-    float pen = 0.0;
-    vec3_copy(node_world(node), pos);
-    /* debug("node_world(player) %f %f %f\n", pos[0], pos[1], pos[2]); */
-    struct tri *tri = collide_terrain(terrain, pos, move, &pen);
-    //struct tri *tri = NULL;
-    /* node_translate(node, move); */
-
-    if (tri) {
-        if (vec3_eq(move, V3(0,0,0), 0.1)) {
-            player->flags |= ENT_ON_GROUND;
-        }
-        else if (pen < 0) {
-            node_translate(node, move);
-            /* vec3_all(player->velocity, 0); */
-            vec3_scale(player->velocity, 0.1, player->velocity);
-        }
-        else {
-            player->flags &= ~ENT_ON_GROUND;
-        }
-    }
-    else {
-        static int tric = 0;
-        /* debug("%d no tri\n", tric++); */
-    }
-
-    if (player->flags & ENT_ON_GROUND &&
-        (was_key_pressed_this_frame(engine, SDL_SCANCODE_SPACE) /*||
-         was_button_pressed_this_frame(engine, SDL_CONTROLLER_BUTTON_X)*/)) {
-        entity_jump(player, 2.0);
-    }
-
-    /* debug("player velocity %f %f %f\n", */
-    /*       player->velocity[0], */
-    /*       player->velocity[1], */
-    /*       player->velocity[2]); */
-
-//    if (player->flags & ENT_AT_REST)
+	struct orbit *camera = &game->orbit_camera;
+	struct node *node = get_node(&player->node_id);
+	struct node *cam_node = get_node(&game->camera_node_id);
+	assert(node);
+	assert(cam_node);
+
+	orbit_update_from_mouse(camera, &engine->input,
+		engine->user_settings.mouse_sens,
+		player, engine->dt);
+
+	camera_keep_above_ground(&game->terrain, cam_node);
+
+	// move player camera toward camera orientation
+	if (input_is_dragging(&engine->input, SDL_BUTTON_RIGHT)) {
+		float yaw = game->orbit_camera.coords.azimuth;
+		quat_axis_angle(V3(0.0, 0.0, 1.0), -yaw - RAD(90), node->orientation);
+	}
+
+	struct terrain *terrain = &game->terrain;
+
+	player_terrain_collision(terrain, node);
+
+	float move[3];
+	float pos[3];
+	float pen = 0.0;
+	vec3_copy(node_world(node), pos);
+	/* debug("node_world(player) %f %f %f\n", pos[0], pos[1], pos[2]); */
+	struct tri *tri = collide_terrain(terrain, pos, move, &pen);
+	//struct tri *tri = NULL;
+	/* node_translate(node, move); */
+
+	if (tri) {
+		if (vec3_eq(move, V3(0,0,0), 0.1)) {
+			player->flags |= ENT_ON_GROUND;
+		}
+		else if (pen < 0) {
+			node_translate(node, move);
+			/* vec3_all(player->velocity, 0); */
+			vec3_scale(player->velocity, 0.1, player->velocity);
+		}
+		else {
+			player->flags &= ~ENT_ON_GROUND;
+		}
+	}
+	else {
+		static int tric = 0;
+		/* debug("%d no tri\n", tric++); */
+	}
+
+	if (player->flags & ENT_ON_GROUND &&
+		(was_key_pressed_this_frame(engine, SDL_SCANCODE_SPACE) /*||
+		 was_button_pressed_this_frame(engine, SDL_CONTROLLER_BUTTON_X)*/)) {
+		entity_jump(player, 2.0);
+	}
+
+	/* debug("player velocity %f %f %f\n", */
+	/*		 player->velocity[0], */
+	/*		 player->velocity[1], */
+	/*		 player->velocity[2]); */
+
+//	  if (player->flags & ENT_AT_REST)
 //	vec3_scale(player->velocity, 0.00001, player->velocity);
 
-    node_translate(node, player->velocity);
-    node_recalc(node);
+	node_translate(node, player->velocity);
+	node_recalc(node);
 }
 
+enum binding_structs {
+	COMMON_UNIFORMS,
+	CHESS_PIECE_UNIFORMS,
+};
+
+static struct structure_binding uniform_bindings[] = {
+	[COMMON_UNIFORMS] =
+		{ common_lenses, ARRAY_SIZE(common_lenses) },
+};
 
 void init_test_gl(struct test_game *game, struct gpu *gpu, int width, int height)
 {
-	struct shader vertex, terrain_vertex, chess_piece_vertex, terrain_geom, fragment, fragment_smooth;
+	struct shader vertex, terrain_vertex, terrain_geom, fragment, fragment_smooth;
 	struct shader terrain_teval, terrain_tc;
 	float tmp_matrix[16];
 	int ok = 0;
 
+	gpu->num_programs = NUM_TEST_GAME_PROGRAMS;
 
 	// Shaders
 	ok = make_shader(GL_VERTEX_SHADER, SHADER("vertex-color.glsl"),
-                     &vertex);
+					 &vertex);
 	rtassert(ok, "vertex-color shader");
 
 	ok = make_shader(GL_VERTEX_SHADER, SHADER("terrain.v.glsl"), &terrain_vertex);
 	rtassert(ok, "terrain vertex shader");
 	check_gl();
 
-	ok = make_shader(GL_VERTEX_SHADER, SHADER("chess-piece.v.glsl"), &chess_piece_vertex);
-	rtassert(ok, "chess-piece vertex shader");
-	check_gl();
-
 	ok = make_shader(GL_FRAGMENT_SHADER, SHADER("main.f.glsl"), &fragment);
 	rtassert(ok, "default fragment shader");
 	check_gl();
@@ -303,38 +338,35 @@ void init_test_gl(struct test_game *game, struct gpu *gpu, int width, int height
 			 game->proj_persp);
 
 	struct gpu_program *programs = gpu->programs;
-	struct gpu_program *program;
 
 	ok = make_program("terrain", &terrain_vertex, &fragment,
-			&programs[TERRAIN_PROGRAM]);
-    // TODO: replace rtassert with error reporting/handling
+			  &programs[TERRAIN_PROGRAM],
+			  uniform_bindings,
+			  ARRAY_SIZE(uniform_bindings));
 	rtassert(ok, "terrain program");
 	check_gl();
 
-	ok = make_program("vertex-color", &vertex, &fragment, &programs[DEFAULT_PROGRAM]);
+	ok = make_program("vertex-color", &vertex, &fragment,
+			  &programs[DEFAULT_PROGRAM],
+			  uniform_bindings,
+			  ARRAY_SIZE(uniform_bindings));
 	rtassert(ok, "vertex-color program");
 	check_gl();
-
-	program = &programs[CHESS_PIECE_PROGRAM];
-	ok = make_program("chess-piece", &chess_piece_vertex, &fragment, program);
-	rtassert(ok, "chess-piece program");
-
-	find_uniforms(programs);
 }
 
 
-void init_gpu_programs(struct gpu *gpu)
-{
-}
+// TODO: add uniforms automatically?
 
+static void render_test_game(struct game *game, struct test_game *res, struct render_config *config) {
+	float gtmp[3];
+	u32 num_entities;
 
-void render_test_game(struct game *game, struct test_game *res, struct render_config *config) {
- 	float gtmp[3];
+	struct common_uniforms *cvars = &res->common_vars;
 
-	glEnable(GL_DEPTH_TEST);
+	cvars->sky_intensity = clamp(cvars->light_intensity, 0.2, 1.0);
+	vec3_scale(cvars->sun_color, cvars->sky_intensity, gtmp);
 
-	float sky_intensity = clamp(res->light_intensity, 0.2, 1.0);
-	vec3_scale(res->sun_color, sky_intensity, gtmp);
+	glEnable(GL_DEPTH_TEST);
 
 	glClearColor( gtmp[0], gtmp[1], gtmp[2], 1.0 ); //clear background screen to black
 	/* glClearColor( 0.5294f * adjust, 0.8078f * adjust, 0.9216f * adjust, 1.0f ); //clear background screen to black */
@@ -346,23 +378,19 @@ void render_test_game(struct game *game, struct test_game *res, struct render_co
 	static float view[MAT4_ELEMS] = { 0 };
 	static float view_proj[MAT4_ELEMS] = { 0 };
 	static float normal_matrix[MAT4_ELEMS] = { 0 };
-	static float model_view[MAT4_ELEMS] = { 0 };
-	static float depth_mvp[MAT4_ELEMS] = { 0 };
+
 	mat4_id(id);
-	mat4_id(model_view);
+	mat4_id(cvars->model_view);
 
-	mat4 *mvp = res->test_mvp;
 	mat4 *projection = config->projection;
-	mat4 *light = res->light_dir;
 
 	struct node *camera_node = get_node(&config->camera);
 	assert(camera_node);
 
 	const mat4 *camera = camera_node->mat;
-	u32 num_entities;
 
 	struct entity *entities =
-	    get_all_entities(&num_entities, NULL);
+		get_all_entities(&num_entities, NULL);
 
 	struct gpu_program *program = NULL;
 
@@ -370,11 +398,11 @@ void render_test_game(struct game *game, struct test_game *res, struct render_co
 	mat4_multiply(projection, view, view_proj);
 
 	if (config->is_depth_pass) {
-	    glDisable(GL_CULL_FACE);
-	    mat4_multiply(bias_matrix, view_proj, config->depth_vp);
+		glDisable(GL_CULL_FACE);
+		mat4_multiply(bias_matrix, view_proj, config->depth_vp);
 	}
 	else {
-	    glCullFace(GL_BACK);
+		glCullFace(GL_BACK);
 	}
 
 	mat4_inverse((float *)camera, view);
@@ -386,6 +414,8 @@ void render_test_game(struct game *game, struct test_game *res, struct render_co
 	glBindTexture(GL_TEXTURE_CUBE_MAP, skybox_model->texture);
 	check_gl();
 
+	cvars->camera_position = (float*)&camera[M_X];
+
 	for (u32 i = 0; i < num_entities; ++i) {
 		struct entity *entity = &entities[i];
 		struct model *model = get_model(&entity->model_id);
@@ -394,44 +424,39 @@ void render_test_game(struct game *game, struct test_game *res, struct render_co
 		assert(node);
 
 		if (entity->flags & ENT_INVISIBLE)
-		    continue;
+			continue;
 
 		if (config->is_depth_pass && !(entity->flags & ENT_CASTS_SHADOWS))
-		    continue;
+			continue;
 
 		program = &game->gpu.programs[model->shader];
 
 		glUseProgram(program->handle);
 		check_gl();
 
-		uniform_3f(program, UNIFORM_CAMERA_POSITION, &camera[M_X]);
-
-		if (model->shader == CHESS_PIECE_PROGRAM) {
-			uniform_1i(program, UNIFORM_IS_WHITE,
-				(entity->flags & ENT_IS_WHITE) == ENT_IS_WHITE);
-		}
+		mat4_multiply(view_proj, node->mat, cvars->mvp);
+		mat4_copy(node->mat, cvars->model_view);
+		mat4_multiply(config->depth_vp, cvars->model_view, cvars->depth_mvp);
 
-		uniform_1i(program, UNIFORM_FOG_ON, res->fog_on);
-		uniform_3f(program, UNIFORM_LIGHT_DIR, light);
-		uniform_1f(program, UNIFORM_LIGHT_INTENSITY, res->light_intensity);
-		uniform_1f(program, UNIFORM_SKY_INTENSITY, sky_intensity);
-		uniform_3f(program, UNIFORM_SUN_COLOR, res->sun_color);
+		void *shader_data[] = {
+			[COMMON_UNIFORMS] = cvars,
+			[CHESS_PIECE_UNIFORMS] = entity->data,
+		};
 
-		mat4_multiply(view_proj, node->mat, mvp);
-		mat4_copy(node->mat, model_view);
-		mat4_multiply(config->depth_vp, model_view, depth_mvp);
-		uniform_m4f(program, UNIFORM_DEPTH_MVP, depth_mvp);
-		uniform_m4f(program, UNIFORM_MVP, mvp);
-		uniform_m4f(program, UNIFORM_MODEL, node->mat);
+		bind_uniforms(program, shader_data, ARRAY_SIZE(shader_data));
+		check_gl();
 
+		/*
 		recalc_normals(program->uniforms[UNIFORM_NORMAL_MATRIX].location,
-		               model_view, normal_matrix);
+					   model_view, normal_matrix);
+
 		check_gl();
+		*/
 
 		struct geometry *geo = get_geometry(&model->geom_id);
 		/* debug("geo node %s\n", node->label); */
 		assert(geo);
-		render_geometry(geo, program->vertex_attrs, program);
+		render_geometry(geo, program);
 		check_gl();
 	}
 
@@ -451,7 +476,7 @@ void render_test_game(struct game *game, struct test_game *res, struct render_co
 	}
 
 	if (config->draw_ui)
-		render_ui(&game->ui, view);
+		render_ui(&res->ui, view);
 
 	//player
 	// y tho
@@ -472,17 +497,18 @@ void render_test_game(struct game *game, struct test_game *res, struct render_co
 void update_test_game (struct game *engine, struct test_game *game) {
 	static int toggle_fog = 0;
 	static int needs_terrain_update = 0;
-    	//struct terrain   *terrain      = &game->terrain;
-	struct node      *root         = get_node(&game->root_id);
-	struct entity    *player       = get_player(game);
-	struct node      *pnode        = get_node(&player->node_id);
-	struct node      *cam_node     = get_node(&game->camera_node_id);
+		//struct terrain   *terrain		 = &game->terrain;
+	struct node *root     = get_node(&game->root_id);
+	struct entity *player = get_player(game);
+	struct node *pnode    = get_node(&player->node_id);
+	struct node *cam_node = get_node(&game->camera_node_id);
+	struct common_uniforms *cvars = &game->common_vars;
 
 	assert(pnode);
 	assert(cam_node);
 
 	float *time = &game->time;
-	float *light = game->light_dir;
+	float *light = cvars->light_dir;
 
 	gravity(player, engine->dt);
 
@@ -491,8 +517,8 @@ void update_test_game (struct game *engine, struct test_game *game) {
 		needs_terrain_update = 0;
 	}
 
-    /* spherical_dir(game->test_resources.camera.coords, camera_dir); */
-    /* vec3_scale(camera_dir, -1, camera_dir); */
+	/* spherical_dir(game->test_resources.camera.coords, camera_dir); */
+	/* vec3_scale(camera_dir, -1, camera_dir); */
 
 	if (engine->input.modifiers & KMOD_ALT &&
 		ideq(&game->camera_node_id, &game->free_camera_id))
@@ -510,8 +536,9 @@ void update_test_game (struct game *engine, struct test_game *game) {
 	player_update(engine, game, player);
 
 #ifdef DEBUG
-	if (was_key_pressed_this_frame(engine, SDL_SCANCODE_R))
+	if (was_key_pressed_this_frame(engine, SDL_SCANCODE_R)) {
 		try_reload_shaders(&engine->gpu);
+	}
 #endif
 
 	if (was_key_pressed_this_frame(engine, SDL_SCANCODE_F5)) {
@@ -538,7 +565,7 @@ void update_test_game (struct game *engine, struct test_game *game) {
 	}
 
 	if (toggle_fog) {
-		game->fog_on = !game->fog_on;
+		cvars->fog_on ^= 1;
 		toggle_fog = 0;
 	}
 
@@ -563,7 +590,7 @@ void test_game_frame(struct game *engine, struct test_game *game)
 	};
 
 	struct render_config default_config = {
-		.draw_ui = 0,
+		.draw_ui = 1,
 		.is_depth_pass = 0,
 		.camera = game->camera_node_id,
 		.projection = game->proj_persp,
@@ -571,7 +598,7 @@ void test_game_frame(struct game *engine, struct test_game *game)
 	};
 
 	struct entity *player = get_entity(&game->player_id);
-        if (engine->input.resized_height) {
+	if (engine->input.resized_height) {
 		mat4_perspective(60 /* fov */,
 				(float)engine->width / (float)engine->height, 0.1,
 				10000.0, game->proj_persp);
@@ -580,28 +607,27 @@ void test_game_frame(struct game *engine, struct test_game *game)
 			engine->width, engine->height);
 	}
 
-        update_test_game(engine, game);
+	update_test_game(engine, game);
 
-        struct fbo *fbo = &game->shadow_buffer;
-        check_fbo(fbo);
-        bind_fbo(fbo);
-        /* glDrawBuffer(GL_NONE); */
+	struct fbo *fbo = &game->shadow_buffer;
+	check_fbo(fbo);
+	bind_fbo(fbo);
+		/* glDrawBuffer(GL_NONE); */
 
-        render_test_game(engine, game, &fbo_render_config);
-        unbind_fbo(&game->shadow_buffer);
-        render_test_game(engine, game, &default_config);
+	render_test_game(engine, game, &fbo_render_config);
+	unbind_fbo(&game->shadow_buffer);
+	render_test_game(engine, game, &default_config);
 }
 
-void init_test_game(struct test_game *game, struct gpu *gpu, int width, int height)
+void init_test_game(struct game *engine, struct test_game *game)
 {
 	memset(game, 0, sizeof(*game));
 	struct terrain *terrain = &game->terrain;
 	struct entity *player;
+	struct model *model;
+	struct common_uniforms *cvars = &game->common_vars;
 
-	init_test_gl(game, gpu, width, height);
-
-	mat4 *mvp = game->test_mvp;
-	mat4 *light_dir = game->light_dir;
+	init_test_gl(game, &engine->gpu, engine->width, engine->height);
 
 	init_id(&game->root_id);
 	init_id(&game->sun_camera_id);
@@ -618,50 +644,46 @@ void init_test_game(struct test_game *game, struct gpu *gpu, int width, int heig
 	//double scale = 0.03;
 	double scale = 0.03;
 
-	create_skybox(&game->skybox, &gpu->programs[SKYBOX_PROGRAM]);
+	create_skybox(&game->skybox, &engine->gpu.programs[SKYBOX_PROGRAM]);
+	create_ui(&game->ui, engine->width, engine->height, &engine->gpu.programs[UI_PROGRAM]);
 
 	terrain->settings = (struct perlin_settings){
-	    .depth = 1,
-	    .freq  = scale * 0.08,
-	    .o1 = 2.0, .o1s = 0.5,
-	    .o2 = 4.0, .o2s = 0.25,
-	    .amplitude  = 70.0,
-	    .ox = 0,
-	    .oy = 0,
-	    .exp = 5.3,
-	    .scale = scale
+		.depth = 1,
+		.freq  = scale * 0.08,
+		.o1 = 2.0, .o1s = 0.5,
+		.o2 = 4.0, .o2s = 0.25,
+		.amplitude	= 70.0,
+		.ox = 0,
+		.oy = 0,
+		.exp = 5.3,
+		.scale = scale
 	};
 
 	init_terrain(terrain, size);
 
 	/* terrain->samples = load_samples(&seed, &terrain->n_samples); */
 	terrain->n_samples = 0;
-	//create_terrain(terrain, size, game->seed);
-	load_terrain(terrain);
+	create_terrain(terrain, size, engine->seed);
+	//load_terrain(terrain);
 	/* update_terrain(terrain, terrain->cell_size); */
 	/* get_entity(&terrain->entity_id)->flags |= ENT_INVISIBLE; */
 
 	/* node_scale(&game->skybox.node, size/4.0); */
 
-	mat4_id(mvp);
+	mat4_id(cvars->mvp);
 
-	game->time = 0;
-	game->light_intensity = 0.8;
+	cvars->light_intensity = 0.8;
 
-	light_dir[0] = 0.8;
-	light_dir[1] = 0.8;
-	light_dir[2] = 0.8;
+	cvars->light_dir[0] = 0.8;
+	cvars->light_dir[1] = 0.8;
+	cvars->light_dir[2] = 0.8;
 
-	game->piece_color[0] = 1.0;
-	game->piece_color[1] = 1.0;
-	game->piece_color[2] = 1.0;
+	cvars->sun_color[0] = 0.5;
+	cvars->sun_color[1] = 0.6;
+	cvars->sun_color[2] = 0.7;
 
-	game->sun_color[0] = 0.5;
-	game->sun_color[1] = 0.6;
-	game->sun_color[2] = 0.7;
-
-	game->fog_on = 0;
-	game->diffuse_on = 0;
+	cvars->fog_on = 0;
+	//cvars->diffuse_on = 0;
 
 	node_init(root);
 	node_init(sun_camera);
@@ -681,6 +703,7 @@ void init_test_game(struct test_game *game, struct gpu *gpu, int width, int heig
 	player->model_id = get_model_by_name("pirate_officer", NULL);
 	assert(!is_null_id(&player->model_id.id));
 
+
 	node_set_label(pnode, "player");
 	/* node_rotate(pnode, V3(-5.0,0,0)); */
 	node_attach(&player->node_id, &game->root_id);
@@ -704,7 +727,7 @@ void init_test_game(struct test_game *game, struct gpu *gpu, int width, int heig
 
 	// FBO STUFF
 	init_fbo(&game->shadow_buffer);
-	resize_fbos(player, &game->shadow_buffer, game->proj_ortho, width, height);
+	resize_fbos(player, &game->shadow_buffer, game->proj_ortho, engine->width, engine->height);
 	// FBO STUFF END
 
 	// TEXTURES
@@ -724,11 +747,12 @@ void default_scene(struct test_game *game) {
 
 	struct entity *tower = new_entity(NULL);
 	struct node *tnode = get_node(&tower->node_id);
+	struct node *pnode = get_node(&player->node_id);
 
 	assert(tnode);
 	tower->model_id = get_model_by_name("tower", NULL);
 	node_set_label(tnode, "tower");
-	//node_attach(&tower->node_id, &player->node_id);
+	node_attach(&tower->node_id, &player->node_id);
 	node_translate(tnode, V3(0.0, 50.0, 0.0));
 	node_recalc(tnode);
 	float z = terrain->fn(terrain, tnode->mat[M_X], tnode->mat[M_Y]);
@@ -740,40 +764,40 @@ void default_scene(struct test_game *game) {
 
 void entity_test_scene(struct terrain *terrain)
 {
-    struct model_id rock_model;
-    init_id(&rock_model.id);
+	struct model_id rock_model;
+	init_id(&rock_model.id);
 
-    /* model_id rock_model = get_static_model(model_tower, NULL); */
-    struct model *pmodel  = new_model(&rock_model); assert(pmodel);
-    struct geometry *geom = get_geometry(&pmodel->geom_id); assert(geom);
-    proc_sphere(geom);
+	/* model_id rock_model = get_static_model(model_tower, NULL); */
+	struct model *pmodel  = new_model(&rock_model); assert(pmodel);
+	struct geometry *geom = get_geometry(&pmodel->geom_id); assert(geom);
+	proc_sphere(geom);
 
-    for (int i = 0; i < 200; i++) {
-        struct entity *ent = new_entity(NULL);
-        struct node *node  = get_node(&ent->node_id);
+	for (int i = 0; i < 200; i++) {
+		struct entity *ent = new_entity(NULL);
+		struct node *node  = get_node(&ent->node_id);
 
-        ent->model_id = rock_model;
+		ent->model_id = rock_model;
 
-        double x = rand_0to1() * terrain->size;
-        double y = rand_0to1() * terrain->size;
-        double z = terrain->fn(terrain, x, y);
+		double x = rand_0to1() * terrain->size;
+		double y = rand_0to1() * terrain->size;
+		double z = terrain->fn(terrain, x, y);
 
-        node_scale(node, pow(15.0, rand_0to1()));
-        node_rotate(node, V3(rand_0to1(),rand_0to1(),rand_0to1()));
-        node_translate(node, V3(x, y, z));
-        node_set_label(node, "rock");
+		node_scale(node, pow(15.0, rand_0to1()));
+		node_rotate(node, V3(rand_0to1(),rand_0to1(),rand_0to1()));
+		node_translate(node, V3(x, y, z));
+		node_set_label(node, "rock");
 
-        node_recalc(node);
-    }
+		node_recalc(node);
+	}
 
 }
 
 void pbr_scene(struct test_game *game)
 {
-    struct entity *ent = new_entity(NULL);
-    struct node *node  = get_node(&ent->node_id); assert(node);
-    struct entity *player = get_player(game);
+	struct entity *ent = new_entity(NULL);
+	struct node *node  = get_node(&ent->node_id); assert(node);
+	struct entity *player = get_player(game);
 
-    ent->model_id = get_model_by_name("icosphere", NULL);
-    node_set_label(node, "sphere");
+	ent->model_id = get_model_by_name("icosphere", NULL);
+	node_set_label(node, "sphere");
 }
diff --git a/src/test_game.h b/src/test_game.h
@@ -10,11 +10,38 @@
 #include "gpu.h"
 #include "game.h"
 
+enum test_game_programs {
+    DEFAULT_PROGRAM,
+    TERRAIN_PROGRAM,
+    UI_PROGRAM,
+    SKYBOX_PROGRAM,
+    CHESS_PIECE_PROGRAM,
+    NUM_TEST_GAME_PROGRAMS,
+};
+
+struct common_uniforms {
+// int diffuse_on;
+	int fog_on;
+	float sky_intensity;
+	float light_intensity;
+	float depth_mvp[MAT4_ELEMS];
+	// mat4 *depth_vp;
+	float model_view[MAT4_ELEMS];
+	float mvp[MAT4_ELEMS];
+	float normal_matrix[MAT4_ELEMS];
+	// float time;
+	// float ambient_str;
+	float sun_color[3];
+	float *camera_position;
+	float light_dir[3];
+};
+
 struct test_game {
 	struct terrain terrain;
 	struct vbo vertex_buffer, element_buffer, normal_buffer;
 	struct fbo shadow_buffer;
 
+	struct common_uniforms common_vars;
 	node_id root_id;
 	entity_id player_id;
 	struct geometry qh_test;
@@ -25,26 +52,22 @@ struct test_game {
 
 	u32 test_cubemap;
 	float time;
-	bool fog_on, diffuse_on;
 
 	struct skybox skybox;
-	float sun_color[3];
-	float piece_color[3];
-	float test_mvp[MAT4_ELEMS];
-	float light_dir[3];
-	float light_intensity;
+	struct ui ui;
 	float proj_persp[MAT4_ELEMS];
 	float proj_ortho[MAT4_ELEMS];
 };
 
 
 void init_test_gl(struct test_game *game, struct gpu *gpu, int width, int height);
-void render_test_game(struct game *engine, struct test_game *game, struct render_config *config);
-void init_test_game(struct test_game *game, struct gpu *gpu, int width, int height);
+void init_test_game(struct game *engine, struct test_game *game);
 void test_game_frame(struct game *engine, struct test_game *game);
 
 void default_scene(struct test_game *);
 void entity_test_scene(struct terrain *);
 void pbr_scene(struct test_game *);
-void chess_scene(struct test_game *);
+void chess_scene(struct game *, struct test_game *);
 
+struct lens *get_common_lenses();
+int get_common_lenses_length();
diff --git a/src/ui.c b/src/ui.c
@@ -5,8 +5,7 @@
 #include "geometry.h"
 #include "util.h"
 #include "common.h"
-
-#include <stdio.h>
+#include "data_id.h"
 
 
 //  v1------v0
@@ -53,29 +52,43 @@ static void create_quad(geometry_id *id)
     check_gl();
 }
 
-void render_ui(struct ui *ui, float *view) {
-    static float mvp[MAT4_ELEMS];
-	glDisable(GL_DEPTH_TEST);
-    glUseProgram(ui->shader->handle);
-    check_gl();
-
-    float uipos[2] = {0.01, 0.01};
-    float uisize[2] = {0.4, 0.4};
 
-    mat4_multiply(ui->ortho, view, mvp);
-
-    // setup camera for shader
-    /* glUniform2f(ui->uipos_uniform, uipos[0] * uisize[0], uipos[1] * uisize[1]); */
-    glUniform2f(ui->uniforms.uipos, uipos[0], uipos[1]);
-    glUniform2f(ui->uniforms.uisize, uisize[0], uisize[1]);
-    glUniform1i(ui->uniforms.texture, 0);
+#define UI_LENS(field, typ) { #field, offsetof(struct ui, field), DATA_ID_##typ }
+static struct lens ui_lenses[] = {
+	UI_LENS(uipos, VEC2),
+	UI_LENS(uisize, VEC2),
+	UI_LENS(mvp, MAT4P),
+	UI_LENS(texture, INT),
+};
+#undef UI_LENS
 
-    glUniformMatrix4fv(ui->uniforms.mvp, 1, 0, ui->ortho);
-    check_gl();
+static struct structure_binding ui_bindings[] = {
+    { ui_lenses, ARRAY_SIZE(ui_lenses) },
+};
 
-    // render quad
-    render_geometry(get_geometry(&ui->quad_geom_id), ui->attrs, ui->shader);
-    check_gl();
+void render_ui(struct ui *ui, float *view) {
+	glDisable(GL_DEPTH_TEST);
+	glUseProgram(ui->shader->handle);
+	check_gl();
+
+	ui->uipos[0] = 0.01;
+	ui->uipos[1] = 0.01;
+	ui->uisize[0] = 0.4;
+	ui->uisize[1] = 0.4;
+	ui->texture = 0;
+
+	void *structures[] = { ui };
+
+	assert(ARRAY_SIZE(structures) == ARRAY_SIZE(ui_bindings));
+	
+	ui->mvp = ui->ortho;
+	//mat4_multiply(ui->ortho, view, ui->mvp);
+	bind_uniforms(ui->shader, structures, ARRAY_SIZE(structures));
+	check_gl();
+	
+	// render quad
+	render_geometry(get_geometry(&ui->quad_geom_id), ui->shader);
+	check_gl();
 }
 
 
@@ -90,7 +103,6 @@ void resize_ui(struct ui *ui, int width, int height) {
     mat4_ortho(left, right, bottom, top, near, far, ui->ortho);
 }
 
-
 void create_ui(struct ui *ui, int width, int height, struct gpu_program *shader) {
     struct shader vertex;
     struct shader fragment;
@@ -108,23 +120,10 @@ void create_ui(struct ui *ui, int width, int height, struct gpu_program *shader)
     ok = make_shader(GL_FRAGMENT_SHADER, SHADER("ui.f.glsl"), &fragment);
     assert(ok && "ui fragment shader");
 
-    ok = make_program("ui", &vertex, &fragment, ui->shader);
+    ok = make_program("ui", &vertex, &fragment, ui->shader, ui_bindings,
+		    ARRAY_SIZE(ui_bindings));
     assert(ok && "ui program");
 
-    GLuint program = ui->shader->handle;
-
-    ui->uniforms.mvp            = glGetUniformLocation(program, "mvp");
-    ui->uniforms.uipos          = glGetUniformLocation(program, "uipos");
-    ui->uniforms.uisize         = glGetUniformLocation(program, "uisize");
-
-    ui->uniforms.texture =
-        glGetUniformLocation(program, "screen_texture");
-
-    /* ui->attrs.normal   = (gpu_addr)glGetAttribLocation(program, "normal"); */
-    ui->attrs[va_position]  = (gpu_addr)glGetAttribLocation(program, "position");
-    ui->attrs[va_color]     = (gpu_addr)glGetAttribLocation(program, "color");
-    ui->attrs[va_tex_coord] = (gpu_addr)glGetAttribLocation(program, "tex_coords");
-
     check_gl();
     resize_ui(ui, width, height);
     check_gl();
diff --git a/src/ui.h b/src/ui.h
@@ -7,18 +7,15 @@
 #include "geometry.h"
 
 struct ui {
-    struct gpu_program *shader;
-    geometry_id quad_geom_id;
-    gpu_addr attrs[MAX_VERTEX_ATTRS];
-
-    struct ui_uniforms {
-        GLint mvp;
-        GLint uipos;
-        GLint uisize;
-        GLint texture;
-    } uniforms;
-
-    float ortho[MAT4_ELEMS];
+	struct gpu_program *shader;
+	geometry_id quad_geom_id;
+
+	GLint texture;
+	float uipos[2];
+	float uisize[2];
+
+	float *mvp;
+	float ortho[MAT4_ELEMS];
 };
 
 void create_ui(struct ui *ui, int width, int height,
diff --git a/src/update.c b/src/update.c
@@ -16,25 +16,3 @@
 #include "debug.h"
 #include <math.h>
 
-
-static void remap_samples(struct point *points, int n_samples,
-                                    double size)
-{
-    double middle = size / 2.0;
-
-    for (int i = 0; i < n_samples; ++i) {
-        struct point *point = &points[i];
-
-        /* double x = point->x/size; */
-        /* double y = point->y/size; */
-        double dx = point->x - middle;
-        double dy = point->y - middle;
-        double dist = sqrt(dx*dx + dy*dy);
-        double nx = dx / dist;
-        double ny = dy / dist;
-        double factor = -log(dist)*50.0;
-        point->x += nx * factor;
-        point->y += ny * factor;
-    }
-}
-
diff --git a/src/vbo.h b/src/vbo.h
@@ -17,6 +17,18 @@ enum vertex_attr {
     MAX_VERTEX_ATTRS
 };
 
+static inline const char *vertex_attr_str(enum vertex_attr addr)
+{
+	switch(addr) {
+		case va_position: return "position";
+		case va_normal: return "normal";
+		case va_color: return "color";
+		case va_index: return "index";
+		case va_tex_coord: return "tex_coord";
+		default: return "unknown";
+	}
+}
+
 struct vbo {
     u32 type;
     int component_type;
diff --git a/test/test_data_id.c b/test/test_data_id.c
@@ -0,0 +1,63 @@
+
+#include "resource.h"
+#include "data_id.h"
+#include <stdio.h>
+
+struct test_struct {
+	struct {
+		char ca, cb;
+		int bleh;
+	} s;
+	float a;
+	float mat4[MAT4_ELEMS];
+	float b;
+};
+
+static void test_data_ids()
+{
+	printf("test_data_ids\n");
+	struct resource_manager r;
+	struct resource_id id;
+	struct test_struct *ts;
+	struct data_id a_id, b_id;
+	float a, b;
+	int ret;
+	// 2 item case
+	init_resource_manager(&r, sizeof(struct test_struct), 1, 2, "test_struct");
+	init_id(&id);
+
+	ts = new_resource(&r, &id);
+	assert(ts);
+
+	new_resource_data_id(&a_id, &r, id, DATA_ID_FLOAT, struct test_struct, a);
+	new_ptr_data_id(&b_id, &a, DATA_ID_FLOAT);
+	
+	ts->a = 1337.0;
+	ts->b = 123.0;
+
+	assert(get_data_id_float(&a_id, &a));
+
+	assert(a == 1337.0);
+	a = 123.0;
+
+	assert(get_data_id_float(&b_id, &b));
+	assert(b == 123.0);
+
+	destroy_resource(&r, &id);
+	ret = get_data_id_float(&a_id, &a);
+	assert(ret == 0);
+	assert(is_resource_destroyed(&r, &a_id.res_id.id));
+
+	b = 0.0;
+	assert(get_data_id_float(&b_id, &b));
+	assert(b == 123.0);
+	
+	destroy_resource_manager(&r);
+}
+
+int main(int argc, char *argv[])
+{
+    test_data_ids();
+
+    return 0;
+}
diff --git a/tools/compile-model.c b/tools/compile-model.c
@@ -60,7 +60,7 @@ int main(int argc, char *argv[])
     int ok = parse_ply_with_mkgeom(filename, &mdl_geom);
     assert(ok);
 
-    save_mdl(outfile, &mdl_geom);
+    save_mdl(outfile, &model, &mdl_geom);
 
     return 0;
 }