Tu ne sais pas coder ? Ce n'est pas grave !
Comment fonctionne un programme
Tu utiliseras plusieurs langages de programmation différents au cours de ton parcours, dans ce chapitre nous abordons le Shell, langage de programmation & interpréteur de commandes permettant d’intéragir avec le système d’exploitation via des commandes écritent par l'utilisateur souvent dans Terminal.app
Vérifier ton environnement ZSH
Dans ton terminal, les commandes sont safes, essaie-les !
# Version de Clang (compilateur C & C++)
clang++ --version
# → Apple clang version 17.0.0 (doit être ≥ 15)
# Version par défaut du C & C++ utilisé par clang
clang++ -dM -E -x c++ /dev/null | grep __cplusplus
# → #define __cplusplus 201402L
# SDK macOS actif
xcrun --sdk macosx --show-sdk-path
# → /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX26.2.sdk
# Compilateur Metal disponible
xcrun -sdk macosx metal --version
# → Apple metal version 32023.921
# Dernière version du langage C++ & supportée par ton clang
clang++ -std=c++26 -x c++ /dev/null -dM -E | grep __cplusplus
# → #define __cplusplus 202400L
# Voir les frameworks Metal disponibles
xcrun --find metal
xcrun --find metallib
# Ton GPU — nom et famille
system_profiler SPDisplaysDataType | grep -E "Chipset|Metal"
# Infos sur la puce et la ram
sysctl -n machdep.cpu.brand_string
sysctl -n hw.memsize | awk '{print $1/1073741824 " GB RAM"}'
Les manuels en Shell, C & C++ principalement, directement dans ton terminal.
# Shell — terminal
## Le premier manuel à consulter est probablement le manuel du manuel :
man man
Dans la description du manuel du manuel on nous informe que c'est un utilitaire qui permet de trouver et d'afficher les pages de DOCUMENTATION. Les sections du manuel sont :
- General Commands Manual ← fonctions shell
- System Calls Manual
- Library Functions Manual ← fonctions C & C++
- Kernel Interfaces Manual
- File Formats Manual
- Games Manual
- Miscellaneous Information Manual
- System Manager's Manual
- Kernel Developer's Manual
Imaginons que nous avons besoin d'informations à propos de la fonction C++ 'fabsf'
# Shell
man 3 fabsf
FABS(3) Library Functions Manual FABS(3)
NAME
fabs – floating-point absolute value function
SYNOPSIS
#include <math.h> ← directive de préprocesseur à inclure dans ton fichier qui utilise fabsf()
double
fabs(double x);
long double
fabsl(long double x);
float
fabsf(float x); ← prototypes & variantes
DESCRIPTION
The fabs() functions compute the absolute value of a floating-point number x.
Les fonctions fabs() calculent la valeur absolue d'un nombre à virgule flottante x.
SPECIAL VALUES
fabs(±0) returns 0.
fabs(±infinity) returns +infinity.
VECTOR OPERATIONS
If you need to apply the fabs() function to SIMD vectors or arrays, using the following functions
provided by the Accelerate.framework may give significantly better performance:
#include <Accelerate/Accelerate.h>
vFloat vfabsf(vFloat x);
void vvfabsf(float *y, const float *x, const int *n);
void vvfabs(double *y, const double *x, const int *n);
SEE ALSO ← d'autres functions dont existe le manuel : man 3 floor
abs(3), ceil(3), floor(3), rint(3), math(3)
STANDARDS ← norme
The fabs() functions conform to ISO/IEC 9899:2011.
macOS 26.5 December 11, 2006 macOS 26.5
Ci-dessous se trouve un programme écrit en langage C. Après cette page, nous afficherons tout et n'importe quoi à l'écran dans notre propre programme…
- L'extension du fichier C est présente → .c
- On a décidé de nommer le fichier "ft_putchar" ainsi que la fonction
- L'include est entre chevrons (< >)
- Inclure l'en-tête 'unistd.h' permet d’utiliser write()
- On ne renvoit aucune valeur (void)
- On appelle une autre fonction, tout comme main() appelle notre fonction
- On remarque l'ordre de déclaration des deux fonctions
- Fonction main = première fonction = point d'entrée
- "&" fait référence à l'adresse mémoire de la variable charactère nommé ici 'c'
- Entre les braces, tu peux tout faire
- main renvoie 0 si tout s'est bien passé
Concrêtement, main() s'execute, appelle 3 fois notre fonctionnalité créé avec le même paramètre 'Y' = 89 dans la table ASCII, là où '\n' = 10, sont tous les deux un caractère de 1 byte envoyé à l'écran un par un (en millisecondes), qui appelle elle-même la fonctionnalité write décrite en dessous et s'arrête sans erreur.
// ft_putchar.c
#include <unistd.h>
void ft_putchar(char c)
{
write(1, &c, 1);
}
int main(void)
{
ft_putchar('Y');
ft_putchar("\n");
ft_putchar(89);
return 0;
// Compilation : clang ft_putchar.c
// Utilisation : ./a.out
// Résultat : Y
} Y
- ssize_t : nombre entier signé
- size_t : nombre entier non-signé
- char : 1 octet
- int : nombre entier de 4 octets allant de ≈ -2 milliards à +2 milliards
On appelle write() avec les même paramètres que le prototype, c'est alors qu'elle tente d'écrire une taille en octets "nbyte" (ici 1 = 8 bits) à partir du tampon pointé par le tampon de stockage vers le fichier associé au descripteur de fichier ouvert (file descriptor). Ici le pointeur n'a pas de types (ex : char, int, float, …) et est constant, il peut donc être tout.
// prototype de la fonction write dans unistd.h
ssize_t write(int fd, const void *buffer, size_t nbyte);
man 2 write # Le manuel en anglais
Descripteur de fichier (fd)
0 Entrée standard stdin : flux associé au clavier. scanf(), std::cin,.. utilisent implicitement ce flux.1 Sortie standard stdout : flux standard de sortie. printf(), write(), .. utilisent implicitement ce flux de données.
2 Erreur standard stderr : flux associé à la sortie standard d'erreur. Normalement redirigé sur la console de l'application également.
En MSL, en C++ et de nombreux autres langages tu retrouveras + ou - les même façons de faire :
Type(s) de donnée(s) que tu veux que la fonction retourne > son nom > son/ses paramètre(s) ou rien > ton code entre les braces > le retour ou non d'un ou de data types
Mais ce langage très niche est probablement le meilleur depuis longtemps et pour longtemps
C'est une chance inouïe que d'avoir de tel outils tant développé alors casse les codes
* Bash : un type spécifique de shell. Son nom complet est Bourne Again Shell. C’est l’un des shells les plus utilisés sur Linux
TTY = teletypewriter
Tous les Bash sont des shells, mais tous les shells ne sont pas Bash.
Quelques commandes communes.. (safe)
cd # revient à la racine (Command Directory)
mkdir nom_du_dossier_que_tu_veux_creer # Créer un nouveau dossier nommé
cd nom_du_dossier_que_tu_viens_de_creer # Va dans ton dossier
cd … # Reviens en arrière
touch nom_dun_fichier_que_tu_veux.creer.bo # Créer un fichier nommé
vim nom_du_fichier_que_tu_viens_de.creer.bo # Éditeur vim
│
└──> dans vim ─> Insert du texte i
├──> Taper une commande (:) esc
├──> Enregistre :w
├──> Quitte :q
├──> Les deux :wq
├──> Sans sauvegarder :q!
├──> Supprime la ligne dd
man vim # Ouvre le manuel de vim
man touch # Ouvre le manuel de la commande 'touch'
ls -la ./* # Liste les fichiers dans le dossier actuel et les suivants
# flag a ─> avec fichiers cachés tel que .env, .vimrc, …
open fichier_que_tu_veux_ouvrir_avec_xcode # Ouvre l'éditeur externe Xcode
cat fichier_avec_contenu # Affiche l'entièreté du fichier
xattr -l *.png # Affiche les attribus des fichiers .png
find . -name ".DS_Store" -print -delete # Trouve les fichiers et les supprimes récurssivement
clear # Nettoie le terminal
pwd # Affiche le chemin du dossier dans lequel tu es
awk
sed
grep # Ultra puissant
cp src goal/dest # Copie 'source' dans 'destination' dans le dossier goal
mv src goal/dest # Déplace (Move) 'source' vers 'dest …
tty # identifiant interne de ta session terminal
who # identifiant interne de tes sessions terminal
whoami # Qui es-tu
+ de commandes dans le chapitre sur git
git clone lien_HTTPS nom_souhaité # Clone le repository depuis GitHub
git status
git add * # Ajoute le contenu du dossier au prochain commit
git commit -m "message d'amélioration bzzz" # Commit avant de push, avec le message informatif
git push
# Note importante; les fichiers sont supprimés définitivement.
rm -r dossier_poubelle # Supprime tous les fichiers de manière récursive
chmod +x script.sh # Rendre le fichier script executable
chmod 777 script.sh # Donner toutes les permissions au fichier
pip3 install --upgrade pip
!!!RTFM is good!!!!
Direction (directionnary), Lire (read), Écrire (write), Executable, attributs étendus (xattr)
On peut également executer un script Shell depuis un fichier :
#!/usr/bin/env zsh (ou bash)
cd && cd learnmsl.dev && python3 app.py
# Utilisation
./script.sh
# Ou avec argument (ne s'applique pas ici)
./script.sh John
Exemples en production :
#!/bin/zsh
SRC="/Users/laboitederemdal/MSLearn"
DEST_BASE="/Users/laboitederemdal/backupLEARNMSL"
# Trouve le prochain numéro de chapitre
i=1 # Très important en shell, de ne pas mettre d'espace quand il y a "="
while [ -d "$DEST_BASE/chapitre_$i" ];
do
i=$((i + 1))
done
DEST="$DEST_BASE/chapitre_$i"
mkdir -p "$DEST"
echo "Backup vers $DEST"
# Trouve et copie les fichiers en excluant Frameworks (je ne veux garder que nos fichiers)
find "$SRC" -type d -name "Frameworks" -prune -o \
-type f \( -name "*.m" -o -name "*.mm" -o -name "*.h" \
-o -name "*.hpp" -o -name "*.cpp" -o -name "*.metal" \)
-print | while read file; do
rel_path="${file#$SRC/}"
dest_path="$DEST/$rel_path"
# Recrée l'arborescence relative
mkdir -p "$(dirname "$dest_path")"
cp "$file" "$dest_path"
done # Fin de la commande 'find'
touch "$DEST/Makefile"
echo "Backup terminé & Makefile file créé."
# Autoriser le script à s'exécuter
chmod +x backup.sh
# Usage
./backup.sh
Ce que ton ordinateur manipule
Avant de parler GPU, avant même de parler C++, il faut comprendre ce que manipule réellement un ordinateur. Tout — absolument tout — est un nombre.
Le binaire — pourquoi 0 et 1
Un transistor a deux états : passant ou bloquant. Tension présente ou absente. Un bit. Pas de philosophie là-dedans — c'est de l'électronique.
8 bits = 1 octet (byte). Un octet peut représenter 2⁸ = 256 valeurs (0 à 255). Un entier 32-bit (int) = 4 octets = 2³² valeurs possibles.
→ Calculateur de types à découvrir dans 'Setup' ou dans ta calculatrice MacOS.
Hexadécimal — le raccourci des programmeurs
Écrire du binaire à la main est illisible. L'hexadécimal regroupe 4 bits par chiffre (0–9, A–F). Un octet = 2 chiffres hex → le 59
// Même valeur, trois représentations :
89 // décimal
0b01011001 // binaire (préfixe 0b)
0x59 // hexadécimal (préfixe 0x)
// En shader Metal, les couleurs sont souvent en hex :
float4 orange = float4(0xFF/255.0f, 0x5F/255.0f, 0x1F/255.0f, 1.0f);
// R=255 G=95 B=31 A=1
Un aperçu externe provenant du cours "CS50’s Introduction to Computer Science" plus détaillé ici
Un aperçu des couleurs représentée en hex du site web Adobe ici (à enregistrer dans tes favoris!)
Types de données — la taille compte
En C++ et MSL, chaque variable a un type qui dit deux choses : combien d'octets elle occupe, et comment interpréter ces octets.
| Type C++/MSL | Taille | Plage | Usage GPU typique |
|---|---|---|---|
bool |
1 bit | true / false | conditions |
uint |
32 bits | 0 → 4 294 967 295 | vertex_id, indices |
int |
32 bits | -2.1G → +2.1G | offsets, compteurs |
float |
32 bits | ±3.4×10³⁸ | positions, couleurs |
half |
16 bits | ±65 504 | textures HDR, perf |
float4 |
4×32 = 128 bits | — | positions, couleurs RGBA |
- Data Types
- float = nombre à virgule avec 6 chiffres de précision après la virgule allant de 0 à 340282346638528859811704183484516925440.000000, nombre arithmétique.
- double =
- Library Functions Manual ← fonctions C & C++
L'ordinateur n'est pas magique, en l'occurence on ne peut pas additionner deux
// 64-bit floats have 53 digits of precision, including the whole-number-part. double a = 0011111110111001100110011001100110011001100110011001100110011010; // imperfect representation of 0.1 double b = 0011111111001001100110011001100110011001100110011001100110011010; // imperfect representation of 0.2 double c = 0011111111010011001100110011001100110011001100110011001100110011; // imperfect representation of 0.3 double a + b = 0011111111010011001100110011001100110011001100110011001100110100; // Note that this is not quite equal to the "canonical" 0.3!half (16 bits) n'existe pas en C++ standard mais existe en MSL. Sur Apple Silicon, les opérations half sont souvent 2× plus rapides que float car le GPU peut en traiter deux en parallèle. À utiliser pour les couleurs et les données de texture.
Une fonction — lire la signature
Une fonction () prend des entrées, fait un calcul, et retourne une sortie. La signature dit tout :
// Exemples de signatures — lire de gauche à droite
void setup(); // retourne rien, prend rien
float distance(float3 a, float3 b); // retourne un float, prend 2 vecteurs
float4 vertex_main(uint vid); // retourne vec4, prend un uint
Le mot-clé return
Une fonction qui ne retourne pas void doit obligatoirement terminer par un return du bon type. En MSL, le vertex shader doit retourner une float4 — c'est la position en clip space.
vertex float4 vertex_main(uint vid [[vertex_id]])
{
// … calculs …
return float4(x, y, z, w); // obligatoire
}
Pointeurs — le concept qu'on ne peut pas éviter
Un pointeur est une variable qui contient l'adresse mémoire d'une autre variable, pas sa valeur. En metal-cpp, tout objet GPU est un pointeur.
// Une variable normale : contient une valeur
float x = 3.14f;
// Un pointeur : contient l'adresse où est stocké l'objet
MTL::Device* _device = MTL::CreateSystemDefaultDevice();
// l'étoile = c'est un pointeur
// Accès aux méthodes via → (flèche) sur un pointeur
MTL::CommandQueue* _commandQueue = device->newCommandQueue();