[vulkan] Use instanced rendering for brush models

The models are broken up into N sub-(sub-)models, one for each texture,
but all faces using the same texture are drawn as an instance, making
for both reduced draw calls and reduced index buffer use (and thus,
hopefully, reduced bandwidth). While texture animations are broken, this
does mark a significant milestone towards implementing shadows as it
should now be possible to use multiple threads (with multiple index and
entid buffers) to render the depth buffers for all the lights.
This commit is contained in:
Bill Currie 2022-05-25 13:29:11 +09:00
parent a08261c620
commit 524c1e27c4
6 changed files with 157 additions and 70 deletions

View file

@ -46,6 +46,11 @@ typedef struct bsp_face_s {
uint32_t flags;
} bsp_face_t;
typedef struct bsp_model_s {
uint32_t first_face;
uint32_t face_count;
} bsp_model_t;
typedef struct bsp_packet_s {
int first_index;
int index_count;
@ -79,6 +84,7 @@ typedef struct bsp_texset_s
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;
@ -96,6 +102,14 @@ typedef struct instface_s {
typedef struct bsp_instfaceset_s
DARRAY_TYPE (instface_t) bsp_instfaceset_t;
typedef struct bsp_modelentset_s
DARRAY_TYPE (uint32_t) bsp_modelentset_t;
typedef struct bsp_instance_s {
int first_instance;
bsp_modelentset_t entities;
} bsp_instance_t;
typedef struct bsp_pass_s {
uint32_t *indices; // points into index buffer
uint32_t index_count; // number of indices written to buffer
@ -109,6 +123,8 @@ typedef struct bsp_pass_s {
regtexset_t *textures;
int num_queues;
bsp_drawset_t *draw_queues;
uint32_t inst_id;
bsp_instance_t *instances;
} bsp_pass_t;
typedef struct bspvert_s {
@ -147,8 +163,6 @@ typedef struct bspframeset_s
DARRAY_TYPE (bspframe_t) bspframeset_t;
typedef struct bspctx_s {
uint32_t inst_id;
regtexset_t registered_textures;
struct qfv_tex_s *default_skysheet;
@ -164,9 +178,12 @@ typedef struct bspctx_s {
struct scrap_s *light_scrap;
struct qfv_stagebuf_s *light_stage;
bsp_model_t *models;
bsp_face_t *faces;
uint32_t *poly_indices;
int model_id;
bsp_pass_t main_pass; // camera view depth, gbuffer, etc
VkSampler sampler;

View file

@ -76,5 +76,6 @@ void Vulkan_Scene_Shutdown (struct vulkan_ctx_s *ctx);
int Vulkan_Scene_MaxEntities (struct vulkan_ctx_s *ctx) __attribute__((pure));
VkDescriptorSet Vulkan_Scene_Descriptors (struct vulkan_ctx_s *ctx) __attribute__((pure));
int Vulkan_Scene_AddEntity (struct vulkan_ctx_s *ctx, struct entity_s *entity);
void Vulkan_Scene_Flush (struct vulkan_ctx_s *ctx);
#endif//__QF_Vulkan_qf_scene_h

View file

@ -425,6 +425,8 @@ typedef struct model_s {
cache_user_t cache;
void (*clear) (struct model_s *m, void *data);
void *data;
int render_id;
} model_t;
// ============================================================================

View file

@ -45,6 +45,7 @@
#include "QF/cvar.h"
#include "QF/darray.h"
#include "QF/heapsort.h"
#include "QF/image.h"
#include "QF/render.h"
#include "QF/sys.h"
@ -123,7 +124,7 @@ chain_surface (const bsp_face_t *face, bsp_pass_t *pass, bspctx_t *bctx)
{
DARRAY_APPEND (&pass->face_queue[face->tex_id],
((instface_t) { bctx->inst_id, face - bctx->faces }));
((instface_t) { pass->inst_id, face - bctx->faces }));
}
static void
@ -215,7 +216,7 @@ typedef struct {
} buildctx_t;
static void
build_surf_displist (faceref_t *faceref, buildctx_t *build)
build_surf_displist (const faceref_t *faceref, buildctx_t *build)
{
msurface_t *surf = faceref->face;
mod_brush_t *brush = &faceref->model->brush;;
@ -296,6 +297,12 @@ 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++) {
DARRAY_CLEAR (&bctx->main_pass.instances[i].entities);
}
free (bctx->main_pass.instances);
bctx->model_id = 0;
// run through all surfaces, chaining them to their textures, thus
// effectively sorting the surfaces by texture (without worrying about
// surface order on the same texture chain).
@ -304,8 +311,13 @@ Vulkan_BuildDisplayLists (model_t **models, int num_models, vulkan_ctx_t *ctx)
model_t *m = models[i];
// sub-models are done as part of the main model
// and non-bsp models don't have surfaces.
if (!m || *m->path == '*' || m->type != mod_brush)
if (!m || m->type != mod_brush) {
continue;
}
m->render_id = bctx->model_id++;
if (*m->path == '*') {
continue;
}
mod_brush_t *brush = &m->brush;
dmodel_t *dm = brush->submodels;
for (unsigned j = 0; j < brush->numsurfaces; j++) {
@ -328,6 +340,11 @@ Vulkan_BuildDisplayLists (model_t **models, int num_models, vulkan_ctx_t *ctx)
}
face_base += brush->numsurfaces;
}
bctx->main_pass.instances = malloc (bctx->model_id
* sizeof (bsp_instance_t));
for (int i = 0; i < bctx->model_id; i++) {
DARRAY_INIT (&bctx->main_pass.instances[i].entities, 16);
}
// All vertices from all brush models go into one giant vbo.
uint32_t vertex_count = 0;
uint32_t index_count = 0;
@ -369,9 +386,21 @@ Vulkan_BuildDisplayLists (model_t **models, int num_models, vulkan_ctx_t *ctx)
// "end of primitive" (~0u)
free (bctx->faces);
free (bctx->poly_indices);
free (bctx->models);
bctx->models = malloc (bctx->model_id * sizeof (bsp_model_t));
bctx->faces = malloc (face_base * sizeof (bsp_face_t));
bctx->poly_indices = malloc (index_count * sizeof (uint32_t));
face_base = 0;
for (int i = 0; i < num_models; i++) {
if (models[i] && models[i]->type == mod_brush) {
bsp_model_t *m = &bctx->models[models[i]->render_id];
m->first_face = face_base;
m->face_count = models[i]->brush.nummodelsurfaces;
face_base += m->face_count;
}
}
// All usable surfaces have been chained to the (base) texture they use.
// Run through the textures, using their chains to build display lists.
// For animated textures, if a surface is on one texture of the group, it
@ -468,61 +497,44 @@ Vulkan_BuildDisplayLists (model_t **models, int num_models, vulkan_ctx_t *ctx)
}
static void
R_DrawBrushModel (entity_t *e, vulkan_ctx_t *ctx)
static int
R_DrawBrushModel (entity_t *e, bsp_pass_t *pass, vulkan_ctx_t *ctx)
{
float dot, radius;
float radius;
model_t *model;
plane_t *plane;
msurface_t *surf;
qboolean rotated;
vec3_t mins, maxs;
vec4f_t org;
mod_brush_t *brush;
//bspctx_t *bctx = ctx->bsp_context;
bspctx_t *bctx = ctx->bsp_context;
model = e->renderer.model;
brush = &model->brush;
mat4f_t mat;
Transform_GetWorldMatrix (e->transform, mat);
memcpy (e->renderer.full_transform, mat, sizeof (mat));//FIXME
if (mat[0][0] != 1 || mat[1][1] != 1 || mat[2][2] != 1) {
rotated = true;
radius = model->radius;
if (R_CullSphere (r_refdef.frustum, (vec_t*)&mat[3], radius)) { //FIXME
return;
return 1;
}
} else {
rotated = false;
VectorAdd (mat[3], model->mins, mins);
VectorAdd (mat[3], model->maxs, maxs);
if (R_CullBox (r_refdef.frustum, mins, maxs))
return;
return 1;
}
if (Vulkan_Scene_AddEntity (ctx, e) < 0) {
return 0;
}
org = r_refdef.frame.position - mat[3];
if (rotated) {
vec4f_t temp = org;
org[0] = DotProduct (temp, mat[0]);
org[1] = DotProduct (temp, mat[1]);
org[2] = DotProduct (temp, mat[2]);
}
surf = &brush->surfaces[brush->firstmodelsurface];
for (unsigned i = 0; i < brush->nummodelsurfaces; i++, surf++) {
// find the node side on which we are
plane = surf->plane;
dot = PlaneDiff (org, plane);
// enqueue the polygon
if (((surf->flags & SURF_PLANEBACK) && (dot < -BACKFACE_EPSILON))
|| (!(surf->flags & SURF_PLANEBACK) && (dot > BACKFACE_EPSILON))) {
//chain_surface (surf, ctx);
pass->inst_id = model->render_id;
if (!pass->instances[model->render_id].entities.size) {
bsp_model_t *m = &bctx->models[model->render_id];
bsp_face_t *face = &bctx->faces[m->first_face];
for (unsigned i = 0; i < m->face_count; i++, face++) {
// enqueue the polygon
chain_surface (face, pass, bctx);
}
}
DARRAY_APPEND (&pass->instances[model->render_id].entities,
e->renderer.render_id);
return 1;
}
static inline void
@ -841,8 +853,12 @@ clear_queues (bspctx_t *bctx, bsp_pass_t *pass)
for (size_t i = 0; i < bctx->registered_textures.size; i++) {
DARRAY_RESIZE (&pass->face_queue[i], 0);
}
for (int j = 0; j < pass->num_queues; j++) {
DARRAY_RESIZE (&pass->draw_queues[j], 0);
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++) {
pass->instances[i].first_instance = -1;
DARRAY_RESIZE (&pass->instances[i].entities, 0);
}
pass->index_count = 0;
}
@ -856,29 +872,50 @@ queue_faces (bsp_pass_t *pass, bspctx_t *bctx, bspframe_t *bframe)
if (!queue->size) {
continue;
}
//FIXME implement draw_queue selection correctly (per face)
int dq = 0;
if (bctx->faces[queue->a[0].face].flags & SURF_DRAWSKY) {
dq = 1;
}
if (bctx->faces[queue->a[0].face].flags & SURF_DRAWTURB) {
dq = 2;
}
bsp_draw_t draw = {
.tex_id = i,
.instance_count = 1,
.first_index = pass->index_count,
};
for (size_t j = 0; j < queue->size; j++) {
__auto_type is = queue->a[j];
__auto_type f = bctx->faces[is.face];
if (pass->instances[is.inst_id].first_instance == -1) {
uint32_t count = pass->instances[is.inst_id].entities.size;
pass->instances[is.inst_id].first_instance = pass->entid_count;
memcpy (pass->entid_data + pass->entid_count,
pass->instances[is.inst_id].entities.a,
count * sizeof (uint32_t));
pass->entid_count += count;
}
int dq = 0;
if (bctx->faces[queue->a[0].face].flags & SURF_DRAWSKY) {
dq = 1;
}
if (bctx->faces[queue->a[0].face].flags & SURF_DRAWTURB) {
dq = 2;
}
size_t dq_size = pass->draw_queues[dq].size;
bsp_draw_t *draw = &pass->draw_queues[dq].a[dq_size - 1];
if (!pass->draw_queues[dq].size
|| draw->tex_id != f.tex_id
|| draw->inst_id != is.inst_id) {
bsp_instance_t *instance = &pass->instances[is.inst_id];
DARRAY_APPEND (&pass->draw_queues[dq], ((bsp_draw_t) {
.tex_id = i,
.inst_id = is.inst_id,
.instance_count = instance->entities.size,
.first_index = pass->index_count,
.first_instance = instance->first_instance,
}));
dq_size = pass->draw_queues[dq].size;
draw = &pass->draw_queues[dq].a[dq_size - 1];
}
memcpy (pass->indices + pass->index_count,
bctx->poly_indices + f.first_index,
f.index_count * sizeof (uint32_t));
draw.index_count += f.index_count;
draw->index_count += f.index_count;
pass->index_count += f.index_count;
}
DARRAY_APPEND (&pass->draw_queues[dq], draw);
}
bframe->index_count += pass->index_count;
}
@ -901,13 +938,11 @@ draw_queue (bsp_pass_t *pass, int queue, VkPipelineLayout layout,
}
static int
add_entity (vulkan_ctx_t *ctx, bsp_pass_t *pass, entity_t *entity)
ent_model_cmp (const void *_a, const void *_b)
{
int entid = Vulkan_Scene_AddEntity (ctx, entity);
if (entid >= 0) {
pass->entid_data[pass->entid_count++] = entid;
}
return entid >= 0;
const entity_t * const *a = _a;
const entity_t * const *b = _b;
return (*a)->renderer.model->render_id - (*b)->renderer.model->render_id;
}
void
@ -937,20 +972,29 @@ Vulkan_DrawWorld (qfv_renderframe_t *rFrame)
};
brush = &r_refdef.worldmodel->brush;
add_entity (ctx, &bctx->main_pass, &worldent);
Vulkan_Scene_AddEntity (ctx, &worldent);
int world_id = worldent.renderer.model->render_id;
bctx->main_pass.inst_id = world_id;
DARRAY_APPEND (&bctx->main_pass.instances[world_id].entities,
worldent.renderer.render_id);
R_VisitWorldNodes (brush, ctx);
if (!bctx->vertex_buffer) {
return;
}
if (r_drawentities) {
heapsort (r_ent_queue->ent_queues[mod_brush].a,
r_ent_queue->ent_queues[mod_brush].size,
sizeof (entity_t *), ent_model_cmp);
for (size_t i = 0; i < r_ent_queue->ent_queues[mod_brush].size; i++) {
entity_t *ent = r_ent_queue->ent_queues[mod_brush].a[i];
if (add_entity (ctx, &bctx->main_pass, ent)) {
R_DrawBrushModel (ent, ctx);
if (!R_DrawBrushModel (ent, &bctx->main_pass, ctx)) {
Sys_Printf ("Too many entities!\n");
break;
}
}
}
bframe->entid_count = bctx->main_pass.entid_count;
queue_faces (&bctx->main_pass, bctx, bframe);
@ -1188,7 +1232,7 @@ Vulkan_Bsp_Init (vulkan_ctx_t *ctx)
DARRAY_INIT (&bctx->registered_textures, 64);
bctx->main_pass.num_queues = 3;
bctx->main_pass.num_queues = 3;//solid, sky, water
bctx->main_pass.draw_queues = malloc (bctx->main_pass.num_queues
* sizeof (bsp_drawset_t));
for (int i = 0; i < bctx->main_pass.num_queues; i++) {
@ -1208,7 +1252,8 @@ Vulkan_Bsp_Init (vulkan_ctx_t *ctx)
bctx->layout = Vulkan_CreatePipelineLayout (ctx, "quakebsp_layout");
bctx->sampler = Vulkan_CreateSampler (ctx, "quakebsp_sampler");
size_t entid_size = Vulkan_Scene_MaxEntities (ctx) * sizeof (uint32_t);
size_t entid_count = Vulkan_Scene_MaxEntities (ctx);
size_t entid_size = entid_count * sizeof (uint32_t);
size_t atom = device->physDev->properties.limits.nonCoherentAtomSize;
size_t atom_mask = atom - 1;
entid_size = (entid_size + atom_mask) & ~atom_mask;
@ -1245,7 +1290,7 @@ Vulkan_Bsp_Init (vulkan_ctx_t *ctx)
va (ctx->va_ctx, "cmd:bsp:%zd:%s", i,
bsp_pass_names[j]));
}
bframe->entid_data = entid_data + i * entid_size;
bframe->entid_data = entid_data + i * entid_count;
bframe->entid_offset = i * entid_size;
}
@ -1273,12 +1318,22 @@ Vulkan_Bsp_Shutdown (struct vulkan_ctx_s *ctx)
dfunc->vkDestroyPipeline (device->dev, bctx->skybox, 0);
dfunc->vkDestroyPipeline (device->dev, bctx->skysheet, 0);
dfunc->vkDestroyPipeline (device->dev, bctx->turb, 0);
DARRAY_CLEAR (&bctx->registered_textures);
for (int i = 0; i < bctx->main_pass.num_queues; i++) {
DARRAY_CLEAR (&bctx->main_pass.draw_queues[i]);
}
free (bctx->faces);
free (bctx->models);
free (bctx->main_pass.draw_queues);
for (int i = 0; i < bctx->model_id; i++) {
DARRAY_CLEAR (&bctx->main_pass.instances[i].entities);
}
free (bctx->main_pass.instances);
DARRAY_CLEAR (&bctx->frames);
QFV_DestroyStagingBuffer (bctx->light_stage);
QFV_DestroyScrap (bctx->light_scrap);
if (bctx->vertex_buffer) {

View file

@ -56,6 +56,7 @@
#include "QF/Vulkan/qf_lightmap.h"
#include "QF/Vulkan/qf_main.h"
#include "QF/Vulkan/qf_particles.h"
#include "QF/Vulkan/qf_scene.h"
#include "QF/Vulkan/qf_sprite.h"
//#include "QF/Vulkan/qf_textures.h"
#include "QF/Vulkan/renderpass.h"
@ -128,6 +129,7 @@ Vulkan_RenderView (qfv_renderframe_t *rFrame)
}
Vulkan_DrawWaterSurfaces (rFrame);
Vulkan_Bsp_Flush (ctx);
Vulkan_Scene_Flush (ctx);
}
void

View file

@ -101,6 +101,16 @@ Vulkan_Scene_AddEntity (vulkan_ctx_t *ctx, entity_t *entity)
return entity->renderer.render_id;
}
void
Vulkan_Scene_Flush (vulkan_ctx_t *ctx)
{
scenectx_t *sctx = ctx->scene_context;
scnframe_t *sframe = &sctx->frames.a[ctx->curFrame];
set_empty (sframe->pooled_entities);
sframe->entity_pool.size = 0;
}
static VkWriteDescriptorSet base_buffer_write = {
VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, 0, 0,
0, 0, 1,