Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

META WIP even less stable and more experimental changes to studio model tracking #543

Merged
merged 9 commits into from
Aug 28, 2023
38 changes: 38 additions & 0 deletions ref/vk/NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
3 changes: 3 additions & 0 deletions ref/vk/vk_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down
57 changes: 57 additions & 0 deletions ref/vk/vk_entity_data.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#include "vk_entity_data.h"

#include "vk_common.h" // ASSERT

#include <stddef.h> // 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;
}
16 changes: 16 additions & 0 deletions ref/vk/vk_entity_data.h
Original file line number Diff line number Diff line change
@@ -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
13 changes: 9 additions & 4 deletions ref/vk/vk_materials.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
8 changes: 5 additions & 3 deletions ref/vk/vk_rmain.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Expand All @@ -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 );
}
Expand Down
25 changes: 19 additions & 6 deletions ref/vk/vk_scene.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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();

Expand All @@ -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
Expand All @@ -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;
}
}
}

Expand All @@ -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);
Expand Down Expand Up @@ -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 )
Expand Down
Loading