Tutorial 06: Drawing Your First 2D Shape

CNA Tutorial Series  ·  Beginner

SpriteBatch Introduction

SpriteBatch is CNA's primary 2D drawing interface. It batches multiple sprite draw calls into as few GPU draw calls as possible, making it efficient for drawing many sprites per frame.

The workflow for every 2D rendering frame is:

  1. Clear the back buffer with device.Clear(color)
  2. Call spriteBatch_->Begin()
  3. Issue as many spriteBatch_->Draw(...) calls as needed
  4. Call spriteBatch_->End() — this flushes the batch to the GPU
  5. Call device.Present() to swap the back buffer to the screen

CNA's SpriteBatch matches the XNA 4.0 API. If you have used MonoGame or XNA before, this will feel familiar.

GraphicsDevice and GraphicsDeviceManager

GraphicsDeviceManager is a component you create in the constructor. It owns the window and the GraphicsDevice. You access the graphics device via getGraphicsDeviceProperty():

// In Draw():
auto& device = getGraphicsDeviceProperty();  // returns GraphicsDevice&
device.Clear(Color::CornflowerBlue);          // clear to a color
device.Present();                              // swap back buffer

The GraphicsDevice also gives you the current viewport dimensions, which you will use to center objects on screen:

auto& vp = getGraphicsDeviceProperty().getViewport();
int screenWidth  = vp.getWidth();   // e.g. 800
int screenHeight = vp.getHeight();  // e.g. 600

The 1×1 White Texture Trick

XNA does not have a primitive rectangle drawing function. The standard approach is to create a 1×1 white Texture2D and stretch it to any size. Multiplying by a color tints the white pixels to that color.

// Create a 1x1 white texture
pixel_ = std::make_unique<Texture2D>(getGraphicsDeviceProperty(), 1, 1);
Color white = Color::White;   // RGBA (255,255,255,255)
pixel_->SetData(&white, 1);  // upload one pixel to the GPU

This is the exact same technique used in XNA and MonoGame games. The texture costs essentially nothing: 4 bytes of VRAM.

Drawing Colored Rectangles

Once you have the 1×1 pixel texture, draw a filled rectangle by passing a Rectangle as the destination:

spriteBatch_->Begin();

// Draw a red rectangle at (50, 50), 200 pixels wide, 100 pixels tall
spriteBatch_->Draw(
    *pixel_,                        // Texture2D&
    Rectangle(50, 50, 200, 100),    // destination rectangle
    Color::Red                      // tint color
);

// Draw a semi-transparent blue rectangle
spriteBatch_->Draw(
    *pixel_,
    Rectangle(100, 200, 150, 80),
    Color(0, 0, 255, 128)           // R=0,G=0,B=255,A=128 (50% alpha)
);

spriteBatch_->End();

The Rectangle struct holds X, Y, Width, Height as integers. X and Y are the top-left corner in screen pixels, with (0,0) at the top-left of the window.

SpriteBatch Begin and End

Begin() and End() bracket a batch of draw calls. You must call them in pairs:

// Default Begin — deferred mode, sorted by texture
spriteBatch_->Begin();

// Immediate mode — each Draw flushes immediately (slower, but predictable)
spriteBatch_->Begin(SpriteSortMode::Immediate);

// Back-to-front depth sorting
spriteBatch_->Begin(SpriteSortMode::BackToFront);

// Additive blending — good for particles and lights
spriteBatch_->Begin(SpriteSortMode::Deferred, BlendState::Additive);

For now, the default parameterless Begin() is sufficient. We will cover blend states and sorting in a later tutorial.

Complete example

Here is a game that draws several colored rectangles demonstrating size, position, and color:

#include <memory>
#include "Microsoft/Xna/Framework/Game.hpp"
#include "Microsoft/Xna/Framework/Color.hpp"
#include "Microsoft/Xna/Framework/Graphics/GraphicsDeviceManager.hpp"
#include "Microsoft/Xna/Framework/Graphics/SpriteBatch.hpp"
#include "Microsoft/Xna/Framework/Graphics/Texture2D.hpp"
#include "Microsoft/Xna/Framework/Rectangle.hpp"
#include "Microsoft/Xna/Framework/Input/Keyboard.hpp"

using namespace Microsoft::Xna::Framework;
using namespace Microsoft::Xna::Framework::Graphics;
using namespace Microsoft::Xna::Framework::Input;

class ShapeDemo final : public Game {
public:
    ShapeDemo() : graphics_(this) {
        graphics_.setPreferredBackBufferWidth(800);
        graphics_.setPreferredBackBufferHeight(600);
    }

protected:
    void LoadContent() override {
        spriteBatch_ = std::make_unique<SpriteBatch>(getGraphicsDeviceProperty());

        // The 1x1 white pixel — the workhorse of 2D shape drawing
        pixel_ = std::make_unique<Texture2D>(getGraphicsDeviceProperty(), 1, 1);
        Color white = Color::White;
        pixel_->SetData(&white, 1);
    }

    void Update(GameTime& gameTime) override {
        if (Keyboard::GetState().IsKeyDown(Keys::Escape)) Exit();
    }

    void Draw(const GameTime& gameTime) override {
        auto& device = getGraphicsDeviceProperty();
        device.Clear(Color::CornflowerBlue);

        spriteBatch_->Begin();

        // Large dark background panel
        spriteBatch_->Draw(*pixel_, Rectangle(50, 50, 700, 500),
                           Color(0, 0, 0, 180));

        // A red square
        spriteBatch_->Draw(*pixel_, Rectangle(100, 100, 120, 120), Color::Red);

        // A green rectangle
        spriteBatch_->Draw(*pixel_, Rectangle(280, 100, 200, 80), Color::Green);

        // A blue tall rectangle
        spriteBatch_->Draw(*pixel_, Rectangle(540, 100, 80, 200), Color::Blue);

        // A yellow wide rectangle
        spriteBatch_->Draw(*pixel_, Rectangle(100, 300, 400, 50), Color::Yellow);

        // A semi-transparent white rectangle
        spriteBatch_->Draw(*pixel_, Rectangle(200, 380, 200, 100),
                           Color(255, 255, 255, 100));

        // An orange square
        spriteBatch_->Draw(*pixel_, Rectangle(550, 380, 100, 100),
                           Color(255, 140, 0, 255));

        spriteBatch_->End();
        device.Present();
    }

private:
    GraphicsDeviceManager graphics_;
    std::unique_ptr<SpriteBatch> spriteBatch_;
    std::unique_ptr<Texture2D> pixel_;
};

int main() {
    ShapeDemo game;
    game.Run();
    return 0;
}

Run this and you will see a window with several colored rectangles layered on a cornflower blue background. The semi-transparent rectangle lets the shapes behind it show through.

In Tutorial 07 we go deeper into the Color struct — predefined colors, RGBA construction, lerping, and alpha blending.