DRW: Add instance buffer manager.

This manager allows to distribute existing batches for instancing
attributes. This reduce the number of batches creation.
Querying a batch is done with a vertex format. This format should
be static so that it's pointer never changes (because we are using
this pointer as identifier [we don't want to check the full format
that would be too slow]).

This might make the original Instance Data manager useless but it's currently used by DRW_object_engine_data_ensure().
This commit is contained in:
Clément Foucault 2018-02-14 18:16:52 +01:00
parent ab7e7a005b
commit 629a874817
4 changed files with 150 additions and 1 deletions

View File

@ -336,6 +336,7 @@ void DRW_shgroup_call_dynamic_add_array(DRWShadingGroup *shgroup, const void *at
} while (0)
/* Use this to set a high number of instances. */
void DRW_shgroup_set_instance_count(DRWShadingGroup *shgroup, int count);
unsigned int DRW_shgroup_get_instance_count(const DRWShadingGroup *shgroup);
void DRW_shgroup_state_enable(DRWShadingGroup *shgroup, DRWState state);
void DRW_shgroup_state_disable(DRWShadingGroup *shgroup, DRWState state);

View File

@ -34,10 +34,21 @@
#include "draw_instance_data.h"
#include "DRW_engine.h"
#include "DRW_render.h" /* For DRW_shgroup_get_instance_count() */
#include "MEM_guardedalloc.h"
#include "BLI_utildefines.h"
#define BUFFER_CHUNK_SIZE 32
#define BUFFER_VERTS_CHUNK 32
typedef struct DRWInstanceBuffer {
struct DRWShadingGroup *shgroup; /* Link back to the owning shGroup. Also tells if it's used */
Gwn_VertFormat *format; /* Identifier. */
Gwn_VertBuf *vert; /* Gwn_VertBuf contained in the Gwn_Batch. */
Gwn_Batch *batch; /* Gwn_Batch containing the Gwn_VertBuf. */
} DRWInstanceBuffer;
struct DRWInstanceData {
struct DRWInstanceData *next;
bool used; /* If this data is used or not. */
@ -54,10 +65,118 @@ struct DRWInstanceDataList {
* This is done to minimize the reattribution misses. */
DRWInstanceData *idata_head[MAX_INSTANCE_DATA_SIZE];
DRWInstanceData *idata_tail[MAX_INSTANCE_DATA_SIZE];
struct {
size_t cursor; /* Offset to the next instance data. */
size_t alloc_size; /* Number of DRWInstanceBuffer alloc'd in ibufs. */
DRWInstanceBuffer *ibufs;
} ibuffers;
};
/* -------------------------------------------------------------------- */
/** \name Instance Buffer Management
* \{ */
/**
* This manager allows to distribute existing batches for instancing
* attributes. This reduce the number of batches creation.
* Querying a batch is done with a vertex format. This format should
* be static so that it's pointer never changes (because we are using
* this pointer as identifier [we don't want to check the full format
* that would be too slow]).
**/
void DRW_instance_buffer_request(
DRWInstanceDataList *idatalist, Gwn_VertFormat *format, struct DRWShadingGroup *shgroup,
Gwn_Batch **r_batch, Gwn_VertBuf **r_vert, Gwn_PrimType type)
{
BLI_assert(format);
DRWInstanceBuffer *ibuf = idatalist->ibuffers.ibufs;
int first_non_alloced = -1;
/* Search for an unused batch. */
for (int i = 0; i < idatalist->ibuffers.alloc_size; i++, ibuf++) {
if (ibuf->shgroup == NULL) {
if (ibuf->format == format) {
ibuf->shgroup = shgroup;
*r_batch = ibuf->batch;
*r_vert = ibuf->vert;
return;
}
else if (ibuf->format == NULL && first_non_alloced == -1) {
first_non_alloced = i;
}
}
}
if (first_non_alloced == -1) {
/* There is no batch left. Allocate more. */
first_non_alloced = idatalist->ibuffers.alloc_size;
idatalist->ibuffers.alloc_size += BUFFER_CHUNK_SIZE;
idatalist->ibuffers.ibufs = MEM_reallocN(idatalist->ibuffers.ibufs,
idatalist->ibuffers.alloc_size * sizeof(DRWInstanceBuffer));
/* Clear new part of the memory. */
memset(idatalist->ibuffers.ibufs + first_non_alloced, 0, sizeof(DRWInstanceBuffer) * BUFFER_CHUNK_SIZE);
}
/* Create the batch. */
ibuf = idatalist->ibuffers.ibufs + first_non_alloced;
ibuf->vert = *r_vert = GWN_vertbuf_create_dynamic_with_format(format);
ibuf->batch = *r_batch = GWN_batch_create_ex(type, ibuf->vert, NULL, GWN_BATCH_OWNS_VBO);
ibuf->format = format;
ibuf->shgroup = shgroup;
GWN_vertbuf_data_alloc(*r_vert, BUFFER_VERTS_CHUNK);
}
void DRW_instance_buffer_finish(DRWInstanceDataList *idatalist)
{
DRWInstanceBuffer *ibuf = idatalist->ibuffers.ibufs;
size_t minimum_alloc_size = 1; /* Avoid 0 size realloc. */
/* Resize down buffers in use and send data to GPU & free unused buffers. */
for (int i = 0; i < idatalist->ibuffers.alloc_size; i++, ibuf++) {
if (ibuf->shgroup != NULL) {
minimum_alloc_size = i + 1;
unsigned int vert_ct = DRW_shgroup_get_instance_count(ibuf->shgroup);
/* Do not realloc to 0 size buffer */
vert_ct += (vert_ct == 0) ? 1 : 0;
/* Resize buffer to reclame space. */
if (vert_ct + BUFFER_VERTS_CHUNK <= ibuf->vert->vertex_ct) {
unsigned int size = vert_ct + BUFFER_VERTS_CHUNK - 1;
size = size - size % BUFFER_VERTS_CHUNK;
GWN_vertbuf_data_resize(ibuf->vert, size);
}
/* Send data. */
GWN_vertbuf_use(ibuf->vert);
/* Set as non used for the next round. */
ibuf->shgroup = NULL;
}
else {
GWN_BATCH_DISCARD_SAFE(ibuf->batch);
/* Tag as non alloced. */
ibuf->format = NULL;
}
}
/* Resize down the handle buffer (ibuffers). */
/* Rounding up to nearest chunk size. */
minimum_alloc_size += BUFFER_CHUNK_SIZE - 1;
minimum_alloc_size -= minimum_alloc_size % BUFFER_CHUNK_SIZE;
/* Resize down if necessary. */
if (minimum_alloc_size < idatalist->ibuffers.alloc_size) {
idatalist->ibuffers.alloc_size = minimum_alloc_size;
idatalist->ibuffers.ibufs = MEM_reallocN(idatalist->ibuffers.ibufs,
minimum_alloc_size * sizeof(DRWInstanceBuffer));
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Instance Data (DRWInstanceData)
* \{ */
@ -143,11 +262,16 @@ DRWInstanceData *DRW_instance_data_request(
DRWInstanceDataList *DRW_instance_data_list_create(void)
{
return MEM_callocN(sizeof(DRWInstanceDataList), "DRWInstanceDataList");
DRWInstanceDataList *idatalist = MEM_callocN(sizeof(DRWInstanceDataList), "DRWInstanceDataList");
idatalist->ibuffers.ibufs = MEM_callocN(sizeof(DRWInstanceBuffer) * BUFFER_CHUNK_SIZE, "DRWInstanceBuffers");
idatalist->ibuffers.alloc_size = BUFFER_CHUNK_SIZE;
return idatalist;
}
void DRW_instance_data_list_free(DRWInstanceDataList *idatalist)
{
DRWInstanceBuffer *ibuf = idatalist->ibuffers.ibufs;
DRWInstanceData *idata, *next_idata;
for (int i = 0; i < MAX_INSTANCE_DATA_SIZE; ++i) {
@ -159,6 +283,11 @@ void DRW_instance_data_list_free(DRWInstanceDataList *idatalist)
idatalist->idata_head[i] = NULL;
idatalist->idata_tail[i] = NULL;
}
for (int i = 0; i < idatalist->ibuffers.alloc_size; i++, ibuf++) {
GWN_BATCH_DISCARD_SAFE(ibuf->batch);
}
MEM_freeN(idatalist->ibuffers.ibufs);
}
void DRW_instance_data_list_reset(DRWInstanceDataList *idatalist)

View File

@ -26,16 +26,30 @@
#ifndef __DRAW_INSTANCE_DATA_H__
#define __DRAW_INSTANCE_DATA_H__
#include "BLI_compiler_attrs.h"
#include "BLI_sys_types.h"
#include "GPU_batch.h"
#define MAX_INSTANCE_DATA_SIZE 42 /* Can be adjusted for more */
typedef struct DRWInstanceData DRWInstanceData;
typedef struct DRWInstanceDataList DRWInstanceDataList;
struct DRWShadingGroup;
void *DRW_instance_data_next(DRWInstanceData *idata);
void *DRW_instance_data_get(DRWInstanceData *idata);
DRWInstanceData *DRW_instance_data_request(
DRWInstanceDataList *idatalist, unsigned int attrib_size, unsigned int instance_group);
void DRW_instance_buffer_request(
DRWInstanceDataList *idatalist, Gwn_VertFormat *format, struct DRWShadingGroup *shgroup,
Gwn_Batch **r_batch, Gwn_VertBuf **r_vert, Gwn_PrimType type);
/* Upload all instance data to the GPU as soon as possible. */
void DRW_instance_buffer_finish(DRWInstanceDataList *idatalist);
void DRW_instance_data_list_reset(DRWInstanceDataList *idatalist);
void DRW_instance_data_list_free_unused(DRWInstanceDataList *idatalist);
void DRW_instance_data_list_resize(DRWInstanceDataList *idatalist);

View File

@ -1074,6 +1074,11 @@ void DRW_shgroup_set_instance_count(DRWShadingGroup *shgroup, int count)
interface->instance_count = count;
}
unsigned int DRW_shgroup_get_instance_count(const DRWShadingGroup *shgroup)
{
return shgroup->interface.instance_count;
}
/**
* State is added to #Pass.state while drawing.
* Use to temporarily enable draw options.