Artisan Numérique

/bureau/3d/povray/ PovRay et multi-coeurs

Officiellement POVRay devrait dans sa version 3.7 gérer le rendu sur plusieurs coeurs simultanément. La béta le fait déjà officiellement mais elle plante chez moi. De plus megapov étant un must, il faudra aussi attendre que cette version soit mis à jour. Entre temps, voici donc une astuce pour profiter de tout ces processeurs qui roupillent.

Profiter de l'illumination partielle

POVRay permet depuis toujours d'illuminer une zone seulement de l'image finale. Cette zone peut être définie d'une ligne à une autre et/ou d'une colonne à une autre. L'avantage de cette possibilité est que le temps pour illuminer une zone seulement de l'image finale est évidemment moins important qu'illuminer la scène complète, et c'est bien cela qui va nous aider.

Imaginons que nous ayons deux cœurs à disposition pour une image de 800 lignes, l'astuce consiste donc simplement à lancer deux sessions de povray en parallèle, l'une illuminant les lignes de 1 à 400, l'autre de 401 à 800. C'est aussi bête que cela :

povray ma_scene.ini +sr 1 +er 400 -Orendu_1.ppm &
povray ma_scene.ini +sr 401 +er 400 -Orendu_2.ppm &

Alors pour être honnête, ce n'est pas aussi bien que lorsque povray supportera réellement le multi-coeurs car les deux sessions sont obligées de perdre le même temps à analyser la scène. Du coup dans notre cas, plus l'image finale est grande, plus le gain est important.

Recoller les morceaux

Une fois le rendu terminé, nous avons deux images au format PPM qui chacune comporte une tranche de l'image finale. Le format PPM est ici choisi pour la simplicité car le jeu va maintenant consister à faire coïncider ces deux tranches pour former l'image finale. C'est ce que fait le code java suivant (codé très rapidement, donc soyez indulgent :-) )

public class CombinePPM {
  public static String readTag(InputStream stream) throws IOException {
      int data;
      String result = "";
      while ((data = stream.read()) != -1) {
          if (data == '\t' || data == '\n') {
              break;
          }
          result += (char) data;
      }
      return result;
  }

 public static void combine(String source) throws IOException {

      int bits; int width = 0; int height = 0;

      FileOutputStream fileOutputStream = new FileOutputStream(new File(outputFile, source + ".ppm"));
      int offset = 0;
      for (int counter = 1;; counter++) {
          File file = new File(outputFile, source + "_" + counter + ".ppm");
          if (!file.exists()) {
              if (counter == 1) {
                  throw new RuntimeException("Unable to find any data");
              }
              break;
          }
          FileInputStream stream = new FileInputStream(file);
          String header = readTag(stream);
          String sizeTag = readTag(stream);
          String bitsTag = readTag(stream);
          if (!header.equals("P6")) {
              throw new RuntimeException("Wrong file format for " + file + ", only raw PPN accepted.");
          }
          if (counter == 1) {
              String[] size = sizeTag.split("\\s");
              width = Integer.parseInt(size[0]);
              height = Integer.parseInt(size[1]);
              bits = Integer.parseInt(bitsTag);
              fileOutputStream.write(("P6\n" + size[0] + " " + size[1] + "\n" + bits + "\n").getBytes());
          }
          byte[] rgb = new byte[3];
          while (stream.read(rgb) != -1) {
              fileOutputStream.write(rgb);
              offset++;
          }
          stream.close();

      }
      fileOutputStream.close();
  }
  public static void main(String[] args) throws IOException {
      combine(args[0]);
  }
}
fichier combineppm.java

Maintenant pour l'utiliser sur notre précédent résultat :

javac combineppm.java
java -cp . CombinePPM result

La même chose, à distance

Et voilà, le tout collé dans un petit script et vous divisez le temps de rendu par autant de coeurs disponibles. Maintenant il est possible par ssh de faire la même chose mais à distance cette fois :

ssh machine_de_guerre "povray ma_scene.ini +sr 1 +er 400 -Orendu_1.ppm ; cat rendu_1.ppm" > rendu_1.ppm &

Conclusion

Voilà, l'avantage est que cela fonctionne tout de suite et avec toutes les versions dérivées de povray. Pour ceux que cela intéresse, j'ai une version plus complète du code java sous la forme d'un petit visualisateur de vue qui découpe et parallélise le rendu et reconstruit l'image finale tout seul en fonction du nombre de coeurs disponible.