18 KiB
Frame structure wrt calls from the engine
- (eng) SCR_UpdateScreen()
- (eng) V_PreRender()
- (ref) R_BeginFrame()
- (eng) V_RenderView()
- (ref) GL_BackendStartFrame() -- ref_gl only sets speeds string to empty here
- (eng) loop over ref_params_t views
- (ref) GL_RenderFrame()
- (eng) ??? SV_DrawDebugTriangles()
- (eng) loop over ref_params_t views
- (ref) GL_BackendEndFrame() -- ref_gl only produces speeds string here
- (ref) GL_BackendStartFrame() -- ref_gl only sets speeds string to empty here
- (eng) V_PostRender()
- (ref) R_AllowFog(), R_Set2DMode(true)
- (ref) R_DrawTileClear() x N
- (vgui) Paint() -> ???
- (eng) SCR_RSpeeds()
- (ref) R_SpeedsMessage()
- (eng) CL_DrawString() ...
- (ref) GL_SetRenderMode()
- (ref) RefGetParm() for texture resolution
- (ref) Color4ub()
- (ref) R_DrawStretchPic()
- (ref) GL_SetRenderMode()
- (eng) SRC_DrawNetGraph()
- (ref) many TriApi calls -- 2D usage of triapi. we were not ready for this (maybe track R_Set2DMode()?)
- (ref) R_ShowTextures() kekw
- (ref) VID_ScreenShot()
- (ref) R_AllowFog(true)
- (ref) R_EndFrame()
- (eng) V_PreRender()
Staging and multiple command buffers
We want to get rid of extra command buffers used for staging (and building blases). That would mean tying any command-buffer related things in there to framectl. However, there are several staging cmdbuf usages which are technically out-of-band wrt framectl: 0. Staging data can get full, which requires sync flush: filling cmdbuf outside of frame (or while still building a frame), submitting it and waiting on it.
- Texture uploading. There's an explicit usage of staging cmdbuf in vk_texture to do layout transfer. This layout transfer can be moved to staging itself.
- BLAS building. Creating a ray model uploads its geometry via staging and then immediately builds its BLAS on the same staging cmdbuf. Ideally(?), we'd like to split BLAS building to some later stage to do it in bulk.
OpenGL-like immediate mode rendering, ~TriApi
Functions:
R_Set2DMode(bool) -- switches between 3D scene and 2D overlay modes; used in engine
R_DrawStretchRaw,
R_DrawStretchPic,
R_DrawTileClear,
CL_FillRGBA,
CL_FillRGBABlend,
R_AllowFog,
GL_SetRenderMode,
void (*GL_Bind)( int tmu, unsigned int texnum );
void (*GL_SelectTexture)( int tmu );
void (*GL_LoadTextureMatrix)( const float *glmatrix ); -- exported to the game, not used in engine
void (*GL_TexMatrixIdentity)( void ); -- exported to the game, not used in engine
void (*GL_CleanUpTextureUnits)( int last ); // pass 0 for clear all the texture units
void (*GL_TexGen)( unsigned int coord, unsigned int mode );
void (*GL_TextureTarget)( unsigned int target ); // change texture unit mode without bind texture
void (*GL_TexCoordArrayMode)( unsigned int texmode );
void (*GL_UpdateTexSize)( int texnum, int width, int height, int depth ); // recalc statistics
TriRenderMode,
TriBegin,
TriEnd,
TriColor4f,
TriColor4ub,
TriTexCoord2f,
TriVertex3fv,
TriVertex3f,
TriFog,
TriGetMatrix,
TriFogParams,
TriCullFace,
Better BLAS management API
~~ BLAS:
- geom_count => kusok.geom/material.size() == geom_count
Model types:
- Fully static (brush model w/o animated textures; studio model w/o animations): singleton, fixed geoms and materials, uploaded only once
- Semi-static (brush model w/ animated textures): singleton, fixed geoms, may update materials, inplace (e.g. animated textures)
- Dynamic (beams, triapi, etc): singleton, may update both geoms and materials, inplace
- Template (sprites): used by multiple instances, fixed geom, multiple materials (colors, textures etc) instances/copies
- Update-from template (studo models): used by multiple dynamic models, deriving from it wvia BLAS UPDATE, dynamic geom+locations, fixed-ish materials.
API ~
- RT_ModelCreate(geometries_count dynamic?static?) -> rt_model + preallocated mem
- RT_ModelBuild/Update(geometries[]) -> (blas + kusok.geom[])
- RT_ModelUpdateMaterials(model, geometries/textures/materials[]); -> (kusok.material[])
- RT_FrameAddModel(model + kusok.geom[] + kusok.material[] + render_type + xform + color) ~~
rt_instance_t/rt_blas_t:
- VkAS blas
- VkASGeometry geom[] -> (vertex+index buffer address)
- VkASBuildRangeInfo ranges[] -> (vtxidx buffer offsets)
TODO: updateable: blas[2]? Ping-pong update, cannot do inplace?Nope, can do inplace.
- kusochki
- kusok[]
- geometry -> (vtxidx buffer offsets)
- TODO roughly the same data as VkASBuildRangeInfo, can reuse?
- material (currently embedded in kusok)
- static: tex[], scalar[]
- semi-dynamic:
- (a few) animated tex_base_color
- emissive
- animated with tex_base_color
- individual per-surface patches
- TODO: extract as a different modality not congruent with kusok data
- geometry -> (vtxidx buffer offsets)
- kusok[]
Usage cases for the above:
- (Fully+semi) static.
- Accept geom[] from above with vtx+idx refernces. Consider them static.
- Allocate static/fixed blas + kusok data once at map load.
- Allocate geom+ranges[] temporarily. Fill them with vtx+idx refs.
- Build BLAS (?: how does this work with lazy/deferred BLAS building wrt geom+ranges allocation)
- Similar to staging: collect everything + temp data, then commit.
- Needs BLAS manager, similar to vk_staging
- Generate Kusok data with current geoms and materials
- Free geom+ranges
- Each frame:
- (semi-static only) Update kusochki materials for animated textures
- Add blas+kusochki_offset (+dynamic color/xform/mmode) to TLAS
- Preallocated dynamic (triapi)
- Preallocate for fixed N geoms:
- geom+ranges[N].
- BLAS for N geometries
- kusochki[N]
- Each frame:
- Fill geom+ranges with geom data fed from outside
- Fill kusochki --//--
- Fast-Build BLAS as new
- Add to TLAS
- Each frame:
- Dynamic with update (animated studio models, beams)
- When a new studio model entity is encountered:
- Allocate:
- AT FIXED OFFSET: vtx+idx block
- geom+ranges[N], BLAS for N, kusochki[N]
- Allocate:
- Each frame:
- Fill geom+ranges with geom data
- Fill kusochki --//--
- First frame: BLAS as new
- Next frames: UPDATE BLAS in-place (depends on fixed offsets for vtx+idx)
- Add to TLAS
- When a new studio model entity is encountered:
- Instanced (sprites, studio models w/o animations).
- Same as static, BUT potentially dynamic and different materials. I.e. have to have per-instance kusochki copies with slightly different material contents.
- I.e. each frame
- If modifying materials (e.g. different texture for sprites):
- allocate temporary (for this frame only) kusochki block
- fill geom+material kusochki data
- Add to TLAS w/ correct kusochki offset.
- If modifying materials (e.g. different texture for sprites):
Exposed ops:
-
Create BLAS for N geoms
-
Allocate kusochki[N]
- static (fixed pos)
- temporary (any location, single frame lifetime)
-
Fill kusochki
- All geoms[]
- Subset of geoms[] (animated textures for static)
-
Build BLAS
- Allocate geom+ranges[N]
- Single frame staging-like?
- Needed only for BLAS BUILD/UPDATE
- from geoms+ranges[N]
- build vs update
- Allocate geom+ranges[N]
-
Add to TLAS w/ color/xform/mmode/...
-
geometry_buffer -- vtx+idx static + multi-frame dynamic + single-frame dynamic
-
kusochki_buffer -- kusok[] static + dynamic + clone_dynamic
-
accel_buffer -- static + multiframe dynamic + single-frame dynamic
-
scratch_buffer - single-frame dynamic
-
model_buffer - single-frame dynamic
E268: explicit kusochki management
Kusochki buffer has a similar lifetime rules to geometry buffer Funcs:
- Allocate kusochki[N] w/ static/long lifetime
- Allocate dynamic (single-frame) kusochki[N]
- Upload geom[N] -> kusochki[N]
- Upload subset geom[ind[M] -> kusochki[M]
E269
RT model alloc:
- blas -- fixed
- accel buffer region -- fixed
- (scratch: once for build)
- (geoms: once for build)
- -> geometry buffer -- fixed
- kusochki[G]: geometry data -- fixed
- materials[G]: -- fixed
RT model update:
- lives in the same statically allocated blas + accel_buffer
RT model draw:
- mmode
- materials[G] -- can be fully static, partially dynamic, fully dynamic
- update inplace for most of dynamic things
- clone for instanced
- color
- transforms
Blocks
Layer 0: abstract, not backing-dependent
handle = R_BlockAlloc(int size, lifetime);
- block possible users: {accel buffer, geometry, kusochki, materials};
- lifetime
- long: map, N frames: basically everything)
- once = this frame only: sprite materials, triapi geometry/kusochki/materials
- handle: offset, size
- R_BlockAcquire/Release(handle);
- R_BlocksClearOnce(); -- frees "once" regions, checking that they are not referenced
- R_blocksClearFull(); -- clears everything, checking that there are not external references
Layer 1: backed by buffer
- lock = R_SmthLock(handle, size, offset)
- marks region/block as dirty (cannot be used by anything yet, prevents release, clear, etc.),
- opens staging regiong for filling and uploading
- R_SmthUnlock(lock)
- remembers dirty region (for barriers)
- submits into staging queue
- ?? R_SmthBarrier -- somehow ask for the barrier struct given pipelines, etc
E271
Map loading sequence
- For a bunch of sprites:
- Load their textures
- Mod_ProcessRenderData(spr, create=1)
- "loading maps/c1a0.bsp" message
- Load a bunch of
#maps/c1a0.bsp:*.mip
textures - Mod_ProcessRenderData(maps/c1a0.bsp, create=1)
- Load a bunch of
- For studio models:
- Load their textures
- Mod_ProcessRenderData(mdl, create=1)
- "level loaded at 0.31 sec" message
- 1-2 frames drawn (judging by vk swapchain logs)
- Do another bunch of sprites (as #1)
- Lightstyles logs
- "Setting up renderer..." message
- R_NewMap() is called
- (vk) load skybox
- (vk) extract WADs, parse entities
- (vk) parse materials
- (vk) parse patches
- (vk) load models
- load brush models
- skip studio and sprite models
- (vk) load lights: parse rad files, etc
- "loading model/scientist02.mdl"
- Load 640_pain.spr ???, Mod_ProcessRenderData() first, then textures ??
Map unloading sequence
- Mod_ProcessRenderData(maps/c1a0.bps, create=0)
- NO similar calls for
*
brush submodels.
- NO similar calls for
- For the rest of studio and sprite models:
- Mod_ProcessRenderData(create=0)
E274
rt_model: - kusok/geom - index_,vertex_offset (static, same as geom/blas lifetime) - ref to material (static or dynamic) - emissive (mostly static, independent to material) - instanceCustomIndex (24 bits) = offset to kusochki buffer - kusochki[G] - geom data (index, vertex offsets) - emissive - material - materials[M]
- kusochki[N] <- iCI
E275 studio models
-
R_StudioDrawPoints()
VK_RenderModelDynamicBegin()
- compute
g_studio.verts
- in:
m_pSubModel
m_pStudioHeader
g_studio.worldtransform
- in:
R_StudioBuildNormalTable()
...R_StudioGenerateNormals()
- in:
m_pStudioHeader
m_pSubModel
g_studio.verts
- out:
g_studio.norms
g_studio.tangents
- for all submodel meshes
- compute normals+tangents
- in:
- for all submodel meshes
R_StudioDrawNormalMesh()
R_GeometryBufferAllocOnceAndLock()
- fills it with vertex/index data, reading
g_studio.verts/norms/tangents/...
R_StudioSetColorBegin()
???
R_GeometryBufferUnlock()
VK_RenderModelDynamicAddGeometry()
VK_RenderModelDynamicCommit()
-
R_StudioDrawPoints()
callers:- external ???
R_StudioRenderFinal()
-
R_StudioRenderFinal()
- ... TBD
VK_RenderDebugLabelBegin()
- for all
m_pStudioHeader->numbodyparts
R_StudioSetupModel()
-- also can be called externally- set
m_pBodyPart
- set
m_pSubModel
- set
R_StudioDrawPoints()
GL_StudioDrawShadow()
VK_RenderDebugLabelEnd()
-
R_StudioDrawModelInternal()
- called from:
R_DrawStudioModel()
3xR_DrawViewModel()
R_RunViewmodelEvents()
VK_RenderDebugLabelBegin()
R_StudioDrawModel()
- in:
RI.currententity
RI.currentmodel
R_StudioSetHeader()
- sets
m_pStudioHeader
- sets
R_StudioSetUpTransform(entity = RI.currententity)
R_StudioLerpMovement(entity)
- updates entity internal state
g_studio.rotationmatrix = Matrix3x4_CreateFromEntity()
- in:
VK_RenderDebugLabelEnd()
- called from:
-
VK_StudioDrawModel()
-- called from vk_scene.c- sets
RI.currententity
,RI.currentmodel
R_DrawStudioModel()
R_StudioSetupTimings()
-- setsg_studio.time/frametime
R_StudioDrawModelInternal()
- sets
E279
Studio model animation
-
studiohdr_t
- int numseq -- number of "sequences"?
- int seqindex -- offset to sequences:
pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + sequence;
-
mstudioseqdesc_t
- int numframes
- int fps
-
mstudioanim_t
- = gEngine.R_StudioGetAnim(studiohdr, model, seqdesc)
-
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.
- Needs tracking of model cache entry whenever
-
Add a cache for entities, store all prev_* stuff there.
- Needs tracking of entity cache entry whenever
RI.currententity
is set.
- Needs tracking of entity cache entry whenever
-
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 upR_StudioDrawModelInternal()
is the main one. It is where it splits into renderer-vs-game rendering functions.
2023-09-19 E298
SURF_DRAWSKY
- (context: want to remove kXVkMaterialSky, #474)
- qrad:
- uses textue name "sky" or "SKY" to check
IsSky()
.IsSky()
surfaces do not get patches and do not participate in radiosity. - uses CONTENTS_SKY node flag to detect whether a ray has hit skybox and can contribute sky light.
- uses textue name "sky" or "SKY" to check
- xash/gl:
- CONTENTS_SKY is not used in any meaningful way
- sets SURF_DRAWSKY for surfaces with "sky" texture.
- uses SURF_DRAWSKY:
- to build skychain, and then draw it in Quake mode (the other branch does a bunch of math, which seemingly isn't used for anything at all).
- for dynamic lighting: if sky ray has hit sky surface then sky is contributing light
2023-09-25 #301
Materials format
Define new material, independently of any existing textures, etc This can be .vmat compatible, primext compatbile, etc. The important parts:
- It has a unique name that we can reference it with
- It has all the fields that we use for our PBR shading model
- (? Material mode can be specified)
{
"material" "MAT_NAME"
"map_base_color" "base.png"
"map_normal" "irregular.ktx2"
"base_color" "1 .5 0"
// ...
}
{
"material" "mirror"
"map_base_color" "white"
"base_color" "1 1 1"
"roughness" "0"
"metalness" "1"
// ...
}
Then, we can map existing textures to new materials:
{
"for_texture" "+EXIT"
"use" "MAT_NAME"
}
Or, with more context:
{
"for_model_type" "brush"
"for_rendermode" "kRenderTransAlpha"
"for_texture" "wood"
"use" "mat_glass"
"mode" "translucent"
"map_base_color" "glass2.ktx"
}
// ??? meh, see the better _xvk_ example below
{
"for_model_type" "brush"
"for_surface_id" "584"
"use" "mirror"
}
// This example: use previously specified material (e.g. via _xvk stuff below)
// (Depends on applying multiple matching rules, see questions below)
{
"for_model_type" "brush"
"for_rendermode" "kRenderTransAlpha"
"mode" "translucent"
"map_normal" "glass2.ktx"
}
// We also want this (for maps, not globally ofc), see https://github.com/w23/xash3d-fwgs/issues/526
{
"for_entity_id" "39"
"for_texture" "generic028"
"use" "generic_metal1"
}
{
"for_entity_id" "39"
"for_texture" "generic029"
"use" "generic_metal2"
}
What it does is:
- If all
"for_"
fields match, apply values from"use"
material (in this case"wood"
) - Additionally, override any extra fields/values with ones specified in this block
As we already have surface-patching ability, can just use that for patching materials directly for brush surfaces:
// mirror in toilet
{
"_xvk_surface_id" "2057"
"_xvk_material" "mirror"
}
Questions:
- Should it apply the first found rule that matches a given geometry and stop? Or should it apply updates to the material using all the rules that matched in their specified order? Doing the first rule and stopping is more readable and perofrmant, but also might be verbose in some cases.
- Should we do "automatic" materials? I.e. if there's no manually specified material for a texture named
"<TEX>"
, then we try to load"<TEX>_basecolor.ktx"
,"<TEX>_normalmap.ktx"
, etc automatically.