Monday, February 17, 2014

How Blink has affected WebGL, Part 2

In a previous post I detailed some of the ways that the migration to Blink has affected the WebGL pipeline. The short version is that we were able to remove some layers of abstraction without changing the code which was executed, which cleaned up our dependency diagram a bit. At the time we ended up with a data flow that looked roughly like this:

That was 8 months ago, and I've been busy in the mean time! So let's take a quick peek at the current state of affairs.

After doing the initial cleanup of the code immediately after the switch to Blink, it quickly became apparent that the GraphicsContext3D class was largely unnecessary. As I explained in the first post, it's purpose was to provide an abstract interface for all of the various WebKit ports to communicate to the graphics hardware, with each port implementing it in whatever way worked best for them. We were able to consolidate the abstract interface and Chrome's implementation in the Blink code, but this left us with a lot of function calls that effectively looked like this:
GraphicsContext3D::clear(GC3Duint flags) {  m_webGraphicsContext3D->clear(flags);}
(That's not actual Blink code, but it communicates what was happening pretty clearly.)

There were a couple of other minor functions that this class provided, but the majority of it was simple passthroughs like this, and as such wasn't strictly needed. In spite of this, most of the Blink codebase was using this class as it's primary means of making hardware accelerated drawing calls. As I'm sure it would most developers, having such an obviously unnecessary layer in the code rubbed me the wrong way and so I set out to do something about it.

This ended up being a bigger task than anticipated.

You can check out the Chrome ticket where I tracked all the progress on this task to get an idea of what was involved and how many code changes were necessary to finally kill off this vestigial abstraction layer. The short version is it took a little over two months and a good deal of back and forth with the code to bang everything into shape. The end result was worth it, though, and yielded some surprising side benefits.

(Warning! Nitty-Gritty browser implementation jargon ahead!)

One of the first things that I had to contend with was that the GraphicsContext3D class (henceforth referred to as GC3D, because I'm lazy) hosted the graphics enums that were used throughout the code. Things like GC3Dint, GC3Dfloat, etc., which were all re-declarations of the equivalent GL types because WebKit didn't want to force a specific version of the OpenGL headers on implementors. In Blink however, we were fine with including the GL(ES) headers directly, and so were able to convert all of the GC3D enum types over to their standard GL equivalents throughout the code, which in my opinion made things much more readable.

I did have to resolve some conflicts with other parts of the code that included their own GL type declarations (NaCl and Skia were the big ones) in order to start including the Khronos headers, however. Specifically, I needed to make sure that 64-bit types were declared consistently throughout the code base.

After I got the entire code base using the standard GL types, the next step was to go through the code class by class and convert each to start storing and calling the WebGraphicsContext3D (Chrome's underlying 3D context implementation, henceforth WGC3D) directly. This was pretty mechanical, and didn't present too many problems.

As that conversion was being done, however, I realized that another class, the Extensions3D object, was in a very similar position of simply passing along function calls to the actual implementation (which was also the WGC3D in this case). Since it wasn't used by too many other classes we were able to eliminate it completely without too much fuss.

Next, I had to update any code that was acquiring a GC3D and make it request a WGC3D instead. This proved tricky due to differences in the memory tracking models used for each class. The GC3D class used Blink's RefPtr to track how many classes held references to it at any given time, and would automatically delete the object of that number ever reached zero. This is convenient, but it requires that the class being tracked inherits from the RefCounted class. The WGC3D class, on the other hand, is simply an interface which Chrome implements, maintaining a separation between the Blink and Chrome code. Since we can't require that Chrome provides us with a RefCounted object, I had to manage lifetime of the WGC3D objects much more explicitly. This was doable, but required some careful consideration of who "owned" the object at any given time.

Another happy side effect at this point is that I was able to get rid of a helper class called SharedGraphicsContext3D at this point, because we realized that the object tracking it provided was no longer necessary in this new GC3D-less world. Bonus!

Finally, with everyone calling WGC3D methods directly now the GC3D was left with only a few, largely static functions left dealing with generic image conversion and some extension management helpers. These I was able to break out into separate classes (WebGLImageConversion and Extensions3DUtil), and drop the GC3D class entirely! Poof! One layer of abstraction gone!

As a final bonus, while factoring out the Extensions3DUtil class I realized we were repeatedly checking to see if some extensions were enabled that Chrome guarantees to be available at all times, which made those checks unnecessary in Blink! Yay for small optimizations! 

So after all that, we now have a data flow that looks like this:

When put that way it's not actually a massive change, but the difference in code clarity is significant. As with the previous refactors, the goal here was not to improve performance (although it's probably a tiny bit better as a result) but to decrease unnecessary layers of code. By removing abstractions that didn't apply to the Blink codebase we make it easier for developers to work on new features, track problems, and find relevant code snippets. That, in turn, translates into faster turnaround time on the fixes and features that you as developers want! Everyone wins!