Skip to content

Buffer Loading OpenGL

Carl Woffenden edited this page Dec 21, 2022 · 6 revisions

To make this clearer all the offsets are named, and these are the same regardless of the graphics API:

/**
 * Offsets into the metadata.
 *
 * \note If \c META_NUM_INDICES is zero (which implies \c META_IBUF_BYTES is
 * also zero) the content is unindexed triangles.
 */
enum MetaOffset {
    META_MAGIC_TEST  =  0, /**< Endianness test/file \e magic. (\c uint16 ). */
    META_SHORTCODE   =  2, /**< Endianness test/file \e magic. (\c uint16 ). */
    META_VBUF_OFFSET =  6, /**< Offset to the start of the vertex data (\c uint32 ). */
    META_VBUF_BYTES  = 10, /**< Number of vertex data bytes (\c uint32 ). */
    META_IBUF_OFFSET = 14, /**< Offset to the start of the index data (\c uint32 ). */
    META_IBUF_BYTES  = 18, /**< Number of index data bytes (\c uint32 ). */
    META_NUM_INDICES = 22, /**< Number of triangle indices to draw (\c uint32 ). */
    META_MESH_SCALE  = 26, /**< Mesh scale to draw at the original size (3x \c float32 ). */
    META_MESH_BIAS   = 38, /**< Mesh bias to restore the origin (3x \c float32 ). */
    META_PACK_TANS   = 50, /**< Where the encoded tangents pair are packed (\c uint8 ). */
    META_PACK_SIGN   = 51, /**< Where the optional tangent sign is packed (\c uint8 ). */
    META_VERT_STRIDE = 52, /**< Single vertex stride in bytes (\c uint8 ). */
    META_NUM_ATTRIBS = 53, /**< Number of vertex attributes (\c uint8 ). */
    META_ATTR_OFFSET = 54, /**< Start of the attribute data (\c n x 4x \c uint8 ). */
    META_MAGIC  =  0xBDA7, /**< \e Magic number at the start of the metadata. */
    META_CIGAM  =  0xA7BD, /**< \c META_MAGIC with the wrong endianness. */
};

/**
 * Offsets into each attribute entry, starting at \c MetaOffset#META_ATTR_OFFSET
 * (with \c MetaOffsetMETA_NUM_ATTRIBS entries, each of \c ATTR_ENTRY_SIZE
 * bytes).
 *
 * \note At a minimum there will be one entry.
 */
enum AttrEntry {
    ATTR_INDEX      = 0, /**< Shader index the attribute is bound to (\c uint8). */
    ATTR_COMPONENTS = 1, /**< Number of components (\c uint8). */
    ATTR_DATA_TYPE  = 2, /**< Data type (\c uint8 ; see \c getDataType()). */
    ATTR_OFFSET     = 3, /**< Offset to the attribute within each \c META_VERT_STRIDE (\c uint8). */
    ATTR_ENTRY_SIZE = 4, /**< Number of bytes per \c AttrEntry record. */
};

The targets for the loaded attributes are also named:

enum VertexID {
    VERT_POSN_ID = 0, /**< Vertex positions. */
    VERT_TEX0_ID = 1, /**< Vertex texture channel channel 0. */
    VERT_NORM_ID = 2, /**< Vertex normals. */
    VERT_TANS_ID = 3, /**< Vertex tangents. */
    VERT_BTAN_ID = 4, /**< Vertex bitangents. */
};

A couple of helpers are then used to get multi-byte values out of the header:

/**
 * Convert \c Storage#BasicType from the exporter to the equivalent GL type
 * (stored at \c AttrEntry#ATTR_DATA_TYPE ).
 *
 * \param[in] type the combined type and normalised byte (7-bits for the type)
 * \return the GL data type to pass to \c glVertexAttribPointer()
 */
GLenum getDataType(unsigned const type) {
#ifndef GL_HALF_FLOAT
#define GL_HALF_FLOAT 0x140B
#endif
    switch (type & 0x7F) {
    case 1:
        return GL_BYTE;
    case 2:
        return GL_UNSIGNED_BYTE;
    case 3:
        return GL_SHORT;
    case 4:
        return GL_UNSIGNED_SHORT;
    case 5:
        return GL_INT;
    case 6:
        return GL_UNSIGNED_INT;
    case 7:
        return GL_HALF_FLOAT;
    default:
        return GL_FLOAT;
    }
}

/**
 * If exported with the \c -m option \c objBuf should have 52-68 bytes of data
 * before the buffer data (described by the offsets in \c MetaOffset ).
 *
 * \note The first entry, \c MetaOffset#META_MAGIC_TEST denotes the endianness,
 * which should be \c MetaOffset#META_MAGIC if the endianness of the data
 * matches this machine (with no provision here to convert from big to little or
 * vice versa).
 *
 * \param[in] offset metadata index
 * \param[in] bytes size of the metadata (e.g. \c 4 for a \c uint32 )
 * \return retrieved value
 */
GLuint getBufMeta(unsigned const offset, unsigned const bytes = 4) {
    if ((offset + bytes) < sizeof(objBuf)) {
        switch (bytes) {
        case 1:
            return objBuf[offset];
        case 2: {
            uint16_t temp = 0;
            memcpy(&temp, objBuf + offset, bytes);
            return temp;
        }
        case 4: {
            uint32_t temp = 0;
            memcpy(&temp, objBuf + offset, bytes);
            return temp;
        }
        default:
            break;
        }
    }
    return 0;
}

The index buffer data type needs determining:

/**
 * Helper to determine the index buffer data type (inferred from the buffer size
 * in bytes and the number of indices).
 *
 * \note Whilst byte and int types can be stored, short should always be
 * supported by all APIs and hardware.
 *
 * \return GL enum for unsigned \c BYTE, \c SHORT or \c INT
 */
GLenum getIndexType() {
    switch (getBufMeta(META_IBUF_BYTES) / getBufMeta(META_NUM_INDICES)) {
    case 1:
        return GL_UNSIGNED_BYTE;
    case 2:
        return GL_UNSIGNED_SHORT;
    default:
        return GL_UNSIGNED_INT;
    }
}

The attribute indices need binding to the names used in the shader, e.g.:

glBindAttribLocation(progId, VERT_POSN_ID, "aPosn");
glBindAttribLocation(progId, VERT_TEX0_ID, "aTex0");
glBindAttribLocation(progId, VERT_NORM_ID, "aNorm");
glBindAttribLocation(progId, VERT_TANS_ID, "aTans");
glBindAttribLocation(progId, VERT_BTAN_ID, "aBtan");

Perform any shader compilation, linking, etc., then load the buffer data from the metadata description. The steps are:

  1. Create the vertex and index buffers
  2. Fill them with data from the offsets
  3. For each of of the attribute entries:
    1. Get the VertexID
    2. Set the relevant data types and sizes
    3. Enable the attribute

This is code is universal, and as long as the shader attribute names are bound it should just work:

glGenBuffers(2, buffers);
glBindBuffer(GL_ARRAY_BUFFER,         buffers[0]);
glBufferData(GL_ARRAY_BUFFER,         getBufMeta(META_VBUF_BYTES),
           objBuf + getBufMeta(META_VBUF_OFFSET), GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[1]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, getBufMeta(META_IBUF_BYTES),
           objBuf + getBufMeta(META_IBUF_OFFSET), GL_STATIC_DRAW);
for (unsigned n = 0; n < getBufMeta(META_NUM_ATTRIBS, 1); n++) {
    unsigned attrOffN = META_ATTR_OFFSET + (n * ATTR_ENTRY_SIZE);
    unsigned attrIdxN = getBufMeta(attrOffN   + ATTR_INDEX,      1);
    glVertexAttribPointer(attrIdxN,
                          getBufMeta(attrOffN + ATTR_COMPONENTS, 1),
              getDataType(getBufMeta(attrOffN + ATTR_DATA_TYPE,  1)),
                         (getBufMeta(attrOffN + ATTR_DATA_TYPE,  1) & 0x80) ? GL_TRUE : GL_FALSE,
                          getBufMeta(META_VERT_STRIDE,           1),
        (void*) ((size_t) getBufMeta(attrOffN + ATTR_OFFSET,     1)));
    glEnableVertexAttribArray(attrIdxN);
}

Then to draw simply:

glDrawElements(GL_TRIANGLES, getBufMeta(META_NUM_INDICES), getIndexType(), 0);
Clone this wiki locally