diff --git a/hw/xbox/nv2a/pgraph/gl/surface.c b/hw/xbox/nv2a/pgraph/gl/surface.c
index e99c43a45c..31403260ad 100644
--- a/hw/xbox/nv2a/pgraph/gl/surface.c
+++ b/hw/xbox/nv2a/pgraph/gl/surface.c
@@ -1349,6 +1349,9 @@ static void surface_download(NV2AState *d, SurfaceBinding *surface, bool force)
memory_region_set_client_dirty(d->vram, surface->vram_addr,
surface->pitch * surface->height,
DIRTY_MEMORY_NV2A_TEX);
+ memory_region_set_client_dirty(d->vram, surface->vram_addr,
+ surface->pitch * surface->height,
+ DIRTY_MEMORY_NV2A);
surface->download_pending = false;
surface->draw_dirty = false;
diff --git a/hw/xbox/nv2a/pgraph/vk/draw.c b/hw/xbox/nv2a/pgraph/vk/draw.c
index 6dc4a16ebb..de3d5cec8b 100644
--- a/hw/xbox/nv2a/pgraph/vk/draw.c
+++ b/hw/xbox/nv2a/pgraph/vk/draw.c
@@ -150,8 +150,14 @@ static void init_pipeline_cache(PGRAPHState *pg)
}
#endif
- VK_CHECK(vkCreatePipelineCache(r->device, &cache_info, NULL,
- &r->vk_pipeline_cache));
+ VkResult cache_result = vkCreatePipelineCache(r->device, &cache_info, NULL,
+ &r->vk_pipeline_cache);
+ if (cache_result != VK_SUCCESS) {
+ cache_info.initialDataSize = 0;
+ cache_info.pInitialData = NULL;
+ VK_CHECK(vkCreatePipelineCache(r->device, &cache_info, NULL,
+ &r->vk_pipeline_cache));
+ }
#ifdef __ANDROID__
g_free(cache_data);
diff --git a/hw/xbox/nv2a/pgraph/vk/glsl.c b/hw/xbox/nv2a/pgraph/vk/glsl.c
index d7aef1b590..2616fc75a2 100644
--- a/hw/xbox/nv2a/pgraph/vk/glsl.c
+++ b/hw/xbox/nv2a/pgraph/vk/glsl.c
@@ -17,6 +17,8 @@
* License along with this library; if not, see .
*/
+#include "qemu/osdep.h"
+#include "qemu/fast-hash.h"
#include "ui/xemu-settings.h"
#include "renderer.h"
@@ -24,6 +26,168 @@
#include
#include
+#define SPIRV_CACHE_VERSION 1
+#define SPIRV_CACHE_MAGIC 0x53505643 /* 'SPVC' */
+
+typedef struct SpirvCacheEntry {
+ uint64_t hash;
+ uint32_t spirv_len;
+ uint8_t *spirv_data;
+} SpirvCacheEntry;
+
+static GHashTable *spirv_cache;
+static bool spirv_cache_dirty;
+static bool spirv_atexit_registered;
+
+static char *get_spirv_cache_path(void)
+{
+ const char *base = xemu_settings_get_base_path();
+ if (!base) {
+ return NULL;
+ }
+ return g_build_filename(base, "spirv_cache.bin", NULL);
+}
+
+static void spirv_cache_entry_free(gpointer data)
+{
+ SpirvCacheEntry *entry = data;
+
+ if (!entry) {
+ return;
+ }
+
+ g_free(entry->spirv_data);
+ g_free(entry);
+}
+
+static void load_spirv_cache(void)
+{
+ if (spirv_cache) {
+ return;
+ }
+
+ spirv_cache = g_hash_table_new_full(g_int64_hash, g_int64_equal, NULL,
+ spirv_cache_entry_free);
+
+ g_autofree char *path = get_spirv_cache_path();
+ if (!path) {
+ return;
+ }
+
+ FILE *f = fopen(path, "rb");
+ if (!f) {
+ return;
+ }
+
+ uint32_t magic, version, count;
+ if (fread(&magic, 4, 1, f) != 1 || magic != SPIRV_CACHE_MAGIC ||
+ fread(&version, 4, 1, f) != 1 || version != SPIRV_CACHE_VERSION ||
+ fread(&count, 4, 1, f) != 1) {
+ fclose(f);
+ return;
+ }
+
+ for (uint32_t i = 0; i < count; i++) {
+ uint64_t hash;
+ uint32_t spirv_len;
+ if (fread(&hash, 8, 1, f) != 1 ||
+ fread(&spirv_len, 4, 1, f) != 1 || spirv_len == 0 ||
+ spirv_len > 1024 * 1024) {
+ break;
+ }
+
+ SpirvCacheEntry *entry = g_malloc(sizeof(*entry));
+ entry->hash = hash;
+ entry->spirv_len = spirv_len;
+ entry->spirv_data = g_malloc(spirv_len);
+
+ if (fread(entry->spirv_data, 1, spirv_len, f) != spirv_len) {
+ spirv_cache_entry_free(entry);
+ break;
+ }
+
+ g_hash_table_insert(spirv_cache, &entry->hash, entry);
+ }
+
+ fclose(f);
+}
+
+static void save_spirv_cache(void)
+{
+ if (!spirv_cache || !spirv_cache_dirty) {
+ return;
+ }
+
+ g_autofree char *path = get_spirv_cache_path();
+ if (!path) {
+ return;
+ }
+
+ FILE *f = fopen(path, "wb");
+ if (!f) {
+ return;
+ }
+
+ bool ok = true;
+ uint32_t magic = SPIRV_CACHE_MAGIC;
+ uint32_t version = SPIRV_CACHE_VERSION;
+ uint32_t count = g_hash_table_size(spirv_cache);
+
+ ok = ok && fwrite(&magic, 4, 1, f) == 1;
+ ok = ok && fwrite(&version, 4, 1, f) == 1;
+ ok = ok && fwrite(&count, 4, 1, f) == 1;
+
+ GHashTableIter iter;
+ gpointer key, value;
+ g_hash_table_iter_init(&iter, spirv_cache);
+ while (ok && g_hash_table_iter_next(&iter, &key, &value)) {
+ SpirvCacheEntry *entry = value;
+ ok = ok && fwrite(&entry->hash, 8, 1, f) == 1;
+ ok = ok && fwrite(&entry->spirv_len, 4, 1, f) == 1;
+ ok = ok && fwrite(entry->spirv_data, 1, entry->spirv_len, f) ==
+ entry->spirv_len;
+ }
+
+ if (fclose(f) == 0 && ok) {
+ spirv_cache_dirty = false;
+ } else {
+ remove(path);
+ }
+}
+
+static GByteArray *spirv_cache_lookup(const char *glsl)
+{
+ if (!spirv_cache) {
+ return NULL;
+ }
+
+ uint64_t hash = fast_hash((const uint8_t *)glsl, strlen(glsl));
+ SpirvCacheEntry *entry = g_hash_table_lookup(spirv_cache, &hash);
+ if (!entry) {
+ return NULL;
+ }
+
+ GByteArray *spirv = g_byte_array_sized_new(entry->spirv_len);
+ g_byte_array_append(spirv, entry->spirv_data, entry->spirv_len);
+ return spirv;
+}
+
+static void spirv_cache_insert(const char *glsl, GByteArray *spirv)
+{
+ if (!spirv_cache || !spirv) {
+ return;
+ }
+
+ uint64_t hash = fast_hash((const uint8_t *)glsl, strlen(glsl));
+ SpirvCacheEntry *entry = g_malloc(sizeof(*entry));
+ entry->hash = hash;
+ entry->spirv_len = spirv->len;
+ entry->spirv_data = g_malloc(spirv->len);
+ memcpy(entry->spirv_data, spirv->data, spirv->len);
+ g_hash_table_insert(spirv_cache, &entry->hash, entry);
+ spirv_cache_dirty = true;
+}
+
static const glslang_resource_t
resource_limits = { .max_lights = 32,
.max_clip_planes = 6,
@@ -133,10 +297,21 @@ static const glslang_resource_t
void pgraph_vk_init_glsl_compiler(void)
{
glslang_initialize_process();
+ load_spirv_cache();
+
+ if (!spirv_atexit_registered) {
+ atexit(save_spirv_cache);
+ spirv_atexit_registered = true;
+ }
}
void pgraph_vk_finalize_glsl_compiler(void)
{
+ save_spirv_cache();
+ if (spirv_cache) {
+ g_hash_table_destroy(spirv_cache);
+ spirv_cache = NULL;
+ }
glslang_finalize_process();
}
@@ -370,8 +545,12 @@ ShaderModuleInfo *pgraph_vk_create_shader_module_from_glsl(
ShaderModuleInfo *info = g_malloc0(sizeof(*info));
info->refcnt = 0;
info->glsl = strdup(glsl);
- info->spirv = pgraph_vk_compile_glsl_to_spv(
- vk_shader_stage_to_glslang_stage(stage), glsl);
+ info->spirv = spirv_cache_lookup(glsl);
+ if (!info->spirv) {
+ info->spirv = pgraph_vk_compile_glsl_to_spv(
+ vk_shader_stage_to_glslang_stage(stage), glsl);
+ spirv_cache_insert(glsl, info->spirv);
+ }
info->module = pgraph_vk_create_shader_module_from_spv(r, info->spirv);
init_layout_from_spv(info);
return info;
diff --git a/hw/xbox/nv2a/pgraph/vk/surface.c b/hw/xbox/nv2a/pgraph/vk/surface.c
index d427f183da..275e496594 100644
--- a/hw/xbox/nv2a/pgraph/vk/surface.c
+++ b/hw/xbox/nv2a/pgraph/vk/surface.c
@@ -494,6 +494,9 @@ static void download_surface(NV2AState *d, SurfaceBinding *surface, bool force)
memory_region_set_client_dirty(d->vram, surface->vram_addr,
surface->pitch * surface->height,
DIRTY_MEMORY_NV2A_TEX);
+ memory_region_set_client_dirty(d->vram, surface->vram_addr,
+ surface->pitch * surface->height,
+ DIRTY_MEMORY_NV2A);
surface->download_pending = false;
surface->draw_dirty = false;