SummaryThis article explores the inner workings of sprite drawing in DirectX 8. Using a low level approach, programmers can gain more control over their rendering code and address the shortcomings of any DirectX blitter. With DirectDraw deprecated from DirectX 8, programmers are now forced to use 3D techniques to draw 2D graphics (or resort to using DirectDraw 7). Instead of "blitting a sprite", they must now "draw a textured quad". Of course, with wrapper functions there is no difference from an API point of view and this is what we'll be developing here: a set of routines to draw sprites in a 3D environment. What about ID3DXSprite?Microsoft provided the ID3DXSprite interface to simplify the task of "drawing a textured quad". So why are we reinventing the wheel? Because this wheel has a bug in it. Sprites drawn with ID3DXSprite are not the same size than their original bitmap. This problem becomes glaringly obvious when you try to tile your bitmaps: tiles will not line up properly if they are bigger or smaller than their target rectangles. Robert Dunlop (X-Zone) suggests that you expand your target rectangle "by 0.5 on all sides to allow proper mapping of texels to pixels." This will "compensate for the texel alignment rules of Direct3D" that are causing this problem [2, 3] But it doesn't work. Here's an example to illustrate. Compare figures 2, 3, and 4 with the original image (figure 1). Note the size differences and inaccuracies.
Figure 2 is smaller than the original. Figures 3 and 4 have noticeable inaccuracies at the top and left sides (and they are bigger). ID3DXSprite images are antialiased by default, hence the blurriness in figure 4. (I have no idea how to turn this off.) Interestingly, there is a very simple solution to this problem: extend the right and bottom sides of your target rectangle by 1-pixel. Figure 4 is the only accurate reproduction of the original. But why does it work? It all has to do with a simple documentation error. The edges of figure 2 appear intact but it's definitely smaller. Figures 3 and 4 have noticeable inaccuracies at the top and left sides. ID3DXSprites are antialiased by default, hence the blurriness in figure 4. (I have no idea how to turn this off.) Interestingly, there's a very simple solution to this problem: extend the right and bottom sides of your sprite by 1 pixel. Figure 4 is the only accurate reproduction of the original. But why does it work? A Microsoft BugThere's a pervasive bug throughout Windows and DirectX regarding rectangles. The pixel coordinates of a rectangle are (x, y) to (x + width - 1, y + height - 1) That "-1" part is really important. You can see this when you plot it out on a grid. (See A Simple Proof, next section.) But in Windows and DirectX, this rectangle is defined as (x, y) to (x + width, y + height) This bug appears throughout Windows and DirectX. Try it with GetWindowRect and CopyRects (from Windows and DirectX respectively). Maybe there's a logical explanation for it. Maybe they aren't referring to "pixel coordinates". The API documentation doesn't explicitly say this (but most people would interpret it that way). Nevertheless, it does explain why extending the right and bottom of a sprite by 1 fixes the problem. Bad DocumentationThe DirectX 8 documentation for rectangles is wrong. You can find this page under DirectX Graphics:
According to the documentation, the coordinates (right, bottom) refer to the bottom-right pixel of the rectangle. This is wrong. The coordinates (right, bottom) are actually 1-pixel outside the rectangle. The documentation describes an inclusive-inclusive coordinate system. But DirectX uses inclusive-exclusive coordinates; the last pixel (right, bottom) is not part of the rectangle. To most people, inclusive-inclusive coordinates make more sense. But inclusive-exclusive coordinates are easier to work with and most APIs favour them. For example, when calculating the width (and height): width = right - left; // inclusive-exclusive coordinates width = right - left + 1; // inclusive-inclusive coordinates The use of inclusive-exclusive coordinates also explains why extending the right and bottom by 1 fixes the problem. |