Dark Bit Factory & Gravity

PROGRAMMING => C / C++ /C# => Topic started by: mammon on April 09, 2017

Title: [C#/GDI+] Xor Texture Tunnel (aka Fake 3D)
Post by: mammon on April 09, 2017
So, before you even consider just copy/paste the following code - you should know this:
this code is beyond flawed, and is slow as hell!
And with that in mind, I'm here asking for this board's help in achieving a better way to make this tunnel animated...

I have tried using both jagged arrays, and multidimension arrays (which you can clearly see) - but it still gives this "lag" feature no matter what I change :/
I suspect very much that it is due to the pixel manipulation, which seem to be too slow :(

Oh yes, I have also tried using Parallel.For - but that didn't even work at all (it just crashes due to memory being inaccesible!)...
And yes, I have also considered using every OpenGL and DirectX solution instead (and tried a few, but still slow as shit!)...

So here it is, no more nagging about me failing - hopefully someone in here can aid me in making this possible :D

Oh by the way, the code is derived from "Lode's Computer Graphics Tutorial" - found here: http://lodev.org/cgtutor/tunnel.html (http://lodev.org/cgtutor/tunnel.html)
Code: [Select]
//Using FastBitmap.cs by boogop which is found on PSC ;)
//http://www.Planet-Source-Code.com/vb/scripts/ShowCode.asp?txtCodeId=5653&lngWId=10

    public partial class Form1 : Form
    {
BackgroundWorker bw;
        Bitmap bmp;
        int fHeight;
        int fWidth;
        Graphics gForm;
        Graphics gBmp;

/*Multi-dimension array:*/
int[,] distanceTable;
/*Jagged array:*/
        int[][] distanceTable2;
/*Multi-dimension array:*/
        int[,] angleTable;
/*Jagged array:*/
        int[][] angleTable2;

Bitmap myTexture;

int animation = 0;
        int rotation = 0;

        public Form1()
        {
            InitializeComponent();
            InitPictureBox();
            this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.DoubleBuffer | ControlStyles.Opaque | ControlStyles.ResizeRedraw, true);
        }

        void InitPictureBox()
        {
            bmp = new Bitmap(pictureBox1.Width, pictureBox1.Height);
            gForm = pictureBox1.CreateGraphics();
            gBmp = Graphics.FromImage(bmp);

            fWidth = bmp.Width;
            fHeight = bmp.Height;
        }

        void DoXorTunnel()
        {
            while (true)
            {
                animation += 3;
                rotation += 1;

                Application.DoEvents();

                var myBitmap = new Bitmap(bmp.Width, bmp.Height);
                var shiftX = myTexture.Width + animation;
                var shiftY = myTexture.Height + rotation;

                var a = new FastBitmap(myTexture);
                var b = new FastBitmap(myBitmap);
                for (int y = 0; y < bmp.Height; y++)
                {
                    Application.DoEvents();
                    for (int x = 0; x < bmp.Width; x++)
                    {
                        /*Multi-dimension array:*/
                        //b.SetPixel(x, y, (a.GetPixel((distanceTable[x, y] + shiftX) % myTexture.Width, (angleTable[x, y] + shiftY) % myTexture.Height)));

                        /*Jagged array:*/
                        b.SetPixel(x, y, (a.GetPixel((distanceTable2[x][y] + shiftX) % myTexture.Width, (angleTable2[x][y] + shiftY) % myTexture.Height)));
                    }
                }

                a.Release();
                b.Release();
                gForm.DrawImageUnscaled(myBitmap, 0, 0, fWidth, fHeight);
            }
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            myTexture = XorTexture(128, 128);

            CreateTables();

            bw = new BackgroundWorker();
            bw.DoWork += Bw_DoWork;
        }

        private void Bw_DoWork(object sender, DoWorkEventArgs e)
        {
            InitPictureBox();
            DoXorTunnel();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            bw.RunWorkerAsync();
        }

        Bitmap XorTexture(int texHeight, int texWidth)
        {
            var myB = new Bitmap(texWidth, texHeight);
            var b = new FastBitmap(myB);

            for (var y = 0; y < texHeight; y++)
            {
                for (var x = 0; x < texWidth; x++)
                {
                    var c = (x ^ y) % 256;
                    b.SetPixel(x, y, Color.FromArgb(c, c, c));
                }
            }
            b.Release();
            return myB;
        }

        void CreateTables()
        {
            distanceTable = new int[bmp.Width, bmp.Height];
            distanceTable2 = new int[bmp.Width][];
            for (var i = 0; i < bmp.Width; i++)
                distanceTable2[i] = new int[bmp.Height];

            angleTable = new int[bmp.Width, bmp.Height];
            angleTable2 = new int[bmp.Width][];
            for (var i = 0; i < bmp.Width; i++)
                angleTable2[i] = new int[bmp.Height];

            for (var y = 0; y < bmp.Height; y++)
            {
                for (var x = 0; x < bmp.Width; x++)
                {
                    int angle, distance;
                    float ratio = 32.0f;
                    distance = (int)(ratio * myTexture.Height / Math.Sqrt((x - bmp.Width / 2.0) * (x - bmp.Width / 2.0) + (y - bmp.Height / 2.0) * (y - bmp.Height / 2.0))) % myTexture.Height;
                    angle = (int)(0.5 * myTexture.Width * Math.Atan2(y - bmp.Height / 2.0, x - bmp.Width / 2.0) / Math.PI);
                    distanceTable[x, y] = distance;
                    angleTable[x, y] = angle;

                    distanceTable2[x][y] = distance;
                    angleTable2[x][y] = angle;
                }
            }
        }
    }

Any suggestions to make this pixel manipulation work faster?
Title: Re: [C#/GDI+] Xor Texture Tunnel (aka Fake 3D)
Post by: Rbz on April 09, 2017
Hi  :hi:

I don't know much about C#, but for what I can see, you are doing it mostly correctly - pre calculating the xor texture  and tunnel table.

But, I would probably not recreating a new Bitmap and FastBitmap everytime and it could be the problem you are having.

So remove those from the while loop, init them on Form1_Load, and see what happens:
Code: [Select]
var myBitmap = new Bitmap(bmp.Width, bmp.Height);
var a = new FastBitmap(myTexture);
var b = new FastBitmap(myBitmap);
Title: Re: [C#/GDI+] Xor Texture Tunnel (aka Fake 3D)
Post by: mammon on April 10, 2017
Heya, and thanks for the tip - however, its no use in here... I've made a small modification on the FastBitmap class to actually able one todo such operation  however, to no gain at all :(
Seems GDI+ (your CPU), is not able to work with anything bigger than something tiny in the end... Pherhaps SFML or SDL is the working way to go in the end...
Title: Re: [C#/GDI+] Xor Texture Tunnel (aka Fake 3D)
Post by: boogop on April 11, 2017
Glad to see some c#! All my contest entries so far have been in c#.

Two items jump out at me
1. Multi-dimension arrays. The compiler has to do bounds checks on those and it will tool your performance.
2. Getpixel. Even with fastbitmap these are bad. They make flame effects problematic in this language.

I can't tell what size your picturebox is but using gdi 640x480 is going to be about the limit. I find 350x350 or thereabouts works pretty good but above that starts really slowing down.

The game in c# is all about optimization. When I've done contest entries I sh1t you not the last couple months are spent optimizing.
Title: Re: [C#/GDI+] Xor Texture Tunnel (aka Fake 3D)
Post by: boogop on April 11, 2017
Note: much discussion on bounds checking exists and unless you read the IL it's not easy to tell what the compiler is doing.
Title: Re: [C#/GDI+] Xor Texture Tunnel (aka Fake 3D)
Post by: mammon on April 11, 2017
True that - I've already had my fair share of optimizing the code to the point of my brain breaking down... And as of IL, well - my nickname isn't chosen by random (my nickname is known a lil' bit in the scene so to speak), I have reversed IL binaries for over a decade now :P

But truth be told, IL is not very efficient when it comes to actual optimization due to the limitations of running ANY code behind some obscured VM :(

That being said, I have now come to a conclusion: only way to make this work at all, is by trying to utilize SDL or something equal... Surely OpenTk/Tao would be a suggestion, but thats even more barebone in comparison to SDL (requires alot of work to make a simple "engine" so to speak) - so I'm gonna attempt to make something with SDL afaik :)

Anyways, I'll keep you guys posted with updates when I make some progress ;)
Title: Re: [C#/GDI+] Xor Texture Tunnel (aka Fake 3D)
Post by: boogop on April 11, 2017
Got a little more speed out of it. I love c# but eventually we run into the limitations of the language    :telloff:

Code: [Select]
        FastBitmap a = new FastBitmap(myTexture);
        FastBitmap b = new FastBitmap(myBitmap);
        for (int x = 0; x < bmp.Width; x++)
        {
          fixed (int* pDistance = &distanceTable[x, 0])
          {
            fixed (int* pAngle = &angleTable[x, 0])
            {
              for (int y = 0; y < bmp.Height; y++)
              {
                int xp = *(pDistance + y);
                int xx = (xp + shiftX) % 128;
                int ap = *(pAngle + y);
                int yy = (ap + shiftY) % 128;
                Color c = a.GetPixel(xx, yy);

                b.SetPixel(x, y, c);
              }
            }
          }
        }
Title: Re: [C#/GDI+] Xor Texture Tunnel (aka Fake 3D)
Post by: hellfire on May 10, 2017
Maybe a bit late but what the heck...

Code: [Select]
b.SetPixel(x, y, (a.GetPixel((distanceTable2[x][y] + shiftX) % myTexture.Width, (angleTable2[x][y] + shiftY) % myTexture.Height)));In order of relevance:
Modulo (%) is a division and expensive. You can use bit-masking if your texture-size is a power-of-2 (e.g. use "& 255" if your texture is 256x256 pixels wide).
Try not to call SetPixel for every pixel - if you fill scanlines from left to right, the next pixel is always one dword further.
Try not to call GetPixel for every pixel - with 256x256 textures your texel is at [( v<<8 )+u]
"[ x ][ y ] " reads your memory from top to bottom which makes bad use of the cache.

About the cache:
For every pixel you read, the cache fetches 64 consecutive bytes (in the hope that you want to access the following bytes, too).
But instead you're reading another pixel in the next row (x+1) and the cache reads another 64 bytes.
So when you reach the bottom of your table, there are height*64 bytes in the cache.
When start to read from the next column (y+1), you would fetch data was already read into the cache - but is it still there?
The L1 Cache is 32kB so the height of your table must be smaller than 512 before the cache is full.
But you actually want to keep the cache for your texture reads (which happen in a much more random order).
And since other tasks are also using the cache you can't expect to get much more than half of it.
Of course when L1 fails, there's still L2 and L3 and you'll probably see significant diffiferences when you consume more than a megabyte of cache space.