Irrlicht Lime is a .NET wrapper for Irrlicht Engine

Announce new projects or updates of Irrlicht Engine related tools, games, and applications.
Also check the Wiki
greenya
Posts: 1012
Joined: Sun Jan 21, 2007 1:46 pm
Location: Ukraine
Contact:

Post by greenya »

zaki wrote: IrrlichtLime definitely has less overhead than Irrlicht.NET, and it doesn't use P/Invoke, so performance must also be better
Yes. Lime provides not bad performance comparing to native C++, since its done in the way you described. Personally i think that most time takes rendering which is done internally by Irrlicht, so will do same speed in C++ and C#. When you get 1000 fps in C++ and get 900 fps in C# equivalent its performance loss, but in general you don't need those 1000 fps, and its not so bad to have 900. Also in real app i believe when C++ will give you 40-50 fps, C# will give the same (maybe -1/-2 fps).

More about performance. Lime compiled in Debug mode should give even bigger performance loss than in Release (comparing to Debug/Release of native Irrlicht), because I add a lot of asserts into the code, in every place where it is clear to know what value acceptable, and this must help user to detect some errors. In Release mode, all the asserts omitted, so it runs as fast as possible.
For example, when you create a Video.Coloru (which is video::SColor), in Debug mode additionally 4 comparisons done each time:

Code: Select all

	Coloru(unsigned int r, unsigned int g, unsigned int b, unsigned int a)
		: Lime::NativeValue<video::SColor>(true)
	{
		LIME_ASSERT(r <= 255);
		LIME_ASSERT(g <= 255);
		LIME_ASSERT(b <= 255);
		LIME_ASSERT(a <= 255);

		m_NativeValue = new video::SColor(a, r, g, b);
	}
Also, next one will help user don't write "new Colorf(255, 255, 255, 255)", since it will give an "assertion failed" in Debug mode:

Code: Select all

	Colorf(float r, float g, float b, float a)
		: Lime::NativeValue<video::SColorf>(true)
	{
		LIME_ASSERT(r >= 0.0f && r <= 1.0f);
		LIME_ASSERT(g >= 0.0f && g <= 1.0f);
		LIME_ASSERT(b >= 0.0f && b <= 1.0f);
		LIME_ASSERT(a >= 0.0f && a <= 1.0f);

		m_NativeValue = new video::SColorf(r, g, b, a);
	}
More about performance. One of the ways to improve performance is to implement native value types, like Dimension, Vector, Triangle, SColor and so on, inside Lime. Because now, each type only wraps its native value by having a reference on it internally. And when you do anything with it, its done with that reference, so by native Irrlicht. This have advantages and disadvantages:
"plus" you don't copy functionality (bodies of methods) from native Irrlicht;
"plus" if native implementation changes (or bug fixed) you don't need to change anything;
"plus" if some Irrlicht' method takes a reference on that value internally and changes it, you don't need to bother "how to sync value with my managed copy?".
"minus" performance loss, for example next one is definitely a bit slower:

Code: Select all

	bool GetIntersectionWithSphere(Vector3Df^ sphereOrigin, float sphereRadius, [Out] double% distance)
	{
		LIME_ASSERT(sphereOrigin != nullptr);

		f64 d = 0.0f;
		bool b = m_NativeValue->getIntersectionWithSphere(
			*sphereOrigin->m_NativeValue,
			sphereRadius,
			d);

		if (b)
			distance = d;

		return b;
	}
but thats not a big price, and it is not a 100% truth that it is possible to implement this method inside Lime and it will be not slower than its native analog.
zaki wrote: For curiousity, greenya, how do you work with IReferenceCounted? I'm not very familiar with managed C++, is there any place we need to be cautious about GC or is it something that will just work with C++?
Each interface (or almost each) has its own wrapping managed class. And IReferenceCounted is not an exception. Lime doesn't do any "automatic" reference counting that you might think. If native Irrlicht doesn't do it, Lime doesn't do it as well. For example if user does bad reference counting, like doing double Drop(), Lime falls like native Irrlicht does. In C++ you do not use "new" and "delete" for Irrlicht, you use "create" and "drop", so Lime wraps this and doesn't uses IDisposable managed interface for this things, because i don't think its right way to help user in this -- better user must know what he doing.

Also, for example Irrlicht.NETCP provides a constructor for IrrlichtDevice, so you create the device by creating an object using "new". I don't think its good way, since than you must enclose executing into try-catch, because if device failed to create, Irrlicht doesn't fall and just return null pointer, which is indication of error. In Lime it's just the same, we have IrrlichtLime.CreateDevice() (a static method), it may return null and it is only way to create the device (just like native Irrlicht do this).

Wrapping in this way, gives us a bonus in further supporting, becasue if native Irrlicht changes internally we just wrap this in that simple way, not building our different engine on Irrlicht, so when base changes some specific part strong enough, we changed only that part and all other part should work just the same if they wasn't changed in native Irrlicht.
zaki wrote: The page I found on the mono site recommends compiling with the /clr:safe switch, but I don't think that is possible, because IrrlichtLime links against native Irrlicht.
Yes, if you use "/clr:safe" than it just managed code and i guess it can run OK. But this is not our case, since "safe" means "no unmanaged pointers at all" which is impossible. If i turn "safe" on, i get million of
error C4956: 'char *' : this type is not verifiable
If i use "/clr:pure", i don't get messages about native pointers, but i get a million of
error C3862: 'swprintf': cannot compile an unmanaged function with /clr:pure or /clr:safe
so i can compile it only as "unsafe".
zaki
Posts: 3
Joined: Mon Dec 21, 2009 9:07 am

Post by zaki »

greenya wrote:Also, next one will help user don't write "new Colorf(255, 255, 255, 255)", since it will give an "assertion failed" in Debug mode:
Nice!
greenya wrote:Lime wraps this and doesn't uses IDisposable managed interface
Indeed, that was the first thing I removed from Irrlicht.NET :) Doing double reference counting (badly) is a LOT worse than trusting your users to do some minimal Drop()-ing.

However I was more interested if you for example allow any delegates coming from the managed side into native Irrlicht and if yes, if there was some particular thing you had to do to keep the delegate from being GCd. Or what happens when you submit a SceneNode to SceneManager::addToDeletionQueue, which if I remember correctly will drop all children, but if you happen to have a reference somewhere in your native code, you won't be notified, so if you try to dereference your element in the wrapper, you might get a exception. Another particular interest would be reference counted native elements put into an irrlicht array and then the array getting deleted.

In managed C++ it might be easier to check if the pointer to the native element in your wrapper is still valid, but as far as I understand you can't really guard against drops happening inside Irrlicht only. Especially if the same memory location was reused in the meantime and your pointer might even be valid, just pointing to a different object. At the company I work, for a while we added a callback to IRefCounted to notify about any element being dropped, but that is clearly not a good solution performance-wise.
greenya wrote:so i can compile it only as "unsafe".
Yes, that's what I got too. I'll try to see what mono on linux makes of the IrrlichtLime.dll when I get home.
greenya
Posts: 1012
Joined: Sun Jan 21, 2007 1:46 pm
Location: Ukraine
Contact:

Post by greenya »

Pseudo code sample:

Code: Select all

// we create node with some children
n = new Node();
n.AddChild(1);
n.AddChild(2);
n.AddChild(3);
c = n.GetChild(1); // now c points to second child of n
n.DropChildren(); // you can do it in C# as well as in C++
// and now c is pointless, and "yes", when you try to call
c.DoSomething(); // this will cause an exception in C# as well as in C++
Looks like all that i can do, is to add to every managed call something like:

Code: Select all

LIME_ASSERT(m_ReferenceCounted->ReferenceCount > 0); // this is valid code for any method within class that is derived from ReferenceCounted
so when you call "c.DoSomething();" you get assertion failed, but that is only at first look, because indeed it has no sense, since when ReferenceCount became zero, Irrlicht automatically releases the memory, so this checking will cause an exception itself by trying to access IReferenceCounted::getReferenceCount().
Next illustration demonstrates what do i taking about:
Image

So when using Lime, on each call user must be sure that object is valid and not dropped.
almondega
Posts: 37
Joined: Sat Jun 21, 2008 2:14 am

Post by almondega »

Hi, first let me thank you for this project,

I changed a project I had in C++ to this Lime + C#, and the code looks just beautiful..

But, you thought c++ in 40/50 fps would result in ~39 fps in C#.

My project ran around 30 fps in c++
Using Lime, it goes to 5 .. 6 .. :cry: :cry:

I'm testing using 27 Animated Meshes..

So far I'll recommend this wrapper for very small projects, until the wrapper can offer best performance for animated scenes.

Thanks and keep updating it

:wink:
greenya
Posts: 1012
Joined: Sun Jan 21, 2007 1:46 pm
Location: Ukraine
Contact:

Post by greenya »

almondega wrote: But, you thought c++ in 40/50 fps would result in ~39 fps in C#.

My project ran around 30 fps in c++
Using Lime, it goes to 5 .. 6 .. :cry: :cry:

I'm testing using 27 Animated Meshes..
This numbers i get from the examples that i ported. I didn't noticed significant performance difference between c# and c++. However, this doesn't mean that it is impossible to write slow code in c#. Also please note: this numbers (c++:40, c#:39) comes true when rendering takes most biggest CPU time and your other code (in OnEvent handler and between BeginScene() and EndScene()) takes small, because rendering done in both cases by native Irrlicht C++ code. And if your other code takes a half (or bigger part) of all time, than final performance completely depends on that code you wrote.

If it is possible, you can post your code here or give a link, so i can check why it can be so slow.

P.S.:
you getting 5-6 fps in Debug mode?
If so, could you try Release and post the result here?
almondega
Posts: 37
Joined: Sat Jun 21, 2008 2:14 am

Post by almondega »

Hi, my code just loads 27 animated meshes, in c++ and c#, and this is it.

I recompiled the IrrLime.dll in release mode, because the downloaded one was crashing on my computer.

Tomorrow I'll recompile again and post the code here.
Thanks.
greenya
Posts: 1012
Joined: Sun Jan 21, 2007 1:46 pm
Location: Ukraine
Contact:

Post by greenya »

OK,
but please note, compiling in Release mode still uses debug version of native Irrlicht.dll.

P.S.: i plaining to provided both dlls starting from next release.
almondega
Posts: 37
Joined: Sat Jun 21, 2008 2:14 am

Post by almondega »

The downloadable Lime comes with Debug Irrlich.lib ?
So, this is the problem =)

I'll download the release .lib and recompile again and test.
greenya
Posts: 1012
Joined: Sun Jan 21, 2007 1:46 pm
Location: Ukraine
Contact:

Post by greenya »

Yes it is.
Indeed, when you comparing the performance, you better do it with Debug C++ analog... but keep in mind (as i said above) Lime in Debug has a lot of assertion checks, so that will slow down too.

almondega,
i can compile Irrlicht.lib, Irrlicht.dll and IrrlichtLime.dll in Release mode for you to check. If you interested -- write a pm to me.
almondega
Posts: 37
Joined: Sat Jun 21, 2008 2:14 am

Post by almondega »

I did the test with those dll you sent to me.
Had to change my target framework to 2.0 (were using 4.0)

Now, C++ has 27fps
C# 24

So, it's a usable binding =)
Thanks !!

-----

Now I want to suggest some things, to a better c# looking code.
Those words to use as sufix in Vector, Dimension.. could they be ommited and use the power of overload?

I mean, could Dimension2Du be only Dimension2D ?

Vector3Df -> Vector3D ?

and the worst one: Coloru
Could be just Color ?

I didn't like this sufixes in Ogre and even in Irrlich, maybe Lime can change it. Just my opinion =)

Thanks and good work!!
greenya
Posts: 1012
Joined: Sun Jan 21, 2007 1:46 pm
Location: Ukraine
Contact:

Post by greenya »

Yes, i was thinking about those naming too.

Currently i plaining next renamings:
Coloru -> Color
Vector3Df -> Vector3D
Vector2Df -> Vector2D
Dimension2Di -> Dimension2D // this is under question still, "u" or "i" (read * below)
Matrix4f -> Matrix // (because i didn't met any other Matrix type), or maybe name it Matrix4 (this naming is from native Irrlicht), maybe better "Matrix4D", or remove that "D" from all types like Vector, Dimension and Matrix (?).... so we get Vector2, Vector3, Dimension2, Matrix4 (but that's look like "version" of type, not a dimension count)...... i also like "Vector2i, Vector2u, Vector3f, Dimension2i, ...".
AABBox3Df -> AABBox // Irrlicht has aabbox3df and aabbox3di (this one used only in /tests folder and only once (!)).... so i think if i call this one as "AABBox" then Matrix4D will get also just "Matrix".

* "Dimension" will get probably "i" version, since i planing to remove all "u" versions of all core types, and Dimension2Du is not an exception. Indeed current situation is:
- Dimension2Du used everywhere,
- Dimension2Di used only in one place: GUIImageList.ImageSize (native IGUIImageList::getImageSize()).
(yes, all that should be changed and proper assertion checks added).

Using only "i" types let us almost never cast. C# doesn't implicit cast int to uint, it gives you compiling error (!), so when you writing code you must use those annoying casting all over the code, for example:
i want to draw a random pixel on the texture, so i need 1 random color and 2 ints (coordinates):

Code: Select all

Random r = new Random(); // this is .NET Framework's native class
Coloru c = new Coloru((uint)r.Next(256), (uint)r.Next(256), (uint)r.Next(256));
int x = r.Next((int)texture.Size.Width);
int y = r.Next((int)texture.Size.Height);
that looks horrible, and i'm going to fix that... just need to think all over in detail ( with LOD level 0 :) )
almondega
Posts: 37
Joined: Sat Jun 21, 2008 2:14 am

Post by almondega »

I vote for #D sufix,

It is easier to read and understand what the class really is.

e.g: Vector3D, Vector2D, Matrix4D and go on...

Also I vote for a complete remove of "i", "f" and "u" as sufix.

e.g: Color

But is up to you choose what is best for the wrapper =)
almondega
Posts: 37
Joined: Sat Jun 21, 2008 2:14 am

Post by almondega »

For those who use Google Reader or another RSS feed reader, subscribe on http://sourceforge.net/export/rss2_keep ... _id=322499

It will list Lime's commits and updates.
:wink:
greenya
Posts: 1012
Joined: Sun Jan 21, 2007 1:46 pm
Location: Ukraine
Contact:

Post by greenya »

almondega,
yes, but you need also to follow the link in rss feed if you want to read a comment of commit :)
almondega
Posts: 37
Joined: Sat Jun 21, 2008 2:14 am

Post by almondega »

You mean click on the post link to go to the sourceforge page to then read the comment?

Would be nice if the the rss post also show the commit comments..
But knowing when there is updates is already good :wink:
Post Reply