When things bog down, check your inner loop. That's the code being run the most often, so even a small inefficiency there stacks up. Here we see:
Color color = Color.Black;
var rnd = Random.Shared.Next(0, 2);
if (rnd % 2 == 0)
{
color = Color.White;
}
bitmap.SetPixel(x, y, color);
Two top suspects:
Random.Shared.Next(0, 2)
- that "Shared" means this object is shared between threads, so to protect it from bugs when two threads try to generate a random number at the same time, there's probably a mutex (mutual exclusion) lock guarding it. That means for every single pixel, this code is acquiring and releasing a lock. That's relatively inexpensive when there are no other threads contesting it, but we have to check every time.Make your own local instance of
Random
outside the loop and use that to avoid this overhead.This is also using a heavier-duty random algorithm than you need (probably a Mersenne Twister). For a basic static effect like this, you could probably get away with a simple Xorshift or similar PRNG that you can run entirely out of L1 cache.
bitmap.SetPixel(x, y, color)
- setting pixels one by one is often very slow, as we need to make sure the copy of the bitmap we're writing to isn't currently being used, take exclusive control of it, set one tiny bit of memory, then potentially re-upload the changed version to the GPU for rendering. (I don't know exactly what steps GDI uses though)Game APIs will often expose a way to set the whole bitmap at once, which is much more efficient. We'll set our colour bits in our own buffer or array, then only at the end of the loop do we ask the bitmap to replace its entire contents with that array.
I'm not deeply familiar with GDI, but it looks like there is a Bitmap constructor that takes a pointer to a raw array of bytes that might be suitable foryou can call
Bitmap.LockBits
to get access to the bitmap'sBitmapData
buffer before the loop, write all your data into that usewithout extra overhead, then unlock it once at the end. (Thanks to Basic for suggesting a better route than I'd initially found!)
In a typical game context, we'd usually get this work off the CPU entirely and do noise generation on the GPU in a shader. That way we can process many pixels in parallel, rather than one at a time in sequence like CPU code. So you may want to consider switching from Windows Forms to a tech stack that makes it easier to offload graphics work this way. Engines like Godot and Unity both support C#, while giving you a richer toolbox of features that are helpful for game development.