
/*! \addtogroup framebuffer rendu multi-passes : shadow maps, post process

cf \ref tuto_framebuffer.cpp

Dans plusieurs cas, il est nécessaire de "récupérer" le résultat d'un rendu pour le modifier avant l'affichage final. par exemple, on
peut vouloir filtrer l'image brute avant de l'afficher. ou ajuster les couleurs pour produire une ambiance chaude ou froide, ajouter 
un grain dans l'image, etc. 
il faut donc pouvoir recupérer le color buffer et éventuellement le zbuffer qui sont habituellement affichés directement dans la fenêtre de l'application. 

De manière générale, certaines méthodes de rendu sont trop complexes pour être réalisées avec une seule exécution du pipeline 
graphique. une solution est de découper le rendu complet en plusieurs étapes, chaque étape utilisant l'image produite par l'étape précédente,
jusqu'à obtenir le résultat final que l'on peut afficher.

Ce sont des objets openGL, des framebuffer objects, notés FBO, qui permettent de configurer le pipeline pour stocker les résultats des fragment 
shaders dans des textures crées par l'application, au lieu de les afficher directement.

Leur utilisation est semblable aux autres objets "complexes" d'openGL (comme les vertex array objects, VAO) : 
	- création, cf glGenFramebuffers( ),
	- sélection, cf glBindFramebuffer( ),
	- sélection des textures sur les sorties du framebuffer, cf glFramebufferTexture( ),
	- association des sorties du fragment shader aux sorties du framebuffer, cf glDrawBuffers( ).
	
mais bien sur, il faut commencer par créer un objet framebuffer et le sélectionner pour le configurer :
\code
GLuint framebuffer;
glGenFramebuffers(1, &framebuffer);

glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer);
\endcode

ensuite, il n'y a plus qu'à indiquer quelles textures vont stocker les résultats exportés par le fragment shader et le pipeline. Les sorties
d'un fragment shader sont une ou plusieurs couleurs, ainsi que la profondeur du fragment. ces différentes sorties sont identifiées par :
	- GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 ... GL_COLOR_ATTACHMENT7, pour les color buffers, les couleurs,
	- GL_DEPTH_ATTACHMENT, pour le zbuffer, la profondeur du fragment.

\code
// selectionner le framebuffer a configurer, si necessaire
// glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer);

GLuint color_texture;
// creer la texture couleur aux dimensions de la fenetre
{ ... }

// associer la texture à une sortie du framebuffer
glFramebufferTexture(GL_DRAW_FRAMEBUFFER, /* attachment */ GL_COLOR_ATTACHMENT0, /* texture */ color_texture, /* mipmap level */ 0);
\endcode

reste une dernière option dans la configuration, récupérer l'identifiant d'une sortie déclarée dans le fragment shader et l'associer à une sortie du 
framebuffer, cf glDrawBuffers( ). il faut lui transmettre un tableau d'identifiants GL_NONE, GL_COLOR_ATTACHMENTxx indexé par 
l'identifiant de la sortie du fragment shader.

si le fragment shader ne déclare qu'une seule sortie, son identifiant est 0 par convention, il suffit de remplir un tableau contenant la sortie 
configurée dans le framebuffer à l'indice 0 :
\code
// selectionner le framebuffer a configurer, si necessaire
// glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer);

GLenum buffers[]= { GL_COLOR_ATTACHMENT0 };
glDrawBuffers(1, buffers);
\endcode

__remarque :__ on peut configurer n'importe quel attachment dans le fbo :
\code
// selectionner le framebuffer a configurer, si necessaire
// glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer);
glFramebufferTexture(GL_DRAW_FRAMEBUFFER, /* attachment */ GL_COLOR_ATTACHMENT4, /* texture */ color_texture, /* mipmap level */ 0);

GLenum buffers[]= { GL_COLOR_ATTACHMENT4 };
glDrawBuffers(1, buffers);
\endcode

pour "décoder" la configuration de fbo, le plus simple est de partir de la sortie dans le fragment shader : son identifiant est 0, donc la valeur
sera écrite dans l'attachment d'indice 0 passé à glDrawBuffers(). et la texture stockant la valeur est celle sélectionnée par glFramebufferTexture( ) 
sur l'attachment correspondant.

## dessiner dans un framebuffer 

il suffit de sélectionner le framebuffer sur GL_DRAW_FRAMEBUFFER avec glBindFramebuffer( ) avant de dessiner quelquechose :
\code
GLuint framebuffer= { ... };
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer);

glBindVertexArray( ... );
glUseProgram( ... );
glUniform( ... );

glDrawArrays(GL_TRIANGLES, ... );
\endcode

__attention :__ glDraw() utilise _implicitement_ plusieurs paramètres : les dimensions du viewport, par exemple (et le framebuffer sélectionné, bien sur).
il faudra donc penser à utiliser `glViewport()` si les dimensions des textures ne sont pas les memes que celles de la fenetre de l'application.

_remarque :_ effacer le framebuffer avant de dessiner :
\code
GLuint framebuffer= { ... };
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
\endcode

`Clear( COLOR_BUFFER_BIT ... )` copie la couleur par défaut dans toutes les textures associées aux `COLOR_ATTACHMENTxx` et 
`Clear( DEPTH_BUFFER_BIT ... )` copie la profondeur par défaut dans la texture associée à `DEPTH_ATTACHMENT`.

`Clear( )` utilise _implicitement_ les dimensions fournies par glViewport( ), donc il faut penser à configurer le viewport avant...

si l'on souhaite utiliser des valeurs / couleurs différentes selon le buffer, il faut utiliser glClearBuffer(). par exemple pour "effacer", le buffer 0 :
\code
GLuint framebuffer= { ... };
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer);
glFramebufferTexture(GL_DRAW_FRAMEBUFFER, /* attachment */ GL_COLOR_ATTACHMENT0, /* texture */ color_texture, /* mipmap level */ 0);

GLenum buffers[]= { GL_COLOR_ATTACHMENT0 };
glDrawBuffers(1, buffers);

Color color(1, 0, 0);
glClearBufferfv(GL_COLOR, /* draw buffer */ 0, /* value */ &color.x);
\endcode

`DRAW_BUFFER0` est l'indice dans le tableau `buffers`, `buffers[0]` contient `COLOR_ATTACHMENT0`, et c'est la texture `color_texture` qui sera 
finalement modifiée / effacée.

## et glViewport( ) ?

il faut aussi configurer le pipeline en fonction des dimensions des textures associées au framebuffer.
\code
GLuint framebuffer= { ... };
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer);
glViewport(0, 0, framebuffer_width, framebuffer_height);

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
\endcode

glClear( ) et glDraw( ) utilisent _implicitement_ les dimensions fournies par glViewport( ), donc il faut configurer viewport avant clear et draw...


## dessiner dans la fenetre ? (framebuffer par défaut)

il suffit de sélectionner le framebuffer 0 avant de dessiner :
\code
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glViewport(0, 0, window_width(), window_height());
glClear(GL_COLOR_BUFFER_BIt | GL_DEPTH_BUFFER_BIT);

glBindVertexArray( ... );
glUseProgram( ... );
glUniform( ... );

glDrawArrays(GL_TRIANGLES, ... );
\endcode


## récupérer l'identifiant d'une sortie du fragment shader 

c'est glGetFragDataLocation( ) qui renvoie l'identifiant du varying déclaré par le fragment shader :
\code
GLuint program= ... ;

GLint location= glGetFragDataLocation(program, "fragment_color");
\endcode

et on peut utiliser cette valeur pour configurer le framebuffer, cf glDrawBuffers() :
\code
GLuint program= ... ;
GLuint framebuffer = .... ;
GLenum buffers[8]= { GL_NONE, GL_NONE, GL_NONE, GL_NONE, GL_NONE, GL_NONE, GL_NONE, GL_NONE };
GLint location= glGetFragDataLocation(program, "fragment_color");
if(location >= 0)
	buffers[location]= GL_COLOR_ATTCHMENT0;

// selectionner le fbo, si necessaire
// glBindFamebuffer(GL_DRAW_FRAMEBUFFER, framebuffer);

glDrawBuffers(8, buffers); 
\endcode

# créer une texture de profondeur / zbuffer

Jusqu'à présent cf \ref tuto5GL, les textures peuvent stocker des couleurs (dont les valeurs sont comprises entre 0 et 1) sur plusieurs canaux r, g, 
b (et alpha, éventuellement). il est aussi possible de créer des textures pour stocker une valeur de profondeur par texel avec le format 
`GL_DEPTH_COMPONENT` (un entier normalise entre 0 et 1) :

\code
GLuint zubffer;
glGenTextures(1, &zbuffer);
glBindTexture(GL_TEXTURE_2D, zbuffer);

glTexImage2D(GL_TEXTURE_2D, 0,
	GL_DEPTH_COMPONENT, width, height, 0,
	GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, nullptr);
\endcode

ou `GL_DEPTH_COMPONENT32F` (un réel 32bits, un `float` classique) :
\code
GLuint zubffer;
glGenTextures(1, &zbuffer);
glBindTexture(GL_TEXTURE_2D, zbuffer);

glTexImage2D(GL_TEXTURE_2D, 0,
	GL_DEPTH_COMPONENT32F, width, height, 0,
	GL_DEPTH_COMPONENT, GL_FLOAT, nullptr);
\endcode


# utiliser une texture attachée à un framebuffer

Dessiner dans un framebuffer, et dans les textures associées, ne modifie que le niveau de mipmap attaché au framebuffer, 
cf glFramebufferTexture( ... level), le mipmap 0, le plus souvent. Mais lorsque l'on utilise cette texture dans l'étape suivante, ses autres mipmaps
n'ont pas changés de valeurs. Et selon le mode filtrage utilsé par le shader pour accéder à la texture, les résultats peuvent être surprenants...

par exemple, les paramètres par défaut de filtrage, cf `LINEAR_MIPMAP_LINEAR`, interpolent 2 mipmaps et 4 texels par mipmap, mais seul le 
mipmap 0 a ete modifie, et le résultat sera faux. 

il y a 2 solutions, soit utiliser un filtrage `NEAREST_MIPMAP_LINEAR` et `MAX_LEVEL` = 0 qui n'utilise que le mipmap 0, soit 
recalculer tous les mipmaps de la texture, cf glGenerateMipmap( ).
## attention ##
Autre subtilité, si une texture est active sur une unité de texture et attachée en même temps au framebuffer actif, glDraw()
génèrera une erreur : le pipeline ne peut pas lire et écrire la même texture en même temps. il faut donc bien penser à nettoyer les unités de texture pour eviter ce problème...

\code
GLuint color= { ... };
GLuint framebuffer= { ... };

init( )
{
	// configuration framebuffer / texture
	glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer);
	glFramebufferTexture(GL_DRAW_FRAMEBUFFER, /* attachment */ GL_COLOR_ATTACHMENT0, /* texture */ color, /* mipmap level */ 0);

	GLenum buffers[]= { GL_COLOR_ATTACHMENT0 };
	glDrawBuffers(1, buffers);

	// nettoyage
	glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
}

render( )
{
// passe 1, dessiner dans la texture color, attachee au framebuffer
	glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer);
	glViewport( ... );
	glClear( ... );

	glBindVertexArray( ... );
	glUseProgram( ... );
	glUniform( ... );
	glDraw( ... );

// passe 2, utiliser la texture de la passe 1 et dessiner dans la fenetre 
	glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
	glViewport( ... );
	glClear( ... );

	// mettre a jour les mipmaps de la texture
	glBindTexture(GL_TEXTURE_2D, color);
	glGenerateMipmap(GL_TEXTURE_2D);

	glBindVertexArray( ... );
	glUseProgram( ... );
	glUniform( ... );
	glDraw( ... );

// nettoyage *obligatoire*, la texture color *ne doit pas* etre active sur une unite de texture et attachee au framebuffer selectionne
	glBindTexture(GL_TEXTURE_2D, 0);
}
\endcode

exemple complet dans cf \ref tuto_framebuffer.cpp.

*/
