vk: studio: implement proper per-submodel rendermodel cache

Gives out individual render submodels for dynamic ones, receives and remembers them when they are not needed anymore.

Stores only one render submodel for static ones. Reuses/instantiates it for everyone.
This commit is contained in:
Ivan 'provod' Avdeev 2023-08-25 15:25:07 -04:00 committed by Ivan Avdeev
parent c42cf2088c
commit 03fc537d54
3 changed files with 117 additions and 172 deletions

View File

@ -2097,7 +2097,7 @@ static void buildStudioSubmodelGeometry(build_submodel_geometry_t args) {
R_GeometryRangeUnlock(&geom_lock);
}
static qboolean studioSubmodelRenderInit(r_studio_render_submodel_t *render_submodel, const mstudiomodel_t *submodel, qboolean is_dynamic) {
static qboolean studioSubmodelRenderInit(r_studio_submodel_render_t *render_submodel, const mstudiomodel_t *submodel, qboolean is_dynamic) {
// Compute vertex and index counts.
// TODO should this be part of r_studio_model_info_t?
int vertex_count = 0, index_count = 0;
@ -2132,13 +2132,11 @@ static qboolean studioSubmodelRenderInit(r_studio_render_submodel_t *render_subm
.index_count = index_count,
});
*render_submodel = (r_studio_render_submodel_t){
.geometries = geometries,
.geometries_count = submodel->nummesh,
.geometry_range = geometry,
.vertex_count = vertex_count,
.index_count = index_count,
};
render_submodel->geometries = geometries;
render_submodel->geometries_count = submodel->nummesh;
render_submodel->geometry_range = geometry;
render_submodel->vertex_count = vertex_count;
render_submodel->index_count = index_count;
if (!R_RenderModelCreate(&render_submodel->model, (vk_render_model_init_t){
.name = submodel->name,
@ -2157,7 +2155,7 @@ static qboolean studioSubmodelRenderInit(r_studio_render_submodel_t *render_subm
return true;
}
static qboolean studioSubmodelRenderUpdate(const r_studio_render_submodel_t *submodel_render) {
static qboolean studioSubmodelRenderUpdate(const r_studio_submodel_render_t *submodel_render) {
buildStudioSubmodelGeometry((build_submodel_geometry_t){
//.submodel = submodel_render->key_submodel,
.geometry = &submodel_render->geometry_range,
@ -2187,20 +2185,20 @@ static qboolean studioSubmodelRenderUpdate(const r_studio_render_submodel_t *sub
static void studioEntityModelDestroy(void *userdata) {
r_studio_entity_model_t *entmodel = (r_studio_entity_model_t*)userdata;
if (entmodel->submodels) {
for (int i = 0; i < entmodel->num_submodels; ++i) {
studioRenderSubmodelDestroy(entmodel->submodels + i);
}
Mem_Free(entmodel->submodels);
for (int i = 0; i < entmodel->bodyparts_count; ++i) {
r_studio_submodel_render_t *const render = entmodel->bodyparts[i];
studioSubmodelRenderModelRelease(render);
}
if (entmodel->bodyparts)
Mem_Free(entmodel->bodyparts);
}
static r_studio_entity_model_t *studioEntityModelCreate(const cl_entity_t *entity) {
r_studio_entity_model_t *const entmodel = Mem_Calloc(vk_core.pool, sizeof(r_studio_entity_model_t));
entmodel->studio_header = m_pStudioHeader;
entmodel->num_submodels = m_pStudioHeader->numbodyparts; // TODO is this correct number?
entmodel->submodels = Mem_Calloc(vk_core.pool, sizeof(*entmodel->submodels) * entmodel->num_submodels);
entmodel->bodyparts_count = m_pStudioHeader->numbodyparts; // TODO is this correct number?
entmodel->bodyparts = Mem_Calloc(vk_core.pool, sizeof(*entmodel->bodyparts) * entmodel->bodyparts_count);
Matrix3x4_Copy(entmodel->prev_transform, g_studio.rotationmatrix);
@ -2221,15 +2219,16 @@ static r_studio_entity_model_t *studioEntityModelGet(const cl_entity_t* entity)
return NULL;
}
gEngine.Con_Reportf("Created studio entity %p model %s: %p (submodels=%d)\n", entity, entity->model->name, entmodel, entmodel->num_submodels);
gEngine.Con_Reportf("Created studio entity %p model %s: %p (bodyparts=%d)\n",
entity, entity->model->name, entmodel, entmodel->bodyparts_count);
VK_EntityDataSet(entity, entmodel, &studioEntityModelDestroy);
return entmodel;
}
static const r_studio_submodel_info_t *studioModelFindSubmodelInfo(void) {
static r_studio_submodel_info_t *studioModelFindSubmodelInfo(void) {
for (int i = 0; i < g_studio_current.entmodel->model_info->submodels_count; ++i) {
const r_studio_submodel_info_t *const subinfo = g_studio_current.entmodel->model_info->submodels + i;
r_studio_submodel_info_t *const subinfo = g_studio_current.entmodel->model_info->submodels + i;
if (subinfo->submodel_key == m_pSubModel)
return subinfo;
}
@ -2254,16 +2253,31 @@ static void R_StudioDrawPoints( void ) {
g_studio_current.entmodel = studioEntityModelGet(RI.currententity);
ASSERT(g_studio_current.bodypart_index >= 0);
ASSERT(g_studio_current.bodypart_index < g_studio_current.entmodel->num_submodels);
r_studio_render_submodel_t *const render_submodel = g_studio_current.entmodel->submodels + g_studio_current.bodypart_index;
ASSERT(g_studio_current.bodypart_index < g_studio_current.entmodel->bodyparts_count);
const r_studio_submodel_info_t *const subinfo = studioModelFindSubmodelInfo();
if (!subinfo) {
gEngine.Con_Printf(S_ERROR "Submodel %s info not found for model %s, this should be impossible\n", m_pSubModel->name, m_pStudioHeader->name);
return;
r_studio_submodel_render_t *render_submodel = g_studio_current.entmodel->bodyparts[g_studio_current.bodypart_index];
// Submodels for bodyparts can potentially change at runtime
if (!render_submodel || render_submodel->_.info->submodel_key != m_pSubModel) {
if (render_submodel) {
gEngine.Con_Reportf(S_WARN "Detected bodypart submodel change from %s to %s for model %s entity %p(%d)\n", render_submodel->_.info->submodel_key->name, m_pSubModel->name, m_pStudioHeader->name, RI.currententity, RI.currententity->index);
studioSubmodelRenderModelRelease(render_submodel);
render_submodel = g_studio_current.entmodel->bodyparts[g_studio_current.bodypart_index] = NULL;
}
r_studio_submodel_info_t *const subinfo = studioModelFindSubmodelInfo();
if (!subinfo) {
gEngine.Con_Printf(S_ERROR "Submodel %s info not found for model %s, this should be impossible\n", m_pSubModel->name, m_pStudioHeader->name);
return;
}
render_submodel = g_studio_current.entmodel->bodyparts[g_studio_current.bodypart_index] = studioSubmodelRenderModelAcquire(subinfo);
ASSERT(render_submodel);
ASSERT(render_submodel->_.info);
}
const qboolean is_dynamic = subinfo->is_dynamic;
const qboolean is_dynamic = render_submodel->_.info->is_dynamic;
if (!render_submodel->geometries) {
if (!studioSubmodelRenderInit(render_submodel, m_pSubModel, is_dynamic)) {
@ -2271,7 +2285,7 @@ static void R_StudioDrawPoints( void ) {
return;
}
gEngine.Con_Reportf("Initialized studio submodel for %s/%d\n", RI.currentmodel->name, g_studio_current.bodypart_index);
gEngine.Con_Reportf("Initialized studio submodel for %s // %s\n", RI.currentmodel->name, render_submodel->_.info->submodel_key->name);
} else if (is_dynamic) {
if (!studioSubmodelRenderUpdate(render_submodel)) {
gEngine.Con_Printf(S_ERROR "Unable to update studio submodel for %s/%d\n", RI.currentmodel->name, g_studio_current.bodypart_index);

View File

@ -6,105 +6,55 @@
#define MODULE_NAME "studio"
// FIXME:
// - [ ] (CRASH) Cache coherence: entities are reused between frames for different studio models. Catch and handle it.
// If it was literally reused between sequential frames, then we're screwed, because we can't delete the previous one, as it might be still in use on GPU. Needs refcounts infrastructure.
// Currently we just push render_submodels being released back into cache. And we don't destroy them ever.
// This needs to change.
// TODO add clearing cache function. Also make sure that it is called on shutdown.
// TODO Potential optimization: (MIGHT STILL NEED THIS FOR DENOISER MOVE VECTORS)
// Keep last generated verts. Compare new verts with previous ones. If they are the same, do not rebuild the BLAS.
typedef struct {
const studiohdr_t *studio_header_key;
r_studio_model_info_t info;
} r_studio_model_info_entry_t;
/*
typedef struct {
// BOTH must match
const cl_entity_t *key_entity;
const model_t *key_model;
r_studio_entity_model_t entmodel;
} r_studio_entity_model_entry_t;
*/
static struct {
/*
#define MAX_CACHED_STUDIO_SUBMODELS 1024
// TODO proper map/hash table
r_studio_model_cache_entry_t entries[MAX_CACHED_STUDIO_SUBMODELS];
int entries_count;
*/
#define MAX_STUDIO_MODELS 256
r_studio_model_info_entry_t models[MAX_STUDIO_MODELS];
int models_count;
// TODO hash table
//r_studio_entity_model_entry_t *entmodels;
//int entmodels_count;
} g_studio_cache;
void studioRenderSubmodelDestroy( r_studio_render_submodel_t *submodel ) {
void studioRenderSubmodelDestroy( r_studio_submodel_render_t *submodel ) {
R_RenderModelDestroy(&submodel->model);
R_GeometryRangeFree(&submodel->geometry_range);
if (submodel->geometries)
Mem_Free(submodel->geometries);
submodel->geometries = NULL;
submodel->geometries_count = 0;
submodel->vertex_count = 0;
submodel->index_count = 0;
}
static void studioSubmodelInfoDestroy(r_studio_submodel_info_t *subinfo) {
while (subinfo->cached_head) {
r_studio_submodel_render_t *render = subinfo->cached_head;
subinfo->cached_head = subinfo->cached_head->_.next;
studioRenderSubmodelDestroy(render);
}
}
void R_StudioCacheClear( void ) {
/*
for (int i = 0; i < g_studio_cache.entries_count; ++i) {
r_studio_model_cache_entry_t *const entry = g_studio_cache.entries + i;
ASSERT(entry->key_submodel);
entry->key_submodel = 0;
entry->key_entity = NULL;
studioRenderSubmodelDestroy(&entry->render);
}
g_studio_cache.entries_count = 0;
*/
for (int i = 0; i < g_studio_cache.models_count; ++i) {
r_studio_model_info_t *info = &g_studio_cache.models[i].info;
for (int j = 0; j < info->submodels_count; ++j)
studioSubmodelInfoDestroy(info->submodels + j);
if (info->submodels)
Mem_Free(info->submodels);
}
g_studio_cache.models_count = 0;
}
// FIXME:
// - [ ] (CRASH) Cache coherence: entities are reused between frames for different studio models. Catch and handle it. If it was literally reused between sequential frames, then we're screwed, because we can't delete the previous one, as it might be still in use on GPU. Needs refcounts infrastructure.
// - [ ] (POTENTIAL CRASH) g_studio_current.bodypartindex doesn't directly correspond to a single mstudiosubmodel_t. Currently it's tracked as it does. This can break and crash.
// - [ ] Proper submodel cache (by pointer, these pointers should be stable)
/*
* - submodels[] (global? per-model?)
* - mstudiosubmodel_t *key
* - qboolean is_static (if true, there's only one render_model_t that is instanced by all users)
* - entries[entries_count|entries_capacity]
* - r_studio_render_submodel_t render_submodel
* - int refcount (is in use by anything? only needed for cache cleaning really, which we can work around by waiting for gpu idle and having hiccups, which is fine as long as this is a really rare situation)
* - int used_frames_ago (or last_used_frame)
*/
// Action items:
// 3. When R_StudioDrawPoints()
// a. Get the correct studio entmodel from the cache. For prev_transform, etc.
// b. Get the correct submodel render model from the cache.
// c. If there's no such submodel, generate the vertices and cache it.
// d. If there is:
// 1. Static is instanced (a single vk_render_model_t used by everyone with different transform matrices and potentially texture patches). No vertices generation is needed
// 2. Non-static ones need vertices updates each frame.
// e. Submit the submodel for rendering.
// TODO Potential optimization for the above: (MIGHT STILL NEED THIS FOR DENOISER MOVE VECTORS)
// Keep last used submodel within studio entmodel. Keep last generated verts. Compare new verts with previous ones. If they are the same, do not rebuild the BLAS.
// TODO
// - [ ] (MORE MODELS CAN BE STATIC) Bones are global, calculated only once per frame for the entire studio model. Submodels reference an indexed subset of bones.
// DONE?
// - [x] How to find all studio model submodels regardless of bodyparts. -- NIKAQUE. they all are under bodyparts
//
// - [x] Is crossbow model really static? -- THIS SEEMS OK
static struct {
vec4_t first_q[MAXSTUDIOBONES];
float first_pos[MAXSTUDIOBONES][3];
@ -268,34 +218,37 @@ r_studio_model_info_t *getStudioModelInfo(model_t *model) {
return NULL;
}
/*
const r_studio_model_cache_entry_t *findSubModelInCacheForEntity(const mstudiomodel_t *submodel, const cl_entity_t *ent) {
// FIXME hash table, there are hundreds of entries
for (int i = 0; i < g_studio_cache.entries_count; ++i) {
const r_studio_model_cache_entry_t *const entry = g_studio_cache.entries + i;
if (entry->key_submodel == submodel && (entry->key_entity == NULL || entry->key_entity == ent))
return entry;
}
return NULL;
}
r_studio_model_cache_entry_t *studioSubModelCacheAlloc(void) {
if (g_studio_cache.entries_count == MAX_CACHED_STUDIO_SUBMODELS) {
PRINT_NOT_IMPLEMENTED_ARGS("Studio submodel cache overflow at %d", MAX_CACHED_STUDIO_SUBMODELS);
return NULL;
}
r_studio_model_cache_entry_t *const entry = g_studio_cache.entries + g_studio_cache.entries_count;
++g_studio_cache.entries_count;
return entry;
}
*/
// TODO ? void studioSubModelCacheFree(r_studio_model_cache_entry_t*);
void VK_StudioModelInit(void) {
// ... R_SPEEDS_METRIC(g_studio_cache.entries_count, "cached_submodels", kSpeedsMetricCount);
// FIXME R_SPEEDS_METRIC(g_studio_cache.entries_count, "cached_submodels", kSpeedsMetricCount);
}
r_studio_submodel_render_t *studioSubmodelRenderModelAcquire(r_studio_submodel_info_t *subinfo) {
r_studio_submodel_render_t *render = NULL;
if (subinfo->cached_head) {
render = subinfo->cached_head;
if (subinfo->is_dynamic) {
subinfo->cached_head = render->_.next;
render->_.next = NULL;
}
return render;
}
render = Mem_Calloc(vk_core.pool, sizeof(*render));
render->_.info = subinfo;
if (!subinfo->is_dynamic)
subinfo->cached_head = render;
return render;
}
void studioSubmodelRenderModelRelease(r_studio_submodel_render_t *render_submodel) {
if (!render_submodel)
return;
if (!render_submodel->_.info->is_dynamic)
return;
render_submodel->_.next = render_submodel->_.info->cached_head;
render_submodel->_.info->cached_head = render_submodel;
}

View File

@ -3,61 +3,41 @@
#include "vk_render.h"
#include "vk_geometry.h"
typedef struct {
struct r_studio_submodel_info_s;
typedef struct r_studio_submodel_render_s {
vk_render_model_t model;
r_geometry_range_t geometry_range;
vk_render_geometry_t *geometries;
// TODO figure out how to precompute this and store it in info
int geometries_count;
int vertex_count, index_count;
} r_studio_render_submodel_t;
typedef struct {
// TODO vec3_t prev_verts;
struct {
struct r_studio_submodel_info_s *info;
struct r_studio_submodel_render_s *next;
} _;
} r_studio_submodel_render_t;
typedef struct r_studio_submodel_info_s {
const mstudiomodel_t *submodel_key;
/* int indices_offset; */
/* int vertices_offset; */
/* int geometries_offset; */
/* int vertices_count; */
/* int indices_count; */
/* int geometries_count; */
qboolean is_dynamic;
// TODO int verts_count; for prev_verts
r_studio_submodel_render_t *cached_head;
} r_studio_submodel_info_t;
typedef struct {
/* int total_vertices; */
/* int total_indices; */
/* int total_geometries; */
r_studio_submodel_render_t *studioSubmodelRenderModelAcquire(r_studio_submodel_info_t *info);
void studioSubmodelRenderModelRelease(r_studio_submodel_render_t *render_submodel);
typedef struct {
int submodels_count;
r_studio_submodel_info_t *submodels;
} r_studio_model_info_t;
/* deprecate
typedef struct {
const mstudiomodel_t *key_submodel;
// Non-NULL for animated instances
const cl_entity_t *key_entity;
r_studio_render_submodel_t render;
} r_studio_model_cache_entry_t;
typedef struct {
const studiohdr_t *key_model;
// Model contains no animations and can be used directly
qboolean is_static;
// Pre-built submodels for static, NULL if not static
r_studio_render_submodel_t *render_submodels;
} r_studio_cached_model_t;
const r_studio_model_cache_entry_t *findSubModelInCacheForEntity(const mstudiomodel_t *submodel, const cl_entity_t *ent);
r_studio_model_cache_entry_t *studioSubModelCacheAlloc(void);
*/
void VK_StudioModelInit(void);
// Entity model cache/pool
@ -67,11 +47,9 @@ typedef struct {
// ??? probably unnecessary matrix3x4 transform;
matrix3x4 prev_transform;
// TODO vec3_t *prev_verts;
// FIXME this is bodyparts, not submodels. Body parts can theoretically switch submodels used at runtime.
int num_submodels;
r_studio_render_submodel_t *submodels;
int bodyparts_count;
r_studio_submodel_render_t **bodyparts;
} r_studio_entity_model_t;
//r_studio_entity_model_t *studioEntityModelGet(const cl_entity_t *ent);
@ -80,6 +58,6 @@ typedef struct {
//void studioEntityModelClear(void);
void studioRenderSubmodelDestroy( r_studio_render_submodel_t *submodel );
void studioRenderSubmodelDestroy( r_studio_submodel_render_t *submodel );
r_studio_model_info_t *getStudioModelInfo(model_t *model);