Show full content
So sollte ein VW Bus heutzutage aussehen!

So sollte ein VW Bus heutzutage aussehen!


TLDR: Render Moana 20 times faster!
I just released v0.1.0 of the Gonzales renderer. After 450 commits it’s time to write a little bit about the progress in the last two and a half years. The main points are:
There are still lots of things to do of course:
May, 15th, 2023
Andreas

There is a course on accelerated Ray Tracing with Optix online (https://github.com/ingowald/optix7course). I decided to go through it, just the other way around; implement Optix in Software. There is some glue code implementing the API and the ray tracing kernel from my own renderer (https://github.com/gonsolo/gonzales). It takes advanted of the fact that Clang can parse the CUDA kernels as pure C which are linked on the fly into a dynamic shared library and dlopen’ed.
Of course it is slow as hell but it was fun doing it. The repository is forked at https://github.com/gonsolo/optix7course. But be warned; it’s not the cleanest code in the universe. 8)

TLDR: Render Disney’s Moana scene in less than 10.000 lines of Swift code.
After Walt Disney Animation Studios released the scene description of the island in Moana some efforts were started to render it besides Disneys Hyperion. I am aware of the following render engines:
Here I present another one, the Gonzales renderer, written by me. It is heavily inspired by PBRT and written in Swift (with a few lines in C++ to call OpenEXR and Ptex). It is optimized only as far as to be able to render it in a reasonable amount of time on a free Google Cloud instance (8 vCPUS, 64GB RAM). As far as I know this is the only renderer able to render Moana not written in C/C++. I wrote it with vi and command line Swift on Ubuntu Linux and Xcode on macOS so it should be relatively painless to get it compiled on these platforms.
Why Swift?I was always uncomfortable with header files and the preprocessor in C and C++. From my point of view something (a variable, a function, …) should be declared and defined once, not twice. Also, the textual inclusion of header files brings with it many problems like having to add implementation details to header files (templates come to mind) or slow compilation times by repeated inclusion of headers and its combinatorial explosion. When I started C++ modules were not available so I evaluated Python (too slow), Go (too much like C) and some others but in the end only Rust and Swift were serious contenders. I finally chose Swift because of readability (I just don’t like „fn main“ of „impl trait“). Also, being written by the implementors of LLVM and Clang gave me confidence that it would a) not be abandoned in the future and b) meet my performance goals. In short, I wanted a compiled language, no pointers, modules, concepts, ranges, readable templates, and I wanted it now. Also, compilers were invented to make the life of programmers easier by making programs more readlabe, and sometimes when looking at templated-based code makes me think we are going backwards in time. I like my stuff readable.
Random notesParsing went through a few incarnations. First it was a simple String(file.availableData, encoding: .utf8) but that is simply to big to fit in memory. Data was not used for similar reasons. Also Scanner from Foundation was evicted at a time. In the end I settled on a InputStream read into an UnsafeMutablePointer<UInt8> array of 64kB.
The Array dead end; in short, don’t ever use Array in a hot path. That is to say, do not ever generate one. This should have been clear from the beginning since it is heap allocated but the lesson was learned quickly since it always turned up at the top of an analysis done with perf. For fixed-size arrays this can be overcome with tuples or Swift’s internal FixedArray. Even if the Array is only used subscript getters tend to show up at the top of perf runs.
In general, I found it quite practical to develop on Linus and macOS in parallel since the available tools to check for performance and memory nicely complement each other. I used mainly four tools:
Talking about memory, while Swift makes it very easy to write readable and compact code, you still have to think about low-level operations like memory allocations and the like. I frequently switched between structs and classes just to see how memory and performance are affected. The nice thing about not having pointers, new and shared_pointers is that I was able most of the time to just switch between the two without changing anything else in the system.

About protocol-based programming: Grepping through todays‘ Gonzales shows 23 protocols, 57 structs, 47 final classes and 2 non-final classes. Inheritance is almost never used. The two remaining non-final classes are TrowbridgeReitzDistribution and Texture, both of which I’m not happy about and think about redesigning them in the future. All in all, protocol-based programming turns out to result in nice code, for example I used to have a Primitive class like PBRT but soon changed it to a protocol inheriting from protocols like Boundable, Intersectable, Emitting (gone now) and others. Now it is gone too, the BoundingHierarchyBuild just depends on a Boundable existential type and returns a hierarchy of Intersectables that is used by BoundingHierarchy. All primitives are now stored as an array of existential types consisting of a composition of protocols of Boundable and Intersectable (var primitives = [Boundable & Intersectable]()).
The primitives in a BoundingHierarchy on the other hand are stored as a [AnyObject & Intersectable]. This has two reasons: 1. Only intersection is needed. 2. AnyObject forces the stored objects to be reference types (or classes) which saves memory since the layout of protocols for both structs and classes (OpaqueExistentialContainer) uses 40 bytes since Swift tries to store structs inline, whereas class-only protocols (ClassExistentialContainer) use only 16 bytes as only a pointer has to be stored as can be seen in Swift’s documentation or verified in the source. I emphasize that this is not only an academic discussion but I came across this since it showed up at the top of a memcheck run.
One of the reasons you can render Moana in less than 10.000 lines is the ability to write compact code in Swift. One extreme example is parameter lists. In PBRT you can attach arbitrary parameters to objects which results in around 1000 lines of code in paramset.[h|cpp]. In Swift you can achieve the same in about three lines:
protocol Parameter {}<String, Parameter>
extension Array: Parameter {}
typealias ParameterDictionary = Dictionary
Actually, I’m cheating a little bit here but you get the point. (Also, I think this has changed in PBRT-v4.)
About interfacing C++ for Ptex and OpenEXR support: Interoperability with C++ is on the way for Swift but wasn’t available when I started/as of now. Since I’m using OpenEXR and Ptex only for reading textures and writing images I resorted to extern "C". One modulemap and a few lines of C++ code later (100 for Ptex, 82 for OpenEXR) I had support for reading and writing OpenEXR images and Ptex textures.
I am releasing the code now as I am able to render Moana on a Google Compute Engine with 8 vCPUs and 64GB memory which is free for three months, so please download the code, get an account at fire it up.
That said, there is a lot to do as I optimized it only as far as to be able to get one image rendered. The following is a big todo list roughly sorted from easily implemented to big projects which I might or might not tackle in the future.
That’s it for now. I would be extremely happy to receive comments what could be done better or implemented more elegantly, bug reports or even pull requests.
Also thanks to Matt Pharr and PBRT, the most valuable resource in the known universe (at least when it involves rendering).
January 14th, 2021.
Andreas

Nachdem der Volksentscheid Fahrrad eingeschlagen hat wie eine Bombe (140.000 Unterschriften) und die CDU im Herbst wohl abgewählt wird (wahlrecht.de), ist es Zeit sich Gedanken zu machen, wie man die Fahrradstruktur in Berlin ausbaut. Dazu hat der Volksentscheid ja auch 10 Punkte identifiziert, die besonders wichtig sind.
Jetzt bin ich selbst nicht der größte Fan von Hauptstraßen, deswegen kann ich mich mit Punkt 2 obiger Ziele nicht ganz identifizieren, deswegen habe ich mir Gedanken gemacht, wie es (noch) besser funktionieren könnte. Wenn ich in Berlin ein neues Ziel ansteuere, konsultiere ich eigentlich immer den Fahrradnavigationsplaner BBBike mit den Optionen „Nebenstraßen bevorzugen“ und „Grüne Wege bevorzugen“. Hierbei habe ich schon sehr oft wunderschöne Nebenstraßen und Plätze entdeckt, die ich noch nicht kannte. Der Zeitverlust dadurch ist mir egal und hält sich auch in Grenzen (bei einer 20-Minuten-Fahrt weniger als eine Minute).
Eine kleine Anekdote dreht sich um das „Loch“ zwischen FT am Friedrichshain und Greifswalder Straße; auf dem Weg von meiner Heimat Friedrichshain zum Fußballspielen im Mauerpark. Wenn man links hinter dem Kino in die Sackgasse hineinfährt, kann man durch ein Loch in der Mauer in den letzten Hinterhof der Greifswalder Straße fahren. Und BBBike kannte dieses Loch. Allgemein kann man sagen, dass die Datenbasis und Routenführung von BBBike einfach sagenhaft gut sind.
Um dem Senat und den Bezirksbürgermeistern eine Datengrundlage zur Verfügung zu stellen, wo man am besten Fahrradstraßen anlegt, habe ich einen kleinen Hack in BBBike geschrieben. Ausgehend von 1000 zufälligen Routen im gesamten Stadtgebiet Berlin konnte ich die Frequenz der benutzten Wege identifizieren. Ausgangspunkt war der Gedanke, dass mich der BBBike immer auf denselben Wegen entlangführt. Aus den 1000 Routen wurde eine Datei mit 165.000 Routenteilstücken, daraus wiederum eine Datei mit 12.000 Kreuzungen, die mehr als einmal (genauer zwischen 2 und 144 Mal) befahren wurden. Von denen habe ich die ersten 5.000 ausgewählt und visualisiert. Die Frequenz dieser Kreuzungen rangiert von 7 bis 144 Durchfahrungen.
Man kann deutlich erkennen, dass BBBike immer dieselben Wege auswählt. Wenn man nur diese Wege für Fahrradfahrer ausbaut, ist schon viel gewonnen. Ein angenehmer Nebeneffekt ist, dass die Autofahrer auf den Hauptstraßen fast völlig ohne Radfahrer fahren können. Im Idealfall muss der Autofahrer am Anfang und Ende der Fahrt nur durch eine oder zwei Nebenstraßen, um auf die Hauptstraßen zu gelangen. Umgekehrt können Radfahrer ungestört auf den Nebenstraßen, am Kanal oder im Park fahren.
Dies sind die Vorteile eine „Dualen Netzes“, auf der Autofahrer und Fahrradfahrer weitgehend unabhängig voneinander fahren können. Gleichzeitig kann man auf den Nebenstraßen die Ampeln abmontieren und rechts-vor-links einführen. Ein Beispiel wäre die Ampel an der Ecke Reichenberger/Glogauer Straße. Wie man auch erkennen kann, führt ein Hauptradverkehrsweg auch am Fraenkelufer entlang, wo sich meiner Meinung nach der schlechteste Rad- und Fußweg Berlins befinden. Auf der Seite des Bürgerbegehrens Fraenkelufer, die sich leider gegen ein Ausbau des Ufers ausgesprochen haben, sieht man das eindrucksvoll auf dem Aufmacherfoto; hoppelige Pflastersteinstraße, quer parkende Autos, kein Radweg, und auch kein Fußweg, der diesen Namen verdient. Und das auf einer der Fahrradhauptstraßen Berlins!
Den (an einem Nachmittag hingerotzten) Quellcode findet man auf Github, aber ich verspreche nicht, dass man ihn versteht, wenn man ihn nicht selbst geschrieben hat. 

Anhang: Das Duale Netz als PDF.