GPU Renderer¶
Learn about Brine2D's modern GPU renderer - high-performance, hardware-accelerated rendering.
Overview¶
The GPU Renderer (SDL3GPURenderer) is Brine2D's default rendering backend:
- Hardware Accelerated - Direct GPU rendering
- Modern API - SDL3's new GPU API
- Cross-Platform - Works on Windows, Linux, macOS
- High Performance - Optimized for modern GPUs
- Feature Rich - Advanced rendering capabilities
Recommended for: All new projects and most use cases.
GPU vs Legacy Renderer¶
Comparison¶
| Feature | GPU Renderer | Legacy Renderer |
|---|---|---|
| API | SDL3 GPU | SDL2-style Renderer |
| Performance | High | Moderate |
| Memory | GPU VRAM | System RAM |
| Features | Full | Basic |
| Platform Support | Modern GPUs | Any GPU |
| Recommended | ✅ Yes | ⚠️ Fallback only |
When to Use GPU Renderer¶
Use the GPU renderer (default) when:
- Building new games
- Targeting modern hardware
- Need high performance
- Want advanced features
- Cross-platform deployment
When to Use Legacy Renderer¶
Use the legacy renderer only when:
- GPU renderer not supported
- Targeting very old hardware
- Debugging graphics issues
- Need SDL2 compatibility
Note: Most users should use the GPU renderer.
Architecture¶
graph TB
A[Your Game] --> B[IRenderer]
B --> C[SDL3GPURenderer]
C --> D[GPU Commands]
D --> D1[Draw Calls]
D --> D2[State Changes]
D --> D3[Resource Management]
C --> E[SDL3 GPU API]
E --> F[GPU Backend]
F --> G1[Vulkan]
F --> G2[Metal]
F --> G3[D3D12]
G1 --> H[GPU Hardware]
G2 --> H
G3 --> H
style B fill:#2d5016,stroke:#4ec9b0,stroke-width:2px,color:#fff
style C fill:#4a2d4a,stroke:#c586c0,stroke-width:2px,color:#fff
style E fill:#4a3d1f,stroke:#ce9178,stroke-width:2px,color:#fff
style F fill:#1e3a5f,stroke:#569cd6,stroke-width:2px,color:#fff
Rendering flow:
- Your game calls
IRenderermethods SDL3GPURenderertranslates to GPU commands- SDL3 GPU API manages backend (Vulkan/Metal/D3D12)
- Commands execute on GPU hardware
- Results presented to screen
Configuration¶
Default Configuration¶
The GPU renderer is enabled by default:
using Brine2D.Hosting;
using Brine2D.SDL;
using Microsoft.Extensions.DependencyInjection;
var builder = GameApplication.CreateBuilder(args);
// GPU renderer is default
builder.Services.AddSDL3Rendering(options =>
{
options.WindowTitle = "My Game";
options.WindowWidth = 1280;
options.WindowHeight = 720;
options.VSync = true;
// Backend defaults to GraphicsBackend.GPU
});
var game = builder.Build();
await game.RunAsync<GameScene>();
Explicit GPU Configuration¶
Explicitly specify GPU backend:
using Brine2D.Rendering;
builder.Services.AddSDL3Rendering(options =>
{
options.WindowTitle = "My Game";
options.WindowWidth = 1280;
options.WindowHeight = 720;
options.Backend = GraphicsBackend.GPU; // Explicit GPU
options.VSync = true;
});
GPU Options¶
builder.Services.AddSDL3Rendering(options =>
{
// Window settings
options.WindowTitle = "GPU Renderer Demo";
options.WindowWidth = 1920;
options.WindowHeight = 1080;
// Rendering backend
options.Backend = GraphicsBackend.GPU;
// VSync (recommended for smooth rendering)
options.VSync = true;
// Window mode
options.Fullscreen = false;
options.Borderless = false;
options.Resizable = true;
});
GPU Backends¶
SDL3's GPU API supports multiple backends:
Vulkan (Windows, Linux, Android)¶
Modern cross-platform API:
- Performance: Excellent
- Features: Full
- Platforms: Windows, Linux, Android
- Driver Support: Modern GPUs
Metal (macOS, iOS)¶
Apple's graphics API:
- Performance: Excellent
- Features: Full
- Platforms: macOS, iOS
- Driver Support: All Apple devices
Direct3D 12 (Windows, Xbox)¶
Microsoft's modern API:
- Performance: Excellent
- Features: Full
- Platforms: Windows 10+, Xbox
- Driver Support: Modern GPUs
Backend Selection¶
SDL3 automatically selects the best backend for your platform:
| Platform | Default Backend |
|---|---|
| Windows | D3D12 or Vulkan |
| Linux | Vulkan |
| macOS | Metal |
| iOS | Metal |
| Android | Vulkan |
You don't need to choose - SDL3 handles it automatically.
Performance Characteristics¶
GPU Memory¶
Textures stored in GPU VRAM:
public class TextureManager
{
private readonly IRenderer _renderer;
private readonly Dictionary<string, ITexture> _textures = new();
public async Task<ITexture> LoadTextureAsync(string path, CancellationToken ct)
{
if (_textures.TryGetValue(path, out var cached))
{
return cached; // Return cached texture
}
// Load texture into GPU memory
var texture = await _renderer.LoadTextureAsync(path, ct);
_textures[path] = texture;
return texture;
}
}
Benefits: - Fast access from GPU - No CPU-GPU transfer per frame - Large textures supported
Batch Rendering¶
GPU renderer batches draw calls:
protected override void OnRender(GameTime gameTime)
{
_renderer.Clear(Color.Black);
// These draws are batched automatically
for (int i = 0; i < 1000; i++)
{
_renderer.DrawTexture(
_sprite,
i * 64, 100,
64, 64);
}
// Single GPU draw call for all sprites!
}
Performance: - Automatic batching - Minimal draw calls - High sprite counts supported
VSync¶
Control frame pacing:
builder.Services.AddSDL3Rendering(options =>
{
// VSync on (recommended)
options.VSync = true; // Smooth 60 FPS
// VSync off (for benchmarking)
// options.VSync = false; // Uncapped FPS
});
With VSync: - Frame rate locked to display (60 Hz = 60 FPS) - No screen tearing - Smooth gameplay - Predictable performance
Without VSync: - Uncapped frame rate - Possible screen tearing - Variable performance - Good for benchmarking
Advanced Features¶
Render Targets¶
Render to textures:
public class RenderTargetExample : Scene
{
private readonly IRenderer _renderer;
private ITexture? _renderTarget;
private ITexture? _sprite;
protected override async Task OnLoadAsync(CancellationToken ct)
{
// Create render target (512x512)
_renderTarget = await _renderer.CreateRenderTargetAsync(512, 512, ct);
// Load sprite
_sprite = await _renderer.LoadTextureAsync("assets/sprite.png", ct);
}
protected override void OnRender(GameTime gameTime)
{
// Render to texture
_renderer.SetRenderTarget(_renderTarget);
_renderer.Clear(Color.Transparent);
_renderer.DrawTexture(_sprite, 0, 0, 512, 512);
// Render to screen
_renderer.SetRenderTarget(null); // Back to screen
_renderer.Clear(Color.Black);
_renderer.DrawTexture(_renderTarget, 0, 0, 800, 600);
}
}
Use cases: - Post-processing effects - Minimap rendering - Dynamic textures - Screen effects
Texture Streaming¶
Load large textures efficiently:
public class LargeTextureLoader
{
private readonly IRenderer _renderer;
public async Task<ITexture> LoadLargeTextureAsync(string path, CancellationToken ct)
{
// GPU renderer handles streaming automatically
var texture = await _renderer.LoadTextureAsync(path, ct);
// Texture uploaded to GPU memory
// No manual streaming needed
return texture;
}
}
Benefits: - Automatic texture streaming - Large textures supported (up to GPU limits) - Efficient memory management
Blend Modes¶
Control how sprites blend:
// Note: Blend modes typically controlled via draw parameters
// or through shader/material systems
protected override void OnRender(GameTime gameTime)
{
_renderer.Clear(Color.Black);
// Draw with default blend (alpha blend)
_renderer.DrawTexture(_background, 0, 0, 800, 600);
// Additive blending for glow effects
// (API-specific - check IRenderer interface)
_renderer.DrawTexture(_glowEffect, 300, 200, 200, 200);
}
Best Practices¶
DO¶
-
Use GPU renderer by default
// ✅ Good - GPU renderer (default) builder.Services.AddSDL3Rendering(options => { ... }); -
Enable VSync
// ✅ Good - smooth rendering options.VSync = true; -
Batch draw calls
// ✅ Good - draw multiple sprites in sequence foreach (var sprite in sprites) { _renderer.DrawTexture(sprite.Texture, sprite.X, sprite.Y, 64, 64); } // Automatically batched! -
Preload textures
// ✅ Good - load during loading screen protected override async Task OnLoadAsync(CancellationToken ct) { _texture = await _renderer.LoadTextureAsync("sprite.png", ct); } -
Unload unused textures
// ✅ Good - free GPU memory protected override void OnDispose() { _renderer.UnloadTexture(_texture); }
DON'T¶
-
Don't load textures in render loop
// ❌ Bad - loads every frame! protected override void OnRender(GameTime gameTime) { var texture = await _renderer.LoadTextureAsync(...); // NO! } -
Don't create textures unnecessarily
// ❌ Bad - creates 1000 textures! for (int i = 0; i < 1000; i++) { var texture = await _renderer.LoadTextureAsync("sprite.png", ct); } // ✅ Good - load once, use many times var texture = await _renderer.LoadTextureAsync("sprite.png", ct); for (int i = 0; i < 1000; i++) { _renderer.DrawTexture(texture, i * 64, 100, 64, 64); } -
Don't forget to clear
// ❌ Bad - no clear, garbage on screen protected override void OnRender(GameTime gameTime) { _renderer.DrawTexture(...); } // ✅ Good - clear first protected override void OnRender(GameTime gameTime) { _renderer.Clear(Color.Black); _renderer.DrawTexture(...); } -
Don't disable VSync without reason
// ❌ Bad - causes screen tearing options.VSync = false; // ✅ Good - smooth rendering options.VSync = true;
Performance Optimization¶
Texture Atlasing¶
Combine multiple textures:
public class TextureAtlas
{
private readonly ITexture _atlas;
private readonly Dictionary<string, Rectangle> _regions = new();
public void DrawSprite(IRenderer renderer, string name, float x, float y)
{
if (_regions.TryGetValue(name, out var region))
{
// Draw from atlas (single texture, multiple sprites)
renderer.DrawTexture(_atlas, x, y, region.Width, region.Height);
}
}
}
Benefits: - Fewer texture switches - Better batching - Reduced draw calls
Culling¶
Don't draw off-screen objects:
public class Viewport
{
public Rectangle Bounds { get; set; }
public bool IsVisible(float x, float y, float width, float height)
{
return x + width >= Bounds.X &&
x <= Bounds.X + Bounds.Width &&
y + height >= Bounds.Y &&
y <= Bounds.Y + Bounds.Height;
}
}
protected override void OnRender(GameTime gameTime)
{
_renderer.Clear(Color.Black);
foreach (var sprite in _sprites)
{
// Only draw visible sprites
if (_viewport.IsVisible(sprite.X, sprite.Y, sprite.Width, sprite.Height))
{
_renderer.DrawTexture(sprite.Texture, sprite.X, sprite.Y,
sprite.Width, sprite.Height);
}
}
}
Sprite Sorting¶
Draw in optimal order:
protected override void OnRender(GameTime gameTime)
{
_renderer.Clear(Color.Black);
// Sort sprites by texture (for batching)
var sortedSprites = _sprites.OrderBy(s => s.Texture.GetHashCode());
// Draw sorted sprites
foreach (var sprite in sortedSprites)
{
_renderer.DrawTexture(sprite.Texture, sprite.X, sprite.Y,
sprite.Width, sprite.Height);
}
}
Benefits: - Better batching - Fewer texture switches - Higher performance
Troubleshooting¶
Problem: Black screen¶
Symptom: Window opens but is black.
Solutions:
-
Check Clear is called:
protected override void OnRender(GameTime gameTime) { _renderer.Clear(Color.Black); // Must clear! // ... draw calls } -
Verify textures loaded:
if (_texture != null) { _renderer.DrawTexture(_texture, 0, 0, 64, 64); } else { Logger.LogError("Texture not loaded!"); } -
Check coordinate system:
// Draw at 0,0 to test _renderer.DrawTexture(_texture, 0, 0, 100, 100);
Problem: Low FPS¶
Symptom: Game runs slowly.
Solutions:
-
Enable VSync:
options.VSync = true; -
Check texture count:
// Too many textures? Logger.LogDebug("Texture count: {Count}", _textureManager.Count); -
Profile rendering:
var sw = Stopwatch.StartNew(); OnRender(gameTime); sw.Stop(); Logger.LogDebug("Render time: {Ms}ms", sw.ElapsedMilliseconds); -
Reduce draw calls:
- Use texture atlasing
- Implement culling
- Sort by texture
Problem: Screen tearing¶
Symptom: Horizontal line artifacts during scrolling.
Solution: Enable VSync:
builder.Services.AddSDL3Rendering(options =>
{
options.VSync = true; // Fixes tearing
});
Problem: GPU not supported¶
Symptom: Error on startup about GPU not supported.
Solution: Fall back to legacy renderer:
builder.Services.AddSDL3Rendering(options =>
{
options.Backend = GraphicsBackend.LegacyRenderer;
// ... other options
});
Problem: High memory usage¶
Symptom: Game uses lots of RAM/VRAM.
Solutions:
-
Unload unused textures:
_renderer.UnloadTexture(_oldTexture); -
Use texture atlases:
- Combine small textures
-
Reduce texture count
-
Compress textures:
- Use appropriate formats
- Reduce texture resolution
GPU Capabilities¶
Query GPU Info¶
Check GPU capabilities:
public class GPUInfo
{
private readonly IRenderer _renderer;
public void LogGPUInfo()
{
// GPU info typically available through SDL3
// Check IRenderer interface for available methods
Logger.LogInformation("GPU Renderer: SDL3GPURenderer");
Logger.LogInformation("Backend: Auto-detected");
Logger.LogInformation("VSync: Enabled");
}
}
Feature Detection¶
Check for feature support:
public class FeatureDetection
{
public bool SupportsRenderTargets { get; private set; }
public bool SupportsHighResolution { get; private set; }
public void DetectFeatures()
{
// GPU renderer supports:
SupportsRenderTargets = true; // Render to texture
SupportsHighResolution = true; // 4K and higher
Logger.LogInformation("Render targets: {Supported}",
SupportsRenderTargets);
}
}
Platform-Specific Notes¶
Windows¶
GPU Backend: Direct3D 12 or Vulkan
// Automatically selected by SDL3
// Prefers D3D12 on Windows 10+
// Falls back to Vulkan if available
Requirements: - Windows 10 or later (for D3D12) - Modern GPU with D3D12/Vulkan support - Updated graphics drivers
Linux¶
GPU Backend: Vulkan
// Automatically uses Vulkan on Linux
Requirements: - Vulkan-capable GPU - Vulkan drivers installed - Mesa 20.0+ or proprietary drivers
macOS¶
GPU Backend: Metal
// Automatically uses Metal on macOS
Requirements: - macOS 10.14 (Mojave) or later - Metal-capable GPU (all modern Macs) - No additional drivers needed
Migration from Legacy Renderer¶
Switching to GPU Renderer¶
Change from legacy to GPU:
// Before (legacy)
builder.Services.AddSDL3Rendering(options =>
{
options.Backend = GraphicsBackend.LegacyRenderer;
});
// After (GPU)
builder.Services.AddSDL3Rendering(options =>
{
options.Backend = GraphicsBackend.GPU; // or omit (default)
});
API Compatibility:
- Same IRenderer interface
- No code changes needed
- Drop-in replacement
Performance Gains¶
Typical improvements:
| Metric | Legacy | GPU | Improvement |
|---|---|---|---|
| FPS (1000 sprites) | 45 FPS | 60 FPS | +33% |
| Memory Usage | 200 MB | 150 MB | -25% |
| Draw Calls | 1000 | 10 | -99% |
| Texture Switches | High | Low | Batched |
Your results may vary based on game and hardware.
Summary¶
GPU Renderer features:
| Feature | Support |
|---|---|
| Hardware Acceleration | ✅ Yes |
| Cross-Platform | ✅ Yes |
| VSync | ✅ Yes |
| Render Targets | ✅ Yes |
| Texture Atlasing | ✅ Yes |
| High Resolution | ✅ Yes (4K+) |
| Batching | ✅ Automatic |
Performance characteristics:
| Aspect | Rating |
|---|---|
| 2D Sprites | Excellent |
| Primitives | Excellent |
| Text Rendering | Good |
| Large Textures | Excellent |
| Memory Efficiency | Excellent |
Platform support:
| Platform | Backend | Status |
|---|---|---|
| Windows | D3D12/Vulkan | ✅ Supported |
| Linux | Vulkan | ✅ Supported |
| macOS | Metal | ✅ Supported |
| iOS | Metal | ✅ Supported |
| Android | Vulkan | ✅ Supported |
Next Steps¶
- Choosing Renderer - GPU vs Legacy comparison
- Sprites - Sprite rendering with GPU
- Primitives - Draw shapes and primitives
- Texture Atlasing - Optimize with atlases
- Performance Optimization - Optimize rendering
Quick Reference¶
// Enable GPU renderer (default)
builder.Services.AddSDL3Rendering(options =>
{
options.WindowTitle = "GPU Renderer Demo";
options.WindowWidth = 1280;
options.WindowHeight = 720;
options.Backend = GraphicsBackend.GPU; // Optional (default)
options.VSync = true; // Recommended
});
// Load texture
var texture = await _renderer.LoadTextureAsync("sprite.png", ct);
// Draw texture
_renderer.Clear(Color.Black);
_renderer.DrawTexture(texture, x, y, width, height);
// Render target
var target = await _renderer.CreateRenderTargetAsync(512, 512, ct);
_renderer.SetRenderTarget(target);
// ... draw to target
_renderer.SetRenderTarget(null); // Back to screen
// Cleanup
_renderer.UnloadTexture(texture);
Ready to compare renderers? Check out Choosing Renderer!