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:
parent
0d54d32dd6
commit
39f7a81176
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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*);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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*);
|
||||
|
|
Loading…
Reference in New Issue