diff --git a/hw/xbox/nv2a/Makefile.objs b/hw/xbox/nv2a/Makefile.objs
index 1a42f60b47..f49bddfab5 100644
--- a/hw/xbox/nv2a/Makefile.objs
+++ b/hw/xbox/nv2a/Makefile.objs
@@ -1,4 +1,4 @@
-obj-y += g-lru-cache.o
+obj-y += lru.o
 obj-y += swizzle.o
 
 obj-y += nv2a.o
diff --git a/hw/xbox/nv2a/g-lru-cache.c b/hw/xbox/nv2a/g-lru-cache.c
deleted file mode 100644
index b5075f3b4f..0000000000
--- a/hw/xbox/nv2a/g-lru-cache.c
+++ /dev/null
@@ -1,372 +0,0 @@
-/* g-lru-cache.c
- *
- * Copyright (C) 2009 - Christian Hergert
- *
- * This is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- * 
- * This is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- * 
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
- */
-
-/* 
- * Ideally, you want to use fast_get. This is because we are using a
- * GStaticRWLock which is indeed slower than a mutex if you have lots of writer
- * acquisitions. This doesn't make it a true LRU, though, as the oldest
- * retrieval from strorage is the first item evicted.
- */
-
-#include "g-lru-cache.h"
-
-#ifndef DEBUG
-#define DEBUG 0
-#endif
-
-#define LRU_CACHE_PRIVATE(object)          \
-    (G_TYPE_INSTANCE_GET_PRIVATE((object), \
-    G_TYPE_LRU_CACHE,                      \
-    GLruCachePrivate))
-
-struct _GLruCachePrivate
-{
-	GRWLock         rw_lock;
-	guint           max_size;
-	gboolean        fast_get;
-	
-	GHashTable     *hash_table;
-	GEqualFunc      key_equal_func;
-	GCopyFunc       key_copy_func;
-	GList          *newest;
-	GList          *oldest;
-	
-	GLookupFunc     retrieve_func;
-	
-	gpointer        user_data;
-	GDestroyNotify  user_destroy_func;
-};
-
-G_DEFINE_TYPE (GLruCache, g_lru_cache, G_TYPE_OBJECT);
-
-static void
-g_lru_cache_finalize (GObject *object)
-{
-	GLruCachePrivate *priv = LRU_CACHE_PRIVATE (object);
-	
-	if (priv->user_data && priv->user_destroy_func)
-		priv->user_destroy_func (priv->user_data);
-	
-	priv->user_data = NULL;
-	priv->user_destroy_func = NULL;
-	
-	g_hash_table_destroy (priv->hash_table);
-	priv->hash_table = NULL;
-	
-	g_list_free (priv->newest);
-	priv->newest = NULL;
-	priv->oldest = NULL;
-	
-	G_OBJECT_CLASS (g_lru_cache_parent_class)->finalize (object);
-}
-
-static void
-g_lru_cache_class_init (GLruCacheClass *klass)
-{
-	GObjectClass *object_class = G_OBJECT_CLASS (klass);
-	
-	object_class->finalize = g_lru_cache_finalize;
-
-	g_type_class_add_private (object_class, sizeof (GLruCachePrivate));
-}
-
-static void
-g_lru_cache_init (GLruCache *self)
-{
-	self->priv = LRU_CACHE_PRIVATE (self);
-	
-	self->priv->max_size = 1024;
-	self->priv->fast_get = FALSE;
-	g_rw_lock_init (&self->priv->rw_lock);
-}
-
-static void
-g_lru_cache_evict_n_oldest_locked (GLruCache *self, gint n)
-{
-	GList *victim;
-	gint   i;
-	
-	for (i = 0; i < n; i++)
-	{
-		victim = self->priv->oldest;
-		
-		if (victim == NULL)
-			return;
-		
-		if (victim->prev)
-			victim->prev->next = NULL;
-		
-		self->priv->oldest = victim->prev;
-		g_hash_table_remove (self->priv->hash_table, victim->data);
-		
-		if (self->priv->newest == victim)
-			self->priv->newest = NULL;
-		
-		g_list_free1 (victim); /* victim->data is owned by hashtable */
-	}
-	
-#if DEBUG
-	g_assert (g_hash_table_size (self->priv->hash_table) == g_list_length (self->priv->newest));
-#endif
-}
-
-GLruCache*
-g_lru_cache_new (GHashFunc      key_hash_func,
-                 GEqualFunc     key_equal_func,
-                 GLookupFunc    retrieve_func,
-                 gpointer       user_data,
-                 GDestroyNotify user_destroy_func)
-{
-	return g_lru_cache_new_full (0,
-	                             NULL,
-	                             NULL,
-	                             0,
-	                             NULL,
-	                             NULL,
-	                             key_hash_func,
-	                             key_equal_func,
-	                             retrieve_func,
-	                             user_data,
-	                             user_destroy_func);
-}
-
-GLruCache*
-g_lru_cache_new_full (GType          key_type,
-                      GCopyFunc      key_copy_func,
-                      GDestroyNotify key_destroy_func,
-                      GType          value_type,
-                      GCopyFunc      value_copy_func,
-                      GDestroyNotify value_destroy_func,
-                      GHashFunc      key_hash_func,
-                      GEqualFunc     key_equal_func,
-                      GLookupFunc    retrieve_func,
-                      gpointer       user_data,
-                      GDestroyNotify user_destroy_func)
-{
-	GLruCache *self = g_object_new (G_TYPE_LRU_CACHE, NULL);
-	
-	self->priv->hash_table = g_hash_table_new_full (key_hash_func,
-	                                                key_equal_func,
-	                                                key_destroy_func,
-	                                                value_destroy_func);
-	
-	self->priv->key_equal_func = key_equal_func;
-	self->priv->key_copy_func = key_copy_func;
-	self->priv->retrieve_func = retrieve_func;
-	self->priv->user_data = user_data;
-	self->priv->user_destroy_func = user_destroy_func;
-	
-	return self;
-}
-
-void
-g_lru_cache_set_max_size (GLruCache *self, guint max_size)
-{
-	g_return_if_fail (G_IS_LRU_CACHE (self));
-	
-	guint old_max_size = self->priv->max_size;
-	
-	g_rw_lock_writer_lock (&(self->priv->rw_lock));
-	
-	self->priv->max_size = max_size;
-	
-	if (old_max_size > max_size)
-		g_lru_cache_evict_n_oldest_locked (self, old_max_size - max_size);
-	
-	g_rw_lock_writer_unlock (&(self->priv->rw_lock));
-}
-
-guint
-g_lru_cache_get_max_size (GLruCache *self)
-{
-	g_return_val_if_fail (G_IS_LRU_CACHE (self), -1);
-	return self->priv->max_size;
-}
-
-guint
-g_lru_cache_get_size (GLruCache *self)
-{
-	g_return_val_if_fail (G_IS_LRU_CACHE (self), -1);
-	return g_hash_table_size (self->priv->hash_table);
-}
-
-gpointer
-g_lru_cache_get (GLruCache *self, gpointer key, GError **error)
-{
-	g_return_val_if_fail (G_IS_LRU_CACHE (self), NULL);
-	
-	gpointer value;
-	GError *retrieve_error = NULL;
-	
-	g_rw_lock_reader_lock (&(self->priv->rw_lock));
-	
-	value = g_hash_table_lookup (self->priv->hash_table, key);
-	
-#if DEBUG
-	if (value)
-		g_debug ("Cache Hit!");
-	else
-		g_debug ("Cache miss");
-#endif
-	
-	g_rw_lock_reader_unlock (&(self->priv->rw_lock));
-	
-	if (!value)
-	{
-		g_rw_lock_writer_lock (&(self->priv->rw_lock));
-		
-		if (!g_hash_table_lookup (self->priv->hash_table, key))
-		{
-			if (g_hash_table_size (self->priv->hash_table) >= self->priv->max_size)
-#if DEBUG
-			{
-				g_debug ("We are at capacity, must evict oldest");
-#endif
-				g_lru_cache_evict_n_oldest_locked (self, 1);
-#if DEBUG
-			}
-			
-			g_debug ("Retrieving value from external resource");
-#endif
-
-			value = self->priv->retrieve_func (key,
-			                                   self->priv->user_data,
-			                                   &retrieve_error);
-
-			if (G_UNLIKELY (retrieve_error != NULL))
-			{
-				g_propagate_error (error, retrieve_error);
-				return value; /* likely 'NULL', but we should be transparent */
-			}
-			
-			if (self->priv->key_copy_func)
-				g_hash_table_insert (self->priv->hash_table,
-					self->priv->key_copy_func (key, self->priv->user_data),
-					value);
-			else
-				g_hash_table_insert (self->priv->hash_table, key, value);
-			
-			self->priv->newest = g_list_prepend (self->priv->newest, key);
-			
-			if (self->priv->oldest == NULL)
-				self->priv->oldest = self->priv->newest;
-		}
-#if DEBUG
-		else g_debug ("Lost storage race with another thread");
-#endif
-		
-		g_rw_lock_writer_unlock (&(self->priv->rw_lock));
-	}
-
-	/* fast_get means that we do not reposition the item to the head
-	 * of the list. it essentially makes the lru, a lru from storage,
-	 * not lru to user.
-	 */
-
-	else if (!self->priv->fast_get &&
-	         !self->priv->key_equal_func (key, self->priv->newest->data))
-	{
-#if DEBUG
-		g_debug ("Making item most recent");
-#endif
-
-		g_rw_lock_writer_lock (&(self->priv->rw_lock));
-
-		GList *list = self->priv->newest;
-		GList *tmp;
-		GEqualFunc equal = self->priv->key_equal_func;
-
-		for (tmp = list; tmp; tmp = tmp->next)
-		{
-			if (equal (key, tmp->data))
-			{
-				GList *tmp1 = g_list_remove_link (list, tmp);
-				self->priv->newest = g_list_prepend (tmp1, tmp);
-				break;
-			}
-		}
-
-		g_rw_lock_writer_unlock (&(self->priv->rw_lock));
-	}
-	
-	return value;
-}
-
-void
-g_lru_cache_evict (GLruCache *self, gpointer key)
-{
-	g_return_if_fail (G_IS_LRU_CACHE (self));
-	
-	GEqualFunc  equal = self->priv->key_equal_func;
-	GList      *list  = NULL;
-	
-	g_rw_lock_writer_lock (&(self->priv->rw_lock));
-	
-	if (equal (key, self->priv->oldest))
-	{
-		g_lru_cache_evict_n_oldest_locked (self, 1);
-	}
-	else
-	{
-		g_hash_table_remove (self->priv->hash_table, key);
-		
-		for (list = self->priv->newest; list; list = list->next)
-		{
-			if (equal (key, list->data))
-			{
-				self->priv->newest = g_list_remove_link (self->priv->newest, list);
-				g_list_free (list);
-				break;
-			}
-		}
-	}
-	
-	g_rw_lock_writer_unlock (&(self->priv->rw_lock));
-}
-
-void
-g_lru_cache_clear (GLruCache *self)
-{
-	g_return_if_fail (G_IS_LRU_CACHE (self));
-	
-	g_rw_lock_writer_lock (&(self->priv->rw_lock));
-	
-	g_hash_table_remove_all (self->priv->hash_table);
-	g_list_free (self->priv->newest);
-	
-	self->priv->oldest = NULL;
-	self->priv->newest = NULL;
-	
-	g_rw_lock_writer_unlock (&(self->priv->rw_lock));
-}
-
-void
-g_lru_cache_set_fast_get (GLruCache *self, gboolean fast_get)
-{
-	g_return_if_fail (G_IS_LRU_CACHE (self));
-	self->priv->fast_get = fast_get;
-}
-
-gboolean
-g_lru_cache_get_fast_get (GLruCache *self)
-{
-	g_return_val_if_fail (G_IS_LRU_CACHE (self), FALSE);
-	return self->priv->fast_get;
-}
-
diff --git a/hw/xbox/nv2a/g-lru-cache.h b/hw/xbox/nv2a/g-lru-cache.h
deleted file mode 100644
index 096e631846..0000000000
--- a/hw/xbox/nv2a/g-lru-cache.h
+++ /dev/null
@@ -1,97 +0,0 @@
-/* g-lru-cache.h
- *
- * Copyright (C) 2009 - Christian Hergert
- *
- * This is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- * 
- * This is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- * 
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
- */
-
-#ifndef __G_LRU_CACHE_H__
-#define __G_LRU_CACHE_H__
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#include <glib.h>
-#include <glib-object.h>
-
-G_BEGIN_DECLS
-
-#define G_TYPE_LRU_CACHE		(g_lru_cache_get_type ())
-#define G_LRU_CACHE(obj)		(G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_LRU_CACHE, GLruCache))
-#define G_LRU_CACHE_CONST(obj)		(G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_LRU_CACHE, GLruCache const))
-#define G_LRU_CACHE_CLASS(klass)	(G_TYPE_CHECK_CLASS_CAST ((klass), G_TYPE_LRU_CACHE, GLruCacheClass))
-#define G_IS_LRU_CACHE(obj)		(G_TYPE_CHECK_INSTANCE_TYPE ((obj), G_TYPE_LRU_CACHE))
-#define G_IS_LRU_CACHE_CLASS(klass)	(G_TYPE_CHECK_CLASS_TYPE ((klass), G_TYPE_LRU_CACHE))
-#define G_LRU_CACHE_GET_CLASS(obj)	(G_TYPE_INSTANCE_GET_CLASS ((obj), G_TYPE_LRU_CACHE, GLruCacheClass))
-#define G_LOOKUP_FUNC(func)             ((GLookupFunc)func)
-
-typedef struct _GLruCache		GLruCache;
-typedef struct _GLruCacheClass		GLruCacheClass;
-typedef struct _GLruCachePrivate	GLruCachePrivate;
-
-typedef gpointer (*GLookupFunc) (gpointer key, gpointer user_data, GError **error);
-
-struct _GLruCache
-{
-	GObject parent;
-	
-	GLruCachePrivate *priv;
-};
-
-struct _GLruCacheClass
-{
-	GObjectClass parent_class;
-};
-
-GType      g_lru_cache_get_type     (void) G_GNUC_CONST;
-
-GLruCache* g_lru_cache_new          (GHashFunc      key_hash_func,
-                                     GEqualFunc     key_equal_func,
-                                     GLookupFunc    retrieve_func,
-                                     gpointer       user_data,
-                                     GDestroyNotify user_destroy_func);
-
-GLruCache* g_lru_cache_new_full     (GType          key_type,
-                                     GCopyFunc      key_copy_func,
-                                     GDestroyNotify key_destroy_func,
-                                     GType          value_type,
-                                     GCopyFunc      value_copy_func,
-                                     GDestroyNotify value_destroy_func,
-                                     GHashFunc      key_hash_func,
-                                     GEqualFunc     key_equal_func,
-                                     GLookupFunc    retrieve_func,
-                                     gpointer       user_data,
-                                     GDestroyNotify user_destroy_func);
-
-void       g_lru_cache_set_max_size (GLruCache *self, guint max_size);
-guint      g_lru_cache_get_max_size (GLruCache *self);
-
-guint      g_lru_cache_get_size     (GLruCache *self);
-
-gpointer   g_lru_cache_get          (GLruCache *self, gpointer key, GError **error);
-void       g_lru_cache_evict        (GLruCache *self, gpointer key);
-void       g_lru_cache_clear        (GLruCache *self);
-
-gboolean   g_lru_cache_get_fast_get (GLruCache *self);
-void       g_lru_cache_set_fast_get (GLruCache *self, gboolean fast_get);
-
-G_END_DECLS
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* __G_LRU_CACHE_H__ */
diff --git a/hw/xbox/nv2a/lru.c b/hw/xbox/nv2a/lru.c
new file mode 100644
index 0000000000..f3adabd9a4
--- /dev/null
+++ b/hw/xbox/nv2a/lru.c
@@ -0,0 +1,201 @@
+/*
+ * Copyright (c) 2018 Matt Borgerson
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+
+#include "lru.h"
+
+#define LRU_DEBUG 0
+
+#if LRU_DEBUG
+#define lru_dprintf(...) do { printf(__VA_ARGS__); } while(0)
+#else
+#define lru_dprintf(...) do {} while(0)
+#endif
+
+/*
+ * Create the LRU cache
+ */
+struct lru *lru_init(
+    struct lru               *lru,
+    lru_obj_init_func         obj_init,
+    lru_obj_deinit_func       obj_deinit,
+    lru_obj_key_compare_func  obj_key_compare
+    )
+{
+    assert(lru != NULL);
+
+    lru->active = NULL;
+    lru->free = NULL;
+
+    lru->obj_init = obj_init;
+    lru->obj_deinit = obj_deinit;
+    lru->obj_key_compare = obj_key_compare;
+
+    lru->num_free = 0;
+    lru->num_collisions = 0;
+    lru->num_hit = 0;
+    lru->num_miss = 0;
+
+    return lru;
+}
+
+/*
+ * Add a node to the free list
+ */
+struct lru_node *lru_add_free(struct lru *lru, struct lru_node *node)
+{
+    node->next = lru->free;
+    lru->free = node;
+    lru->num_free++;
+    return node;
+}
+
+/*
+ * Lookup object in cache:
+ * - If found, object is promoted to front of RU list and returned
+ * - If not found,
+ *   - If cache is full, evict LRU, deinit object and add it to free list
+ *   - Allocate object from free list, init, move to front of RU list
+ */
+struct lru_node *lru_lookup(struct lru *lru, uint64_t hash, void *key)
+{
+    struct lru_node *prev, *node;
+
+    assert(lru != NULL);
+    assert((lru->active != NULL) || (lru->free != NULL));
+
+    /* Walk through the cache in order of recent use */
+    prev = NULL;
+    node = lru->active;
+
+    lru_dprintf("Looking for hash %016lx...\n", hash);
+
+    if (node != NULL) {
+        do {
+            lru_dprintf("  %016lx\n", node->hash);
+
+            /* Fast hash compare */
+            if (node->hash == hash) {
+                /* Detailed key comparison */
+                if (lru->obj_key_compare(node, key) == 0) {
+                    lru_dprintf("Hit, node=%p!\n", node);
+                    lru->num_hit++;
+
+                    if (prev == NULL) {
+                        /* Node is already at the front of the RU list */
+                        return node;
+                    }
+
+                    /* Unlink and promote node */
+                    lru_dprintf("Promoting node %p\n", node);
+                    prev->next = node->next;
+                    node->next = lru->active;
+                    lru->active = node;
+                    return node;
+                }
+
+                /* Hash collision! Get a better hashing function... */
+                lru_dprintf("Hash collision detected!\n");
+                lru->num_collisions++;
+            }
+
+            if (node->next == NULL) {
+                /* No more nodes left to look at after this... Stop here as we
+                 * may need to evict this final (last recently used) node.
+                 */
+                break;
+            }
+
+            prev = node;
+            node = node->next;
+        } while (1);
+    }
+
+    lru_dprintf("Miss\n");
+    lru->num_miss++;
+
+    /* Reached the end of the active list.
+     *
+     * `node` points to:
+     * - NULL if there are no active objects in the cache, or
+     * - the last object in the RU list
+     *
+     * `prev` points to:
+     * - NULL if there are <= 1 active objects in the cache, or
+     * - the second to last object in the RU list
+     */
+
+    if (lru->free == NULL) {
+        /* No free nodes left, must evict a node. `node` is LRU. */
+        assert(node != NULL); /* Sanity check: there must be an active object */
+        lru_dprintf("Evicting %p\n", node);
+
+        if (prev == NULL) {
+            /* This was the only node */
+            lru->active = NULL;
+        } else {
+            /* Unlink node */
+            prev->next = node->next;
+        }
+
+        lru->obj_deinit(node);
+        lru_add_free(lru, node);
+    }
+
+    /* Allocate a node from the free list */
+    node = lru->free;
+    assert(node != NULL); /* Sanity check: there must be a free node */
+    lru->free = node->next;
+    lru->num_free--;
+
+    /* Initialize, promote, and return the node */
+    lru->obj_init(node, key);
+    node->hash = hash;
+    node->next = lru->active;
+    lru->active = node;
+    return node;
+}
+
+/*
+ * Remove all items in the active list
+ */
+void lru_flush(struct lru *lru)
+{
+    struct lru_node *node, *next;
+
+    node = lru->active;
+    next = NULL;
+
+    while (node != NULL) {
+        next = node->next;
+        lru->obj_deinit(node);
+        lru_add_free(lru, node);
+        node = next;
+    }
+
+    lru->active = NULL;
+}
diff --git a/hw/xbox/nv2a/lru.h b/hw/xbox/nv2a/lru.h
new file mode 100644
index 0000000000..9725cdf3bb
--- /dev/null
+++ b/hw/xbox/nv2a/lru.h
@@ -0,0 +1,101 @@
+/*
+ * Simple LRU Object List
+ * ======================
+ * - Designed for pre-allocated array of objects which are accessed frequently
+ * - Objects are identified by a hash and an opaque `key` data structure
+ * - Lookups are first done by hash, then confirmed by callback compare function
+ * - Two singly linked lists are maintained: a free list and an active list
+ * - On cache miss, object is created from free list or by evicting the LRU
+ * - When created, a callback function is called to fully initialize the object
+ *
+ * Setup
+ * -----
+ * - Create an object data structure, embed in it `struct lru_node`
+ * - Create an init, deinit, and compare function
+ * - Call `lru_init`
+ * - Allocate a number of these objects
+ * - For each object, call `lru_add_free` to populate entries in the cache
+ *
+ * Runtime
+ * -------
+ * - Initialize custom key data structure (will be used for comparison)
+ * - Create 64b hash of the object and/or key
+ * - Call `lru_lookup` with the hash and key
+ *   - The active list is searched, the compare callback will be called if an
+ *     object with matching hash is found
+ *   - If object is found in the cache, it will be moved to the front of the
+ *     active list and returned
+ *   - If object is not found in the cache:
+ *     - If no free items are available, the LRU will be evicted, deinit
+ *       callback will be called
+ *     - An object is popped from the free list and the init callback is called
+ *       on the object
+ *     - The object is added to the front of the active list and returned
+ *
+ * ---
+ *
+ * Copyright (c) 2018 Matt Borgerson
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ */
+#ifndef LRU_H
+#define LRU_H
+
+#include <stdint.h>
+#include <string.h>
+
+struct lru_node;
+
+typedef struct lru_node *(*lru_obj_init_func)(struct lru_node *obj, void *key);
+typedef struct lru_node *(*lru_obj_deinit_func)(struct lru_node *obj);
+typedef int              (*lru_obj_key_compare_func)(struct lru_node *obj, void *key);
+
+struct lru {
+	struct lru_node *active; /* Singly-linked list tracking recently active */
+	struct lru_node *free;   /* Singly-linked list tracking available objects */
+
+	lru_obj_init_func         obj_init;
+	lru_obj_deinit_func       obj_deinit;
+	lru_obj_key_compare_func  obj_key_compare;
+
+	size_t num_free;
+	size_t num_collisions;
+	size_t num_hit;
+	size_t num_miss;
+};
+
+/* This should be embedded in the object structure */
+struct lru_node {
+	uint64_t hash;
+	struct lru_node *next;
+};
+
+struct lru *lru_init(
+	struct lru *lru,
+	lru_obj_init_func obj_init,
+	lru_obj_deinit_func obj_deinit,
+	lru_obj_key_compare_func obj_key_compare
+	);
+
+struct lru_node *lru_add_free(struct lru *lru, struct lru_node *node);
+struct lru_node *lru_lookup(struct lru *lru, uint64_t hash, void *key);
+void lru_flush(struct lru *lru);
+
+#endif
diff --git a/hw/xbox/nv2a/nv2a_int.h b/hw/xbox/nv2a/nv2a_int.h
index 702a2808c5..6a07377a5e 100644
--- a/hw/xbox/nv2a/nv2a_int.h
+++ b/hw/xbox/nv2a/nv2a_int.h
@@ -30,7 +30,7 @@
 // #include "qemu/thread.h"
 // #include "cpu.h"
 
-#include "g-lru-cache.h"
+#include "lru.h"
 #include "gl/gloffscreen.h"
 
 #include "hw/xbox/nv2a/nv2a_debug.h"
@@ -132,19 +132,20 @@ typedef struct TextureShape {
     unsigned int pitch;
 } TextureShape;
 
-typedef struct TextureKey {
-    TextureShape state;
-    uint64_t data_hash;
-    uint8_t *texture_data;
-    uint8_t *palette_data;
-} TextureKey;
-
 typedef struct TextureBinding {
     GLenum gl_target;
     GLuint gl_texture;
     unsigned int refcnt;
 } TextureBinding;
 
+typedef struct TextureKey {
+    struct lru_node node;
+    TextureShape state;
+    uint8_t *texture_data;
+    uint8_t *palette_data;
+    TextureBinding *binding;
+} TextureKey;
+
 typedef struct KelvinState {
     hwaddr object_instance;
 } KelvinState;
@@ -189,7 +190,8 @@ typedef struct PGRAPHState {
     SurfaceShape last_surface_shape;
 
     hwaddr dma_a, dma_b;
-    GLruCache *texture_cache;
+    struct lru texture_cache;
+    struct TextureKey *texture_cache_entries;
     bool texture_dirty[NV2A_MAX_TEXTURES];
     TextureBinding *texture_binding[NV2A_MAX_TEXTURES];
 
diff --git a/hw/xbox/nv2a/nv2a_pgraph.c b/hw/xbox/nv2a/nv2a_pgraph.c
index c2d604f687..88e799aeab 100644
--- a/hw/xbox/nv2a/nv2a_pgraph.c
+++ b/hw/xbox/nv2a/nv2a_pgraph.c
@@ -286,11 +286,10 @@ static void convert_yuy2_to_rgb(const uint8_t *line, unsigned int ix, uint8_t *r
 static uint8_t* convert_texture_data(const TextureShape s, const uint8_t *data, const uint8_t *palette_data, unsigned int width, unsigned int height, unsigned int depth, unsigned int row_pitch, unsigned int slice_pitch);
 static void upload_gl_texture(GLenum gl_target, const TextureShape s, const uint8_t *texture_data, const uint8_t *palette_data);
 static TextureBinding* generate_texture(const TextureShape s, const uint8_t *texture_data, const uint8_t *palette_data);
-static guint texture_key_hash(gconstpointer key);
-static gboolean texture_key_equal(gconstpointer a, gconstpointer b);
-static gpointer texture_key_retrieve(gpointer key, gpointer user_data, GError **error);
-static void texture_key_destroy(gpointer data);
 static void texture_binding_destroy(gpointer data);
+static struct lru_node *texture_cache_entry_init(struct lru_node *obj, void *key);
+static struct lru_node *texture_cache_entry_deinit(struct lru_node *obj);
+static int texture_cache_entry_compare(struct lru_node *obj, void *key);
 static guint shader_hash(gconstpointer key);
 static gboolean shader_equal(gconstpointer a, gconstpointer b);
 static unsigned int kelvin_map_stencil_op(uint32_t parameter);
@@ -2681,21 +2680,17 @@ static void pgraph_init(NV2AState *d)
 
     //glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
 
-    pg->texture_cache = g_lru_cache_new_full(
-        0,
-        NULL,
-        texture_key_destroy,
-        0,
-        NULL,
-        texture_binding_destroy,
-        texture_key_hash,
-        texture_key_equal,
-        texture_key_retrieve,
-        NULL,
-        NULL
-        );
-
-    g_lru_cache_set_max_size(pg->texture_cache, 512);
+    // Initialize texture cache
+    const size_t texture_cache_size = 512;
+    lru_init(&pg->texture_cache,
+        &texture_cache_entry_init,
+        &texture_cache_entry_deinit,
+        &texture_cache_entry_compare);
+    pg->texture_cache_entries = malloc(texture_cache_size * sizeof(struct TextureKey));
+    assert(pg->texture_cache_entries != NULL);
+    for (i = 0; i < texture_cache_size; i++) {
+        lru_add_free(&pg->texture_cache, &pg->texture_cache_entries[i].node);
+    }
 
     pg->shader_cache = g_hash_table_new(shader_hash, shader_equal);
 
@@ -2740,7 +2735,10 @@ static void pgraph_destroy(PGRAPHState *pg)
     glDeleteFramebuffers(1, &pg->gl_framebuffer);
 
     // TODO: clear out shader cached
-    // TODO: clear out texture cache
+
+    // Clear out texture cache
+    lru_flush(&pg->texture_cache);
+    free(pg->texture_cache_entries);
 
     glo_set_current(NULL);
 
@@ -3753,20 +3751,19 @@ static void pgraph_bind_textures(NV2AState *d)
         };
 
 #ifdef USE_TEXTURE_CACHE
+        uint64_t texture_hash = fast_hash(texture_data, length, 5003)
+                              ^ fnv_hash(palette_data, palette_length);
+
         TextureKey key = {
             .state = state,
-            .data_hash = fast_hash(texture_data, length, 5003)
-                            ^ fnv_hash(palette_data, palette_length),
             .texture_data = texture_data,
             .palette_data = palette_data,
         };
 
-        gpointer cache_key = g_malloc(sizeof(TextureKey));
-        memcpy(cache_key, &key, sizeof(TextureKey));
-
-        GError *err;
-        TextureBinding *binding = (TextureBinding *)g_lru_cache_get(pg->texture_cache, cache_key, &err);
-        assert(binding);
+        struct lru_node *found = lru_lookup(&pg->texture_cache, texture_hash, &key);
+        TextureKey *key_out = container_of(found, struct TextureKey, node);
+        assert((key_out != NULL) && (key_out->binding != NULL));
+        TextureBinding *binding = key_out->binding;
         binding->refcnt++;
 #else
         TextureBinding *binding = generate_texture(state,
@@ -4371,35 +4368,6 @@ static TextureBinding* generate_texture(const TextureShape s,
     return ret;
 }
 
-/* functions for texture LRU cache */
-static guint texture_key_hash(gconstpointer key)
-{
-    const TextureKey *k = (const TextureKey *)key;
-    uint64_t state_hash = fnv_hash(
-        (const uint8_t*)&k->state, sizeof(TextureShape));
-    return state_hash ^ k->data_hash;
-}
-static gboolean texture_key_equal(gconstpointer a, gconstpointer b)
-{
-    const TextureKey *ak = (const TextureKey *)a, *bk = (const TextureKey *)b;
-    return memcmp(&ak->state, &bk->state, sizeof(TextureShape)) == 0
-            && ak->data_hash == bk->data_hash;
-}
-static gpointer texture_key_retrieve(gpointer key, gpointer user_data, GError **error)
-{
-    const TextureKey *k = (const TextureKey *)key;
-    TextureBinding *v = generate_texture(k->state,
-                                         k->texture_data,
-                                         k->palette_data);
-    if (error != NULL) {
-        *error = NULL;
-    }
-    return v;
-}
-static void texture_key_destroy(gpointer data)
-{
-    g_free(data);
-}
 static void texture_binding_destroy(gpointer data)
 {
     TextureBinding *binding = (TextureBinding *)data;
@@ -4411,6 +4379,32 @@ static void texture_binding_destroy(gpointer data)
     }
 }
 
+/* functions for texture LRU cache */
+static struct lru_node *texture_cache_entry_init(struct lru_node *obj, void *key)
+{
+    struct TextureKey *k_out = container_of(obj, struct TextureKey, node);
+    struct TextureKey *k_in = (struct TextureKey *)key;
+    memcpy(k_out, k_in, sizeof(struct TextureKey));
+    k_out->binding = generate_texture(k_in->state,
+                                      k_in->texture_data,
+                                      k_in->palette_data);
+    return obj;
+}
+
+static struct lru_node *texture_cache_entry_deinit(struct lru_node *obj)
+{
+    struct TextureKey *a = container_of(obj, struct TextureKey, node);
+    texture_binding_destroy(a->binding);
+    return obj;
+}
+
+static int texture_cache_entry_compare(struct lru_node *obj, void *key)
+{
+    struct TextureKey *a = container_of(obj, struct TextureKey, node);
+    struct TextureKey *b = (struct TextureKey *)key;
+    return memcmp(&a->state, &b->state, sizeof(a->state));
+}
+
 /* hash and equality for shader cache hash table */
 static guint shader_hash(gconstpointer key)
 {