-
Bonjour a tous
J'ai un petit problème avec les Shaders et RenderTexture qui produisent une image retournée. J'ai cherché, et je suis tombé sur des oublis de display(), mais c'est pas mon cas.
Mon problème à moi, il se produit quand on utilise la texture retournée par RenderTexture::getTexture() comme paramètre d'un shader.
Chose étrange, copier la texture avec le constructeur par recopie (ce qui n'est pas vraiment optimal) semble résoudre le problème.
Mon intuition c'est que Shader::setParameter() ne s'en sort pas bien avec les Textures dont le flag pixelFlipped est a true (ce qui arrive seulement dans les textures contenues dans les RenderTexture si je ne m'abuse).
Une idée de comment remédier a ce problème proprement? Pour l'instant dans mon vrai code, j'ai affiché mon image à l'envers et je n'ai pas appelé display(). Ça semble résoudre le problème mais je n'aime pas trop le caractère "undefined behaviour" de ce hack... En plus j'imagine que le glFlush qui est fait à l'update doit avoir son importance.
Voila un petit code qui montre mon problème:
main.cpp
#include <iostream>
#include "SFML/Graphics.hpp"
using namespace std;
int main()
{
//Chargement de l'image
sf::Texture tex;
tex.loadFromFile("lena.png");
int s_x = tex.getSize().x;
int s_y = tex.getSize().y;
//Chargement du shader
sf::Shader shader;
shader.loadFromFile("shader.frag",sf::Shader::Fragment);
//Création d'un rectangle pour dessiner
sf::VertexArray rect(sf::Quads,4);
rect[0].position = sf::Vector2f(0,0);
rect[1].position = sf::Vector2f(0,s_y);
rect[2].position = sf::Vector2f(s_x,s_y);
rect[3].position = sf::Vector2f(s_x,0);
rect[0].texCoords = sf::Vector2f(0.0,0.0);
rect[1].texCoords = sf::Vector2f(0.0,1.0);
rect[2].texCoords = sf::Vector2f(1.0,1.0);
rect[3].texCoords = sf::Vector2f(1.0,0.0);
//Rendu avec la texture chargée en paramètre du shader
shader.setParameter("texture", tex);
sf::RenderTexture renderTexture;
renderTexture.create(s_x,s_y);
renderTexture.clear();
renderTexture.draw(rect, &shader);
renderTexture.display();
//Rendu avec la texture de RenderTexture en paramètre du shader
const sf::Texture& tex2 = renderTexture.getTexture();
//sf::Texture tex2 = renderTexture.getTexture(); //--->En mettant ca à la place ca fonctionne
shader.setParameter("texture", tex2);
sf::RenderTexture renderTexture2;
renderTexture2.create(s_x,s_y);
renderTexture2.clear();
renderTexture2.draw(rect, &shader);
renderTexture2.display();
//affichage
const sf::Texture& tex3 = renderTexture2.getTexture();
sf::Sprite sprite(tex3);
sf::RenderWindow window(sf::VideoMode(s_x,s_y), "");
window.clear();
window.draw(sprite);
window.display();
char c;
std::cin>>c;
return 0;
}
vertex.frag
uniform sampler2D texture;
void main()
{
// lookup the pixel in the texture
vec4 pixel = texture2D(texture, gl_TexCoord[0].xy);
// multiply it by the color
gl_FragColor = gl_Color * pixel;
}
-
La texture résultante d'un sf::RenderTexture étant effectivement retournée, SFML se débrouille normalement pour jouer sur d'autres paramètres afin qu'au final tout soit dans le bon sens. En l'occurence, lorsque tu dessines un objet SFML avec une telle texture, une matrice de texture est mise en place avec un facteur d'échelle -1 en Y, pour re-retourner la texture. Le problème ici c'est que ta texture passe complètement au travers de ce système puisque tu la passes directement au shader.
La solution simple, si ton vrai cas d'utilisation est similaire à ce code minimal, est donc de passer la texture lors de l'appel à draw() pour que la mixture interne de SFML fasse correctement son boulot. Si tu ne peux pas le faire, alors il faudra gérer cette inversion toi-même.
-
Mon cas est plus compliqué: les shaders qui sont utilisés pour le rendu sont quelconques et peuvent avoir plusieurs textures en entrée.
Retourner moi même la texture ne me semble pas non plus possible: il faudrait que je la retourne seulement si son état interne pixelFlipped est true, et je n'ait pas accès a cet attribut... Retourner l'image quand pixelFlipped=true c'est ce que fait le constructeur par recopie si je ne me trompe pas. Il n'y aurait donc pas d'autre solution que de copier la texture tout entière?
Dans tous les cas, ce petit problème mériterait d'être documenté quelque part
-
Le moyen le plus simple, a priori, serait d'écrire un vertex shader qui retourne les coordonnées de texture en Y selon un paramètre booléen qui indiquerait si la texture doit être retournée (i.e. si elle provient d'un sf::RenderTexture) ou non. Ou alors, si tu veux un truc homogène, tu retournes toutes les autres textures ;D
-
Vu que je fais draw avec des vertex, je peux juste changer les coordonnées des textures sur les vertex pour retourner l'image. Le vrai problème est de savoir quand il faut les retourner.
D'ailleurs si c'est possible de retourner une image comme ça, la copie de Textures devrait pas être plus couteuse et donc j'imagine que le constructeur par recopie de Texture doit pas être vraiment optimal. Enfin je dis j'imagine, mais j'ai été regarder le code et je suis sûr qu'il y a pas besoin de faire passer la texture par la mémoire centrale par le biais d'une Image. Enfin je dis ça, je pense que tu doit très bien être au courant de tout ça ;)
Pour en finir avec ce problème: est ce que tu penses que c'est safe de ne pas appeler display pour éviter que ce flag soit mis à vrai? (tout aurait été tellement plus simple si il y avait une méthode flipTexture() )
Je pense surtout aux problèmes que ça pourrait causer quand l'implémentation "FBO" n'est pas disponible. Déjà est ce que c'est possible que ça ne soit pas dispo avec une version 2.1 de SFML? C'est lié au matériel?
-
Le vrai problème est de savoir quand il faut les retourner.
Quand ça vient d'un sf::RenderTexture.
D'ailleurs si c'est possible de retourner une image comme ça, la copie de Textures devrait pas être plus couteuse et donc j'imagine que le constructeur par recopie de Texture doit pas être vraiment optimal. Enfin je dis j'imagine, mais j'ai été regarder le code et je suis sûr qu'il y a pas besoin de faire passer la texture par la mémoire centrale par le biais d'une Image. Enfin je dis ça, je pense que tu doit très bien être au courant de tout ça
Quel rapport entre le retournement des coordonnées de texture, et la duplication d'une texture ?
Et puisque tu es sûr qu'on peut dupliquer une texture sans passer par la mémoire centrale, je serai ravi que tu me montres comment faire ;)
Pour en finir avec ce problème: est ce que tu penses que c'est safe de ne pas appeler display pour éviter que ce flag soit mis à vrai?
Non, cf. ce que tu dis après (ça dépend de l'implémentation).
tout aurait été tellement plus simple si il y avait une méthode flipTexture()
Et comment cette fonction aurait-elle magiquement fonctionné ? Autrement que recopier la texture en mémoire centrale, inverser les pixels, et la ré-uploader en mémoire graphique, bien sûr.
Déjà est ce que c'est possible que ça ne soit pas dispo avec une version 2.1 de SFML? C'est lié au matériel?
C'est lié au driver OpenGL. SFML supporte les cartes OpenGL 1.4 (1.2 si on laisse de côté une ou deux fonctionnalités obscures) ; les FBO sont arrivés dans le core OpenGL 3.0. Donc entre les deux, ça dépend si le driver (et donc la carte) les supporte sous forme d'extension.
-
Utiliser draw sur un sprite avec une texture ne revient pas a copier la texture vers la texture interne au rendertexture sans passer par la mémoire centrale?
Il y a un moyen de tester que FBO est bien disponible? Histoire que je puisse donner un petit message de Warning a mon utilisateur si ce n'est pas le cas?
-
Utiliser draw sur un sprite avec une texture ne revient pas a copier la texture vers la texture interne au rendertexture sans passer par la mémoire centrale?
Donc si j'extrapole un peu, tu suggérerais d'implémenter la copie de sf::Texture de la sorte :
Texture::Texture(const Texture& source)
{
sf::RenderTexture rt;
rt.create(source.getSize().x, source.getSize().y);
rt.draw(sf::Sprite(source));
rt.display();
m_id = rt.getTexture().m_id;
m_size = rt.getTexture().m_size;
...
// ... bidouiller un peu pour que la destruction de rt n'entraîne pas la destruction de sa texture interne ...
}
?
Il y a un moyen de tester que FBO est bien disponible? Histoire que je puisse donner un petit message de Warning a mon utilisateur si ce n'est pas le cas?
Oui, mais pas via SFML. Regarde dans l'implémentation comment c'est fait, et fait pareil dans ton code.
-
Mon idée c’était plutôt d'utiliser le code qui est à l’intérieur de RenderTexture pour copier la texture, mais dans l'idée c'est ça.
Enfin je sais pas ce qu'il y a exactement dans le Sprite, mais je vais supposer que ça ne copie pas la texture.
Mon argument c'est juste que si RenderTexture peut copier une texture sans passer par la mémoire centrale, le constructeur de copie de texture doit aussi pouvoir le faire. C'est aussi ce que fait mon premier code: il copie 2 fois les textures sans passer par la mémoire centrale (mais il se trouve que ça merde un peu alors...).
J'ai pas vraiment le temps en ce moment, mais je peux mieux étudier la question et proposer une pull request quand j'aurais le temps.
-
Je ne suis pas certain de vouloir aller dans cette direction. Ca peut apporter pas mal de complications potentielles, et je ne suis même pas sûr que ça serait plus performant. Et pour pas grand chose. La copie de texture c'est pas quelque chose que tu es censé faire 60 fois par seconde. Ici on a été amené aborder le sujet par un moyen détourné ; copier la texture ne serait de toute façon pas la bonne solution à ton problème.
-
Je comprends que ça pose problème, surtout avec les différence de matériel qu'il peut y avoir entre les machines qui utilisent SFML.
Copier la texture est une solution à mon problème, vu que ça remet la texture à l'endroit durant la copie. Mais c'est vrai que ce n'est pas du tout une solution optimale et qu'il vaut mieux travailler en remettant la texture à l'endroit "à la main".
Je posterais la solution que j'ai retenu pour éviter ce problème, au cas ou quelqu'un aurait le même.
-
J'ai écrit un code qui corrige l'exemple que j'ai donné. Personnellement il me reste toujours un petit soucis pour déterminer quand une texture vient d'un RenderTexture dans mon code qui est un peu plus compliqué que ça. Pour moi il manque un accesseur vers pixelFlipped qui me permettrait de savoir, mais comme il s'agit d'un problème très particulier et très lié a l'implémentation, je pense pas que cet accesseur soit une vraie bonne idée. Une autre idée pour déterminer quand une texture provient d'une RenderTexture?
Voila le code corrigé. L'idée c'est d'afficher à l'envers les textures issues d'un RenderTexture en donnant aux vertex des coordonnées de textures inversées. On voit que comme le shader court circuite le mécanisme des Textures, les coordonnées de textures sont normalisées (entre 0.0 et 1.0) et non en pixels comme l'indique la doc.
#include <iostream>
#include "SFML/Graphics.hpp"
using namespace std;
int main()
{
sf::Texture tex;
tex.loadFromFile("lena.png");
int s_x = tex.getSize().x;
int s_y = tex.getSize().y;
sf::Shader shader;
shader.loadFromFile("shader.frag",sf::Shader::Fragment);
shader.setParameter("texture", tex);
sf::VertexArray rect(sf::Quads,4);
rect[0].position = sf::Vector2f(0,0);
rect[1].position = sf::Vector2f(0,s_y);
rect[2].position = sf::Vector2f(s_x,s_y);
rect[3].position = sf::Vector2f(s_x,0);
//1er render: on dessine la texture à l'endroit
rect[0].texCoords = sf::Vector2f(0.0,0.0);
rect[1].texCoords = sf::Vector2f(0.0,1.0);
rect[2].texCoords = sf::Vector2f(1.0,1.0);
rect[3].texCoords = sf::Vector2f(1.0,0.0);
sf::RenderTexture renderTexture;
renderTexture.create(s_x,s_y);
renderTexture.clear();
renderTexture.draw(rect, &shader);
renderTexture.display();//L'image se retourne mais pixelFlipped est bien à true (fonctionnement normal de display)
const sf::Texture& tex2 = renderTexture.getTexture();
shader.setParameter("texture", tex2);
//2eme render: on dessine la texture à l'envers (1.0 <-> 0.0 en y)
rect[0].texCoords = sf::Vector2f(0.0,1.0);
rect[1].texCoords = sf::Vector2f(0.0,0.0);
rect[2].texCoords = sf::Vector2f(1.0,0.0);
rect[3].texCoords = sf::Vector2f(1.0,1.0);
sf::RenderTexture renderTexture2;
renderTexture2.create(s_x,s_y);
renderTexture2.clear();
renderTexture2.draw(rect, &shader);
renderTexture2.display();//L'image se retourne 2 fois : une fois à cause des vertex avec les coordonnées de textures à l'envers
//+ une fois à cause du rendu openGl (la texture en entrée du shader n'est pas redressée)
//L'image est bien toujours à l'envers avec pixelFlipped à true
const sf::Texture& tex3 = renderTexture2.getTexture();
sf::Sprite sprite(tex3);
sf::RenderWindow window(sf::VideoMode(s_x,s_y), "");
window.clear();
window.draw(sprite);
window.display();
char c;
std::cin>>c;
return 0;
}
-
Une autre idée pour déterminer quand une texture provient d'une RenderTexture?
Via SFML, aucune chance. Tu n'as pas d'autre choix que de trimballer cette information toi-même.