Denis Salem | Blog

CATEGORIES

ARCHIVES

L'Oiseau pendule, animated step by step

Aout 2015

Je cherchais quelque chose de plus visuel et de beaucoup moins mental dirons nous. Une troisième tentative avec les champignons l'été derniers, au dernier festival de la Hadra dans le Vercors, semble avoir confirmé que ça ne correspond pas à ce que je cherche, mais ce pourrait être aussi une question de dosage et d'état d'esprit. Toujours est-il que j'ai pu mettre la mains sur de la Salvia Divinorium, de la bonne du terroir, si l'on peut dire. Je m'efforçais à ce moment de me défaire des aprioris que j'avais développé sur les effets en lisant les trip reports d'autres psychonauts, retenant que ce que j'avais vécu avec les champignons était finalement assez différent de ce que j'avais lu. Encore une fois le dosage et l'état d'esprit y sont sans dans doute pour quelque chose.

La Salvia se chique ou se fume. Je n'aime pas fumer et il paraitrait que chiquer la Salvia augmenterait substantiellement ses effets. Mon amie et moi même choisîmes donc de mâcher la plante jusqu'à ce que les effets apparaissent. Nous avions préparé un grand bol de feuilles fraiches. Pendant ce temps, et bien que je n'étais guère inquiet d'un éventuelle accident ou bad trip, je m'assurais que son appartement, qui donc serait le lieu de notre trip, ne présenterait aucun danger potentiel.

Le goût est infâme. Il est peu probable que vous ayez déjà gouttez quelque chose d'aussi infecte. C'est d'autant plus difficile qu'il faut garder la mixture ainsi malaxée en bouche. La substance active pénètre par les muqueuse, en cas d'ingestion, celle-ci est détruite et ne pénètre pas dans le sang. Nous poursuivons péniblement la chique assis sur le canapé de mon amie. Malheureusement pour elle, le goût est tellement ignoble qu'elle finit par tout recracher et vomir par la même occasion.

Elle reviens à mes côtés. Des effets très léger se font déjà sentir, nous ressentons vite une pesanteur dans notre corps et notre esprit. Ne pouvant pas parler, parce que la bouche pleine, je lui propose avec la main de nous coucher dans le lit. J'aurais voulu (et j'aurais sans doute du) garder en bouche plus longtemps le produit que je recrache finalement peu de temps après elle. Je l'embrasse du bout des lèvres. Je la sens un peu déçu de ne pas avoir pu garder en bouche la Salvia, découragée aussi peut-être.

Allongé l'un contre l'autre je l'enlace dans mes bras, mais je ne suis pas bien installé. Ce n'est pas exactement de la fatigue, mais je ressens le besoins d'immobilité et de confort. Je lui explique et je m'excuse, je sais qu'elle veut être contre moi. Je reste collé à elle, mais détendu de tout mon long, emmitouflé avec elle sous sa couette patchwork. Bien qu'elle ne dise rien je ressens qu'elle perçoit quelque chose. Au bout d'un moment elle me dit qu'elle contemple les légères déformations de l’environnement. Comme moi, elle est assommée par la substance. De mon côté, c'est plus que des déformations. Je n'ai pas spécialement d'états d'âmes, c'est très visuel et ça me plait ainsi. Je me rend tout de même compte que comme pour elle, la dose est faible. C'est comme si le trip était lointain. Je suis à mit chemin entre le vrai monde et « là-bas ».

Je ne suis pas sûr qu'elle le perçoit comme moi, pour elle l'environnement ne fait qu'onduler, ce qui est déjà pas mal quand on a pas l'habitude, j'imagine. C'est comme si notre être était multidimensionnel. Il faut entendre par là que celui-ci s'étend au-delà des trois dimensions physiques. C'est comme si cet être, dont le prolongement est l'esprit, changeait de perspective ; de dimension. Par décalage ou translation successive sur l'un des axes d'une dimension inconnue du commun des mortels. Je repense alors à une expérience de pensée que j'avais imaginé. Lorsque vous faites de l'infographie 3D ou des mathématiques vous représentez le mouvement d'un corps ou d'un objet en fonction du temps qui correspond à une dimension à part entière. Je me demandais alors ce qu'il se passerait visuellement si l'on inversait l'axe x,y ou z avec t. Comment cela affecterait-il les formes et le mouvement?

À mesure de cette lente progression vers « l'autre côté », effectivement, l'environnement ondule et se transforme en un autre monde. Il devient évident que le temps est une dimension spatial. Ce n'est pas le temps qui s'écoule, c'est la conscience qui glisse dans un univers statique. Les objets les plus insignifiants semblent habités par un esprit qui les animes, littéralement.

De l'autre côté de la chambre en bazar, des vêtements sont jetés pêle mêle sur les meubles, et notamment une chaise solitaire et encombrante que mon amie aime d'autant plus qu'elle est orange. Mon regards semble figé sur ces vêtements. Ils semblent voler majestueusement. Les plis du tissu s'anime gracieusement, comme des méduses à la dérive ou un voile sous la brise. J'ignore si c'est l'univers qui bouge ou si c'est le vêtement qui se déplace. Il ressemble à une créature étrange et inoffensive, le genre sans système nerveux complexe et qui se nourrirait de micro organisme invisible à l’œil nu.

Malgré les visions je sens qu'il me faut un effort de concentration pour les maintenir. Je ressens mon corps vibrer comme si j'allais sortir de celui-ci à tout moment. Non, ça n'arrivera pas me dis-je. J'y suis encore bien encré. Je n'en ai pas assez pris. Le lit semble immense, comme un océan, les plis de la couette ondulent, c'est comme si j'étais sur un océans de tissu multicolore. C'est vraiment magnifique. Par instant on croirait à un décors de théâtre de papier et de carton animé en coulisse, derrière la scène. Je suis minuscule dans cet espace si grand.

Je suis frustré de ne pas avoir gardé la Salvia plus longtemps en bouche ça aurait pus aller plus loin, je le sens, c'est certains. Je m'endors finalement avec mon amis qui ne m'as pas attendu pour perdre connaissance.

Au réveil, elle me confirmera n'avoir finalement rien vu d'extra ordinaire si ce n'est des distorsions de l'espace, très légère, comme tout le reste de notre trip.

Peut-être la plante s'introduit elle à nous doucement. Peut-être est-ce une invitation à quelque chose de plus profond.

Le 10 février 2014

C. a répondu. Pas de la manière la plus élégante. Je ne suis pas non plus toujours élégant. Le trou noir s'étant évaporé il emporte avec lui une part de mes illusions. Bien d'autres persistent.

Peu de temps après ma première expérience, comme je l'avait dit dans mon précédent TR, j'avais manifesté de la déception quand au produit, mais son esprit semblait m'habiter encore discrètement. Comme une présence ou une inspiration. J'ai cette idée curieuse que la réalité est une chose vivante en soit, et qu'il est possible de communiquer avec. Jung parlait de synchronicité pour un phénomène à mon sens similaire. Après mon premier trip il restait moins d'un gramme et demi de champignons. J'ai tout gobé un dimanche matin de la même façon. Avec un vers d'eau. Mon trip à été successivement perturbé par plusieurs petit événement anodin qu'il n'est pas intéressant de mentionner. L'expérience aura duré deux à trois heures. Nous retiendrons notamment ce moment tristement comique où, confus, je tâtais à l'aveugle dans le boitier électrique -propriété d'EDF- de chez moi pour attraper mon portable que j'avais fait tomber dedans. Il n'y avait que des files de phases, neutre, et de terre qui ont manqué de me faire gagner les darwins awards 2014.

Les effets se sont fait sentir 40 minutes après ingestions. Tranquillement en train de refaire le papier peint de mon bureau -assurément psychédélique-. Quand soudain il me semble que je me déconnecte. Tout me semble étrange. Tout semble m'appeler. Une lourde fatigue m'arrasse. Pour une obscure raison mon oscilloscope est allumé. Le signal bruité qui s'y affiche est vaguement périodique et semble traduire un état de confusion mental croissant. Je l’éteins. J'ai besoin de m'assoir. La lumière semble irréel. C'est un peu le malaise à vrai dire, je comprend évidement tout de suite ce qu'il m'arrive. Je suis amusé de physiquement ressentir un truc et me laisse surprendre. Je suis étonné que les effets se manifestent de cette façon avec une si petite dose.

Il ne fait pas beau dehors. Une lumière blafarde jaunâtre traverse les nuages et s'efforce de réchauffer l'air et les couleurs de l'extérieur. Par instant la lumière se reflète de tel sorte que, sur le goudron mouillé, si l'on plisse les yeux, éblouit, on pourrait penser que c'est la mer. Des bourdonnements et des sifflements sourds me traversent le corps. Je connais cette sensation. C'est comme quand je m’apprête à sortir de mon corps sans le vouloir. Je suis incapable de me concentrer, et je me sens sensiblement oppressé. J'appréhende un coup de fil ou une interaction humaine requérant des phrases construites. Je m'allonge à même le sol, sur la moquette. Je reste là un moment bouche bée. Mon corps semble paralysée. Seule mes yeux semblent encore relativement dynamique. Je m'amuse à flouter ma vision comme lorsque, tout petit, incapable de dormir, je me prêtais à ces jeux optique, en fermant plus ou moins mes paupières.

Je suis étendu dans la lumière. Le ciel est gris, mais la pièce est très clair. À un moment, une camionnette se gare devant chez moi. Le ronronnement du moteurs m'apaise et me berce. Bien que je ne la vois pas, j’entends la portière s'ouvrir. J’entends les pas et les clapotis. J’entends le moteur. Je reste ainsi un moment. La pièce semble vivante, et chaque motif, chaque forme semble chargé d'une histoire confuse. Des formes se précisent, d'autres disparaissent. Je rêve. Je ressens de lourdes vagues à mesure que le moteur bourdonne. Je suis dans cet état si particulier ou mon esprit semble pouvoir se détacher de mon corps à tout instant. Il me semble être véritablement entre deux réalités.

Entre la réalité et le rêve, il n'y a rien. Dans cet endroit la mémoire est un fardeaux pesant. C'est peut-être plus vide encore que l'image que l'on se fait de la mort. L'horizon de cet espace intermédiaire est tapissé d'entités et de concepts volatiles. De loin, le monde semble déstructuré. Sans cohérence, à l'image d'un rêve confus dont persisterait pourtant l'impression que l'on serait impliqué dans une conjecture grave, des enjeux important. Ce qui est angoissant, c'est de ne pas pouvoir comprendre la quantité d'information qui me submerge. Pourtant à ce moment là, la réalité lointaine semble plus loquace. Et nous semblons entreprendre de communiquer, ou du moins poser les fondements d'une interaction intelligible. Ce n'est pas exactement Dieu, la réalité serait plus comme un langage, une interface dont on ne saisit absolument le sens la plupart du temps. Je me souviens que lorsque je parle de philosophie au chat, lui n'entend que du bruit. Pour lui, la réalité, c'est avant tout sa sieste, le radiateur, le bol de merde aseptisé que je lui sert et qui me coûte la peau du cul et, bien sûr, ses éternelles querelle avec le chat du voisin. Intelligence fractal convergente me dis-je. En ce moment, un Inconnue me parle, il me parle depuis toujours, mais c'est à peine si je l'écoute ou même tente de vraiment le comprendre sans que ma condition d'humain faible et peu concentré prennent le dessus. Sans que mes habitudes, mes désirs, la médiocrité d'un quotidien morbide s'empare de moi.

De ce chaos quelque chose de clair apparaît. Un énième avertissement, un appelle à ne pas s'égarer du chemin. Je suis un rescapé, un fortuné. Pour combien de temps? J'ai chuté de nombreuses fois et Il m'a relevé alors que d'autres sont resté à terre.

Je me redresse brusquement, le son du moteur à quelque chose d'étrange. D'inconsistant. Je l'entend dans ma tête, mais la camionnette est partie. Plus d'une heure s'est écoulée. Les objet jaune dans la pièce ressortent particulièrement plus que tout le reste, et cela me frappe. Je prête l'oreille. J'entends mon téléphone portable sonner. Je me relève, déséquilibré. Et tente de me rappeler où j'ai bien pu le poser. Sur le boitier EDF. Le vibreur le fait se déplacer, et je n'ai pas le temps de le saisir qu'il tombe dans le SEUL PUTAIN de trou de ce PUTAIN de boitier électrique.

Après cet incident, je redescend doucement. Troublé, naturellement. Bien que satisfait, je réalise comme je suis bloqué. J'étais tellement persuadé d'être maître de ma conscience à force de rêve lucide. Je réapprend à rêver, et à vivre. Je réapprend à être humble et à craindre que mon esprit défaille, de perdre ce que j'ai acquis spirituellement. Un héritage qui tout compte fait constitue quelque chose de bien fragile.

J'en veux encore.

À C., le 1er Décembre 2013

Prise de 2.2g de Cubensis Equador, avec de l'eau, Chez moi, Pas mangé depuis 4 ou 5 heure. Bonne lumière et bonne ambiance. Aucune montée, 2h après ingestions, aucun effet visuel notoire mais un fort état introspectif.

L'heure est grave. Les champignons magique, que j'ai avalé ne me font aucun effet. Leur chair bleutée était pourtant, semblait-t-il, saturée en psilocybine. La déception est immense. Il s'agit de cet instant, à la frontière du nihilisme et de la misanthropie, où tu te dis qu'il n'y a rien pour s’échapper de l'aliénante réalité. J'étais déjà bien allumé au collège. On peut même dire que j'ai connue, en quelque sorte, l'illumination. Je sais ce que c'est que de faire l'expérience de réalités alternées, peut-être que c'est pour ça que ces champignons se refusent à moi. Pas de couleurs, pas de rêves éveillés, pas de révélation que je n'ai déjà eu. Je n'apprendrais rien de nouveau. Il me semble connaître tout du monde et du cœur des hommes. Mon ami qui lui aussi écrit à son acte manqué me confessait tout à l'heure qu'il se sent seul. Une amie me confessait tout à l'heure, en pleure, au téléphone qu'elle se sentait seul. Ils se sentent tous seul. En couple ou célibataire, en famille ou entre ami. La solitude est la chose la mieux partagé du monde. Je me sens seul, immensément seul. Trahi au dernier degré par mes espoirs, par mon corps et par mon esprit.

C., tu es vraiment une fille magnifique. Mais la cigarette te fais vieillir prématurément. Je regrette vraiment de ne pas avoir pu être ton ami, je regrette de ne pas avoir pu te dire quand j'aurais pu que je t'aimais. Mon esprit perpétuellement confus ne fait pas la distinction entre passé, présent et futur. La vie est un rêve comme un autre, et le temps est une dimension spatial. Tout est affaire de perspective. Carl Gustave Jung, père de la psychanalyse, s'extasierait devant cette explosion de spontanéité et de sincérité. C., tu le savais ? On à tendance à être sexuellement attiré par des personnes ressemblant à nos parents. Enfin je dis sexuellement, mais en réalité, je parle du mécanisme primitif à l'origine du sentiment amoureux. Il y a quelque temps, en rangeant des cartons, je tombais sur des photos de ma mère. Tu lui ressemble beaucoup, au même âge. Vous avez le même caractère. La même tendresse apparente et la même frustration latente et insidieuse. La même féminité. À moi qui tombe dans un trou noir, au delà de l'horizon des évènements, sur la ligne de ma vie. Je ne m'attend plus à rien. Si jeune et déjà si fatigué de vivre.

Je ne suis pas capable de dire si, à l'issu de ce tripe improbablement introspectif, je sortirais de cette convergence entropique ou si, au contraire, je serais capable de transcender ma condition. Dans un univers parallèle, une réalité subjective, un rêve sans doute, nous nous aimons. Dans cette réalité ta sœur se moque de moi. Se serait comme dans un manga où les protagonistes se parle pour ne rien dire. Où les personnages se chamaille bêtement, et c'est mignon. Tendre. Innocent. Dans cette réalité, je ne connais pas encore le sexe, et je te désire sans comprendre comment ça fonctionne. J'aimerais te toucher. Comme ton regard m'a toujours fasciné, c'est ton visage que j'embrasse en premier, et tu me rend ce baisé, avec ton si beau sourire. Mais nous n'allons pas plus loin parce que nous n'osons pas. Nous n'oserons jamais. Nous nous aimons d'un amour impossible, parce que cette réalité alternative, comme toute les autres prendra fin. C., la réalité n'est pas absolu. Je regrette que tout le monde ne rêve pas comme moi je rêve pour s'en rendre compte.

Tout le monde ne peut saisir cette notion pourtant concrète de réalité alternée.

Tes yeux, ça c'est quelque chose. La topologie de ton visage va bien au delà de tout ce que j'ai pu voir. Une géométrie parfaite qui semble ne parler qu'a moi. Et pourtant tu es si loin. La vie est trop courte pour perdre son temps. Dans la réalité objective, dans le passé, en cours de biologie, nous somme exceptionnellement assis l'un à côté de l'autre. Comme on passait le peu de temps ensemble à être désagréable l'un envers l'autre, il me semble qu'a ce moment là nous étions mal à l'aise. Il fallait pourtant être courtois, ou se montrer qu'on était capable « de ». Difficile de t'observer sans que tu t'en rende compte. Nous nous parlons, nous échangeons. J'aimerais que ça dure, j'aimerais que ça devienne naturel. Tu sens bon. Dans la réalité objective, dans le passé, en cours de français. Tu es assise, et tu regarde droit devant toi. Tu es entourée de je ne sais trop qui. A un moment, quelqu'un sous entend, pour t’embarrasser que tu es amoureuse de moi. Faux souvenir ? Pourtant, c'est à ce moment que j'ai commencé à te considérer. Si cet instant n'a jamais eu lieu, tout une réalité s'est pourtant déroulé, conséquence de cet événement fictif. C'est peut-être ça le big-bang. Une hallucination. Avant, je n'avais pas suffisamment confiance en moi pour pouvoir espérer être aimé par quelqu'un d'aussi belle et doué. À ce moment ambiguë pourtant, il me semble que tu es amoureuse de moi, dans cette réalité objective. Nous ne nous l'avouerons jamais. Pour Erwin Schrödinger, physicien quantique de son époque, l'univers est double.

Tant que je n'ai pas les moyens de vérifier tes sentiments. Tu es simultanément amoureuse et indifférente. Se soir, dans les ténèbres, je rompt la symétrie. J'avais peint un tableau de toi, et je t'en avais envoyé une copie numérique. Sans réponse. En même temps, c'était pas brillant, mais ça rend mieux en vrai. Saisir tes traits, c'est comme te faire mienne. Qu'aurais tu pu répondre ? Plus de sept ans se sont écoulé, plus que le temps nécessaire pour que toutes les cellules de nos deux corps se soient entièrement régénéré. Autrement dit, tu n'es plus du tout la même personne. Ni moi non plus. Je cours après un fantôme, un souvenir. Merci Facebook de conserver pour moi ton image.

C.

Merci internet d'abolir la notion d'espace temps. J'ai franchi l'event horizon. Je me sens chuter. Je redescend. L'univers, au delà du trou noir s'accélère. Le décalage temporel est tel que toute la lumière de l'univers se concentre en un seul point et me brûle. Compressé, il ne reste que la peur. Broyé, dans un maelström de souvenirs et de matière en fusion, dans un vide glacial, dans la lumière. Dans l'obscurité. Au delà du trou noir, l'univers poursuit son expension. Moi qui suit figé dans un abysse dont la science peine à saisir la nature, je ne peu vivre avec mon temps. Dimanche. Dans un univers qui n'est pas encore présent, tu te réveille d'une cuite. Tu te réveille tout court. Tu ne te réveille pas. Mais tu reçoit ce message confus. Indifférente, ou troublée. Surtout très mal à l'aise devant mes embarrassantes obsessions. Peut-être touchée. Peut-être... Mais il fallait bien que j'en finisse, d'une manière ou d'une autre. Je n'ai jamais cessé d'aimer les personnes que j'ai aimé, et qui ne sont plus dans ma vie. Ça n'a plus aucun sens mais je continue d'étendre cet affection torturé dans le vide. J'espère secrètement un écho à ma mesure. J'espère ne pas être seul ; j'espère un signal. Un écho. Juste un écho. Un écho lointain. Tous les trous noirs sont destinés à s'évaporer. En son sein, je ne puis plus communiquer avec l'univers. C'est mon dernier message, qui s'étire et s'étend, longuement, puis le silence. Enfin.

Avant de se lancer dans ce tutoriel il convient d'être à l’aise avec la notion de shaders et les opérations basiques d'OpenGL.

Si vous ne connaissez pas OpenGL, il est sans doute trop tôt pour se lancer dans les compute shaders. Il existe une pléthore de très bons tutoriels sur l'OpenGl moderne qui vous permettrons de vous familiariser avec l'API. J'en retiens notamment trois

Pour comprendre cette introduction aux compute shaders vous n'avez pas besoin d'aller trop loin dans les tutoriels OpenGL ci-dessus. Le minimum étant de savoir rendre à l'écran un simple triangle, auquel on applique une texture, en OpenGL moderne, avec les vertex/fragment shader minimaux qu'il convient d'avoir.

Nous allons voir ici une fonctionnalité introduite depuis la version 4.3 de notre API favorite qui va nous permettre d'étendre nos possibilités.

Rappelons qu’OpenGL définit une pipeline de rendu bien précise dont certaines étapes (stages) sont programmables en GLSL (OpenGL Shading Language) ; Ces étapes programmables sont appelées shaders. On connaissait déjà les vertex shaders qui permettent de manipuler les vertices et les fragments shaders qui permettent de réaliser et d’appliquer des effets graphiques à notre image de sortie.

Avec les compute shaders il est maintenant possible de manipuler arbitrairement des données de la façon dont on le souhaite. Ce gain en flexibilité représente un grand intérêt puisque l’on peut tirer parti de la puissance de la GPU pour, par exemple, créer à la volée des textures de synthèses, générer procéduralement des objets, ou effectuer des calculs parallèles qui ne sont pas forcément en rapport avec de l’imagerie 3D sans dépendre des APIs comme CUDA ou OpenCL dont le propos n'est de faire QUE du calcul parallèle. En effet, embrayer dans un même programme d'OpenGL vers l'une des APIs mentionnées à l'instant (ou vis versa) est très chronophage, les compute shaders constituent une solution commode à ce problème.

Aperçu

Lorsque l’on manipule les vertex shaders ou les fragment shaders on se rend bien compte que chacun des appels de ces shaders sont indépendant est ignorent les autres. Ces shaders, s’inscrivant dans une pipeline de rendu bien définit, leurs entrées/sorties sont elles aussi bien définies, tout comme leur fréquence d’exécution (par exemple un vertex shader donné s’exécutera pour chaque vertex qu'on lui donne).

Les computes shaders, eux, sont indépendant de cette pipeline de rendu et lorsque ceux là sont invoqués les entrées/sorties sont définies par le programmeur et non plus par la pipeline. C’est également le programmeur qui détermine la charge de travail à effectuer en définissant des groupes de travail (workgroups).

Pour illustrer le fonctionnement des computes shaders on se donne le programme exemple suivant :

damnSimpleComputeShader

Ce code source sert de support à ce tutoriel mais relire le code d'un autre peut être laborieux et/ou peut-être n'utilisez vous pas les mêmes librairies que moi. En réalité, il est très simple d'intégrer le compute shader rudimentaire que je présente ici dans un petit programme OpenGL tout aussi rudimentaire. N'ayez donc pas peur d'écrire votre propre programme et d'intégrer au fur et à mesure les éléments manquants qui nous intéressent.

Pour se représenter un peu le tableau de loin, on se propose à l’aide d’un compute shader de générer le contenu d’une texture vide pour réaliser un damier, et l’afficher. En fait, c’est assez simple ; dans notre exemple, tout se passe comme si on allait créer une texture vierge et qu’on l’affichait normalement sur un quad aux dimensions du viewport, à la différence près qu’avant d’entrer dans la boucle de rendu, on génère le damier en appelant notre compute shader et en s’assurant que la boucle de rendu n’interfère pas avec la génération des données. En effet, sans explicitement bloquer le fil d’exécution OpenGL va écrire dans la texture et simultanément essayer de l’afficher, parallélisme oblige, ce qui est évidemment très peu commode.

Il faut insister sur un point important. L'architecture de la carte graphique est conçu pour exécuter parallèlement des instructions. Ce qui impose nécessairement de prendre en considération cet état de fait lors de l'implémentation d'un algorithme, en particulier quand celui là est à l'origine conçu pour des architectures plus traditionnelles où le multi-threading est en option.

Création de la texture

Maintenant que l’on a un aperçu de la façon dont se déroule notre programme, allons un peu plus dans le détail. La première chose à faire est de créer la texture qui nous servira de support.

glGenTextures(1, &quadTextureID);
  glActiveTexture(GL_TEXTURE0);
  glBindTexture(GL_TEXTURE_2D, quadTextureID);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glGenerateMipmap(GL_TEXTURE_2D);  
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, 640, 480, 0, GL_RGBA, GL_FLOAT, NULL);
    glBindImageTexture (0, quadTextureID, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA32F);
    glBindImageTexture (0, 0, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA32F);
  glBindTexture(GL_TEXTURE_2D, 0);

Rien de bien nouveau à la différence qu’ici on bind la texture courante à une image unit. Ce qui signifie dans la pratique que l’on va pouvoir travailler dans un tableau avec des indices entiers, et non plus dans un sampler2D où les coordonnées sont normalisées.

La plupart du temps, dans les tutoriels que l'on peut lire en ligne (en français ou en anglais), il n'est jamais question d'image unit et de texture unit. Pourtant c'est important dans la mesure où même s'il semble que l'on peut travailler sans, dans des cas simples, il est beaucoup plus rigoureux (mais plus fastidieux) d'expliciter le comportement de votre application. J'ouvre donc une parenthèse pour éclaircir un peu ce dont il s'agit: glBindTexture associe l'objet texture à la cible d'une texture unit active (Cette cible c'est GL_TEXTURE_2D en général). Ce qui signifie qu'il faut… L'activer. Sauf dans le cas où l'on utilise qu'une seule texture où, en effet, la texture unit par défaut est fixée à GL_TEXTURE0. Cette petite opération est donc invisible la plupart du temps.

La texture unit permet de récupérer la texture sous la forme d'un sampler2D dans le shader qui souhaite l'utiliser, et par là, accéder à l'image de la texture avec le mode d'interpolation définit avec glTexParameteri. Dans ce cas là les coordonnées utilisées pour accéder à l'image de la texture sont normalisées, d'où l'interpolation.

L'image unit associée avec glBindImageTexture permet de récupérer dans le shader l'image à proprement parler, sans coordonnées normalisées. On se déplace donc dans un tableau traditionnel avec des coordonnées entières.

Remarquons également qu'en général on passe à glTexImage2D un pointeur vers un tableau de données pour initialiser la texture avec celle-ci. Comme on alloue la mémoire côté GPU et qu'on générera ces données toujours sur la GPU ce pointeur peut-être NULL.

Comme on le sait déjà, on unbind généralement ses buffers par précaution après avoir fini de travailler avec. C’est la même chose pour glBindImageTexture. Plus loin dans le code de l’exemple on peut voir qu’à chaque appels de glBindImageTexture un unbind de circonstance est placé avant glBindTexture(GL_TEXTURE_2D, 0).

Enfin, l’image unit associée à la texture est en WRITE_ONLY. En effet pour ce que l’on se propose de faire on a pas besoin de lire l’image depuis le compute shader, mais seulement d’y écrire.

Création du compute shader

Un peu plus bas dans le programme, on arrive au bloc d’instructions où l’on crée notre compute shader.

 1 PrintWorkGroupsCapabilities();
 2 
 3   GLuint computeShaderID;
 4   GLuint csProgramID;
 5   char * computeShader = 0;
 6 
 7   GLint Result = GL_FALSE;
 8   int InfoLogLength = 1024;
 9   char ProgramErrorMessage[1024] = {0};
10 
11   computeShaderID = glCreateShader(GL_COMPUTE_SHADER);
12 
13   loadShader(&computeShader, "compute.shader");
14   compileShader(computeShaderID, computeShader);
15 
16   csProgramID = glCreateProgram();
17 
18   glAttachShader(csProgramID, computeShaderID);
19   glLinkProgram(csProgramID);
20   glDeleteShader(computeShaderID);

La première chose à faire, bien que ce ne soit pas obligatoire ici, c’est de connaître la capacité des groupes de travail offerte par le GPU. Ça ne mange pas de pain et ça va être l’occasion de détailler comment les groupes de travail sont organisés. Les groupes de travail contiennent un certain nombre d’invocations du compute shader, définit à l’intérieur du shader lui même comme nous le verrons plus bas. Ce nombre est appelé taille locale du groupe de travail. On définit également au moment de l’exécution du compute shader le nombre de groupes de travail à effectuer.

On peut schématiser ce qui vient d’être dit ainsi :

Groupes de travail

Où chaque petit carré représente une invocation de shader contenu dans un groupe de travail.

Ceci étant dit on devine que la carte graphique a une capacité limitée de groupes de travail et de nombre d’invocations locales. OpenGL permet de déterminer la capacité de la carte graphique en la matière. En effet dans la fonction printWorkGroupsCapabilities on récupère ces informations et on les affiches dans la sortie standard :

 1 void printWorkGroupsCapabilities() {
 2   int workgroup_count[3];
 3   int workgroup_size[3];
 4   int workgroup_invocations;
 5 
 6   glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 0, &workgroup_count[0]);
 7   glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 1, &workgroup_count[1]);
 8   glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 2, &workgroup_count[2]);
 9 
10   printf ("Taille maximale des workgroups globaux:\n\tx:%u\n\ty:%u\n\tz:%u\n",
11   workgroup_size[0], workgroup_size[1], workgroup_size[2]);
12 
13   glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_SIZE, 0, &workgroup_size[0]);
14   glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_SIZE, 1, &workgroup_size[1]);
15   glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_SIZE, 2, &workgroup_size[2]);
16 
17   printf ("Taille maximale des workgroups locaux:\n\tx:%u\n\ty:%u\n\tz:%u\n",
18   workgroup_size[0], workgroup_size[1], workgroup_size[2]);
19 
20   glGetIntegerv (GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS, &workgroup_invocations);
21   printf ("Nombre maximum d'invocation de workgroups locaux:\n\t%u\n", workgroup_invocations);
22 }

Ce qu’on peut remarquer, et c’est très important, c’est que la taille locale d’un groupe de travail ainsi que le nombre de groupes de travail est définit en trois dimensions. Autrement dit les compute shaders travaillent dans l’espace. C’est très pratique, évidement, pour faire du traitement d’image, ou travailler en volume. Nous verrons plus bas comment organiser notre shader en fonction de ces informations.

Précisons également que ces groupes de travail sont lancés en parallèles selon la capacité et la disponibilité de la carte graphique. Il n’est pas possible de connaître l’ordre d’exécution des invocations et dans certains cas il est absolument vital de composer avec cet état de fait sous peine d’obtenir des résultats pour le moins inattendus ou pire, de planter le système. Heureusement, la question de la synchronisation n’est pas le sujet ici, et le programme que l’on étudie ne nécessite pas que l’on soit attentif à l’ordre d’exécution des groupes de travail et d’invocations des compute shaders. Notre exemple n’étant pas non plus gourmand en ressource il ne nécessite pas que l’on implémente une vérification particulière de la capacité des groupes de travail. La fonction printWorkGroupsCapabilities est donc ici purement informative.

Comme on le voit dans le listing plus haut, les instructions qui servent à créer le compute shader sont en fait tout à fait classique.

Rappelons tout de même que l’on crée un programme spécifiquement POUR le compute shader. Un programme qui n’est donc PAS celui où sera lié le vertex shader et le fragment shader qui servent au rendu. On le voit bien, plus bas dans le code ; la création du programme de rendu est indépendante et ne fait intervenir à aucun moment le compute shader.

Exécution du shader

Une fois que tout est prêt on peut enfin lancer notre compute shader.

1 glUseProgram(csProgramID);
2     glBindTexture(GL_TEXTURE_2D, quadTextureID);
3       glBindImageTexture (0, quadTextureID, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA32F);
4         glDispatchCompute(40,30,1);
5         glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
6       glBindImageTexture (0, 0, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA32F);
7     glBindTexture(GL_TEXTURE_2D, 0);
8   glUseProgram(0);

OpenGL étant une machine à état on bind notre texture et l’image unit qui lui est associé exactement comme on l’a fait au moment de la création de la texture.

C’est finalement avec glDispatchCompute() que l’on lance l’exécution du compute shader et plus exactement que l’on lance l’exécution d’un certains nombre de groupes de travail. En effet glDispatchCompute prend trois paramètres x, y et z spécifiant chacun les dimensions de l’espace des workgroups à lancer. En particulier, dans notre example, on travaille sur une image, donc dans un espace en deux dimensions, le troisième paramètre est naturellement fixé à 1.

Dans le programme on voit que j’ai choisi pour l’espace des groupes de travail une largeur de 40 et une hauteur de 30 (j’expliquerais le choix de ces valeurs plus bas), ce qui signifie qu’OpenGL va exécuter 40 x 30 = 1200 groupes de travail. Cela peut paraître beaucoup mais c’est en fait relativement peu.

Après l’appel de glDispatchCompute, OpenGL se met au travail et le fil d’exécution continue côté CPU. Sauf qu’en fait non ; étant vigilant, on s’assure que le fil d’exécution de notre programme s’interrompe le temps qu’OpenGL termine l’exécution de tous les groupes de travail à l’aide de la commande glMemoryBarrier qui permet comme on le devine de mettre en pause l’exécution du programme tant que la carte graphique n’a pas terminé ses transactions mémoires. En particulier, cela permet de bloquer l’exécution en fonction du type de mémoire que l’on veut. Ici, comme nous travaillons sur une image on utilise la valeur prédéfinie GL_SHADER_IMAGE_ACCESS_BARRIER_BIT.

On pourrait également, si cela était nécessaire, combiner plusieurs valeurs prédéfinies par OpenGL pour bloquer l’exécution du programme en fonction de plusieurs zones mémoire. Enfin si GL_ALL_BARRIER_BITS est utilisé alors le programme se verrouille en fonction de toutes les zones mémoire précédemment sollicitées.

Ceci étant fait, les workgroups étant tous exécutés, on peut reprendre le fil d’exécution du programme et unbind nos états OpenGL.

Après quoi, on rentre tout simplement dans la boucle de rendu qui ne fait absolument rien de nouveau.

Détail du compute shader

C’est donc le moment de voir à quoi ressemble le compute shader que l’on a exécuté.

 1 #version 430
 2 
 3 layout (local_size_x = 16, local_size_y = 16) in;
 4 
 5 layout (rgba32f, binding = 0) uniform image2D img_output;
 6 
 7 void main() {
 8   // Aucun tableau de donnée n'étant passé au moment de la création de la texture,
 9   // c'est le compute shader qui va dessiner à l'intérieur de l'image associé
10   // à la texture.
11 
12   // gl_LocalInvocationID.xy * gl_WorkGroupID.xy == gl_GlobalInvocationID
13   ivec2 coords = ivec2(gl_GlobalInvocationID);
14 
15   // Pour mettre en evidence. Les groupes de travail locaux on dessine un damier.
16   vec4 pixel;
17   if ( ((gl_WorkGroupID.x & 1u) != 1u) != ((gl_WorkGroupID.y & 1u) == 1u)) {
18     pixel = vec4(1.0,.5,.0,1.0);
19   }
20   else {
21     pixel = vec4(.0,.5,1.0,1.0);
22   }
23 
24   imageStore(img_output, coords, pixel);
25 }

Un compute shader se présente en fait de façon très similaire aux shaders classiques. La nouveauté c’est que l’on définit à l’intérieur du shader lui même la taille des groupes de travail. Comme on le voit, la taille locale d’un groupe de travail est de 16 par 16. Rappelons que les dimensions de l’espace de travail des workgroups étaient de 40 par 30. On aura donc 16² x 1200 invocations du compute shader. On peut également remarquer que 16 * 40 = 640 et 16 * 30 = 480. Et ça tombe bien parce que c’est justement les dimensions de notre image et de notre viewport.

Quelques explications s’imposent. Nous pourrions effectivement générer un damier dans une image de façon itérative dans un seul thread. Mais ça ne serait pas vraiment optimal si on tient compte du fait que l’on peut faire la même chose en tirant parti de l’aptitude du GPU à faire du calcul parallèle.

Comme on l'a dit plus haut, la raison pour laquelle le nombre de groupes de travail et la taille de ceux là sont formulés en terme d’espace est que les compute shaders travaillent dans l'espace. Pour se repérer dans l’espace de la structure de donnée dans laquelle on travaille on utilise des variables introduites avec les compute shaders qui nous renseignent sur l’identifiant de l’invocation courante du compute shader. Cet identifiant étant tridimensionnel, on sait donc où l’on se trouve au moment d’une invocation donnée. Comme les invocations sont parallèles (mais également dans un ordre arbitraire décidé par OpenGL) on peut donc ici travailler simultanément à différents endroits de notre image, ce qui fait substantiellement gagner du temps.

Du coup, la seule façon de savoir où l’on se trouve est d’interroger ces variables spéciales qui identifient l’invocation courante.

Ces variables sont des vec3. À titre de rappel, OpenGL travail le plus souvent avec des vecteurs de deux, trois ou quatre composantes; respectivement vec2, vec3 et vec4. Les vecteurs peuvent contenir des entiers signés ou non signés ou, comme c'est le cas la plupart du temps, des nombres à virgules flottantes. Pour en savoir plus rendez vous sur le wiki d'OpenGL.

  • gl_LocalInvocationID est un vec3 qui nous dit où l’on se trouve relativement au groupe de travail courant. Les coordonnées xyz ainsi retournées ne peuvent donc pas être en dehors du volume décrit par la taille du groupe locale, à savoir ici : 16 par 16 par 1.
  • gl_WorkGroupID est un vec3 qui nous renseigne sur le groupe de travail courant, à ne pas confondre avec l’invocation courante donc. Les coordonnées xyz ainsi retournées ne peuvent pas être en dehors du volume décrit par la taille de l’espace des groupes de travail, à savoir ici : 40 par 30 par 1.
  • gl_GlobalInvocationID est un vec3 qui est en fait le produit des deux variables vues précédemment. Il repère l’invocation courante non plus relativement au groupe de travail courant mais dans l’espace global des invocations. Autrement dit, dans notre programme, cette variable nous dit où l’on se trouve dans l’image.

Finalement pour dessiner notre damier, on détermine la couleur de sortie en fonction de gl_WorkGroupID ; si sa composante x est paire et que sa composante y ne l’est pas alors la couleur de sortie est orange, sinon, elle est bleue. Toutes les invocations à l’intérieurs d’un groupe de travail seront donc soit oranges, soit bleues.

Pour terminer, on utilise la fonction GLSL imageStore pour écrire dans notre image. Remarquez qu’en fait on n’enregistre qu’un seul et unique pixel pour une invocation donnée du compute shader.

Résultat

En compilant et en exécutant le programme exemple (en vous assurant préalablement de satisfaire les dépendances) on obtient l’image ci-dessous.

Damier de 40x30 secteurs de 16 pixels²

Comme on pouvait s’y attendre notre damier comporte des secteurs de 16 pixels² au nombre de 40 en largeur et 30 en hauteur. On illustre ainsi sans ambiguïté l’utilisation d’un compute shader et des groupes de travail.

Pour terminer ce cours introductif vous pouvez jouer avec le compute shader présenté ici et modifier la taille du groupe de travail locale et adapter l'espace des groupes en conséquence pour changer la taille des cases du damier ou pour rendre à l'écran des formes géométriques et les positionner comme bon vous semble.

Bibliographie

Remerciement

Cet article à été rédigé dans le cadre d’un stage à l’INRIA, un grand merci à Sylvain Lefebvre et son équipe pour leur aide et leur disponibilité.

Un très grand merci également à Ge0 et Shenzyn pour leurs conseils et leurs relectures.