What are the most 'idiomatic' ways to use gl::VboMesh?

Hello, this is my first post here, nice to find such a helpful site.

Recently I am digging into the examples to find the most proper ways to use Cinder’s VboMesh Class. It is so flexible to use and thus sometimes a bit confusing to decide which way to choose on what requirements.

Here I mostly ask the most ‘idiomatic’ ways to create a VboMesh from custom vbos.

//! Creates a VboMesh which represents the user's vertex buffer objects. Allows optional \a indexVbo to enable indexed vertices; creates a static index VBO if none provided.
static VboMeshRef	create( uint32_t numVertices, GLenum glPrimitive, const std::vector<std::pair<geom::BufferLayout,VboRef>> &vertexArrayBuffers, uint32_t numIndices = 0, GLenum indexType = GL_UNSIGNED_SHORT, const VboRef &indexVbo = VboRef() );
//! Creates a VboMesh which represents the user's vertex buffer objects. Allows optional \a indexVbo to enable indexed vertices; creates a static index VBO if none provided.
static VboMeshRef	create( uint32_t numVertices, GLenum glPrimitive, const std::vector<Layout> &vertexArrayLayouts, uint32_t numIndices = 0, GLenum indexType = GL_UNSIGNED_SHORT, const VboRef &indexVbo = VboRef() );

The two ‘create’ calls above only differs in ‘layout’ part. Please help me clarify if my understanding is right.
const std::vector<std::pairgeom::BufferLayout,VboRef> &vertexArrayBuffers
which associate the layouts info with correspondent vbo. One VboMesh can have many vbos, each vbo can have many interleaved attributes. But I know the gl::Batch is kind of wrapper of Vao, so, are there any max attribute number requirements for the VboMesh? Like the sum of all vbo’s attribute numbers should be below 16?

The second one is even more confusing, I can’t see how the VboMesh::Layout associate with the vbo or the data. I still can’t make a minimum working example using this api. When is this one useful against custom vbo or vertex data?

Below are my minimum examples to learn how to use the first api to draw triangles.

#include "cinder/app/App.h"
#include "cinder/app/RendererGl.h"

#include "cinder/gl/VboMesh.h"
#include "cinder/gl/gl.h"
using namespace ci;
using namespace ci::app;
//class VboMesh;
using fms = std::chrono::duration<float, std::milli>;
using Clock = std::chrono::steady_clock;
class BasicApp : public App {
public:
    void draw() override;
    void setup() override;

private:
    struct vertex {
        vec2 pos;
        vec3 color;
    };
    gl::GlslProgRef m_shader;
    gl::BatchRef m_batchAOS;
    gl::BatchRef m_batchSOA;
};

void BasicApp::draw()
{
    gl::clear();
    m_batchSOA->draw();
    m_batchAOS->draw();
}

void BasicApp::setup() {
    m_shader =  gl::GlslProg::create(loadAsset("vertex.vert"), loadAsset("fragment.frag"));
    gl::enableDepthRead();
    gl::enableDepthWrite();

    std::vector<vertex> vertices = {
            {vec2(-0.5, 0.5), vec3(1, 0, 0)},
            {vec2(-1, -0.5), vec3(0, 1, 0)},
            {vec2(0, -0.5), vec3(0, 0, 1)}
    };
    std::vector<vec2> position {vec2(0.5, 0.5), vec2(0,-0.5), vec2(1, -0.5)};
    std::vector<vec3> colors {vec3(1, 0, 1), vec3(1, 1, 0), vec3(0, 1, 1)};
/**
 * @brief try array of structures layout!
 */
    auto vbo = gl::Vbo::create(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW);
    geom::BufferLayout layout;
    layout.append(geom::CUSTOM_0, 2, sizeof(vertex), offsetof(vertex, pos));
    layout.append(geom::CUSTOM_1, 3, sizeof(vertex), offsetof(vertex, color));
    auto meshAOS = gl::VboMesh::create(
            vertices.size(),
            GL_TRIANGLES,
            {{layout, vbo}}
    );
    auto glsl = gl::GlslProg::create(loadAsset("vertex.vert"), loadAsset("fragment.frag"));
    gl::Batch::AttributeMapping map = {{geom::CUSTOM_0, "pos"},{geom::CUSTOM_1, "color"}};
    m_batchAOS = gl::Batch::create(meshAOS, m_shader, map);
/**
 * @brief try structure of arrays layout!
 */
    auto vboPos = gl::Vbo::create(GL_ARRAY_BUFFER, position);
    auto vboColor = gl::Vbo::create(GL_ARRAY_BUFFER, colors);
    auto meshSOA = gl::VboMesh::create(
            (uint32_t)3, GL_TRIANGLES,
            {
                    {geom::BufferLayout({geom::AttribInfo(geom::CUSTOM_0, 2, 0, 0)}), vboPos},
                    {geom::BufferLayout({geom::AttribInfo(geom::CUSTOM_1, 3, 0, 0)}), vboColor}
            }
    );
    m_batchSOA = gl::Batch::create(meshSOA, m_shader, map);
}

CINDER_APP( BasicApp, RendererGl )

I couldn’t preserve the format after pasting my code here. See gist here https://gist.github.com/superboy0712/ca5c6586318f1647029100abb38b4ae8


I fixed that for you.
~Paul

I haven’t tested if your code work or not because it uses separate shader files;
but your mesh is using custom attribute ids that need to be passed into the shader with extra setting. If you are not familiar with the shader binding process, I suggest you use default attributes and default stock shaders in Cinder to focus on vbomesh first.

The simplest sample available to get started with vbomesh I think is the ImageHeightField sample though it doesn’t show how to bind the vert/uv data directly.

The first constructor you quoted, I believe is used when you want to add custom attributes - which probably is out of your scope for now. And for the 2nd constructor, I have some pseudo code that could show the idea:

gl::VboMesh::Layout vertLayout, uvLayout;
vertLayout.usage(GL_DYNAMIC_DRAW).attrib(geom::Attrib::POSITION, 3); //be sure to pass dimensions 
uvLayout.usage(GL_STATIC_DRAW).attrib(geom::Attrib::TEX_COORD_0, 2);

auto mesh = gl::VboMesh::create((uint32_t)verts.size(),  //number of vertices
                            GL_TRIANGLES,                                 
                            {vertLayout, uvLayout},                       //layout declared above
                            (uint32_t)indices.size(),                       //number of indices
                            GL_UNSIGNED_INT );

mesh->bufferIndices(mIndices.size() * sizeof(uint32_t), &indices[0]);
mesh->bufferAttrib(geom::Attrib::POSITION,           verts      ); //be ware to use pre-defined attributes
mesh->bufferAttrib(geom::Attrib::TEX_COORD_0,  texcoords  );

And for the shader to construct the batch, you can simply do:

auto shader = gl::getStockShader(gl::ShaderDef().color());

This will make sure that if anything goes wrong, its not the binding but how your vbomesh is constructed.