diff --git a/ref/vk/vk_studio.c b/ref/vk/vk_studio.c index e0a5dfdb..4d0e702c 100644 --- a/ref/vk/vk_studio.c +++ b/ref/vk/vk_studio.c @@ -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); diff --git a/ref/vk/vk_studio_model.c b/ref/vk/vk_studio_model.c index d9bb8adc..6fe171c4 100644 --- a/ref/vk/vk_studio_model.c +++ b/ref/vk/vk_studio_model.c @@ -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; } diff --git a/ref/vk/vk_studio_model.h b/ref/vk/vk_studio_model.h index 0402b2c4..8ea9ec5e 100644 --- a/ref/vk/vk_studio_model.h +++ b/ref/vk/vk_studio_model.h @@ -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);