40
\$\begingroup\$

To be more precise, if I needed to recreate this functionality from scratch in another API (e.g. in OpenGL) what would it need to be capable of doing?

I do have a general idea of some of the steps, such as how it prepares an orthographic projection matrix and creates a quad for each draw call.

I'm not too familiar, however, with the batching process itself. Are all quads stored in the same vertex buffer? Does it need an index buffer? How are different textures handled?

If possible I'd be grateful if you could guide me through the process from when SpriteBatch.Begin() is called until SpriteBatch.End(), at least when using the default Deferred mode.

\$\endgroup\$
6
  • 1
    \$\begingroup\$ Have a look at how it is implemented in MonoGame via OpenGL: github.com/mono/MonoGame/blob/master/MonoGame.Framework/… \$\endgroup\$
    – Den
    Commented Dec 17, 2011 at 16:11
  • \$\begingroup\$ Did you try to use DotPeek or Reflector on Microsoft.Xna.Graphics (I am not suggesting to do anything illegal though :)? \$\endgroup\$
    – Den
    Commented Dec 17, 2011 at 16:13
  • \$\begingroup\$ Thanks for the link. And that thought did cross me (I even have dotPeek here at hand), but I figured it might be a better idea to ask for a general description from someone who might have already done that before and knows the procedure by memory. That way the answer would be preserved here and available to other people. In the case noone provides such a description, I'll do the analysis myself and post an answer to my own question, but I'll wait a bit more for now. \$\endgroup\$ Commented Dec 17, 2011 at 16:25
  • \$\begingroup\$ Yeah, that's the reason I used a comment. I think Andrew Russell might be a good person to answer this question. He is active in this community and is working on ExEn which is an alternative to MonoGame: andrewrussell.net/exen. \$\endgroup\$
    – Den
    Commented Dec 17, 2011 at 22:19
  • \$\begingroup\$ Yeah, this is the sort of question that Andrew Russel (or Shawn Hargreaves back at the XNA forum) would usually be able provide a lot of insight into. \$\endgroup\$ Commented Dec 17, 2011 at 22:38

2 Answers 2

43
\$\begingroup\$

I have sort of replicated the behaviour of SpriteBatch in deferred mode for a cross-platform engine I'm working on, so here are the steps I have reverse engineered so far:

  1. SpriteBatch constructor: creates a DynamicIndexBuffer, DynamicVertexBuffer and array of VertexPositionColorTexture of fixed size (in this case, the maximum batch size - 2048 for sprites and 8192 for vertices).

    • The index buffer is filled with the vertex indices of the quads that will be drawn (0-1-2, 0-2-3, 4-5-6, 4-6-7 and so on).
    • An internal array of SpriteInfo structs is created, too. This will store temporal sprite settings to be used when batching.
  2. SpriteBatch.Begin: internally stores the values of BlendState, SamplerState, etc. specified and checks if it has been called twice without a SpriteBatch.End in between.

  3. SpriteBatch.Draw: takes all the sprite info (texture, position, color) and copies it to a SpriteInfo. If the max batch size is reached, the entire batch is drawn to make room for new sprites.

    • SpriteBatch.DrawString just issues a Draw for each character of the string, taking into account kerning and spacing.
  4. SpriteBatch.End: does the following operations:

    • Sets the render states specified in Begin.
    • Creates the orthographic projection matrix.
    • Applies the SpriteBatch shader.
    • Binds the DynamicVertexBuffer and DynamicIndexBuffer.
    • Performs the following batching operation:
        startingOffset = 0;
        currentTexture, oldTexture = null;
    
        // Iterate through all sprites
        foreach SpriteInfo in SpriteBuffer
        {
            // Store sprite index and texture
            spriteIndex = SpriteBuffer.IndexOf(SpriteInfo);
            currentTexture = SpriteInfo.Texture;
    
            // Issue draw call if batch count > 0 and there is a texture change
            if (currentTexture != oldTexture)
            {
                if (spriteIndex > startingOffset)
                {
                    RenderBatch(oldTexture, SpriteBuffer, startingOffset,
                                spriteIndex - startingOffset);
                }
                startingOffset = spriteIndex;
                oldTexture = currentTexture;
            }
        }
    
        // Draw remaining batch and clear the sprite data
        RenderBatch(currentTexture, SpriteBuffer, startingOffset,
                    SpriteBuffer.Count - startingOffset);
        SpriteBuffer.Clear();
    
  5. SpriteBatch.RenderBatch: executes the following operations for each of the SpriteInfo in the batch:

    • Takes the position of the sprite and calculates the final position of the four vertices according to the origin and size. Applies existing rotation.
    • Calculates the UV coordinates and applies specified SpriteEffects to them.
    • Copies the sprite color.
    • These values are then stored in the array of VertexPositionColorTexture elements previously created. When all sprites have been calculated, SetData is called on the DynamicVertexBuffer and a DrawIndexedPrimitives call is issued.
    • The vertex shader only performs a tranform operation, and the pixel shader applies the tinting on the color fetched from the texture.
\$\endgroup\$
8
  • \$\begingroup\$ That's exactly what I needed, thanks a lot! Took me a bit to absorb all of that, but I think I finally got all the details. One that caught my attention and I wasn't entirely aware of before is that even in deferred mode, if I can somehow sort my SpriteBatch.Draw calls by Texture (i.e. if I don't care about the order of these calls) it will reduce the number of RenderBatch operations, and probably speed up the rendering. \$\endgroup\$ Commented Dec 18, 2011 at 3:29
  • \$\begingroup\$ By the way, I can't seem to find in the documentation - what's the limit of Draw calls you can make before the buffer gets full? And does calling DrawText fill that buffer by the entire amount of characters in the string? \$\endgroup\$ Commented Dec 18, 2011 at 3:32
  • \$\begingroup\$ Impressive! How will your engine compare to ExEn and MonoGame? Will it require MonoTouch and MonoAndroid as well? Thanks. \$\endgroup\$
    – Den
    Commented Dec 18, 2011 at 12:01
  • \$\begingroup\$ @DavidGouveia: 2048 sprites is the max batch size - edited the answer and added it for convenience. Yes, sorting by texture and letting the z-buffer take care of sprite ordering would use the minimum draw calls. If you need it, try implementing SpriteSortMode.Texture to draw in whatever order you want and let the SpriteBatch do the sorting. \$\endgroup\$
    – r2d2rigo
    Commented Dec 18, 2011 at 12:25
  • 1
    \$\begingroup\$ @Den: ExEn and MonoGame are lower-level APIs that allow XNA code run in non-Windows platform. The project I'm working on is a component-based engine purely written in C#, but we need a very thin layer to provide access to system APIs (that's where I'm working on). We just chose to reimplement SpriteBatch for convenience. And yes, it requires MonoTouch/MonoDroid since it's all C#! \$\endgroup\$
    – r2d2rigo
    Commented Dec 18, 2011 at 12:29
13
\$\begingroup\$

Just want to point out that the XNA SpriteBatch code has been ported to DirectX and released as OSS by a previous member of the XNA team. So if you want to see exactly how it works in XNA, the latest version of the XNA SpriteBatch as native C++ can be found on this GitHub repo: .h & .cpp

A DirectX12 version is also available: .h & .cpp

\$\endgroup\$
2
  • \$\begingroup\$ +1 for "code I'll never use but is interesting to skim over" \$\endgroup\$
    – Kyle Baran
    Commented Jun 20, 2014 at 7:48
  • 1
    \$\begingroup\$ The latest version of the XNA SpriteBatch as native C++ can be found on GitHub h / cpp. As a bonus, a DirectX12 version is also available h / cpp \$\endgroup\$ Commented Sep 12, 2019 at 19:14

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .