nv2a/gl: Cache shader modules

This commit is contained in:
Matt Borgerson 2025-07-02 20:01:13 -07:00
parent c11c098ec6
commit 93598e9648
4 changed files with 196 additions and 34 deletions

View File

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

View File

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

View File

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

View File

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