glVertexAttribPointer behaviour when drawing with glDrawTransformFeedback

Hi all!

I have a very basic particle system using transform feedback buffers (ogl 4.3), based on the code from a tutorial (sorry, can’t post links), but there is one thing that is driving me crazy: why are we calling glVertexAttribPointer in the render methods?
I thought that glVertexAttribPointer is used to define the location and data format for the vertex attributes of the shader, so, I have been always using it during the Init() methods.

But in this example that I found, I have seen that they are calling glVertexAttribPointer in the render methods, for example:

void ParticleSystem::UpdateParticles(float deltaTime)
{
	//UpdateEmitters(deltaTime);

	Shader *particleSystem_shader = DEMO->shaderManager.shader[particleSystemShader];
	particleSystem_shader->use();
	particleSystem_shader->setValue("gTime", this->m_time);
	particleSystem_shader->setValue("gDeltaTime", deltaTime);

	bindRandomTexture(RANDOM_TEXTURE_UNIT);

	glEnable(GL_RASTERIZER_DISCARD);	// Stop drawing on the screen

	glBindBuffer(GL_ARRAY_BUFFER, m_particleBuffer[m_currVB]);
	glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, m_transformFeedback[m_currTFB]);

	glEnableVertexAttribArray(0);
	glEnableVertexAttribArray(1);
	glEnableVertexAttribArray(2);
	glEnableVertexAttribArray(3);
	glEnableVertexAttribArray(4);
	glEnableVertexAttribArray(5);

	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Particle), (const GLvoid*)0);	// Position (12 bytes)
	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Particle), (const GLvoid*)12);	// Velocity (12 bytes)
	glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(Particle), (const GLvoid*)24);	// Color (12 bytes)
	glVertexAttribPointer(3, 1, GL_FLOAT, GL_FALSE, sizeof(Particle), (const GLvoid*)36);	// Lifetime (4 bytes)
	glVertexAttribPointer(4, 1, GL_FLOAT, GL_FALSE, sizeof(Particle), (const GLvoid*)40);	// Size (4 bytes)
	glVertexAttribPointer(5, 1, GL_INT, GL_FALSE, sizeof(Particle), (const GLvoid*)44);		// Type (4 bytes)
	
	glBeginTransformFeedback(GL_POINTS);

	if (m_isFirst) {
		glDrawArrays(GL_POINTS, 0, numEmitters);
		m_isFirst = false;
	}
	else {
		glDrawTransformFeedback(GL_POINTS, m_transformFeedback[m_currVB]);
	}

	glEndTransformFeedback();
	glDisableVertexAttribArray(0);
	glDisableVertexAttribArray(1);
	glDisableVertexAttribArray(2);
	glDisableVertexAttribArray(3);
	glDisableVertexAttribArray(4);
	glDisableVertexAttribArray(5);

	glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, 0);
}

or…

void ParticleSystem::RenderParticles(const glm::mat4 &VP, const glm::vec3 &CameraPos)
{
	//Use the billboard shader and send variables
	Shader *my_shader;
	my_shader = DEMO->shaderManager.shader[billboardShader];
	my_shader->use();
	my_shader->setValue("gCameraPos", CameraPos); // Set camera position
	my_shader->setValue("gVP", VP);	// Set View Projection Matrix

	// Activate texture
	m_pTexture->active(0);
	m_pTexture->bind();

	glDisable(GL_RASTERIZER_DISCARD);	// Start drawing on the screen
	glBindBuffer(GL_ARRAY_BUFFER, m_particleBuffer[m_currTFB]);
	glEnableVertexAttribArray(0);
		glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Particle), (const GLvoid*)0);	// Position
		glDrawTransformFeedback(GL_POINTS, m_transformFeedback[m_currTFB]);
	glDisableVertexAttribArray(0);
}

Why glVertexAttribPointer is in the render functions? the positions are fixed, they will never change, so… why cannot be in the Init() method?

I have tried changing the calls to glVertexAttribPointer in the Init() method, and removing them (and the glEnable/DisableVertexAttribArray, of course) from the Render() methods, but the render outputs are not good… some particles are shown in wrong positions:

bool ParticleSystem::InitParticleSystem(const glm::vec3 &Pos)
{
	this->initPosition = Pos;
	Particle* Particles = (Particle*)malloc(sizeof(Particle) * numEmitters);
	ZERO_MEM(Particles);

	// Init the emitters
	for (unsigned int i = 0; i < numEmitters; i++) {
		Particles[i].Type = PARTICLE_TYPE_LAUNCHER;
		float circle = 2*3.1415f* ( (float)(i+1) / ((float)numEmitters));
		Particles[i].Pos = initPosition + glm::vec3(sin(circle), 0, cos(circle));
		Particles[i].Vel = glm::vec3(0.0f, 1.0f, 0.0f);
		Particles[i].Col = glm::vec3(1.0f, 1.0f, 1.0f);
		Particles[i].Size = 1.0;
		Particles[i].lifeTime = 0.0f;
	}

	// Gen the VAO
	glGenVertexArrays(1, &m_VAO);
	glBindVertexArray(m_VAO);

	// Gen buffers
	glGenTransformFeedbacks(2, m_transformFeedback);	// Transform Feedback object
	glGenBuffers(2, m_particleBuffer);					// Transform Feedback buffer


	for (unsigned int i = 0; i < 2; i++) {
		glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, m_transformFeedback[i]);
		glBindBuffer(GL_ARRAY_BUFFER, m_particleBuffer[i]);
		glBufferData(GL_ARRAY_BUFFER, sizeof(Particle)*numMaxParticles, NULL, GL_DYNAMIC_DRAW);	// Allocate mem, uploading an empty buffer for all the particles
		glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(Particle)*numEmitters, Particles);			// Upload only the emitters to the Buffer
		glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, m_particleBuffer[i]);
	}


	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Particle), (const GLvoid*)0);	// Position (12 bytes)
	glEnableVertexAttribArray(0);
	
	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Particle), (const GLvoid*)12);	// Velocity (12 bytes)
	glEnableVertexAttribArray(1);
	
	glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(Particle), (const GLvoid*)24);	// Color (12 bytes)
	glEnableVertexAttribArray(2);
	
	glVertexAttribPointer(3, 1, GL_FLOAT, GL_FALSE, sizeof(Particle), (const GLvoid*)36);	// Lifetime (4 bytes)
	glEnableVertexAttribArray(3);
	
	glVertexAttribPointer(4, 1, GL_FLOAT, GL_FALSE, sizeof(Particle), (const GLvoid*)40);	// Size (4 bytes)
	glEnableVertexAttribArray(4);
	
	glVertexAttribPointer(5, 1, GL_INT, GL_FALSE, sizeof(Particle), (const GLvoid*)44);		// Type (4 bytes)
	glEnableVertexAttribArray(5);

	free(Particles);
	// Make sure the VAO is not changed from the outside
	glBindVertexArray(0);
	// Some other init calls go here...
	[...]
}

Any clue? I’m sure is there a good reason to be in the Render function, but I don’t understand it :frowning:

  • Could be that I am drawing with glDrawTransformFeedback call? and this call needs to define the VertexAttributes on each render?
  • Or maybe is because I am using 1 VAO for all, and the 2 render functions are using a different layout? (UpdateParticles uses the 5 attributes, while renderParticles is using only the first attribute [position]). Should this mean that I should use 2 VAO’s?

Thanks in advance!!!

Because the buffers that they come from do change. That’s what the binding of m_particleBuffer[m_currTFB] is for. Each frame, m_currTFB changes. Therefore, glVertexAttribPointer must also change to use the new buffer.

Remember: the current GL_ARRAY_BUFFER-bound buffer is effectively a hidden parameter to glVertexAttribPointer.

Now granted, if the tutorial had used a sane API, this wouldn’t be needed. They could just use glBindVertexBuffer and move on with their lives. But they use the old API.

1 Like

Thanks a lot for the reply! I have replaced some code, but still is not working properly…

So, if I understood correctly, in OpenGL 4.3, in the Init() method I should Enable and Setup the Attribute formats and then define 2 different bindings:

  • One binding with all location attributes (used in the UpdateParticles() method, which updates the particle positions)
  • And another binding with only the location attribute of particle Position (used in RenderParticles() method, which renders the billboard)

//accessible constant declarations
constexpr int particleBindingPoint = 0;
constexpr int billboardBindingPoint = 1;// Must be less than the GL_MAX_VERTEX_ATTRIB_BINDINGS limit


bool ParticleSystem::InitParticleSystem(const glm::vec3 &Pos)
{
	this->initPosition = Pos;
	Particle* Particles = (Particle*)malloc(sizeof(Particle) * numEmitters);
	ZERO_MEM(Particles);

	// Init the particle 0, the initial emitter
	for (unsigned int i = 0; i < numEmitters; i++) {
		Particles[i].Type = PARTICLE_TYPE_LAUNCHER;
		float circle = 2*3.1415f* ( (float)(i+1) / ((float)numEmitters));
		Particles[i].Pos = initPosition + glm::vec3(sin(circle), 0, cos(circle));
		Particles[i].Vel = glm::vec3(0.0f, 1.0f, 0.0f);
		Particles[i].Col = glm::vec3(1.0f, 1.0f, 1.0f);
		Particles[i].Size = 1.0;
		Particles[i].lifeTime = 0.0f;
	}

	// Gen the VAO
	glGenVertexArrays(1, &m_VAO);
	glBindVertexArray(m_VAO);

	// Gen buffers
	glGenTransformFeedbacks(2, m_transformFeedback);	// Transform Feedback object
	glGenBuffers(2, m_particleBuffer);					// Transform Feedback buffer


	for (unsigned int i = 0; i < 2; i++) {
		glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, m_transformFeedback[i]);
		glBindBuffer(GL_ARRAY_BUFFER, m_particleBuffer[i]);
		//glBufferData(GL_ARRAY_BUFFER, sizeof(Particle)*numMaxParticles, Particles, GL_DYNAMIC_DRAW); // Upload the entire buffer (requires that "Particles" should have the size of all the particles [numMaxParticles])
		glBufferData(GL_ARRAY_BUFFER, sizeof(Particle)*numMaxParticles, NULL, GL_DYNAMIC_DRAW);	// Allocate mem, uploading an empty buffer for all the particles
		glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(Particle)*numEmitters, Particles);			// Upload only the emitters to the Buffer
		glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, m_particleBuffer[i]);
	}

	// Setup Vertex Attribute formats
	// Definitions for Binding 0 (Update particles positions)
	glVertexAttribFormat(LOC_POSITION, 3, GL_FLOAT, GL_FALSE, offsetof(Particle, Pos));	// Position (12 bytes)
	glVertexAttribBinding(LOC_POSITION, particleBindingPoint);
	glEnableVertexAttribArray(LOC_POSITION);

	glVertexAttribFormat(LOC_VELOCITY, 3, GL_FLOAT, GL_FALSE, offsetof(Particle, Vel));	// Velocity (12 bytes)
	glVertexAttribBinding(LOC_VELOCITY, particleBindingPoint);
	glEnableVertexAttribArray(LOC_VELOCITY);

	glVertexAttribFormat(LOC_COLOR, 3, GL_FLOAT, GL_FALSE, offsetof(Particle, Col));	// Color (12 bytes)
	glVertexAttribBinding(LOC_COLOR, particleBindingPoint);
	glEnableVertexAttribArray(LOC_COLOR);

	glVertexAttribFormat(LOC_LIFETIME, 1, GL_FLOAT, GL_FALSE, offsetof(Particle, lifeTime));	// Lifetime (4 bytes)
	glVertexAttribBinding(LOC_LIFETIME, particleBindingPoint);
	glEnableVertexAttribArray(LOC_LIFETIME);

	glVertexAttribFormat(LOC_SIZE, 1, GL_FLOAT, GL_FALSE, offsetof(Particle, Size));	// Size (4 bytes)
	glVertexAttribBinding(LOC_SIZE, particleBindingPoint);
	glEnableVertexAttribArray(LOC_SIZE);

	glVertexAttribFormat(LOC_TYPE, 1, GL_INT, GL_FALSE, offsetof(Particle, Type));	// Type (4 bytes)
	glVertexAttribBinding(LOC_TYPE, particleBindingPoint);
	glEnableVertexAttribArray(LOC_TYPE);

	//Definitions for Binding 1 (Render billboard)
	glVertexAttribFormat(LOC_POSITION, 3, GL_FLOAT, GL_FALSE, offsetof(Particle, Pos));	// Position (12 bytes)
	glVertexAttribBinding(LOC_POSITION, billboardBindingPoint);							// Define binding 1
	glEnableVertexAttribArray(LOC_POSITION);

	free(Particles);
	// Make sure the VAO is not changed from the outside
	glBindVertexArray(0);
	[... some other irrelevant things go here]
}

So the glBindBertexBuffer goes just before the Rendering functions:

void ParticleSystem::UpdateParticles(float deltaTime)
{
	Shader *particleSystem_shader = DEMO->shaderManager.shader[particleSystemShader];
	particleSystem_shader->use();
	particleSystem_shader->setValue("gTime", this->m_time);
	particleSystem_shader->setValue("gDeltaTime", deltaTime);

	bindRandomTexture(RANDOM_TEXTURE_UNIT);

	glEnable(GL_RASTERIZER_DISCARD);	// Stop drawing on the screen

	glBindBuffer(GL_ARRAY_BUFFER, m_particleBuffer[m_currVB]);
	glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, m_transformFeedback[m_currTFB]);

	// Use Binding 0 (all attributes)
	glBindVertexBuffer(particleBindingPoint, m_particleBuffer[m_currVB], 0, sizeof(Particle));
	glBeginTransformFeedback(GL_POINTS);

	if (m_isFirst) {
		glDrawArrays(GL_POINTS, 0, numEmitters);
		m_isFirst = false;
	}
	else {
		glDrawTransformFeedback(GL_POINTS, m_transformFeedback[m_currVB]);
	}

	glEndTransformFeedback();
	glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, 0);
}

void ParticleSystem::RenderParticles(const glm::mat4 &VP, const glm::vec3 &CameraPos)
{
	//Use the billboard shader and send variables
	Shader *my_shader;
	my_shader = DEMO->shaderManager.shader[billboardShader];
	my_shader->use();
	my_shader->setValue("gCameraPos", CameraPos); // Set camera position
	my_shader->setValue("gVP", VP);	// Set billboard size

	// Activate texture
	m_pTexture->active(0);
	m_pTexture->bind();

	glDisable(GL_RASTERIZER_DISCARD);	// Start drawing on the screen
	glBindBuffer(GL_ARRAY_BUFFER, m_particleBuffer[m_currTFB]);
	// Use Binding 1 (only Position attribute)
	glBindVertexBuffer(billboardBindingPoint, m_particleBuffer[m_currTFB], 0, sizeof(Particle));
	glDrawTransformFeedback(GL_POINTS, m_transformFeedback[m_currTFB]);
}

But I think I’m doing something wrong, because particles have again strange positions… Sorry for the messy code, but I come from fixed pipeline and I’m quite impressed how has changed everything! :sweat_smile::sweat_smile::sweat_smile:

EDIT: Updated some code in the Init() method, and an error in the UpdateParticles(), but still no success…

After many time… I found the solution!

In the “InitParticleSystem” method, I was missing the “glBindVertexBuffer” before the attribute definition!!

So… in case anyone gets blocked like I was… before any call to “glEnableVertexAttribArray”, “glVertexAttribFormat” or “glVertexAttribBinding”… do the “glBindVertexBuffer” call first!