Notes On Ray Tracing in One Weekend

Peter Shirley’s Ray Tracing in One Weekend had been on my reading list since a long time. Based on brief incursions into its first few chapters, I was aware of the intensive hands-on approach that the book takes to explain its subject matter.

In addition to the mathematics, this also became a solid opportunity to hone my skills with C# because of several forays into implementing concepts from first-principles. I also encountered some language-specific peculiarities which are uncommon in web development, or as a result of translating the C++ code from the book to C#.

This blog post contains an ongoing list of lessons learned during this exercise.

Console output can be redirected to a file

Now this I knew from earlier, but I’m adding it here for sake of completeness. Anything that the program prints to the console (through Console.Write() or Console.WriteLine()) can be saved to a file by using the > operator in PowerShell (or most other shells).

MyApp.exe > output.log

The commonly used redirection operators are >, >> and N>, which usually present in all shells, across platforms. PowerShell supports additional techniques to pipe output to files using the Out-File and Tee-File cmdlets.

The C# equivalent to std:cerr is Console.Error

The use of stderr is common in several environments because of its convenience and ubiquity. I’ve seen it less often in colloquial C# for runtime logging, where the ILogger interface has far more traction.

The Console.Error is a TextWriter instance that prints to a console by default. And it can be changed to point to any other stream, such as a file or a network endpoint.

Console.Error.WriteLine("The Thing Broke");

But it’s also possible to use the > operator in PowerShell to redirect the standard error output to any other destination, which is far more convenient and flexible.

The unary minus (-) operator negates a value

Easy-peasy. Standard mathematics rules apply. The language has its own rules though, which must be adhered to.

  1. The operator takes only 1 parameter (hence, unary).
  2. The parameter must be the same type that contains the operator declaration.

The unary * operator does not multiply a number by itself

This was my “Doh!” moment. It’s a pointer dereference operator, of course. I had a temporary brain fade when I read this in the book at first. But the documentation set things right.

Compound assignment operators cannot be explicitly overloaded

All compound assignment operators are implicitly overloaded by overloading their corresponding binary operator. These operators are +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>= and >>>=.

The in modifier on a parameter is a poor man’s substitute for const

Applying the in modifier on a parameter makes it a read only reference. The compiler does not prevent the invocation of mutating methods on an in parameter, but the instance is not modified.

public class Apple
{
  public int Size;

  public void Bite()
  {
    Size -= 1;
  }
}
public void Program
{
  public static void Main()
  {
    var fruit = new Apple();
    Mutate();
  }

  private static void Mutate(in Apple fruit)
  {
    // fruit.Size--; // Won't compile
    fruit.Bite(); // Compiles and runs on a copy of fruit
  }
}

When the Bite() method is called, it transparently operates on a copy of the original object. The value of Size is never altered in the original instance. This can introduce subtle bugs for the unwary programmer. The const keyword in C++ comes with much stronger guarantees of immutability by completely preventing the invocation of mutable methods.

And remember, property getters and setters in C# are methods under the hood.

C# 10 adds support for global aliases

Declaring an alias allows the programmer to step around class name conflicts when using unalterable code (such as third-party libraries). Earlier versions of the language required that the alias be declared separately in each file where it was to be used. This is no longer necessary. The global modifier makes the alias available across the entire project.

global using NativeScrollBar = System.Windows.Forms.HScrollBar;
global using LibraryScrollBar = Vendor.Library.HScrollBar;

var a = new NativeScrollBar();
var b = new LibraryScrollBar();

This feature comes in handy when declaring the Color and Point3 types as aliases for the Vec3 type during the course of the book.

Method inlining is not guaranteed

Inline expansion is an optimisation technique that copies the body of the function to the call site. The compiler will almost always do a better job at selecting methods to inline. Besides, C# does not have forced inlining. The JIT heuristics will always be the final authority to determine whether a method should be inlined or not, even when the programmer has explicitly decorated a method with the MethodImplAttribute. So it’s best to leave it out from your code.

Convert.ToInt32() is different from static_cast<int>

This was a significant stumbling block that had a material change on the colour output. In C++, the statement static_cast<int>(255.999) truncates the decimal digit, returning the value 255. The Convert.ToInt32() method in C# rounds to the nearest integer. So Convert.ToInt32(255.999) results in 256, which is outside the 8-bit range of allowed values for each RGB channel. This can give rise to some weird outputs, ranging from wildly incorrect colours, to mosquito noise patterns.

Math.Truncate() could have been used instead, because it truncates the decimal portion of the number and returns only the integral part. But the return type of this method is still a decimal, which has to be cast to an int.

Eventually, the solution turned out to be simpler than anticipated. Since casting to int also discards the decimal portion, the call to Math.Truncate() can be excluded, and the type can be directly cast to an int.

(int)255.999; // Returns 255

Users Don’t Read Error Messages

I’m reminded of the story of the Microsoft usability test in which an error message was displayed saying, “There is a $50 bill taped to the bottom of your chair.” There was. It was still there at the end of the day.

Joel Spolsky

A particular staffer in a customer’s internal IT team was often focused on keeping down incident metrics at all costs. They’d sooner to wipe clean and reinstall an application to get the system back online as quickly as possible, than figure out why it suffered downtime to begin with.

But when one particularly gnarly ticket refused to go away in spite of their numerous attempts at reinstalling, they were forced to reach out to our support team for help. When the ticket eventually bubbled its way up to my desk, my first reaction was to ask for the error log. So it came as a surprise when the customer reported back that there was no log. That was impossible, because it has been our policy since forever to always log errors, and make them easy to access. It was one of those things that we got right early on.

When I remoted into the customer’s screen, this dialog was sitting there in plain sight.

Say what, now?

I proceeded to click the Details button, which showed an in-depth description of the error along with a helpful stack trace. In the end it turned out to be something completely external to the application. I think it was a misconfigured proxy, which had to be doubled right back to the customer’s own IT team to be resolved. The employee could have saved themselves a lot of time and anguish by being a bit more attentive.

Porting a Windows Forms Application to .NET – Part 2

Previously, I described the legacy of the Vertika Player, the bottlenecks in its initial development, some elementary efforts to refactor the code, and a major roadblock that came from the deprecation of the Flash Player.

Once we decided to move to .NET, work began in full earnest. Enthusiasm was running high, and nothing seemed impossible. Don’t mind the old code. It was trash. We’d rewrite everything from scratch! Heck, we were so smart, we could do it twice over. But this fervour lasted all of 15 minutes before we folded up and rolled back all our changes.

I’m exaggerating, of course. We did write a new application shell from scratch, using newer APIs like the Microsoft.Extensions.Hosting.IHost interface and its dependency injection framework for some of the most fundamental classes that were needed. But there was immense pressure to get the product out of the door. Remember that the Flash Player uninstaller was well and truly active now, and support staff were working overtime to keep up with restoring it every time it got nuked. After a couple of weeks of this effort, the enormity of the exercise hit us and we fell back to copying files wholesale from the old code-base. On the brighter side, in spite of rewriting only a small portion of the code, the groundwork had been laid for more significant breakthroughs in the near future.

The singular monolith had been deconstructed into separate projects based on functionality, such as the primary executable, model classes, and the web API host. Over the following months, we added more projects to isolate critical portions of data synchronisation, the network download library (eventually replaced by the Downloader library, written by Behzad Khosravifar) and background services.

A SOAP-y Muddle

There’s a significant chunk of the application functionality that depends on communicating with a remote SOAP service (stop sniggering; this stuff is from 15 years ago). Visual Studio continues to provide tools to automatically generate a SOAP client from a service. But the output does not maintain API compatibility with the client generated with earlier versions of the tool. Among the methods missing from the new client are the synchronous variants of the service calls, which, unfortunately, were a mainstay in the earlier application code.

That’s right. Microsoft used to ship tools that allowed developers to make synchronous network calls.

But this is all gone now. And I had a problem on my hands.

public void ClickHandler(object sender, MouseEvent e)
{
    var users = serviceClient.GetUsers();
}
CS1061	'ServiceClient' does not contain a definition for 'GetUsers' and no accessible extension method 'GetUsers' accepting a first argument of type 'object' could be found (are you missing a using directive or an assembly reference?)

Welp!

Calling asynchronous code from previously written synchronous methods was not going to be easy. Web service calls were tightly integrated in many classes that were still oversized even after aggressive refactoring. Stephen Cleary’s AsyncEx library came to our rescue. Asynchronous method invocations were wrapped inside calls to AsyncContext.Run(), which we liberally abused to build a synchronous abstraction over the TAP interface.

public void ClickHandler(object sender, MouseEvent e)
{
    var users = AsyncContext.Run(serviceClient.GetUsersAsync());
}

This was much better than the alternative of calling Wait() or Result directly on the task. In addition to blocking what could potentially be the UI thread, it would also wrap any exceptions thrown during the invocation into an additional AggregateException. And anybody who’s dealt with InnerException hell knows how bad that can be.

The second API incompatibility was in the collection types returned from the service. The earlier service client returned a System.Data.DataSet type for collections. This was changed to a new type called ArrayOfXElement. Fortunately, this was an easy fix with a simple extension method to convert the ArrayOfXElement into a DataSet.

Wrapping Up and Rolling Out

The hour of reckoning arrived about 5 months later, when we finally removed references to the Flash Player ActiveX control from the project, replacing them with the WebView2 control. The minuscule amount of effort required of this step belies the enormity of its significance. Flash had been our rendering mainstay for over a decade. All those thousands of man-hours invested into the application were gone in a blink of an eye, replaced with a still-wet-behind-the-ears port to Blazor. This was the first time in the history of the company that legacy code was discarded entirely, and rewritten from scratch on an empty slate.

The new product was deployed to several test sites for a month to ensure that everything worked as expected. And other than a few basic layout errors, there were no problems that we encountered. The porting exercise was a success, and offered a major lifeline to the business.

Porting a Windows Forms Application to .NET – Part 1

In a time when every dinky software company is writing massive systems that run in The Cloud™, it’s a source of astonishment for some to hear about my continued work on a desktop application. It’s not quite the same effort as synchronising 5K posts per second with millions of followers. But it does have its high notes nevertheless.

Understanding the Legacy

Like many products from early-stage startups, the first iteration of the Vertika Player was built as a proof-of-concept. The objective was to fetch text, video and images from a web service and play it on a large screen. But the underlying Windows Forms framework wasn’t really geared towards rendering multimedia content. So a separate application was written in ActionScript, which was hosted inside the Windows Forms application through the Flash Player ActiveX control.

Even during those days, web development was topping the list of desired programming skills. Nobody on the original team had significant experience engineering a desktop application. As a result, it came to be that most of the code got piled into the main window class, a couple of static utility classes, and a single god class containing approximately 200 public fields to maintain application state. The latter was referenced hither and yon all over the project. It was awful. The project was so fragile that even changing a simple network port number was fraught with risk of breakages in parts of the application that had nothing to do with networking.

Apparently, none of the programmers understood the significance of the UI thread, or even basic threading in general. Resource contentions were handled with a hail Mary running inside an infinite loop.

while (!result)
{
    try
    {
        xmlDoc.Save(FilePath);
        result = true;
    }
    catch (Exception exception)
    {
        log.Error("An error occurred while saving to disk");
    }
}

There were some hard to replicate bugs that reared their heads at times. Support incidents came up occasionally about missing content, or false alarms were raised when the telemetry and monitoring thread went awry. A particularly egregious bug was the cause for occasional runaway file downloads that never stopped, appending the same sequence of bytes in a file ad infinitum. To the end user, the file would seem to grow continuously until it filled the entire hard disk.

But in spite of all these issues, the application worked as expected more often than it didn’t. It generated steady revenue from paying customers who were mostly pleased with its performance. Support staff had formulated scripts for almost every scenario that they were likely to encounter. They could identify symptoms and were able to get in and resolve the problem quickly.

Ringing in the New

Teams changed, until there was nobody left who had worked on the first iteration of the product.

New features were gradually added over time. But architectural issues continued to fester, while more lines of code were added to already expansive classes. At one time, the main window class exceeded 5,000 lines of code. It wasn’t a pleasant experience maintaining this project. But the one thing that we all agreed upon was that we would never discard the project and start from scratch. We bided our time and waited for an opportunity to refactor the application.

We got our foot in when customers started reporting stutter during playback after minor update. Telemetry reports had to include a screen shot of the running application. But due to data caps at many locations, captured images used to be down-sampled to postage stamp sizes before uploading. Network capacities had recently been improved, and screen shot dimensions were increased accordingly.

Herein lay the catch. A 100 pixel square image is about 2 KB in size. But a 500 pixel square accounts for a 25x increase in data. The code to upload the image was being executed on the UI thread, blocking it while the network request was completing. This was imperceptible when the file sizes were small. But it became immediately noticeable once the images sizes were increased. It was glaringly obvious during playback of video because of periodic frame freeze that occurred while an image was uploaded.

We chose the expeditious fix for the time being. Instead of directly calling the upload method, it was wrapped into a WaitCallback delegate and queued for later execution through ThreadPool.QueueUserWorkItem(). The UI thread was freed up again, solving the playback stutters. But we made dozens of commits in the project while working our way to these few lines of change. This was the chance we were looking for. We had to get the cat out of the bag.

All our early modifications were mechanical and low-risk. Auto-format the code. Reduce the API surface area by hiding unnecessary public members. Remove dead and commented lines of code.

This exercise gave us the exposure to the codebase that was necessary to make further modifications. Further refactoring was driven by some of the SOLID principles. If we found a class that was doing too many things, we’d try to split it into different types with individual responsibilities. Interfaces were scarcely in use so far. So we began to define some new ones, being careful to contain their scope. But dependency inversion wasn’t quite as high on our list of priorities. .NET Framework didn’t ship with built-in DI features, and adding a third-party library for the job would impede our path to upgrade to .NET Core later down the line.

Over time, the quality of the codebase improved, and our knowledge of its internal working increased. There were few changes which were visible to the end user. In general, it was just a little more robust, faster to launch and run, and the UI looked a bit tidier.

The Demise of Flash

Come January 2021, disaster struck. Flash had been on its last legs since a long time. But nobody expected Adobe to pull the rug from under us by forcefully removing the plug-in from all computers. This bricked every deployed instance of the application. Nothing could be displayed on any screen without the Flash Player.

He’s dead, Jim.

We scrambled to find workarounds, such as installing older versions of Flash Player that did not contain the the self-destruct switch, blocking Windows Updates, and even deploying older versions of Windows that shipped with working versions of Flash. These were all temporary solutions. The juggernaut was unstoppable, and we were fielding support calls every week that involved a missing Flash Player. In addition to the technical complexities, Adobe had also revoked the license to install and run the Flash Player. Continuing to use it was exposing the company to legal ramifications that nobody wanted to deal with. We needed an alternative for Flash as soon as possible.

Blazing Guns of Glory

Our salvation came in the form of the recently released Blazor framework from Microsoft. Built on the C# language, it borrowed a lot of concepts from ASP.NET and MVC. A crack team of developers was assembled, with the singular motive of porting the entire Flash application into Blazor. This was a significant diversion from our ground rule to never throw away everything and start from scratch.

Blazor generates a web application that runs in a browser. Embed a browser control into a Windows Forms project, and voila! It works just the same as the Flash application. The only significant difference was the communication protocol across process boundaries. Flash used to expose a TCP sockets API to communicate with the host process. Since web browsers don’t expose any such interface, we had to fall back upon standard HTTP. The hosting process would expose a web API that would be accessible on localhost over the HTTP protocol.

The last item was a significant pivot point. Hosting a HTTP API would require a web server, for which there were limited options that worked within the .NET Framework ecosystem. HttpListener was far too low-level. Cassini wasn’t as tightly integrated. GenHttp came with its own learning curve. In the long-term, it was going to be far better to switch from .NET Framework to .NET 5. And it shipped with its own built-in web server that supported running ASP.NET Web API out of the box.

The writing was on the wall. We would be porting to .NET 5.

Valknut – JSON Serialization

Many service responses are returned as JSON objects. For this, the application must implement some basic configuration and error handling in each method. By utilising the cross-cutting facet of attributes, this code is unified into a common location and applied wherever required.

Signalling

Firstly, the action method that returns JSON has to be marked. The JsonHandlerAttribute class inherits from ActionFilterAttribute. It is applied to the method in question. When the method is invoked, the ASP.NET pipeline executes the OnActionExecuted method on its attribute. The method converts the System.Web.Mvc.JsonResult result into a custom-written class called FitNet.Web.Infrastructure.JsonNetResult. This class inherits from JsonResult and adds some shared functionality.

Error Handling and Serialization

The conversion from JsonResult to JsonNetResult is performed inside the JsonHandlerAttribute class. The Result property of the current filter context is typed into JsonNetResult. If the conversion is successful, then a new instance of JsonNetResult is created and the copies of the current result copied over.

Finally, the ExecuteResult method is invoked on the newly created result instance. This step performs the following error checks.

  1. Deny GET requests by default, unless it is explicitly allowed in the method.
  2. Set the content type of the response to application/json.
  3. Configure the underlying serializer to throw an error in case of looping references.

If all the checks pass, then the response data is serialized and the output is assigned to the Output property of the current response.

This structure of the code removes the need to have all these checks in each method that has to return a JSON response.