Skip to content

Performance Benchmarks

shmellyorc edited this page Sep 17, 2026 · 1 revision

Performance Benchmarks

This page documents reproducible performance measurements for VOID Engine 2.1.0, including a direct 2D sprite rendering comparison with MonoGame 3.8.5.1.

The purpose of these benchmarks is not to claim that one framework will be faster in every game or on every machine. The goal is to publish the exact workload, configuration, hardware, and results so developers can see how VOID performs under a defined 2D rendering test.

Quick Result

In the tested 25,000-sprite workload, VOID completed frames with a 1.1258 ms median frame time compared with 1.2580 ms for MonoGame.

That is approximately 10.5% lower median frame time for VOID in this benchmark.

Both frameworks completed the measured hot path with 0 bytes of managed allocation.

Metric VOID 2.1.0 MonoGame 3.8.5.1
Whole frame mean 1.1478 ms 1.2787 ms
Whole frame median 1.1258 ms 1.2580 ms
Whole frame P99 1.5331 ms 1.7525 ms
Batch CPU median 0.9928 ms 1.0600 ms
Draw-command CPU median 0.3526 ms 0.4680 ms
End / Flush CPU median 0.6393 ms 0.5914 ms
Measured frames 8,712 7,820
Managed allocation 0 bytes 0 bytes

VOID completed about 11.4% more measured frames during the same 10-second measurement window.

These results apply to this specific benchmark and test system. They should not be interpreted as a universal statement that VOID is faster than MonoGame for every workload.


Test System

Component Configuration
CPU AMD Ryzen 7 1700X
GPU AMD Radeon RX 6600 8 GB
Memory 32 GB RAM
OS Linux
Window backend X11
Runtime .NET 10
VOID 2.1.0
VOID renderer Built-in OpenGL backend
MonoGame 3.8.5.1
MonoGame backend DesktopGL

VOID benchmark build hash:

956C2605CC5B820

Benchmark Workload

The comparison uses a deliberately simple static sprite workload so the test is focused on the rendering and batching paths rather than gameplay logic.

Setting Value
Sprites 25,000
Sprite size 32x32
Texture One in-memory white texture
Positions Deterministic
Blend mode Alpha
Resolution 1280x720
VSync Disabled
Fixed timestep Disabled
Build Release
Warmup 5 seconds
Measurement 10 seconds
VOID batch capacity 32,768
MonoGame offscreen target Enabled

No debugger was attached during the measurements.

The test uses the same logical sprite count, sprite dimensions, alpha blending, output resolution, warmup time, and measurement duration for both frameworks.


Why MonoGame Uses an Offscreen Target

VOID's normal window rendering path renders the game to its internal render target and then presents that texture to the window.

For the primary comparison, MonoGame was therefore run with an offscreen RenderTarget2D and a final presentation pass as well.

This makes the overall rendering structure closer to VOID's normal path instead of comparing VOID's offscreen-present path against a direct-to-backbuffer MonoGame path.

A separate MonoGame direct-backbuffer test was also performed during investigation and showed nearly identical performance to its offscreen test on this machine.


Important: Supersampling Must Match

During initial testing, VOID appeared to be roughly three times slower than MonoGame.

That result was wrong.

VOID's default SuperSample value is 4. The first benchmark configuration did not override it, so VOID was internally rendering a 1280x720 viewport at:

5120x2880

while MonoGame was rendering at:

1280x720

That is 16 times as many render-target pixels.

For a fair raw rendering comparison, the VOID benchmark explicitly uses:

.SetViewport(1280, 720)
.SetSuperSample(1)
.SetVsync(false)
.SetFixedTimeStep(false)

Once both frameworks were rendering at the same physical resolution, the apparent large VOID performance deficit disappeared.

This is also why benchmark configuration matters. A framework's default quality settings should not be mistaken for equivalent test conditions.


Final VOID Result

VOID BENCHMARK RESULT

Framework:      VOID
Version:        2.1.0.0
Scenario:       StaticSprites_BlendAB
Sprites:        25,000
Sprite Size:    32x32
Blend:          alpha
Batch Capacity: 32,768
Resolution:     1280x720
Frames:         8,712

Whole Frame:
  Mean:         1.1478 ms
  Median:       1.1258 ms
  P99:          1.5331 ms
  Min:          1.0583 ms
  Max:          6.8223 ms

Batch CPU:
  Mean:         1.0142 ms
  Median:       0.9928 ms
  P99:          1.3703 ms

Draw Calls CPU:
  Mean:         0.3612 ms
  Median:       0.3526 ms
  P99:          0.5210 ms

End / Flush CPU:
  Mean:         0.6526 ms
  Median:       0.6393 ms
  P99:          0.8396 ms

Draw Calls:     1
Allocated:      0 bytes

Final MonoGame Result

MONOGAME BENCHMARK RESULT

Framework:      MonoGame
Version:        3.8.5.1
Scenario:       StaticSprites_OffscreenPresent_BlendAB
Sprites:        25,000
Offscreen:      Enabled
Sprite Size:    32x32
Blend:          alpha
Resolution:     1280x720
Frames:         7,820

Whole Frame:
  Mean:         1.2787 ms
  Median:       1.2580 ms
  P99:          1.7525 ms
  Min:          1.1573 ms
  Max:          4.1940 ms

Batch CPU:
  Mean:         1.0846 ms
  Median:       1.0600 ms
  P99:          1.5242 ms

Draw Calls CPU:
  Mean:         0.4817 ms
  Median:       0.4680 ms
  P99:          0.8302 ms

End / Flush CPU:
  Mean:         0.6028 ms
  Median:       0.5914 ms
  P99:          0.7331 ms

Draw Calls:     6
Allocated:      0 bytes

What the Numbers Mean

Whole Frame

Whole-frame time is the most useful end-to-end measurement in this comparison.

VOID:

1.1258 ms median

MonoGame:

1.2580 ms median

VOID's median whole-frame time was approximately 10.5% lower in this workload.

The mean results tell a similar story:

VOID:      1.1478 ms
MonoGame:  1.2787 ms

VOID's mean whole-frame time was approximately 10.2% lower.

P99 Frame Time

P99 is useful because it shows how the slower end of normal frame delivery behaves without relying on a single worst-frame outlier.

VOID:      1.5331 ms
MonoGame:  1.7525 ms

VOID's P99 frame time was approximately 12.5% lower in this run.

Batch CPU

VOID:      0.9928 ms median
MonoGame:  1.0600 ms median

VOID's measured batch CPU time was approximately 6.3% lower.

Draw-Command CPU

VOID:      0.3526 ms median
MonoGame:  0.4680 ms median

VOID's measured draw-command CPU section was approximately 24.7% lower.

End / Flush

MonoGame was faster in the measured flush section:

VOID:      0.6393 ms median
MonoGame:  0.5914 ms median

MonoGame's measured value was about 8.1% lower here.

That means VOID still has an area worth profiling further even though its complete frame was faster in this workload.

Measured Frames

Both tests used the same 10-second measurement interval.

VOID completed:

8,712 frames

MonoGame completed:

7,820 frames

The higher frame count is consistent with VOID's lower average whole-frame time. VOID returned to the next frame sooner and therefore completed more frames during the same measurement period.


Managed Allocations

The final result for both frameworks was:

VOID:      0 bytes
MonoGame:  0 bytes

This measurement uses:

GC.GetAllocatedBytesForCurrentThread()

around the measured game-thread workload.

An Allocation Problem Was Found and Fixed

Earlier VOID runs consistently allocated about:

160 bytes per frame

At 8,152 measured frames, one run reported:

1,304,480 bytes

The renderer and sprite batcher were instrumented individually and were found to allocate 0 bytes per frame.

The allocation came from InputActionState.

The input action system was creating a new snapshot every update, and the snapshot constructor cloned two dictionaries even when the logical action state had not changed.

That meant two managed dictionary objects were being created every frame.

The system was changed so that:

  1. No action snapshot is created when no actions are registered.
  2. Existing immutable snapshots are reused while the logical action state is unchanged.
  3. A new snapshot is still created whenever an input transition requires one.

The important snapshot behavior remains intact:

Up -> JustPressed -> Pressed
Pressed -> JustReleased -> Up

Retained InputActionState values remain immutable and independent of future updates.

After the fix, the same allocation diagnostic reported:

After Draw -> OnUpdate     0.00 B/frame
OnUpdate body              0.00 B/frame
OnUpdate -> OnDraw         0.00 B/frame
Batch.Begin                0.00 B/frame
Sprite Draw loop           0.00 B/frame
Batch.End                  0.00 B/frame

Allocated:                 0 bytes

This was an engine-level optimization, not a benchmark-only workaround.


What Was Profiled During the Investigation

The benchmark investigation went considerably deeper than simply comparing two frame-time numbers.

Temporary profiling included:

  • CPU timing around batch begin, sprite submission, flush, presentation, and buffer swap
  • asynchronous OpenGL timer queries
  • forced GPU synchronization tests
  • sprite-size sweeps
  • alpha versus opaque blending
  • simplified fragment shaders
  • constant-color fragment shaders
  • rasterizer discard
  • disabled color writes
  • direct-backbuffer rendering
  • texture-backed framebuffer rendering
  • renderbuffer-backed framebuffer rendering
  • different vertex-buffer capacities and upload paths
  • managed allocation tracing by game-loop phase

Those experiments initially appeared to point toward a GPU raster/output bottleneck inside VOID.

After the supersampling mismatch was discovered, the results made sense: the experiments had been measuring VOID at a much larger physical render resolution than MonoGame.

The investigation was still useful because it verified several things independently, but the early VOID-versus-MonoGame conclusions were not valid until the resolutions matched.


Draw Call Counts

The final output reported:

VOID:      1 draw call
MonoGame:  6 draw calls

This number should be interpreted carefully.

VOID's counter reflects its sprite batch draw submission, while the MonoGame benchmark includes its internal sprite-batch flush behavior and presentation path.

The counters are useful for understanding each implementation, but they should not be treated as a perfectly identical cross-framework metric by themselves.

Whole-frame time is the cleaner comparison.


What This Benchmark Does Not Prove

This test does not prove that VOID will be faster in every game.

Real projects may behave differently depending on:

  • texture changes
  • sprite sorting
  • multiple blend modes
  • shaders
  • render targets
  • text
  • primitives
  • cameras
  • post-processing
  • asset streaming
  • CPU-heavy gameplay
  • driver behavior
  • operating system
  • GPU architecture
  • CPU architecture

This benchmark isolates one common 2D workload: a large number of static alpha-blended sprites using one texture.

More scenarios can be added over time.


Current Conclusion

For this 25,000-sprite test on the documented Linux/OpenGL system:

  • VOID and MonoGame are in the same overall performance class.
  • VOID completed the workload with approximately 10.5% lower median whole-frame time.
  • VOID completed approximately 11.4% more frames during the same measurement window.
  • Both frameworks measured 0 bytes of managed allocation on the benchmark game thread.
  • VOID's sprite batching and draw-command CPU measurements were lower in this workload.
  • MonoGame's measured End / Flush section was lower.
  • VOID's batcher did not show evidence of an allocation or architectural performance problem in this test.
  • Further VOID optimization work is most reasonably focused on profiling the flush/upload/submission path rather than rewriting the batching architecture.

Reproducing the Test

VOID:

dotnet Bench.Void/bin/Release/net10.0/Bench.Void.dll \
    --sprites 25000 \
    --capacity 32768 \
    --size 32 \
    --blend alpha

MonoGame:

dotnet Bench.MonoGame/bin/Release/net10.0/Bench.MonoGame.dll \
    --sprites 25000 \
    --offscreen true \
    --size 32 \
    --blend alpha

For VOID, make sure the benchmark configuration contains:

.SetViewport(1280, 720)
.SetSuperSample(1)
.SetVsync(false)
.SetFixedTimeStep(false)

A Release build should be used, and the benchmark should be run without a debugger attached.


Benchmark Date

These results were recorded on September 17, 2026 using VOID 2.1.0 and MonoGame 3.8.5.1.

Future engine versions, framework versions, drivers, or benchmark changes may produce different results.

When benchmark results are updated, the test configuration should be updated with them so the comparison remains reproducible.


Home · Rendering · Input · Custom Renderers · GitHub Repository

Clone this wiki locally