nv2a: Replace texture cache with a simpler implementation

This commit is contained in:
Matt Borgerson 2018-12-11 22:05:40 -07:00 committed by mborgerson
parent e4a1e7ace6
commit 02f3b701d0
7 changed files with 365 additions and 536 deletions

View File

@ -1,4 +1,4 @@
obj-y += g-lru-cache.o
obj-y += lru.o
obj-y += swizzle.o
obj-y += nv2a.o

View File

@ -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;
}

View File

@ -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__ */

201
hw/xbox/nv2a/lru.c Normal file
View File

@ -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;
}

101
hw/xbox/nv2a/lru.h Normal file
View File

@ -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

View File

@ -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];

View File

@ -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)
{