From 880bea3e5ef27f52dd0c3dcf219ab7da810eb3fd Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Wed, 2 Jul 2025 20:01:13 -0700 Subject: [PATCH] nv2a/gl: Cache shader modules --- hw/xbox/nv2a/pgraph/gl/renderer.h | 27 ++++++ hw/xbox/nv2a/pgraph/gl/shaders.c | 133 ++++++++++++++++++++++-------- hw/xbox/nv2a/pgraph/glsl/geom.c | 68 +++++++++++++++ hw/xbox/nv2a/pgraph/glsl/geom.h | 2 + 4 files changed, 196 insertions(+), 34 deletions(-) diff --git a/hw/xbox/nv2a/pgraph/gl/renderer.h b/hw/xbox/nv2a/pgraph/gl/renderer.h index 9ad29db4ef..28277fcdf5 100644 --- a/hw/xbox/nv2a/pgraph/gl/renderer.h +++ b/hw/xbox/nv2a/pgraph/gl/renderer.h @@ -82,6 +82,30 @@ typedef struct TextureBinding { GLuint gl_texture; } TextureBinding; +typedef struct ShaderModuleCacheKey { + GLenum kind; + union { + struct { + VshState state; + GenVshGlslOptions glsl_opts; + } vsh; + struct { + GeomState state; + GenGeomGlslOptions glsl_opts; + } geom; + struct { + PshState state; + GenPshGlslOptions glsl_opts; + } psh; + }; +} ShaderModuleCacheKey; + +typedef struct ShaderModuleCacheEntry { + LruNode node; + ShaderModuleCacheKey key; + GLuint gl_shader; +} ShaderModuleCacheEntry; + typedef struct ShaderBinding { LruNode node; bool initialized; @@ -175,6 +199,9 @@ typedef struct PGRAPHGLState { QemuMutex shader_cache_lock; QemuThread shader_disk_thread; + Lru shader_module_cache; + ShaderModuleCacheEntry *shader_module_cache_entries; + unsigned int zpass_pixel_count_result; unsigned int gl_zpass_pixel_count_query_count; GLuint *gl_zpass_pixel_count_queries; diff --git a/hw/xbox/nv2a/pgraph/gl/shaders.c b/hw/xbox/nv2a/pgraph/gl/shaders.c index dfc0d1acf2..b27d398548 100644 --- a/hw/xbox/nv2a/pgraph/gl/shaders.c +++ b/hw/xbox/nv2a/pgraph/gl/shaders.c @@ -132,7 +132,68 @@ static void update_shader_uniform_locs(ShaderBinding *binding) } } -static void generate_shaders(ShaderBinding *binding) +static void shader_module_cache_entry_init(Lru *lru, LruNode *node, void *key) +{ + ShaderModuleCacheEntry *module = + container_of(node, ShaderModuleCacheEntry, node); + memcpy(&module->key, key, sizeof(ShaderModuleCacheKey)); + + const char *kind_str; + MString *code; + + switch (module->key.kind) { + case GL_VERTEX_SHADER: + kind_str = "vertex shader"; + code = pgraph_glsl_gen_vsh(&module->key.vsh.state, + module->key.vsh.glsl_opts); + break; + case GL_GEOMETRY_SHADER: + kind_str = "geometry shader"; + code = pgraph_glsl_gen_geom(&module->key.geom.state, + module->key.geom.glsl_opts); + break; + case GL_FRAGMENT_SHADER: + kind_str = "fragment shader"; + code = pgraph_glsl_gen_psh(&module->key.psh.state, + module->key.psh.glsl_opts); + break; + default: + assert(!"Invalid shader module kind"); + kind_str = "unknown"; + code = NULL; + } + + module->gl_shader = + create_gl_shader(module->key.kind, mstring_get_str(code), kind_str); + mstring_unref(code); +} + +static void shader_module_cache_entry_post_evict(Lru *lru, LruNode *node) +{ + ShaderModuleCacheEntry *module = + container_of(node, ShaderModuleCacheEntry, node); + glDeleteShader(module->gl_shader); +} + +static bool shader_module_cache_entry_compare(Lru *lru, LruNode *node, + void *key) +{ + ShaderModuleCacheEntry *module = + container_of(node, ShaderModuleCacheEntry, node); + return memcmp(&module->key, key, sizeof(ShaderModuleCacheKey)); +} + +static GLuint get_shader_module_for_key(PGRAPHGLState *r, + ShaderModuleCacheKey *key) +{ + uint64_t hash = fast_hash((void *)key, sizeof(ShaderModuleCacheKey)); + LruNode *node = lru_lookup(&r->shader_module_cache, hash, key); + ShaderModuleCacheEntry *module = + container_of(node, ShaderModuleCacheEntry, node); + return module->gl_shader; +} + +static void generate_shaders(PGRAPHGLState *r, ShaderBinding *binding) { char *previous_numeric_locale = setlocale(LC_NUMERIC, NULL); if (previous_numeric_locale) { @@ -144,43 +205,28 @@ static void generate_shaders(ShaderBinding *binding) GLuint program = glCreateProgram(); ShaderState *state = &binding->state; + ShaderModuleCacheKey key; - /* Create an optional geometry shader and find primitive type */ - GLenum gl_primitive_mode = get_gl_primitive_mode( - state->geom.polygon_front_mode, state->geom.primitive_mode); - MString *geometry_shader_code = - pgraph_glsl_gen_geom(&state->geom, (GenGeomGlslOptions){ 0 }); - if (geometry_shader_code) { - const char* geometry_shader_code_str = - mstring_get_str(geometry_shader_code); - GLuint geometry_shader = create_gl_shader(GL_GEOMETRY_SHADER, - geometry_shader_code_str, - "geometry shader"); - glAttachShader(program, geometry_shader); - mstring_unref(geometry_shader_code); + bool need_geometry_shader = pgraph_glsl_need_geom(&state->geom); + if (need_geometry_shader) { + memset(&key, 0, sizeof(key)); + key.kind = GL_GEOMETRY_SHADER; + key.geom.state = state->geom; + glAttachShader(program, get_shader_module_for_key(r, &key)); } /* create the vertex shader */ - MString *vertex_shader_code = pgraph_glsl_gen_vsh( - &state->vsh, (GenVshGlslOptions){ - .prefix_outputs = geometry_shader_code != NULL, - }); - GLuint vertex_shader = create_gl_shader(GL_VERTEX_SHADER, - mstring_get_str(vertex_shader_code), - "vertex shader"); - glAttachShader(program, vertex_shader); - mstring_unref(vertex_shader_code); + memset(&key, 0, sizeof(key)); + key.kind = GL_VERTEX_SHADER; + key.vsh.state = state->vsh; + key.vsh.glsl_opts.prefix_outputs = need_geometry_shader; + glAttachShader(program, get_shader_module_for_key(r, &key)); /* generate a fragment shader from register combiners */ - MString *fragment_shader_code = - pgraph_glsl_gen_psh(&state->psh, (GenPshGlslOptions){ 0 }); - const char *fragment_shader_code_str = - mstring_get_str(fragment_shader_code); - GLuint fragment_shader = create_gl_shader(GL_FRAGMENT_SHADER, - fragment_shader_code_str, - "fragment shader"); - glAttachShader(program, fragment_shader); - mstring_unref(fragment_shader_code); + memset(&key, 0, sizeof(key)); + key.kind = GL_FRAGMENT_SHADER; + key.psh.state = state->psh; + glAttachShader(program, get_shader_module_for_key(r, &key)); /* link the program */ glLinkProgram(program); @@ -196,7 +242,8 @@ static void generate_shaders(ShaderBinding *binding) glUseProgram(program); binding->gl_program = program; - binding->gl_primitive_mode = gl_primitive_mode; + binding->gl_primitive_mode = get_gl_primitive_mode( + state->geom.polygon_front_mode, state->geom.primitive_mode); binding->initialized = true; set_texture_sampler_uniforms(binding); @@ -521,6 +568,20 @@ void pgraph_gl_init_shaders(PGRAPHState *pg) qemu_thread_create(&r->shader_disk_thread, "pgraph.renderer_state->shader_cache", shader_reload_lru_from_disk, pg, QEMU_THREAD_JOINABLE); + + /* FIXME: Make this configurable */ + const size_t shader_module_cache_size = 50*1024; + lru_init(&r->shader_module_cache); + r->shader_module_cache_entries = + g_malloc_n(shader_module_cache_size, sizeof(ShaderModuleCacheEntry)); + assert(r->shader_module_cache_entries != NULL); + for (int i = 0; i < shader_module_cache_size; i++) { + lru_add_free(&r->shader_module_cache, &r->shader_module_cache_entries[i].node); + } + + r->shader_module_cache.init_node = shader_module_cache_entry_init; + r->shader_module_cache.compare_nodes = shader_module_cache_entry_compare; + r->shader_module_cache.post_node_evict = shader_module_cache_entry_post_evict; } void pgraph_gl_finalize_shaders(PGRAPHState *pg) @@ -532,6 +593,10 @@ void pgraph_gl_finalize_shaders(PGRAPHState *pg) free(r->shader_cache_entries); r->shader_cache_entries = NULL; + lru_flush(&r->shader_module_cache); + g_free(r->shader_module_cache_entries); + r->shader_module_cache_entries = NULL; + qemu_mutex_destroy(&r->shader_cache_lock); } @@ -730,7 +795,7 @@ void pgraph_gl_bind_shaders(PGRAPHState *pg) if (!binding->initialized && !pgraph_gl_shader_load_from_memory(binding)) { nv2a_profile_inc_counter(NV2A_PROF_SHADER_GEN); - generate_shaders(binding); + generate_shaders(r, binding); if (g_config.perf.cache_shaders) { pgraph_gl_shader_cache_to_disk(binding); } diff --git a/hw/xbox/nv2a/pgraph/glsl/geom.c b/hw/xbox/nv2a/pgraph/glsl/geom.c index 7acd0e3b07..ca46a5f3af 100644 --- a/hw/xbox/nv2a/pgraph/glsl/geom.c +++ b/hw/xbox/nv2a/pgraph/glsl/geom.c @@ -39,6 +39,74 @@ void pgraph_glsl_set_geom_state(PGRAPHState *pg, GeomState *state) NV_PGRAPH_CONTROL_3_SHADEMODE_SMOOTH; } +bool pgraph_glsl_need_geom(const GeomState *state) +{ + /* FIXME: Missing support for 2-sided-poly mode */ + assert(state->polygon_front_mode == state->polygon_back_mode); + enum ShaderPolygonMode polygon_mode = state->polygon_front_mode; + + /* POINT mode shouldn't require any special work */ + if (polygon_mode == POLY_MODE_POINT) { + return false; + } + + switch (state->primitive_mode) { + case PRIM_TYPE_TRIANGLES: + if (polygon_mode == POLY_MODE_FILL) { + return false; + } + return true; + case PRIM_TYPE_TRIANGLE_STRIP: + if (polygon_mode == POLY_MODE_FILL) { + return false; + } + assert(polygon_mode == POLY_MODE_LINE); + return true; + case PRIM_TYPE_TRIANGLE_FAN: + if (polygon_mode == POLY_MODE_FILL) { + return false; + } + assert(polygon_mode == POLY_MODE_LINE); + return true; + case PRIM_TYPE_QUADS: + if (polygon_mode == POLY_MODE_LINE) { + return true; + } else if (polygon_mode == POLY_MODE_FILL) { + return true; + } else { + assert(false); + return false; + } + break; + case PRIM_TYPE_QUAD_STRIP: + if (polygon_mode == POLY_MODE_LINE) { + return true; + } else if (polygon_mode == POLY_MODE_FILL) { + return true; + } else { + assert(false); + return false; + } + break; + case PRIM_TYPE_POLYGON: + if (polygon_mode == POLY_MODE_LINE) { + return false; + } + if (polygon_mode == POLY_MODE_FILL) { + if (state->smooth_shading) { + return false; + } + return true; + } else { + assert(false); + return false; + } + break; + default: + return false; + } +} + MString *pgraph_glsl_gen_geom(const GeomState *state, GenGeomGlslOptions opts) { /* FIXME: Missing support for 2-sided-poly mode */ diff --git a/hw/xbox/nv2a/pgraph/glsl/geom.h b/hw/xbox/nv2a/pgraph/glsl/geom.h index f4f72dd0c7..41ff255161 100644 --- a/hw/xbox/nv2a/pgraph/glsl/geom.h +++ b/hw/xbox/nv2a/pgraph/glsl/geom.h @@ -38,6 +38,8 @@ typedef struct GenGeomGlslOptions { void pgraph_glsl_set_geom_state(PGRAPHState *pg, GeomState *geom); +bool pgraph_glsl_need_geom(const GeomState *state); + MString *pgraph_glsl_gen_geom(const GeomState *state, GenGeomGlslOptions opts); #endif