Renderune
- 1 Devlogs
- 4 Total hours
ANSI Console rendering engine, made in .NET 10
ANSI Console rendering engine, made in .NET 10
⬇️ A lot of text for the reading enthusiasts coming :P
~3 years ago school tasked us with creatively coding a console minigame in .NET using Unicode characters. I got inspired by my favorite game of all time, Geometry Dash. I got a basic renderer, 3 levels, saves, menus & controls working, but the app was full of bottlenecks - rough object placement, inaccurate collisions, one shared thread for controls/physics/rendering, and color switching via Console API instead of ANSI. Result: ~3-10 FPS and janky logic.
Years later, with way more knowledge on rendering, multithreading, performance & physics, I felt thrilled to redo it properly.
So here we are: building a console rendering engine in C# .NET.
Setup a solution with Lib (library code) and Demo (testing features). It all started with an empty canvas - “In The Beginning There Was Darkness”:
⬇️Screenshot 1 below⬇️
The ‘Screen’ was a 2D array of custom objects, so changing a pixel at [X,Y] was easy. Instead of Console.ForegroundColor/BackgroundColor (the biggest bottleneck last time), I used ANSI escape sequences - strings telling the terminal what to do to the next characters, here changing the background color of a whitespace ‘pixel’.
Pros & Cons of ANSI:
+ Faster - build one long string of pixels with their ANSI color codes and let the terminal resolve it, instead of calling the console API per-pixel.
+ Full 16.8 million RGB colors vs ConsoleColor’s 16.
- Not universally supported, but most modern terminals handle it fine.
Here’s changing a single pixel on a small canvas:
⬇️Screenshot 2 below⬇️
Why the long pixel? Console cells are tall, not square. Fixing that (print twice, or double precision) is for the next vlog. For now we keep pixels stretched - which also lets us layer text on top via the ForegroundColor layer, on top of pixels drawn to BackgroundColor.
Added more utilities next: painting the whole screen, painting a rectangular selection, and letting text overwrite its own BackgroundColor:
⬇️Screenshot 3 below⬇️
Prefilled the grid with black Pixels via Array.Fill. Since Pixel was a class, every cell got the same instance. So the first blue DrawPixel call after Fill/DrawRect turned the entire grid blue - all pixels shared one reference. Fixed by making Pixel a record struct, but not before accidentally bluescreening the whole canvas with a single pixel call:
⬇️Screenshot 4 below⬇️
Switched back to a 1D array for the pixel grid - closer to how real buffers work (one continuous memory block), and bulk ops like full-screen painting get simpler. For [X,Y] access, added a private ref Pixel this[uint x, uint y] custom indexer. And since this grid is the frame’s render buffer, we don’t render on every change - changes go into the frameBuffer and wait for render().