Skip navigation

Category Archives: software development

My wife bas been pestering me to write a post or two about some of my early years at Microsoft. Thinking about what to write took me back to a time when my entire focus at work was writing the best code possible. In those days, I would even sometimes dream about code. This post talks about software implementation details and may leave some readers behind in a few spots. Bear with me; future posts will be less technical.

For many years, one of my main pursuits as a developer was software-based graphics acceleration. This meant using the CPU to render graphics on the screen as fast as possible by carefully tuning software and algorithms. The goal was to extract every last bit performance from the CPU. One of the key reasons I joined Microsoft in 1993 was for the opportunity to work for Mike Abrash who even then was renowned for mastery of CPU-based performance optimization, and he was aware of some of my work. He was running the Windows NT GDI (Graphics Device Interface) team at the time, and I had come on board to focus on optimizing GDI performance in the first version of Windows NT (somewhat confusingly named “Windows NT 3.1”).

GDI was a 2D graphics framework doing the important work of rendering fonts, lines, rectangles, and all of the primitives that rendered the Windows desktop UI and programs written for it. But being strictly 2D-based, GDI was never going to include 3D graphics, and 3D was as fascinating to me as VR is to some people today (including Mike, who is now Oculus’ chief scientist).

One of NT’s goals was to take a share of the workstation market, and Microsoft had licensed the OpenGL graphics API from Silicon Graphics (SGI). SGI’s business was based on selling expensive high-end hardware, and they had put little effort into the performance of their reference software implementation of the API. Without very expensive hardware, OpenGL was pretty useless.

I had fallen in love with 3D graphics in the late 80’s, and I joined the recently-formed OpenGL team soon after Windows NT shipped. Even though speeding up OpenGL wasn’t part of my job when I joined the team, I was excited to be part of effort that would broaden access to 3D graphics capabilities. My dream was to have 3D graphics be standard on every PC. My job was to help integrate OpenGL into the Windows operating system.

But official responsibilities aside, I couldn’t resist the temptation and challenge of speeding up OpenGL to make it useful without requiring very expensive hardware. I immediately started tinkering with the OpenGL stack when I had free time. This was the mid-90’s – the era of Intel’s new Pentium processor line. This family of CPUs allowed overlapping of floating-point and integer instructions – a primitive form of parallel processing. A floating-point instruction could be started, and then integer instructions could continue to be executed while the much lengthier floating point command was processed. This mixing of floating-point and integer operations was perfect for speeding up 3D rendering operations which could be broken into floating-point setup and fixed-point, integer-based scanline fragment processing.

A complicating factor in speeding up OpenGL was the complexity of its state machine. There were many possible combinations of rendering modes based on attributes such as color depth, z-buffering, shading model, transparency, texture-mapping, etc. An early (Windows 3.1 or Windows 95) solution for optimizing GDI rendering was to build the rasterizer on the fly on the stack based on the GDI state (I believe this was all or mostly Todd Laney’s handiwork). But I was working on Windows NT, and such clever hackery was not allowed in a next-generation operating system. After considering my options, I determined that my best bet was to pre-compile a set of renderers that represented the most common cases for rendering (for example, Gouraud-shaded, 16-bit color, 16-bit z-buffered triangles). I did this by building rendering functions that consisted of groupings of macro statements that themselves were chunks of hand-crafted inline assembly code. These macros could then be grouped together to perform unique sets of rendering operations. In order for this approach to work, I constructed a framework for the chunks of assembly code in the macro blocks to be able access variables and registers in a common and consistent way so that they could interoperate predictably and efficiently.

This was definitely not how the C language was intended to be used. It wasn’t pretty – I believe I even used “goto” statements out of necessity – but the code was highly effective. The approach also entailed risk because things could go horribly wrong with unanticipated edge cases. I remember one embarrassing bug that in certain cases failed to return the floating-point control register to its previous state which effected floating-point operations in the rest of the operating system. I quickly found and fixed the issue but it was a reminder that there was no safety net with what I was doing.

It’s interesting how thinking about past work jogs the mind. In the middle of writing this post, I had a dream about how I may have implemented dithering. Dithering is an old technique going back to the print business that allows gradations in tone and color to appear smoother by breaking up transitions using patterns of dots or pixels at different densities. For example, an area halfway between one color and another would have half the pixels in that area set to one color, and the remaining half set to the other color. Today, even our phones are capable of producing millions of colors eliminating the need for such techniques, but in the mid-90’s, the capabilities of most PCs were far more limited. In my dream, I implemented dithering look-up-tables (LUTs) for red, green, and blue values so that I could construct the right 15 or 16-bit RGB value using three highly-cached indexed memory operations and two OR instructions. I probably did something along those lines, but who knows…I’d love to have access to that old code (of no use to anyone at this point) just to remind myself how I did what I did.

My initial performance improvements were compelling enough to justify making the software acceleration work a full-time endeavor. I added functionality over time culminating with real-time texture-mapping using quadratic subdivision. Even though it was still part of a fully compliant general-purpose OpenGL implementation, textured rendering throughput got reasonably close to that of Doom, the texture-mapping speed champion at the time. In fact, my software-accelerated pipeline became competitive with high-end hardware graphics cards and beat them in some instances (anti-aliased lines comes to mind).

I had taken rendering performance from being measured in seconds per frame to dozens of frames per second; many operations were over 100 times faster than the original versions. Ah, the power of highly-tuned, efficient coding! I often worry that Moore’s Law is being buried under so many layers of frameworks, objects, interpreters, and interfaces that it’s hard to tell what the hardware is actually doing at the bottom of the pile multiple layers of indirection. Then again, even CPUs now hide some of their internal operation with sophisticated out-of-order instruction execution engines. Sadly, the last time I tried to out-optimize a modern C compiler with an integer-only routine, the results were a draw.

Pipes screen shotmaze2Our OpenGL implementation was incorporated into both Windows NT and Windows 9x code bases and was starting to get significant traction. Being part of the operating system and eliminating the need for hardware acceleration meant that an OpenGL application could now target a very wide audience. As a means of promoting our OpenGL’s capabilities, I wrote the first set of 3D screen savers (“Flying Objects”) which proved popular and inspired other people on the team to write their own as well (such as “Pipes” and “Maze” pictured here). For many years afterward, I would get a chuckle when I saw one of our screen savers running in the background of a TV or movie set. Our efforts had taken what had been an expensive workstation technology and made it readily available on millions of desktops.

Despite the effort’s success, the writing was already on the wall for the future of software-accelerated computer graphics. Hardware acceleration and the rise of the GPU were just around the corner.

Advertisements

When people attempt to build complex software solutions using a mix of vendors, I call it vendor soup.

A recent example is healthcare.gov. That particular vendor soup was rich with delays, chunky with cost overruns, seasoned with vague requirements, and thickened with a lack of leadership and accountability. The GAO report on the problems with healthcare.gov is a great case study. The soup got made, but was it tasty?

Vendor soupThe problem with vendor soup isn’t necessarily the vendors themselves. There are plenty of technology solutions and services that typically don’t make sense to implement from scratch: large-scale cloud computing infrastructures, billing systems, content delivery networks, source code management systems, software development tools, databases, and content management systems are just some examples. Vendors also provide expertise needed on an infrequent basis that may be inefficient to build in-house. I’ve used vendors in the past to help implement hardware prototype designs.

Even companies like Apple, Google, Amazon, and Microsoft use technology vendors because it makes economic and strategic sense; they don’t re-invent the wheel, don’t spend more than they have to, and focus on the things that are core to their businesses. But these companies still have armies of highly skilled software engineers and technical leadership to architect and direct the use of external technology vendors.

Quality software is hard to make. It requires a robust and well-defined architecture, a clear product vision and leadership, well documented code and APIs, consistently high engineering quality, common coding conventions and design principles, friction free communication between functional groups, an efficient and repeatable process for continuous product improvement, and relentless focus on the consumer experience.

Was even a single one of these attributes present with healthcare.gov?

No matter how competent the vendors may be, you can’t create an accountable, coherent, and high-functioning software development organization with vendor soup. Vendor soup is a tempting shortcut that looks great on a PowerPoint presentation to management. But who’s in charge when the countless things that can go wrong so easily in the software world…unstable apps, web sites that crash, user accounts that disappear, confusing user interfaces, links to nowhere, transactions that never go through…do go wrong?

If you’ve managed to cobble together a consumer-facing product relying primarily on vendors, you may be asking yourself: Did I make vendor soup? Obviously, the first hint will be that you have a lot of vendors doing things for you. But that alone does not make vendor soup. You need to ask yourself a few more questions. Does my product scale for future growth? Can I deploy new features and functionality quickly and easily? Are my costs under control? Do I have an efficient, well-defined architecture? Am I generating and protecting relevant intellectual property? Is my product constantly improving? Is the code elegant, robust, maintainable, well documented, well tested, and consistent? Do I have clear accountability? Is my product uniquely differentiated and superior to competitive solutions?

Now, be honest. If you find yourself saying “no” to more than a few of those questions, you have vendor soup. Don’t panic, it’s not the end of the world – you’ve gotten this far and nothing terrible has happened. They’ve fixed healthcare.gov (with help from former Microsoft colleague, Kurt DelBene). And there’s the possibility that for what you need, vendor soup may actually be OK. But at the very least, if you can recognize what you have, you won’t be surprised when vendor soup fails to meet your needs in the future. And if your long-term goals exceed vendor soup limitations, you can start planning a different approach.