SFML and fast pixel drawing in C#

Started by
1 comment, last by Nypyren 6 years, 5 months ago

Hi. It's been a while since I posted here, and my last posts are almost about this exact same subject. Just saying to demonstrate how annoying this is to me.

Here is the problem : I'm trying to make a decent raycaster in C#. The main issue is that for this to happen, I need pixel by pixel drawing. My previous raycaster used VS GDI+, and trough several tricks involving pointers and filling a bitmap byte by byte, I was able to obtain half decent results, and make an online server-client style 3d engine complete with a map builder and several really cool features. I unfortunately wasn't able to expand the project further due to poorly written code (I am an hobbyist, I study Business Administration at Uni) and the fact that my quick hack for performance was barely able to carry the bare minimum of what I needed to make a very bare bone raycaster possible. This came with very real sadness, the realization that the project I spent almost 2 years on was essentially useless, bloated and impossible to expand on. 

Enough background. Now, starting anew, my main concern is to find a way to gain fast pixel by pixel control over the screen. I'm using SFML and C#. My current testbench is pretty simple, I'm using a method I found on the internet written for C++. I Adapted it for C#. I'm filling a Color[,] array (each color is a pixel) and then I copy the RGB values inside a byte[] array before moving them inside the texture buffer. I then display the texture on the screen. I'm not sure what the bottleneck is, the application is faster than my previous one, but it's still too slow for my liking. Raycasters work by redrawing stuff ontop of other stuff, and I fear that adding more stuff would creep it to an halt. I'm posting what I have as a testbench right now, any help would be greatly appreciated. Keep in mind I am not a professional programmer by any mean, I am pretty uncomfortable with pointers and true 3d stuff, but I will use them if I must. 


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SFML.Audio;
using SFML.Graphics;
using SFML.System;
using SFML.Window;

namespace RayCastFF
{
    class Program
    {
        public static int TestCounter = 0;
        public static Color[,] ScreenBuffer = new Color[640, 360]; //an array containing the color of all the pixel, this is intended to be the main target of all manipulation and draw call
        public static Texture MainViewPort = new Texture(640, 360);//main screen texture

        unsafe static void Main(string[] args)
        {
            //MAINWINDOW SETUP
            RenderWindow window = new RenderWindow(new VideoMode(640, 360), "RayCaster");

            while (window.IsOpen)//MAIN GAME LOOP
            {
                //CALL FOR UPDATE
                Update();

                //DRAW
                window.Clear();
                window.DispatchEvents();

                Sprite mainviewport = new Sprite(MainViewPort);
                window.Draw(mainviewport);//draw the texture over the screen

                window.Display();



                //TAKE INPUT


            }
        }
        static void Update()
        {
            TestCounter++;
            if (TestCounter > 639) { TestCounter = 0; }
            //RESET THE BUFFER (COULD BE REMOVED LATER I GUESS)
            for (int x = 0; x < 640; x++)
            {
                for (int y = 0; y < 360; y++)
                {
                    ScreenBuffer[x, y] = Color.Black;
                }
            }

            //DO STUFF
            DrawLine(Color.Red, TestCounter, 200, 100); //(for this test, i simply draw a moving line)

            //WRITING THE BUFFER INTO THE IMAGE
            //THIS SHOULD ALWAYS BE THE LAST STEP OF THE UPDATE METHOD
            byte[] pixels = new byte[640 * 360 * 4]; //640 x 360 pixels x 4 bytes per pixel
            Color[] cpixels = new Color[640 * 360];//intermediary step to keep everything clear
            for (int x = 0; x < 640; x++)
            {
                for (int y = 0; y < 360; y++)
                {
                    cpixels[x+(640*y)] = ScreenBuffer[x, y];//make an intermediary array the correct dimention and arrange the pixels in the correct position to be drawn (separate step to keep everything clean, I find this operation incredibly confusing mainly because I had no idea how the pixels are supposed to be arrenged in the first place(still kind of dont))
                }
            }
            for (int i = 0; i < 640 * 360 * 4; i += 4)//fill the byte array
            {
                
                pixels[i + 0] = cpixels[i / 4].R;
                pixels[i + 1] = cpixels[i / 4].G;
                pixels[i + 2] = cpixels[i / 4].B;
                pixels[i + 3] = cpixels[i / 4].A;
            }

            MainViewPort.Update(pixels);//update the texture with the array
        }

        //[X , Y]
        static void DrawLine(Color color, int Wpos, int Ytop, int Ybottom)//simple draw method making a vertical line
        {

            for (int y = Ybottom; y < Ytop; y++)
            {
                ScreenBuffer[Wpos, y] = color;
            }
        }
    }
}

What I'd like to end up with is a very fast way to draw the pixels on the window by the abstraction of a single 2d array of 640x360 unit that I could easily and simply manipulate. However, while being simple, it's also somewhat slow. It's also using 30% GPU load for some reason on a 1070GTX 8GB. Again, any help would be greatly appreciated.

Thanks in advance.

Advertisement

See those massive 'new <huge array>' operations in the middle of your Update loop?  Remove those.  Those allocations are going to be causing GC collections too frequently.  They're also going to be automatically filling the entire array with zeroes which you don't need since you immediately fill them yourself.

The part where you clear ScreenBuffer to black is eating a bunch of time.  In old programs, instead of clearing the screen and redrawing everything, we would update only the parts of the image that were changing.  Your line moves, so the old-school technique would be to 'erase' ONLY the pixels where the line was last time only, instead of setting the entire screen black first.  For mouse cursors in old apps, what I would do was save a backup copy of all of the pixels underneath where I was about to render the mouse cursor, then render the cursor, then on the next frame render the backup pixels to 'undo' rendering the mouse.

You have two full-array copy loops (ScreenBuffer -> cpixels, cpixels -> pixels), but you only need one ScreenBuffer -> pixels).  Get rid of the cpixels array as well.

In fact you should probably get rid of ScreenBuffer and only use pixels.  Just write to pixels and nothing else.  You will have to know which bytes are red/green/blue/etc but you want high performance so you've gotta remove as many "worthless" operations as possible to get it fast.

If SFML allows you to use uint[] instead of byte[], one assignment to a uint will be faster than four assignments to bytes.  This can be useful as one uint will be one pixel anyway.

C# *should* know how to optimize the constant subexpressions within inner loops such as (y*640) but you can try hand optimizing them yourself to see if it makes any difference.

Make sure you are allowing JIT to fully optimize your code.  If you're using Visual Studio this means using the Release configuration (or enabling optimization on the debug configuration) as well as {launching without the debugger attached or disabling Tools -> Options -> Debugging -> Suppress JIT optimization on module load}.

This topic is closed to new replies.

Advertisement