Math Libraries for Game Development - Research Report
Date: December 2024 Purpose: Evaluate math libraries for game engine development focusing on graphics-oriented operations (vectors, matrices, quaternions)
Executive Summary
After evaluating System.Numerics, Silk.NET.Maths, OpenTK.Mathematics, and GlmSharp, System.Numerics is the recommended primary choice for a custom game engine due to its built-in SIMD acceleration, zero dependencies, and runtime integration. For features not available in System.Numerics (such as integer vectors or Matrix3x3), Silk.NET.Maths provides the best supplementary option given its active maintenance and integration with the Silk.NET graphics ecosystem.
Library Comparison
System.Numerics
| Attribute | Value |
|---|---|
| Package | Built into .NET Runtime |
| Latest Version | Ships with .NET 10 |
| License | MIT |
| Dependencies | None (runtime included) |
Strengths:
- SIMD-accelerated - Hardware intrinsics for Vector2/3/4, Matrix4x4, Quaternion, Plane
- Zero allocation overhead - All types are structs with value semantics
- No external dependencies - Part of the runtime, always available
- JIT-optimized - Runtime generates optimal code for the target CPU
- Widely understood - Standard .NET type, extensive documentation
Weaknesses:
- Limited type coverage - No Matrix3x3, no integer vectors
- No Euler angles - Must use Quaternion or manually implement
- DirectX-style conventions - Functions designed for Direct3D clip space
- Row-vector multiplication - Uses
v * Mconvention, requires attention when interfacing with OpenGL
Available Types:
System.Numerics.Vector2 // 2D float vector
System.Numerics.Vector3 // 3D float vector
System.Numerics.Vector4 // 4D float vector
System.Numerics.Matrix3x2 // 2D transformation matrix
System.Numerics.Matrix4x4 // 4x4 transformation matrix
System.Numerics.Quaternion // Rotation quaternion
System.Numerics.Plane // 3D plane
API Example:
using System.Numerics;
// Create transformation matrices
var translation = Matrix4x4.CreateTranslation(10, 5, 0);
var rotation = Matrix4x4.CreateRotationZ(MathF.PI / 4);
var scale = Matrix4x4.CreateScale(2.0f);
// Compose transformations (row-vector convention: scale * rotate * translate)
var world = scale * rotation * translation;
// Transform a point
var point = new Vector3(1, 0, 0);
var transformed = Vector3.Transform(point, world);
// Create view and projection
var view = Matrix4x4.CreateLookAt(
cameraPosition: new Vector3(0, 0, 10),
cameraTarget: Vector3.Zero,
cameraUpVector: Vector3.UnitY);
var projection = Matrix4x4.CreatePerspectiveFieldOfView(
fieldOfView: MathF.PI / 4,
aspectRatio: 16f / 9f,
nearPlaneDistance: 0.1f,
farPlaneDistance: 100f);
Silk.NET.Maths
| Attribute | Value |
|---|---|
| NuGet Package | Silk.NET.Maths |
| Latest Version | 2.22.0 (November 2024) |
| Total Downloads | 1.5M |
| License | MIT |
Strengths:
- Generic types -
Vector3D<T>,Matrix4X4<T>work with float, double, int, etc. - Active development - Regular releases, .NET Foundation backed
- Silk.NET integration - Seamless with Silk.NET.OpenGL bindings
- Integer support -
Vector2D<int>for grid-based systems - Mobile support - iOS and Android compatible
Weaknesses:
- No SIMD optimization - Maintainers explicitly recommend System.Numerics for performance
- Supplementary role - Designed to extend System.Numerics, not replace it
- Extra dependency - Adds package reference and transitive dependencies
Key Insight from Maintainers:
"Avoid Silk.NET.Maths if you can avoid it. System.Numerics is faster and will always be faster than non-SIMD Silk.NET.Maths."
API Example:
using Silk.NET.Maths;
// Generic vectors - useful for integer grid systems
Vector2D<int> gridPos = new(5, 10);
Vector3D<float> position = new(1.0f, 2.0f, 3.0f);
// Convert to System.Numerics for performance-critical operations
System.Numerics.Vector3 sysVec = position.ToSystem();
// Matrix operations
var transform = Matrix4X4.CreateScale(2.0f) *
Matrix4X4.CreateRotationZ(0.5f) *
Matrix4X4.CreateTranslation(10f, 0f, 0f);
OpenTK.Mathematics
| Attribute | Value |
|---|---|
| NuGet Package | OpenTK.Mathematics |
| Latest Version | 4.9.4 (March 2025) |
| Total Downloads | 7.1M |
| License | MIT |
Strengths:
- Comprehensive types - Vector2/3/4, Matrix2/3/4, Quaternion, Box, Color4
- OpenGL-friendly API - Methods like
LookAt,CreatePerspectiveFieldOfView - Battle-tested - 15+ years of development, widely used
- Excellent documentation - Extensive tutorials and API docs
- Matrix3 support - Useful for normal matrix calculations
Weaknesses:
- Desktop only - No mobile platform support
- Row naming confusion -
Row0-Row3fields actually represent columns in OpenGL terms - Tightly coupled - Designed for OpenTK ecosystem
- Community maintained - No foundation backing
Memory Layout Note:
OpenTK's Matrix4 uses row-major storage with fields named Row0, Row1, Row2, Row3. When passing to OpenGL via GL.UniformMatrix4, this layout is compatible with OpenGL's column-major expectation when using transpose: false because OpenGL interprets the sequential memory as columns.
API Example:
using OpenTK.Mathematics;
// Create transformation
var model = Matrix4.CreateScale(2.0f) *
Matrix4.CreateRotationZ(MathHelper.DegreesToRadians(45)) *
Matrix4.CreateTranslation(10, 5, 0);
// Camera setup
var view = Matrix4.LookAt(
eye: new Vector3(0, 0, 10),
target: Vector3.Zero,
up: Vector3.UnitY);
var projection = Matrix4.CreatePerspectiveFieldOfView(
fovy: MathHelper.DegreesToRadians(45),
aspect: 16f / 9f,
depthNear: 0.1f,
depthFar: 100f);
// Upload to shader (transpose: false for OpenTK matrices)
GL.UniformMatrix4(location, false, ref model);
// Convert to/from System.Numerics
System.Numerics.Matrix4x4 sysMatrix = (System.Numerics.Matrix4x4)model;
Matrix4 opentkMatrix = (Matrix4)sysMatrix;
GlmSharp
| Attribute | Value |
|---|---|
| NuGet Package | GlmSharp |
| Latest Version | 0.9.8 (June 2016) |
| Total Downloads | 95.6K |
| License | MIT |
Strengths:
- GLM-compatible API - Familiar to C++ developers using GLM
- Swizzling support -
v.swizzle.bgr,v.zw = v.yx - Wide type support - int, uint, long, float, double, decimal, Complex, bool
- No dependencies - Pure C# implementation
- Non-square matrices - mat2x3, mat3x4, etc.
Weaknesses:
- Not maintained - Last update June 2016 (8+ years ago)
- No SIMD - Pure managed code, no hardware acceleration
- No modern .NET features - Targets .NET 2.0, misses modern optimizations
- Low adoption - 95K downloads vs millions for alternatives
Not Recommended due to maintenance status. Use System.Numerics + Silk.NET.Maths instead.
API Example (for reference):
using GlmSharp;
vec3 v = new vec3(1, 2, 3);
mat4 view = mat4.LookAt(vec3.Ones, vec3.Zero, vec3.UnitY);
// Swizzling
vec2 xy = v.swizzle.xy;
v.swizzle.xy = v.swizzle.yx; // Swap x and y
Memory Layout and OpenGL Compatibility
The Row-Major vs Column-Major Question
This is one of the most confusing aspects of graphics math. Here's the practical reality:
| Library | Storage Order | Multiplication Convention | OpenGL Upload |
|---|---|---|---|
| System.Numerics | Column-major* | Row-vector (v * M) |
transpose: true |
| OpenTK.Mathematics | Row-major | Column-vector (M * v)** |
transpose: false |
| Silk.NET.Maths | Matches System.Numerics | Row-vector (v * M) |
transpose: true |
| GlmSharp | Column-major | Column-vector (M * v) |
transpose: false |
*System.Numerics stores data for SIMD efficiency in column-major order but uses row-vector multiplication semantics.
**OpenTK uses model * view * projection order despite row-major storage.
Practical OpenGL Integration
With System.Numerics:
// Option 1: Transpose on upload
GL.UniformMatrix4fv(location, 1, true, ref matrix.M11);
// Option 2: Reverse multiplication in shader
// Instead of: gl_Position = projection * view * model * vec4(position, 1.0);
// Use: gl_Position = vec4(position, 1.0) * model * view * projection;
With OpenTK.Mathematics:
// Direct upload without transpose
GL.UniformMatrix4(location, false, ref matrix);
// Shader uses standard: gl_Position = projection * view * model * vec4(position, 1.0);
Translation Component Location
When you need to extract or verify translation:
| Library | Translation Access |
|---|---|
| System.Numerics | matrix.M41, M42, M43 (row 4) |
| OpenTK.Mathematics | matrix.Row3.Xyz (actually column in OpenGL terms) |
| GlmSharp | matrix.Column3.xyz |
Performance Considerations
SIMD Acceleration
Only System.Numerics provides true hardware SIMD acceleration in standard .NET:
Vector<T>.Count at runtime:
- Vector<float>: 4 (SSE) or 8 (AVX)
- Vector<double>: 2 (SSE) or 4 (AVX)
- Vector<byte>: 16 (SSE) or 32 (AVX)
The JIT compiler generates optimal SIMD instructions (SSE2/AVX/AVX2) based on the CPU at runtime.
Benchmark Expectations
Based on general principles (specific benchmarks vary by workload):
| Operation | System.Numerics | Other Libraries |
|---|---|---|
| Matrix4x4 multiply | ~4 SIMD ops | ~64 scalar ops |
| Vector3 normalize | SIMD rsqrt | Scalar sqrt |
| Quaternion slerp | SIMD optimized | Scalar |
| Dot product | Single SIMD op | 3 multiplies + 2 adds |
Memory Efficiency
All evaluated libraries use value types (structs) with sequential layout:
// All are blittable and GPU-upload friendly
sizeof(Vector3) = 12 bytes
sizeof(Vector4) = 16 bytes
sizeof(Matrix4x4) = 64 bytes
sizeof(Quaternion) = 16 bytes
API Ergonomics Comparison
Creating a View-Projection Matrix
System.Numerics:
var view = Matrix4x4.CreateLookAt(cameraPos, target, Vector3.UnitY);
var proj = Matrix4x4.CreatePerspectiveFieldOfView(fov, aspect, near, far);
var viewProj = view * proj; // Row-vector order
OpenTK.Mathematics:
var view = Matrix4.LookAt(cameraPos, target, Vector3.UnitY);
var proj = Matrix4.CreatePerspectiveFieldOfView(fov, aspect, near, far);
var viewProj = proj * view; // Column-vector order (reversed)
Silk.NET.Maths:
var view = Matrix4X4.CreateLookAt(cameraPos, target, Vector3D<float>.UnitY);
var proj = Matrix4X4.CreatePerspectiveFieldOfView(fov, aspect, near, far);
var viewProj = view * proj; // Same as System.Numerics
Rotating a Vector
System.Numerics:
var rotation = Quaternion.CreateFromAxisAngle(Vector3.UnitY, angle);
var rotated = Vector3.Transform(point, rotation);
OpenTK.Mathematics:
var rotation = Quaternion.FromAxisAngle(Vector3.UnitY, angle);
var rotated = Vector3.Transform(point, rotation);
// Or: rotation * point (operator overload)
Missing Features
| Feature | System.Numerics | OpenTK | Silk.NET.Maths | GlmSharp |
|---|---|---|---|---|
| Matrix3x3 | ❌ | ✅ | ✅ | ✅ |
| Integer vectors | ❌ | ✅ (Vector2i) | ✅ | ✅ |
| Euler angles | ❌ | ✅ | ✅ | ✅ |
| Swizzling | ❌ | ❌ | ❌ | ✅ |
| Color types | ❌ | ✅ (Color4) | ❌ | ❌ |
| Bounding boxes | ❌ | ✅ (Box2/3) | ✅ | ❌ |
Decision Matrix
| Criteria | Weight | System.Numerics | Silk.NET.Maths | OpenTK.Math | GlmSharp |
|---|---|---|---|---|---|
| Performance | High | 10 | 6 | 7 | 5 |
| Maintenance | High | 10 | 9 | 8 | 2 |
| Type Coverage | High | 6 | 8 | 9 | 9 |
| OpenGL Compat | High | 7 | 7 | 9 | 8 |
| Dependencies | Medium | 10 | 7 | 8 | 9 |
| Documentation | Medium | 9 | 7 | 9 | 5 |
| Mobile Support | Medium | 10 | 9 | 3 | 6 |
| Weighted Score | - | 8.7 | 7.5 | 7.6 | 5.9 |
Scores: 1-10, higher is better
Recommendations
Primary Recommendation: System.Numerics + Silk.NET.Maths
For the KeenEyes ECS and custom game engine development:
Use System.Numerics as the primary math library
- SIMD acceleration for all hot-path operations
- Zero dependencies, always available
- Best performance for bulk ECS operations
Use Silk.NET.Maths for gaps
- Integer vectors for grid systems:
Vector2D<int> - Generic math when needed:
Matrix4X4<T> - Seamless integration with Silk.NET graphics
- Integer vectors for grid systems:
Handle OpenGL integration explicitly
// Create a helper for shader uploads public static void UploadMatrix(int location, ref Matrix4x4 matrix) { GL.UniformMatrix4fv(location, 1, true, ref matrix.M11); // transpose: true }
Alternative: OpenTK.Mathematics
Consider OpenTK.Mathematics if:
- Desktop-only development is acceptable
- Already using OpenTK for windowing/graphics
- Prefer
Matrix3andColor4included - Want OpenGL-native conventions without transpose handling
Avoid: GlmSharp
Do not use GlmSharp due to:
- 8+ years without updates
- No SIMD optimization
- Low community adoption
- Better alternatives available
Integration Notes for KeenEyes
Component Design
using System.Numerics;
[Component]
public partial struct Transform
{
public Vector3 Position;
public Quaternion Rotation;
public Vector3 Scale;
}
[Component]
public partial struct Velocity
{
public Vector3 Linear;
public Vector3 Angular;
}
// For grid-based systems, use Silk.NET.Maths
using Silk.NET.Maths;
[Component]
public partial struct GridPosition
{
public Vector2D<int> Cell;
}
System Implementation
public class MovementSystem : SystemBase
{
public override void Update(float deltaTime)
{
foreach (var entity in World.Query<Transform, Velocity>())
{
ref var transform = ref World.Get<Transform>(entity);
ref readonly var velocity = ref World.Get<Velocity>(entity);
// SIMD-accelerated operations
transform.Position += velocity.Linear * deltaTime;
transform.Rotation = Quaternion.Concatenate(
transform.Rotation,
Quaternion.CreateFromAxisAngle(
Vector3.Normalize(velocity.Angular),
velocity.Angular.Length() * deltaTime));
}
}
}
Matrix Composition Helper
public static class TransformExtensions
{
public static Matrix4x4 ToMatrix(this Transform transform)
{
return Matrix4x4.CreateScale(transform.Scale) *
Matrix4x4.CreateFromQuaternion(transform.Rotation) *
Matrix4x4.CreateTranslation(transform.Position);
}
public static Matrix4x4 ToNormalMatrix(this Transform transform)
{
// For normal transformation, use inverse-transpose of upper-left 3x3
if (Matrix4x4.Invert(transform.ToMatrix(), out var inverse))
{
return Matrix4x4.Transpose(inverse);
}
return Matrix4x4.Identity;
}
}
Research Task Checklist
Completed
- [x] Evaluate System.Numerics capabilities
- [x] Evaluate Silk.NET.Maths
- [x] Evaluate OpenTK.Mathematics
- [x] Evaluate GlmSharp
- [x] Compare memory layouts
- [x] Document OpenGL interop requirements
- [x] Assess SIMD utilization
- [x] Evaluate API ergonomics
Future Investigation
- [ ] Benchmark common operations (matrix multiply, normalize, cross product)
- [ ] Test actual GPU upload performance with different transpose options
- [ ] Prototype frustum culling with System.Numerics Plane type
- [ ] Evaluate ray-AABB intersection implementations
Sources
- SIMD-accelerated types in .NET - Microsoft Learn
- System.Numerics.Matrix4x4 - Microsoft Learn
- Silk.NET.Maths NuGet
- Silk.NET.Maths vs System.Numerics Discussion
- OpenTK.Mathematics API
- OpenTK Matrix4 Documentation
- OpenTK Transformations Tutorial
- OpenTK Matrix Row/Column Issue
- GlmSharp GitHub
- GlmSharp NuGet
- OpenGL Matrix Conventions
- Sending Matrices to OpenGL
- System.Numerics Transposed Notation Discussion
- Row-Major vs Column-Major in OpenGL