longpeaksIl y a presque un an maintenant, le groupe Khronos auquel venait d’être transféré le contrôle d’OpenGL dévoilait l’avenir vers lequel s’orientait notre API préférée (enfin la mienne en tout cas ;-): Noms de code “Longs Peak” et “Mt. Evans”, deux révisions prévues pour 2007 et qui constituent beaucoup plus qu’une simple évolution de l’API. C’est finalement il y a quelques semaines, au SIGGRAPH 2007, qu’a été officiellement présenté et baptisé “Longs Peak”:OpenGL 3.0.

Je vais, dans cet article, essayer de présenter rapidement les changements que vont apporter ces nouvelles versions. Enfilez donc vos baudriers et chaussons d’escalade, ca grimpe 😉

Présentation

L’idée principale derrière OpenGL 3.0 (“Long Peak”) est un peu celle qui avait déjà été avancée lors de la spécification d’OpenGL 2.0: Une refonte totale de l’API afin de l’adapter au mieux au mode de fonctionnement des GPU “modernes”. Le but est ainsi de mettre à jour l’API afin de refléter les changements matériels (Les G80 n’ont plus tout à fait le même fonctionnement que les SGI de 1992 !) mais également de la rapprocher du matériel (“the bare metal” comme on dit chez NVidia) en éliminant un certain nombre de couches qui n’ont plus de raison d’être à la charge d’une API graphique de haut niveau (les fonctions fixes et le mode immédiat en particulier).  L’idée est également d’améliorer les performances globales des implémentations en éliminant les overheads coté driver inhérents aux défauts de l’API. Pour finir OpenGL 3.0 doit permettre de simplifier le développement d’applications pour les utilisateurs (en éliminant les redondances diverses et en se focalisant sur une utilisation efficace) mais également de simplifier le développement de drivers pour les fabriquant (améliorant ainsi la qualité de ces implémentations).
OpenGL 3.0 (“Long Peak”) est la première étape de cette refonte, cette version proposera l’équivalent des fonctionnalités d’OpenGL 2.1 dans la nouvelle API. L’étape suivante porte le nom de code “Mt Evans” et introduira toutes les nouvelles fonctionnalités au-dela d’OpenGL 2.1 (typiquement les fonctionnalités pour le G80 actuellement disponibles sous forme d’extensions).

Ce qui va changer

Le premier changement qu’apportera OpenGL 3.0, c’est l’élimination de tout un ensemble de fonctionnalités conservées pour la compatibilité et devenues obsolètes. Vont donc disparaitre le mode immédiat (glBegin/End()) ainsi que toutes les fonctions dites fixes des étapes devenues programmables (T&L, Texture Application, etc.), il s’agit d’ailleurs également de l’approche adoptée par DirectX 10. Les mode selection et feedback (Un vieux mode permettant de récupérer un ID des objets rendus, à ne pas confondre avec le Transform feedback), les evaluators ainsi que l’accumulation buffer tombés en désuétude vont également disparaitre.
Ainsi, tout programme OpenGL devra implémenter un vertex ainsi qu’un fragment shader, il sera impossible d’émettre directement des primitives et celles-ci devront être placées dans des vertex-arrays pour être passés au vertex shader. L’ensemble des matrices fixes (MODELVIEW, PROJECTION, TEXTURE) devraient également disparaitre et avec elles le mécanisme glPuhs/PopMatrix. Il sera donc à la charge de l’application ou d’une surcouche de plus haut niveau de réimplémenter l’ensemble de ces mécanismes. OpenGL devient ainsi une API encore de plus bas niveau, conçue pour la performance.

Le deuxième changement majeur est la refonte totale du mode de fonctionnement de l’API en l’unifiant sous un tout nouveau modèle objet que je vais tenter de décrire dans la section suivante.

Notez tout de même qu’a l’heure ou j’écris ces ligne, la spécification d’OpenGL 3.0 n’est pas encore disponible, les informations fournies sont donc provisoires et certaines pourraient devenir obsolètes lors de la sortie de la specification finale (fin septembre en théorie), bien que la plus part des aspects de l’API soient à l’heure actuelle finalisés.

Un nouveau modèle objet

Le modèle d’objets actuellement en place dans OpenGL 2.1 est apparu progressivement au fil des ajouts à l’API et de manière inconsistante. Il y a tout d’abord eu les Display List dans OpenGL 1.0, puis sont apparues les objets de textures dans OpenGL 1.1. Ont ensuite suivis les VBO (Vertex Buffer Object) avec une API totalement nouvelle puis les PBO (Pixel Buffer Object) utilisant la même interface. Enfin les FBO (Frame Buffer Object) utilisant eux encore une interface différente ont été introduits.
OpenGL 3.0 unifie la gestion de ces objet et étend le modèle objet à l’ensemble de l’API. Dans OpenGL 3.0 tout devient ainsi objet (aussi bien les données que les états ou les conteneurs).

Philosophie

Le nouveau modèle objet sera la pierre angulaire d’OpenGL 3.0. Il a deux buts principaux: améliorer globalement les performances d’exécution du driver ainsi que simplifier et rendre plus robuste la gestion des objets coté client. Ce modèle permettra d’améliorer globalement les performances d’exécution du driver en encapsulant l’ensemble des attributs d’un même objet et en les passant de manière atomique à l’API. Il permet également d’assurer la complétude des objets transmis. Le but général est finalement de permettre d’alléger la charge de traitement du drivers tout en simplifiant et unifiant la gestion des objets coté utilisateur. Ils devraient également permettre le partage aisé d’objets entre contextes.

Description de l’API

graphics-context-lg
Le contexte graphique tel que nous le connaissons dans OpenGL 2.1, contenant l’ensemble des états du pipeline et sur lequel agit les opérations graphiques subsiste dans OpenGL 3.0. La différence est que dans OpenGL 3.0 ces états sont encapsulés à l’intérieur d’objets d’états gérés coté serveur et associés, soit directement soit au travers d’objets conteneurs (cf. section suivante), au contexte graphique. Toute une hiérarchie d’objets se met ainsi en place et seul un petit nombre d’objets sont associés directement au contexte graphique (via une opération de binding).

Le changement de l’un de ces binding pour l’associer à un autre objet entraine la mise à jour de l’ensemble des états matériels (affectés par le changement) en fonction des propriétés de l’objet. Ce changement d’états est beaucoup plus efficace que dans OpenGL 2.1 du fait qu’un large ensemble d’états sont mis à jour simultanément. Ces états auront de plus pu être pré-validés par groupes (par le serveur) lors de la création de l’objet (et de ceux le constituant) ce qui améliore encore l’efficacité.

Ce mode de fonctionnement élimine totalement la nécessité d’opérations précédemment couteuses telles que la sauvegarde/restauration d’états (glPush/PopAttrib()). Dans une grosse application constituées de multiples parties indépendantes effectuant diverses opérations graphiques, chacune de ces parties peut ainsi conserver et gérer indépendamment les objets qui lui sont nécessaires et les binder efficacement au contexte graphique lors de leurs opérations de rendu.

Le contexte graphique d’OpenGL 3.0 tel qu’il est décrit au moment ou j’écris ces lignes (il ne s’agit peut etre pas de la version totalement définitive) contient les bindings pour un Vertex Array Object qui est un conteneur définissant les données géométriques d’entrée, un Program Object encapsulant Vertex et Fragment Shader ainsi qu’un Framebuffer Object paramétrant l’ensemble des buffers de sortie. Il possède également des bindings pour un Sample Operations Object qui est un objet d’états gérant les framebuffer operations du pipeline standard OpenGL (blending, depth/stencil test, etc.) ainsi que pour un Rasterization Object regroupant l’ensemble des autres liés à l’étape de rasterisation (polygon mode, point size, line width, smoothing, polygon offset, etc.). Cet objet pourra a terme être remplacé par un shader object lorsque le matériel le permettra. L’ensemble de ces objets est décrit plus en détail dans la paragraphe suivant.

Objets de manipulation des données

  • Buffer Objects:

Ils remplacent et unifient l’ensemble des buffers de données manipulés dans OpenGL 2.1 (VBO/PBO, mais aussi données des Vertex Arrays, Texture Objects et Render Buffers). Ce sont des zones de données non formatées qui possèdent des attributs de taille et d’utilisation (à la VBO/PBO) et peuvent contenir indifféremment des données de sommets, de pixels ou même des paramètres uniformes pour un shader (sur le modèle de l’extension NV_parameter_buffer_object). Cette unification des buffers de données devrait en théorie permettre la réinterprétation de tous les types de données (sur le matériel le supportant) et améliorer ainsi l’efficacité des opérations type Render To vertex Array en éliminant les copies de données sur GPU précédemment nécessaires.

  • Image Objects:

Ce type d’objet est une spécialisation des Buffer Objects (donc manipulé avec la même API) contenant en plus un Format Object immuable décrivant l’interprétation des données. Ils correspondent à la partie données des textures et sont directement attachés aux programmes de shaders (Program Environment Object). Ils remplacent également les Render Buffers attachés aux Frame Buffers.

  • Format Objects:

Ceux-ci définissent entièrement les formats de données qui sont utilisés par d’autres objets. Il s’agit d’une généralisation du format interne spécifié lors de la création d’une texture dans OpenGL 2.1. En plus du format de données brut, les objets de format possèdent entre autre les attributs suivant: L’usage prévu pour les données (Pixels, texture avec dimensionalité 1D/2D/3D/cube
map/array, vertex, buffer d’uniforms, etc.), la taille minimum et maximum allouée, la profondeur de la pyramide de mip-map, taille de l’array

  • Texture Filter Objects:

Ils encapsulent les états contrôlés par glTexParameter dans OpenGL 2.1 et définissant le mode de filtrage des texture, le wrapping, les propriétés de mip-mapping, etc. Ils permettent de découpler totalement les textures elles mêmes (image buffer) de leur mode de filtrage, un texture filter pouvant être utilisé avec différents image buffers. Ils sont associés directement à un programme de shader (via un Program Environment Object) dans lequel ils représentent la notion de sampler. Le nouveau modèle de shader découple en effet données de texture et sampler définissant leur mode d’accès (cf. section sur les Shader Programs).

Entrées et sorties du pipeline

  • Vertex Array Objects:

    Ce sont des conteneurs représentant la géométrie et encapsulant un ensemble complet de points d’attaches pour des Buffer Objects (contenant les listes d’attributs de sommets) associés à leur interprétation (type, stride, etc.). Ils sont stockés coté serveur (à la différence des Vertex Array d’OpenGL 2.1) et permettent ainsi le changement efficace de toutes les listes d’attributs en un seul appel de binding sur un autre VAO pré-validé coté serveur. Avec OpenGL 3.0, l’ensemble des attributs sont devenus génériques, ils sont donc uniquement désignés par un numéro (ce point n’est pas encore clair, il est possible que la VAO encapsule directement le nom d’attribut utilisé dans le vertex shader), toute référence aux attributs fixes (position, normal, couleur, etc.) ayant disparu.

  • Framebuffer Objects:

C’est un type de conteneurs combinant un ou plusieurs Image Objects pour former une cible de rendu complete (avec color cuffers, depth buffer, stencil buffer). Ils encapsulent l’ensemble des fonctionnalités des FBO d’OpenGL 2.1.

Les Shaders

  • Modèle global:

 

progenvobject

 

  • Program Environment Objects: C’est en quelque sorte l’interface externe d’un Program Object. Ils encapsulent l’ensemble des point d’attache (paramètres uniformes, images, filtres de texture, …) d’un Program Object ainsi qu’une référence vers ce programme. Cette interface permet ainsi de changer en un seul appel l’ensemble des états associés au programme.
  • Program Objects: Ils correspondent aux Program Objects d’OpenGL 2.1 (shaders GLSL) et possèdent exactement le même conportement.
  • Shader Objects: Ils correspondent aux Shaders Objects d’OpenGL 2.1. Ils sont généralement manipulés compilés et peuvent représenter une partie ou l’intégralité d’une étape programmable (vertex ou fragment shader).

 

  • Modifications du GLSL:

Le langage GLSL est également légèrement modifié avec OpenGL 3.0 afin de refléter les changements dans l’API. La modification majeure concerne la manipulation des textures avec l’élimination de la notion de sampler en faveur d’un découplage entre données images (spécifiées via Image Objects) et modes d’accès à ces données (spécifiés via Texture Filter Object).

 

gl3_glsl

 

Catégories des objets

Les objets d’OpenGL 3.0 se répartissent en différentes catégories en fonction de leur rôle et de leur comportement. Ces catégories seraient implémentées comme des classes abstraites dans un langage
objet dont les implémentations hériteraient du comportement. Ici, il s’agit donc plutôt d’un outils conceptuel pour appréhender l’API.

  • Les objets d’attributs ou “Templates”
    • Les
      templates permettent de stocker et de regrouper l’ensemble des attributs immuables nécessaire à la création d’un objet. Ils sont gérés coté client et transmis en un unique appel au serveur pour la création d’un objet. Chaque type d’objet (buffer, image, programme, etc.) possède son propre type de template constitué des attributs nécessaire à sa création.
    • Lorsque l’utilisateur souhaite créer un objet, il créé tout d’abord un template correspondant à cet objet, rempli ses attributs (contenant déjà des valeurs par défaut spécifiées
      par le serveur en fonction du type d’objet), puis le transmet au serveur via un fonction de création d’objet, récupérant en échange un identifiant pour le nouvel objet.
    • Cette notion de template permet une grande généricité dans la création d’objets et permettra une extension aisée de l’API dans les révisions futures ou via des extensions propriétaires ou ARB.
  • Les objets d’états
    • Ces
      objets contiennent des ensembles d’attributs proches contrôlant une partie du pipeline graphique. Ils sont totalement immuables après leur création ce qui permet au driver d’optimiser leur utilisation. Ils peuvent également être partagés entre différents contextes. Ce type d’objet est celui des format objects, shader objects et texture filter objects décrits plus loin.
  • Les objets de données
    • Ils permettent de manipuler les données en elles mêmes, stockées dans un format immuable défini à la création de l’objet. Ces objets peuvent être partagés entre différents contextes. Il s’agit entre autre des Buffer Objects, Image Object et Sync Objects décrits plus loin.
  • Les object Conteneurs
    • Ces
      objets sont le point d’attache des objets de données (ou d’autres conteneurs) auxquels il permet d’associer les objets d’états décrivant leur interprétation et utilisation. Ils correspondent aux frame buffer objects, program objects et vertex array objects. Au contraire des deux types précédents, ces objets ne peuvent pas être partagés entre contextes.

Propriétés du modèle

  • Encapsulation des attributs
    • L’encapsulation
      des “attributs” assure la complétude des objets. Leur spécification est atomique lors de la création de l’objet et ceux-ci sont immuables durant toute la vie de ce dernier. Les données contenues dans l’objet sont par contre modifiables. Par exemple pour une texture, la taille est définie de manière immuable lors de la création de l’objet, ses données sont par contre modifiables à tout moment (au contraire du mécanisme d’OpenGL 2.0 permettant la modification de la taille comme des données).
    • Ceci permettra de réduire l’overhead qui existe actuellement coté driver en réduisant le nombre de validations nécessaires sur les objets. La possibilité actuelle de définir des objets incomplets (eg. une texture pour laquelle on définit chaque niveau de mipmap séparément) offre une certaine flexibilité mais rend très difficile l’allocation efficace de mémoire pour les objets par le driver. Elle peut également être aisément source d’erreurs difficilement décelables pour le programmeur qui n’obtiendrait pas le résultat escompté après l’oublie de la définition d’un élément (rendu erroné ou même non effectué). Le nouveau modèle permettra ainsi de réduire ce genre de problèmes.
  • Binding uniquement pour le rendu
    • Le mécanisme de binding d’objets (textures, buffer objects, etc.) d’OpenGL 2.0 est utilisé à la fois pour la modification et pour l’utilisation des objets pour le rendu ce
      qui peut entrainer des effets de bords dangereux. Le binding d’objets dans OpenGL 3.0 est maintenant réservé aux opérations de rendu.
  • Partage d’objets entre contextes
    • Le
      partage d’objets entre différents contextes OpenGL se fait dans OpenGL 2.0 de manière globale pour tous les attributs. Le résultats de la modification ou de la destruction d’un objet par un contexte lorsqu’un second l’utilise n’est à l’heure actuelle pas correctement définit par la spec ce qui entraine des résultats différents en fonction des implémentations.
  • Handles d’objets de la taille des pointeurs du système
    • Ceci laissera la possibilité aux implémentation d’optimiser la gestion des objets en fournissant directement comme handles les pointeurs sur les objets stockés en interne par le driver, éliminant ainsi une indirection à chaque manipulation d’objets.

Nouveaux contextes OpenGL et intéropérabilité

L’introduction d’OpenGL 3.0 se fera via un nouveau type de contexte OpenGL 3.0. Il sera ainsi possible de demander au driver soit la création d’un contexte OpenGL 2.x soit celle d’un contexte OpenGL 3.0. Ceci permettra ainsi de faciliter la migration progressive des applications actuelles utilisant OpenGL 2.x. A terme, des extensions devraient également permettre le partage d’objets entre contextes 2.x et 3.x. Un contexte de debugging devrait également etre fournit et permettre un debuggage plus aisé des applications au détriment de la vitesse d’execution. Il ajoutera des fonctionnalités de logging, d’avantage de tests d’erreurs et de validations et sera destiné à etre utilisé uniquement lors de la phase de développement d’une application.

En plus du contexte, un problème de namespace (utilisation des préfixes gl et GL_) se pose avec OpenGL 3.0. Il est en effet apparu un trop grand nombre de conflits entre les deux API pour conserver ces préfixes et un nouveau préfixe devrait donc faire son apparition pour OpenGL 3.0. Dans les exemples fournis le préfixe utilisé est lp (Long Peak) mais il est uniquement provisoire, le préfixe final n’est pas encore connu au moment ou j’écris ces lignes.

Le modèle objet dans l’API

Malgré un modèle totalement objet, l’API reste destinée à être utilisée avec des langages de bas niveau et implémentée en C, elle reste de ce fait totalement procédurale. Ce modèle permettra par contre un layering efficace sous des implémentations objet de l’API en C++, Java ou C#.
Pour vous donner une idée d’a quoi ressemblera la programmation avec OpenGL 3.0 voici quelques exemples récupérés principalement de la newsletter OpenGL (auteur: Jon Leech).

  • Un exemple d’utilisation d’un template pour la création d’un Image Object :

 

// Create an image template
GLtemplate template = glCreateTemplate(GL_IMAGE_OBJECT);
assert(template != GL_NULL_OBJECT);

// Define image attributes for a 256x256 2D texture image
// with specified internal format
glTemplateAttribt_o(template, GL_FORMAT, format);
glTemplateAttribt_i(template, GL_WIDTH, 256);
glTemplateAttribt_i(template, GL_HEIGHT, 256);
glTemplateAttribt_i(template, GL_TEXTURE, GL_TRUE);

// Create the texture image object
GLbuffer image = glCreateImage(template);

// Define the contents of the texture image
glImageData2D(image,
0, // mipmap level 0
0, 0, // copy at offset (0,0) within the image
256, 256, // copy width and height 256 texels
GL_RGBA, GL_UNSIGNED_BYTE, // format and type of data
data); // and the actual texels to use

 

  • Un exemple de rendu complet en pseudo-langage:
// Create a framebuffer object to render to/ This is the fully general form for offscreen rendering,
// but there will be a way to bind a window-system provided drawable as a framebuffer object, or
// as the color image of an FBO, as well.LPformat cformat, dformat, sformat = { create format objects for color, depth, and stencil buffers respectively }LPframebuffer fbo = { create a framebuffer object, specifying cformat, dformat, and sformat as the required formats of color buffer 0, the depth buffer, and the stencil buffer respectively }LPbuffer cimage, dimage, simage = { create image objects, specifying cformat, dformat, and sformat as the formats of the color image, depth image, and stencil image respectively }{Attach cimage, dimage, and simage to fbo at its color buffer 0, depth buffer, and stencil buffer attachment points respectively}
// Create a program object to render withLPshader vertshader, fragshader = { create shader objects for the vertex and fragment shader stages, specifying the shader program text for each stage as an attribute of the respective shader object}

LPprogram program = { create program object, specifying vertshader and fragshader as attributes of the program object}

LPbuffer vertbuffer, fragbuffer = { create unformatted buffer objects for the uniform storage used by the vertex and fragment shaders, respectively }

{Attach vertbuffer and fragbuffer to program as the backing store for the uniform partitions of the vertex and fragment shaders, respectively}
// Create vertex attribute arrays to render with
LPbuffer attribs = { create an unformatted buffer object containing all the attribute data required by the bound programs }

LPvertexArray vao = { create a vertex array object with specified size/type/stride/offset attributes for each required attribute array }

{Attach attribs to vao at each attachment point for a required attributes}
// Create miscellaneous required state objects
LPsampleops sampleops = { create sample operations object with specified fixed-function depth test, stencil test, blending, etc. attributes }

LPmiscstate misc = { create "miscellaneous state" object with specified rasterization settings, hints, etc. }
// Bind everything to the context
lpBindVertexArray(vao);
lpBindProgram(program);
lpBindFramebuffer(fbo);
lpBindSampleops(sampleops);
lpBindMiscState(misc);
// Finally, all required objects are defined and we can draw a single triangle (or lots of them)
LPint first = 0, count = 3;
lpDrawArrays(LP_TRIANGLES, &first, &count, 1, 1);

Conclusion

OpenGL 3.0 est un énorme pas en avant pour OpenGL, il devrait permettre (je l’espère) de combler toutes les faiblesses qu’avait accumulé l’API par rapport à DirectX. Avec OpenGL 3.0, nous disposerons à nouveau d’une API 3D de pointe, efficace, propre, facile d’utilisation et surtout multi-plateforme qui devrait pouvoir faire regretter à John Carmack d’ètre passé de coté obscur 😉 (http://www.beyond3d.com/content/news/462)

Cyril Crassin

 

Références