Gawain: geometry batches (unfinished)

Vertex Buffer to store vertex attribute data.
Element List (AKA Index Buffer) to select which vertices to use.
Batch combines these into an object that can be built once then drawn
many times.

Porting over from the C++ version… Most of this C code is compiled but
unused. Some of it is not even compiled. Committing now in case I’m
lost at sea.
This commit is contained in:
Mike Erwin 2016-09-13 02:41:43 -04:00
parent b6bd299359
commit ddb1d5648d
Notes: blender-bot 2023-02-13 12:02:23 +01:00
Referenced by commit 39f7a81176, Gawain: batch rendering API
8 changed files with 554 additions and 0 deletions

View File

@ -63,8 +63,12 @@ set(SRC
gawain/attrib_binding.c
gawain/attrib_binding.h
gawain/common.h
gawain/element.c
gawain/element.h
gawain/immediate.c
gawain/immediate.h
gawain/vertex_buffer.c
gawain/vertex_buffer.h
gawain/vertex_format.c
gawain/vertex_format.h

View File

@ -0,0 +1,33 @@
/*
* ***** BEGIN GPL LICENSE BLOCK *****
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2016 Blender Foundation.
* All rights reserved.
*
*
* Contributor(s): Mike Erwin
*
* ***** END GPL LICENSE BLOCK *****
*/
/* Batched geometry rendering is powered by the Gawain library.
* This file contains any additions or modifications specific to Blender.
*/
#pragma once
#include "gawain/batch.h"

View File

@ -0,0 +1,18 @@
// Gawain geometry batch
//
// This code is part of the Gawain library, with modifications
// specific to integration with Blender.
//
// Copyright 2016 Mike Erwin
//
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
// the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
#include "batch.h"
// BasicBatches
// Vertex buffer with 3D pos only
// Index buffer for edges (lines)
// Index buffer for surface (triangles)
// glGenBuffers(3,xxx)

View File

@ -0,0 +1,135 @@
// Gawain geometry batch
//
// This code is part of the Gawain library, with modifications
// specific to integration with Blender.
//
// Copyright 2016 Mike Erwin
//
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
// the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
#pragma once
#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 {
VertexBuffer* verts;
ElementList* elem; // <-- NULL if element list not needed
GLenum prim_type;
GLuint vao_id;
GLuint bound_program;
AttribBinding attrib_binding;
} Batch;
// We often need a batch with its own data, to be created and discarded together.
// WithOwn variants reduce number of system allocations.
typedef struct {
Batch batch;
VertexBuffer verts; // link batch.verts to this
} BatchWithOwnVertexBuffer;
typedef struct {
Batch batch;
ElementList elem; // link batch.elem to this
} BatchWithOwnElementList;
typedef struct {
Batch batch;
ElementList elem; // link batch.elem to this
VertexBuffer verts; // link batch.verts to this
} BatchWithOwnVertexBufferAndElementList;
Batch* create_BatchWithOwnVertexBuffer(GLenum prim_type, VertexFormat*, unsigned v_ct, ElementList*);
Batch* create_BatchWithOwnElementList(GLenum prim_type, VertexBuffer*, unsigned prim_ct);
Batch* create_BatchWithOwnVertexBufferAndElementList(GLenum prim_type, VertexFormat*, unsigned v_ct, unsigned prim_ct);
// verts: shared, own
// 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;

View File

@ -0,0 +1,253 @@
// Gawain element list (AKA index buffer)
//
// This code is part of the Gawain library, with modifications
// specific to integration with Blender.
//
// Copyright 2016 Mike Erwin
//
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
// the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
#include "element.h"
#include <stdlib.h>
#define KEEP_SINGLE_COPY 1
unsigned ElementList_size(const ElementList* elem)
{
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);
}
return 0;
}
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
}
}
void ElementList_done_using()
{
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
void init_ElementListBuilder(ElementListBuilder* builder, GLenum prim_type, unsigned prim_ct, unsigned vertex_ct)
{
unsigned verts_per_prim;
switch (prim_type)
{
case GL_POINTS:
verts_per_prim = 1;
break;
case GL_LINES:
verts_per_prim = 2;
break;
case GL_TRIANGLES:
verts_per_prim = 3;
break;
default:
assert(false);
}
builder->max_allowed_index = vertex_ct - 1;
builder->max_index_ct = prim_ct * verts_per_prim;
builder->index_ct = 0; // start empty
builder->prim_type = prim_type;
builder->data = calloc(builder->max_index_ct, sizeof(unsigned));
}
void add_generic_vertex(ElementListBuilder* builder, unsigned v)
{
#if TRUST_NO_ONE
assert(builder->data != NULL);
assert(builder->index_ct < builder->max_index_ct);
assert(v <= builder->max_allowed_index);
#endif
builder->data[builder->index_ct++] = v;
}
void add_point_vertex(ElementListBuilder* builder, unsigned v)
{
#if TRUST_NO_ONE
assert(builder->prim_type == GL_POINTS);
#endif
add_generic_vertex(builder, v);
}
void add_line_vertices(ElementListBuilder* builder, unsigned v1, unsigned v2)
{
#if TRUST_NO_ONE
assert(builder->prim_type == GL_LINES);
assert(v1 != v2);
#endif
add_generic_vertex(builder, v1);
add_generic_vertex(builder, v2);
}
void add_triangle_vertices(ElementListBuilder* builder, unsigned v1, unsigned v2, unsigned v3)
{
#if TRUST_NO_ONE
assert(builder->prim_type == GL_TRIANGLES);
assert(v1 != v2 && v2 != v3 && v3 != v1);
#endif
add_generic_vertex(builder, v1);
add_generic_vertex(builder, v2);
add_generic_vertex(builder, v3);
}
#if TRACK_INDEX_RANGE
// Everything remains 32 bit while building to keep things simple.
// Find min/max after, then convert to smallest index type possible.
static unsigned index_range(const unsigned values[], unsigned value_ct, unsigned* min_out, unsigned* max_out)
{
unsigned min_value = values[0];
unsigned max_value = values[0];
for (unsigned i = 1; i < value_ct; ++i)
{
const unsigned value = values[i];
if (value < min_value)
min_value = value;
else if (value > max_value)
max_value = value;
}
*min_out = min_value;
*max_out = max_value;
return max_value - min_value;
}
static void squeeze_indices_byte(const unsigned values[], ElementList* elem)
{
const unsigned index_ct = elem->index_ct;
GLubyte* data = malloc(index_ct * sizeof(GLubyte));
if (elem->max_index > 0xFF)
{
const unsigned base = elem->min_index;
elem->base_index = base;
elem->min_index = 0;
elem->max_index -= base;
for (unsigned i = 0; i < index_ct; ++i)
data[i] = (GLubyte)(values[i] - base);
}
else
{
elem->base_index = 0;
for (unsigned i = 0; i < index_ct; ++i)
data[i] = (GLubyte)(values[i]);
}
elem->data = data;
}
static void squeeze_indices_short(const unsigned values[], ElementList* elem)
{
const unsigned index_ct = elem->index_ct;
GLushort* data = malloc(index_ct * sizeof(GLushort));
if (elem->max_index > 0xFFFF)
{
const unsigned base = elem->min_index;
elem->base_index = base;
elem->min_index = 0;
elem->max_index -= base;
for (unsigned i = 0; i < index_ct; ++i)
data[i] = (GLushort)(values[i] - base);
}
else
{
elem->base_index = 0;
for (unsigned i = 0; i < index_ct; ++i)
data[i] = (GLushort)(values[i]);
}
elem->data = data;
}
#endif // TRACK_INDEX_RANGE
void build_ElementList(ElementListBuilder* builder, ElementList* elem)
{
#if TRUST_NO_ONE
assert(builder->data != NULL);
#endif
elem->index_ct = builder->index_ct;
#if TRACK_INDEX_RANGE
const unsigned range = index_range(builder->data, builder->index_ct, &elem->min_index, &elem->max_index);
if (range <= 0xFF)
{
elem->index_type = GL_UNSIGNED_BYTE;
squeeze_indices_byte(builder->data, elem);
}
else if (range <= 0xFFFF)
{
elem->index_type = GL_UNSIGNED_SHORT;
squeeze_indices_short(builder->data, elem);
}
else
{
elem->index_type = GL_UNSIGNED_INT;
elem->base_index = 0;
if (builder->index_ct < builder->max_index_ct)
{
builder->data = realloc(builder->data, builder->index_ct * sizeof(unsigned));
// TODO: realloc only if index_ct is much smaller than max_index_ct
}
elem->data = builder->data;
}
#else
if (builder->index_ct < builder->max_index_ct)
{
builder->data = realloc(builder->data, builder->index_ct * sizeof(unsigned));
// TODO: realloc only if index_ct is much smaller than max_index_ct
}
elem->data = builder->data;
#endif
// elem->data will never be *larger* than builder->data... how about converting
// in place to avoid extra allocation?
elem->vbo_id = 0;
// TODO: create GL buffer object directly, based on an input flag
// discard builder (one-time use)
if (builder->data != elem->data)
free(builder->data);
builder->data = NULL;
// other fields are safe to leave
}

View File

@ -0,0 +1,56 @@
// Gawain element list (AKA index buffer)
//
// This code is part of the Gawain library, with modifications
// specific to integration with Blender.
//
// Copyright 2016 Mike Erwin
//
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
// the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
#pragma once
#include "common.h"
#define TRACK_INDEX_RANGE 1
typedef struct {
unsigned index_ct;
#if TRACK_INDEX_RANGE
GLenum index_type;
unsigned min_index;
unsigned max_index;
unsigned base_index;
#endif
void* data; // NULL indicates data in VRAM (unmapped) or not yet allocated
GLuint vbo_id; // 0 indicates not yet sent to VRAM
} ElementList;
void ElementList_use(ElementList*);
void ElementList_done_using(void);
unsigned ElementList_size(const ElementList*);
typedef struct {
unsigned max_allowed_index;
unsigned max_index_ct;
unsigned index_ct;
GLenum prim_type;
unsigned* data;
} ElementListBuilder;
// supported primitives:
// GL_POINTS
// 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 add_generic_vertex(ElementListBuilder*, unsigned v);
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*);

View File

@ -0,0 +1,14 @@
// Gawain geometry batch
//
// This code is part of the Gawain library, with modifications
// specific to integration with Blender.
//
// Copyright 2016 Mike Erwin
//
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
// the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
#include "vertex_buffer.h"
// TODO: implement C functions

View File

@ -0,0 +1,41 @@
// Gawain geometry batch
//
// This code is part of the Gawain library, with modifications
// specific to integration with Blender.
//
// Copyright 2016 Mike Erwin
//
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
// the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
#pragma once
#include "vertex_format.h"
typedef struct {
VertexFormat format;
unsigned vertex_ct;
GLubyte* data; // NULL indicates data in VRAM (unmapped) or not yet allocated
GLuint vbo_id; // 0 indicates not yet sent to VRAM
} VertexBuffer;
VertexBuffer* create_VertexBuffer(VertexFormat*, unsigned v_ct); // create means allocate, then init
void init_VertexBuffer(VertexBuffer*, VertexFormat*, 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
// to the vertex attribute's type and component count. They're in control of both, so this
// should not be a problem.
void setAttrib(VertexBuffer*, unsigned a_idx, unsigned v_idx, const void* data);
void fillAttrib(VertexBuffer*, unsigned a_idx, const void* data);
void fillAttribStride(VertexBuffer*, unsigned a_idx, unsigned stride, const void* data);
// void setAttrib1f(unsigned a_idx, unsigned v_idx, float x);
// void setAttrib2f(unsigned a_idx, unsigned v_idx, float x, float y);
// void setAttrib3f(unsigned a_idx, unsigned v_idx, float x, float y, float z);
// void setAttrib4f(unsigned a_idx, unsigned v_idx, float x, float y, float z, float w);
//
// 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);