You are browsing as a guest. Sign up (or log in) to start making projects!

DenizCiger

@DenizCiger

Joined June 8th, 2026

  • 4Devlogs
  • 2Projects
  • 0Ships
  • 0Votes
Open comments for this post

3h 44m 53s logged

⬇️ A lot of text for the reading enthusiasts coming :P

Rendervlog 1 - The first Canvas

A little story about the past (skip if you just want the dev stuff)

~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.

  • Why this framework? It’s the one I’m most fluent (and have the most fun) in.
  • Why not use an existing renderer? I want to actually learn rendering principles - way simpler to apply to console apps than diving into something like Vulkan - and couldn’t find a C# lib fitting my exact needs anyway.
  • An entire engine for one game??? No, I’ll reuse this for future console projects. Maybe even run Doom on it once it’s done 👀

Now onto the actual development :D

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⬇️

Bug(s) during development 🪲

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⬇️

Final Optimizations

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().

0
0
41
Open comments for this post

3h 55m 2s logged

I worked quite a bit on some minor QoL tweaks like taskbar pinning, desktop icons, and improved window instance management, however most of my time probably went into the whole ‘file system’ logic.

I read a bit about System development online, which taught me about tree structures and optimized child/parent architectures. For this project however we can stay simple as with those few files we likely won’t run into any intense RAM problems anyways.
By iterating through the parent nodes until it hits the root node, and from that exploration data, the explorer then constructs the visible path name for the user.

A few features are still on the radar, but I’m excited that I’ll soon be able to finish & ship this fun little project and tackle the next challenge!

0
0
1
Open comments for this post

2h 40m 16s logged

Added logic for opening, minimizing & opening windows. Also made windows draggable and focusable. Made sure to make window management logic more robust & future-proof

0
0
2
Open comments for this post

1h 17m 5s logged

Setup the initial project with a basic static window and a taskbar. Then refactored everything to reusable react components to stay organized

0
0
1

Followers

Loading…