[vulkan] Partially document bsp rendering

This did involve changing some field names and a little bit of cleanup,
but I've got a better handle on what's going on (I think I was in one of
those coding trances where I quickly forget how things work).
This commit is contained in:
Bill Currie 2022-06-08 18:16:10 +09:00
parent 3e7f2f0578
commit aafb3c1d98
3 changed files with 259 additions and 81 deletions

View file

@ -39,13 +39,27 @@
#include "QF/simd/types.h"
/** \defgroup vulkan_bsp Brush model rendering
\ingroup vulkan
*/
/** Represent a single face (polygon) of a brush model.
*
* There is one of these for each face in the bsp (brush) model, built at run
* time when the model is loaded (actually, after all models are loaded but
* before rendering begins).
*/
typedef struct bsp_face_s {
uint32_t first_index;
uint32_t index_count;
uint32_t tex_id;
uint32_t flags;
uint32_t first_index; ///< index of first index in poly_indices
uint32_t index_count; ///< includes primitive restart
uint32_t tex_id; ///< texture bound to this face (maybe animated)
uint32_t flags; ///< face drawing (alpha, side, sky, turb)
} bsp_face_t;
/** Represent a brush model, both main and sub-model.
*
* Used for rendering non-world models.
*/
typedef struct bsp_model_s {
uint32_t first_face;
uint32_t face_count;
@ -62,21 +76,57 @@ typedef struct texmip_s {
uint32_t offsets[MIPLEVELS];
} texmip_t;
#endif
/** \defgroup vulkan_bsp_texanim Animated Textures
* \ingroup vulkan_bsp
*
* Brush models support texture animations. For general details, see
* \ref bsp_texture_animation. These structures allow for quick lookup
* of the correct texture to use in an animation cycle, or even whether there
* is an animation cycle.
*/
///@{
/** Represent a texture's animation group.
*
* Every texture is in an animation group, even when not animated. When the
* texture is not animated, `count` is 1, otherwise `count` is the number of
* frames in the group, thus every texture has at least one frame.
*
* Each texture in a particular groupp shares the same `base` frame, with
* `offset` giving the texture's relative frame number within the group.
* The current frame is given by `base + (anim_index + offset) % count` where
* `anim_index` is the global time-based texture animation frame.
*/
typedef struct texanim_s {
uint16_t base;
byte offset;
byte count;
uint16_t base; ///< first frame in group
byte offset; ///< relative frame in group
byte count; ///< number of frames in group
} texanim_t;
/** Holds texture animation data for brush models.
*
* Brush models support one or two texture animation groups, based on the
* entity's frame (0 or non-0). When the entity's frame is 0, group 0 is used,
* otherwise group 1 is used. If there is no alternate (group 1) animation
* data for the texture, then the texture's group 0 data is copied to group 1
* in order to avoid coplications in selecting which texture a face is to use.
*
* As all of a group's frames are together, `frame_map` is used to get the
* actual texture id for the frame.
*/
typedef struct texdata_s {
// texname_t *names;
// texmip_t **mips;
texanim_t *anim_main;
texanim_t *anim_alt;
uint16_t *anim_map;
texanim_t *anim_main; ///< group 0 animations
texanim_t *anim_alt; ///< group 1 animations
uint16_t *frame_map; ///< map from texture frame to texture id
// int num_tex;
} texdata_t;
///@}
/** \defgroup vulkan_bsp_draw Brush model drawing
* \ingroup vulkan_bsp
*/
///@{
typedef struct vulktex_s {
struct qfv_tex_s *tex;
VkDescriptorSet descriptor;
@ -86,57 +136,186 @@ typedef struct vulktex_s {
typedef struct regtexset_s
DARRAY_TYPE (vulktex_t *) regtexset_t;
/** Represent a single draw call.
*
* For each texture that has faces to be rendered, one or more draw calls is
* made. Normally, only one call per texture is made, but if different models
* use the same texture, then a separate draw call is made for each model.
* When multiple entities use the same model, instanced rendering is used to
* draw all the faces sharing a texture for all the entities using that model.
* Thus when there are multiple draw calls for a single texture, they are
* grouped together so there is only one bind per texture.
*
* The index buffer is populated every frame with the vertex indices of the
* faces to be rendered for the current frame, grouped by texture and instance
* id (model render id).
*
* The model render id is assigned after models are loaded but before rendering
* begins and remains constant until the next time models are loaded (level
* change).
*
* The entid buffer is also populated every frame with the render id of the
* entities to be drawn that frame, It is used to map gl_InstanceIndex to
* entity id so as to look up the entity's transform and color (and any other
* data in the future).
*
* \dot
* digraph vulkan_bsp_draw_call {
* layout=dot; rankdir=LR; compound=true; nodesep=1.0;
* vertices [shape=none,label=< <table border="1" cellborder="1">
* <tr><td>vertex</td></tr>
* <tr><td>vertex</td></tr>
* <tr><td>...</td></tr>
* <tr><td port="p">vertex</td></tr>
* <tr><td>vertex</td></tr>
* </table> >];
* indices [shape=none,label=< <table border="1" cellborder="1">
* <tr><td>index</td></tr>
* <tr><td>index</td></tr>
* <tr><td>...</td></tr>
* <tr><td port="p">index</td></tr>
* <tr><td>index</td></tr>
* </table> >];
* entids [shape=none,label=< <table border="1" cellborder="1">
* <tr><td>entid</td></tr>
* <tr><td>...</td></tr>
* <tr><td port="p">entid</td></tr>
* <tr><td>entid</td></tr>
* <tr><td>...</td></tr>
* <tr><td>entid</td></tr>
* </table> >];
* entdata [shape=none,label=< <table border="1" cellborder="1">
* <tr><td>transform</td><td>color</td></tr>
* <tr><td>transform</td><td>color</td></tr>
* <tr><td colspan="2">...</td></tr>
* <tr><td port="p">transform</td><td>color</td></tr>
* <tr><td colspan="2">...</td></tr>
* <tr><td>transform</td><td>color</td></tr>
* </table> >];
* drawcall [shape=none,label=< <table border="1" cellborder="1">
* <tr><td port="tex" >tex_id</td></tr>
* <tr><td >inst_id</td></tr>
* <tr><td port="ind" >first_index</td></tr>
* <tr><td >index_count</td></tr>
* <tr><td port="inst">first_instance</td></tr>
* <tr><td >instance_count</td></tr>
* </table> >];
* textures [shape=none,label=< <table border="1" cellborder="1">
* <tr><td>texture</td></tr>
* <tr><td>texture</td></tr>
* <tr><td port="p">texture</td></tr>
* <tr><td>...</td></tr>
* <tr><td>texture</td></tr>
* </table> >];
* vertex [label="vertex shader"];
* fragment [label="fragment shader"];
* drawcall:tex -> textures:p;
* drawcall:ind -> indices:p;
* drawcall:inst -> entids:p;
* entids:p -> entdata:p;
* indices:p -> vertices:p;
* vertex -> entdata [label="storage buffer"];
* vertex -> entids [label="per instance"];
* vertex -> indices [label="index buffer"];
* vertex -> vertices [label="per vertex"];
* fragment -> textures [label="per call"];
* }
* \enddot
*/
///@{
typedef struct bsp_draw_s {
uint32_t tex_id;
uint32_t inst_id;
uint32_t index_count;
uint32_t instance_count;
uint32_t first_index;
uint32_t first_instance;
uint32_t tex_id; ///< texture to bind for this draw call
uint32_t inst_id; ///< model render id owning this draw call
uint32_t index_count; ///< number of indices for this draw call
uint32_t instance_count; ///< number of instances to draw
uint32_t first_index; ///< index into index buffer
uint32_t first_instance; ///< index into entid buffer
} bsp_draw_t;
typedef struct bsp_drawset_s
DARRAY_TYPE (bsp_draw_t) bsp_drawset_t;
///@}
/** Tag models that are to be queued for translucent drawing.
*/
#define INST_ALPHA (1u<<31)
/** Representation of a single face queued for drawing.
*/
///@{
typedef struct instface_s {
uint32_t inst_id;
uint32_t face;
uint32_t inst_id; ///< model render id owning this face
uint32_t face; ///< index of face in context array
} instface_t;
typedef struct bsp_instfaceset_s
DARRAY_TYPE (instface_t) bsp_instfaceset_t;
///@}
/** Track entities using a model.
*/
///@{
typedef struct bsp_modelentset_s
DARRAY_TYPE (uint32_t) bsp_modelentset_t;
/** Represent a single model and the entities using it.
*/
typedef struct bsp_instance_s {
int first_instance;
bsp_modelentset_t entities;
int first_instance; ///< index into entid buffer
bsp_modelentset_t entities; ///< list of entity render ids using this model
} bsp_instance_t;
///@}
typedef struct bsp_pass_s {
vec4f_t position;
plane_t *frustum;
const struct mod_brush_s *brush;
struct bspctx_s *bsp_context;
uint32_t *indices; // points into index buffer
uint32_t index_count; // number of indices written to buffer
uint32_t *entid_data; // points into entid buffer
uint32_t entid_count;
int vis_frame;
int *face_frames;
int *leaf_frames;
int *node_frames;
bsp_instfaceset_t *face_queue;
regtexset_t *textures;
int num_queues;
bsp_drawset_t *draw_queues;
uint32_t inst_id;
bsp_instance_t *instances;
int ent_frame;
vec4f_t position; ///< view position
plane_t *frustum; ///< view frustum for culling
const struct mod_brush_s *brush;///< data for current model
struct bspctx_s *bsp_context; ///< owning bsp context
/** \name GPU data
*
* The indices to be drawn and the entity ids associated with each draw
* instance are updated each frame. The pointers are to the per-frame
* mapped buffers for the respective data.
*/
///@{
uint32_t *indices; ///< polygon vertex indices
uint32_t index_count; ///< number of indices written to buffer
uint32_t *entid_data; ///< instance id to entity id map
uint32_t entid_count; ///< numer of entids written to buffer
///@}
/** \name Potentially Visible Sets
*
* For an object to be in the PVS, its frame id must match the current
* visibility frame id, thus clearing all sets is done by incrementing
* `vis_frame`, and adding an object to the PVS is done by setting its
* current frame id to the current visibility frame id.
*/
///@{
int vis_frame; ///< current visibility frame id
int *face_frames; ///< per-face visibility frame ids
int *leaf_frames; ///< per-leaf visibility frame ids
int *node_frames; ///< per-node visibility frame ids
///@}
bsp_instfaceset_t *face_queue; ///< per-texture face queues
regtexset_t *textures; ///< textures to bind when emitting calls
int num_queues; ///< number of pipeline queues
bsp_drawset_t *draw_queues; ///< per-pipeline draw queues
uint32_t inst_id; ///< render id of current model
bsp_instance_t *instances; ///< per-model entid lists
// FIXME There are several potential optimizations here:
// 1) ent_frame could be forced to be 0 or 1 and then used to index a
// two-element array of texanim pointers
// 2) ent_frame could be a pointer to the correct texanim array
// 3) could update a tex_id map each frame and unconditionally index that
//
// As the texture id is used for selecting the face queue, 3 could be used
// for mapping all textures to 1 or two queues for shadow rendering
int ent_frame; ///< animation frame of current entity
} bsp_pass_t;
///@}
/// \ingroup vulkan_bsp
///@{
typedef enum {
QFV_bspDepth,
QFV_bspGBuffer,
@ -159,31 +338,33 @@ typedef struct bspframe_s {
typedef struct bspframeset_s
DARRAY_TYPE (bspframe_t) bspframeset_t;
/** Main BSP context structure
*
* This holds all the state and resources needed for rendering brush models.
*/
typedef struct bspctx_s {
regtexset_t registered_textures;
struct qfv_tex_s *default_skysheet;
struct qfv_tex_s *skysheet_tex;
struct qfv_tex_s *default_skybox;
struct qfv_tex_s *skybox_tex;
VkDescriptorSet skybox_descriptor;
vulktex_t notexture;
vulktex_t notexture; ///< replacement for invalid textures
struct scrap_s *light_scrap;
struct qfv_stagebuf_s *light_stage;
bsp_model_t *models;
bsp_face_t *faces;
uint32_t *poly_indices;
int num_models; ///< number of loaded brush models
bsp_model_t *models; ///< all loaded brush models
bsp_face_t *faces; ///< all faces from all loaded brush models
uint32_t *poly_indices; ///< face indices from all loaded brush models
texdata_t texdata;
int anim_index;
regtexset_t registered_textures;///< textures for all loaded brush models
texdata_t texdata; ///< texture animation data
int anim_index; ///< texture animation frame (5fps)
struct qfv_tex_s *default_skysheet;
struct qfv_tex_s *skysheet_tex; ///< scrolling sky texture for current map
int model_id;
struct qfv_tex_s *default_skybox;
struct qfv_tex_s *skybox_tex; ///< sky box texture for current map
VkDescriptorSet skybox_descriptor;
bsp_pass_t main_pass; // camera view depth, gbuffer, etc
bsp_pass_t main_pass; ///< camera view depth, gbuffer, etc
VkSampler sampler;
VkPipelineLayout layout;
@ -207,7 +388,6 @@ typedef struct bspctx_s {
struct vulkan_ctx_s;
struct qfv_renderframe_s;
void Vulkan_ClearElements (struct vulkan_ctx_s *ctx);
void Vulkan_DrawWorld (struct qfv_renderframe_s *rFrame);
void Vulkan_DrawSky (struct qfv_renderframe_s *rFrame);
void Vulkan_DrawWaterSurfaces (struct qfv_renderframe_s *rFrame);
@ -219,5 +399,6 @@ void Vulkan_BuildDisplayLists (model_t **models, int num_models,
struct vulkan_ctx_s *ctx);
void Vulkan_Bsp_Init (struct vulkan_ctx_s *ctx);
void Vulkan_Bsp_Shutdown (struct vulkan_ctx_s *ctx);
///@}
#endif//__QF_Vulkan_qf_bsp_h

View file

@ -35,6 +35,9 @@
#endif
#include <vulkan/vulkan.h>
/** \defgroup vulkan Vulkan Renderer
*/
enum {
QFV_rp_shadowmap,
QFV_rp_main,

View file

@ -91,14 +91,12 @@ static const char * __attribute__((used)) bsp_pass_names[] = {
};
static QFV_Subpass subpass_map[] = {
QFV_passDepth, // QFV_bspDepth
QFV_passGBuffer, // QFV_bspGBuffer
QFV_passTranslucent, // QFV_bspSky
QFV_passTranslucent, // QFV_bspTurb
[QFV_bspDepth] = QFV_passDepth,
[QFV_bspGBuffer] = QFV_passGBuffer,
[QFV_bspSky] = QFV_passTranslucent,
[QFV_bspTurb] = QFV_passTranslucent,
};
#define ALLOC_CHUNK 64
static void
add_texture (texture_t *tx, vulkan_ctx_t *ctx)
{
@ -113,12 +111,6 @@ add_texture (texture_t *tx, vulkan_ctx_t *ctx)
}
}
void
Vulkan_ClearElements (vulkan_ctx_t *ctx)
{
// bspctx_t *bctx = ctx->bsp_context;
}
static inline void
chain_surface (const bsp_face_t *face, bsp_pass_t *pass, const bspctx_t *bctx)
{
@ -128,7 +120,7 @@ chain_surface (const bsp_face_t *face, bsp_pass_t *pass, const bspctx_t *bctx)
const texanim_t *anim = ent_frame ? &bctx->texdata.anim_alt[face->tex_id]
: &bctx->texdata.anim_main[face->tex_id];
int anim_ind = (bctx->anim_index + anim->offset) % anim->count;
int tex_id = bctx->texdata.anim_map[anim->base + anim_ind];
int tex_id = bctx->texdata.frame_map[anim->base + anim_ind];
DARRAY_APPEND (&pass->face_queue[tex_id],
((instface_t) { pass->inst_id, face - bctx->faces }));
}
@ -165,7 +157,6 @@ clear_textures (vulkan_ctx_t *ctx)
void
Vulkan_RegisterTextures (model_t **models, int num_models, vulkan_ctx_t *ctx)
{
clear_textures (ctx);
add_texture (r_notexture_mip, ctx);
{
@ -209,11 +200,13 @@ Vulkan_RegisterTextures (model_t **models, int num_models, vulkan_ctx_t *ctx)
}
}
// 2.5 for two texanim_t structs (32-bits each) and 1 uint16_t for each
// element
size_t texdata_size = 2.5 * num_tex * sizeof (texanim_t);
texanim_t *texdata = Hunk_AllocName (0, texdata_size, "texdata");
bctx->texdata.anim_main = texdata;
bctx->texdata.anim_alt = texdata + num_tex;
bctx->texdata.anim_map = (uint16_t *) (texdata + 2 * num_tex);
bctx->texdata.frame_map = (uint16_t *) (texdata + 2 * num_tex);
int16_t map_index = 0;
for (int i = 0; i < num_tex; i++) {
texanim_t *anim = bctx->texdata.anim_main + i;
@ -222,7 +215,7 @@ Vulkan_RegisterTextures (model_t **models, int num_models, vulkan_ctx_t *ctx)
continue;
}
*anim = (texanim_t) { .base = map_index, .offset = 0, .count = 1 };
bctx->texdata.anim_map[anim->base] = i;
bctx->texdata.frame_map[anim->base] = i;
if (textures[i]->anim_total > 1) {
// bsp loader multiplies anim_total by ANIM_CYCLE to slow the
@ -240,7 +233,7 @@ Vulkan_RegisterTextures (model_t **models, int num_models, vulkan_ctx_t *ctx)
}
*a = *anim;
a->offset = j;
bctx->texdata.anim_map[a->base + a->offset] = vtex->tex_id;
bctx->texdata.frame_map[a->base + a->offset] = vtex->tex_id;
tx = tx->anim_next;
}
if (tx != textures[i]) {
@ -260,6 +253,7 @@ Vulkan_RegisterTextures (model_t **models, int num_models, vulkan_ctx_t *ctx)
}
}
// create face queue arrays
bctx->main_pass.face_queue = malloc (num_tex * sizeof (bsp_instfaceset_t));
for (int i = 0; i < num_tex; i++) {
bctx->main_pass.face_queue[i]
@ -379,11 +373,11 @@ Vulkan_BuildDisplayLists (model_t **models, int num_models, vulkan_ctx_t *ctx)
face_sets[i] = (facerefset_t) DARRAY_STATIC_INIT (1024);
}
for (int i = 0; i < bctx->model_id; i++) {
for (int i = 0; i < bctx->num_models; i++) {
DARRAY_CLEAR (&bctx->main_pass.instances[i].entities);
}
free (bctx->main_pass.instances);
bctx->model_id = 0;
bctx->num_models = 0;
// run through all surfaces, chaining them to their textures, thus
// effectively sorting the surfaces by texture (without worrying about
@ -396,7 +390,7 @@ Vulkan_BuildDisplayLists (model_t **models, int num_models, vulkan_ctx_t *ctx)
if (!m || m->type != mod_brush) {
continue;
}
m->render_id = bctx->model_id++;
m->render_id = bctx->num_models++;
if (*m->path == '*') {
continue;
}
@ -422,9 +416,9 @@ Vulkan_BuildDisplayLists (model_t **models, int num_models, vulkan_ctx_t *ctx)
}
face_base += brush->numsurfaces;
}
bctx->main_pass.instances = malloc (bctx->model_id
bctx->main_pass.instances = malloc (bctx->num_models
* sizeof (bsp_instance_t));
for (int i = 0; i < bctx->model_id; i++) {
for (int i = 0; i < bctx->num_models; i++) {
DARRAY_INIT (&bctx->main_pass.instances[i].entities, 16);
}
// All vertices from all brush models go into one giant vbo.
@ -469,7 +463,7 @@ Vulkan_BuildDisplayLists (model_t **models, int num_models, vulkan_ctx_t *ctx)
free (bctx->faces);
free (bctx->poly_indices);
free (bctx->models);
bctx->models = malloc (bctx->model_id * sizeof (bsp_model_t));
bctx->models = malloc (bctx->num_models * sizeof (bsp_model_t));
bctx->faces = malloc (face_base * sizeof (bsp_face_t));
bctx->poly_indices = malloc (index_count * sizeof (uint32_t));
@ -943,7 +937,7 @@ clear_queues (bspctx_t *bctx, bsp_pass_t *pass)
for (int i = 0; i < pass->num_queues; i++) {
DARRAY_RESIZE (&pass->draw_queues[i], 0);
}
for (int i = 0; i < bctx->model_id; i++) {
for (int i = 0; i < bctx->num_models; i++) {
pass->instances[i].first_instance = -1;
DARRAY_RESIZE (&pass->instances[i].entities, 0);
}
@ -1507,7 +1501,7 @@ Vulkan_Bsp_Shutdown (struct vulkan_ctx_s *ctx)
free (bctx->models);
free (bctx->main_pass.draw_queues);
for (int i = 0; i < bctx->model_id; i++) {
for (int i = 0; i < bctx->num_models; i++) {
DARRAY_CLEAR (&bctx->main_pass.instances[i].entities);
}
free (bctx->main_pass.instances);