Author Topic: Adventures in C#  (Read 9366 times)

0 Members and 1 Guest are viewing this topic.

Offline lance.ewing

Adventures in C#
« on: November 25, 2016, 05:19:03 PM »
As mentioned in another thread, I'm going to attempt to teach myself C# over the coming months, with a goal to eventually fully port my old MEKA AGI interpreter to C#, thereby hopefully making it useful to the Visual AGI project. I'm also hoping to pick up some experience that will then make me useful in contributing to other parts of the Visual AGI project.

I've got some experience with writing a few simple games in Java, JavaScript, and way back in the late 90s, in C. I've also written a few VIC 20 emulators, the first in Pascal, then in C, and then in Java (the first two also having some assembly code included).

One of the first things I start thinking about when picking up a new language is how I'd implement the game loop. The emulation loop used in the emulators is similar in many ways.

What I started looking at in the case of C# was how to get direct access to the pixels of a Bitmap as an array of ARGB values. Turns out it wasn't as difficult as I was expecting. When I ported my Java VIC 20 emulator to use the libGDX library so it could run on Android, I had to work with an abstraction of OpenGL, which when you want to update every pixel on the screen on every frame is actually not as straight forward as you might think. The GPU doesn't really want to help you when you intend sending it a whole screen full of pixels on every frame.

I don't know what's happening under the hood with this approach, but the following is what I've found through Googling on C# and direct access to Bitmap pixels:

http://stackoverflow.com/questions/24701703/c-sharp-faster-alternatives-to-setpixel-and-getpixel-for-bitmaps-for-windows-f

The first answer works fine for me. The Bits array gives me exactly what I want. I've currently got a little window running in Visual Studio with coloured pixels being placed in random positions.



Offline Kawa

Re: Adventures in C#
« Reply #1 on: November 25, 2016, 06:17:24 PM »
I'm resisting the temptation to have my method names start in lower case
privateField, localVariable, _propertyBackingField, PublicFieldOrProperty, PublicMethod, CONSTANT.

I think it's PrivateMethod too, but I kinda like having them match privateField myself. I can of course always use localLambdas as a compromise.

Quote
People say that the syntax is similar.
In much the same way all C-likes have similar syntax.

Offline lance.ewing

Re: Adventures in C#
« Reply #2 on: November 25, 2016, 06:56:41 PM »
Back in the day, before Markus Persson (aka Notch) became famous as the creator of Minecraft, he used to enter and sometimes win the Java4K games contest:

http://web.archive.org/web/20160329012721/http://java4k.com/index.php?action=games&method=user&uid=25

I believe his Miners4K in 2006 and Left 4K Dead in 2009 won the competition on those particular years.

I first became aware of this Java4K games contest in 2006 and I kept an eye on it every year thereafter. I actually knew of the name Markus Persson as the famous Java4K games entrant long before I heard of Minecraft or that he was the one that wrote it.

Anyway, what I did a couple of years ago was decompile the code (as it isn't directly available) for one of his entries, the final one in fact, from 2010 when he was just starting to become famous worldwide for Minecraft. The entry was called VVVV, a Java4K clone of VVVVVV. You can see someone completing it without dying in the Youtube video below:



So this evening I took that decompiled Java code, pasted it in to my simple random pixel C# app, fixed the bits that weren't compiling, and now I've got VVVV as a C# game, and it's not too bad. A bit of a flicker that I'm going to look in to now, and I haven't yet got the text at the bottom showing up, but the games is actually playable. I'll hopefully upload it here before the end of the weekend if you're all interested. I'm going to use this game as a test game for trying out various mechanisms for the timer and graphics to see what appears to perform the best.

Offline Kawa

Re: Adventures in C#
« Reply #3 on: November 25, 2016, 07:06:10 PM »
Regarding direct pixel access: I haven't read your link but I use Bitmap.LockBits and Marshal.Copy. The latter because I try to avoid unsafe code and thus can't use pointers like in C, so I "draw" to a buffer array, lockbits, marshalcopy the buffer onto the bitmap's Scan0 intptr, and unlock. Bam. Fast enough for AGI, and works for RGB, ARGB, and indexed.

Practical code to follow tomorrow if you want it.

Offline lance.ewing

Re: Adventures in C#
« Reply #4 on: November 26, 2016, 04:42:45 AM »
Perhaps that stackoverflow link I posted above is using an unsafe mechanism then. I wouldn't know yet whether what that approach is doing is unsafe or not.

Offline Kawa

Re: Adventures in C#
« Reply #5 on: November 26, 2016, 05:37:55 AM »
Code: [Select]
using Marshal = System.Runtime.InteropServices.Marshal; //we don't need the whole thing, just Marshal.
using System.Drawing.Imaging;
...
//Now, given these...
byte[] pixels = null;
Bitmap screenBitmap = new Bitmap(screenWidth, screenHeight, PixelFormat.Format8bppIndexed);
//We use Format8bppIndexed because lolagi and simplifying the pixel buffer.

var bitmapData = screenBitmap.LockBits(new Rectangle(0, 0, screenBitmap.Width, screenBitmap.Height), ImageLockMode.ReadWrite, screenBitmap.PixelFormat);
//First pass, we can be sure we know how big the stride is, which you may know may not match the width even in 8-bit mode.
if (pixels == null)
pixels = new byte[bitmapData.Stride * bitmapData.Height];

//You can now set your pixel data...
pixels[targetRow * bitmapData.stride + targetColumn] = someColorIndex;

//...and blast it up in there.
Marshal.Copy(pixels, 0, bitmapData.Scan0, pixels.Length);
screenBitmap.UnlockBits(bitmapData);

Roughly.

And no, now that I look at SO, that's unsafe code in the first answer, and somewhat unmanaged (but brilliant) code in the last.
« Last Edit: November 26, 2016, 05:46:50 AM by Kawa »

Offline lance.ewing

Re: Adventures in C#
« Reply #6 on: November 26, 2016, 07:26:20 AM »
I just realised that when I said I was using the first answer to that SO question, the answers were ordered by votes, so it's actually the last answer when ordered by age with oldest at the top, i.e. the most recent answer. I think that is the one that you've referred to as being somewhat unmanaged but brilliant, is that right?

I like the fact that changes made to the array are immediately applied to the Bitmap and that it doesn't therefore require a copy of the array.

Would you be concerned if I were to continue using such an approach? What would the drawbacks be? The answer says only to use it when performance is required, so I probably don't need to use such an approach. I do like how simple it is to work with though.

Offline Kawa

Re: Adventures in C#
« Reply #7 on: November 26, 2016, 09:36:37 AM »
The drawback, as far as I can tell, is that you're bypassing the GC.

Offline lance.ewing

Re: Adventures in C#
« Reply #8 on: November 26, 2016, 05:43:34 PM »
Reading about this a bit more, it sounds like the GC knows about it, but because it is pinned, the GC can't move it. Apparently this undermines the efficiency of the GC in optimising the use of memory, so it can't move that object as part of compacting the memory to clean up fragmentation within the managed heap and this can cause performance problems. What is interesting is that there are two ways to pin an object, right? There's the fixed keyword, which I've read in a few places can only be used in an unsafe context, and there's GCHandle.Alloc with a handle type of Pinned, which is allowed outside of an unsafe context. Seems a bit strange.

It feels like I shouldn't be doing this. If I were to do this, then I'd be pinning the object for the lifetime of the app. Who knows where it might live in the managed heap and how its permanent position might affect the GC. Better to rely on something that will be consistent. So I'll give the approach in your sample code a go and will probably go with that.

Edit: I've got that working now with LockBits and Marshal.Copy, but used ARGB int values for the pixel array instead.
« Last Edit: November 26, 2016, 06:27:06 PM by lance.ewing »

Offline OmerMor

Re: Adventures in C#
« Reply #9 on: November 27, 2016, 12:58:21 AM »
If the object you allocate is larger than 85,000 bytes, then it will be allocated on the Large Object Heap (LOH). This heap is special in that it doesn't go through compaction, so it'll probably won't be an issue that you allocate your memory for the lifetime of the program.

Another possible approach would be to allocate unmanaged memory. This can be done via the Marshal.AllocHGlobal() function. That way it won't interfere with the GC.

Offline Kawa

Re: Adventures in C#
« Reply #10 on: November 27, 2016, 03:44:20 AM »
Another possible approach would be to allocate unmanaged memory.
Quote
allocate unmanaged memory
Vade retro, Satana.

Offline lance.ewing

Re: Adventures in C#
« Reply #11 on: November 27, 2016, 05:25:23 AM »
If the object you allocate is larger than 85,000 bytes, then it will be allocated on the Large Object Heap (LOH). This heap is special in that it doesn't go through compaction, so it'll probably won't be an issue that you allocate your memory for the lifetime of the program.

I've just been reading up on this and you're right. I think I'd be fine with my array, as it would be about 3 times larger than 85000. The VVVV game's array is less than 85000, but AGI will be higher, so I'd be fine.

https://www.simple-talk.com/dotnet/net-framework/the-dangers-of-the-large-object-heap/

Interesting quote from the above article:

Quote
.NET solves these problems by simply never moving large objects around. After large objects are removed by the garbage collector, they leave behind holes in the large object heap, thereby causing the free space to become fragmented. When there?s no space at the end of the large object heap for an allocation, .NET searches these holes for a space, and expands the heap if none of the holes are large enough.

So I think that means that the problem I thought I would be creating by pinning the object is actually always a problem for objects as large as the one I was pinning since they are never moved. So I guess this means that simply by using a byte or int array of the size required for the pixels, it is going in the LOH and will never move. So pinning it would be fine, right? Well, as fine as it normally would be without pinning for such an object.

I think this option might be back on the table then.

I've just read another page about the LOH that suggests that it may now be possible to configure the GC to compact the LOH if you want it to do that. Default is still not to do it, but perhaps it isn't something that can be 100% relied on in the future. A Pinned large object would cause issues for such a configuration of the LOH but not more I guess than what there would be under the default behaviour.

https://blogs.msdn.microsoft.com/mariohewardt/2013/06/26/no-more-memory-fragmentation-on-the-net-large-object-heap/

Another possible approach would be to allocate unmanaged memory. This can be done via the Marshal.AllocHGlobal() function. That way it won't interfere with the GC.

Not so keen on this approach. How would I use that to directly map a C# primitive array (e.g. a int[] or byte[]) to the allocated memory? I couldn't immediately see how I could do this without copying. And besides, it sounds like I could be burnt at the stake for trying it.  :)

Offline OmerMor

Re: Adventures in C#
« Reply #12 on: November 27, 2016, 11:45:03 AM »
Another possible approach would be to allocate unmanaged memory. This can be done via the Marshal.AllocHGlobal() function. That way it won't interfere with the GC.

Not so keen on this approach. How would I use that to directly map a C# primitive array (e.g. a int[] or byte[]) to the allocated memory? I couldn't immediately see how I could do this without copying. And besides, it sounds like I could be burnt at the stake for trying it.  :)

See here for an example of mapping unmanaged memory:
http://www.tsjensen.com/blog/post/2014/05/17/Unmanaged-Memory-in-C-and-the-Power-of-MarshalAllocHGlobal

Regarding the heresy of this approach:
I wouldn't pick it as my first option. Only if real performance issues arise in the other options, would I try it. However you shouldn't be dismayed so much by words like "unsafe" and "unmanaged". As long as you know what you're doing, you'll be fine.

Offline lance.ewing

Re: Adventures in C#
« Reply #13 on: November 27, 2016, 11:49:51 AM »
I've got VVVV as a C# game, and it's not too bad. A bit of a flicker that I'm going to look in to now

I should mention that the cause of the flicker was due to the way in which I was initially triggering the 60 times a second call to my game loop. So this brings us on to the next subject in our "Adventures in C#", i.e. the timer.

From experience using libGDX for a VIC 20 emulator project targeting Android, I had started out putting the game loop logic from that emulator project in to the render method that gets called 60 times a second. In libGDX there is no control over how often that gets called. But using the delta time I could easily work out how many cycles of the VIC 20 emulator to execute for the elapsed time since the last call (as there is obviously no guarantee that it gets called exactly 60 times a second). After getting it working quite nicely on my own phone, I tried it on a few others and was surprised to see that for devices that were comparable in specs, I was seeing a reported FPS of 8000 in some cases and 2 in other cases. I was a bit surprised because what I'd read about libGDX suggested that the render method should be called 60 times a second. It turns out that this rate is affected by what you do within the render method. I was doing too much and different devices were handling this in different ways. So I shifted all of the logic not related to rendering in to a separate thread and kept the render method and therefore UI thread doing solely rendering. I then had to solve the problem of the other thread not being able to do rendering directly (as that is the domain of the UI thread). But I did get it working, and that solved the FPS issues I was having on the other devices. They all started reporting 60 FPS and everything worked nicely.

So coming from that recent experience working on the VIC 20 emulator (which was Java based), my first instinct when coming in to C# was to trigger the game loop with a separate thread, i.e. avoid running it in the UI thread. That is why I was getting the flicker, because the UI thread was sometimes redrawing the Bitmap at a point where the background had been drawn but the man had not yet been drawn on top of the background (a similar scenario could exist in AGI where a "save area" for an animated object might have been applied to the background but the new cel not yet having been drawn).

Rather than coming up with a way of managing this, I decided to change the Thread to using a Timer within the UI thread. This seems to work well, at least for the type of simple graphics that I'm working with. There is no flicker.

But this brings me on to the following article, which I spent some time reading over and studying in the process of considering a timer loop:

https://blogs.msdn.microsoft.com/shawnhar/2010/12/06/when-winforms-met-game-loop/

My first instinct is to trust what Shawn Hargreaves says, as he was the original author of Allegro, the graphics library I was using back in the 90s when I wrote MEKA and one of the two C versions of the VIC 20 emulator. He also works at Microsoft, so such a blog post is possibly reviewed by other Microsoft colleagues. As I assessed some of the approaches mentioned, I came across a reddit discussion, in which Shawn's post is described as an abomination:

https://www.reddit.com/r/programming/comments/4o11h6/when_winforms_met_game_loop/

I was curious to know what the experienced C# programmers on this forum thought of the two sides to this discussion. I'm assuming that the "abomination" comment is in relation to using the Idle event approach and therefore taking up 100% of one core. It doesn't sound ideal, does it? So why would Shawn Hargreaves suggest this? It seems that this is only for scenarios where you really need so much CPU. I certainly don't, so Shawn's first suggestion using a Timer is what I'm currently using and it is working well for me.

Any other suggestions? Anything to be aware of using a Timer?
« Last Edit: November 27, 2016, 12:53:27 PM by lance.ewing »

Offline troflip

Re: Adventures in C#
« Reply #14 on: November 27, 2016, 12:25:52 PM »
I think you'll find that most games on Windows eat up all of one of the cores, so the "abomination" is common practice anyway (and doesn't have anything specifically to do with C#). I don't know if this is also commonplace for mobile applications... I doubt it.

After getting it working quite nicely on my own phone, I tried it on a few others and was surprised to see that for devices that were comparable in specs, I was seeing a reported FPS of 8000 in some cases and 2 in other cases. I was a bit surprised because what I'd read about libGDX suggested that the render method should be called 60 times a second. It turns out that this rate is affected by what you do within the render method. I was doing too much and different devices were handling this in different ways. So I shifted all of the logic not related to rendering in to a separate thread and kept the render method and therefore UI thread doing solely rendering.

Did you profile your code to see what was slow before splitting some of it off to a different thread? This is an AGI interpreter, right? I can't imagine it being complex enough to eat up 16ms in one game update cycle unless you're doing something wrong.
Check out my website: http://icefallgames.com
Groundhog Day Competition


SMF 2.0.19 | SMF © 2021, Simple Machines
Simple Audio Video Embedder

Page created in 0.048 seconds with 24 queries.