There are many reasons for differences between platforms, and several ways an
API designer can try to rationalize such things.
- Some
features fundamentally don't exist, and are not practical to emulate.
Earlier Radeon graphics cards do not support TextureAddressMode.Border,
and there's nothing we can do to change that.
- Other
features could exist, but are too expensive to implement. Sure, we
could have ported XACT to Zune, but we would have had to cut other
features to find time. Would you be willing to trade XACT on Zune for
Avatars on Xbox? No? I suspected as much :-)
- Sometimes
things are blocked for business policy rather than technical reasons.
Personally, I would love to someday allow full LIVE support for Windows
games, but I also understand why the powers that be didn't want that.
Pragmatism requires us to figure out the most consistent story possible
within the constraints we are given.
So, there will
always be some differences across platforms. What should we do about
them?
- We could say "hey, this is not consistent, so
let's just cut it entirely from all platforms"
- We
could remove the API only from the platforms that do not support it
- We could leave the API in place, but have it throw an exception
- We could expose caps bits to query if the API is supported
- 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:
- 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.
- 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.
- 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.
- 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.
- 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.