IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

DirectX Discussion :

Renseignements techniques sur les Buffers.


Sujet :

DirectX

  1. #1
    Membre éprouvé
    Profil pro
    Inscrit en
    Décembre 2004
    Messages
    585
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2004
    Messages : 585
    Points : 1 139
    Points
    1 139
    Par défaut Renseignements techniques sur les Buffers.
    Bonjour.
    Dans les exemples simples de programmes DirectX, la boucle de rendu fonctionne plus ou moins toujours sur le même principe : pour chaque mesh, désigner le VertexBuffer, l'IndexBuffer, l'InputLayout, les shaders, les Textures, etc. et appeler Draw_quelquechose() .
    Dans quelques sites, comme ici , ou même dans la FAQ DirectX, on conseille d'optimiser la gestion des Buffers, en particulier en essayant de regrouper les données dans un nombre minimal de Buffers de taille assez importante (sans trop exagérer mais sans précisions supplémentaires sur le sujet...).
    Je comprends bien l'idée. Néanmoins, je me pose quelques questions :
    1- lorsqu'à chaque boucle je choisis un VB, un IB, un IL, un Shader, une Texture etc. , est-ce que le transfert des données vers la carte graphique se fait à ce moment-là, ou bien est-ce qu'il est fait dès la création du VB et de l'IB, par exemple, ou bien encore une fois au début, à la première utilisation uniquement mais plus ensuite ?
    2- d'un autre côté, si les cartes actuelles ont des tas de giga-octets de mémoire, ça ne doit pas être seulement pour pouvoir stocker des Textures gigantesques, mais aussi retenir d'une Frame à l'autre les données utilisées lors de la Frame précédente, même si elles concernent plusieurs meshes totalement indépendants ?
    3- et si c'est la solution 2, comment spécifier à la Carte qu'elle peut virer définitivement tels Buffers, ou bien préférer celui-ci à celui-là ?
    Merci !
    L'avis publié ci-dessus est mien et ne reflète pas obligatoirement celui de mon entreprise.

  2. #2
    Membre expérimenté
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Mars 2011
    Messages
    576
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mars 2011
    Messages : 576
    Points : 1 528
    Points
    1 528
    Par défaut
    Salut,

    En OpenGL (qui doit faire plus ou moins comme DirectX), les transfert CPU->GPU sont généralement effectué lors du premier draw call qui les utilises. Cela concerne quasiment tous les tranferts: changement d'état, textures, VertexBuffer, FrameBuffer, ... Ca permet entre autre d'eviter d'envoyer des données qui ne serviront à rien (ce qui arrive beaucoup plus souvent qu'on ne le pense dans une appli classique...)

    Ensuite, il faut savoir que les tranferts CPU/GPU ont un très gros débit (de l'ordre du Go/s), mais aussi une très grosse lattence (de l'ordre de la ms). Il est beaucoup plus performant d'envois un gros buffer que plusieurs petits. En gros, si tu envois une fois 10Go, tu en aura pour 1ms de latence + 1s de tranfert. Si tu envois 1000 fois 10Mo, je te laisse faire le calcul

    Il y a aussi un grand principe en gestion mémoire d'une manière général qui est celui de la localité spaciale. Lorsque tu accède à une donnée, il est en général très courant d'accéder aux données voisines (pixel dans une image, vertex dans un mesh, valeur dans un tableau, ...). Lorsque tu accède à une données, ses voisins sont mis en cache pour pouvoir être accédé plus rapidement. Si tes données sont toutes dans un buffer, elles sont assez "proches" et ont plus de chance d'être mise en cache qui si elles sont éparpillées aux 4 coins de la mémoire. Lorsque tu crés plusieurs buffers, rien ne dit qu'ils seront mis les uns à la suite des autres en mémoire. Ils seront mis "là où il y a de la place".

    Je ne comprend pas trop ton point 2
    La carte garde en mémoire les buffers que tu lui as envoyé (vertex, texture, ...). Par contre, à chaque frame elle recommence le rendu depuis le début. Elle n'a pas de système permettant de déduire de la frame précédente ce qu'elle doit recalculer ou pas. Ce genre de technique existe, mais c'est à toi de l'implémenter.

    En OpenGL, il y a des fonctions glDeleteBuffers, ... qui te permettent de libérer la mémoire GPU. Il doit y avoir la même chose en DirectX.
    La perfection est atteinte, non pas lorsqu’il n’y a plus rien à ajouter, mais lorsqu’il n’y a plus rien à retirer. - Antoine de Saint-Exupéry

  3. #3
    Membre éprouvé
    Profil pro
    Inscrit en
    Décembre 2004
    Messages
    585
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2004
    Messages : 585
    Points : 1 139
    Points
    1 139
    Par défaut
    Ah, merci pour cette excellente réponse ! Ca éclaire quelques points nébuleux. Comprendre la technique n'a jamais nui à une utilisation intelligente
    En gros, si je résume et si j'ai bien compris, dans une boucle du type "j'affecte le VertexBuffer (et les autres), je dessine, je reboucle" , la partie "copie des données du VertexBuffer vers la carte graphique" ne se produit qu'une seule fois.
    C'est ce que je voulais dire par "retenir d'une frame à l'autre les données nécessaires" : non pas éviter de recalculer le rendu, mais ne pas recharger les Vertex déjà chargés une fois juste avant.
    Reste ma question du point 3 : comment informer la carte que telles données ne sont plus utiles ? Comme je ne connais pas OpenGL mais comme en général il fonctionne toujours sur les mêmes cartes modernes que DirectX (et inversement), j'imagine que ce qu'un langage sait faire, l'autre le sait aussi... Reste à trouver la commande !
    Encore merci.

    [edit] PS : ben on dirait que ça n'existe pas, le glDeleteBuffer en DirectX !?! Ou en tout cas pas via SharpDX, ou bien je ne cherche pas au bon endroit.
    L'avis publié ci-dessus est mien et ne reflète pas obligatoirement celui de mon entreprise.

  4. #4
    Membre expérimenté
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Mars 2011
    Messages
    576
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mars 2011
    Messages : 576
    Points : 1 528
    Points
    1 528
    Par défaut
    Voilà, enfin, si tu utilise des buffer de type VBO ou display list.
    Si tu utilises des vieux buffer type Vertex Array, ils ne sont pas stockés sur la carte et tu dois les renvoyer à chaque fois. A fuire comme la peste donc...

    Pour que le rendu soit performant, il faut absolument éviter les transferts CPU/GPU, donc envoyer les données sur GPU et y rester. Pour info il y a aussi un autre gros point noir à transférer des données CPU/GPU, c'est que ça impose un point de synchro. Le GPU fait sont boulot dans sont coin sans s'occuper du CPU. Typiquement, lorsque tu envois une commande de dessin Draw, le CPU rend tout de suite la main. Le temps de l'appel à Draw coté CPU est quasi nul et la commande sera réellement executé plus tard par le GPU. Pendant ce temps, le CPU peut continuer son boulot.
    Il est très fréquent de commencer à dessiner une frame (après un SwapBuffer), alors que la frame d'avant n'est même pas terminer, voir n'a même pas commencé dans les cas les plus extrèmes.
    Par contre, si tu veux récupérer le résultat du GPU (par un ReadPixel par exemple), le CPU est obligé d'attendre que le GPU ait fini son boulot. Pendant ce temps il ne fait rien...

    Pour ce qui est de la mémoire sous DirectX, d'après la faq, ca ressemblerai plus à un system retain/release qu'a un système alloc/désalloc. Apparement, elle est désaloué automatiquement lorsque plus personne ne pointe dessus à la manière d'un smart pointeur, contrairement à OpenGL où il faut explicitement la détruire.

    Ps: Je ne connais pas du tout DirectX et j'utilise la terminologie OpenGL mais ça doit se ressembler.
    La perfection est atteinte, non pas lorsqu’il n’y a plus rien à ajouter, mais lorsqu’il n’y a plus rien à retirer. - Antoine de Saint-Exupéry

  5. #5
    Membre éprouvé
    Profil pro
    Inscrit en
    Décembre 2004
    Messages
    585
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2004
    Messages : 585
    Points : 1 139
    Points
    1 139
    Par défaut
    Citation Envoyé par pyros Voir le message
    Pour ce qui est de la mémoire sous DirectX, d'après la faq, ca ressemblerai plus à un system retain/release qu'a un système alloc/désalloc. Apparement, elle est désaloué automatiquement lorsque plus personne ne pointe dessus à la manière d'un smart pointeur, contrairement à OpenGL où il faut explicitement la détruire.
    Ben c'est pas si clair que ça... qu'une absence de pointeur côté CPU libère automatiquement la mémoire du GPU ! Et je n'arrive pas à activer le mode debug :/ . Je vais donc me contenter de tester !
    Encore merci pour ces renseignements.
    L'avis publié ci-dessus est mien et ne reflète pas obligatoirement celui de mon entreprise.

  6. #6
    Membre expérimenté
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Mars 2011
    Messages
    576
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mars 2011
    Messages : 576
    Points : 1 528
    Points
    1 528
    Par défaut
    En fait, ce n'est pas vraiment l'absence de pointeur qui provoque la destruction.
    D'après ce que j'ai compris, chaque objet COM possède un compteur de référence interne. Lorsque tu fait un "Retain" sur cette objet, le compteur est incrémenté de 1. Lorsque tu fait un "Release", le compteur est décrémenté:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    COMObject* ptr = new COMObject;
    ptr->retain(); // le compteur de ref pass à 1
    COMObject* ptr2 = ptr;
    ptr2->retain() // le compteur passe à 2 car ptr et ptr2 pointent sur la même ressource
    [...]
    ptr->release(); // le compteur passe à 1
    [...]
    ptr2->release(); // le compteur passe à 0. Plus personne n'utilise le COMObject, donc son destructeur est appelé. Ce qui permet de libérer les ressource GPU.
    Tu peux imaginer COMObject comme une structure avec un int (le compteur de ref) et un pointeur sur la "vrai" ressource COM.

    A noter aussi que la mémoire GPU n'est pas forcement libérée tout de suite. C'est toujours le même principe. Tu envois une commande de libération au driver qui s'occupera de libérer la mémoire plus tard si nécessaire.

    Au cas où tu ne connaitrais pas, il y a Process Explorer ("clubic process explorer" dans google, tu devrais trouver facilement), qui permet entre autre de monitorer la mémoire GPU de manière assez précise.

    Après, l'incrémentation ou la décrémentation n'est peut être pas explicitement faite par un retain/release. En fait, il suffit de redefinir l'operateur= pour incrémenter le compteur, et le destructeur pour le décrémenter. C'est le principe des smart pointeur (Boost, std, il en existe plusieur mais ils marchent tous plus ou moin selon le même principe):
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    {
      COMObjectPtr ptr = new COMObject; // ref passe à 1
      {
        COMObjectPtr ptr2 = ptr; // ref passe à 2
      }
      // destruction de ptr2, ref passe à 1
    }
    // destruction de ptr, ref passe à 0, la ressource est détruite.
    Note qu'il s'agit cette fois d'un COMObjectPtr (et non un simple "pointeur C"), une classe un peu plus intelligente qui encasule un COMBject* et la logique d'incrémentation/décrémentation. Google est ton amis si tu veux plus d'info sur les smart pointeur. Au delà des ressource GPU, c'est toujours bon de savoir s'en servir pour ses propres objets .
    La perfection est atteinte, non pas lorsqu’il n’y a plus rien à ajouter, mais lorsqu’il n’y a plus rien à retirer. - Antoine de Saint-Exupéry

  7. #7
    Membre éprouvé
    Profil pro
    Inscrit en
    Décembre 2004
    Messages
    585
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2004
    Messages : 585
    Points : 1 139
    Points
    1 139
    Par défaut
    Heureusement que je connais un peu le C++, que je me suis farci le Thinking in C++ et que j'ai pas mal lu sur le sujet . Mais après avoir gouté au C#, pas question de revenir au C++ . Et donc j'ai bien compris ce que tu as dit, reste que je ne sais pas comment ça s'arrange avec dotnet et l'intermédiaire SharpDX. Ce que je me dis, c'est qu'en dessous c'est du C++ ...
    En tout cas, merci une fois de plus pour Process Explorer : il y a 2 indicateurs sur la mémoire GPU que je vais analyser un peu en faisant quelques tests.
    L'avis publié ci-dessus est mien et ne reflète pas obligatoirement celui de mon entreprise.

  8. #8
    Expert confirmé

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2007
    Messages
    1 895
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Septembre 2007
    Messages : 1 895
    Points : 4 551
    Points
    4 551
    Par défaut
    Citation Envoyé par Thorna Voir le message
    Et donc j'ai bien compris ce que tu as dit, reste que je ne sais pas comment ça s'arrange avec dotnet et l'intermédiaire SharpDX. Ce que je me dis, c'est qu'en dessous c'est du C++ ...
    Pas que je sache. En fait, c'est même le contraire. : SharpDX est entièrement écrit en C#, l'interrop se faisant au plus bas niveau sans avoir besoin de passer par du C++/CLI. SlimDX est écrit en C++/CLI pour une grande part (mais ce sont deux projets différents).
    [FAQ des forums][FAQ Développement 2D, 3D et Jeux][Si vous ne savez pas ou vous en êtes...]
    Essayez d'écrire clairement (c'est à dire avec des mots français complets). SMS est votre ennemi.
    Evitez les arguments inutiles - DirectMachin vs. OpenTruc ou G++ vs. Café. C'est dépassé tout ça.
    Et si vous êtes sages, vous aurez peut être vous aussi la chance de passer à la télé. Ou pas.

    Ce site contient un forum d'entraide gratuit. Il ne s'use que si l'on ne s'en sert pas.

  9. #9
    Expert éminent sénior
    Avatar de Mat.M
    Profil pro
    Développeur informatique
    Inscrit en
    Novembre 2006
    Messages
    8 361
    Détails du profil
    Informations personnelles :
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Novembre 2006
    Messages : 8 361
    Points : 20 381
    Points
    20 381
    Par défaut
    bonjour,
    Citation Envoyé par pyros Voir le message
    D'après ce que j'ai compris, chaque objet COM possède un compteur de référence interne. Lorsque tu fait un "Retain" sur cette objet, le compteur est incrémenté de 1. Lorsque tu fait un "Release", le compteur est décrémenté:
    ressource GPU.
    non ce n'est pas la méthode Retain mais AddRef() et effectivement Release() pour décrémenter le compteur de référence
    Il y a 4méthodes principales pour les objets COM , AddRef(), Release(),DllRegister() et DllUnRegister()
    On ne fait jamais un new avec un objet COM on appelle l'API QueryInterface

    Ceci dit en programmation Direct X on ne manipule jamais les objets COM

  10. #10
    Expert éminent sénior
    Avatar de Mat.M
    Profil pro
    Développeur informatique
    Inscrit en
    Novembre 2006
    Messages
    8 361
    Détails du profil
    Informations personnelles :
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Novembre 2006
    Messages : 8 361
    Points : 20 381
    Points
    20 381
    Par défaut
    Citation Envoyé par Thorna Voir le message
    2- d'un autre côté, si les cartes actuelles ont des tas de giga-octets de mémoire, ça ne doit pas être seulement pour pouvoir stocker des Textures gigantesques, mais aussi retenir d'une Frame à l'autre les données utilisées lors de la Frame précédente, même si elles concernent plusieurs meshes totalement indépendants ?

    d'après ce que je comprends tu parles de BeginScene() /EndScene()...
    Direct3D redessine tout et effectue toutes les transformations nécessaires sur les objets
    Il est évident que plus tu feras des opérations sur les buffers plus tu vas consommer les ressources de la carte.
    Pour éviter de trop à redessiner il faut utiliser des algorithmes des gestion de scène

    Citation Envoyé par Thorna Voir le message
    3- et si c'est la solution 2, comment spécifier à la Carte qu'elle peut virer définitivement tels Buffers, ou bien préférer celui-ci à celui-là ?
    Merci !
    je pense qu'il faut totalement éviter d'utiliser plusieurs Index buffers et n'en utiliser qu'un seul
    Ensuite comme l'écrit Pyros il doit exister une méthode pour le vider
    On accède à un Buffer avec Lock()/Unlock

    Ben c'est pas si clair que ça... qu'une absence de pointeur côté CPU libère automatiquement la mémoire du GPU ! Et je n'arrive pas à activer le mode debug :/ . Je vais donc me contenter de tester !
    Encore merci pour ces renseignements.
    Utiliser un pointeur ? Avec Direct3D comme mentionné précédemment pour accéder à un VertexBuffer Ou IndexBuffer il faut appeler les méthodes Lock et Unlock
    Voir le SDK de Direct3D
    C'est le seul moyen


    voilà un exemple du livre de Franck Di Luna disponibles ici
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
     
    Device->CreateVertexBuffer(
    		6 * sizeof(Vertex), 
    		D3DUSAGE_WRITEONLY,
    		Vertex::FVF,
    		D3DPOOL_MANAGED,
    		&Quad,
    		0);
     
    	Vertex* v;
    	Quad->Lock(0, 0, (void**)&v, 0);
     
    	// quad built from two triangles, note texture coordinates:
    	v[0] = Vertex(-1.0f, -1.0f, 1.25f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f);
    	v[1] = Vertex(-1.0f,  1.0f, 1.25f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f);
    	v[2] = Vertex( 1.0f,  1.0f, 1.25f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f);
     
    	v[3] = Vertex(-1.0f, -1.0f, 1.25f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f);
    	v[4] = Vertex( 1.0f,  1.0f, 1.25f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f);
    	v[5] = Vertex( 1.0f, -1.0f, 1.25f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f);
     
    	Quad->Unlock();
    ensuite pour dessiner

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    Device->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xffffffff, 1.0f, 0);
    		Device->BeginScene();
     
    		Device->SetStreamSource(0, Quad, 0, sizeof(Vertex));
    		Device->SetFVF(Vertex::FVF);
    		Device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 2);
     
    		Device->EndScene();
    		Device->Present(0, 0, 0, 0);

  11. #11
    Membre éprouvé
    Profil pro
    Inscrit en
    Décembre 2004
    Messages
    585
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2004
    Messages : 585
    Points : 1 139
    Points
    1 139
    Par défaut
    Citation Envoyé par Mat.M Voir le message
    d'après ce que je comprends tu parles de BeginScene() /EndScene()...
    Direct3D redessine tout et effectue toutes les transformations nécessaires sur les objets
    Pas exactement. En fait, je m'interrogeais surtout sur le moment auquel les données des VertexBuffers etc. étaient envoyées vers la carte. D'ailleurs, pas la moindre trace de BeginScene ou de EndScene dans la vision que SharpDX me donne de DirectX10/11... Et mon histoire de "retenir" des données d'une Frame à l'autre ne concernait pas les résultats de rendu, mais justement les données de Vertex et d'Index etc.
    Citation Envoyé par Mat.M Voir le message
    je pense qu'il faut totalement éviter d'utiliser plusieurs Index buffers et n'en utiliser qu'un seul
    Euh... Ca ne ressemble à aucun des exemples fournis ici et là, ni des petits codes qu'on trouve dans les tutos où, pour dessiner 2 cubes et 1 cylindre, on utilise 3 VB et 3 IB . Néanmoins, je comprends maintenant bien mieux qu'avant pourquoi il en faut un minimum.
    Citation Envoyé par Mat.M Voir le message
    Ensuite comme l'écrit Pyros il doit exister une méthode pour le vider. On accède à un Buffer avec Lock()/Unlock
    Pas facile de trouver de la doc pour Lock() dans DirectX11, même que la méthode n'existe pas du tout via SharpDX !
    Citation Envoyé par Mat.M Voir le message
    voilà un exemple du livre de Franck Di Luna
    J'ai commandé le Di Luna pour DirectX11. Je devais même le recevoir aujourd'hui... On verra ça, mais ça a vraiment l'air d'un monde totalement différent de celui de DirectX9.
    Citation Envoyé par Mat.M Voir le message
    [Exemple de code de CreateVertexBuffer]
    Justement, la méthode utilisée via dotnet et SharpDX n'a pas l'air de fonctionner comme ça : je crée mon tableau de Vertices et j'utilise
    VertexBuffer = Buffer.Create(device, BindFlags.VertexBuffer, tableauDeVertices);
    Puis, dans le dessin, j'affecte le Buffer par
    device.InputAssembler.SetVertexBuffers(0,
    new VertexBufferBinding(VertexBuffer, TailleDuType, 0));

    et pareil pour l'IndexBuffer. Pas de trace de Lock ni de rien de tout ça... Un peu comme pour BeginScene et EndScene !
    L'avis publié ci-dessus est mien et ne reflète pas obligatoirement celui de mon entreprise.

Discussions similaires

  1. Réponses: 3
    Dernier message: 21/07/2009, 10h27
  2. Réponses: 7
    Dernier message: 02/01/2008, 14h32
  3. Question technique sur les listes déroulantes
    Par kenny49 dans le forum Langage
    Réponses: 3
    Dernier message: 18/04/2007, 10h41
  4. Question techniques sur les extents
    Par lecharcutierdelinux dans le forum Oracle
    Réponses: 16
    Dernier message: 24/08/2006, 09h01

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo