Accueil / Rendu en cluster
Rendu en cluster
Le module cluster (srcs/core/cluster/*, interfaces srcs/common/cluster/*)
permet de répartir le rendu d’une image par tuiles sur plusieurs machines du réseau.
Une machine joue le rôle de serveur : elle détient la scène et distribue les tuiles à rendre.
Les autres machines sont des workers (clients) : elles calculent les tuiles reçues et
renvoient les pixels au serveur, qui assemble l’image finale.
Vue d’ensemble
Le but du cluster est d’accélérer un rendu en faisant travailler plusieurs ordinateurs en parallèle.
L’image est découpée en tuiles rectangulaires (taille par défaut 32×32),
chacune identifiée par un tile_id. Le serveur maintient une file de tuiles à faire et
les envoie aux workers connectés ; chaque worker rend sa tuile localement puis renvoie le tableau
de pixels correspondant. Le serveur accumule ces pixels dans le tampon d’image.
Le rendu est progressif par échantillons (sample) : chaque tuile est
associée à un numéro d’échantillon, et une nouvelle passe complète peut être relancée pour affiner le
résultat. La coordination des tuiles est gérée par ClusterRenderCoordinator, et
l’accumulation par ClusterRenderer (qui implémente à la fois ISceneRenderer
et IClusterTileSink).
Le mode cluster est optionnel et s’active en ligne de commande
(--server / --connect) ou depuis l’interface. Sans ces options, le Raytracer
fonctionne en solo, exactement comme décrit dans le pipeline de rendu.
Serveur et workers
Le module distingue deux rôles, exposés par ClusterMode
(CLIENT, SERVER, NONE) :
| Rôle | Classe | Responsabilités |
|---|---|---|
| Serveur (hôte) | ClusterServer (server/*) |
Détient la scène, écoute les connexions TCP entrantes, gère une liste de
Connection, distribue les tuiles (dispatchRenderRequests),
reçoit les pixels et diffuse l’état de rendu à tous les workers. |
| Worker (client) | ClusterClient (client/*) |
Se connecte au serveur, récupère la scène, attend des demandes de tuiles, rend chaque
tuile dans un thread dédié (renderLoop) et renvoie les pixels. |
Côté serveur, chaque worker est représenté par une Connection qui suit un
ConnectionState (PENDING, CONNECTED, DISCONNECTED,
REFUSED). Côté worker, le client suit son propre ClientState
(au fil de la session : récupération des données, réception, rendu, envoi, repos) et l’état de
rendu du serveur (ServerRenderState : IDLING ou RENDERING).
API — IClusterModule
Le point d’entrée est l’interface IClusterModule
(srcs/common/cluster/IClusterModule.hpp), implémentée par ClusterModule.
Elle possède un serveur ou un client (jamais les deux à la fois) et refuse de démarrer si un
mode est déjà actif.
namespace rc
{
enum class ClusterMode { CLIENT, SERVER, NONE };
class IClusterModule
{
public:
virtual ClusterMode getClusterMode() const = 0;
virtual IClusterServer *getClusterServer() const = 0;
virtual IClusterClient *getClusterClient() const = 0;
// Démarre un serveur qui détient `scene` ; port 0 = port automatique.
virtual void startServer(IScene *scene, size_t port = 0) = 0;
// Rejoint un serveur en tant que worker.
virtual void joinCluster(const std::string &address, size_t port) = 0;
// Quitte le cluster (uniquement valable en mode CLIENT).
virtual void leaveCluster() = 0;
};
}
| Méthode | Effet |
|---|---|
startServer(scene, port) | Crée un ClusterServer pour la scène et le démarre ; passe en mode SERVER. Lève une exception si un cluster est déjà actif ou si le démarrage échoue. |
joinCluster(address, port) | Crée un ClusterClient et se connecte à address:port ; passe en mode CLIENT. Lève une exception en cas d’échec de connexion. |
leaveCluster() | Déconnecte et détruit le client, repasse en mode NONE. Lève une exception si l’on n’est pas connecté en tant que client. |
getClusterMode() | Renvoie le mode courant (CLIENT / SERVER / NONE). |
getClusterServer() / getClusterClient() | Accès à l’instance active (ou nullptr). |
Déroulé d’une session
La communication passe par des sockets TCP. Chaque message est encadré par un en-tête de 6 octets (identifiant de paquet sur 2 octets + taille de la charge utile sur 4 octets, en ordre réseau), suivi de la charge utile sérialisée. Le déroulé nominal d’une session est le suivant :
- Connexion — le worker ouvre une connexion TCP vers le serveur et envoie
ClientJoinRequest. - Acceptation — le serveur marque la connexion
CONNECTEDet répondServerJoinResponse. - Demande de scène — le worker envoie
ClientFetchSceneData. - Envoi de la scène — le serveur sérialise la scène en JSON
(
SceneRegister) et renvoieServerSceneData. - Préparation — le worker analyse le JSON (
SceneParser), construit le BVH et passe au repos (IDLING). - Distribution — pendant le rendu, le serveur assigne des tuiles avec
ServerRenderRequest(identifiant, échantillon, coordonnées). - Retour des pixels — le worker rend la tuile et renvoie
ClientTileData(les pixels de la tuile). - Assemblage — le serveur valide la tuile (
markComplete) et accumule les pixels dans l’image (applyTileSample). - État & annulation — le serveur diffuse son état
(
ServerRenderState) et peut annuler les tuiles en cours (ServerCancelRender).
Worker (client) Serveur (détient la scène)
| |
| 1. connexion TCP + ClientJoinRequest |
| -----------------------------------------> |
| | → CONNECTED
| 2. ServerJoinResponse |
| <----------------------------------------- |
| |
| 3. ClientFetchSceneData |
| -----------------------------------------> |
| |
| 4. ServerSceneData (scène en JSON) |
| <----------------------------------------- |
| (parseScene + buildBvh, état IDLING) |
| |
| 5. ServerRenderRequest (tuile) |
| <----------------------------------------- |
| (render_tile_sample sur la tuile) |
| 6. ClientTileData (pixels) |
| -----------------------------------------> |
| | markComplete + applyTileSample
| ... autres tuiles ... |
| ServerRenderState / ServerCancelRender |
| <----------------------------------------- |
Une file de tuiles avec délai de garde (requeueTimedOut, ~5 s) permet de
réassigner à un autre worker une tuile qui n’est pas revenue à temps, ce qui rend le rendu tolérant aux
workers lents ou perdus.
Types de paquets
Les paquets sont typés par PacketID et reconstruits à la réception par
PacketFactory::createPacket(id, payload). Chaque paquet implémente IPacket
(serialize / deserialize / handle / getId) et
s’auto-traite via un IPacketHandler (la connexion côté serveur, le client côté worker).
Le tableau ci-dessous liste les paquets réellement enregistrés dans la fabrique :
| Paquet | Sens | Contenu |
|---|---|---|
PacketClientJoinRequest | worker → serveur | Demande de connexion (charge utile vide). |
PacketServerJoinResponse | serveur → worker | Acceptation de la connexion (charge utile vide). |
PacketClientFetchSceneData | worker → serveur | Demande d’envoi de la scène (charge utile vide). |
PacketServerSceneData | serveur → worker | Scène complète sérialisée en JSON (sceneData). |
PacketServerClusterData | serveur → worker | Nombre de workers connectés (nb_clients). |
PacketServerRenderRequest | serveur → worker | Demande de rendu d’une tuile : tile_id, sample, start_x, start_y, end_x, end_y. |
PacketClientTileData | worker → serveur | Pixels calculés d’une tuile : mêmes identifiants/coordonnées + tableau pixels (ColorF). |
PacketServerRenderState | serveur → worker | État de rendu du serveur (IDLING / RENDERING). |
PacketServerCancelRender | serveur → worker | Annulation du rendu en cours (charge utile vide). |
L’énumération PacketID réserve aussi deux identifiants
(CLIENT_FETCH_CLUSTER_DATA et SERVER_TILE_DATA) qui ne sont pas encore
enregistrés dans PacketFactory — ils n’ont donc pas d’effet à ce stade.
Format sur le fil (identique dans les deux sens) :
┌───────────────┬────────────────────┬───────────────────────┐
│ id (2 octets) │ taille (4 octets) │ charge utile (N octets)│
└───────────────┴────────────────────┴───────────────────────┘
uint16 uint32 sérialisation IPacket
(ordre réseau) (ordre réseau)
Démarrer un cluster
Les options sont analysées par srcs/config/Options.hpp. Le fichier de scène est un argument
positionnel optionnel ; sans lui, une scène par défaut est chargée.
Sur la machine serveur (elle détient la scène) :
# Démarre l'interface et un serveur de cluster sur le port 8080
./raytracer --server 8080 scenes/demo.cfg
# Port automatique (choisi par le système) si l'on omet le numéro
./raytracer --server scenes/demo.cfg
Sur chaque worker (il rejoint le serveur) :
# Rejoint le serveur situé à l'IP et au port indiqués
./raytracer --connect 192.168.1.10 8080
| Option | Effet |
|---|---|
--server [PORT] | Lance l’interface et démarre le serveur (startServer). PORT est optionnel (défaut : port automatique). |
--connect IP PORT | Lance l’interface et rejoint le serveur IP:PORT (joinCluster). IP et PORT sont obligatoires. |
Règles d’exclusion. Le mode headless -r / --render
ne peut pas être combiné avec --server ni --connect
(le cluster nécessite l’interface). De plus, --server et --connect sont
mutuellement exclusifs — une instance est serveur ou worker, jamais les deux.
Rejoindre depuis l’interface
En plus de la ligne de commande, on peut rejoindre un cluster depuis l’interface graphique via la
fenêtre JoinClusterWindow
(srcs/plugins/user_interface/windows/JoinClusterWindow.hpp). C’est une petite fenêtre
« Join a cluster » (400×180) proposant :
- un champ IP (adresse du serveur) ;
- un champ Port, restreint aux entiers non signés ;
- un bouton JOIN.
La validation d’un champ ou le clic sur JOIN déclenche un rappel
windowCallback(ip, port) qui appelle in fine joinCluster(ip, port) sur le
module. Valider le champ IP fait passer le focus au champ Port, et le port doit être renseigné pour que
la connexion soit tentée.
Limites & comportement
D’après le code actuel :
- IPv4 uniquement — les sockets utilisent
AF_INET/inet_pton(AF_INET, …); les adresses IPv6 ne sont pas gérées. - Transport en clair — communication TCP brute, sans authentification ni chiffrement. À réserver à un réseau local de confiance.
- Scène transmise en JSON — le serveur re-sérialise la scène via
SceneRegisteret le worker la reconstruit avecSceneParser; chaque worker reconstruit son propre BVH à la réception. - Tolérance aux workers lents — une tuile non revenue avant le délai de garde
(~5 s) est réassignée ; la gestion fine des déconnexions en cours de rendu reste
minimale (
handleClientDisconnectest actuellement un point d’extension quasi vide). - Un seul rôle à la fois — le module refuse de démarrer un serveur ou de rejoindre
un cluster s’il est déjà en mode
SERVERouCLIENT. - Rendu piloté par le serveur — les workers ne rendent que les tuiles que le serveur leur assigne ; ils restent au repos tant qu’aucune demande n’arrive.