Making my own sprite class

Started by
17 comments, last by TheSilverHammer 16 years, 6 months ago
After running into several walls with the directX sprite class, such as rendering a bunch of sprites that have different scales, rotations and positions, I decied to make my own sprite class. While making it, I started thinking about the problem of animated sprites. If I just want one quad for every sprite, then the only way I know of to animate the texture would be to edit each vertex each frame and update the UV coordinates. From other sources, I have heard that editing vertex buffers is a bad thing. I am now considering creating a quad for each sprite. Not each instance of each sprite, just for each frame. For example, lets say I have two sprites. One is a space ship that isn't animated. Another is a spinning asteroid which has 5 frames of animation. I would then create 6 quads. If I wanted to render 100 asteroids, I would just use transfroms to position each asteroid. The amount of quads I would need would be equal to all the 'frames' of animation in total, with unanimated sprites being just one frame. Is this a good way to do it, or should I be editing UV coordinates or is there a better way that is none of the above?
Advertisement
Modifying the vertex buffer is fine. You'll probably be doing that every frame anyway, since the sprites can change position. Just make sure you create the VB as a dynamic VB, otherwise it'll be particularly expensive to lock.

Also, Draw[Indexed]Primitive() calls are particularly expensive, you don't want to be making more than 500 or so calls per frame. Using transforms for each sprite means you have to call Draw[Indexed]Primitive() once per sprite, which will give you horrible performance.

My sprite manager uses a std::vector of structs, one for each sprite and it fills an internal VB and renders from it once per frame.
Ill be using matrix transforms to move the sprites. Ill never need to modify the vertex locations. I am using PositionTextured types.
But that way, you need to call Draw(xxx) for every sprite. You will get performance issues if you don't change this. You should really use a dynamic VB and then update the buffer every frame, it doesn't hurt at all.
But you would need to call Draw(xxx) every time anyway. If I edit my vert UVs, I still need to call Draw to render it. If I just have a different set of verts, I call Draw on them instead.

I could be completly misunderstanding you though, if so, I have no idea what you mean by Draw once vs multiple times.
For my latest version of Doom.Net the technique I used went something like this:

1. Each image is loaded into one giant volume texture, this eliminates the need to to switch textures.

2. Each image stores an array of 4 vertices and 6 indices. The vertices looked something like this:

Vertex V1 = new Vertex( 0, Height, -Width / 2, U1, V1, W );
Vertex V2 = new Vertex( 0, Height, Width / 2, U2, V1, W );
Vertex V3 = new Vertex( 0, 0, -Width / 2, U1, V2, W );
Vertex V4 = new Vertex( 0, 0, Width / 2, U2, V2, W );

This was for 3D billboarding, but something similar will also for 2D.

Edit: Width and Height are the dimensions of the image. That's because in Doom, 1 unit in object space is the size of one pixel. You'll of course need to adjust these to match your game.

3. At the start of each frame a list(vector for C++) of vertices and a list of indices are cleared.

4. When an image is "drawn" its vertices and indices are added to the list. The copies of the vertices in the vertex list then have the necessary transformations done to them through software calculations.

5. When everything is added, every sprite is then rendered with a single DrawIndexedUserPrimitives call.

I'm no GPU guru, but this was the best method I could think of at the time. I never tested it with a dynamic VB/IB, so I don't know if it would be any faster.
There's a million ways to do this but if you want proper speed you MUST use a large dynamic vertex buffer, as has been suggested. It's just not feasible to use a matrix transform for each sprite; you can't afford to be rendering just one sprite per DrawPrimitive. Make a massive dynamic VB and each frame use the next available vertices (locking with NOOVERWRITE) until the VB is full; then start over at the beginning (locking with DISCARD). Since your data is fully dynamic this is really the only choice you have; this allows you to scale, rotate, animation, and reposition sprites however you see fit without regard to frame coherence.

If you can group your sprites by texture and render state you will get even greater speed as you will be able to tell the GPU to render hundreds of sprites at once.
That is similar to what I did, except my list of textures is attached do a corrisponding quad opposed to making a 'list' of quads to render with one draw.

Anyway this whole sprite thing is really frustrating me, it has been 2 weeks and every time I get close to doing one way, something comes up and hoses it all up. Let me go over the history here.

I start off with making my own quads and then notice the Sprite class.
I spend a few days messing with Sprite.Draw2d.
Then I get told I shouldn't use that, and just use my own transforms in lew of Draw2d.

Ok fine...

I then go on and start using Sprite.Draw() and spend a full week on this. I end up doing a bunch of matrix math so I can get a virtual cordinate system, handle aspect ratio and viewport changes. Everything seems great and then...

I try and draw two sprites. They overlap. I can use the Draw() to move them in screen space, which breaks my virtual coordinate system, but I absolutly can not rotate or scale them. The last matrix used affects all sprites. I ask around and am told that I just can't do that without a bunch of Begin / Ends, which is a bad thing.

Now I am hosed, again... I go back to doing my own quads and sprite class just so I can be done with this whole Sprite class mess.

So I had some other issues, like textures dissapearing when my viewport changed size and I couldn't make my textures transparent. I figured that all out, but in the course of all that I get several people telling me (not all from this thread):

I really should use the sprite class. Try Draw2d... like I was doing before? Don't do a bunch of DrawPrimitives either, its slow. Don't edit vertex buffers, its slow. WTF?!

I just want to cry now. 2 weeks and not even a basic sprite system and I am running in circles. I keep getting conflicting information.

Is there anyone, who can difinitavly give me a 'best practices' on how I should be rendering 2D sprites? Id like to move on to other programming issues and stop with the sprite business.
Quote:
Don't do a bunch of DrawPrimitives either, its slow. Don't edit vertex buffers, its slow. WTF?!

I just want to cry now. 2 weeks and not even a basic sprite system and I am running in circles.


I would consider frustration and lack of information to be the "normal" state of an average programmer. Which is why you keep searching, keep learning and keep trying until you are good enough. And then your next problem is a lot more difficult than the last so the cycle repeats.

Now, about your sprite problem. I might be mistaken, but ID3DXSprite::SetTransform should affect individual sprites. Meaning, you can call SetTransform once, render 3 sprites all with that transform, then call SetTransform again, render 5 more sprites with that transform, etc.

Because you are allowed to specify screen coordinates in the call, you should probably use SetTransform only for rotation (which may include translation if center is off-center), or scaling. You can also animate through changing texture coordinates of the RECT that you pass to ID3DXSprite::Draw.

Perhaps you can give more info on how you set up your rendering (why you are having problems with SetTransform?) It looks like you should be able to make ID3DXSprite work for you, but instead you are trying to roll your own, which is by far not a simple task. For "general" case scenarios, you probably can't create something that is better than ID3DXSprite.
Quote:Original post by MasterWorks
There's a million ways to do this but if you want proper speed you MUST use a large dynamic vertex buffer, as has been suggested. It's just not feasible to use a matrix transform for each sprite; you can't afford to be rendering just one sprite per DrawPrimitive. Make a massive dynamic VB and each frame use the next available vertices (locking with NOOVERWRITE) until the VB is full; then start over at the beginning (locking with DISCARD). Since your data is fully dynamic this is really the only choice you have; this allows you to scale, rotate, animation, and reposition sprites however you see fit without regard to frame coherence.

If you can group your sprites by texture and render state you will get even greater speed as you will be able to tell the GPU to render hundreds of sprites at once.


I just re-read what you wrote and read some manuals on this. I have a few questions on implementation.

So I have a giant VB, maybe one that has 250 'quads', worth of sprites, 1000 verts. I have a list of 600 sprites to draw. I then follow these steps:

1. The very first run (per frame) I use Lock.None.
2. I loop through my sprite list, and edit the verts in the VB until I have done all 1000, or 250 quads worth.
3. Unlock the buffer, issue the draw command.
4. Lock with Lock.discard and go to step 2 until I am done with my 600 sprites.

Is that essentially it?

There is another thing you said, I am confused about. You said I can't afford a matrix transform per sprite. The only way I know how to rotate a point (or 4 of them) is to multiply them against a matrix. In fact, I would think that for each quad, you would have to do for each vert, Vector3() * RotationMatrix * ScaleMatrix * TranslationMatrix.

If not, how am I suppose to set up each sprite? Also since I will be doing this stuff manually, should I switch from PositionTextured to TransformedTexturered verts?

This topic is closed to new replies.

Advertisement