Bienvenue, Invité. Merci de vous connecter ou de vous inscrire.
Avez-vous perdu votre e-mail d'activation ?

Auteur Sujet: RenderTexture, shaders et image retournée  (Lu 3247 fois)

0 Membres et 1 Invité sur ce sujet

arnodu

  • Newbie
  • *
  • Messages: 7
    • Voir le profil
RenderTexture, shaders et image retournée
« le: Mars 23, 2014, 07:15:28 pm »
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;
}
 

Laurent

  • Administrator
  • Hero Member
  • *****
  • Messages: 32498
    • Voir le profil
    • SFML's website
    • E-mail
Re : RenderTexture, shaders et image retournée
« Réponse #1 le: Mars 23, 2014, 07:53:01 pm »
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.
Laurent Gomila - SFML developer

arnodu

  • Newbie
  • *
  • Messages: 7
    • Voir le profil
Re : RenderTexture, shaders et image retournée
« Réponse #2 le: Mars 23, 2014, 09:13:00 pm »
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

Laurent

  • Administrator
  • Hero Member
  • *****
  • Messages: 32498
    • Voir le profil
    • SFML's website
    • E-mail
Re : RenderTexture, shaders et image retournée
« Réponse #3 le: Mars 23, 2014, 09:42:43 pm »
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
Laurent Gomila - SFML developer

arnodu

  • Newbie
  • *
  • Messages: 7
    • Voir le profil
Re : RenderTexture, shaders et image retournée
« Réponse #4 le: Mars 23, 2014, 10:02:57 pm »
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?

Laurent

  • Administrator
  • Hero Member
  • *****
  • Messages: 32498
    • Voir le profil
    • SFML's website
    • E-mail
Re : RenderTexture, shaders et image retournée
« Réponse #5 le: Mars 23, 2014, 11:37:59 pm »
Citer
Le vrai problème est de savoir quand il faut les retourner.
Quand ça vient d'un sf::RenderTexture.

Citer
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 ;)

Citer
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).

Citer
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.

Citer
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.
Laurent Gomila - SFML developer

arnodu

  • Newbie
  • *
  • Messages: 7
    • Voir le profil
Re : RenderTexture, shaders et image retournée
« Réponse #6 le: Mars 24, 2014, 12:11:04 pm »
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?

Laurent

  • Administrator
  • Hero Member
  • *****
  • Messages: 32498
    • Voir le profil
    • SFML's website
    • E-mail
Re : RenderTexture, shaders et image retournée
« Réponse #7 le: Mars 24, 2014, 01:22:53 pm »
Citer
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 ...
}
?

Citer
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.
Laurent Gomila - SFML developer

arnodu

  • Newbie
  • *
  • Messages: 7
    • Voir le profil
Re : RenderTexture, shaders et image retournée
« Réponse #8 le: Mars 24, 2014, 06:32:09 pm »
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.

Laurent

  • Administrator
  • Hero Member
  • *****
  • Messages: 32498
    • Voir le profil
    • SFML's website
    • E-mail
Re : RenderTexture, shaders et image retournée
« Réponse #9 le: Mars 24, 2014, 07:27:25 pm »
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.
Laurent Gomila - SFML developer

arnodu

  • Newbie
  • *
  • Messages: 7
    • Voir le profil
Re : RenderTexture, shaders et image retournée
« Réponse #10 le: Mars 24, 2014, 08:28:14 pm »
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.

arnodu

  • Newbie
  • *
  • Messages: 7
    • Voir le profil
Re : RenderTexture, shaders et image retournée
« Réponse #11 le: Mars 24, 2014, 09:01:31 pm »
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;
}
 

Laurent

  • Administrator
  • Hero Member
  • *****
  • Messages: 32498
    • Voir le profil
    • SFML's website
    • E-mail
Re : RenderTexture, shaders et image retournée
« Réponse #12 le: Mars 24, 2014, 09:22:12 pm »
Citer
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.
Laurent Gomila - SFML developer