mirror of https://github.com/xemu-project/xemu.git
nv2a: Replace texture cache with a simpler implementation
This commit is contained in:
parent
e4a1e7ace6
commit
02f3b701d0
|
@ -1,4 +1,4 @@
|
|||
obj-y += g-lru-cache.o
|
||||
obj-y += lru.o
|
||||
obj-y += swizzle.o
|
||||
|
||||
obj-y += nv2a.o
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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__ */
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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];
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue