Profiling Events vs. Virtual Functions On The 360

Over the past week or so I’ve been completely reworking my collision system in order to better decouple it from other areas of code, and also make it more flexible.  One part I got stuck on for a bit was deciding on the mechanism to use for notifying owners of collision components when the component collides with something.  I narrowed it down to two options:

-notify owners via the ICollisionOwner interface I was using

OR

-use an Event

I was leaning more towards events because I felt their semantics naturally fit with the usage pattern I was working.  If game entities want to be notified, they simply subscribe and they get notified.  This seemed cleaner and easier to understand than letting each collision component have some sort of  “NotifyOwner” flag, and then call a virtual function if the flag was true.  However I was a little worried about performance…I hadn’t really used delegates on the 360 before and I wanted to make sure that the overhead wasn’t going to be something astronomical before proceeding. So I set up a simple test harness that vaguely resembled how I was going to use events:

public delegate void EventDelegate(object sender, ref Vector3 parameter);

public class EventServer
{
    public event EventDelegate SomeEvent;

    public void RaiseEvent()
    {
        Vector3 param = new Vector3();

        if (SomeEvent != null)
            SomeEvent(this, ref param);

        //for (int i = 0; i < Handlers.Count; i++)
        //{
        //    if (Handlers[i].HandlesEvent)
        //        Handlers[i].HandleEventVirtual(this, ref param);
        //}
    }

    public List<IEventHandler> Handlers = new List<IEventHandler>();
}

public interface IEventHandler
{
    void HandleEventVirtual(object sender, ref Vector3 parameter);
    bool HandlesEvent
    {
        get;
    }
}

public class EventHandler : IEventHandler
{
    EventServer server;
    bool handleEvent;

    public EventHandler(EventServer server, bool handleEvent)
    {
        this.server = server;
        this.handleEvent = handleEvent;  

        if (handleEvent)
            server.SomeEvent += new EventDelegate(HandleEvent);
    }

    void HandleEvent(object sender, ref Vector3 parameter)
    {
        parameter.Y += 0.001f;
    }

    public virtual void HandleEventVirtual(object sender, ref Vector3 parameter)
    {
        parameter.X += 0.001f;
    }

    public bool HandlesEvent
    {
        get { return handleEvent; }
    }
}

public class EventHandler2 : EventHandler
{
    public EventHandler2(EventServer server, bool handleEvent)
        : base(server, handleEvent)
    {
    }

    public override void HandleEventVirtual(object sender, ref Vector3 parameter)
    {
        base.HandleEventVirtual(sender, ref parameter);
        parameter.Normalize();
    }
}

Pretty simple set up: a class that will dole out events to a collection of handlers, with a derivative of the handler class also being thrown in just to make sure the compiler doesn’t do anything funky that will prevent us from actually getting virtual functions.  To test events we leave it like this, to test virtual functions we comment out the event invocation and use the virtual function call instead.  Any .NET junkies might notice I’ve violated the guidelines for creating custom event handlers by not using a an EventArgs derivate…the reason why is because EventArgs in a class, so creating a new instance would generate garbage everytime the event fires.  And as we all know..the GC is not our friend on the Xbox.

I set it up to run with various amounts of event handlers distributed across various amounts of event servers.  I then set up the game class to fire off all the event servers in the Update function and use a Stopwatch to time how long it took.  I also averaged the timing results across 64 frames to smooth out the results.  This is what I got:

50:1         9
             22

500:1        710
             220

5000:1       163000 (3.26ms)
             2200

5000:10      18600
             2200

5000:100     1000
             2200

5000:1000    820
             2200

The table shows the EventHandler:EventServer ratio, and on the right is the of time taken for invocation (in ticks).  The number on top is from using Events, the bottom from using virtual functions.  The first few results are pretty interesting:  the virtual function method scales linearly with the amount of handlers we have, while the the time required for firing events goes up exponentially.   The bottom half of the results are even more interesting: the time taken goes way down as we start to distribute the handlers more evenly across servers.  In fact it goes down so much, it becomes quicker than virtual functions!.  Crazy.

Anyway I had my answer: events would be fine with my setup.  I can’t foresee any reason why more than one handler would subscribe to the same collision component, and even if it did the overhead is basically miniscule for the numbers I’ll be working with.  But it’s always fun to experiment, right?

Deferred Shadow Maps Sample

Got a new sample ready, this one  shows how you can defer shadow map calculations to a separate screen-space pass using a depth buffer.  Check it out on Ziggyware!

deferredshadowmaps

Teach Your Effect’s A New Trick

The Effects Framework is a pretty damn awesome tool.  However I’m afraid that’s not totally obvious to a lot of newbies, who either just don’t what it can do or haven’t been exposed to some of the situations where Effect’s can really come in handy.

One neat thing Effect’s can do that isn’t obvious from the documentation or samples is auto-generate variants of shaders for you based on the value of uniform parameters.  For instance let’s take a common scenario: lets say you have a shader for model, and you need it to work for either a point light, a spot light, or a directional light one-at-a-time.  You might write your shader code like this:

int g_iLightType;

float4 ModelPixelShader(in PSInput input) : COLOR0
{
    float4 vColor;
    if (g_iLightType == LIGHT_TYPE_POINT)
        vColor = DoPointLighting(input);
    else if (g_iLightType == LIGHT_TYPE_SPOT)
        vColor = DoSpotLighting(input);
    else
        vColor = DoDirectionalLighting(input);

    return vColor;
}

Alright, so this works.  The app sets the  g_iLightType shader parameter, and the right calculations get used. However is it optimal?  We’ve got these if statements in there, and maybe we’re not sure what they’ll get compiled into depending on the shader profile we’re targetting.  And maybe we’re not sure what the heck the driver is going to do once it gets the compiled shader.  Wouldn’t it be nice if we could avoid that?  Of course it would.  So let’s make some small changes:

float4 ModelPixelShader(in PSInput input, uniform int iLightType) : COLOR0
{   
    float4 vColor;
    if (iLightType == LIGHT_TYPE_POINT)
        vColor = DoPointLighting(input);
    else if (iLightType == LIGHT_TYPE_SPOT)
        vColor = DoSpotLighting(input);
    else
        vColor = DoDirectionalLighting(input);
}

technique PointLight
{
    pass p0
    {
        VertexShader = compile vs_2_0 ModelVertexShader();
        PixelShader = compile ps_2_0 ModelPixelShader(LIGHT_TYPE_POINT);       
    }
}

technique SpotLight
{
    pass p0
    {
        VertexShader = compile vs_2_0 ModelVertexShader();
        PixelShader = compile ps_2_0 ModelPixelShader(LIGHT_TYPE_SPOT);       
    }
}

technique DirectionalLight
{
    pass p0
    {
        VertexShader = compile vs_2_0 ModelVertexShader();
        PixelShader = compile ps_2_0 ModelPixelShader(LIGHT_TYPE_DIRECTIONAL);       
    }
}

Very similar, but one big difference: the HLSL code branches on a uniform int parameter to the pixel shader function, whose value is set in our technique declaration.  This means that the Effect knows that this parameter has a constant value for that entire technique, which allows it to generate a seperate shader for each technique where the parameter is a constant and not a variable.  Since it’s a constant for each shader variant, no branching of any sort is necessary.  Now our app just picks the technique it wants for each light source it’s handling, rather than setting a shader parameter.

Now keep in mind that using separate shaders like this will have performance implications:  switching vertex or pixel shaders has an associated overhead, and if you auto-generate different variants like we did above you’ll be switching shaders more than if you used one big shader.  Whether or not it’s a performance win will depend on what you’re doing.  However, it’s always good to be aware of all the neat tricks your tools can pull off.

Fun With Compiled Content

EDIT:  I realized it was probably a much smarter idea to just zip up the code along with the designer code and upload it somewhere.  So here it is.

Wouldn’t it be neat to be able to have a dialog you could pop up that would show all the pre-compiled content of a certain Type, with it all listed in a nice tree showing the directory structure?  Of course it would!  Only a crazy person would think otherwise.  Well the good news is I already did this, so feel free to plunder the code for your own use.

public partial class ContentBrowser : Form
{

    private static Dictionary<string, TreeNode> contentTrees = new Dictionary<string, TreeNode>();
    private static string contentDirectory;
    private static ContentManager contentManager;

    private static string[] contentTypes = {    "Texture",
                                                "Texture2D",
                                                "Texture3D",
                                                "TextureCube",
                                                "SpriteFont",
                                                "Model",
                                                "Effect"    };

    /// <summary>
    /// Traverses the specified content directory for all loadable content, and stores
    /// it as static data for use when an instance of the Form is created.
    /// </summary>
    /// <param name="services">IServiceProvider implementation, contains IGraphicsDeviceService</param>
    /// <param name="contentDirectory">The content directory to traverse</param>
    /// <param name="ownerWindow">Owner window for the status dialog</param>
    public static void Initialize(ServiceContainer services, string contentDirectory, IWin32Window ownerWindow)
    {
        ContentBrowser.contentDirectory = contentDirectory;

        // Make a content manager
        contentManager = new ContentManager(services, contentDirectory);

        // Make a small progress dialog so the user knows something is going on
        Form notificationDialog = new Form();
        notificationDialog.FormBorderStyle = FormBorderStyle.FixedDialog;
        notificationDialog.Size = new Size(350, 150);
        notificationDialog.Text = "JSMapEditor";
        notificationDialog.StartPosition = FormStartPosition.CenterScreen;
        notificationDialog.ShowInTaskbar = false;
        notificationDialog.ShowIcon = false;
        notificationDialog.ControlBox = false;

        Label statusLabel = new Label();
        statusLabel.Size = new Size(200, 50);
        statusLabel.Location = new System.Drawing.Point(100, 50);
        statusLabel.Text = "Loading Content";
        notificationDialog.Controls.Add(statusLabel);            

        notificationDialog.Show(ownerWindow);

        // Do the content loading/enumeration on a worker thread so we
        // can keep pumping messages on this thread
        Stopwatch timer = new Stopwatch();
        timer.Start();
        int count = 0;
        long time = 0;
        long lastTime = 0;
        long loadTime = 0;
        Thread workerThread = new Thread(EnumerateContent);
        workerThread.Start();

        while (!workerThread.Join(0))
        {
            Application.DoEvents();

            time = timer.ElapsedMilliseconds;
            loadTime += time - lastTime;
            lastTime = time;

            if (loadTime > 300)
            {
                statusLabel.Text = "Loading Content";
                for (int i = 1; i <= count % 4; i++)
                    statusLabel.Text += ".";
                count++;
                loadTime -= 300;
            }
        }

        notificationDialog.Hide();

        // Dispose of the content
        contentManager.Dispose();
        contentManager = null;
        GC.Collect();
    }

    /// <summary>
    /// Enumerates all loadable content for types in contentTypes, and stores
    /// the resulting tree in contentTrees
    /// </summary>
    private static void EnumerateContent()
    {
        // Recursively build the content tree
        foreach (string contentType in contentTypes)
        {
            TreeNode rootNode = new TreeNode("Content\\");
            BuildContentTree(rootNode, contentManager.RootDirectory, contentType);
            contentTrees.Add(contentType, rootNode);
        }
    }

    /// <summary>
    /// Builds the tree by looking for acceptable content.  Recursively calls itself
    /// to traverse subdirectories
    /// <param name="parentNode">The TreeNode representing the current directory</param>
    /// <param name="directory">The current direcotry to traverse</param>
    /// <param name="contentType">The name of the content Type to look for</param>
    /// </summary>
    private static void BuildContentTree(TreeNode parentNode, string directory, string contentType)
    {
        // Find all the subdirectories, and recursively search them
        string[] subdirectories = Directory.GetDirectories(directory);
        foreach (string subdirectory in subdirectories)
        {
            string relativePath = subdirectory.Substring(directory.Length + 1);
            TreeNode directoryNode = new TreeNode(relativePath + "\\");
            BuildContentTree(directoryNode, subdirectory, contentType);
            if (directoryNode.Nodes.Count > 0)
                parentNode.Nodes.Add(directoryNode);
        }

        // Check out all the .xnb files, see if we can load them as the target type
        string[] contentFiles = Directory.GetFiles(directory, "*.xnb");
        foreach (string contentFile in contentFiles)
        {
            string loadName = Path.GetDirectoryName(contentFile) + "\\"
                                  + Path.GetFileNameWithoutExtension(contentFile);
            if (TryLoadContent(loadName, contentType))
            {
                TreeNode contentNode = new TreeNode(
                                         Path.GetFileNameWithoutExtension(contentFile));
                contentNode.Tag = loadName;
                parentNode.Nodes.Add(contentNode);
            }
        }

    }

    /// <summary>
    /// Checks whether the filename is valid by attempting to
    /// load it with the ContentManager.
    /// </summary>
    /// <param name="contentFile">The filename to check</param>
    /// <param name="contentType">The name of the content Type to check the content against</param>
    /// <returns>True if successful</returns>
    private static bool TryLoadContent(string contentFile, string contentType)
    {
        try
        {
            object content = contentManager.Load<object>(contentFile);
            if (content.GetType().Name == contentType)
                return true;
            else
                return false;
        }
        catch (ContentLoadException)
        {
            return false;
        }
    }

    private string selectedContentFile;

    public string SelectedContentFile
    {
        get { return selectedContentFile; }
    }

    /// <summary>
    /// Creates an instance of ContentBrowser
    /// </summary>
    /// <param name="value">The default content filename</param>
    /// <param name="contentType">The type of content to browse</param>
    public ContentBrowser(String value, Type contentType)
    {
        InitializeComponent();

        contentTree.Nodes.Add(contentTrees[contentType.Name]);
        contentTree.ExpandAll();

        selectedContentFile = value;
    }

    /// <summary>
    /// Called when the Form is closed
    /// </summary>
    /// <param name="e"></param>
    protected override void OnClosed(EventArgs e)
    {
        contentTree.Nodes.Clear();
        base.OnClosed(e);
    }

    /// <summary>
    /// Event handler for the TreeView's mouse clicks
    /// </summary>
    /// <param name="sender">contentTree</param>
    /// <param name="e">Event args</param>
    private void contentTree_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e)
    {
        // Tag != null means it's a content node
        if (e.Node.Tag != null)
        {
            selectedContentFile = (string)e.Node.Tag;
            selectedContentFile = selectedContentFile.Substring(contentDirectory.Length + 1);
        }
    }
}

Okay so a few notes on usage…it uses the string array “contentTypes” to know which types to check for.  You should fill this out with whatever Type’s you’re loading from the ContentManager.  The static Initialize method should either be called when your app starts up, and you’ll need to do it before you can actually create an instance of ContentBrowser.  It shows a little loading dialog while it’s working, so you have something to show the user while it’s happening.  You could take that out, if you wanted.

I also made a UITypeEditor so that you can have this dialog to set a property in a PropertyGrid:

/// <summary>
/// Used to allow the user to browse for content in the PropertyGrid
/// </summary>
/// <typeparam name="T">The Type of content to display in the ContentBrowser</typeparam>
public class ContentEditor<T> : UITypeEditor
{
    public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
    {           
        return UITypeEditorEditStyle.Modal;
    }

    public override object EditValue(ITypeDescriptorContext context,
                                        IServiceProvider provider,
                                        object value)
    {
        IWindowsFormsEditorService editorService = null;

        if (provider != null)
        {
            editorService = provider.GetService(typeof(IWindowsFormsEditorService)) as IWindowsFormsEditorService;
        }

        if (editorService != null)
        {
            // Pop up our dialog
            ContentBrowser browser = new ContentBrowser((string)value, typeof(T));

            if (editorService.ShowDialog(browser) == DialogResult.OK)
                value = browser.SelectedContentFile;
        }

        return value;
    }
}

Book Recommendation: Real-Time Collision Detection

I just recently ordered and received Real-Time Collision Detection from Amazon, and it was worth every penny.  Collision detection was never something I was never particularly interested in, and in that past I was always willing to just leave it all up to a physics package to handle.  But as anyone else working on an XNA game for the 360 knows, a physics engine isn’t really a practical option this time around.  I’ve had some basic stuff working for a long time now (thanks to Fabio Policarpo’s BoxCollider), but my level designer has made some requests that have led me to finally dig in and start making my system robust while keeping it performance.  Fortunately the book I ordered cam to the rescue:  it has some fantastic overviews and demonstration of just about every aspect of collision detection.  The explanations are concise and get right to the important points, and derivation of the mathematical formulas are always followed by a sample implementation in C++ (If you’re like me and you hate pseudo-code, then this truly is a wonderful thing).

So kids if you don’t have it yet…go get this book.