Questo tutorial ti mostrerà come iniziare con Metal, un framework introdotto in iOS 8 che supporta il rendering grafico 3D accelerato GPU e carichi di lavoro di calcolo parallelo dei dati. In questo tutorial, daremo un'occhiata ai concetti teorici che sottostanno a Metal. Imparerai anche come creare un'applicazione Metal che imposta lo stato hardware richiesto per la grafica, impegna i comandi per l'esecuzione nella GPU e gestisce il buffer, gli oggetti texture e gli shader precompilati.
Questo tutorial presume che tu abbia familiarità con il linguaggio Objective-C e abbia esperienza con OpenGL, OpenCL o un'API grafica comparabile.
Richiede anche un dispositivo fisico con un processore Apple A7 o A8. Ciò significa che avrai bisogno di un iPhone 5S, 6 o 6 Plus o di un iPad Air o mini (2a generazione). Il simulatore iOS ti darà errori di compilazione.
Questo tutorial si concentra solo su Metal e non coprirà il Metal Shading Language. Creeremo uno shader, ma riguarderemo solo le operazioni di base per interagire con esso.
Se stai usando Xcode per la prima volta, assicurati di aggiungere il tuo ID Apple in conti sezione di Xcode Preferenze. Ciò garantirà che non si verifichino problemi durante la distribuzione di un'applicazione sul dispositivo.
Xcode 6 include un modello di progetto per Metal, ma per aiutarti a capire meglio Metal, creeremo un progetto da zero.
Una nota finale, useremo Objective-C in questo tutorial ed è importante avere una conoscenza di base di questo linguaggio di programmazione.
Per quelli di voi che hanno familiarità con OpenGL o OpenGL ES, Metal è un framework grafico 3D di basso livello, ma con un sovraccarico minore. In contrasto con i framework Sprite Kit o Scene Kit di Apple con i quali, per impostazione predefinita, non è possibile interagire con la pipeline di rendering, con Metal hai il potere assoluto di creare, controllare e modificare quella pipeline.
Il metallo ha le seguenti caratteristiche:
Basta con la teoria, è ora di capire come viene costruita un'applicazione Metal.
Un'applicazione Metal è caratterizzata da una serie di passaggi richiesti per presentare correttamente i dati sullo schermo. Questi passaggi vengono in genere creati nell'ordine e alcuni riferimenti vengono passati da uno all'altro. Questi passaggi sono:
Questo passaggio comporta la creazione di a MTLDevice
oggetto, il cuore di un'applicazione Metal. Il MTLDevice
la classe fornisce un modo diretto per comunicare con il driver e l'hardware della GPU. Per ottenere un riferimento a a MTLDevice
esempio, è necessario chiamare il Dispositivo predefinito di sistema come mostrato di seguito. Con questo riferimento, si ha accesso diretto all'hardware del dispositivo.
idmtlDevice = MTLCreateSystemDefaultDevice ();
Il MTLCommandQueue
class fornisce un modo per inviare comandi o istruzioni alla GPU. Per inizializzare un'istanza di MTLCommandQueue
classe, è necessario utilizzare il MTLDevice
oggetto che abbiamo creato in precedenza e chiamiamo il newCommandQueue
metodo su di esso.
idmtlCommandQueue = [mtlDevice newCommandQueue];
Questo passaggio comporta la creazione di oggetti buffer, trame e altre risorse. In questo tutorial, creerai i vertici. Questi oggetti sono memorizzati sul lato server / GPU e per comunicare con loro è necessario creare una struttura dati specifica che deve contenere dati simili a quelli disponibili nell'oggetto vertice.
Ad esempio, se è necessario passare i dati per una posizione vertice 2D, è necessario dichiarare una struttura dati contenente un oggetto per quella posizione 2D. Quindi, devi dichiararlo in entrambi i client, le tue applicazioni iOS e server, lo shader Metal. Dai un'occhiata al seguente esempio per chiarimenti.
strucedef struct GLKVector2 position; YourDataStruct;
Si noti che è necessario importare il GLKMath libreria dal GLKit quadro come mostrato di seguito.
#importare
Quindi dichiarare un oggetto con le coordinate corrette.
Triangolo YourDataStruct [3] = -.5f, 0.0f, 0.5f, 0.0f, 0.0f, 0.5f;
Creare la pipeline di rendering è probabilmente il passo più difficile, dal momento che devi occuparti di diverse inizializzazioni e configurazioni, ognuna delle quali è illustrata nello schema seguente.
La pipeline di rendering viene configurata utilizzando due classi:
MTLRenderPipelineDescriptor
: fornisce tutti gli stati della pipeline di rendering, come posizioni dei vertici, colore, profondità e buffer degli stencil, tra gli altriMTLRenderPipelineState
: la versione compilata di MTLRenderPipelineDescriptor
e che verranno distribuiti sul dispositivoSi noti che non è necessario creare tutti gli oggetti della pipeline di rendering. Dovresti solo creare quelli che soddisfano le tue esigenze.
Il seguente frammento di codice mostra come creare il file MTLRenderPipelineDescriptor
oggetto.
MTLRenderPipelineDescriptor * mtlRenderPipelineDescriptor = [MTLRenderPipelineDescriptor new];
A questo punto, hai creato il descrittore, ma devi comunque configurarlo con almeno il formato pixel. Questo è ciò che facciamo nel seguente blocco di codice.
mtlRenderPipelineDescriptor.colorAttachments [0] .pixelFormat = MTLPixelFormatBGRA8Unorm;
Per le applicazioni più avanzate, è inoltre necessario impostare gli ombreggiatori di vertici e frammenti predefiniti come mostrato di seguito.
idlib = [mtlDevice newDefaultLibrary]; mtlRenderPipelineDescriptor.vertexFunction = [lib newFunctionWithName: @ "SomeVertexMethodName"]; mtlRenderPipelineDescriptor.fragmentFunction = [lib newFunctionWithName: @ "SomeFragmentMethodName"];
Il newFunctionWithName
metodo cerca nel tuo file sorgente di Metal, cercando il file SomeVertexMethodName
metodo. Il nome dello shader stesso non è importante poiché la ricerca viene effettuata direttamente attraverso i nomi dei metodi. Ciò significa che è necessario definire metodi univoci per operazioni di ombreggiamento uniche. Analizzeremo più in profondità gli shader di Metal in seguito.
Con il MTLRenderPipelineDescriptor
oggetto creato e configurato, il passo successivo è quello di creare e definire il MTLRenderPipelineState
passando nella nuova creazione MTLRenderPipelineDescriptor
oggetto.
NSError * error = [[NSError alloc] init]; idmtlRenderPipelineState = [mtlDevice newRenderPipelineStateWithDescriptor: mtlRenderPipelineDescriptor error: & error];
Per creare una vista Metallo, devi creare una sottoclasse UIView
e scavalcare il layerClass
metodo come mostrato di seguito.
+(id) layerClass return [classe CAMetalLayer];
In questo tutorial, vedremo un altro modo per creare un CAMetalLayer
classe che offre allo sviluppatore un maggiore controllo sulle caratteristiche e sulla configurazione del livello.
Ora che abbiamo inizializzato gli oggetti necessari, dobbiamo iniziare a disegnare qualcosa sullo schermo. Proprio come l'inizializzazione, devi seguire una serie di passaggi:
Il primo passaggio consiste nel creare un oggetto che memorizzi un elenco seriale di comandi per il dispositivo da eseguire. Tu crei a MTLCommandBuffer
oggetto e aggiungi comandi che verranno eseguiti sequenzialmente dalla GPU. Il seguente frammento di codice mostra come creare un buffer di comandi. Noi usiamo il MTLCommandQueue
oggetto che abbiamo creato in precedenza.
idmtlCommandBuffer = [mtlCommandQueue commandBuffer];
In Metal, la configurazione del rendering è complessa e devi indicare esplicitamente quando inizia il passaggio di rendering e quando finisce. È necessario definire le configurazioni del framebuffer in anticipo affinché iOS possa configurare correttamente l'hardware per quella specifica configurazione.
Per chi ha familiarità con OpenGL e OpenGL ES, questo passaggio è simile poiché il framebuffer ha le stesse proprietà, Colore allegato (Da 0 a 3), Profondità, e stampino configurazioni. Puoi vedere una rappresentazione visiva di questo passaggio nel diagramma sottostante.
Per prima cosa devi creare una trama per renderizzare. La trama viene creata dal CAMetalDrawable
classe e usa il nextDrawable
metodo per recuperare la texture successiva da disegnare nella lista.
idframeDrawable; frameDrawable = [renderLayer nextDrawable];
Questo nextDrawable
la chiamata può e sarebbe il collo di bottiglia dell'applicazione poiché può bloccare facilmente la tua applicazione. CPU e GPU possono essere desincronizzate e l'una deve attendere l'altra, il che può causare un'istruzione di blocco. Esistono meccanismi sincroni che possono e devono sempre essere implementati per risolvere questi problemi, ma non li tratteremo in questo tutorial introduttivo.
Ora che hai una texture su cui eseguire il rendering, devi creare un MTLRenderPassDescriptor
oggetto per memorizzare il framebuffer e le informazioni sulla trama. Dai un'occhiata al seguente frammento di codice per vedere come funziona.
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);
Il primo passo imposta la trama per disegnare. Il secondo definisce un'azione specifica da intraprendere, in questo caso eliminando la trama e impedendo il caricamento del contenuto di tale texture nella cache della GPU. Il passaggio finale cambia il colore di sfondo in un colore specifico.
Con il framebuffer e la texture configurata, è tempo di creare un MTLRenderCommandEncoder
esempio. Il MTLRenderCommandEncoder
la classe è responsabile delle interazioni tradizionali con lo schermo e può essere vista come un contenitore per uno stato di rendering grafico. Converte anche il tuo codice in un formato comando specifico per l'hardware che verrà eseguito dal dispositivo.
idrenderCommand = [mtlCommandBuffer renderCommandEncoderWithDescriptor: mtlRenderPassDescriptor]; // Imposta MTLRenderPipelineState // Disegna gli oggetti qui [renderCommand endEncoding];
Ora abbiamo un buffer e le istruzioni in attesa in memoria. Il passaggio successivo consiste nel commettere i comandi nel buffer dei comandi e vedere la grafica disegnata sullo schermo. Si noti che la GPU eseguirà solo il codice che si è specificamente impegnato per l'effetto. Le seguenti righe di codice consentono di pianificare il framebuffer e il commit del buffer dei comandi sulla GPU.
[mtlCommandBuffer presentDrawable: frameDrawable]; [mtlCommandBuffer commit];
A questo punto, dovresti avere un'idea generale di come è strutturata un'applicazione Metal. Tuttavia, per comprendere correttamente tutto questo, devi farlo da solo. È giunto il momento di codificare la tua prima applicazione Metal.
Avvia Xcode 6 e scegli Nuovo> Progetto ... dal File menu. Selezionare Applicazione vista singola dall'elenco di modelli e scegli un nome di prodotto. Impostato Objective-C come lingua e selezionare i phone dal dispositivi menu.
Aperto ViewController.m e aggiungere le seguenti istruzioni di importazione nella parte superiore.
#importare#importare
È inoltre necessario aggiungere il Metallo e QuartzCore strutture nel Quadri e biblioteche collegati sezione del bersaglio Costruisci fasi. D'ora in poi, la tua attenzione dovrebbe essere mirata al file di implementazione di ViewController
classe.
Come accennato in precedenza, il primo compito è impostare e inizializzare gli oggetti core utilizzati nell'intera applicazione. Nel seguente frammento di codice, dichiariamo un numero di variabili di istanza. Questi dovrebbero sembrare familiari se hai letto la prima parte di questo tutorial.
@implementation ViewController idmtlDevice; id mtlCommandQueue; MTLRenderPassDescriptor * mtlRenderPassDescriptor; CAMetalLayer * metalLayer; id frameDrawable; CADisplayLink * displayLink;
Nel controller della vista viewDidLoad
metodo, inizializziamo il MTLDevice
e CommandQueue
casi.
mtlDevice = MTLCreateSystemDefaultDevice (); mtlCommandQueue = [mtlDevice newCommandQueue];
Ora puoi interagire con il tuo dispositivo e creare code di comandi. Ora è il momento di configurare il CAMetalLayer
oggetto. Il tuo CAMetalLayer
il livello dovrebbe avere una configurazione specifica a seconda del dispositivo, del formato pixel e della dimensione del fotogramma. Dovresti anche specificare che userà solo il framebuffer e che dovrebbe essere aggiunto al layer corrente.
Se si verifica un problema durante la configurazione di CAMetalLayer
oggetto, quindi il seguente frammento di codice ti aiuterà con questo.
metalLayer = [layer CAMetalLayer]; [metalLayer setDevice: mtlDevice]; [metalLayer setPixelFormat: MTLPixelFormatBGRA8Unorm]; metalLayer.framebufferOnly = YES; [metalLayer setFrame: self.view.layer.frame]; [self.view.layer addSublayer: metalLayer];
È inoltre necessario impostare l'opacità della vista, il colore di sfondo e il fattore di scala del contenuto. Questo è illustrato nel prossimo snippet di codice.
[self.view setOpaque: YES]; [self.view setBackgroundColor: nil]; [self.view setContentScaleFactor: [UIScreen mainScreen] .scale];
L'unico passo rimasto è quello di rendere qualcosa sullo schermo. Inizializza il CADisplayLink
, passando dentro se stesso
come bersaglio e @selector (renderScene)
come il selettore. Infine, aggiungi il CADisplayLink
oggetto al ciclo di esecuzione corrente.
displayLink = [CADisplayLink displayLinkWithTarget: self selector: @selector (renderScene)]; [displayLink addToRunLoop: [NSRunLoop currentRunLoop] forMode: NSDefaultRunLoopMode];
Questo è ciò che è stato completato viewDidLoad
il metodo dovrebbe assomigliare.
- (void) viewDidLoad [super viewDidLoad]; mtlDevice = MTLCreateSystemDefaultDevice (); mtlCommandQueue = [mtlDevice newCommandQueue]; metalLayer = [layer CAMetalLayer]; [metalLayer setDevice: mtlDevice]; [metalLayer setPixelFormat: MTLPixelFormatBGRA8Unorm]; metalLayer.framebufferOnly = YES; [metalLayer setFrame: self.view.layer.frame]; [self.view.layer addSublayer: metalLayer]; [self.view setOpaque: YES]; [self.view setBackgroundColor: nil]; [self.view setContentScaleFactor: [UIScreen mainScreen] .scale]; displayLink = [CADisplayLink displayLinkWithTarget: self selector: @selector (renderScene)]; [displayLink addToRunLoop: [NSRunLoop currentRunLoop] forMode: NSDefaultRunLoopMode];
Se costruisci il progetto, noterai che Xcode ci dà un avvertimento. Dobbiamo ancora implementare il renderScene
metodo.
Il renderScene
il metodo viene eseguito ogni frame. Ci sono diversi oggetti che devono essere inizializzati per ogni nuovo frame, come ad esempio MTLCommandBuffer
e MTLRenderCommandEncoder
oggetti.
I passi che dobbiamo compiere per rendere un frame sono:
MTLCommandBuffer
oggettoCAMetalDrawable
oggettoMTLRenderPassDescriptor
oggettostruttura
, loadAction
, clearColor
, e storeAction
proprietà del MTLRenderPassDescriptor
oggettoMTLRenderCommandEncoder
oggettoSentiti libero di rivisitare ciò che abbiamo visto finora per risolvere questa sfida da solo. Se vuoi continuare con questo tutorial, dai un'occhiata alla soluzione mostrata di seguito.
- (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; id renderCommand = [mtlCommandBuffer renderCommandEncoderWithDescriptor: mtlRenderPassDescriptor]; // Disegna oggetti qui // imposta MTLRenderPipelineState ... [renderCommand endEncoding]; [mtlCommandBuffer presentDrawable: frameDrawable]; [mtlCommandBuffer commit]; mtlRenderPassDescriptor = nil; frameDrawable = nil;
Dobbiamo anche implementare il controller della vista dealloc
metodo in cui invalidiamo il DisplayLink
oggetto. Abbiamo impostato il mtlDevice
e mtlCommandQueue
oggetti a zero
.
-(void) dealloc [displayLink invalidate]; mtlDevice = nil; mtlCommandQueue = nil;
Ora hai un'applicazione di base molto semplice. È ora di aggiungere la tua prima primitiva grafica, un triangolo. Il primo passo è creare una struttura per il triangolo.
strucedef struct GLKVector2 position; Triangolo;
Non dimenticare di aggiungere una dichiarazione di importazione per GLKMath libreria nella parte superiore di ViewController.m.
#importare
Per rendere il triangolo, è necessario creare un MTLRenderPipelineDescriptor
oggetto e a MTLRenderPipelineState
oggetto. Inoltre, ogni oggetto che è disegnato sullo schermo appartiene al MTLBuffer
classe.
MTLRenderPipelineDescriptor * renderPipelineDescriptor; idrenderPipelineState; id oggetto;
Con queste variabili di istanza dichiarate, dovresti inizializzarle nel file viewDidLoad
metodo come ho spiegato prima.
renderPipelineDescriptor = [MTLRenderPipelineDescriptor new]; renderPipelineDescriptor.colorAttachments [0] .pixelFormat = MTLPixelFormatBGRA8Unorm;
Per ombreggiare il triangolo, avremo bisogno di shader Metal. Gli shader Metal dovrebbero essere assegnati al MTLRenderPipelineDescriptor
oggetto e incapsulato attraverso a MTLLibrary
protocollo. Può sembrare complesso, ma è sufficiente utilizzare le seguenti righe di codice:
idlib = [mtlDevice newDefaultLibrary]; renderPipelineDescriptor.vertexFunction = [lib newFunctionWithName: @ "VertexColor"]; renderPipelineDescriptor.fragmentFunction = [lib newFunctionWithName: @ "FragmentColor"]; renderPipelineState = [mtlDevice newRenderPipelineStateWithDescriptor: renderPipelineDescriptor error: nil];
La prima riga crea un oggetto conforme al MTLLibrary
protocollo. Nella seconda riga, diciamo alla libreria quale metodo deve essere invocato all'interno dello shader per operare il vertice all'interno della pipeline di rendering. Nella terza riga, ripetiamo questo passaggio a livello di pixel, i frammenti. Infine, nell'ultima riga creiamo a MTLRenderPipelineState
oggetto.
In Metal, puoi definire le coordinate del sistema, ma in questo tutorial userai il sistema di coordinate di default, cioè le coordinate del centro dello schermo sono (0,0)
.
Nel seguente blocco di codice, creiamo un oggetto triangolare con tre coordinate, (-.5f, 0.0f), (0.5f, 0.0f), (0.0f, 0.5f)
.
Triangolo triangolo [3] = -.5f, 0.0f, 0.5f, 0.0f, 0.0f, 0.5f;
Quindi aggiungiamo il triangolo al
oggetto, che crea un buffer per il triangolo.
object = [mtlDevice newBufferWithBytes: & triangle length: sizeof (Triangle [3]) options: MTLResourceOptionCPUCacheModeDefault];
Questo è ciò che è stato completato viewDidLoad
il metodo dovrebbe assomigliare.
- (void) viewDidLoad [super viewDidLoad]; mtlDevice = MTLCreateSystemDefaultDevice (); mtlCommandQueue = [mtlDevice newCommandQueue]; metalLayer = [layer CAMetalLayer]; [metalLayer setDevice: mtlDevice]; [metalLayer setPixelFormat: MTLPixelFormatBGRA8Unorm]; metalLayer.framebufferOnly = YES; [metalLayer setFrame: self.view.layer.frame]; [self.view.layer addSublayer: metalLayer]; [self.view setOpaque: YES]; [self.view setBackgroundColor: nil]; [self.view setContentScaleFactor: [UIScreen mainScreen] .scale]; // Crea una pipeline riutilizzabile renderPipelineDescriptor = [MTLRenderPipelineDescriptor new]; renderPipelineDescriptor.colorAttachments [0] .pixelFormat = MTLPixelFormatBGRA8Unorm; idlib = [mtlDevice newDefaultLibrary]; renderPipelineDescriptor.vertexFunction = [lib newFunctionWithName: @ "VertexColor"]; renderPipelineDescriptor.fragmentFunction = [lib newFunctionWithName: @ "FragmentColor"]; renderPipelineState = [mtlDevice newRenderPipelineStateWithDescriptor: renderPipelineDescriptor error: nil]; Triangolo triangolo [3] = -.5f, 0.0f, 0.5f, 0.0f, 0.0f, 0.5f; object = [mtlDevice newBufferWithBytes: & triangle length: sizeof (Triangle [3]) options: MTLResourceOptionCPUCacheModeDefault]; displayLink = [CADisplayLink displayLinkWithTarget: self selector: @selector (renderScene)]; [displayLink addToRunLoop: [NSRunLoop currentRunLoop] forMode: NSDefaultRunLoopMode];
Il viewDidLoad
il metodo è completo, ma manca un ultimo passaggio, creando gli shader.
Per creare uno shader di metallo, selezionare Nuovo> File ... dal File menu, scegliere fonte > File di metallo dal iOS sezione, e nominarlo myshader. Xcode creerà quindi un nuovo file per te, MyShader.metal.
Nella parte superiore, dovresti vedere le seguenti due righe di codice. Il primo include il Libreria standard di metallo mentre il secondo usa il metallo
namespace.
#includereusando il namespace metal;
Il primo passo è copiare la struttura triangolare sullo shader. Gli shader sono solitamente divisi in due diverse operazioni, vertice e pixel (frammenti). Il primo è relativo alla posizione del vertice mentre il secondo è relativo al colore finale di quel vertice e tutte le posizioni dei pixel all'interno del poligono. Puoi guardarlo in questo modo, il primo rasterizzerà il poligono, i pixel del poligono e il secondo ombreggia quegli stessi pixel.
Dal momento che hanno bisogno di comunicare in modo unidirezionale, dal vertice al frammento, è meglio creare una struttura per i dati che verranno passati. In questo caso, passiamo solo la posizione.
typedef struct float4 position [[position]]; TriangleOutput;
Ora creiamo i metodi dei vertici e dei frammenti. Ricorda quando hai programmato il RenderPipelineDescriptor
oggetto sia per vertice che per frammento? Hai usato il newFunctionWithName
metodo, passando in un NSString
oggetto. Quella stringa è il nome del metodo che chiami all'interno dello shader. Ciò significa che devi dichiarare due metodi con quei nomi, VertexColor
e FragmentColor
.
Cosa significa questo? Puoi creare i tuoi shader e nominarli come preferisci, ma devi chiamare i metodi esattamente come li dichiari e dovrebbero avere nomi univoci.
All'interno degli shader, aggiungi il seguente blocco di codice.
vertice TriangleOutput VertexColor (const device Triangle * Vertices [[buffer (0)]], const uint index [[vertex_id]]) TriangleOutput out; out.position = float4 (Vertices [index] .position, 0.0, 1.0); ritorna; fragment half4 FragmentColor (void) return half4 (1.0, 0.0, 0.0, 1.0);
Il VertexColor
il metodo riceverà i dati memorizzati nella posizione 0
del buffer (memoria allocata) e il vertex_id
del vertice. Dal momento che abbiamo dichiarato un triangolo a tre vertici, il vertex_id
sarà 0
, 1
, e 2
. Emette a TriangleOutput
oggetto che viene automaticamente ricevuto dal FragmentColor
. Infine, ombreggia ogni pixel all'interno di questi tre vertici usando un colore rosso.
Questo è tutto. Crea ed esegui la tua applicazione e goditi la tua prima, nuovissima applicazione Metal 60fps.
Se vuoi saperne di più sulla struttura metallica e su come funziona, puoi consultare diverse altre risorse:
Questo conclude il nostro tutorial introduttivo sul nuovo framework Metal. Se avete domande o commenti, sentitevi liberi di lasciare una riga nei commenti.