Ce didacticiel vous montrera comment démarrer avec Metal, un framework introduit dans iOS 8 qui prend en charge les charges de travail de rendu graphique 3D accéléré par GPU et de calcul parallèle de données. Dans ce tutoriel, nous allons examiner les concepts théoriques qui sous-tendent le métal. Vous apprendrez également à créer une application Metal qui définit l'état matériel requis pour les graphiques, valide les commandes à exécuter dans le GPU et gère les tampons, les objets de texture et les shaders précompilés..
Ce tutoriel suppose que vous maîtrisiez bien le langage Objective-C et que vous maîtrisiez bien OpenGL, OpenCL ou une API graphique comparable..
Il nécessite également un périphérique physique doté d'un processeur Apple A7 ou A8. Cela signifie que vous aurez besoin d'un iPhone 5S, 6 ou 6 Plus, d'un iPad Air ou d'un mini (2e génération). Le simulateur iOS vous donnera des erreurs de compilation.
Ce didacticiel est uniquement axé sur le métal et ne couvre pas le langage d’ombrage des métaux. Nous allons créer un shader, mais nous ne couvrirons que les opérations de base pour interagir avec lui.
Si vous utilisez Xcode pour la première fois, assurez-vous d’ajouter votre identifiant Apple à la Comptes section de Xcode Préférences. Cela garantira que vous ne rencontrez pas de problèmes lors du déploiement d'une application sur votre appareil.
Xcode 6 inclut un modèle de projet pour Metal, mais pour vous aider à mieux comprendre Metal, nous allons créer un projet à partir de zéro..
Sur une note finale, nous utiliserons Objective-C dans ce tutoriel et il est important que vous ayez une compréhension de base de ce langage de programmation..
Pour ceux d'entre vous qui connaissent OpenGL ou OpenGL ES, Metal est un framework graphique 3D de bas niveau, mais avec un temps système réduit. Contrairement aux frameworks Sprite Kit ou Scene Kit d'Apple avec lesquels, par défaut, vous ne pouvez pas interagir avec le pipeline de rendu, Metal dispose du pouvoir absolu de créer, contrôler et modifier ce pipeline..
Le métal présente les caractéristiques suivantes:
Assez avec la théorie, il est temps de comprendre comment une application Metal est construite.
Une application Métal est caractérisée par un ensemble d'étapes requises pour présenter correctement les données à l'écran. Ces étapes sont généralement créées dans l’ordre et certaines références sont transmises de l’une à l’autre. Ces étapes sont:
Cette étape implique la création d’un MTLDevice
objet, le coeur d'une application Metal. le MTLDevice
class fournit un moyen direct de communiquer avec le pilote et le matériel du processeur graphique. Pour obtenir une référence à un MTLDevice
par exemple, vous devez appeler le Dispositif par défaut du système comme indiqué ci-dessous. Avec cette référence, vous avez un accès direct au matériel de l'appareil.
identifiantmtlDevice = MTLCreateSystemDefaultDevice ();
le MTLCommandQueue
La classe fournit un moyen de soumettre des commandes ou des instructions au GPU. Pour initialiser une instance du MTLCommandQueue
classe, vous devez utiliser le MTLDevice
objet que nous avons créé plus tôt et appeler le newCommandQueue
méthode sur elle.
identifiantmtlCommandQueue = [mtlDevice newCommandQueue];
Cette étape implique la création de vos objets tampons, textures et autres ressources. Dans ce tutoriel, vous allez créer des sommets. Ces objets sont stockés côté serveur / GPU et pour communiquer avec eux, vous devez créer une structure de données spécifique, qui doit contenir des données similaires à celles disponibles dans l'objet sommet..
Par exemple, si vous devez transmettre des données pour une position de sommet 2D, vous devez déclarer une structure de données contenant un objet pour cette position 2D. Ensuite, vous devez le déclarer dans le client, votre application iOS, et côté serveur, le Metal Shader. Regardez l'exemple suivant pour plus de précisions.
typedef struct position GLKVector2; YourDataStruct;
Notez que vous devez importer le GLKMath bibliothèque de la GLKit cadre comme indiqué ci-dessous.
#importation
Vous déclarez ensuite un objet avec les coordonnées correctes.
Triangle YourDataStruct [3] = -.5f, 0.0f, 0.5f, 0.0f, 0.0f, 0.5f;
La création du pipeline de rendu est probablement l'étape la plus délicate, car vous devez prendre en charge plusieurs initialisations et configurations, chacune d'elles étant illustrée dans le diagramme suivant..
Le pipeline de rendu est configuré à l'aide de deux classes:
MTLRenderPipelineDescriptor
: fournit tous les états de votre pipeline de rendu, tels que les positions des sommets, la couleur, la profondeur et les tampons de gabarit, entre autresMTLRenderPipelineState
: la version compilée de MTLRenderPipelineDescriptor
et qui sera déployé sur l'appareilNotez qu'il n'est pas nécessaire de créer tous les objets de pipeline de rendu. Vous devriez juste créer ceux qui répondent à vos besoins.
L’extrait de code suivant vous montre comment créer le MTLRenderPipelineDescriptor
objet.
MTLRenderPipelineDescriptor * mtlRenderPipelineDescriptor = [MTLRenderPipelineDescriptor new];
À ce stade, vous avez créé le descripteur, mais vous devez toujours le configurer avec au moins le format de pixel. C'est ce que nous faisons dans le bloc de code suivant.
mtlRenderPipelineDescriptor.colorAttachments [0] .pixelFormat = MTLPixelFormatBGRA8Unorm;
Pour des applications plus avancées, vous devez également définir les shaders de vertex et de fragment par défaut, comme indiqué ci-dessous..
identifiantlib = [mtlDevice newDefaultLibrary]; mtlRenderPipelineDescriptor.vertexFunction = [lib newFunctionWithName: @ "SomeVertexMethodName"]; mtlRenderPipelineDescriptor.fragmentFunction = [lib newFunctionWithName: @ “SomeFragmentMethodName"];
le newFunctionWithName
méthode recherche dans votre fichier source Metal, à la recherche du SomeVertexMethodName
méthode. Le nom du shader lui-même n'est pas important car la recherche s'effectue directement à travers les noms de méthodes. Cela signifie que vous devez définir des méthodes uniques pour des opérations de shader uniques. Nous examinerons plus en profondeur les shaders métalliques plus tard.
Avec le MTLRenderPipelineDescriptor
objet créé et configuré, l'étape suivante consiste à créer et à définir le MTLRenderPipelineState
en passant dans le nouveau créé MTLRenderPipelineDescriptor
objet.
NSError * error = [[NSError alloc] init]; identifiantmtlRenderPipelineState = [mtlDevice newRenderPipelineStateWithDescriptor: erreur mtlRenderPipelineDescriptor: & error];
Pour créer une vue Metal, vous devez créer une sous-classe. UIView
et remplacer le layerClass
méthode comme indiqué ci-dessous.
+(id) layerClass return [classe CAMetalLayer];
Dans ce tutoriel, nous examinerons une autre façon de créer un CAMetalLayer
classe qui donne au développeur plus de contrôle sur les caractéristiques et la configuration de la couche.
Maintenant que nous avons initialisé les objets nécessaires, nous devons commencer à dessiner quelque chose sur l’écran. Tout comme l'initialisation, vous devez suivre un certain nombre d'étapes:
La première étape consiste à créer un objet qui stocke une liste série de commandes à exécuter par le périphérique. Vous créez un MTLCommandBuffer
objet et ajouter des commandes qui seront exécutées séquentiellement par le GPU. L'extrait de code suivant montre comment créer un tampon de commande. Nous utilisons le MTLCommandQueue
objet que nous avons créé plus tôt.
identifiantmtlCommandBuffer = [mtlCommandQueue commandBuffer];
Dans Metal, la configuration du rendu est complexe et vous devez indiquer explicitement quand le rendu du rendu commence et quand il se termine. Vous devez définir les configurations de framebuffer à l’avance pour que iOS puisse configurer le matériel correctement pour cette configuration spécifique..
Pour ceux qui connaissent OpenGL et OpenGL ES, cette étape est similaire car le framebuffer a les mêmes propriétés., Attachement de couleur (0 à 3), Profondeur, et Pochoir configurations. Vous pouvez voir une représentation visuelle de cette étape dans le diagramme ci-dessous.
Vous devez d’abord créer une texture à rendre. La texture est créée à partir du CAMetalDrawable
classe et utilise le nextDrawable
méthode pour récupérer la prochaine texture à dessiner dans la liste.
identifiantframeDrawable; frameDrawable = [renderLayer nextDrawable];
Ce nextDrawable
appeler peut et serait le goulot d’étranglement de votre application car il peut facilement bloquer votre application. La CPU et le GPU peuvent être désynchronisés et l'un doit attendre l'autre, ce qui peut provoquer une instruction de blocage. Il existe des mécanismes synchrones qui peuvent, et devraient toujours, être mis en œuvre pour résoudre ces problèmes, mais je ne les couvrirai pas dans ce didacticiel d'introduction..
Maintenant que vous avez une texture à rendre, vous devez créer un MTLRenderPassDescriptor
objet pour stocker les informations de framebuffer et de texture. Jetez un coup d'œil à l'extrait de code suivant pour voir comment cela fonctionne..
MTLRenderPassDescriptor * mtlRenderPassDescriptor; mtlRenderPassDescriptor = [MTLRenderPassDescriptor new]; mtlRenderPassDescriptor.colorAttachments [0] .texture = frameDrawable.texture; mtlRenderPassDescriptor.colorAttachments [0] .loadAction = MTLLoadActionClear; mtlRenderPassDescriptor.colorAttachments [0] .clearColor = MTLClearColorMake (0,75, 0,25, 1,0, 1,0);
La première étape configure la texture à dessiner. La seconde définit une action spécifique à effectuer, dans ce cas, effacer la texture et empêcher le chargement du contenu de cette texture dans le cache du GPU. La dernière étape change la couleur de fond en une couleur spécifique.
Avec le framebuffer et la texture configurés, il est temps de créer un MTLRenderCommandEncoder
exemple. le MTLRenderCommandEncoder
La classe est responsable des interactions traditionnelles avec l'écran et peut être considérée comme un conteneur pour un état de rendu graphique. Il traduit également votre code en un format de commande spécifique au matériel qui sera exécuté par le périphérique..
identifiantrenderCommand = [mtlCommandBuffer renderCommandEncoderWithDescriptor: mtlRenderPassDescriptor]; // Définit MTLRenderPipelineState // Dessine des objets ici [renderCommand endEncoding];
Nous avons maintenant un tampon et des instructions en attente dans la mémoire. L'étape suivante consiste à valider les commandes dans le tampon de commandes et à visualiser les graphiques dessinés à l'écran. Notez que le GPU n'exécutera que le code que vous avez spécifiquement validé pour l'effet. Les lignes de code suivantes vous permettent de planifier votre framebuffer et de valider le tampon de commande dans le GPU.
[mtlCommandBuffer presentDrawable: frameDrawable]; [mtlCommandBuffer commit];
À ce stade, vous devriez avoir une idée générale de la structure d’une application Metal. Cependant, pour bien comprendre tout cela, vous devez le faire vous-même. Il est maintenant temps de coder votre première application Metal.
Lancez Xcode 6 et choisissez Nouveau> Projet… du Fichier menu. Sélectionner Application à vue unique dans la liste des modèles et choisissez un nom de produit. Ensemble Objectif c comme langue et sélectionnez iPhone du Dispositifs menu.
Ouvrir ViewController.m et ajoutez les instructions d'importation suivantes en haut.
#importation#importation
Vous devez également ajouter le Métal et QuartzCore cadres dans le Cadres et bibliothèques liés section de la cible Phases de construction. À partir de maintenant, votre attention devrait être ciblée sur le dossier de mise en œuvre du ViewController
classe.
Comme je l'ai mentionné précédemment, votre première tâche consiste à définir et à initialiser les objets principaux utilisés dans l'ensemble de l'application. Dans l'extrait de code suivant, nous déclarons un certain nombre de variables d'instance. Cela devrait vous paraître familier si vous avez lu la première partie de ce tutoriel..
@implementation ViewController idmtlDevice; identifiant mtlCommandQueue; MTLRenderPassDescriptor * mtlRenderPassDescriptor; CAMetalLayer * metalLayer; identifiant frameDrawable; CADisplayLink * displayLink;
Dans le contrôleur de vue viewDidLoad
méthode, on initialise le MTLDevice
et CommandQueue
les instances.
mtlDevice = MTLCreateSystemDefaultDevice (); mtlCommandQueue = [mtlDevice newCommandQueue];
Vous pouvez maintenant interagir avec votre appareil et créer des files d'attente de commandes. Il est maintenant temps de configurer le CAMetalLayer
objet. Votre CAMetalLayer
La couche doit avoir une configuration spécifique en fonction du périphérique, du format de pixel et de la taille de trame. Vous devez également spécifier qu'il utilisera uniquement le framebuffer et qu'il devra être ajouté au calque actuel..
Si vous avez un problème pour configurer le CAMetalLayer
objet, l'extrait de code suivant vous aidera avec cette opération..
metalLayer = [couche CAMetalLayer]; [metalLayer setDevice: mtlDevice]; [metalLayer setPixelFormat: MTLPixelFormatBGRA8Unorm]; metalLayer.framebufferOnly = YES; [metalLayer setFrame: self.view.layer.frame]; [self.view.layer addSublayer: metalLayer];
Vous devez également définir l'opacité de la vue, la couleur d'arrière-plan et le facteur d'échelle du contenu. Ceci est illustré dans l'extrait de code suivant.
[auto.view setOpaque: YES]; [self.view setBackgroundColor: nil]; [self.view setContentScaleFactor: [UIScreen mainScreen] .scale];
La seule étape qui reste consiste à rendre quelque chose à l'écran. Initialiser le CADisplayLink
, en passant soi
comme cible et @selector (renderScene)
en tant que sélecteur. Enfin, ajoutez le CADisplayLink
objet à la boucle courante.
displayLink = [CADisplayLink displayLinkWithTarget: sélecteur automatique: @selector (renderScene)]; [displayLink addToRunLoop: [NSRunLoop currentRunLoop] pourMode: NSDefaultRunLoopMode];
C'est ce que l'achèvement viewDidLoad
la méthode devrait ressembler à.
- (void) viewDidLoad [super viewDidLoad]; mtlDevice = MTLCreateSystemDefaultDevice (); mtlCommandQueue = [mtlDevice newCommandQueue]; metalLayer = [couche CAMetalLayer]; [metalLayer setDevice: mtlDevice]; [metalLayer setPixelFormat: MTLPixelFormatBGRA8Unorm]; metalLayer.framebufferOnly = YES; [metalLayer setFrame: self.view.layer.frame]; [self.view.layer addSublayer: metalLayer]; [auto.view setOpaque: YES]; [self.view setBackgroundColor: nil]; [self.view setContentScaleFactor: [UIScreen mainScreen] .scale]; displayLink = [CADisplayLink displayLinkWithTarget: sélecteur automatique: @selector (renderScene)]; [displayLink addToRunLoop: [NSRunLoop currentRunLoop] pourMode: NSDefaultRunLoopMode];
Si vous construisez le projet, vous remarquerez que Xcode nous envoie un avertissement. Nous devons encore mettre en œuvre le renderScene
méthode.
le renderScene
La méthode est exécutée à chaque image. Plusieurs objets doivent être initialisés pour chaque nouvelle image, tels que le MTLCommandBuffer
et MTLRenderCommandEncoder
objets.
Les étapes à suivre pour rendre un cadre sont les suivantes:
MTLCommandBuffer
objetCAMetalDrawable
objetMTLRenderPassDescriptor
objettexture
, loadAction
, clearColor
, et storeAction
propriétés du MTLRenderPassDescriptor
objetMTLRenderCommandEncoder
objetN'hésitez pas à revenir sur ce que nous avons vu jusqu'à présent pour résoudre ce défi par vous-même. Si vous souhaitez continuer avec ce tutoriel, jetez un coup d'œil à la solution ci-dessous..
- (void) renderScene idmtlCommandBuffer = [mtlCommandQueue commandBuffer]; while (! frameDrawable) frameDrawable = [metalLayer nextDrawable]; if (! mtlRenderPassDescriptor) mtlRenderPassDescriptor = [MTLRenderPassDescriptor new]; mtlRenderPassDescriptor.colorAttachments [0] .texture = frameDrawable.texture; mtlRenderPassDescriptor.colorAttachments [0] .loadAction = MTLLoadActionClear; mtlRenderPassDescriptor.colorAttachments [0] .clearColor = MTLClearColorMake (0,75, 0,25, 1,0, 1,0); mtlRenderPassDescriptor.colorAttachments [0] .storeAction = MTLStoreActionStore; identifiant renderCommand = [mtlCommandBuffer renderCommandEncoderWithDescriptor: mtlRenderPassDescriptor]; // Dessine des objets ici // définit MTLRenderPipelineState… [renderCommand endEncoding]; [mtlCommandBuffer presentDrawable: frameDrawable]; [mtlCommandBuffer commit]; mtlRenderPassDescriptor = nil; frameDrawable = nil;
Nous devons également implémenter le contrôleur de vue dealloc
méthode dans laquelle on invalide la displayLink
objet. Nous avons mis le mtlDevice
et mtlCommandQueue
objets à néant
.
-(void) dealloc [displayLink invalidate]; mtlDevice = nil; mtlCommandQueue = nil;
Vous avez maintenant une application Metal très basique. Il est temps d'ajouter votre première primitive graphique, un triangle. La première étape consiste à créer une structure pour le triangle.
typedef struct position GLKVector2; Triangle;
N'oubliez pas d'ajouter une déclaration d'importation pour le GLKMath bibliothèque au sommet de ViewController.m.
#importation
Pour rendre le triangle, vous devez créer un MTLRenderPipelineDescriptor
objet et un MTLRenderPipelineState
objet. En outre, chaque objet dessiné sur l'écran appartient à la MTLBuffer
classe.
MTLRenderPipelineDescriptor * renderPipelineDescriptor; identifiantrenderPipelineState; identifiant objet;
Avec ces variables d’instance déclarées, vous devez maintenant les initialiser dans le viewDidLoad
méthode comme je l'ai expliqué plus tôt.
renderPipelineDescriptor = [MTLRenderPipelineDescriptor new]; renderPipelineDescriptor.colorAttachments [0] .pixelFormat = MTLPixelFormatBGRA8Unorm;
Pour ombrer le triangle, nous aurons besoin de shaders en métal. Les shaders métalliques doivent être affectés à la MTLRenderPipelineDescriptor
objet et encapsulé à travers un MTLLibrary
protocole. Cela peut sembler complexe, mais vous devez uniquement utiliser les lignes de code suivantes:
identifiantlib = [mtlDevice newDefaultLibrary]; renderPipelineDescriptor.vertexFunction = [lib newFunctionWithName: @ "VertexColor"]; renderPipelineDescriptor.fragmentFunction = [lib newFunctionWithName: @ "FragmentColor"]; renderPipelineState = [mtlDevice newRenderPipelineStateWithDescriptor: erreur renderPipelineDescriptor: nil];
La première ligne crée un objet conforme à la MTLLibrary
protocole. Dans la deuxième ligne, nous indiquons à la bibliothèque quelle méthode doit être invoquée dans le shader pour exécuter le passage de sommet dans le pipeline de rendu. Dans la troisième ligne, nous répétons cette étape au niveau des pixels, les fragments. Enfin, à la dernière ligne, nous créons un MTLRenderPipelineState
objet.
Dans Metal, vous pouvez définir les coordonnées du système, mais dans ce tutoriel, vous utiliserez le système de coordonnées par défaut, c'est-à-dire que les coordonnées du centre de l'écran sont (0,0)
.
Dans le bloc de code suivant, nous créons un objet triangle avec trois coordonnées, (-5f, 0.0f), (0.5f, 0.0f), (0.0f, 0.5f)
.
Triangle triangle [3] = -.5f, 0.0f, 0.5f, 0.0f, 0.0f, 0.5f;
Nous ajoutons ensuite le triangle au
objet, qui crée un tampon pour le triangle.
objet = [mtlDevice newBufferWithBytes: & triangle length: sizeof (Triangle [3]) options: MTLResourceOptionCPUCacheModeDefault];
C'est ce que l'achèvement viewDidLoad
la méthode devrait ressembler à.
- (void) viewDidLoad [super viewDidLoad]; mtlDevice = MTLCreateSystemDefaultDevice (); mtlCommandQueue = [mtlDevice newCommandQueue]; metalLayer = [couche CAMetalLayer]; [metalLayer setDevice: mtlDevice]; [metalLayer setPixelFormat: MTLPixelFormatBGRA8Unorm]; metalLayer.framebufferOnly = YES; [metalLayer setFrame: self.view.layer.frame]; [self.view.layer addSublayer: metalLayer]; [auto.view setOpaque: YES]; [self.view setBackgroundColor: nil]; [self.view setContentScaleFactor: [UIScreen mainScreen] .scale]; // Créer un pipeline réutilisable renderPipelineDescriptor = [MTLRenderPipelineDescriptor new]; renderPipelineDescriptor.colorAttachments [0] .pixelFormat = MTLPixelFormatBGRA8Unorm; identifiantlib = [mtlDevice newDefaultLibrary]; renderPipelineDescriptor.vertexFunction = [lib newFunctionWithName: @ "VertexColor"]; renderPipelineDescriptor.fragmentFunction = [lib newFunctionWithName: @ "FragmentColor"]; renderPipelineState = [mtlDevice newRenderPipelineStateWithDescriptor: erreur renderPipelineDescriptor: nil]; Triangle triangle [3] = -.5f, 0.0f, 0.5f, 0.0f, 0.0f, 0.5f; objet = [mtlDevice newBufferWithBytes: & triangle length: sizeof (Triangle [3]) options: MTLResourceOptionCPUCacheModeDefault]; displayLink = [CADisplayLink displayLinkWithTarget: sélecteur automatique: @selector (renderScene)]; [displayLink addToRunLoop: [NSRunLoop currentRunLoop] pourMode: NSDefaultRunLoopMode];
le viewDidLoad
la méthode est terminée, mais il manque une dernière étape, la création des shaders.
Pour créer un shader en métal, sélectionnez Nouveau> Fichier… du Fichier menu, choisissez La source > Lime en métal du iOS section, et nommez-le MyShader. Xcode créera alors un nouveau fichier pour vous, MyShader.metal.
En haut, vous devriez voir les deux lignes de code suivantes. Le premier comprend le Bibliothèque standard en métal tandis que le second utilise le métal
espace de noms.
#comprendreutiliser le métal de l'espace de noms;
La première étape consiste à copier la structure en triangle dans le shader. Les shaders sont généralement divisés en deux opérations différentes, le sommet et le pixel (fragments). Le premier est lié à la position du sommet tandis que le second est lié à la couleur finale de ce sommet et à toutes les positions des pixels à l'intérieur du polygone. Vous pouvez le voir de cette façon, le premier pixellisera le polygone, les pixels du polygone, et le second ombrera ces mêmes pixels..
Comme ils ont besoin de communiquer de manière unidirectionnelle, d'un sommet à un fragment, il est préférable de créer une structure pour les données qui seront transmises. Dans ce cas, nous passons seulement la position.
typedef struct float4 position [[position]]; TriangleOutput;
Créons maintenant les méthodes vertex et fragment. Rappelez-vous quand vous avez programmé le RenderPipelineDescriptor
objet pour le sommet et le fragment? Vous avez utilisé le newFunctionWithName
méthode, en passant dans un NSString
objet. Cette chaîne est le nom de la méthode que vous appelez dans le shader. Cela signifie que vous devez déclarer deux méthodes avec ces noms., VertexColor
et FragmentColor
.
Qu'est-ce que ça veut dire? Vous pouvez créer vos shaders et les nommer comme vous le souhaitez, mais vous devez appeler les méthodes exactement comme vous les déclarez et elles doivent avoir des noms uniques..
Dans vos shaders, ajoutez le bloc de code suivant.
sommet TriangleOutput VertexColor (dispositif const Triangle * sommets [[tampon (0)]], index de constante [[id_ vertex]]) TriangleOutput out; out.position = float4 (Vertices [index] .position, 0.0, 1.0); retourner fragment half4 FragmentColor (vide) return half4 (1.0, 0.0, 0.0, 1.0);
le VertexColor
méthode recevra les données stockées en position 0
de la mémoire tampon (alloué en mémoire) et la vertex_id
du sommet. Depuis que nous avons déclaré un triangle à trois sommets, le vertex_id
sera 0
, 1
, et 2
. Il produit un TriangleOutput
objet qui est automatiquement reçu par le FragmentColor
. Enfin, chaque pixel à l’intérieur de ces trois sommets sera ombré en rouge..
C'est tout. Construisez et exécutez votre application et profitez de votre première nouvelle application Metal 60 fps.
Si vous souhaitez en savoir plus sur le framework Metal et son fonctionnement, vous pouvez consulter plusieurs autres ressources:
Ceci conclut notre tutoriel d’introduction au nouveau framework Metal. Si vous avez des questions ou des commentaires, n'hésitez pas à laisser une ligne dans les commentaires.