C# woes

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
C# woes
by on (#103228)
Hello all!

I'm trying to ultimately program a NES metatile editor with C# for this game I've been working on.. the only thing thus far I've been able to complete was drawing an 8x8 tile to a picturebox.. The whole C# class/stuct stuff really confuses me but I have a basic understanding of it. My question is what steps would I go through to program a routine to draw an entire CHR page rather than a single 8x8 tile.. It's probably simple and I'm over thinking it, but, I dunno. Than, I would like to know how to arrange those into an array of 8x8 tiles where I could draw a 16x16 tile by saying upper left for 16x16 tile 0x02 is 8x8 tile 0x1C and so on..

Thanks.
Re: C# woes
by on (#103241)
zkip wrote:
What steps would I go through to program a routine to draw an entire CHR page rather than a single 8x8 tile


Are you trying to draw a whole tile table, or a whole screen out of tiles?

Personally, I tend to use very simple metatile structs.
Code:
struct MetaTile
{
    public byte topLeft;
    public byte topRight;
    public byte bottomLeft;
    public byte bottomRight;
}

Some utility functions to extract metatiles from a byte array (and/or stream) and write them back are usually handy and simple to write.

As far as drawing screens made out of tiles, I've written plenty of NES level editors, and in my experience, GDI+ simply isn't fast enough with tiled images to make anything more than a bare-bones quick-and-dirty editor. If that's what you're after then you're all set. For my needs I ended up writing my own drawing code that supports drawing tile-based images with palettes. If you're interested, I'd be glad to share the code.

As far as actually drawing things out, I tend to use a set of classes along these lines:
  • GameScreen - Represents the data that defines a screen. The actual data would vary depending on whether you're using an object-based layout, a metatile-based layout, or something else.
  • NameTable - Represents an NES name table (32x30 tiles), tile and attribute data. The GameScreen would "draw" itself to the NameTable by telling it which tiles go where.
  • ScreenRenderer - Takes a NameTable and a tile source and renders a screen image to a bitmap.

Again, if you're doing something Q&D, you might not want to spend a ton of time architecturing. You could just declare a couple of arrays to represent your nametable and write a single function that loops over the nametable and draws each tile.

If it sounds like I'm severely over-thinking things (yeah... I tend to do that on occasion), it basically breaks down to this:
Code:
class Example {
    byte[,] ntTiles = new byte[32,30];
    byte[,] ntPalettes = new byte[32,30]; // If applicable

    Bitmap screenImage = new Bitmap(256, 240, 32bppwhatever);
    Graphics gScreenImage; // Initialize with screenImage (if you're using GDI)
 
    void DrawScreen() {
        // lay out your nametable here
    }

    void RenderScreen() {
        for(tileY as int = 0; tileY < 30; tileY++) {
            for(tileX as int = 0; tileX < 32; tileX++) {
                DrawTile(tileX * 8, tileY * 8, ntTiles[tileX, tileY], ntPalettes[tileX, tileY]);
            }
        }
    }
   
    void DrawTile(blah blah) {
        // It sounds like you've got this figured out
    }
}
Re: C# woes
by on (#103248)
Thanks a lot for writing such a detailed response.. but honestly I'm still sort of lost and so confused... :x
Yes, eventually my plan is a level editor but at the moment, I just need this metatile editor.

Also,
Code:
    void DrawScreen() {
        // lay out your nametable here
    }

What exactly goes here??

Here is my 8x8 tile drawing routine:
Code:
        private Bitmap _8x8(byte[] tileDat, Bitmap canvas, Color[] palDat)
        {
            byte plane0;
            byte plane1;
            byte final;

            for (int y = 0; y < canvas.Height; y++)
            {
                plane0 = 0;
                plane1 = 0;
                for (int x = 0; x < canvas.Width; x++)
                {
                    plane0 = 0;
                    plane1 = 0;
                    plane0 = Convert.ToByte(((tileDat[y]) & (0x80 >> x)) >> (7 - x));
                    plane1 = Convert.ToByte(((tileDat[y + 8]) & (0x80 >> x)) >> (7 - x));
                    final = Convert.ToByte((plane1 << 1) + plane0);
                    canvas.SetPixel(x, y, palDat[final]);

                }
            }
            return canvas;
        }

I'm not sure how to incorporate this into what you're talking about. I'm terribly sorry for the bother, but I'd really like to fully understand this.
Re: C# woes
by on (#103285)
zkip wrote:
Yes, eventually my plan is a level editor but at the moment, I just need this metatile editor.

Well then you can safely ignore most of what I said for now. One thing I'll re-emphasize is that GDI+ is a little slow, but sometimes workable. On the other hand, using SetPixel makes GDI+ look like greased lightning. If you really want to avoid the much-more-complicated but oh-so-much-faster approach of directly modifying raw image data, then you might be able to get away using SetPixel to load the tiles. You'll have to try and see how fast it is.

I would recommend drawing all your tiles to an image that is 8 pixels wide and 2048 (8 px * 256 tiles) pixels tall. Basically, you want to stack all your tiles together vertically. That way the location for tile x would be (0, 8 * x).

It looks like you're already almost there. All you need to do is accept a tile number as a parameter and adjust where you draw your pixels based on the tile number. If you use a vertical arrangement like I described:

Code:
canvas.SetPixel(x, y + tileNumber * 8, palDat[final]);


I personally load the whole ROM into a single byte array, so if I snarf-ified your code a little it would look like this:
Code:
Bitmap Load_8x8(int tileNum, byte[] rom, int tileOffset, Bitmap canvas, Color[] palDat)
        {
            byte plane0;
            byte plane1;
            byte final;

            for (int y = 0; y < 8; y++)
            {
                for (int x = 0; x < 8; x++)
                {
                    plane0 = Convert.ToByte(((rom[tileOffset + y]) & (0x80 >> x)) >> (7 - x));
                    plane1 = Convert.ToByte(((rom[tileOffset + y + 8]) & (0x80 >> x)) >> (7 - x));
                    final = Convert.ToByte((plane1 << 1) + plane0);
                    canvas.SetPixel(x, y + tileNum * 8, palDat[final]);

                }
            }
            return canvas;
        }

Then you can load a whole page of contiguous CHR like so:
Code:
void LoadWholeChrPage(int chrOffset) {
    Bitmap chr = new Bitmap(8, 2048, PixelFormat.32bitgoodness);

    for(i as integer = 0; i < 256; i++) {
        int tileOffset = chrOffset + i * 16;
        Load_8x8(i, _theRom, tileOffset, chr, _thePalette);
    }
}


Hope that's a little more helpful.
Re: C# woes
by on (#103287)
Unless you absolutely want to write your own editor, you could try MapEd Pro, it has a metatile editor.
Re: C# woes
by on (#103298)
Ugh, I'm terribly sorry.. I just can't grasp anything :oops: .. I finally understand this, however, now I can't figure out how to define each 8x8 as separate tiles to use to construct a 16x16 tile in a separate picture box. Would this be the metatile structs you referred to earlier?

Thanks again, and sorry for so much bother.
Re: C# woes
by on (#103303)
It sounds like you're pretty new to C#. I'm not sure whether you're getting stuck on the C# side of things or the underlying concepts. I don't mind in the least trying to help, but right now I'm just basically guessing which blanks need to be filled in.

I know you probably already understand this, but just to walk through the logic... A complete CHR table contains 256 tiles. Each tile is identified by its index. So if you load your tiles into a vertical strip, the top tile would be tile 0, the next one down would be 1, and so on.

The metatile structure simply stores the four tile numbers that make up a metatile. So to store all your actual metatile data you could declare an array of MetaTile or a List<MetaTile>, depending on which one suits your needs better. An array might be simpler if you want a fixed number of metatiles. A List<MetaTile> would be easier if you want to be able to add/remove/insert MetaTile entries.

Code:
List<MetaTile> metatiles = new List<MetaTile> ()

void Example() {
    // Create and initialize a metatile and add it to the list

    MetaTile mt = new MetaTile();
    mt.topLeft = 0x40;
    mt.topRight = 0x41;
    mt.bottomLeft = 0x50;
    mt.bottomRight = 0x51;

    metatiles.Add(mt);
}


The trickier part is presenting the data in the UI and letting the user manipulate it. If you want to draw a metatile, you'll probably want to start with a function that draws an individual tile by index.
Code:
void DrawTile(Graphics target, byte tileIndex, int x, int y) {
    // Determine where we will grab the tile from, assuming tiles are stored in a vertical strip
    var sourceRect = new Rectangle(0, tileIndex * 8, 8, 8);

    // Determine where it will be drawn
    var destRect = new Rectangle(x, y, 8, 8);

    // Draw it
    target.DrawImage(tileSourceImage, destRect, sourceRect);
}

Then you can simply call this function four times to draw a metatile.
Code:
void DrawMetatile(MetaTile mt, int x, int y) {
    DrawTile(mt.TopLeft, x, y);
    DrawTile(mt.TopRight, someX, someY);
    // ... and two more times for the bottom tiles
}

void Usage() {
    // Draw the first metatile (number 0) at the location 0,0
    DrawMetatile(metatiles[0], 0, 0);
}


Hopefully that clears things up a bit.
Re: C# woes
by on (#103313)
Thank you so much snarfblam. :D I owe you man. I do have a few more questions though; :?

This is working like I wanted it to, however, any suggestions as to how to get everything all cleaned up and how to arrange it to where I have a page of metatiles displayed? Such as rows? I.E Row 0 contains metatiles 0-A etc.

Edit: Erm.. I see what you mean now about the slowness.. It takes ~20 secs. to draw 49 metatiles.. :? I'd like for you to explain the faster method if possible. Thanks again.
Re: C# woes
by on (#103334)
How are you drawing the tiles? In my custom tool I can draw a whole screen of 240 16x16 metatiles many times per second without any slowdown at all, so it shouldn't take long to draw 50 metatiles. Have you tried profiling your code to find out where it's taking so long?
Re: C# woes
by on (#103335)
In C#, if you still want to use the awful System.Drawing way of drawing images, at least use LockBits as your way to draw stuff.
Re: C# woes
by on (#103336)
Basically, I'm doing nothing more than what is already shared here in this thread... the 8x8 function to render the 8x8 and the DrawTile, and DrawMetatile functions from snarfblam to construct the full metatile.

Anyways, here is the whole shabang complete with trimmings.
Re: C# woes
by on (#103337)
Don't use SetPixel, it's too slow. Instead use LockBits and Marshal.Copy along with an int array.
Something like this:
Code:
            int[] pixels = new int[128 * 128];
            //your code here, set the pixels.  A value is like R + (G << 8) + (B << 16).  The index in the array = X + Y * width
            Bitmap bmp = new Bitmap(128,128,  System.Drawing.Imaging.PixelFormat.Format32bppRgb);
            var bits = bmp.LockBits(new Rectangle(0, 0, 128, 128), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppRgb);
            Marshal.Copy(pixels, 0, bits.Scan0, pixels.Length);
            bmp.UnlockBits(bits);
            //assign bmp somewhere like to a picture box or panel


If you already have a bitmap to store the new image into, there's no need to create a new one.
Re: C# woes
by on (#103338)
Erm.. could it really be ONLY the SetPixel doing this?? I just tried a class from here. And it seems like it's even slower... :x

By doing this
Code:
                    LockBitmap lockBitmap = new LockBitmap(canvas);
                    lockBitmap.LockBits();
                    lockBitmap.SetPixel(x, y + tileNum * 8, palDat[final]);
                    lockBitmap.UnlockBits();

in the Load_8x8 function and using that class it still hasn't loaded yet so it's slower apparently.....
Re: C# woes
by on (#103339)
Basically, you need to make ALL your drawing happen on the int array, then at the very very end, you copy that to a bitmap. So you won't be making a 8x2048 image, and doing rectangle copies or anything like that.
Re: C# woes
by on (#103340)
Dwedit wrote:
Basically, you need to make ALL your drawing happen on the int array, then at the very very end, you copy that to a bitmap. So you won't be making a 8x2048 image, and doing rectangle copies or anything like that.

Argh... I'm so confused.. I'm sorry, but then what exactly do I need to do. Get rid of the .DrawImage parts?? I'm so lost.. :?

I'm sorry for so much bother on everyone and thanks.
Re: C# woes
by on (#103341)
Here's something I quickly whipped up:
http://pastebin.com/DVYrxjJv

I decided to throw everything into a class called "NesTileDrawing" just to pass less parameters to the draw function.
Re: C# woes
by on (#103371)
Note that rectangle copies aren't the slow part in anything what you're doing; SetPixel is the culprit as it checks the range of x and y every time and needs to compute the array index every time, too.

Dwedit's code is a way to do it, although I prefer decoding individual tiles and storing them in a List<Bitmap>, as I can easily reorder tiles and tile display as much as I like (e.g. 3 tiles per row or 4 tiles per column). Decoding like in his code is undefined IIRC, because the image stride (line width) doesn't always have to be the same as width*sizeof(pixel). In this case it should work out though :)
Re: C# woes
by on (#103408)
Jsolo wrote:
Note that rectangle copies aren't the slow part in anything what you're doing; SetPixel is the culprit as it checks the range of x and y every time and needs to compute the array index every time, too.

Well... kinda. Accessing image data requires some preparation before and cleanup after. Using SetPixel causes prep and cleanup to occur for every single pixel, which is why it is incredibly slow. If you draw all of your images to a tile sheet and use Graphics.DrawImage to draw your images, you cut way down on redundant housekeeping, but Graphics.DrawImage is designed with flexibility as a priority over speed.

Also, am I the only one who uses 8-bit formats to take advantage of the ability to change palettes?
Re: C# woes
by on (#103417)
snarfblam wrote:
. If you draw all of your images to a tile sheet and use Graphics.DrawImage to draw your images, you cut way down on redundant housekeeping, but Graphics.DrawImage is designed with flexibility as a priority over speed.

Surely you don't need optimized code that runs in 1ms instead of 5ms for an editing tool written in C#. I believe DrawImage is a way easier solution than writing your own blitter :)

snarfblam wrote:
Also, am I the only one who uses 8-bit formats to take advantage of the ability to change palettes?

I do that too ;)
Re: C# woes
by on (#103435)
Jsolo wrote:
Surely you don't need optimized code that runs in 1ms instead of 5ms for an editing tool written in C#. I believe DrawImage is a way easier solution than writing your own blitter :)

It depends on how much needs to be drawn and how often. I wrote my custom blitter for my Metroid level editor, initially because it made the program feel much more responsive when dragging objects on the screen. When I modified the program to show as many screens as will fit on your monitor, allowing you to scroll freely, that optimization became absolutely essential. And in my experience, the difference between DrawImage and a custom blitter for tile-based graphics is a little more dramatic that you realize. The difference was very noticeable even when the program only dealt with one screen at a time. Also, like I said, I use indexed image formats to allow the use of and changing of palettes. GDI+ cannot manipulate images that use an indexed format.

However, while it is nice to have super-fast drawing code (or more flexible palette support), it usually isn't necessary. Especially if you're still learning. It's probably better in this case to focus on how to construct images from tiles rather than trying to write your own blitting code.

Zkip, maybe you should post the code that uses DrawImage. Like Jsolo said, the performace should be acceptable at the very least.
Re: C# woes
by on (#103567)
Erm, sorry for taking so long to get back.. I'm going through a 10 month break up now and time is creeping.. and I'm really confused about life but as they say it goes on, I reckon.

So, I've rewritten a few things, but Dwedit's code has really helped me along. I've moved the metatile struct to a constructor in a separate class. I'm still having trouble though :/ I have the full definitions I need in a byte array and I can't figure out how to put it all in a for loop to display them all. Any chances I could snag some help on this? Basically I put a metatile together and draw it like this:
Code:
       var gfxeditor = new NesTileDrawing(ROM.chrData, palette, pixels, 256);
     MetaTile tilename = new MetaTile(0xa5,0xa6,0xa7,0xa8); <- need help here
            DrawMetatile(tilename, 0, 0, gfxeditor);

Code:
        void DrawMetatile(MetaTile mt, int x, int y, NesTileDrawing source)
        {
            source.DrawTile(x, y, mt.topLeft);
            source.DrawTile(x + 8, y, mt.topRight);
            source.DrawTile(x, y + 8, mt.bottomLeft);
            source.DrawTile(x + 8, y + 8, mt.bottomRight);
           
        }

NesTileDrawing is Dwedit's code from a couple posts back.

Thanks.
Re: C# woes
by on (#103573)
You looking for something like this?
Code:
byte[] bytes = File.ReadAllBytes("metatiles.bin");
List<Metatile> list = new List<Metatile>();
for (int i = 0; i <= bytes.Length - 4; i += 4)
{
    var newMetatile = new Metatile(bytes[i], bytes[i+1], bytes[i+2], bytes[i+3]);
    list.Add(newMetatile);
}

for (int i=0; i<list.Count; i++)
{
  x = i % 16;
  y = i / 16;
  var metaTile = list[i];
  metaTile.DrawMetatile(metaTile, x*16,y*16,source);
}
Re: C# woes
by on (#103611)
This is working amazingly now.. Thank you :D Now I just need to figure out how to allow each tile it's own palette... I guess this is going to be a hardship? Involving heavily modifying the NesTileDrawing class?

I'm terribly sorry that I can't grasp this.. I hope I'm not too bad of an thorn. :?