Some platforms are more equal than others

Originally posted to Shawn Hargreaves Blog on MSDN, Monday, March 1, 2010

There are many reasons for differences between platforms, and several ways an API designer can try to rationalize such things.

So, there will always be some differences across platforms. What should we do about them?

  1. We could say "hey, this is not consistent, so let's just cut it entirely from all platforms"
  2. We could remove the API only from the platforms that do not support it
  3. We could leave the API in place, but have it throw an exception
  4. We could expose caps bits to query if the API is supported
  5. We could make the API a graceful no-op, ignoring calls and returning default values

XNA Game Studio currently uses a mishmash of all five techniques, chosen for individual features on an ad-hoc basis. This is an area I think we have much room to improve.

The ultimate goal can be summed up in two words: "avoid surprises".

A surprise occurs when a friend tries to play your game, but it renders incorrectly because his GPU does not support TextureAddressMode.Border, and you didn't know you were supposed to check the caps for that. Or when you try to port your game from Windows to Zune, only to realize you must rewrite your sound code because Zune does not support XACT.

The XNA Framework provides a set of features that are available and work the same way on all platforms, surrounded by a smaller set of features that are not so consistent. Trouble is, there is no good way to tell which features are which! It is too easy to accidentally stumble outside the consistent zone.

Let's look again at my list of five techniques, this time with the goal of avoiding surprise:

  1. Cutting a feature certainly does avoid surprise, but also leaves us with a lowest common denominator platform. Sometimes this can be the right thing to do, but it's not a good choice for important features that many people want to use.

  2. Removing APIs from specific platforms avoids runtime surprise, but creates a different kind of surprise if overused ("huh? This built fine for Windows, but now I get a ton of compile errors on Xbox"). I think this is best done at a very coarse level, where entire namespaces or assemblies are only available on some platforms. If individual classes or methods change per platform, it gets too hard to document and remember all the subtle differences.

  3. APIs that throw runtime exceptions are pretty bad. The exception can provide a nice error message, but you only get to see that message if you happen to test on a machine where the feature is not supported.

  4. Caps bits are even worse than exceptions. You still have to test on many machines, plus you have to figure out the cause of any compatibility problems, which isn't always obvious even after you notice them.

  5. No-op can be a good choice, depending on how the API is used. When the Zune KeyboardState or Windows PictureCollection come back empty, that is a legal value even on platforms where these things are supported, so the caller should not be surprised and will not need special code to handle this case. But if OcclusionQuery always returned 0, that would surprise me, since I'm most likely planning on using the return value for something and am counting on it being correct!

Verdict: #1 and #5 are appropriate for some APIs.  #2 is good at a coarse level, but should not be too fine grained.  #3 and #4 should be avoided.

Blog index   -   Back to my homepage