Home » Community » Forums » DirectX and XNA » Using EffectHandles, quick analysis and utility code
Intel sponsors gamedev.net search:
Control Panel Register Bookmarks Who's Online Active Topics Stats FAQ Search

Add Forum to Favorites |  Send Topic To a Friend | View Forum FAQ | Track this topic


 Last Thread Next Thread 
 Using EffectHandles, quick analysis and utility code
Post New Topic  Post Reply 
Edited: Fixed EffectHandlerProxy Reset method, didn't work because of Hashtable synchronization issues.
_____________________________________

I was just playing around with my cloud generator and I decided that I should be setting the effect parameters using EffectHandles instead of name strings. Everyone says these are more efficient, but there seem to be no solid figures on exactly how much more efficient. So, I thought my results might be interesting for other (M)DX developers. I also developed a little utility class to facilitate using EffectHandles, which you can find below.


Analysis

When I started replacing the SetParameter calls to use handles, I noticed quite an impressive speed gain as I went along. After I replaced all 26 SetParameter calls, which are called each frame, my average framerate went up from 950 to 1100 fps. That's a 16% gain, by only switching from strings to handles!

My EffectHandleProxy class below uses a Hashtable to look up handles for parameter names, so you can keep setting the parameters using the hassle-free, descriptive name strings, while taking advantage of the efficiency of the handles. I'm a bit puzzled why the DirectX team doesn't incorporate this into the Effect.SetParameter method directly. At first I thought the overhead of the Hashtable lookups might be just as inefficient as calling SetParameter using the name string, but the 16% fps gain already includes this overhead.


EffectHandleProxy utility class

The EffectHandleProxy class allows you to easily use handles for setting effect parameters, with a minimum of recoding. You can create an instance of the EffectHandleProxy alongside an effect. When you create the Effect object during a device reset (or initialization), simply call the Reset(Effect) method on the proxy object. This will bind the proxy to the effect and it will recreate any handles that were used on the effect before. Note that you need one proxy per effect and that you can keep reusing this proxy for the same effect over a device reset, using the Reset(Effect) method.

Actually setting the parameters is easy as well. You simply replace effect.SetParameter with proxyObject.SetParameter and that's that. The proxy supports all overloads of the Effect method (plus an extra one for structs) and it will automatically create a new EffectHandle for any parameter name that hasn't been used before.

The code (or download it without layout quirks):

[source lang=c#]
using System;
using System.Collections;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using System.Runtime.InteropServices;

namespace Clouds.Util
{
	/// <summary>
	/// The EffectHandleProxy caches effecthandles to efficiently set 
	/// effect parameters while using the name strings in the main app.
	/// </summary>
	public class EffectHandleProxy
	{
		private Hashtable effectHandles = new Hashtable();
		private Effect effect;

		public EffectHandleProxy()
		{			
		}

		public void Reset( Effect effect )
		{
			this.effect = effect;
			effectHandles.Clear();
		}

		public void SetValue( string name, BaseTexture val )
		{
			effect.SetValue( GetHandle( name ), val );
		}

		public void SetValue( string name, bool val )
		{
			effect.SetValue( GetHandle( name ), val );
		}

		public void SetValue( string name, bool[] val )
		{
			effect.SetValue( GetHandle( name ), val );
		}

		public void SetValue( string name, ColorValue val )
		{
			effect.SetValue( GetHandle( name ), val );
		}

		public void SetValue( string name, ColorValue[] val )
		{
			effect.SetValue( GetHandle( name ), val );
		}

		public void SetValue( string name, GraphicsStream val )
		{
			effect.SetValue( GetHandle( name ), val );
		}

		public void SetValue( string name, int val )
		{
			effect.SetValue( GetHandle( name ), val );
		}

		public void SetValue( string name, int[] val )
		{
			effect.SetValue( GetHandle( name ), val );
		}

		public void SetValue( string name, Matrix val )
		{
			effect.SetValue( GetHandle( name ), val );
		}

		public void SetValue( string name, Matrix[] val )
		{
			effect.SetValue( GetHandle( name ), val );
		}

		public void SetValue( string name, float val )
		{
			effect.SetValue( GetHandle( name ), val );
		}

		public void SetValue( string name, float[] val )
		{
			effect.SetValue( GetHandle( name ), val );
		}

		public void SetValue( string name, string val )
		{
			effect.SetValue( GetHandle( name ), val );
		}

		public void SetValue( string name, Vector4 val )
		{
			effect.SetValue( GetHandle( name ), val );
		}

		public void SetValue( string name, Vector4[] val )
		{
			effect.SetValue( GetHandle( name ), val );
		}

		public unsafe void SetValue( string name, void* val, int len )
		{
			effect.SetValue( GetHandle( name ), val, len );
		}

		public unsafe void SetStructure(string name, object objStr)
		{
			int nSize = Marshal.SizeOf(objStr);
			IntPtr unmanagedPointer = Marshal.AllocHGlobal(nSize);

			Marshal.StructureToPtr(objStr, unmanagedPointer, true);

			effect.SetValue(GetHandle(name), unmanagedPointer.ToPointer(), nSize);

			Marshal.FreeHGlobal(unmanagedPointer);
		}

		private EffectHandle GetHandle( string name )
		{
			if (!effectHandles.ContainsKey(name))
			{
				effectHandles.Add( name, effect.GetParameter( null, name ) );
			}

			return (EffectHandle)effectHandles[name];
		}
	}
}






[Edited by - remigius on December 8, 2005 1:18:01 PM]

 User Rating: 1683   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

Quote:
Original post by remigius
My EffectHandleProxy class below uses a Hashtable to look up handles for parameter names, so you can keep setting the parameters using the hassle-free, descriptive name strings, while taking advantage of the efficiency of the handles. I'm a bit puzzled why the DirectX team doesn't incorporate this into the Effect.SetParameter method directly. At first I thought the overhead of the Hashtable lookups might be just as inefficient as calling SetParameter using the name string, but the 16% fps gain already includes this overhead.

This is pretty nifty. However, did you calculate what FPS you get without using a hashmap and just using the traditional handles? I have heard that some implementations of hashmaps are pretty inefficient for accessing elements (if their size is say < 1000), so that could be a factor.

I like it. It is always kinda annoying prototyping shaders and having to go the whole way and make dedicated handle members, just so you can find its performance accurately.


Dustin Franklin ( circlesoft :: KBase :: Mystic GD :: ApolloNL )

 User Rating: 1712   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

Good to see it was interesting enough for a sticky :)

I'm using the Hashtable implementation from the .NET Runtime, so I assumed it was pretty optimized for all sizes of datasets. To be sure, I replaced 13 of the 26 SetParameter calls with versions that directly used instance variable EffectHandles. The improvement over the Hashtable approach was marginal, around 3 fps and even that may well fall within the fps variations caused by the random noise textures used in shader.

Anyway, verifying this highlighted another benefit of the proxy class, as it's very easy to find out which effect handles are set at runtime, by simply checking the keys in the Hashtable. This can be used to generate variable declarations for the handles for example (see below) and it may be useful for debugging.

Code which can be inserted in the class to generate handle declarations and assignments:

[source lang=c#]
public void PrintHandleDeclaration()
{
	foreach( string name in effectHandles.Keys )
	{
		System.Diagnostics.Debug.WriteLine(string.Format("private EffectHandle {0}Handle;", name));
	}
}

public void PrintHandleAssignment(string effectName)
{
	foreach( string name in effectHandles.Keys )
	{
		System.Diagnostics.Debug.WriteLine( 
		string.Format("{0}Handle = {1}.GetParameter(null, \"{2}\");",
		name, effectName, name));
	}
}




 User Rating: 1683   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

Interesting! I was just thinking about this today, as the Effect wrapper class in my engine just sets the effect parameters with strings. I thought the same thing - that using a hashtable to look up parameters myself would just be moving the lookup from the ID3DXEffect interface to my own code, so I didn't think I'd gain any performance. Well, looks like I was wrong, so I'm going to go do that now.

_______________________________________________________________________
Hoo-rah.

 User Rating: 1109   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

Please do post your results, I'm interested to see if you get a similar performance gain when using normal (C++) DirectX.

 User Rating: 1683   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

Odd. I get no change in speed.

Perhaps it could be how my effect system is implemented. I'm already caching the parameter values using a hashtable, so the only time the parameters are actually set to the effect interface is once a frame. So it's already pretty optimized that way. However, when the parameters are set to the effect interface, it basically just loops through a regular array of handles - no hashtable used for actually retrieving the handles corresponding to the parameters.

So it could be one of two things. One, the Effect interface in MDX might not be implemented as efficiently as in UDX. This is entirely possible, and it might just be a case of some safety or testing code left in the MDX Effect interface while it's under development.

The other thing that it could be is the number of parameters and how frequently they are set. The effect I was using only had about 8 parameters. This is something I wanted to ask you - for your 26 SetParameter calls, are they all called every frame?

Of course, it could be a third thing, which is that I messed up somewhere ;)

I'll see if I can get a more parameter-happy effect to see if I can get some more conclusive results.

_______________________________________________________________________
Hoo-rah.

 User Rating: 1109   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

Hmmm, strange indeed...

The 26 parameters I use are all set once every frame and they're completely different parameters, spread over 3 effects. About 8 are texture setters, 6 for matrices, 4 Vector4's and that leaves 8 floats. I don't know if that makes any difference, but I thought I'd mention it for completeness.

Quote:
Of course, it could be a third thing, which is that I messed up somewhere ;)


Door #4 might also be a possibility, that I messed up :)

Anyway, in previous test runs, I my framerate was always 950 on average. For the test, I ran the app at least 10 times from VS.NET using debug -without DX debug runtimes- for both the string and EffectHandle implementation (since I couldn't believe it myself). When I run it now, with a full taskbar (ie. with too many programs open), it still averages to 1080 fps.

Could the efficiency of the SetParameter method be influenced by the video card?

 User Rating: 1683   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

I just tried an effect with 44 parameters, and made sure they were all being set to the effect every frame. No speed difference between names and handles.

As for your last statement, I kind of wonder.. what shader model are you using, anyway? I've just got an SM2 card.

_______________________________________________________________________
Hoo-rah.

 User Rating: 1109   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

I'm also just using sm2 on an X850PE. My last statement was just a hunch, since I can reproduce my results every time, with the same speed gain. So there's three possibilities; either my video card is very sensitive about EffectHandles, or the MDX implementation of the effects framework could use some optimization or a miracle has happened... with MDX, the last one seems unlikely ;)

Anyway, I'll write some test projects tomorrow, focussing solely on setting the effect parameters. It might be that the rest of the rendering process of my cloud generator is somehow influencing the results... I didn't change anything in the rest of the code though, but I'll get to the bottom of this :)

 User Rating: 1683   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

I've run some more tests and for MDX the performance gain is there and significant. I wrote a new test app that sets 10 matrices, 10 floats and 10 float4's on an effect. It doesn't do any rendering at all with this effect, only setting these effect parameters and calculating framerates.

The average framerates are calculated over a 10 second period and for the comparison I took the average framerate in the 2nd period (from 10 to 20 secs), so the app initialization doesn't interfere. The test was conducted by having the EffectHandlerProxy simply return the name string from the GetHandle method, instead of a handle, automatically using the appropriate Effect.SetParameter overload. This was easiest to implement, but it also shouldn't have any influence on the test. In code:

[source=c#]
// normal proxy functionality

private EffectHandle GetHandle( string name )
{
	if (!effectHandles.ContainsKey(name))
	{
		effectHandles.Add( name, effect.GetParameter( null, name ) );
	}

	return (EffectHandle)effectHandles[name];
}

// returning the name instead of the handle, for string name setting

private string GetHandle( string name )
{
	return name;
}



In this set up, the average framerate over 10 secs was 4548 fps using the handles, and 3928 fps using name strings. That's again a 16% gain, consistent with previous results. So my testing would suggest that handles give indeed a 16% better result than name strings, at least for Managed DirectX on an ATI Radeon X850PE.

For more details, download the test project (~280KB ZIP)

If someone can test this on other (nvidia) video cards, that might give us some more insight in whether or not the results are dependant on the video card.

 User Rating: 1683   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

Interesting - I downloaded your project, and tried it with both methods (cached handles and strings). There is a difference in speed, but not very much - I tried it several times (letting it run for 40 seconds and ignoring the first 10-second report) and got an average of 1620FPS with the cached handles, but an average of 1600FPS with strings. That's only a 1.25% increase.

Perhaps it has something to do with the .Net framework version. Or maybe video card drivers. Or, like you said, maybe it's the video card itself. I've got a Radeon 9500 128MB with the Catalyst 5.11 drivers (and the release notes for the 5.12 drivers released two days ago mention nothing about MDX, although something could have changed).

_______________________________________________________________________
Hoo-rah.

 User Rating: 1109   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

I did something very similar but:

My constructor converted all strings to effecthandles to avoid converting them each frame and I seperated my Parameters into 2 categories. One category updated the parameter each frame (i.e WorldView) using the handles and the other was a hashtable that triggered an event to update the params (i.e Texture) using the string.

It's still pretty flaky but I saw a big jump in performance. If i remember correctly I went from 140fps to about 250fps...

I've been waiting impatiently for generics to work with VS2005 to see any additional benefits...

Anyway, I think your code is very clean and efficient and if you don't mind, I could use what you did in the part of my code that updates by events.



 User Rating: 972   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

Quote:
Anyway, I think your code is very clean and efficient and if you don't mind, I could use what you did in the part of my code that updates by events.


Sure, go ahead. That's why I posted it after all :)

Quote:
Perhaps it has something to do with the .Net framework version. Or maybe video card drivers. Or, like you said, maybe it's the video card itself. I've got a Radeon 9500 128MB with the Catalyst 5.11 drivers (and the release notes for the 5.12 drivers released two days ago mention nothing about MDX, although something could have changed).


I'm using the .NET framework version 1.1.4322.573. I don't know if this is the latest version, but I bought my machine in September with XP & SP2 preinstalled, so it should at least be a recent one. It shouldn't have anything to do with the video card, since ATI wouldn't degrade performance from the 9500 to the X850PE on something like this... On the other hand, it might well be that the handles were introduced after the 9500 was released, so it may not have support for it and still use the string approach behind the scenes. But that's just guessing though...

It could also be my drivers, since I stuck with the preinstalled drivers when I got my system. It runs HL2 at full detail, so I don't think they're all that bad. But I'm downloading Catalyst 5.12 now anyway to see if that makes any difference. Edit: I've just installed Catalyst 5.12 and the performance on the test app actually seems to have decreased! With handles the framerate is now 4520 fps and 3901 fps for the strings. The overall framerate is actually less than the previous results on both tests, but the relative difference has gotten a little bigger (from 15.7% to 15.8%).

I think some authorative comments from the source could clear up this discussion, so I'll send an e-mail to ZMan, maybe he can tell us what going on.

[Edited by - remigius on December 12, 2005 2:51:06 AM]

 User Rating: 1683   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

Quote:
On the other hand, it might well be that the handles were introduced after the 9500 was released, so it may not have support for it and still use the string approach behind the scenes.


I kind of doubt that, since the entire Effect system runs entirely in software and is part of the DirectX runtime and not the video card drivers. The effect parameter strings and handles simply map to some kind of internal constant table which is used to set the actual constants to the device. The video card isn't involved in the process at all, hence why I thought maybe the .Net version had something to do with it, in case they changed the implementation of Hashtable or something. But that's obviously not the case..!

_______________________________________________________________________
Hoo-rah.

 User Rating: 1109   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

All times are ET (US)

Post Reply
 Last Thread Next Thread 
Forum Rules:
You may not post new threads
You may post replies
You may not edit your posts
You may not use HTML in your posts
Jump To:
Administrative Options: