• Announcements

    • khawk

      Download the Game Design and Indie Game Marketing Freebook   07/19/17

      GameDev.net and CRC Press have teamed up to bring a free ebook of content curated from top titles published by CRC Press. The freebook, Practices of Game Design & Indie Game Marketing, includes chapters from The Art of Game Design: A Book of Lenses, A Practical Guide to Indie Game Marketing, and An Architectural Approach to Level Design. The GameDev.net FreeBook is relevant to game designers, developers, and those interested in learning more about the challenges in game development. We know game development can be a tough discipline and business, so we picked several chapters from CRC Press titles that we thought would be of interest to you, the GameDev.net audience, in your journey to design, develop, and market your next game. The free ebook is available through CRC Press by clicking here. The Curated Books The Art of Game Design: A Book of Lenses, Second Edition, by Jesse Schell Presents 100+ sets of questions, or different lenses, for viewing a game’s design, encompassing diverse fields such as psychology, architecture, music, film, software engineering, theme park design, mathematics, anthropology, and more. Written by one of the world's top game designers, this book describes the deepest and most fundamental principles of game design, demonstrating how tactics used in board, card, and athletic games also work in video games. It provides practical instruction on creating world-class games that will be played again and again. View it here. A Practical Guide to Indie Game Marketing, by Joel Dreskin Marketing is an essential but too frequently overlooked or minimized component of the release plan for indie games. A Practical Guide to Indie Game Marketing provides you with the tools needed to build visibility and sell your indie games. With special focus on those developers with small budgets and limited staff and resources, this book is packed with tangible recommendations and techniques that you can put to use immediately. As a seasoned professional of the indie game arena, author Joel Dreskin gives you insight into practical, real-world experiences of marketing numerous successful games and also provides stories of the failures. View it here. An Architectural Approach to Level Design This is one of the first books to integrate architectural and spatial design theory with the field of level design. The book presents architectural techniques and theories for level designers to use in their own work. It connects architecture and level design in different ways that address the practical elements of how designers construct space and the experiential elements of how and why humans interact with this space. Throughout the text, readers learn skills for spatial layout, evoking emotion through gamespaces, and creating better levels through architectural theory. View it here. Learn more and download the ebook by clicking here. Did you know? GameDev.net and CRC Press also recently teamed up to bring GDNet+ Members up to a 20% discount on all CRC Press books. Learn more about this and other benefits here.
Sign in to follow this  
Followers 0
Jack Shannon

Good design of a VertexBuffer class

8 posts in this topic

OK, so I have written my vertex buffer class how I thought it should be but it seems very messy for instance it needs the shader program id to know where the attributes are (this can only be a bad thing right, a vbo should be able to use multiple shaders easily) please can someone have a look and tell me what should be there and what shouldn't.

 

It also doesn't seem right that I'm having to come back in and call disableAttributes here after the draw call, there must be a cleaner way of doing things.

 

I think it's a case of not having clear responsibilities.

 

.h

#ifndef KVERTEXBUFFER_H
#define KVERTEXBUFFER_H

#include "KGraphicsLib.h"
#include "KShaderManager.h"

enum EFormatBitMask
{
    FORMAT_POSITION = (1u << 0),
    FORMAT_UV       = (1u << 1),
    FORMAT_NORMAL   = (1u << 2),
    FORMAT_TANGENT  = (1u << 3)
};

class CVertexBuffer
{
public:
    struct CAttrib
    {
        CAttrib(GLuint i, GLint s, int o)
        {
            index = i;
            size = s;
            offset = (GLvoid*)o;
        }
        GLuint index; // location that it's bound to
        GLint size; // component count (float[2] has size 2)
        GLvoid* offset;
    };

    
                CVertexBuffer();
    
    virtual     ~CVertexBuffer();

    void        init();
    void        destroy();

    void        uploadData(GLint programID, unsigned int format, float* data, int vertexCount);
    void        pointToData();
    void        disableAttributes();
private:
    GLuint identifier;
    GLsizei stride;
    vector<CAttrib*> attribs;
};

#endif  // #ifndef KVERTEXBUFER_H

 

.cpp

#include "KVertexBuffer.h"

CVertexBuffer::CVertexBuffer()
{
    init();
}

CVertexBuffer::~CVertexBuffer()
{
    destroy();
}

void CVertexBuffer::init()
{
    glGenBuffers(1, &identifier);
    GetError();
}

void CVertexBuffer::destroy()
{
    glDeleteBuffers(1, &identifier);
}

void CVertexBuffer::pointToData()
{
    CAttrib* attrib;
    vector<CAttrib*>::iterator it = attribs.begin();
    for (; it != attribs.end(); ++it) {
        attrib = (*it);
        glEnableVertexAttribArray(attrib->index);
        glBindBuffer(GL_ARRAY_BUFFER, identifier);
        glVertexAttribPointer(
                              attrib->index,                  // index
                              attrib->size,      // component count
                              GL_FLOAT,
                              GL_FALSE,           // normalized?
                              stride,
                              attrib->offset
                              );
        GetError();
    }
}

void CVertexBuffer::disableAttributes()
{
    vector<CAttrib*>::iterator it = attribs.begin();
    for (; it != attribs.end(); ++it) {
        glDisableVertexAttribArray((*it)->index);
    }
}


void CVertexBuffer::uploadData(GLint programID, unsigned int format, float* data, int vertexCount)
{
    stride = 0;
    CAttrib* attr;
    if ((format & FORMAT_POSITION) == FORMAT_POSITION) {
        attr = new CAttrib(glGetAttribLocation(programID, ""), 3, stride);
        stride += attr->size * sizeof(GLfloat);
        attribs.push_back(attr);
    }
    if ((format & FORMAT_UV) == FORMAT_UV) {
        attr = new CAttrib(glGetAttribLocation(programID, ""), 2, stride);
        stride += attr->size * sizeof(GLfloat);
        attribs.push_back(attr);
    }
    if ((format & FORMAT_NORMAL) == FORMAT_NORMAL) {
        attr = new CAttrib(glGetAttribLocation(programID, ""), 3, stride);
        stride += attr->size * sizeof(GLfloat);
        attribs.push_back(attr);
    }
    if ((format & FORMAT_TANGENT) == FORMAT_TANGENT) {
        attr = new CAttrib(glGetAttribLocation(programID, ""), 4, stride);
        stride += attr->size * sizeof(GLfloat);
        attribs.push_back(attr);
    }

    glBindBuffer(GL_ARRAY_BUFFER, identifier);
    glBufferData(GL_ARRAY_BUFFER, stride * vertexCount, data, GL_STATIC_DRAW);
}

 

Thank you

 

Jack

0

Share this post


Link to post
Share on other sites

I would separate the vertex buffer itself from the vertex attributes.  This will decouple the vertex buffer from a program, and allow you to spread attributes across multiple buffers.

0

Share this post


Link to post
Share on other sites

How should pointToData() know the attribute locations needed for glVertexAttribPointer() then? Should I just pass an array of the locations through?

0

Share this post


Link to post
Share on other sites

There are two ways I would consider:

 

1) the shader-aware approach (more advanced, more pointless overall) that fills the buffer with all attributes required by the shader, even if they're dummies

 

2) the "provoking vertex" approach: the VBO contains only those attributes that are set before the first vertex and after Begin(). After this the format becomes locked and defining other properties has no effect. Consider this:

 

Begin(TRIANGLES);

 

Color();

Normal3D();

TexCoord2D();

Vertex3D(); <--- provoking vertex, locking in only color, UV and normal

 

TexCoord2D();

Vertex3D(); <--- uses same color and normal as first vertex, but has its own UV

 

Tangent3D(); <---- ignored - not defined before provoking vertex

TexCoord3D();

Vertex3D();

 

End();

Edited by irreversible
0

Share this post


Link to post
Share on other sites
As was mentioned, what is inside the shader and what is inside the vertex buffer should be decoupled.
 
 
I use my own shader language which uses semantics (exactly like in HLSL) to specify to the shader after compilation which attributes are POSITION, TEXCOORD, NORMAL, etc.
 
 
Since there is a fixed number of semantic types and a fixed number of indices for each type (POSITION0, POSITION1, POSITION2, …, POSITION15) the shader builds a 2D table of attribute locations for each semantic.
 
When it is time to draw, the vertex buffer uses this table to activate attributes as follows (called just before the actual render call, which may end up being called on an index buffer rather than a vertex buffer):
 
 
 
	/**
	 * Prepare for rendering.
	 */
	LSVOID LSE_CALL COpenGlVertexBuffer::PrepareToRenderApi() {
		assert( CFnd::GetShader() );
		CCriticalSection::CLockerS lsLockBindBuffer( m_csOglCrit );
		COpenGl::glBindBuffer( GL_ARRAY_BUFFER, m_uiVboId );	// Even if m_uiVboId is 0, that is perfect.
		// Set vertex attribute locations.
		for ( LSUINT32 I = m_vVertexAttribPointers.Length(); I--; ) {
			const LSG_VERTEX_ATTRIB_POINTER * pvapThis = &m_vVertexAttribPointers[I];
			GLint iAttrib = CFnd::GetShader()->GetAttributeLocs()[pvapThis->ui16LocClass].iLoc[pvapThis->ui16LocUsage];
			if ( iAttrib != -1 ) {
				COpenGl::glEnableVertexAttribArray( iAttrib );
				COpenGl::glVertexAttribPointer( iAttrib,
					pvapThis->ui8Elements,
					pvapThis->ui32Type,
					pvapThis->ui8Normalized,
					m_siStride,
					reinterpret_cast<LSUINT8 *>(pvapThis->pvPointer) + m_ui32OffsetApi );
				CFnd::AssertError();
			}
		}
	}
The vertex buffer knows what attributes it will use and stores them in its own linear array called m_vVertexAttribPointers.
A simple for () loop goes over that, gets each attribute type (POSITION0) and usage index (POSITION0) and quickly gets the location.
The shaders have preprocessed all of their locations in a 2D array and the vertex buffer has preprocessed all of its attribute data (size, type, elements, etc.) making the system very efficient at run-time.
 
 
I am able to do this because I wrote a new shader language.
I expect that you are not really wanting to go that far, however this should give you some inspiration about how the locations can stay on the shader and how the vertex buffer can efficiently look them up at run-time.  This decoupling between shader and vertex buffer is very important.
You should be able to think of a simpler way to automate the process where the shader can look up information on all its uniforms ahead of time.  It is fairly easy to make a custom preprocessing step over your shaders to extract information that you have embedded using custom characters.
 
You don’t have to invent a whole new language.  Just change the way you declare uniforms to something like this:
@uniform vec4 g_vPosition : POSITION0
 
You can write a very simple parser that looks for lines beginning with @uniform, parses the variable name and usage data, and then inserts into the shader a valid declaration such as:
uniform vec4 g_vPosition;
 
 
 
L. Spiro
0

Share this post


Link to post
Share on other sites

Very interesting thank you everyone.

 

L. Spiro, would you mind explaining how calling CFnd::GetShader() returns the current shader in this line, it seems a good way of decoupling things?

 

 

GLint iAttrib = CFnd::GetShader()->GetAttributeLocs()[pvapThis->ui16LocClass].iLoc[pvapThis->ui16LocUsage];
 

Thanks

 

Jack

0

Share this post


Link to post
Share on other sites

There is simpler way, just bind the same locations for each shader or use layout qualifier: layout(location = ...). This way you don't have to query locations on runtime, so you're decoupled from shader. No need to invent some special semantic for this, just stick to some naming convention. 

 

VertexAttr enum that defines locations for our vertex attribute semantic:

enum class VertexAttr
{
    POSITION = 0,
    COLOR,
    NORMAL,
    TEX0,
    TEX1,
    MATERIAL,
    TANGENT,
    BLEND_INDEX,
    BLEND_WEIGHT,
};

 

Using binding:

glBindAttribLocation(program, static_cast<GLuint>(VertexAttr::POSITION),        "position");
glBindAttribLocation(program, static_cast<GLuint>(VertexAttr::COLOR),           "color");
glBindAttribLocation(program, static_cast<GLuint>(VertexAttr::NORMAL),          "normal");
glBindAttribLocation(program, static_cast<GLuint>(VertexAttr::TEX0),            "tex0");  

 

This way every shader will use the same locations. Only drawback is that you're forced to use specified names in GLSL, so when you want VertexAttr::POSITION you have to use "position" name in your shaders. This is not really a problem, but if you want for some weird reason flexibility in naming, other way to do this would be to predefine attributes in your C++ code (same as before, so enum VertexAttr that has 0..n attribute locations) and prepend them to each shader you compile:

// GLSL code prepended to each shader source

#define POSITION 0
#define COLOR    1
#define NORMAL   2
#define TEX0     3

 

This will require a bit more information than just enum, you need to have an array that will map enum VertexAttr to its GLSL semantic, so "POSITION", "COLOR", but you get idea:

const char* VertexAttrSemantic[] = {
    "POSITION",    /* VertexAttr::POSITION */
    "COLOR",       /* VertexAttr::COLOR */
    ...
}

 

Then you can loop over these structures and create GLSL definitions that you compile with each shader. This allows you to use these #defines in layout like this:

// GLSL

layout(location=POSITION) in vec3 myownname;
layout(location=TEX0) in vec2 texcoord;

 

So this is 2 ways how you can decouple your vertex attribute semantic from shader and still have a lot of flexibility in how you name/define attributes in GLSL. I like approaches that don't create need to invent whole new semantic, language in language etc. and can be used by people that know only GLSL - I think these two ways are more than enough for average user, but again, I'm not writing some big, universal engine. If you want to keep it simple that would be the way to go IMO.

0

Share this post


Link to post
Share on other sites

Firstly I want to say that I am not an advocate of globals and the reasons against them are quite sound, but I elected to simplify my architecture in this case by representing the graphics state as a single per-application global instead of making my renderer instance-based.  It is certainly a valid option if you want to make yours instance-based, but I opted to go with a design that ensures only one rendering device is available and can be accessed by anything from anywhere (while maintaining logical hierarchies such that only modules that actually deal with graphics will have access to it) in order to avoid having to pass a graphics object around.  It is just a design decision and not provably more correct than going fully instance-based.

 

So with this in mind, things are fairly simple.

Only one shader can be active at a time.  Shaders are instances (certainly), but when you activate one a global pointer to that shader is stored inside the CFnd class (all of is methods and members are static, so it is entirely a global encapsulation of the render state).

CFnd::SetShader() to set the current shader.

CFnd::GetShader() to get the current shader.

 

When a shader is destroyed it checks within its destructor whether or not it is set as the current shader, and CFnd::SetShader( NULL ) is called if it is, so you can’t have lingering pointers to dead shaders.  This is a bit more complex than that to truly ensure this is the case but that is the basic idea.

 

Finally, CFnd::Render() is called to actually perform a render of anything.  First you set shaders, vertex buffers, index buffers, etc.  The order doesn’t matter because all of these sets just set pointers to things.  Once everything is set, call CFnd::Render().

Inside of CFnd::Render() it makes sure things happen in the proper order.  That is, the shader pointer must already be set (which will have been done before the call to CFnd::Render(), so this is guaranteed) before the vertex buffers can get attribute locations.  Meaning CFnd::Render() is what actually calls PrepareToRender() on the set vertex buffers.

 

This setup is actually very important for optimizations as it allows you to greatly reduce redundant state changes and to allow state changes, including vertex-buffer setting and shader setting, to be done in any order.

 

 

L. Spiro

1

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now
Sign in to follow this  
Followers 0