Gawain: batch rendering API

Follow-up to rBddb1d5648dbd

API is nearly complete but untested.

1) create batch with vertex buffer & optional index buffer
2) choose shader program
3) draw!
This commit is contained in:
Mike Erwin 2016-09-15 16:51:10 +02:00
parent 0d54d32dd6
commit 39f7a81176
7 changed files with 211 additions and 125 deletions

View File

@ -62,6 +62,8 @@ set(SRC
gawain/attrib_binding.c
gawain/attrib_binding.h
gawain/batch.c
gawain/batch.h
gawain/common.h
gawain/element.c
gawain/element.h
@ -95,6 +97,7 @@ set(SRC
shaders/gpu_shader_smoke_vert.glsl
GPU_basic_shader.h
GPU_batch.h
GPU_buffers.h
GPU_compositing.h
GPU_debug.h

View File

@ -10,9 +10,107 @@
// the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
#include "batch.h"
#include <stdlib.h>
// BasicBatches
// Vertex buffer with 3D pos only
// Index buffer for edges (lines)
// Index buffer for surface (triangles)
// glGenBuffers(3,xxx)
Batch* Batch_create(GLenum prim_type, VertexBuffer* verts, ElementList* elem)
{
#if TRUST_NO_ONE
assert(verts != NULL);
assert(prim_type == GL_POINTS || prim_type == GL_LINES || prim_type == GL_TRIANGLES);
// we will allow other primitive types in a future update
#endif
Batch* batch = calloc(1, sizeof(Batch));
batch->verts = verts;
batch->elem = elem;
batch->prim_type = prim_type;
return batch;
}
void Batch_set_program(Batch* batch, GLuint program)
{
batch->program = program;
batch->program_dirty = true;
}
static void Batch_update_program_bindings(Batch* batch)
{
#if TRUST_NO_ONE
assert(glIsProgram(program));
#endif
const VertexFormat* format = &batch->verts->format;
const unsigned attrib_ct = format->attrib_ct;
const unsigned stride = format->stride;
for (unsigned a_idx = 0; a_idx < attrib_ct; ++a_idx)
{
const Attrib* a = format->attribs + a_idx;
const GLvoid* pointer = (const GLubyte*)0 + a->offset;
const unsigned loc = glGetAttribLocation(batch->program, a->name);
glEnableVertexAttribArray(loc);
switch (a->fetch_mode)
{
case KEEP_FLOAT:
case CONVERT_INT_TO_FLOAT:
glVertexAttribPointer(loc, a->comp_ct, a->comp_type, GL_FALSE, stride, pointer);
break;
case NORMALIZE_INT_TO_FLOAT:
glVertexAttribPointer(loc, a->comp_ct, a->comp_type, GL_TRUE, stride, pointer);
break;
case KEEP_INT:
glVertexAttribIPointer(loc, a->comp_ct, a->comp_type, stride, pointer);
}
}
batch->program_dirty = false;
}
static void Batch_prime(Batch* batch)
{
glGenVertexArrays(1, &batch->vao_id);
glBindVertexArray(batch->vao_id);
VertexBuffer_use(batch->verts);
if (batch->elem)
ElementList_use(batch->elem);
// vertex attribs and element list remain bound to this VAO
}
void Batch_draw(Batch* batch)
{
if (batch->vao_id)
glBindVertexArray(batch->vao_id);
else
Batch_prime(batch);
if (batch->program_dirty)
Batch_update_program_bindings(batch);
if (batch->elem)
{
const ElementList* el = batch->elem;
#if TRACK_INDEX_RANGE
if (el->base_index)
glDrawRangeElementsBaseVertex(batch->prim_type, el->min_index, el->max_index, el->index_ct, el->index_type, 0, el->base_index);
else
glDrawRangeElements(batch->prim_type, el->min_index, el->max_index, el->index_ct, el->index_type, 0);
#else
glDrawElements(batch->prim_type, el->index_ct, GL_UNSIGNED_INT, 0);
#endif
}
else
glDrawArrays(batch->prim_type, 0, batch->verts->vertex_ct);
glBindVertexArray(0);
}

View File

@ -13,34 +13,53 @@
#include "vertex_buffer.h"
#include "element.h"
#include "attrib_binding.h"
// How will this API be used?
// create batch
// ...
// profit!
// TODO: finalize Batch struct design & usage, pare down this file
typedef struct {
VertexBuffer; // format is fixed at "vec3 pos"
ElementList line_elem;
ElementList triangle_elem;
GLuint vao_id;
GLenum prev_prim; // did most recent draw use GL_POINTS, GL_LINES or GL_TRIANGLES?
} BasicBatch;
// How to do this without replicating code?
typedef struct {
// geometry
VertexBuffer* verts;
ElementList* elem; // <-- NULL if element list not needed
ElementList* elem; // NULL if element list not needed
GLenum prim_type;
GLuint vao_id;
GLuint bound_program;
AttribBinding attrib_binding;
// book-keeping
GLuint vao_id; // remembers all geometry state (vertex attrib bindings & element buffer)
bool program_dirty;
// state
GLuint program;
} Batch;
Batch* Batch_create(GLenum prim_type, VertexBuffer*, ElementList*);
void Batch_set_program(Batch*, GLuint program);
// Entire batch draws with one shader program, but can be redrawn later with another program.
// Vertex shader's inputs must be compatible with the batch's vertex format.
void Batch_draw(Batch*);
#if 0 // future plans
// Can multiple batches share a VertexBuffer? Use ref count?
// for multithreaded batch building:
typedef enum {
READY_TO_FORMAT,
READY_TO_BUILD,
BUILDING, BUILDING_IMM, // choose one
READY_TO_DRAW
} BatchPhase;
Batch* immBeginBatch(GLenum prim_type, unsigned v_ct);
// use standard immFunctions after this. immEnd will finalize the batch instead
// of drawing.
// We often need a batch with its own data, to be created and discarded together.
// WithOwn variants reduce number of system allocations.
@ -67,69 +86,4 @@ Batch* create_BatchWithOwnVertexBufferAndElementList(GLenum prim_type, VertexFor
// elem: none, shared, own
Batch* create_BatchInGeneral(GLenum prim_type, VertexBufferStuff, ElementListStuff);
typedef struct {
// geometry
GLenum prim_type;
VertexBuffer verts;
ElementList elem; // <-- elem.index_ct = 0 if element list not needed
// book-keeping
GLuint vao_id; // <-- remembers all vertex state (array buffer, element buffer, attrib bindings)
// wait a sec... I thought VAO held attrib bindings but not currently bound array buffer.
// That's fine but verify that VAO holds *element* buffer binding.
// Verified: ELEMENT_ARRAY_BUFFER_BINDING is part of VAO state.
// VERTEX_ATTRIB_ARRAY_BUFFER_BINDING is too, per vertex attrib. Currently bound ARRAY_BUFFER is not.
// Does APPLE_vertex_array_object also include ELEMENT_ARRAY_BUFFER_BINDING?
// The extension spec refers only to APPLE_element_array, so.. maybe, maybe not?
// Will have to test during development, maybe alter behavior for APPLE_LEGACY. Can strip out this
// platform-specific cruft for Blender, keep it for legacy Gawain.
// state
GLuint bound_program;
AttribBinding attrib_binding;
} Batch;
typedef struct {
Batch* batch;
} BatchBuilder;
// One batch can be drawn with multiple shaders, as long as those shaders' inputs
// are compatible with the batch's vertex format.
// Can multiple batches share a VertexBuffer? Use ref count?
// BasicBatch
// Create one VertexBuffer from an object's verts (3D position only)
// Shader must depend only on position + uniforms: uniform color, depth only, or object ID.
// - draw verts via DrawArrays
// - draw lines via DrawElements (can have 2 element lists: true face edges, triangulated edges)
// - draw faces via DrawElements (raw triangles, not polygon faces)
// This is very 3D-mesh-modeling specific. I'm investigating what Gawain needs to allow/expose
// to meet Blender's needs, possibly other programs' needs.
Batch* BatchPlease(GLenum prim_type, unsigned prim_ct, unsigned v_ct);
// GL_TRIANGLES 12 triangles that share 8 vertices
// Is there ever a reason to index GL_POINTS? nothing comes to mind...
// (later) ok now that I'm thinking straight, *of course* you can draw
// indexed POINTS. Only some verts from the buffer will be drawn. I was
// just limiting my thinking to immediate needs. Batched needs.
Batch* batch = BatchPlease(GL_TRIANGLES, 12, 8);
unsigned pos = add_attrib(batch->verts.format, "pos", GL_FLOAT, 3, KEEP_FLOAT);
pack(batch->verts->format); // or ...
finalize(batch); // <-- packs vertex format, allocates vertex buffer
Batch* create_Batch(GLenum prim_type, VertexBuffer*, ElementList*);
// and don't forget
Batch* immBeginBatch(GLenum prim_type, unsigned v_ct);
// use standard immFunctions after this. immEnd will finalize the batch instead
// of drawing.
typedef enum {
READY_TO_FORMAT,
READY_TO_BUILD,
BUILDING, BUILDING_IMM, // choose one
READY_TO_DRAW
} BatchPhase;
#endif // future plans

View File

@ -16,14 +16,34 @@
unsigned ElementList_size(const ElementList* elem)
{
#if TRACK_INDEX_RANGE
switch (elem->index_type)
{
case GL_UNSIGNED_BYTE: return elem->index_ct * sizeof(GLubyte);
case GL_UNSIGNED_SHORT: return elem->index_ct * sizeof(GLushort);
case GL_UNSIGNED_INT: return elem->index_ct * sizeof(GLuint);
default:
assert(false);
return 0;
}
return 0;
#else
return elem->index_ct * sizeof(GLuint);
#endif
}
static void ElementList_prime(ElementList* elem)
{
glGenBuffers(1, &elem->vbo_id);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elem->vbo_id);
// fill with delicious data & send to GPU the first time only
glBufferData(GL_ELEMENT_ARRAY_BUFFER, ElementList_size(elem), elem->data, GL_STATIC_DRAW);
#if KEEP_SINGLE_COPY
// now that GL has a copy, discard original
free(elem->data);
elem->data = NULL;
#endif
}
void ElementList_use(ElementList* elem)
@ -31,28 +51,12 @@ void ElementList_use(ElementList* elem)
if (elem->vbo_id)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elem->vbo_id);
else
{
glGenBuffers(1, &elem->vbo_id);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elem->vbo_id);
// fill with delicious data & send to GPU the first time only
glBufferData(GL_ELEMENT_ARRAY_BUFFER, ElementList_size(elem), elem->data, GL_STATIC_DRAW);
#if KEEP_SINGLE_COPY
// now that GL has a copy, discard original
free(elem->data);
elem->data = NULL;
#endif
}
ElementList_prime(elem);
}
void ElementList_done_using()
void ElementListBuilder_init(ElementListBuilder* builder, GLenum prim_type, unsigned prim_ct, unsigned vertex_ct)
{
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
void init_ElementListBuilder(ElementListBuilder* builder, GLenum prim_type, unsigned prim_ct, unsigned vertex_ct)
{
unsigned verts_per_prim;
unsigned verts_per_prim = 0;
switch (prim_type)
{
case GL_POINTS:
@ -195,7 +199,7 @@ static void squeeze_indices_short(const unsigned values[], ElementList* elem)
#endif // TRACK_INDEX_RANGE
void build_ElementList(ElementListBuilder* builder, ElementList* elem)
void ElementList_build(ElementListBuilder* builder, ElementList* elem)
{
#if TRUST_NO_ONE
assert(builder->data != NULL);

View File

@ -28,7 +28,6 @@ typedef struct {
} ElementList;
void ElementList_use(ElementList*);
void ElementList_done_using(void);
unsigned ElementList_size(const ElementList*);
typedef struct {
@ -44,8 +43,8 @@ typedef struct {
// GL_LINES
// GL_TRIANGLES
void init_ElementListBuilder(ElementListBuilder*, GLenum prim_type, unsigned prim_ct, unsigned vertex_ct);
//void init_CustomElementListBuilder(ElementListBuilder*, GLenum prim_type, unsigned index_ct, unsigned vertex_ct);
void ElementListBuilder_init(ElementListBuilder*, GLenum prim_type, unsigned prim_ct, unsigned vertex_ct);
//void ElementListBuilder_init_custom(ElementListBuilder*, GLenum prim_type, unsigned index_ct, unsigned vertex_ct);
void add_generic_vertex(ElementListBuilder*, unsigned v);
@ -53,4 +52,4 @@ void add_point_vertex(ElementListBuilder*, unsigned v);
void add_line_vertices(ElementListBuilder*, unsigned v1, unsigned v2);
void add_triangle_vertices(ElementListBuilder*, unsigned v1, unsigned v2, unsigned v3);
void build_ElementList(ElementListBuilder*, ElementList*);
void ElementList_build(ElementListBuilder*, ElementList*);

View File

@ -13,19 +13,21 @@
#include <stdlib.h>
#include <string.h>
VertexBuffer* create_VertexBuffer()
#define KEEP_SINGLE_COPY 1
VertexBuffer* VertexBuffer_create()
{
VertexBuffer* verts = malloc(sizeof(VertexBuffer));
init_VertexBuffer(verts);
VertexBuffer_init(verts);
return verts;
}
void init_VertexBuffer(VertexBuffer* verts)
void VertexBuffer_init(VertexBuffer* verts)
{
memset(verts, 0, sizeof(VertexBuffer));
}
void allocate_vertex_data(VertexBuffer* verts, unsigned v_ct)
void VertexBuffer_allocate_data(VertexBuffer* verts, unsigned v_ct)
{
VertexFormat* format = &verts->format;
if (!format->packed)
@ -89,3 +91,27 @@ void fillAttribStride(VertexBuffer* verts, unsigned a_idx, unsigned stride, cons
memcpy((GLubyte*)verts->data + a->offset + v * format->stride, (const GLubyte*)data + v * stride, a->sz);
}
}
static void VertexBuffer_prime(VertexBuffer* verts)
{
const VertexFormat* format = &verts->format;
glGenBuffers(1, &verts->vbo_id);
glBindBuffer(GL_ARRAY_BUFFER, verts->vbo_id);
// fill with delicious data & send to GPU the first time only
glBufferData(GL_ARRAY_BUFFER, vertex_buffer_size(format, verts->vertex_ct), verts->data, GL_STATIC_DRAW);
#if KEEP_SINGLE_COPY
// now that GL has a copy, discard original
free(verts->data);
verts->data = NULL;
#endif
}
void VertexBuffer_use(VertexBuffer* verts)
{
if (verts->vbo_id)
glBindBuffer(GL_ARRAY_BUFFER, verts->vbo_id);
else
VertexBuffer_prime(verts);
}

View File

@ -29,13 +29,13 @@ typedef struct {
GLuint vbo_id; // 0 indicates not yet sent to VRAM
} VertexBuffer;
VertexBuffer* create_VertexBuffer(void); // create means allocate, then init
void init_VertexBuffer(VertexBuffer*);
VertexBuffer* VertexBuffer_create(void); // create means allocate, then init
void VertexBuffer_init(VertexBuffer*);
// TODO: use copy of existing format
// void init_VertexBuffer_with_format(VertexBuffer*, VertexFormat*);
void allocate_vertex_data(VertexBuffer*, unsigned v_ct);
void VertexBuffer_allocate_data(VertexBuffer*, unsigned v_ct);
// The most important setAttrib variant is the untyped one. Get it right first.
// It takes a void* so the app developer is responsible for matching their app data types
@ -56,3 +56,5 @@ void fillAttribStride(VertexBuffer*, unsigned a_idx, unsigned stride, const void
//
// void setAttrib3ub(unsigned a_idx, unsigned v_idx, unsigned char r, unsigned char g, unsigned char b);
// void setAttrib4ub(unsigned a_idx, unsigned v_idx, unsigned char r, unsigned char g, unsigned char b, unsigned char a);
void VertexBuffer_use(VertexBuffer*);