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;