diff --git a/ref/vk/NOTES.md b/ref/vk/NOTES.md index 0e0db3413..e1029befd 100644 --- a/ref/vk/NOTES.md +++ b/ref/vk/NOTES.md @@ -360,3 +360,41 @@ rt_model: - cl_entity_t - sequence -- references studio model sequence - animtime/frame -- references animation state within sequence + +# E282 +## Studio model tracking +`m_pStudioHeader` is set from: +- `R_StudioSetHeader()` from: + - EXTERNAL + - `R_StudioDrawModel()` + - `R_StudioDrawPlayer()` +- `R_StudioDrawPlayer()` + +## Detecting static/unchanged studio submodels +### Parse `studiohdr_t` eagerly +Go deeply into sequences, animations, etc and figure out whether vertices will actually change. +Might not catch models which are not being animated right now, i.e. current frame is the same as previous one, altough it is not guaranteed to be so. +This potentially conflicts with game logic updating bonetransforms manually even though there are no recorded animations in studio file. + +### Detect changes dynamically +Let it process vertices as usual, but then compute hash of vertices values. +Depends on floating point vertices coordinates being bit-perfect same every time, even for moving entities. This is not strictly speaking true because studio model rendering is organized in such a way that bone matrices are pre-multiplied by entity transform matrix. This is done outside of vk_studio.c, and in game dll,which we have no control over. We then undo this multiplication. Given floating point nature of all of this garbage, there will be precision errors and resulting coordinates are not guaranteed to be the same even for completely static models. + +### Lazily detect static models, and draw the rest as fully dynamic with fast build +- Detect simple static cases (one sequence, one frame), and pre-build those. +- For everything else, just build it from scratch every frame w/o caching or anything. +If that is not fast enough, then we can proceed with more involved per-entity caching, BLAS updates, cache eviction, etc. + +TODO: can we not have a BLAS/model for each submodel? Can it be per-model instead? This would need prior knowledge of submodel count, mesh count, vertices and indices counts. (Potentially conflicts with game dll doing weird things, e.g. skipping certain submodels based on whatever game specific logic) + +### Action plan +- [ ] Try to pre-build static studio models. If fails (e.g. still need dynamic knowledge for the first build), then build it lazily, i.e. when the model is rendered for the first time. + - [ ] Needs tracking of model cache entry whenever `m_pStudioHeader` is set. +- [ ] Add a cache for entities, store all prev_* stuff there. + - [ ] Needs tracking of entity cache entry whenever `RI.currententity` is set. + +- [ ] Alternative model/entity tracking: just check current ptrs in `R_StudioDrawPoints()` and update them if changed. + +# 2023-07-30 +- ~~R_DrawStudioModel is the main func for drawing studio model. Called from scene code for each studio entity, with everything current (RI and stuff) set up~~ +- `R_StudioDrawModelInternal()` is the main one. It is where it splits into renderer-vs-game rendering functions. diff --git a/ref/vk/vk_core.c b/ref/vk/vk_core.c index 8d216d7ee..c018d1110 100644 --- a/ref/vk/vk_core.c +++ b/ref/vk/vk_core.c @@ -22,6 +22,7 @@ #include "vk_sprite.h" #include "vk_beams.h" #include "vk_combuf.h" +#include "vk_entity_data.h" // FIXME move this rt-specific stuff out #include "vk_light.h" @@ -810,6 +811,8 @@ qboolean R_VkInit( void ) void R_VkShutdown( void ) { XVK_CHECK(vkDeviceWaitIdle(vk_core.device)); + VK_EntityDataClear(); + R_SpriteShutdown(); if (vk_core.rtx) diff --git a/ref/vk/vk_entity_data.c b/ref/vk/vk_entity_data.c new file mode 100644 index 000000000..27ef86e60 --- /dev/null +++ b/ref/vk/vk_entity_data.c @@ -0,0 +1,57 @@ +#include "vk_entity_data.h" + +#include "vk_common.h" // ASSERT + +#include // NULL + +// TODO proper hash map with dynamic size, etc +#define MAX_ENTITIES 1024 + +typedef struct { + const struct cl_entity_s *entity; + void *userdata; + entity_data_dtor_f *dtor; +} entity_data_cache_entry_t; + +struct { + int entries_count; + entity_data_cache_entry_t entries[MAX_ENTITIES]; +} g_entdata; + +void* VK_EntityDataGet(const struct cl_entity_s* entity) { + for (int i = 0; i < g_entdata.entries_count; ++i) { + entity_data_cache_entry_t *const entry = g_entdata.entries + i; + if (entry->entity == entity) + return entry->userdata; + } + + return NULL; +} + +void VK_EntityDataClear(void) { + for (int i = 0; i < g_entdata.entries_count; ++i) { + entity_data_cache_entry_t *const entry = g_entdata.entries + i; + entry->dtor(entry->userdata); + } + + g_entdata.entries_count = 0; +} + +void VK_EntityDataSet(const struct cl_entity_s* entity, void *userdata, entity_data_dtor_f *dtor) { + for (int i = 0; i < g_entdata.entries_count; ++i) { + entity_data_cache_entry_t *const entry = g_entdata.entries + i; + if (entry->entity == entity) { + entry->dtor(entry->userdata); + entry->userdata = userdata; + entry->dtor = dtor; + return; + } + } + + ASSERT(g_entdata.entries_count < MAX_ENTITIES); + entity_data_cache_entry_t *const entry = g_entdata.entries + g_entdata.entries_count; + entry->entity = entity; + entry->userdata = userdata; + entry->dtor = dtor; + ++g_entdata.entries_count; +} diff --git a/ref/vk/vk_entity_data.h b/ref/vk/vk_entity_data.h new file mode 100644 index 000000000..a9bfb0c00 --- /dev/null +++ b/ref/vk/vk_entity_data.h @@ -0,0 +1,16 @@ +#pragma once + +struct cl_entity_s; +void* VK_EntityDataGet(const struct cl_entity_s*); + +typedef void (entity_data_dtor_f)(void*); + +// Will destroy and overwrite the older userdata if it exists. +// TODO: Make sure that the older userdata is not used (i.e. in parallel on GPU for rendering a still in-flight frame). +// This'd require a proper usage tracking (e.g. using refcounts) with changes to the rest of the renderer. +// Someday... +void VK_EntityDataSet(const struct cl_entity_s*, void *userdata, entity_data_dtor_f *dtor); + +void VK_EntityDataClear(void); + +// TODO a function to LRU-clear userdata that hasn't been used for a few frames diff --git a/ref/vk/vk_materials.c b/ref/vk/vk_materials.c index 831b07064..c71849075 100644 --- a/ref/vk/vk_materials.c +++ b/ref/vk/vk_materials.c @@ -24,6 +24,8 @@ static xvk_material_t k_default_material = { static struct { xvk_material_t materials[MAX_TEXTURES]; + + qboolean verbose_logs; } g_materials; static struct { @@ -38,7 +40,8 @@ static struct { static int loadTexture( const char *filename, qboolean force_reload ) { const uint64_t load_begin_ns = aprof_time_now_ns(); const int tex_id = force_reload ? XVK_LoadTextureReplace( filename, NULL, 0, 0 ) : VK_LoadTexture( filename, NULL, 0, 0 ); - gEngine.Con_Reportf("Loaded texture %s => %d\n", filename, tex_id); + if (g_materials.verbose_logs) + gEngine.Con_Reportf("Loaded texture %s => %d\n", filename, tex_id); g_stats.texture_loads++; g_stats.texture_load_duration_ns += aprof_time_now_ns() - load_begin_ns; return tex_id ? tex_id : -1; @@ -75,7 +78,8 @@ static void loadMaterialsFromFile( const char *filename, int depth ) { string basecolor_map, normal_map, metal_map, roughness_map; - gEngine.Con_Reportf("Loading materials from %s\n", filename); + if (g_materials.verbose_logs) + gEngine.Con_Reportf("Loading materials from %s\n", filename); if ( !data ) return; @@ -140,8 +144,9 @@ static void loadMaterialsFromFile( const char *filename, int depth ) { current_material.metalness = 1.f; } - gEngine.Con_Reportf("Creating%s material for texture %s(%d)\n", create?" new":"", - findTexture(current_material_index)->name, current_material_index); + if (g_materials.verbose_logs) + gEngine.Con_Reportf("Creating%s material for texture %s(%d)\n", create?" new":"", + findTexture(current_material_index)->name, current_material_index); g_materials.materials[current_material_index] = current_material; g_materials.materials[current_material_index].set = true; diff --git a/ref/vk/vk_rmain.c b/ref/vk/vk_rmain.c index 4961910b1..37c3ef4f8 100644 --- a/ref/vk/vk_rmain.c +++ b/ref/vk/vk_rmain.c @@ -133,7 +133,9 @@ static qboolean Mod_ProcessRenderData( model_t *mod, qboolean create, const byte switch( mod->type ) { case mod_studio: - Mod_LoadStudioModel( mod, buffer, &loaded ); + // This call happens before we get R_NewMap, which frees all current buffers + // So we can't really load anything here + // TODO we might benefit a tiny bit (a few ms loading time) from reusing studio models from previous map break; case mod_sprite: Mod_LoadSpriteModel( mod, buffer, &loaded, mod->numtexinfo ); @@ -142,8 +144,8 @@ static qboolean Mod_ProcessRenderData( model_t *mod, qboolean create, const byte Mod_LoadAliasModel( mod, buffer, &loaded ); break; case mod_brush: - // FIXME this happens before we get R_NewMap, which frees all current buffers - // loaded = VK_LoadBrushModel( mod, buffer ); + // This call happens before we get R_NewMap, which frees all current buffers + // So we can't really load anything here break; default: gEngine.Host_Error( "Mod_LoadModel: unsupported type %d\n", mod->type ); } diff --git a/ref/vk/vk_scene.c b/ref/vk/vk_scene.c index 959ad7819..f2e6ba937 100644 --- a/ref/vk/vk_scene.c +++ b/ref/vk/vk_scene.c @@ -19,6 +19,7 @@ #include "camera.h" #include "vk_mapents.h" #include "profiler.h" +#include "vk_entity_data.h" #include "com_strings.h" #include "ref_params.h" @@ -85,6 +86,7 @@ static void loadLights( const model_t *const map ) { // Clears all old map data static void mapLoadBegin( const model_t *const map ) { + VK_EntityDataClear(); R_StudioCacheClear(); R_GeometryBuffer_MapClear(); @@ -104,7 +106,7 @@ static void mapLoadEnd(const model_t *const map) { VK_UploadLightmap(); } -static void loadBrushModels( void ) { +static void preloadModels( void ) { const int num_models = gEngine.EngineGetParm( PARM_NUMMODELS, 0 ); // Load all models at once @@ -117,11 +119,20 @@ static void loadBrushModels( void ) { gEngine.Con_Reportf( " %d: name=%s, type=%d, submodels=%d, nodes=%d, surfaces=%d, nummodelsurfaces=%d\n", i, m->name, m->type, m->numsubmodels, m->numnodes, m->numsurfaces, m->nummodelsurfaces); - if( m->type != mod_brush ) - continue; + switch (m->type) { + case mod_brush: + if (!VK_BrushModelLoad(m)) + gEngine.Host_Error( "Couldn't load brush model %s\n", m->name ); + break; + + case mod_studio: + if (!R_StudioModelPreload(m)) + gEngine.Host_Error( "Couldn't preload studio model %s\n", m->name ); + break; - if (!VK_BrushModelLoad(m)) - gEngine.Host_Error( "Couldn't load model %s\n", m->name ); + default: + break; + } } } @@ -140,7 +151,7 @@ static void loadMap(const model_t* const map) { // Depends on loaded materials. Must preceed loading brush models. XVK_ParseMapPatches(); - loadBrushModels(); + preloadModels(); loadLights(map); mapLoadEnd(map); @@ -234,6 +245,8 @@ void R_NewMap( void ) { XVK_SetupSky( gEngine.pfnGetMoveVars()->skyName ); loadMap(map); + + R_StudioResetPlayerModels(); } qboolean R_AddEntity( struct cl_entity_s *clent, int type ) diff --git a/ref/vk/vk_studio.c b/ref/vk/vk_studio.c index dd4a59d24..6e4a97f12 100644 --- a/ref/vk/vk_studio.c +++ b/ref/vk/vk_studio.c @@ -10,6 +10,8 @@ #include "vk_cvar.h" #include "camera.h" #include "r_speeds.h" +#include "vk_studio_model.h" +#include "vk_entity_data.h" #include "xash3d_mathlib.h" #include "const.h" @@ -79,7 +81,6 @@ typedef struct vec3_t verts[MAXSTUDIOVERTS]; vec3_t norms[MAXSTUDIOVERTS]; vec3_t tangents[MAXSTUDIOVERTS]; - vec3_t prev_verts[MAXSTUDIOVERTS]; // last frame state for motion vectors // lighting state @@ -112,10 +113,6 @@ typedef struct // playermodels player_model_t player_models[MAX_CLIENTS]; - - // drawelements renderer - uint numverts; - uint numelems; } studio_draw_state_t; // studio-related cvars @@ -140,7 +137,7 @@ studiohdr_t *m_pStudioHeader; float m_flGaitMovement; int g_iBackFaceCull; int g_nTopColor, g_nBottomColor; // remap colors -int g_nFaceFlags, g_nForceFaceFlags; +int g_nForceFaceFlags; // FIXME VK this should be promoted to somewhere global-ish, and done properly // For now it's just a hack to get studio models to compile basically @@ -150,73 +147,10 @@ static struct { model_t *currentmodel; } RI; -typedef struct { - const mstudiomodel_t *key_submodel; - - // Non-NULL for animated instances - const cl_entity_t *key_entity; - - vk_render_model_t render_model; - r_geometry_range_t geometry_range; - vk_render_geometry_t *geometries; - int geometries_count; - int vertex_count, index_count; -} r_studio_model_cache_entry_t; - static struct { -#define MAX_CACHED_STUDIO_MODELS 1024 - // TODO proper map/hash table - r_studio_model_cache_entry_t entries[MAX_CACHED_STUDIO_MODELS]; - int entries_count; -} g_studio_cache; - -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); - R_RenderModelDestroy(&entry->render_model); - R_GeometryRangeFree(&entry->geometry_range); - Mem_Free(entry->geometries); - entry->key_submodel = 0; - entry->key_entity = NULL; - entry->geometries = NULL; - entry->geometries_count = 0; - entry->vertex_count = 0; - entry->index_count = 0; - } - - g_studio_cache.entries_count = 0; -} - -static 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; -} - -void R_StudioInit( void ) -{ - Matrix3x4_LoadIdentity( g_studio.rotationmatrix ); - - // g-cont. cvar disabled by Valve -// gEngine.Cvar_RegisterVariable( &r_shadows ); - - g_studio.interpolate = true; - g_studio.framecount = 0; - m_fDoRemap = false; - - R_SPEEDS_COUNTER(g_studio_stats.models_count, "models", kSpeedsMetricCount); - R_SPEEDS_COUNTER(g_studio_stats.submodels_total, "submodels_total", kSpeedsMetricCount); - R_SPEEDS_COUNTER(g_studio_stats.submodels_static, "submodels_static", kSpeedsMetricCount); - R_SPEEDS_COUNTER(g_studio_stats.submodels_dynamic, "submodels_dynamic", kSpeedsMetricCount); - - R_SPEEDS_METRIC(g_studio_cache.entries_count, "cached_submodels", kSpeedsMetricCount); -} + r_studio_entity_model_t *entmodel; + int bodypart_index; +} g_studio_current; /* ================ @@ -412,126 +346,12 @@ void R_StudioComputeSkinMatrix( const mstudioboneweight_t *boneweights, matrix3x } } -/* -=============== -pfnGetCurrentEntity - -=============== -*/ -static cl_entity_t *pfnGetCurrentEntity( void ) -{ - return RI.currententity; -} - -/* -=============== -pfnPlayerInfo - -=============== -*/ -player_info_t *pfnPlayerInfo( int index ) -{ - if( !RI.drawWorld ) - index = -1; - - return gEngine.pfnPlayerInfo( index ); -} - -/* -=============== -pfnMod_ForName - -=============== -*/ -static model_t *pfnMod_ForName( const char *model, int crash ) -{ - return gEngine.Mod_ForName( model, crash, false ); -} - -/* -=============== -pfnGetPlayerState - -=============== -*/ -entity_state_t *R_StudioGetPlayerState( int index ) -{ - if( !RI.drawWorld ) - return &RI.currententity->curstate; - - return gEngine.pfnGetPlayerState( index ); -} - -/* -=============== -pfnGetViewEntity - -=============== -*/ -static cl_entity_t *pfnGetViewEntity( void ) -{ - return gEngine.GetViewModel(); -} - -static void pfnGetEngineTimes( int *framecount, double *current, double *old ) -{ - /* FIXME VK NOT IMPLEMENTED */ - /* if( framecount ) *framecount = tr.realframecount; */ - if( framecount ) *framecount = 0; - if( current ) *current = gpGlobals->time; - if( old ) *old = gpGlobals->oldtime; -} - -static void pfnGetViewInfo( float *origin, float *upv, float *rightv, float *forwardv ) -{ - if( origin ) VectorCopy( g_camera.vieworg, origin ); - if( forwardv ) VectorCopy( g_camera.vforward, forwardv ); - if( rightv ) VectorCopy( g_camera.vright, rightv ); - if( upv ) VectorCopy( g_camera.vup, upv ); -} static model_t *R_GetChromeSprite( void ) { return gEngine.GetDefaultSprite( REF_CHROME_SPRITE ); } -static int fixme_studio_models_drawn; - -static void pfnGetModelCounters( int **s, int **a ) -{ - *s = &g_studio.framecount; - - /* FIXME VK NOT IMPLEMENTED */ - /* *a = &r_stats.c_studio_models_drawn; */ - *a = &fixme_studio_models_drawn; -} - -static void pfnGetAliasScale( float *x, float *y ) -{ - if( x ) *x = 1.0f; - if( y ) *y = 1.0f; -} - -static float ****pfnStudioGetBoneTransform( void ) -{ - return (float ****)g_studio.bonestransform; -} - -static float ****pfnStudioGetLightTransform( void ) -{ - return (float ****)g_studio.lighttransform; -} - -static float ***pfnStudioGetAliasTransform( void ) -{ - return NULL; -} - -static float ***pfnStudioGetRotationMatrix( void ) -{ - return (float ***)g_studio.rotationmatrix; -} - void R_StudioPlayerBlend( mstudioseqdesc_t *pseqdesc, int *pBlend, float *pPitch ) { // calc up/down pointing @@ -998,12 +818,6 @@ void R_StudioSetupBones( cl_entity_t *e ) } } -/* -==================== -StudioSaveBones - -==================== -*/ static void R_StudioSaveBones( void ) { mstudiobone_t *pbones; @@ -1200,12 +1014,6 @@ void R_StudioGenerateNormals( void ) } } -/* -==================== -StudioSetupChrome - -==================== -*/ void R_StudioSetupChrome( float *pchrome, int bone, vec3_t normal ) { float n; @@ -1243,12 +1051,6 @@ void R_StudioSetupChrome( float *pchrome, int bone, vec3_t normal ) pchrome[1] = (n + 1.0f) * 32.0f; } -/* -==================== -StudioCalcAttachments - -==================== -*/ static void R_StudioCalcAttachments( void ) { mstudioattachment_t *pAtt; @@ -1263,12 +1065,6 @@ static void R_StudioCalcAttachments( void ) } } -/* -=============== -pfnStudioSetupModel - -=============== -*/ static void R_StudioSetupModel( int bodypart, void **ppbodypart, void **ppsubmodel ) { int index; @@ -1276,6 +1072,8 @@ static void R_StudioSetupModel( int bodypart, void **ppbodypart, void **ppsubmod if( bodypart > m_pStudioHeader->numbodyparts ) bodypart = 0; + g_studio_current.bodypart_index = bodypart; + m_pBodyPart = (mstudiobodyparts_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bodypartindex) + bodypart; index = RI.currententity->curstate.body / m_pBodyPart->base; @@ -1287,12 +1085,6 @@ static void R_StudioSetupModel( int bodypart, void **ppbodypart, void **ppsubmod if( ppsubmodel ) *ppsubmodel = m_pSubModel; } -/* -=============== -R_StudioCheckBBox - -=============== -*/ static int R_StudioCheckBBox( void ) { if( !RI.currententity || !RI.currentmodel ) @@ -1301,12 +1093,6 @@ static int R_StudioCheckBBox( void ) return R_StudioComputeBBox( NULL ); } -/* -=============== -R_StudioDynamicLight - -=============== -*/ void R_StudioDynamicLight( cl_entity_t *ent, alight_t *plight ) { movevars_t *mv = gEngine.pfnGetMoveVars(); @@ -1885,6 +1671,7 @@ typedef struct { const vec3_t *pstudionorms; float s, t; int texture; + int face_flags; uint32_t vertices_offset; uint32_t indices_offset; @@ -1898,8 +1685,7 @@ typedef struct { } build_submodel_mesh_t; static void buildSubmodelMeshGeometry( build_submodel_mesh_t args ) { - float *lv; - int i; + int i; uint32_t vertex_offset = 0, index_offset = 0; int num_vertices = 0, num_indices = 0; @@ -1919,15 +1705,16 @@ static void buildSubmodelMeshGeometry( build_submodel_mesh_t args ) { for(int j = 0; j < vertices ; ++j, ++dst_vtx, args.ptricmds += 4 ) { + const int vi = args.ptricmds[0]; *dst_vtx = (vk_vertex_t){0}; - VectorCopy(g_studio.verts[args.ptricmds[0]], dst_vtx->pos); - VectorCopy(g_studio.prev_verts[args.ptricmds[0]], dst_vtx->prev_pos); - VectorCopy(g_studio.norms[args.ptricmds[0]], dst_vtx->normal); - VectorCopy(g_studio.tangents[args.ptricmds[0]], dst_vtx->tangent); + VectorCopy(g_studio.verts[vi], dst_vtx->pos); + VectorCopy(g_studio.prev_verts[vi], dst_vtx->prev_pos); + VectorCopy(g_studio.norms[vi], dst_vtx->normal); + VectorCopy(g_studio.tangents[vi], dst_vtx->tangent); dst_vtx->lm_tc[0] = dst_vtx->lm_tc[1] = 0.f; - if (FBitSet( g_nFaceFlags, STUDIO_NF_CHROME )) + if (FBitSet( args.face_flags, STUDIO_NF_CHROME )) { // FIXME also support glow mode const int idx = args.ptricmds[1]; @@ -1979,9 +1766,8 @@ static void buildSubmodelMeshGeometry( build_submodel_mesh_t args ) { ASSERT(vertex_offset == num_vertices); *args.out_geometry = (vk_render_geometry_t){ - //.lightmap = tglob.whiteTexture, .texture = args.texture, - .material = FBitSet( g_nFaceFlags, STUDIO_NF_CHROME ) ? kXVkMaterialChrome : kXVkMaterialRegular, + .material = FBitSet( args.face_flags, STUDIO_NF_CHROME ) ? kXVkMaterialChrome : kXVkMaterialRegular, .vertex_offset = args.vertices_offset, .max_vertex = num_vertices, @@ -2084,23 +1870,19 @@ static vk_render_type_e studioRenderModeToRenderType( int render_mode ) { } typedef struct { - const mstudiomodel_t *submodel; + //const mstudiomodel_t *submodel; const r_geometry_range_t *geometry; vk_render_geometry_t *geometries; int vertex_count, index_count; } build_submodel_geometry_t; -static qboolean buildStudioSubmodelGeometry(build_submodel_geometry_t args) { +static void buildStudioSubmodelGeometry(build_submodel_geometry_t args) { // FIXME: do not reference global things like RI.* m_pStudio* here, pass everything by args const r_geometry_range_lock_t geom_lock = R_GeometryRangeLock(args.geometry); - if (!geom_lock.vertices) { - gEngine.Con_Printf(S_ERROR "Unable to lock staging for %d vertices %d indices for submodel %s", - args.vertex_count, args.index_count, args.submodel->name); - R_GeometryRangeFree(args.geometry); - return false; - } + ASSERT(geom_lock.vertices); + ASSERT(geom_lock.indices); - g_studio.numverts = g_studio.numelems = 0; + // FIXME VK entity->curstate.skin can potentially be animated // safety bounding the skinnum const int m_skinnum = bound( 0, RI.currententity->curstate.skin, ( m_pStudioHeader->numskinfamilies - 1 )); @@ -2186,13 +1968,14 @@ static qboolean buildStudioSubmodelGeometry(build_submodel_geometry_t args) { qboolean need_sort = false; for( int j = 0, k = 0; j < m_pSubModel->nummesh; j++ ) { - g_nFaceFlags = ptexture[pskinref[pmesh[j].skinref]].flags | g_nForceFaceFlags; + const int face_flags = ptexture[pskinref[pmesh[j].skinref]].flags | g_nForceFaceFlags; // fill in sortedmesh info - g_studio.meshes[j].flags = g_nFaceFlags; + g_studio.meshes[j].flags = face_flags; g_studio.meshes[j].mesh = &pmesh[j]; - if( FBitSet( g_nFaceFlags, STUDIO_NF_MASKED|STUDIO_NF_ADDITIVE )) + // FIXME VK cannot into "dynamic" blending/alpha-test + if( FBitSet( face_flags, STUDIO_NF_MASKED|STUDIO_NF_ADDITIVE )) need_sort = true; if( RI.currententity->curstate.rendermode == kRenderTransAdd ) @@ -2201,7 +1984,7 @@ static qboolean buildStudioSubmodelGeometry(build_submodel_geometry_t args) { { // FIXME VK const struct { float blend; } tr = {1.f}; - if( FBitSet( g_nFaceFlags, STUDIO_NF_CHROME )) + if( FBitSet( face_flags, STUDIO_NF_CHROME )) R_StudioSetupChrome( g_studio.chrome[k], *pnormbone, (float *)pstudionorms ); VectorSet( g_studio.lightvalues[k], g_studio.blend, g_studio.blend, g_studio.blend ); } @@ -2211,10 +1994,10 @@ static qboolean buildStudioSubmodelGeometry(build_submodel_geometry_t args) { for( int i = 0; i < pmesh[j].numnorms; i++, k++, pstudionorms++, pnormbone++ ) { float lv_tmp; if( FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BONEWEIGHTS )) - R_StudioLighting( &lv_tmp, -1, g_nFaceFlags, g_studio.norms[k] ); - else R_StudioLighting( &lv_tmp, *pnormbone, g_nFaceFlags, (float *)pstudionorms ); + R_StudioLighting( &lv_tmp, -1, face_flags, g_studio.norms[k] ); + else R_StudioLighting( &lv_tmp, *pnormbone, face_flags, (float *)pstudionorms ); - if( FBitSet( g_nFaceFlags, STUDIO_NF_CHROME )) + if( FBitSet( face_flags, STUDIO_NF_CHROME )) R_StudioSetupChrome( g_studio.chrome[k], *pnormbone, (float *)pstudionorms ); VectorScale( g_studio.lightcolor, lv_tmp, g_studio.lightvalues[k] ); } @@ -2238,14 +2021,14 @@ static qboolean buildStudioSubmodelGeometry(build_submodel_geometry_t args) { const mstudiomesh_t *const pmesh = g_studio.meshes[j].mesh; const short *const ptricmds = (short *)((byte *)m_pStudioHeader + pmesh->triindex); - g_nFaceFlags = ptexture[pskinref[pmesh->skinref]].flags | g_nForceFaceFlags; + const int face_flags = ptexture[pskinref[pmesh->skinref]].flags | g_nForceFaceFlags; const float s = 1.0f / (float)ptexture[pskinref[pmesh->skinref]].width; const float t = 1.0f / (float)ptexture[pskinref[pmesh->skinref]].height; /* FIXME VK const float oldblend = g_studio.blend; - if( FBitSet( g_nFaceFlags, STUDIO_NF_MASKED )) + if( FBitSet( face_flags, STUDIO_NF_MASKED )) { pglEnable( GL_ALPHA_TEST ); pglAlphaFunc( GL_GREATER, 0.5f ); @@ -2253,7 +2036,7 @@ static qboolean buildStudioSubmodelGeometry(build_submodel_geometry_t args) { if( R_ModelOpaque( RI.currententity->curstate.rendermode )) g_studio.blend = 1.0f; } - else if( FBitSet( g_nFaceFlags, STUDIO_NF_ADDITIVE )) + else if( FBitSet( face_flags, STUDIO_NF_ADDITIVE )) { if( R_ModelOpaque( RI.currententity->curstate.rendermode )) { @@ -2268,9 +2051,9 @@ static qboolean buildStudioSubmodelGeometry(build_submodel_geometry_t args) { const int texture = R_StudioSetupSkin( m_pStudioHeader, pskinref[pmesh->skinref] ); - /* FIXME VK if( FBitSet( g_nFaceFlags, STUDIO_NF_CHROME )) + /* FIXME VK if( FBitSet( face_flags, STUDIO_NF_CHROME )) R_StudioDrawChromeMesh( ptricmds, pstudionorms, s, t, shellscale ); - else if( FBitSet( g_nFaceFlags, STUDIO_NF_UV_COORDS )) + else if( FBitSet( face_flags, STUDIO_NF_UV_COORDS )) R_StudioDrawFloatMesh( ptricmds, pstudionorms ); else*/ @@ -2280,6 +2063,7 @@ static qboolean buildStudioSubmodelGeometry(build_submodel_geometry_t args) { .s = s, .t = t, .texture = texture, + .face_flags = face_flags, .vertices_offset = args.geometry->vertices.unit_offset + vertices_offset, .indices_offset = args.geometry->indices.unit_offset + indices_offset, .dst_vertices = geom_lock.vertices + vertices_offset, @@ -2293,12 +2077,12 @@ static qboolean buildStudioSubmodelGeometry(build_submodel_geometry_t args) { ASSERT(indices_offset <= args.index_count); /* FIXME VK - if( FBitSet( g_nFaceFlags, STUDIO_NF_MASKED )) + if( FBitSet( face_flags, STUDIO_NF_MASKED )) { pglAlphaFunc( GL_GREATER, DEFAULT_ALPHATEST ); pglDisable( GL_ALPHA_TEST ); } - else if( FBitSet( g_nFaceFlags, STUDIO_NF_ADDITIVE ) && R_ModelOpaque( RI.currententity->curstate.rendermode )) + else if( FBitSet( face_flags, STUDIO_NF_ADDITIVE ) && R_ModelOpaque( RI.currententity->curstate.rendermode )) { pglDepthMask( GL_TRUE ); pglDisable( GL_BLEND ); @@ -2311,81 +2095,50 @@ static qboolean buildStudioSubmodelGeometry(build_submodel_geometry_t args) { } R_GeometryRangeUnlock(&geom_lock); - return true; -} - -static qboolean isStudioModelDynamic(const studiohdr_t *hdr) { - gEngine.Con_Reportf("Studio model %s, sequences = %d:\n", hdr->name, hdr->numseq); - if (hdr->numseq == 0) - return false; - - for (int i = 0; i < hdr->numseq; ++i) { - const mstudioseqdesc_t *const pseqdesc = (mstudioseqdesc_t *)((byte *)hdr + hdr->seqindex) + i; - gEngine.Con_Reportf(" %d: fps=%f numframes=%d\n", i, pseqdesc->fps, pseqdesc->numframes); - } - - // This is rather conservative. - // TODO We might be able to cache: - // - individual sequences w/o animation frames - // - individual submodels that are not affected by any sequences or animations - const mstudioseqdesc_t *const pseqdesc = (mstudioseqdesc_t *)((byte *)hdr + hdr->seqindex) + 0; - return hdr->numseq > 1 || (pseqdesc->fps > 0 && pseqdesc->numframes > 1); } -static const r_studio_model_cache_entry_t *buildCachedStudioSubModel( const mstudiomodel_t *submodel, const cl_entity_t *ent ) { - if (g_studio_cache.entries_count == MAX_CACHED_STUDIO_MODELS) { - PRINT_NOT_IMPLEMENTED_ARGS("Studio submodel cache overflow at %d", MAX_CACHED_STUDIO_MODELS); - return NULL; - } - - const mstudiomesh_t *const pmesh = (mstudiomesh_t *)((byte *)m_pStudioHeader + m_pSubModel->meshindex); - +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; - for( int i = 0; i < submodel->nummesh; i++ ) { - const short* const ptricmds = (short *)((byte *)m_pStudioHeader + pmesh[i].triindex); - addVerticesIndicesCounts(ptricmds, &vertex_count, &index_count); - } + { + const mstudiomesh_t *const pmesh = (mstudiomesh_t *)((byte *)m_pStudioHeader + m_pSubModel->meshindex); + for(int i = 0; i < submodel->nummesh; i++) { + const short* const ptricmds = (short *)((byte *)m_pStudioHeader + pmesh[i].triindex); + addVerticesIndicesCounts(ptricmds, &vertex_count, &index_count); + } - ASSERT(vertex_count > 0); - ASSERT(index_count > 0); + ASSERT(vertex_count > 0); + ASSERT(index_count > 0); + } + // TODO can be coalesced into a single allocation for the entire model const r_geometry_range_t geometry = R_GeometryRangeAlloc(vertex_count, index_count); if (geometry.block_handle.size == 0) { gEngine.Con_Printf(S_ERROR "Unable to allocate %d vertices %d indices for submodel %s\n", vertex_count, index_count, submodel->name); - return NULL; + return false; } + // TODO can be coalesced vk_render_geometry_t *const geometries = Mem_Malloc(vk_core.pool, submodel->nummesh * sizeof(*geometries)); ASSERT(geometries); - if (!buildStudioSubmodelGeometry((build_submodel_geometry_t){ - .submodel = submodel, + buildStudioSubmodelGeometry((build_submodel_geometry_t){ + //.submodel = submodel, .geometry = &geometry, .geometries = geometries, .vertex_count = vertex_count, .index_count = index_count, - })) { - gEngine.Con_Printf(S_ERROR "Unable to build geometry for submodel %s", submodel->name); - // FIXME leaks - return NULL; - } - - const qboolean is_dynamic = isStudioModelDynamic(m_pStudioHeader); - - r_studio_model_cache_entry_t *const entry = g_studio_cache.entries + g_studio_cache.entries_count; + }); - *entry = (r_studio_model_cache_entry_t){ - .key_submodel = submodel, - .key_entity = is_dynamic ? ent : NULL, - .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( &entry->render_model, (vk_render_model_init_t){ + if (!R_RenderModelCreate(&render_submodel->model, (vk_render_model_init_t){ .name = submodel->name, .geometries = geometries, .geometries_count = submodel->nummesh, @@ -2395,31 +2148,23 @@ static const r_studio_model_cache_entry_t *buildCachedStudioSubModel( const mstu Mem_Free(geometries); // FIXME everything else leaks ;_; // FIXME sync up with staging and free - return NULL; + memset(render_submodel, 0, sizeof(*render_submodel)); + return false; } - ++g_studio_cache.entries_count; - - return entry; + return true; } -static void updateCachedStudioSubModel(const r_studio_model_cache_entry_t *entry) { - if (!buildStudioSubmodelGeometry((build_submodel_geometry_t){ - .submodel = entry->key_submodel, - .geometry = &entry->geometry_range, - .geometries = entry->geometries, - .vertex_count = entry->vertex_count, - .index_count = entry->index_count, - })) { - gEngine.Con_Printf(S_ERROR "Unable to build geometry for submodel %s", entry->key_submodel->name); - return; - } - - if (!R_RenderModelUpdate(&entry->render_model)) { - gEngine.Con_Printf(S_ERROR "Unable to update render model for submodel %s", entry->key_submodel->name); - } +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, + .geometries = submodel_render->geometries, + .vertex_count = submodel_render->vertex_count, + .index_count = submodel_render->index_count, + }); - ++g_studio_stats.submodels_dynamic; + return R_RenderModelUpdate(&submodel_render->model); } /* TODO @@ -2438,36 +2183,133 @@ static void updateCachedStudioSubModel(const r_studio_model_cache_entry_t *entry * - dynamic: per-entity */ +static void studioEntityModelDestroy(void *userdata) { + r_studio_entity_model_t *entmodel = (r_studio_entity_model_t*)userdata; + 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->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); + + entmodel->model_info = getStudioModelInfo(entity->model); + ASSERT(entmodel->model_info); + + return entmodel; +} + +static r_studio_entity_model_t *studioEntityModelGet(const cl_entity_t* entity) { + r_studio_entity_model_t *entmodel = (r_studio_entity_model_t*)VK_EntityDataGet(entity); + if (entmodel && entmodel->studio_header == m_pStudioHeader) + return entmodel; + + entmodel = studioEntityModelCreate(entity); + if (!entmodel) { + gEngine.Con_Printf(S_ERROR "Cannot create studio entity model for %s\n", entity->model->name); + return NULL; + } + + 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 r_studio_submodel_info_t *studioModelFindSubmodelInfo(void) { + for (int i = 0; i < g_studio_current.entmodel->model_info->submodels_count; ++i) { + r_studio_submodel_info_t *const subinfo = g_studio_current.entmodel->model_info->submodels + i; + if (subinfo->submodel_key == m_pSubModel) + return subinfo; + } + + return NULL; +} + +// Draws current studio model submodel +// Can be called externally, i.e. from game dll. +// Expects m_pStudioHeader, m_pSubModel, RI.currententity, etc to be already set up static void R_StudioDrawPoints( void ) { if( !m_pStudioHeader || !m_pSubModel || !m_pSubModel->nummesh) return; - const r_studio_model_cache_entry_t *cached_model = findSubModelInCacheForEntity( m_pSubModel, RI.currententity ); + ASSERT(g_studio_current.bodypart_index >= 0); + + // Ideally, this "get current entity and model" stuff should happen early, when we're just starting to + // draw this entity/model. However, call structure/graph is a bit weird: we start rendering in ref code, + // but relevant states (transform, various headers) are updated only later, and potentially in game dll code. + // So we're forced to do this later here, when it is guaranteed that all the relevant state has been set. + if (!g_studio_current.entmodel) + g_studio_current.entmodel = studioEntityModelGet(RI.currententity); - if (!cached_model) - cached_model = buildCachedStudioSubModel(m_pSubModel, RI.currententity); - else if (cached_model->key_entity) { - updateCachedStudioSubModel(cached_model); + ASSERT(g_studio_current.bodypart_index >= 0); + ASSERT(g_studio_current.bodypart_index < g_studio_current.entmodel->bodyparts_count); + + 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) { + // This does happen in practice a lot. Shouldn't be a warning. + // 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); } - if (!cached_model) - return; + const qboolean is_dynamic = render_submodel->_.info->is_dynamic; - if (!cached_model->key_entity) { - ++g_studio_stats.submodels_static; + if (!render_submodel->geometries) { + if (!studioSubmodelRenderInit(render_submodel, m_pSubModel, is_dynamic)) { + gEngine.Con_Printf(S_ERROR "Unable to init studio submodel for %s/%d\n", RI.currentmodel->name, g_studio_current.bodypart_index); + return; + } + + 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); + return; + } } + if (is_dynamic) + ++g_studio_stats.submodels_dynamic; + else + ++g_studio_stats.submodels_static; + vec4_t color = {1, 1, 1, g_studio.blend}; - if (g_studio.rendermode2 == kRenderTransAdd) { + if (g_studio.rendermode2 == kRenderTransAdd) Vector4Set(color, g_studio.blend, g_studio.blend, g_studio.blend, 1.f); - } // TODO r_model_draw_t.transform should be matrix3x4 matrix4x4 transform; Matrix4x4_LoadIdentity(transform); Matrix3x4_Copy(transform, g_studio.rotationmatrix); - R_RenderModelDraw(&cached_model->render_model, (r_model_draw_t){ - .render_type = RI.currententity->curstate.rendermode, + + R_RenderModelDraw(&render_submodel->model, (r_model_draw_t){ + .render_type = studioRenderModeToRenderType(RI.currententity->curstate.rendermode), .color = &color, .transform = &transform, .prev_transform = /* FIXME */ &transform, @@ -2495,6 +2337,8 @@ void R_StudioResetPlayerModels( void ) memset( g_studio.player_models, 0, sizeof( g_studio.player_models )); } +static player_info_t *pfnPlayerInfo( int index ); + static model_t *R_StudioSetupPlayerModel( int index ) { player_info_t *info = gEngine.pfnPlayerInfo( index ); @@ -2648,57 +2492,27 @@ static void R_StudioClientEvents( void ) } } -/* -=============== -R_StudioGetForceFaceFlags - -=============== -*/ int R_StudioGetForceFaceFlags( void ) { return g_nForceFaceFlags; } -/* -=============== -R_StudioSetForceFaceFlags - -=============== -*/ void R_StudioSetForceFaceFlags( int flags ) { g_nForceFaceFlags = flags; } -/* -=============== -pfnStudioSetHeader - -=============== -*/ void R_StudioSetHeader( studiohdr_t *pheader ) { m_pStudioHeader = pheader; m_fDoRemap = false; } -/* -=============== -R_StudioSetRenderModel - -=============== -*/ void R_StudioSetRenderModel( model_t *model ) { RI.currentmodel = model; } -/* -=============== -R_StudioSetupRenderer - -=============== -*/ static void R_StudioSetupRenderer( int rendermode ) { studiohdr_t *phdr = m_pStudioHeader; @@ -3154,6 +2968,8 @@ static int R_StudioDrawPlayer( int flags, entity_state_t *pplayer ) return 1; } +static entity_state_t *R_StudioGetPlayerState( int index ); + static int R_StudioDrawModel( int flags ) { alight_t lighting; @@ -3249,22 +3065,32 @@ static void R_StudioDrawModelInternal( cl_entity_t *e, int flags ) { VK_RenderDebugLabelBegin( e->model->name ); + // Mark this a new model to draw + g_studio_current.entmodel = NULL; + g_studio_current.bodypart_index = -1; + ++g_studio_stats.models_count; if( !RI.drawWorld ) { if( e->player ) R_StudioDrawPlayer( flags, &e->curstate ); - else R_StudioDrawModel( flags ); + else + R_StudioDrawModel( flags ); } else { // select the properly method if( e->player ) pStudioDraw->StudioDrawPlayer( flags, R_StudioGetPlayerState( e->index - 1 )); - else pStudioDraw->StudioDrawModel( flags ); + else + pStudioDraw->StudioDrawModel( flags ); } + // Reset current state, no drawing should happen outside of this function + g_studio_current.entmodel = NULL; + g_studio_current.bodypart_index = -1; + VK_RenderDebugLabelEnd(); } @@ -3552,6 +3378,54 @@ void Mod_StudioUnloadTextures( void *data ) } } +static cl_entity_t *pfnGetCurrentEntity( void ) +{ + return RI.currententity; +} + +static player_info_t *pfnPlayerInfo( int index ) +{ + if( !RI.drawWorld ) + index = -1; + + return gEngine.pfnPlayerInfo( index ); +} + +static model_t *pfnMod_ForName( const char *model, int crash ) +{ + return gEngine.Mod_ForName( model, crash, false ); +} + +static entity_state_t *R_StudioGetPlayerState( int index ) +{ + if( !RI.drawWorld ) + return &RI.currententity->curstate; + + return gEngine.pfnGetPlayerState( index ); +} + +static cl_entity_t *pfnGetViewEntity( void ) +{ + return gEngine.GetViewModel(); +} + +static void pfnGetEngineTimes( int *framecount, double *current, double *old ) +{ + /* FIXME VK NOT IMPLEMENTED */ + /* if( framecount ) *framecount = tr.realframecount; */ + if( framecount ) *framecount = 0; + if( current ) *current = gpGlobals->time; + if( old ) *old = gpGlobals->oldtime; +} + +static void pfnGetViewInfo( float *origin, float *upv, float *rightv, float *forwardv ) +{ + if( origin ) VectorCopy( g_camera.vieworg, origin ); + if( forwardv ) VectorCopy( g_camera.vforward, forwardv ); + if( rightv ) VectorCopy( g_camera.vright, rightv ); + if( upv ) VectorCopy( g_camera.vup, upv ); +} + static model_t *pfnModelHandle( int modelindex ) { return gEngine.pfnGetModelByIndex( modelindex ); @@ -3597,6 +3471,43 @@ static void R_StudioDrawBones( void ) PRINT_NOT_IMPLEMENTED(); } +static int fixme_studio_models_drawn; + +static void pfnGetModelCounters( int **s, int **a ) +{ + *s = &g_studio.framecount; + + /* FIXME VK NOT IMPLEMENTED */ + /* *a = &r_stats.c_studio_models_drawn; */ + *a = &fixme_studio_models_drawn; +} + +static void pfnGetAliasScale( float *x, float *y ) +{ + if( x ) *x = 1.0f; + if( y ) *y = 1.0f; +} + +static float ****pfnStudioGetBoneTransform( void ) +{ + return (float ****)g_studio.bonestransform; +} + +static float ****pfnStudioGetLightTransform( void ) +{ + return (float ****)g_studio.lighttransform; +} + +static float ***pfnStudioGetAliasTransform( void ) +{ + return NULL; +} + +static float ***pfnStudioGetRotationMatrix( void ) +{ + return (float ***)g_studio.rotationmatrix; +} + static engine_studio_api_t gStudioAPI = { pfnMod_Calloc, @@ -3676,7 +3587,21 @@ void CL_InitStudioAPI( void ) void VK_StudioInit( void ) { - R_StudioInit(); + Matrix3x4_LoadIdentity( g_studio.rotationmatrix ); + + // g-cont. cvar disabled by Valve +// gEngine.Cvar_RegisterVariable( &r_shadows ); + + g_studio.interpolate = true; + g_studio.framecount = 0; + m_fDoRemap = false; + + R_SPEEDS_COUNTER(g_studio_stats.models_count, "models", kSpeedsMetricCount); + R_SPEEDS_COUNTER(g_studio_stats.submodels_total, "submodels_total", kSpeedsMetricCount); + R_SPEEDS_COUNTER(g_studio_stats.submodels_static, "submodels_static", kSpeedsMetricCount); + R_SPEEDS_COUNTER(g_studio_stats.submodels_dynamic, "submodels_dynamic", kSpeedsMetricCount); + + VK_StudioModelInit(); } void VK_StudioShutdown( void ) @@ -3684,11 +3609,6 @@ void VK_StudioShutdown( void ) R_StudioCacheClear(); } -void Mod_LoadStudioModel( model_t *mod, const void *buffer, qboolean *loaded ) -{ - PRINT_NOT_IMPLEMENTED_ARGS("(%s)", mod->name); -} - void VK_StudioDrawModel( cl_entity_t *ent, int render_mode, float blend ) { RI.currententity = ent; diff --git a/ref/vk/vk_studio.h b/ref/vk/vk_studio.h index 90adcf18a..ab3673511 100644 --- a/ref/vk/vk_studio.h +++ b/ref/vk/vk_studio.h @@ -9,7 +9,6 @@ struct model_s; void VK_StudioInit( void ); void VK_StudioShutdown( void ); -void Mod_LoadStudioModel( model_t *mod, const void *buffer, qboolean *loaded ); void Mod_StudioLoadTextures( model_t *mod, void *data ); void VK_StudioDrawModel( cl_entity_t *ent, int render_mode, float blend ); @@ -22,4 +21,8 @@ void CL_InitStudioAPI( void ); float R_StudioEstimateFrame( cl_entity_t *e, mstudioseqdesc_t *pseqdesc, double time ); void R_StudioLerpMovement( cl_entity_t *e, double time, vec3_t origin, vec3_t angles ); +qboolean R_StudioModelPreload(model_t *mod); + void R_StudioCacheClear( void ); + +void R_StudioResetPlayerModels( void ); diff --git a/ref/vk/vk_studio_model.c b/ref/vk/vk_studio_model.c new file mode 100644 index 000000000..a9fa9e4f0 --- /dev/null +++ b/ref/vk/vk_studio_model.c @@ -0,0 +1,262 @@ +#include "vk_studio_model.h" +#include "r_speeds.h" +#include "vk_entity_data.h" + +#include "xash3d_mathlib.h" + +#define MODULE_NAME "studio" + +typedef struct { + const studiohdr_t *studio_header_key; + r_studio_model_info_t info; +} r_studio_model_info_entry_t; + +static struct { +#define MAX_STUDIO_MODELS 256 + r_studio_model_info_entry_t models[MAX_STUDIO_MODELS]; + int models_count; + + int submodels_cached_dynamic; + int submodels_cached_static; +} g_studio_cache; + +void studioRenderSubmodelDestroy( r_studio_submodel_render_t *submodel ) { + R_RenderModelDestroy(&submodel->model); + R_GeometryRangeFree(&submodel->geometry_range); + if (submodel->geometries) + Mem_Free(submodel->geometries); +} + +static void studioSubmodelInfoDestroy(r_studio_submodel_info_t *subinfo) { + // Not zero means that something still holds a cached render submodel instance somewhere + ASSERT(subinfo->render_refcount == 0); + + 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.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; + + g_studio_cache.submodels_cached_dynamic = g_studio_cache.submodels_cached_static = 0; +} + +static struct { + vec4_t first_q[MAXSTUDIOBONES]; + float first_pos[MAXSTUDIOBONES][3]; + + vec4_t q[MAXSTUDIOBONES]; + float pos[MAXSTUDIOBONES][3]; +} gb; + +static void studioModelCalcBones(int numbones, const mstudiobone_t *pbone, const mstudioanim_t *panim, int frame, float out_pos[][3], vec4_t *out_q) { + for(int b = 0; b < numbones; b++ ) { + // TODO check pbone->bonecontroller, if the bone can be dynamically controlled by entity + float *const adj = NULL; + const float interpolation = 0; + R_StudioCalcBoneQuaternion( frame, interpolation, pbone + b, panim + b, adj, out_q[b] ); + R_StudioCalcBonePosition( frame, interpolation, pbone + b, panim + b, adj, out_pos[b] ); + } +} + +qboolean Vector4CompareEpsilon( const vec4_t vec1, const vec4_t vec2, vec_t epsilon ) +{ + vec_t ax, ay, az, aw; + + ax = fabs( vec1[0] - vec2[0] ); + ay = fabs( vec1[1] - vec2[1] ); + az = fabs( vec1[2] - vec2[2] ); + aw = fabs( vec1[3] - vec2[3] ); + + if(( ax <= epsilon ) && ( ay <= epsilon ) && ( az <= epsilon ) && ( aw <= epsilon )) + return true; + return false; +} + +static qboolean isBoneSame(int b) { + if (!Vector4CompareEpsilon(gb.first_q[b], gb.q[b], 1e-4f)) + return false; + + if (!VectorCompareEpsilon(gb.first_pos[b], gb.pos[b], 1e-4f)) + return false; + + return true; +} + +static void studioModelProcessBonesAnimations(const model_t *const model, const studiohdr_t *const hdr, r_studio_submodel_info_t *submodels, int submodels_count) { + for (int i = 0; i < hdr->numseq; ++i) { + const mstudioseqdesc_t *const pseqdesc = (mstudioseqdesc_t *)((byte *)hdr + hdr->seqindex) + i; + + const mstudiobone_t* const pbone = (mstudiobone_t *)((byte *)hdr + hdr->boneindex); + const mstudioanim_t* const panim = gEngine.R_StudioGetAnim( (studiohdr_t*)hdr, (model_t*)model, (mstudioseqdesc_t*)pseqdesc ); + + // Compute the first frame bones to compare with + studioModelCalcBones(hdr->numbones, pbone, panim, 0, gb.first_pos, gb.first_q); + + // Compute bones for each frame + for (int frame = 1; frame < pseqdesc->numframes; ++frame) { + studioModelCalcBones(hdr->numbones, pbone, panim, frame, gb.pos, gb.q); + + // Compate bones for each submodel + for (int si = 0; si < submodels_count; ++si) { + r_studio_submodel_info_t *const subinfo = submodels + si; + + // Once detected as dynamic, there's no point in checking further + if (subinfo->is_dynamic) + continue; + + const mstudiomodel_t *const submodel = subinfo->submodel_key; + const qboolean use_boneweights = FBitSet(hdr->flags, STUDIO_HAS_BONEWEIGHTS) && submodel->blendvertinfoindex != 0 && submodel->blendnorminfoindex != 0; + + if (use_boneweights) { + const mstudioboneweight_t *const pvertweight = (mstudioboneweight_t *)((byte *)hdr + submodel->blendvertinfoindex); + for(int vi = 0; vi < submodel->numverts; vi++) { + for (int bi = 0; bi < 4; ++bi) { + const int8_t bone = pvertweight[vi].bone[bi]; + if (bone == -1) + break; + + subinfo->is_dynamic |= !isBoneSame(bone); + if (subinfo->is_dynamic) + break; + } + if (subinfo->is_dynamic) + break; + } // for submodel verts + + } /* use_boneweights */ else { + const byte *const pvertbone = ((const byte *)hdr + submodel->vertinfoindex); + for(int vi = 0; vi < submodel->numverts; vi++) { + subinfo->is_dynamic |= !isBoneSame(pvertbone[vi]); + if (subinfo->is_dynamic) + break; + } + } // no use_boneweights + } // for all submodels + } // for all frames + } // for all sequences +} + +// Get submodels count and/or fill submodels array +static int studioModelGetSubmodels(const studiohdr_t *hdr, r_studio_submodel_info_t *out_submodels) { + int count = 0; + for (int i = 0; i < hdr->numbodyparts; ++i) { + const mstudiobodyparts_t* const bodypart = (mstudiobodyparts_t *)((byte *)hdr + hdr->bodypartindex) + i; + if (out_submodels) { + gEngine.Con_Reportf(" Bodypart %d/%d: %s (nummodels=%d)\n", i, hdr->numbodyparts - 1, bodypart->name, bodypart->nummodels); + for (int j = 0; j < bodypart->nummodels; ++j) { + const mstudiomodel_t * const submodel = (mstudiomodel_t *)((byte *)hdr + bodypart->modelindex) + j; + gEngine.Con_Reportf(" Submodel %d: %s\n", j, submodel->name); + out_submodels[count++].submodel_key = submodel; + } + } else { + count += bodypart->nummodels; + } + } + return count; +} + +qboolean R_StudioModelPreload(model_t *mod) { + const studiohdr_t *const hdr = (const studiohdr_t *)gEngine.Mod_Extradata(mod_studio, mod); + + ASSERT(g_studio_cache.models_count < MAX_STUDIO_MODELS); + + r_studio_model_info_entry_t *entry = &g_studio_cache.models[g_studio_cache.models_count++]; + entry->studio_header_key = hdr; + + gEngine.Con_Reportf("Studio model %s, sequences = %d:\n", hdr->name, hdr->numseq); + for (int i = 0; i < hdr->numseq; ++i) { + const mstudioseqdesc_t *const pseqdesc = (mstudioseqdesc_t *)((byte *)hdr + hdr->seqindex) + i; + gEngine.Con_Reportf(" %d: fps=%f numframes=%d\n", i, pseqdesc->fps, pseqdesc->numframes); + } + + // Get submodel array + const int submodels_count = studioModelGetSubmodels(hdr, NULL); + r_studio_submodel_info_t *submodels = Mem_Calloc(vk_core.pool, sizeof(*submodels) * submodels_count); + studioModelGetSubmodels(hdr, submodels); + + studioModelProcessBonesAnimations(mod, hdr, submodels, submodels_count); + + qboolean is_dynamic = false; + gEngine.Con_Reportf(" submodels_count: %d\n", submodels_count); + for (int i = 0; i < submodels_count; ++i) { + const r_studio_submodel_info_t *const subinfo = submodels + i; + is_dynamic |= subinfo->is_dynamic; + gEngine.Con_Reportf(" Submodel %d/%d: name=\"%s\", is_dynamic=%d\n", i, submodels_count-1, subinfo->submodel_key->name, subinfo->is_dynamic); + } + + entry->info.submodels_count = submodels_count; + entry->info.submodels = submodels; + + return true; +} + +r_studio_model_info_t *getStudioModelInfo(model_t *model) { + const studiohdr_t *const hdr = (studiohdr_t *)gEngine.Mod_Extradata( mod_studio, model ); + + for (int i = 0; i < g_studio_cache.models_count; ++i) { + r_studio_model_info_entry_t *const entry = g_studio_cache.models + i; + if (entry->studio_header_key == hdr) { + return &entry->info; + } + } + + return NULL; +} + +void VK_StudioModelInit(void) { + R_SPEEDS_METRIC(g_studio_cache.submodels_cached_static, "submodels_cached_static", kSpeedsMetricCount); + R_SPEEDS_METRIC(g_studio_cache.submodels_cached_dynamic, "submodels_cached_dynamic", 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; + } + subinfo->render_refcount++; + return render; + } + + render = Mem_Calloc(vk_core.pool, sizeof(*render)); + render->_.info = subinfo; + + if (!subinfo->is_dynamic) { + subinfo->cached_head = render; + ++g_studio_cache.submodels_cached_static; + } else { + ++g_studio_cache.submodels_cached_dynamic; + } + + subinfo->render_refcount++; + return render; +} + +void studioSubmodelRenderModelRelease(r_studio_submodel_render_t *render_submodel) { + if (!render_submodel) + return; + + ASSERT(render_submodel->_.info->render_refcount > 0); + render_submodel->_.info->render_refcount--; + + 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 new file mode 100644 index 000000000..ff1b368f7 --- /dev/null +++ b/ref/vk/vk_studio_model.h @@ -0,0 +1,66 @@ +#pragma once + +#include "vk_render.h" +#include "vk_geometry.h" + +struct r_studio_submodel_info_s; + +// Submodel render data that is enough to render given submodel +// Included render model (that also incapsulates BLAS) +// This can be static (built once), or dynamic (updated frequently) +// Lives in per-model-info submodel cache +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; + + // 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; + +// Submodel metadata and render-model cache +typedef struct r_studio_submodel_info_s { + const mstudiomodel_t *submodel_key; + qboolean is_dynamic; + + // TODO int verts_count; for prev_verts + + r_studio_submodel_render_t *cached_head; + + // Mostly for debug: how many cached render models were acquired and not given back + int render_refcount; +} r_studio_submodel_info_t; + +// Submodel cache functions, used in vk_studio.c +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; + +r_studio_model_info_t *getStudioModelInfo(model_t *model); + +// Entity model cache/pool +typedef struct { + const studiohdr_t *studio_header; + const r_studio_model_info_t *model_info; + + // ??? probably unnecessary matrix3x4 transform; + matrix3x4 prev_transform; + + int bodyparts_count; + r_studio_submodel_render_t **bodyparts; +} r_studio_entity_model_t; + +void VK_StudioModelInit(void); +//void VK_StudioModelShutdown(void);