3.8 Vérification de la construction

En théorie, nous sommes maintenant capables d'utiliser notre classe FrameBuffer sans ajouter de code quelconque. Cependant, faire cela serait assez imprudent car nous ne savons absolument pas si notre FBO est valide ou non.

Pour éviter d'être surpris lors de notre prochain affichage, nous allons coder encore un petit bloc dans la méthode charger() qui nous permettra de vérifier ce que l'on peut appeler l'intégrité du FBO. Si une erreur s'est produite au moment de sa construction (buffer, association, etc.) OpenGL nous le fera savoir, et nous devrons réagir en conséquence.

Mais euh comment on fait pour savoir si le FBO est mal construit ?

La réponse est très simple : il existe une fonction pour nous le dire, tout comme avec les shaders.

Heureusement pour nous, la fonction en question est moins compliquée à utiliser que celle des shaders une fois de plus. Vous vous souvenez qu'avec eux, il fallait vérifier s'il y avait une erreur, récupérer la taille du message et l'afficher. Avec les FBO, nous n'avons pas à faire tout ça. Mais en contrepartie, nous n'aurons pas de détails précis sur l'erreur remontée. Ce n'est pas trop grave ici car nous n'avons pas de code source à vérifier, l'erreur est donc moins susceptible de venir de nous.

Cette fonction de vérification s'appelle glCheckFramebufferStatus() :

1
GLenum glCheckFramebufferStatus(GLenum target);
  • target : Le paramètre target des FBO ! La constante GL_FRAMEBUFFER. Oui c'est un peu spécial mais je vous rassure, la fonction fonctionne correctement.

Elle renvoie plusieurs constantes en cas d'erreur ou GL_FRAMEBUFFER_COMPLETE si tout s'est bien passé. Pour plus de simplicité, nous n'utiliserons que cette dernière. ;)

Nous commençons donc notre code en vérifiant la valeur renvoyée par la fonction glCheckFramebufferStatus(). Si elle est différente de GL_FRAMEBUFFER_COMPLETE alors on entre dans un bloc if :

1
2
3
4
// Vérification de l'intégrité du FBO
if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE){

}

Si le programme entre dans ce bloc, c'est qu'il s'est passé quelque chose de mauvais dans la méthode (un Render Buffer mal initialisé par exemple). Si cela arrive, il faudra libérer toute la mémoire prise par les différents objets.

Pour cela, nous allons appeler la fonction glDeleteFramebuffers() pour détruire le FBO et la fonction glDeleteRenderbuffers() pour détruire le double-buffer gérant le Depth et le Stencil :

1
2
3
4
5
6
// Vérification de l'intégrité du FBO
if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE){
	// Libération des buffers
	glDeleteFramebuffers(1, &m_id);
	glDeleteRenderbuffers(1, &m_depthBufferID);
}

Hum au fait, on ne libère pas tout là ... Il manque encore le Color Buffer non ?

Oui c'est exact ! Je l'ai gardé pour la fin celui-la. :p

En fait, ce buffer-la est un peu spécial vu qu'il s'agit d'une texture. Sa libération dépend donc entièrement de son destructeur. Pour l'appeler, il nous suffit simplement de supprimer la texture dans le tableau m_colorBuffers, le programme appellera automatiquement le destructeur concerné. C'est une des bases du C++.

Pour supprimer la texture, nous n'allons pas utiliser la méthode pop_back() contrairement à ce qu'on pourrait penser. A la place, nous allons prendre un peu d'avance et imaginer que nous ayons 7 Color Buffers à détruire. Si nous étions dans ce cas, nous n'appellerions pas la même méthode 7 fois d'affilé. Ce serait une perte de temps, surtout que la classe vector nous fourni une jolie méthode qui permet de vider entièrement son contenu. Cette méthode s'appelle clear().

Si nous l'utilisons, le tableau se videra entièrement et le programme appellera automatiquement les destructeurs de tous les objets qu'il contient.

Donc au final, pour libérer la mémoire prise par toutes les textures, nous appellerons la méthode clear() :

1
2
3
4
5
6
7
8
9
// Vérification de l'intégrité du FBO

if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE){
	// Libération des buffers
	glDeleteFramebuffers(1, &m_id);
	glDeleteRenderbuffers(1, &m_depthBufferID);

	m_colorBuffers.clear();
}

Maintenant, tous nos objets sont détruits proprement et la mémoire est libérée.

Il ne reste plus qu'à afficher un message d'erreur pour conclure le tout et renvoyer la valeur false :

1
2
3
4
5
6
7
8
9
10
11
// Vérification de l'intégrité du FBO

if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE){
	// Libération des buffers
	glDeleteFramebuffers(1, &m_id);
	glDeleteRenderbuffers(1, &m_depthBufferID);
	m_colorBuffers.clear();
	// Affichage d'un message d'erreur et retour de la valeur false
	std::cout << "Erreur : le FBO est mal construit" << std::endl;
	return false;
}

Si on récapitule toute notre méthode charger() :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
bool FrameBuffer::charger(){
	// Vérification d'un éventuel ancien FBO
	if(glIsFramebuffer(m_id) == GL_TRUE) glDeleteFramebuffers(1, &m_id);

	// Génération d'un id
	glGenFramebuffers(1, &m_id);

	// Verrouillage du Frame Buffer
	glBindFramebuffer(GL_FRAMEBUFFER, m_id);

	// Création du Color Buffer
	Texture colorBuffer(m_largeur, m_hauteur, GL_RGBA, GL_RGBA, true);
	colorBuffer.chargerTextureVide();

	// Ajout au tableau
	m_colorBuffers.push_back(colorBuffer);

	// Création du Depth et du Stencil Buffer
	creerRenderBuffer(m_depthBufferID, GL_DEPTH24_STENCIL8);

	// Association du Color Buffer
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_colorBuffers[0].getID(), 0);

	// Association du Depth et du Stencil Buffer
	glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, m_depthBufferID);

	// Vérification de l'intégrité du FBO
	if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE){
		// Libération des buffers
		glDeleteFramebuffers(1, &m_id);
		glDeleteRenderbuffers(1, &m_depthBufferID);
		m_colorBuffers.clear();
		// Affichage d'un message d'erreur et retour de la valeur false
		std::cout << "Erreur : le FBO est mal construit" << std::endl;
		return false;
	}

	// Déverrouillage du Frame Buffer
	glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

Il ne manque plus que la touche finale : le renvoi de la valeur true pour indiquer que tout s'est bien passé (tant que la condition précédente n'a pas été déclenchée) :

1
2
3
4
5
6
7
8
9
10
11
12
bool FrameBuffer::charger()
{
	// Création du FBO + Vérification
	
	....
	
	// Déverrouillage du Frame Buffer
	glBindFramebuffer(GL_FRAMEBUFFER, 0);
	
	// Si tout s'est bien passé, on renvoie la valeur true
	return true;
}

Cette fois, nous avons enfin terminé tout le codage des Frame Buffer. Nous avons créé tout ce dont ils avaient besoin pour fonctionner. Nous avons même inclus un code de vérification en cas d'erreur.

On passe maintenant aux derniers détails à régler avant d'utiliser notre premier FBO dans une scène 3D.