iPhone SDK et le framework OpenGL ES — Partie 1
2 September 2008 par Paul Arthur HenrionBonjour à tous,
ce petit article est une introduction à la programmation OpenGL ES pour iPhoneOS 2. Il vous présentera les frameworks nécessaires à sa mise en œuvre, ainsi que quelques notions utiles pour le développement d'applications en Objective-C. S'en suivra un tutoriel de mise en pratique de ces notions en développant une application affichant un joli cube en 3D que l'on fera tourner avec son doigt.
Pas de "vrai" code dans ce post donc, mais plutôt une introduction poussée pour le post qui s'en suivra.
![]() |
+ | ![]() |
iPhoneOS 2
Il était une fois, un terminal mobile estampillé Apple lancé au cours de l'été 2007, l'iPhone ou l'iPod Touch. À l'époque de sa sortie, tout le monde était content. Tous ? Non évidemment ! Les développeurs habitués de sortir des logiciels sur des produits de la marqué étaient bien tristes : ils ne pouvaient pas mettre en branle leur génie pour en pondre pour ce fameux bidule ! La solution qu'ils avaient était de créer des applications Web pour celui-ci — appelées Webapps — ce qui impliquait qu'elles devaient fonctionner dans le navigateur Web de la bête (Safari) et surtout que celui-ci devait être connecté au Net, ce qui n'était pas sa force à l'époque (Wifi ou GSM).
Une autre solution existait et a été permise grâce à la découverte d'une faille sur la machine : l'utilisation du SDK non-officiel. Ce kit de développement a été créé par une troupe de joyeux lutins qui, non contents d'avoir trouvé la manière d'exécuter du code arbitraire sur le terminal, sont allés encore plus loin en cherchant à redévelopper les outils réservés alors qu'aux seuls employés d'Apple. Afin d'utiliser les applications créées à l'aide de ce SDK, il était nécessaire d'hacker (de jailbreaker) le système. Malheureusement à chaque mise à jour de l'iPhoneOS, les utilisateurs finaux devaient attendre qu'une nouvelle méthode de jailbreak soit trouvée pour pouvoir réutiliser les logiciels en question.
Et puis un jour, Apple a annoncé qu'il allait finalement mettre à disposition des développeurs des outils adéquats pour le développpement d'applications natives pour l'iPhoneOS. Afin de supporter ces nouvelles fonctionnalités, il leur a fallu sortir une nouvelle version du système d'exploitation du terminal : l'iPhoneOS 2.
Tout comme son prédécesseur, l'iPhoneOS 2 est le moteur de l'iPhone et de l'iPod Touch avec les mêmes caractéristiques techniques mais pas tout à fait les mêmes fonctionnalités (pas de support pour la téléphonie et le GPS sur la version iPod Touch). Sa version actuelle est la 2.0 et il semblerait qu'une version 2.1 soit bien avancée.
OpenGL ES ?
Comme annoncé plus haut, ce tutoriel (ainsi que les suivants) a pour but d'introduire une nouveauté dans le SDK de l'iPhone — oui, en fait, presque tout est nouveau dedans, ainsi que lui-même. Cette fameuse nouveauté (présente dans le SDK depuis la bétâ 4) est le framework OpenGL ES (pour "OpenGL for Embedded System" ; si vous êtes vraiment curieux : "Open Graphics Library for Embedded System", et si vous êtes curieux et francophone : "Bibliothèque libre de [traitements] graphiques pour système embarqué").
Un framework mobile
Comme son nom l'indique (ou pas, ça dépend de vos capacités linguistiques), cet ensemble d'outils graphiques est un "portage" de la fameuse bibliothèque OpenGL. "portage" car il ne s'agit pas là d'un simple portage de technologies, mais bien d'une nouvelle spécification de celle-ci afin qu'elle puisse être utilisable sur un "système embarqué". Vous l'aurez compris, c'est une version mobile d'OpenGL !
Et quand je dis "mobile", c'est vraiment mobile : la plupart des systèmes d'exploitation des téléphones modernes implémentent au moins la version 1.0. Mais il n'existe pas que des téléphones qui utilisent cette technologie, ainsi la PlayStation3 gère OpenGL ES, et en version 2.0, s'il-vous-plaît.
À la Apple
La version d'OpenGL ES sur iPhone est la 1.1, ce qui correspond à la version 1.5 des spécifications d'OpenGL. La force de cette implémentation est que, comme Apple réalise de l'ensemble des éléments logiciels (système d'exploitation, implémentation des bibliothèques externes, etc.) et surtout connaît le matériel de leur produit, elle sait parfaitement comment optimiser à fond l'implémentation de ses logiciels pour en tirer le meilleur parti. Ainsi, la version iPhoneOS de OpenGL ES se place 3ème dans des résultats de benchmarks réalisés par GLBenchmarks.com, et ceci avec une résolution d'affichage un tiers de fois plus grandes que ces proches concurrents.
Différence avec OpenGL
Les principales différences entre OpenGL ES et son ensemble père réside dans l'optimisation des fonctions existantes. Ainsi, certaines primitives inutiles (GL_QUADS par exemple) ont été supprimées, la syntaxe de création d'éléments à afficher s'est vu allégée (écriture à coup de batch de points et non une déclaration un à un entre les balises glBegin() et glEnd()), tout comme celle des couleurs qui a souvent été considérée comme trop verbeuse, des types ont été virés (comme les GL_DOUBLE, hein, pas des personnes), des capacités ont été réduites (pas la peine de faire un truc de fou hyper-détaillé à l'infini sur un petit écran)... mais le plus important reste l'introduction d'extensions très utiles, comme un système de particules, l'apparition des sprites de point, l'habillage de points et l'accélération du chargement des textures 2D.
Objective-C 2.0
Avant de continuer...
Si le développement en Objective-C / Cocoa [Touch], vous connaissez, alors passer directement à la section suivante, sinon, voici un peu d'aide pour comprendre quelques principes de développement. Attention, je ne traiterai pas des fondements de la programmation, de la programmation orientée objet ou d'autres sujets sous-jacents à ce que l'on fait là, car ce serait un poil trop longuet.
Objet et méthode
En Objective-C, les objets s'appellent bien des objets, mais les méthodes non ! On dit que l'on passe un message à un objet et non qu'on appelle une méthode d'un objet... bon, d'accord, c'est assez inutile de savoir ça, mais c'est pour vous le dire. De plus, ces messages ne possèdent pas une suite d'arguments anonymes, mais peuvent être (entendre : il est fortement conseillé de le faire) étiquetés. Les étiquettes sont des mots nommant les arguments que l'on passe à la méthode. Ca ne sert pas à grand chose pouvez-vous croire, mais il n'en est rien : couplé avec les conventions de nommage, ça permet de savoir exactement ce que fait une méthode. C'est extrêmement pratique lorsque l'on chercher une méthode à appeler mais qu'on ne sait pas laquelle utiliser. Enfin, lors d'un passage d'un message, il faut indiquer toutes les étiquettes et dans l'ordre et se fait en plaçant l'objet entre crochets.
// C++ Style object.message(firstParameter, secondParameter); // Objective-C Style [object message:firstParameter secondParameterOfMyMessage:secondParameter];
Un exemple d'étiquette est ici secondParameterOfMyMessage
Déclaration d'une classe
Une classe se déclare comme suit :
@interface MyClassyClass : MyClassyClassSuperClass
{
int thisIsAnInteger;
}
@endLe mot-clef @interface signifie que l'on est en train de déclarer l'interface d'une classe et non pas une interface au sens du Java ! MyClassyClassSuperClass est la classe dont hérite MyClassyClass. Entre les accolades on n'indique que les attributs de la classe ; les méthodes sont déclarées entre l'accolade fermante et le @end.
Il est à noter qu'en Objective-C le multi-héritage n'est pas possible. Il a cependant été mis en place des systèmes (pas cons) paliatifs comme par exemple les protocoles, dont je ne parlerai pas ici.
Déclaration d'une méthode d'instance
Une méthode d'instance se définit comme suit :
@interface MyClassyClass
{
// Des attributs ici (pas de méthode)
}
- (returnType) instanceMethodWithParameter:(firstParameterType)firstParameter secondParameter:(secondParameterType)theSecondOne;
@endLe returnType est le type de retour de la méthode. Ca peut être n'importe quel type provenant du C (void, char, int, etc.), ou de Cocoa (NSString *, etc.). firstParameterType et secondParameterType sont les types respectifs des arguments (ou paramètres, ça dépend de votre religion) firstParameter et theSecondOne.
Le - signifie que c'est une méthode d'instance.
L'appel d'une méthode d'instance sur un objet se fait comme montré plus haut. Dans notre exemple, cela donne :
MyClassyClass *anInstance = [MyClassyClass alloc]; [anInstance init]; [anInstance instanceMethodWithParameter:42 secondParameter:51];
alloc et init sont respectivement une méthode de classe et une méthode d'instance, expliquées un peu plus bas.
Déclaration d'une méthode de classe
Une méthode de classe se définit comme suit :
@interface MyClassyClass
{
// Des attributs ici (pas de méthode)
}
+ (returnType) classMethod;
@endL'unique différence est le + qui signifie que cette méthode est une méthode de classe.
L'appel d'une méthode de classe se fait sur une classe. Dans notre exemple, cela donne :
returnType theResult = [MyClassyClass classMethod];
Les propriétés
L'intérêt des propriétés (ou properties) est qu'elles permettent de manipuler aisément et dynamiquement les propriétés intrinsèques d'un objet — le plus souvent, elles correspondent aux attributs publics de celui-ci — sans passer par des méthodes d'accès. Une des nouveautés apportées par Objective-C 2.0 est l'apparition de mots-clefs permettant de générer à la compilation des accesseurs à des propriétés. Les propriétés sont en fait des éléments représentant les attributs (en général, car parfois on peut se passer des attributs) d'un objet.
Cela peut vous paraître abstrait pour le moment, mais retenez juste que les propriétés sont des attributs intelligents d'un objet et que celles-ci peuvent être de n'importe quel type (objet comme scalaire).
@interface MyClassyClass
{
int myInteger;
}
@property int myInteger;Dans le code source de la classe (le fichier ".m"), il faut indiquer que l'on veut générer les méthodes d'accès à la compilation, car on est feignant.
@implementation MyClassyClass; @synthesize myInteger;
Si l'on indique pas @synthesize, il faut écrire nous-même les accesseurs. Ainsi, il faut écrire les méthodes (int) myInteger; et (void) setMyInteger:(int)anInteger;, sinon ça va péter à l'exécution !
Une propriété est accessible à la manière des attributs d'un objet en C++, c'est-à-dire en utilisant un . .
En lecture :
NSLog(@"%i", myObjet.myInteger);
équivaut à :
NSLog(@"%i", [myObjet myInteger]); //NSLog est une fonction à la printf pour afficher des informations dans la console
En écriture :
myObjet.myInteger = 42;
équivaut à :
[myObjet setMyInteger:42];
Une dernière remarque à propos des propriétés. Il existe différentes manières de déclarer une propriété, celles-ci influent sur la manière dont le compilateur devra générer ses accesseurs. À l'aide d'attributs placés entre @property et le type de celui-ci, on indique la manière dont le compilateur doit traiter la propriété.
Les attributs par défaut (entendre : si on n'a rien indiqué, voici ce que choisit le compilateur) sont :
assign: indique que l'on va assigner la nouvelle valeur (pas la copier, ni la retenir)readwrite: indique que l'on peut lire et modifier la propriétéatomic: (ce mot-clef n'existe pas, mais son comportement si, cherchez la logique) indique que les accesseurs seront sécurisés pour fonctionner dans une application multi-threadéegetter=leNomDeVotrePropriété, setter=setLeNomDeVotrePropriété: indique les noms des accesseurs
Les autres attributs sont :
copy: indique que l'on va copier la nouvelle valeur et l'assigner à la propriétéretain: indique que l'on va retenir la valeur et l'assigner à la propriétéreadonly: indique que l'on ne peut que lirenonatomic: indique que les accesseurs ne seront pas sécurisés pour fonctionner dans une application multi-threadée
Un petit exemple :
@property (retain, readonly, nonatomic, getter=jeVeuxMonBidule, setter=jeVaisSetterUnTrucDeOuf) NSColor *blush;
et il faut que - (NSColor *) jeVeuxMonBidule; et - (void) jeVaisSetterUnTrucDeOuf:(NSColor *)argument; existent, sinon le compilateur va crier.
Gestion de la mémoire en Cocoa et Cocoa Touch
Contrairement à ce que l'on pourrait croire, il n'existe pas de gestion de la mémoire en Objective-C... mais elle existe en Cocoa et Cocoa Touch ! Et oui, il est souvent cru c'est le langage qui supporte ça, mais c'est une erreur ; c'est en fait le framework majeur utilisant cette technologie qui le fait.
Comme indiqué un peu plus haut, on utilise un système de pointeurs intelligents (ou Smart pointers) pour gérer la mémoire. Ainsi, on peut allouer de la mémoire avec la méthode de classe alloc, on peut retenir une zone mémoire déjà allouée pour travailler dessus sans avoir à en faire une copie ou "taper directement dessus" avec la méthode d'instance retain, on peut désallouer la mémoire avec la méthode d'instance dealloc (mais vous n'aurez normalement JAMAIS à l'appeler), on peut relâcher une zone mémoire retenue ou allouée avec la méthode d'instance retain (c'est ça qu'il faut utiliser !) et enfin, on peut dire que c'est au système de gérer la libération de la mémoire pour nous, une fois qu'on a fini d'utiliser un objet en appelant la méthode d'instace autorelease.
MyClassyClass *object = [MyClassyClass alloc]; MyClassyClass *retainedObject = [object retain]; MyClassyClass *copiedObject = [object copy]; [copiedObject dealloc]; //Attention c'est le mal absolu de faire ça [retainedObject autorelease]; [object release];
Dernières petites choses
En Objective-C, on aime bien les conventions de nommage — vous l'avez peut-être remarqué dans les exemples précédents. Ainsi, il est bon de nommer les arguments d'une méthode à l'aide d'étiquette donnant le plus souvent une indication sur le type de l'argument, leur nombre, etc. Dans le même genre, lorsqu'une méthode renvoie un pointeur sur un objet, il suffit de lire le nom de celle-ci pour savoir comment la zone mémoire liée à celui-ci doit être gérée. Ainsi, si le nom de la méthode commence par le nom du type de l'objet, celui retourné sera libéré par le système si vous ne le retenez pas, en revanche, si le nom contient alloc, copy ou encore new se sera à vous de le libérer de la mémoire.
On aime bien aussi déléguer le travail. Ainsi, il est souvent utilisé des delegates, qui sont des objets qui réalisent le travail de leurs délégueurs afin que ces derniers puissent rester disponibles pour d'autres calculs. Les délégués sont surtout utilisés pour altérer le comportement d'une classe sans avoir à créer une classe héritant de celle-ci. Ils sont aussi utilisés pour séparer le code métier de l'affichage afin de suivre le modèle de conception Modèle-Vue-Contrôleur. Ainsi, l'ensemble des classes "graphiques" (fenêtre, bouton, etc.) dispose d'un système de délégation afin de ne pas être responsable d'action
Considérations graphiques
Avant de pouvoir afficher quelque chose sur un iPhone/iPod Touch, il faut comprendre comment fonctionne.
Les frameworks
Pour afficher, iPhoneOS, comme son grand frère MacOS X, repose sur OpenGL. Là-dessus reposent plusieurs couches logiciels, du plus bas au plus haut niveau, nous avons (je n'ai pas tout mis !) :
- OpenGL [ES] : bah on ne le présente plus...
- CoreGraphics : l'API C sur laquelle repose à peu près tout ce qui se fait sur Mac et iPhone. Elle s'occupe de lier ce qu'il y a au-dessus avec OpenGL en tirant parti au mieux du matériel et des OS.
- Quartz2D : l'API C basé sur PDF et Postscript pour faire la 2D sur Mac et iPhone. Lorsque l'on ne fait pas de 3D, c'est ça qu'il faut utiliser.
- CoreAnimation : le framework Objective-C le plus haut niveau pour faire de la (fausse) 3D et animée sur Mac et iPhone. Il est organisé en couche, fait appelle à tout ce qu'il a au-dessous de lui pour produire facilement des animations et effets kikoolol optimisés.
- UIKit : bah c'est ici que vous avez toutes les fenêtres, boutons, etc. Il n'y a rien à faire niveau programmation graphique — sauf si vous voulez modifer l'aspect de vos éléments de l'application...
De l'art d'afficher de l'OpenGL dans un milieu trop haut niveau pour lui
Vous l'aurez compris, sans le vouloir, rien qu'en voulant afficher une simple fenêtre avec un bouton, on fait de l'OpenGL ! Mais la question qu'on peut se poser légitimement est : comment faire directement de l'OpenGL dans une application Cocoa [Touch] sans passer par tout ça ? La réponse est que ce n'est pas possible
Pour faire l'OpenGL dans une application Cocoa [Touch], il faut créer une classe qui va pouvoir s'inclure dans l'ensemble de vos classe graphiques (fenêtres, vues, etc.) qui utilisent ce framework haut-niveau. Ce n'est pas possible autrement et c'est bien normal, car ça se trouve vous voulez justement utiliser des fonctionnalités de ce framework haut niveau — comme par exemple le multui-touch pour faire tourner votre cube OpenGL —, mais aussi, vous voulez être sûr de pouvoir faire vos affaires sans déranger les autres. Imaginez un homme de cromagnon voulant simplement éclater la tête d'un cerf avec sa massue sur le parvis de La Défense ; s'il le fait sans s'expliquer en terme compris par ses voisins, alors il risque fort de finir au commissariat de Puteaux ; par contre, s'il leur explique dans leurs termes que c'est dans un but artistique en vue dénoncer des exactions commises contre le peuple canadien, alors on le laissera sans doute faire — on l'admira peut-être.
Il est donc nécessaire de civiliser notre code OpenGL pour pouvoir lui faire afficher ce que l'on souhaite dans un milieu trop évolué pour lui. Il lui faut ce qu'on appelle un context et c'est grâce à une couche fournit par CoreAnimation que ça va se faire.
Conclusion
Vous êtes sûrement frustré de ne pas avoir mis les mains dans le cambouis, mais cette introduction (à rallonge) était nécessaire. J'espère que vous avez compris quelque chose, car ce n'était pas simple à expliquer.
Ce qu'il faut retenir c'est que :
- l'iPhone peut faire tourner de l'OpenGL ES 1.1 et tout à faire correctement
- l'Objective-C 2.0 c'est différent du C et du C++
- CoreAnimation va nous permettre de faire de l'OpenGL ES alors que ceci s'exécutera dans un milieu hostile à celui-ci
- on va faire de la 3D dans le prochain tutoriel, promis !
Tags: CoreAnimation, CoreGraphics, iphone, iPod Touch, OpenGL ES


19 November 2008 à 0:05
Methode de classe et methode d’instance: il faudrait peut etre expliquer la difference histoire que les gens comprennent non?
17 December 2008 à 22:41
Merci de votre post Metabaron, j’apprecie de voir que cet article est lu
Effectivement, ce serait une bonne idee. Le probleme est que je ne souhaitais pas faire un post expliquant les fondements de la programmations orientee objet, car ca l’aurait encore plus rallonge ! Dans le meme gout, je n’explique pas la difference entre un objet et une classe, la hierarchie de classe ni d’autres joyeusetes de la POO.
“Attention, je ne traiterai pas des fondements de la programmation, de la programmation orientée objet ou d’autres sujets sous-jacents à ce que l’on fait là, car ce serait un poil trop longuet.”
18 December 2008 à 0:30
Merci pour le petit cours Paul Arthur, tu es mon nouveau dieu
24 January 2009 à 15:53
Ah ! Merci beaucoup pour ce tutoriel, est-ce qu’il y aura une partie sur Quartz2D ?
Bravo en tout cas !
11 September 2009 à 13:45
Merci pour ce cour, dont on sent se dégager le travail de concision et de clarté !