Skip to content

Drawing Primitives

Master the fundamentals of rendering in Brine2D by learning to draw shapes, lines, colors, and primitives.

Overview

Brine2D's rendering system provides simple, immediate-mode drawing APIs for 2D graphics: - ✅ Rectangles - Filled and outlined rectangles - ✅ Circles - Filled and outlined circles - ✅ Lines - Lines with configurable thickness - ✅ Text - String rendering with fonts - ✅ Textures - Images and sprites (covered in Sprites)

sequenceDiagram
    participant Scene as Your Scene
    participant Renderer as IRenderer
    participant SDL as SDL3

    Note over Scene: Every Frame

    Scene->>Renderer: DrawRectangleFilled(...)
    Renderer->>SDL: SDL_RenderFillRect

    Scene->>Renderer: DrawLine(...)
    Renderer->>SDL: SDL_RenderLine

    Scene->>Renderer: DrawCircleFilled(...)
    Renderer->>SDL: Draw circle algorithm

    Scene->>Renderer: DrawText(...)
    Renderer->>SDL: SDL_TTF render

    Note over Renderer,SDL: Frame management automatic!

Prerequisites

  • Quick Start - Basic scene setup
  • ✅ Basic C# knowledge

Automatic Frame Management

In Brine2D, frame management happens automatically! You don't need to call Clear(), BeginFrame(), or EndFrame():

public class DrawingScene : Scene
{
    protected override void OnRender(GameTime gameTime)
    {
        // Just draw! Frame management is automatic
        _renderer.DrawRectangleFilled(100, 100, 200, 150, Color.Red);
        _renderer.DrawCircleFilled(400, 300, 50, Color.Blue);
        _renderer.DrawLine(100, 100, 300, 200, Color.Yellow, 2f);
    }
}

Manual Control Available

Need manual control? Set EnableAutomaticFrameManagement = false in your scene. See Lifecycle Hooks.


Drawing Rectangles

Filled Rectangle

// DrawRectangleFilled(x, y, width, height, color)
_renderer.DrawRectangleFilled(100, 100, 50, 50, Color.Red);

Parameters: - x - Left edge position - y - Top edge position - width - Rectangle width - height - Rectangle height - color - Fill color

Screen Coordinates:
(0,0) ───────► X
  │
  │    (100,100)
  │      ┌──────────┐
  │      │ 50x50 px │
  │      │  rect    │
  │      └──────────┘
  ▼
  Y

Rectangle Outline

// DrawRectangleOutline(x, y, width, height, color, thickness)
_renderer.DrawRectangleOutline(100, 100, 200, 150, Color.White, 2f);

Thickness parameter: - Default: 1.0f (1 pixel) - Larger values = thicker lines


Drawing Circles

Filled Circle

// DrawCircleFilled(centerX, centerY, radius, color)
_renderer.DrawCircleFilled(400, 300, 50, Color.Blue);

Circle Outline

// DrawCircleOutline(centerX, centerY, radius, color, thickness)
_renderer.DrawCircleOutline(400, 300, 50, Color.White, 2f);

Drawing Lines

Draw lines between two points with configurable thickness:

Basic Line

// DrawLine(x1, y1, x2, y2, color, thickness)
_renderer.DrawLine(100, 100, 300, 200, Color.Yellow, 1f);

Parameters: - x1, y1 - Start point - x2, y2 - End point - color - Line color - thickness - Line thickness (default: 1.0f)

Thick Lines

// Thin line
_renderer.DrawLine(100, 100, 300, 100, Color.Red, 1f);

// Medium line
_renderer.DrawLine(100, 150, 300, 150, Color.Green, 3f);

// Thick line
_renderer.DrawLine(100, 200, 300, 200, Color.Blue, 5f);

// Very thick line
_renderer.DrawLine(100, 250, 300, 250, Color.Yellow, 10f);

Line Patterns

Draw a cross:

var centerX = 640f;
var centerY = 360f;
var size = 50f;

_renderer.DrawLine(centerX - size, centerY, centerX + size, centerY, Color.White, 2f);
_renderer.DrawLine(centerX, centerY - size, centerX, centerY + size, Color.White, 2f);

Draw a box with lines:

float x = 100, y = 100, w = 200, h = 150;

_renderer.DrawLine(x, y, x + w, y, Color.White, 2f);         // Top
_renderer.DrawLine(x + w, y, x + w, y + h, Color.White, 2f); // Right
_renderer.DrawLine(x + w, y + h, x, y + h, Color.White, 2f); // Bottom
_renderer.DrawLine(x, y + h, x, y, Color.White, 2f);         // Left

Velocity vectors (from collision demo):

private void DrawVelocityVector(Vector2 position, Vector2 velocity, Color color)
{
    var end = position + velocity * 0.1f;
    _renderer.DrawLine(position.X, position.Y, end.X, end.Y, color, 2f);

    // Arrow head
    var direction = Vector2.Normalize(velocity);
    var perpendicular = new Vector2(-direction.Y, direction.X);

    var arrowPoint1 = end - direction * 10f + perpendicular * 5f;
    var arrowPoint2 = end - direction * 10f - perpendicular * 5f;

    _renderer.DrawLine(end.X, end.Y, arrowPoint1.X, arrowPoint1.Y, color, 2f);
    _renderer.DrawLine(end.X, end.Y, arrowPoint2.X, arrowPoint2.Y, color, 2f);
}

Grid with lines:

private void DrawLineGrid(int gridSize, Color gridColor)
{
    // Vertical lines
    for (int x = 0; x <= 1280; x += gridSize)
    {
        _renderer.DrawLine(x, 0, x, 720, gridColor, 1f);
    }

    // Horizontal lines
    for (int y = 0; y <= 720; y += gridSize)
    {
        _renderer.DrawLine(0, y, 1280, y, gridColor, 1f);
    }
}

Working with Colors

Predefined Colors

Color.White           // (255, 255, 255)
Color.Black           // (0, 0, 0)
Color.Red             // (255, 0, 0)
Color.Green           // (0, 255, 0)
Color.Blue            // (0, 0, 255)
Color.Cyan            // (0, 255, 255)
Color.Yellow          // (255, 255, 0)
Color.Gray            // (128, 128, 128)
Color.CornflowerBlue  // (100, 149, 237)
Color.Transparent     // (0, 0, 0, 0)

Custom Colors

RGB Color

var purple = new Color(128, 0, 128);

RGBA Color (with transparency)

var semiTransparentRed = new Color(255, 0, 0, 128); // 50% transparent

Helper Methods

var color1 = Color.FromRgb(100, 150, 200);
var color2 = Color.FromRgba(100, 150, 200, 128);

Drawing Text

Basic Text

_renderer.DrawText("Hello, World!", 100, 100, Color.White);

Note: Brine2D includes an embedded font that loads automatically!


Complete Drawing Example

Here's a scene with all drawing primitives:

using Brine2D.Core;
using Brine2D.Input;
using Brine2D.Rendering;
using Microsoft.Extensions.Logging;

public class PrimitivesDemo : Scene
{
    private readonly IRenderer _renderer;
    private readonly IInputService _input;
    private readonly IGameContext _gameContext;

    private float _pulse = 0f;

    public PrimitivesDemo(
        IRenderer renderer,
        IInputService input,
        IGameContext gameContext,
        ILogger<PrimitivesDemo> logger
    ) : base(logger)
    {
        _renderer = renderer;
        _input = input;
        _gameContext = gameContext;
    }

    protected override void OnUpdate(GameTime gameTime)
    {
        if (_input.IsKeyPressed(Keys.Escape))
        {
            _gameContext.RequestExit();
        }

        _pulse += (float)gameTime.DeltaTime * 2f;
    }

    protected override void OnRender(GameTime gameTime)
    {
        // Frame management automatic! Just draw

        // Grid background
        DrawGrid();

        // Filled rectangles
        _renderer.DrawRectangleFilled(100, 100, 100, 100, Color.Red);
        _renderer.DrawRectangleFilled(250, 100, 150, 80, Color.Green);

        // Rectangle outlines
        _renderer.DrawRectangleOutline(100, 250, 100, 100, Color.Yellow, 3f);
        _renderer.DrawRectangleOutline(250, 250, 150, 80, Color.Cyan, 2f);

        // Filled circles
        _renderer.DrawCircleFilled(600, 150, 50, Color.Red);
        _renderer.DrawCircleFilled(750, 150, 30, Color.Green);

        // Circle outlines
        _renderer.DrawCircleOutline(600, 300, 50, Color.Yellow, 3f);
        _renderer.DrawCircleOutline(750, 300, 30, Color.Cyan, 2f);

        // Lines with different thickness
        _renderer.DrawLine(100, 400, 300, 400, Color.White, 1f);
        _renderer.DrawLine(100, 430, 300, 430, Color.White, 3f);
        _renderer.DrawLine(100, 470, 300, 470, Color.White, 5f);

        // Cross pattern
        DrawCross(500, 450, 50, Color.Yellow, 3f);

        // Pulsing square
        DrawPulsingSquare();

        // Text
        _renderer.DrawText("Primitives Demo", 10, 10, Color.White);
        _renderer.DrawText($"FPS: {(int)(1.0 / gameTime.DeltaTime)}", 10, 30, Color.Yellow);
    }

    private void DrawGrid()
    {
        var gridSize = 50;
        var gridColor = new Color(60, 60, 60);

        for (int x = 0; x <= 1280; x += gridSize)
        {
            _renderer.DrawLine(x, 0, x, 720, gridColor, 1f);
        }

        for (int y = 0; y <= 720; y += gridSize)
        {
            _renderer.DrawLine(0, y, 1280, y, gridColor, 1f);
        }
    }

    private void DrawCross(float centerX, float centerY, float size, Color color, float thickness)
    {
        _renderer.DrawLine(centerX - size, centerY, centerX + size, centerY, color, thickness);
        _renderer.DrawLine(centerX, centerY - size, centerX, centerY + size, color, thickness);
    }

    private void DrawPulsingSquare()
    {
        var scale = 1.0f + MathF.Sin(_pulse) * 0.3f;
        var size = 80 * scale;
        var centerX = 900f;
        var centerY = 500f;

        var intensity = (byte)(128 + MathF.Sin(_pulse * 2) * 127);
        var color = new Color(intensity, 0, intensity);

        _renderer.DrawRectangleFilled(
            centerX - size / 2,
            centerY - size / 2,
            size,
            size,
            color);
    }
}

See It In Action

Check out the Collision Demo in FeatureDemos to see DrawLine used for velocity vectors!

cd samples/FeatureDemos
dotnet run
# Select "Collision Detection" from the menu
# Press F2 to toggle velocity vectors

API Reference

Rectangles

DrawRectangleFilled(float x, float y, float width, float height, Color color)
DrawRectangleOutline(float x, float y, float width, float height, Color color, float thickness = 1f)

Circles

DrawCircleFilled(float centerX, float centerY, float radius, Color color)
DrawCircleOutline(float centerX, float centerY, float radius, Color color, float thickness = 1f)

Lines

DrawLine(float x1, float y1, float x2, float y2, Color color, float thickness = 1f)

Text

DrawText(string text, float x, float y, Color color)

Best Practices

DO

Let the framework manage frames

protected override void OnRender(GameTime gameTime)
{
    // Just draw!
    _renderer.DrawRectangleFilled(...);
}

Use constants for repeated values

private const float PLAYER_SIZE = 50f;
_renderer.DrawRectangleFilled(x, y, PLAYER_SIZE, PLAYER_SIZE, Color.Red);

Cache colors

private static readonly Color PlayerColor = new Color(100, 200, 255);

DON'T

Don't create colors every frame

// Bad
_renderer.DrawRectangleFilled(x, y, 100, 100, new Color(255, 0, 0));

// Good
private static readonly Color Red = Color.Red;
_renderer.DrawRectangleFilled(x, y, 100, 100, Red);

Don't draw off-screen objects

if (IsVisible(entity))
{
    DrawEntity(entity);
}

Next Steps


Remember: Frame management is automatic - just draw!