Welcome to MSDN Blogs Sign in | Join | Help

BasicEffect and SpriteBatch shader source code

I'm pleased to announce we have released the HLSL source code for the shaders used by BasicEffect and SpriteBatch.

If you are just getting started with shaders and looking for something simple, these are probably not the best place to start. I would recommend the Shader Series as an introduction to 3D, or the Sprite Effects sample for 2D.

These new releases are intended for experienced programmers who are already familiar with how shaders work, and want to see exactly how we implemented the shaders that come built into the XNA Framework. The Xbox version of the SpriteBatch vertex shader is particularly interesting in the way it uses a vfetch instancing technique to offload computations to the GPU.

Everything you ever wanted to know about IntermediateSerializer

It is good to know how to fish, but sometimes you just want to go to the store and buy a salmon that somebody else already caught for you. With this in mind, here is my attempt to fill in the blanks about exactly how to use IntermediateSerializer.

 

The Basics

Consider this test class:

    class TestClass
{
public int PublicField = 1;
protected int ProtectedField = 2;
private int PrivateField = 3;
internal int InternalField = 4;

public string GetSetProperty
{
get { return "Hello World"; }
set { }
}

public string GetOnlyProperty
{
get { return "Hello World"; }
}

public string SetOnlyProperty
{
set { }
}

public NestedClass Nested = new NestedClass();
}

class NestedClass
{
public string Name = "Shawn";
public bool IsEnglish = true;
}

Using the technique described in my previous post, it will create this XML:

    <?xml version="1.0" encoding="utf-8"?>
<XnaContent>
<Asset Type="TestClass">
<GetSetProperty>Hello World</GetSetProperty>
<PublicField>1</PublicField>
<Nested>
<Name>Shawn</Name>
<IsEnglish>true</IsEnglish>
</Nested>
</Asset>
</XnaContent>

We can draw many conclusions from this:

  • The XML always starts with an <XnaContent> element
  • This contains an <Asset> element, with a Type attribute that specifies the type of the data
  • Within the <Asset> element, all public fields and properties are serialized, using a separate XML element for each
  • Protected, private, or internal data is skipped
  • Get-only or set-only properties are skipped
  • Properties come before fields
  • If there is more than one field or property, these are serialized in the order they were declared
  • Nested types are serialized using nested XML elements

 

Inheritance

What if we have a class hierarchy where one type derives from another?

    class BaseClass
{
public int elf = 23;
}

class TestClass : BaseClass
{
public string hello = "world";
}

This produces:

    <?xml version="1.0" encoding="utf-8"?>
<XnaContent>
<Asset Type="TestClass">
<elf>23</elf>
<hello>world</hello>
</Asset>
</XnaContent>

Conclusion:

  • Base class members are serialized before data from the derived type

 

Including Private Members

By default, IntermediateSerializer only includes public fields and properties. We can customize this behavior using the ContentSerializerAttribute, which forces even private members to be serialized:

    class TestClass
{
[ContentSerializer]
private int elf = 23; // will be serialized
}
 

 

Excluding Public Members

If there are public fields or properties that we do not wish to serialize, we can use the ContentSerializerIgnoreAttribute to skip them:

    class TestClass
{
[ContentSerializerIgnore]
public int elf = 23; // will not be serialized
}

 

Renaming XML Elements

By default, XML elements are named after the field or property which defined them. If you want a different element name, you can specify this using attributes:

    class TestClass
{
[ContentSerializer(ElementName = "ShawnSaysHello")]
string hello = "world";

[ContentSerializer(ElementName = "ElvesAreCool")]
int elf = 23;
}

Resulting XML:

    <?xml version="1.0" encoding="utf-8"?>
<XnaContent>
<Asset Type="TestClass">
<ShawnSaysHello>world</ShawnSaysHello>
<ElvesAreCool>23</ElvesAreCool>
</Asset>
</XnaContent>

 

Null References

If your data contains a null value, for instance:

    class TestClass
{
public string hello = null;
}

This will produce:

    <?xml version="1.0" encoding="utf-8"?>
<XnaContent>
<Asset Type="TestClass">
<hello Null="true" />
</Asset>
</XnaContent>

Note the use of a special XML attribute to indicate the null value. This is different to just not putting any data inside the XML element! The distinction is important for types such as strings, because a null string is not the same thing as a string which is present but empty.

 

Optional Elements

You can use attributes to specify that null values should be skipped entirely, rather than serialized with a Null attribute tag. For instance:

    class TestClass
{
[ContentSerializer(Optional = true)]
public string a = null;

public string b = null;

[ContentSerializer(Optional = true)]
public string c = string.Empty;
}

Resulting XML:

    <?xml version="1.0" encoding="utf-8"?>
<XnaContent>
<Asset Type="TestClass">
<b Null="true" />
<c></c>
</Asset>
</XnaContent>

Notes:

  • Field a has been skipped, because it was null and had the optional attribute
  • Field b was not skipped, even though it is null, because it did not have this attribute
  • Field c was not skipped, even though it had the attribute, because it was not null

 

AllowNull

If you have a field that should never be null, you can specify this with an attribute:

    class TestClass
{
[ContentSerializer(AllowNull = false)]
string a;
}

This has no effect when serializing to XML, but it will throw an exception while deserializing if the XML tries to specify a null value.

 

Collections

What about collections?

    class TestClass
{
public string[] StringArray = { "Hello", "World" };
public List<string> StringList = new List<string> { "This", "is", "a", "test" };
public int[] IntArray = { 1, 2, 3, 23, 42 };
}

Resulting XML:

    <?xml version="1.0" encoding="utf-8"?>
<XnaContent>
<Asset Type="TestClass">
<StringArray>
<Item>Hello</Item>
<Item>World</Item>
</StringArray>
<StringList>
<Item>This</Item>
<Item>is</Item>
<Item>a</Item>
<Item>test</Item>
</StringList>
<IntArray>1 2 3 23 42</IntArray>
</Asset>
</XnaContent>

Note how IntArray is serialized in a different way to StringArray or StringList. This is a special optimization that IntermediateSerializer applies to collections of numeric types (ints, longs, floats, doubles, bytes, etc). To save space, it uses whitespace to separate each item, rather than XML markup. This is an important part of efficiently handling 3D model data such as vertex and index buffers.

 

CollectionItemName

By default, each item in a collection is serialized as an <Item> element. You can change this using attributes:

    class TestClass
{
[ContentSerializer(ElementName = "w00t", CollectionItemName = "Flibble")]
string[] StringArray = { "Hello", "World" };
}

Result:

    <?xml version="1.0" encoding="utf-8"?>
<XnaContent>
<Asset Type="TestClass">
<w00t>
<Flibble>Hello</Flibble>
<Flibble>World</Flibble>
</w00t>
</Asset>
</XnaContent>

 

Dictionaries

XmlSerializer doesn't support dictionaries, but IntermediateSerializer does!

    class TestClass
{
public Dictionary<int, bool> TestDictionary = new Dictionary<int, bool>();

public TestClass()
{
TestDictionary.Add(23, true);
TestDictionary.Add(42, false);
}
}

Result:

    <?xml version="1.0" encoding="utf-8"?>
<XnaContent>
<Asset Type="TestClass">
<TestDictionary>
<Item>
<Key>23</Key>
<Value>true</Value>
</Item>
<Item>
<Key>42</Key>
<Value>false</Value>
</Item>
</TestDictionary>
</Asset>
</XnaContent>

 

Math Types

People are often surprised by how IntermediateSerializer handles the XNA Framework math types. Let's look at some examples:

    class TestClass
{
public Vector3 Vector = new Vector3(1, 2, 3);
public Rectangle Rectangle = new Rectangle(1, 2, 3, 4);
public Quaternion Quaternion = new Quaternion(1, 2, 3, 4);
public Color Color = Color.CornflowerBlue;
public Vector2[] VectorArray = new Vector2[] { Vector2.Zero, Vector2.One };
}

This will produce:

    <?xml version="1.0" encoding="utf-8"?>
<XnaContent>
<Asset Type="TestClass">
<Vector>1 2 3</Vector>
<Rectangle>1 2 3 4</Rectangle>
<Quaternion>1 2 3 4</Quaternion>
<Color>FF6495ED</Color>
<VectorArray>0 0 1 1</VectorArray>
</Asset>
</XnaContent>

This is similar to the optimization we previously saw being applied to collections of numeric values, using whitespace instead of XML markup to make the encoding more efficient. When you have a collection of math types, such as the vector array shown here, both optimizations are combined.

Colors are represented in hex format, similar to HTML.

 

Polymorphic Types

One advantage of IntermediateSerializer over XmlSerializer is that it supports arbitrary polymorphic data without requiring all the possible subtypes to be predeclared. Let's look at an example:

    class A
{
public bool Value = true;
}

class B : A { }
class C : A { }

class TestClass
{
public object Hello = "World";
public object Elf = 23;
public A[] PolymorphicArray = { new A(), new B(), new C() };
}

Resulting XML:

    <?xml version="1.0" encoding="utf-8"?>
<XnaContent>
<Asset Type="TestClass">
<Hello Type="string">World</Hello>
<Elf Type="int">23</Elf>
<PolymorphicArray>
<Item>
<Value>true</Value>
</Item>
<Item Type="B">
<Value>true</Value>
</Item>
<Item Type="C">
<Value>true</Value>
</Item>
</PolymorphicArray>
</Asset>
</XnaContent>

XML Type attributes are used to indicate the types of polymorphic data. The Hello and Elf fields need this attribute, because they are declared as type object, but then contain data of a more specialized subtype.

Note how in the PolymorphicArray, the first item (of type A) is not polymorphic, but the second two (of types B and C) are. This is because I declared it as an array of A, so only the items that are subclasses of A require a Type attribute. If I had declared it as an array of object, all three items would require this attribute.

 

Namespaces

If your XML uses a lot of Type attributes, it can get pretty boring having to enter long names like "Microsoft.Xna.Framework.Graphics.Color" over and over again. IntermediateSerializer can cut down this redundancy by using XML namespaces as shortcuts for C# namespaces. You can think of this as an XML equivalent of the C# "using" keyword.

For instance this class:

    namespace This.Is.A.Long.Namespace.For.A.Test
{
class TestHelper
{
public bool Value = true;
}

class TestClass
{
public object A = new TestHelper();
public object B = Vector2.Zero;
public object C = Color.CornflowerBlue;
}
}

Can be serialized like so:

    <?xml version="1.0" encoding="utf-8"?>
<XnaContent xmlns:Test="This.Is.A.Long.Namespace.For.A.Test"
xmlns:Framework="Microsoft.Xna.Framework"
xmlns:Graphics="Microsoft.Xna.Framework.Graphics">
<Asset Type="Test:TestClass">
<A Type="Test:TestHelper">
<Value>true</Value>
</A>
<B Type="Framework:Vector2">0 0</B>
<C Type="Graphics:Color">FF6495ED</C>
</Asset>
</XnaContent>

The three xmlns attributes define namespace shortcuts, after which we can refer to types using the abbreviated syntax "Test:TestHelper", "Framework:Vector2", etc.

This is optional: you could expand out the namespaces and the XML would load exactly the same. When you save an XML file, IntermediateSerializer uses some heuristics to choose namespace shortcuts, as shown in the above example. If you are writing the XML by hand, you are free to define any shortcuts you like.

 

FlattenContent

By default, every nested class or collection will open up a nested XML element. You can change this behavior using attributes. For instance this class:

    class TestClass
{
[ContentSerializer(FlattenContent = true)]
public NestedClass Nested = new NestedClass();

[ContentSerializer(FlattenContent = true, CollectionItemName = "Boo")]
public string[] Collection = { "Hello", "World" };
}

class NestedClass
{
public string Name = "Shawn";
public bool IsEnglish = true;
}

Produces this XML:

    <?xml version="1.0" encoding="utf-8"?>
<XnaContent>
<Asset Type="TestClass">
<Name>Shawn</Name>
<IsEnglish>true</IsEnglish>
<Boo>Hello</Boo>
<Boo>World</Boo>
</Asset>
</XnaContent>

If I left out the FlattenContent attributes, it would produce this instead:

    <?xml version="1.0" encoding="utf-8"?>
<XnaContent>
<Asset Type="TestClass">
<Nested>
<Name>Shawn</Name>
<IsEnglish>true</IsEnglish>
</Nested>
<Collection>
<Item>Hello</Item>
<Item>World</Item>
</Collection>
</Asset>
</XnaContent>

 

Shared Resources

The default behavior of representing nested classes as nested XML elements works fine as long as you limit yourself to tree-like data, but is not capable of representing circular data structures. For instance if Foo contained a reference to Bar, and then Bar contained a reference back to Foo, the serializer would nest Bar inside Foo, and then another copy of Foo inside Bar, with another Bar inside that... Infinite recursion = stack overflow = badness.

Solution: use attributes to specify that your circular references should be serialized as shared resources. Consider this circular linked list:

    class TestClass
{
[ContentSerializer(SharedResource = true)]
public Linked Head;

public TestClass()
{
Head = new Linked();
Head.Value = 1;

Head.Next = new Linked();
Head.Next.Value = 2;

Head.Next.Next = new Linked();
Head.Next.Next.Value = 3;

Head.Next.Next.Next = Head;
}
}

class Linked
{
public int Value;

[ContentSerializer(SharedResource = true)]
public Linked Next;
}

If I left out the SharedResource attributes, serializing this data would throw an exception complaining that it is a cyclic data structure. But with the attributes, we get this XML:

    <?xml version="1.0" encoding="utf-8"?>
<XnaContent>
<Asset Type="TestClass">
<Head>#Resource1</Head>
</Asset>
<Resources>
<Resource ID="#Resource1" Type="Linked">
<Value>1</Value>
<Next>#Resource2</Next>
</Resource>
<Resource ID="#Resource2" Type="Linked">
<Value>2</Value>
<Next>#Resource3</Next>
</Resource>
<Resource ID="#Resource3" Type="Linked">
<Value>3</Value>
<Next>#Resource1</Next>
</Resource>
</Resources>
</XnaContent>

See what's going on here? Rather than being embedded directly at the point where they are referenced, the shared resource objects have been gathered up into a <Resources> element, which comes after the regular <Asset> data. Resources are identified by unique ID strings. In this case the serializer has generated ID values  using a simple naming convention (#Resource1, #Resource2, etc) but you can use any string you like if you are writing the XML by hand.

 

External References

The final thing I want to talk about today is how IntermediateSerializer handles the Content Pipeline ExternalReference type. This test class:

    class TestClass
{
public ExternalReference<Texture2D> Texture = new ExternalReference<Texture2D>("grass.tga");
public ExternalReference<Effect> Shader = new ExternalReference<Effect>("foliage.fx");
}

Produces this XML:

<?xml version="1.0" encoding="utf-8"?>
<XnaContent>
<Asset Type="TestClass">
<Texture>
<Reference>#External1</Reference>
</Texture>
<Shader>
<Reference>#External2</Reference>
</Shader>
</Asset>
<ExternalReferences>
<ExternalReference ID="#External1" TargetType="Microsoft.Xna.Framework.Graphics.Texture2D">grass.tga</ExternalReference>
<ExternalReference ID="#External2" TargetType="Microsoft.Xna.Framework.Graphics.Effect">foliage.fx</ExternalReference>
</ExternalReferences>
</XnaContent>

Similar to shared resources, the ExternalReference data has been gathered up into a special <ExternalReferences> element at the end of the file, outside the regular <Asset> data. Each reference is identified by a unique ID string, which is generated by the serializer when it writes XML, but can be anything you like if you are writing the XML by hand.

There are two reasons for this seemingly crazy encoding:

Firstly, IntermediateSerializer applies special logic to the filenames used by ExternalReference objects. In memory, ExternalReference always stores filenames in absolute format. This keeps things nice and simple, avoiding the confusion that can occur with relative filenames if you lose track of what root directory they are supposed to be relative to. But on disk, filenames are stored relative to the XML file which contains them. This allows you to move an entire content directory, containing many files which reference each other, to a different location, and still have the references resolve correctly. IntermediateSerializer handles the translation between absolute and relative formats during the Serialize and Deserialize operations. This is the purpose of the third parameter to the Serialize method, which tells it what directory references should be relative to. If you don't specify that, the output XML will contain absolute filenames.

Secondly, gathering the ExternalReference data together at the end of the file makes it easy for other tools to mine this information. Even without knowing anything about what a "TestClass" is, I could write a program that scanned the above XML, skipped over the entire <Asset> tag, and extracted the target type and filename of the two references. This could be useful for things like dependency analysis or source control management. For instance it would be trivial to write a tool that scanned the obj directory after a build and answered questions like "which models are using texture X?". We've never actually written such a tool, but who knows? It seemed like a good idea to make this data easily available in case we someday needed it.

 

But wait, there is more! Stay tuned for some more advanced IntermediateSerializer customization options...

Teaching a man to fish

Variants of the following question come up somewhat regularly on the XNA forums:

  • I want to import game data from an XML file
  • I plan to do this using the Content Pipeline IntermediateSerializer
  • I have my custom data type all ready to go
  • But I don't know how I should format the XML for it!

You already have the tools to answer this yourself. Here's how:

Fire up Visual Studio and create a new Console Application project.

Right-click on the References node, and add the Microsoft.Xna.Framework.Content.Pipeline assembly.

Add using declarations for the System.Xml and Microsoft.Xna.Framework.Content.Pipeline.Serialization.Intermediate namespaces.

Add a test class with the same layout as whatever data you want to serialize, but initialized with dummy test values. For instance:

    namespace XmlTest
    {
        class MyTest
        {
            public int elf = 23;
            public string hello = "Hello World";
        }
    }

Add this code to Main:

    MyTest testData = new MyTest();

    XmlWriterSettings settings = new XmlWriterSettings();
    settings.Indent = true;

    using (XmlWriter writer = XmlWriter.Create("test.xml", settings))
    {
        IntermediateSerializer.Serialize(writer, testData, null);
    }

Run the program. Look in the bin\Debug folder, and open the test.xml output file. With the class shown above, this will look like:

    <?xml version="1.0" encoding="utf-8"?>
    <XnaContent>
      <Asset Type="XmlTest.MyTest">
        <elf>23</elf>
        <hello>Hello World</hello>
      </Asset>
    </XnaContent>

Tada! That's how IntermediateSerializer represents this particular class in XML.

How serializers work

To understand the differences between the XNA Framework IntermediateSerializer and the standard .NET XmlSerializer, it can be useful to look at how they are implemented.

Fundamentally, all serializers work in a similar way:

  • Use the .NET Reflection API to scan over an object
  • Find all the fields and properties that need to be serialized
  • Get the value of each member
  • Write each value to the output XML file

At a basic level, this is easy stuff. You can write a simple serializer in just one page of C# code.

Trouble is, reflection is SLOOOW. If we actually went through all those steps for every object we encountered, it would take hours to serialize a large and complex object!

To speed things up, the Windows implementation of XmlSerializer uses a radically different approach:

  • When you create an XmlSerializer, you must tell it what type(s) you intend to serialize
  • The XmlSerializer constructor uses reflection to scan over the specified type and find what data it contains
  • It generates a C# function that will efficiently serialize that particular type
  • It dynamically compiles the C# code into IL instructions
  • This means there is no need for the Serialize or Deserialize methods to use reflection at all: they can just call into a highly optimized C# function

Once you understand this implementation detail, you will release some of the behaviors of XmlSerializer are inevitable results of its design:

  • XmlSerializer can only serialize public fields and properties, because C# does not allow the generated serializer function to access private fields of some other class
  • XmlSerializer cannot handle arbitrary polymorphic types, requiring attributes to predeclare all the possible subtypes, because if it did not know these types ahead of time, it would not be possible to generate a correct C# function to handle them all

When I implemented IntermediateSerializer for the XNA Framework Content Pipeline, I chose a different strategy:

  • Lazily initialize serializer state the first time each type is encountered
  • Use reflection to scan over the type and find what data it contains
  • Use Reflection.Emit to generate dynamic methods for getting and setting member data, and for constructing new instances of types
  • Store these generated methods in a table
  • Serializing a type is now just a matter of walking through this table and calling the lazily generated helper methods

These implementation choices behave a little differently to XmlSerializer:

  • IntermediateSerializer has no problem with arbitrary polymorphic types, and does not require all the possible subclasses to be predeclared, because it can lazily create new helper methods any time it encounters a new type
  • Because it generates IL rather than C#, IntermediateSerializer can access internal and private members as well as publics
  • As a direct result of the previous point, IntermediateSerializer cannot work in a partial trust environment

So which approach is better? Both work. The XmlSerializer implementation is perhaps slightly faster, but my way is more flexible. I suspect one of the main reasons XmlSerializer works the way it does is that it predates the DynamicMethod class, which was added in the 2.0 CLR. I wonder whether the core framework guys would design XmlSerializer the same way today, if they had access to the more powerful and dynamic reflection features of more recent CLR versions.

What about Xbox? It doesn't have Reflection.Emit or DynamicMethod, which is why IntermediateSerializer can only be used during the Content Pipeline build process, and is not available from your runtime Xbox code. But Xbox doesn't have CSharpCodeProvider, either, so how come XmlSerializer works at runtime?

The answer is that the .NET Compact Framework provides an entirely different implementation of XmlSerializer. This has the same API as the Windows version, and generates the same XML, but it does so using regular reflection calls, which are slow and cause a lot of boxing. This is fine for small tasks like loading a config file or saving a hiscore table, but wouldn't work so well for an entire level. Yet another reason why the Content Pipeline precompiles data into XNB format, and avoids trying to deserialize XML at runtime.

IntermediateSerializer vs. XmlSerializer

I'm posting again!

This must mean my workload has quieted down, right?

Sadly no. My dev machine is currently tied up running a build and unit test pass, plus I'm building the Zune tree at the same time, which has slowed things to a crawl. I would normally try to work on a different bug at the same time as building my previous fix, but right now my computer can barely manage Live Writer, let alone Visual Studio :-)

So let's talk some more about XML in the Content Pipeline.

Some time before we formally started work on Game Studio 1.0, I was trying to figure out how the Content Pipeline could save model data in XML cache files. My first thought was to use the standard .NET XmlSerializer, but the more I looked into this, the more problems I encountered.

The main problem was size. Model and texture data tends to be pretty big. It often contains huge lists of numbers (such as triangle indices) and vectors (vertex positions or texture colors). To make a usable build system, we had to be able to cache this data in a small and efficient format.

This is what XmlSerializer does if you give it a list containing two vectors:

  <Vertices>
    <Vertex>
      <X>1</X>
      <Y>2</Y>
      <Z>3</Z>
    </Vertex>
    <Vertex>
      <X>23</X>
      <Y>42</Y>
      <Z>-1</Z>
    </Vertex>
  </Vertices>

Yikes! I wanted to store that data in a more compact format, perhaps something like:

  <Vertices>1 2 3 23 42 -1</Vertices>

So I started looking at the serializer extensibility model. XmlSerializer is pretty flexible: you can use attributes to adjust things, or implement the IXmlSerializable interface to control exactly how the XML is written.

Trouble is, the attributes were not powerful enough to do what I wanted. It didn't seem like a good idea to have our math types implement IXmlSerializable, because every game uses vectors, while this XML stuff was only relevant inside the Content Pipeline. If Vector3 implemented IXmlSerializable, that would force every XNA Framework game to load the (rather large) System.Xml assembly. Zune was only a vague possibility back then, but it was a safe guess we might someday want to run on some kind of portable device, and we didn't want to build such a fundamental memory inefficiency into our API design.

Even if we did make Vector3 implement IXmlSerializable, that wouldn't help with collections of vectors. There is obviously no way for us to add a custom interface to the standard List<T> class, so instead we would have to implement IXmlSerializable on every parent type that might contain such a list. Ick.

Another problem with XmlSerializer was the limited support for polymorphic types. Given a class such as:

    class Model
    {
        public object Tag { get; set; }
    }

XmlSerializer is unable to serialize the Tag value, because it does not know ahead of time what the data type might be. To make this work, the serializer must be told all the possible types this property could ever contain:

    class Model
    {
        [XmlElement(typeof(string))]
        [XmlElement(typeof(bool))]
        [XmlElement(typeof(int))]
        public object Tag { get; set; }
    }

Not very scalable! The Content Pipeline object model includes a lot of polymorphic information, such as Tag properties and OpaqueData dictionaries. What if you wanted to set your Tag to a custom, user defined type?

Also, XmlSerializer doesn't support dictionary types, and it can only serialize public fields and properties. Not fatal, but this was irritating for some of the data I was trying to save.

The more time I spent working with XmlSerializer, the more problems I ran into. What to do? I made a list of options:

  1. Use XmlSerializer, and put up with the limitations of an awkward object model, huge cache files, and slow content build
  2. Change our design to remove the need for cache files, reducing the quality of incremental rebuilds
  3. Use a simpler binary format for the cache files, which would enable fast rebuilds, but would not be human readable and thus useless for debugging
  4. Write our own serializer that could work exactly the way we wanted

That last choice was appealing, but also deeply scary. Surely writing a whole new serializer from scratch would be a huge amount of work?

When in doubt, prototype.

I came in to the office one weekend, and spent two days attempting to quantify exactly how hard this would be. By Sunday night I had my answer: easy peasy! In just 48 hours I was able to make a serializer that efficiently saved my data into a compact XML format, exactly the way I wanted it. Of course it took a couple more weeks to handle all the corner cases (generics, nullables, robust error handling, etc) and write unit tests, but it is much easier to get approval for a seemingly risky feature design if you can show your boss an already working prototype implementation.

So that is why IntermediateSerializer exists.

I created it to do exactly what I needed for caching intermediate Content Pipeline data. You might find it useful if you have similar requirements:

  • Compact XML representation for lists of numeric and vector types
  • Handles arbitrary polymorphic data without requiring subtypes to be predeclared
  • Handles shared resources, for serializing cyclic data structures
  • Handles external references, automatically fixing up relative filename links from one file to another
  • Supports dictionaries
  • XML representation can be customized without directly modifying the target types (by implementing a ContentTypeSerializer)

But there are also situations where XmlSerializer would be a better choice:

  • Works on Xbox, so you can use it at runtime as well as during your content build
  • More flexible attributes for controlling whether data appears as an XML element or attribute
  • Supports XML schemas
  • Supports SOAP

Next up: more details on how the IntermediateSerializer works, and how to control it.

Pandemonium

Andy Patrick, an ex-Rare developer of Kameo and Viva Pinata fame, has started blogging about his adventures making a game with the XNA Framework.

His first couple of posts have some great advice about the importance of building good debugging support into your game engine. I can second his experience of having taken such things for granted until I one day had to work on a game without them!

In fact I suspect this philosophy of working smart and building tools to save yourself time is one of the biggest differentiators between great and merely ok games, far more than the shader wizardry and optimization tricks that usually get all the attention.

ShaderX2

Many years ago, in my former life as a UK game developer, I wrote a couple of chapters for a book called ShaderX2. And now this is available for download as a PDF. How cool is that? It would be great if more publishers would release their old books for free.

I had a lot of fun writing my articles, and learned two important things:

  • Writing is hard and time consuming
  • Technical books are not a good source of income :-)

I love fixing bugs

There is something deeply satisfying about prolonged periods of bug fixing.

Of course writing original code can be satisfying too, but at least for me, fixing bugs tickles an itch that normal programming simply cannot reach.

I think partly this is because working on bugs makes me feel more productive. When I am working on a new feature it may be weeks or even months before I am ready to check in, but I can sometimes fix dozens of bugs in a single day. Think, implement, test, check in, mark as resolved. Such visible signs of progress make me feel good :-)

It is interesting how most bugs take me along a similar path:

  • What is this crazy issue someone filed on Connect?
  • Huh? That can't possibly be the case!
  • Stare at the code for a while
  • Nope, no bug here: code looks fine to me
  • Oohhhh...
  • Yeah, there's a problem sure 'nuff
  • Dang, I don't see how I can fix this without breaking backward compatibility
  • Think hard
  • I wonder if...
  • Type furiously for a minute or two
  • Yeah!
  • Check in
  • Satisfaction

GameFest 2008

Details of the XNA Game Studio track for the 2008 GameFest conference are up. I'm doing two talks, about the Content Pipeline and networking.

Our officially scheduled topic (XML and the Content Pipeline) will resume just as soon as I finish preparing my GameFest slides and implementing my 3.0 framework features. Things are a little hectic right now :-)

/me is famous

Nazeeh continues his series of XNA team member interviews by talking not only to yours truly, but also my cat Rhys.

Indexing my old blog posts

While replying to questions in the forums, I often find myself wanting to link to one of my old blog posts. The only problem is finding the post in question! Google works if I can remember the title, but otherwise I'm stuck looking through my blog history for something I can vaguely remember writing about last year, or was it the year before?

So I made this index.

Thanks to the power of automated scripts, I should be able to keep this up to date as I add new posts, too.

XML and the Content Pipeline

I decided to write a few posts about the role played by XML in the XNA Framework Content Pipeline, because this isn't well documented and people seem to find it confusing.

The first thing to get straight is the distinction between which things are fundamental parts of the pipeline architecture, versus which are just specific implementations for one particular type of data. Let's start with a recap of the basic pipeline architecture:

  1. You have a file containing game data, which can be in any format you like
  2. The ContentImporter reads this file from disk, returning a managed object
    1. It might return one of our standard Microsoft.Xna.Framework.Content.Pipeline.Graphics types, but could also load any custom type of your own
  3. The ContentProcessor converts the managed object into a different format
    1. Sometimes it returns the same type, but massages the contents of the data (for instance adding mipmaps to a texture)
    2. Other times it may return an entirely different type (for instance converting a FontDescription into a SpriteFontContent)
    3. The processor may also be a no-op
  4. The ContentTypeWriter writes the processor output object into a binary .xnb file
  5. The .xnb file is deployed to Xbox
  6. Your game calls ContentManager.Load
  7. The ContentTypeReader loads the .xnb data into memory

Note that there is no mention of XML in any of these steps. So at a fundamental level, XML is not part of the underlying Content Pipeline architecture.

XML enters the picture in two places:

In stage 1 of the steps described above, your file might well happen to be in XML format. If that is the case, you would want the importer in stage 2 to read XML data. There are many ways this can be achieved:

  • You could use our built in XmlImporter, which is a trivial wrapper around the IntermediateSerializer class

  • Or you could write a custom importer using any of the following:
    • The standard .NET XmlSerializer
    • Or XmlDocument
    • Or XPath
    • Or XmlReader
    • Or the serializer formerly known as Indigo
    • Or the WPF XAML serializer
    • Or any of the various third party XML solutions
    • Notice a trend here? .NET offers a lot of different ways to read XML data!

Why, given all these options, did we bother to create our own IntermediateSerializer? Weren't there enough different serializers already?

Because of the second place where the pipeline uses XML.

After the importer runs, but before the processor, we have an optional stage 2.5, where we write the data that was just loaded to an XML file in the obj directory. We do this for two reasons:

  • When you are debugging a problem with your data, it can be useful to examine it in a human readable XML format. This makes it easy to see exactly what has been read by the importer, and what is going into the processor.

  • For performance. Because we have cached the data in this XML file, if a later part of the build requests that same data again, we can just deserialize it rather than having to re-import the original file from scratch. This was originally designed to speed up the case where you change your processor code, requiring the processor to run again, but the importer and original file have not changed. We never had time to implement that level of smarts in the pipeline (I still hope we'll get around to it someday!) but this cache file is part of our planning to eventually make that possible.

Caching is optional: importers can turn it off by setting an attribute. We disable it for our texture importer (textures are big, fast to import, and not very interesting to debug, so caching them would be a waste of time) but we do cache the outputs from our X and FBX importers.

We originally designed our IntermediateSerializer for the purpose of managing these cache files (I will explain why XmlSerializer was unsuitable in my next post). Once we had a serializer of our very own, we decided it would be useful to expose this as a public API so people could use it for other things as well. For instance:

  • When you are debugging a complex processor, it can be useful to manually call IntermediateSerializer.Serialize at interesting points, dumping out copies of your data for later analysis.

  • Since this serializer can efficiently transfer model data between a human readable XML format and the pipeline object model, perhaps this might be useful for people writing tools such as level editors? For instance they could use a technique like this sample to import models from X and FBX formats, then do all their editing directly on the NodeContent data, using the IntermediateSerializer to load and save it.

  • Once we had the IntermediateSerializer, we found ourselves wanting to use it to import XML files into the pipeline. For instance it was trivial for us to load .spritefont files by calling into this existing serializer code. We decided it would be useful if we wrapped it up to create the generic XmlImporter, so people could easily use it to load their own XML data.

The important thing to take away from all this is that there is nothing special or magic about our XmlImporter. This happens to be the default importer which we select when you add an XML file to your content project, but if you don't like how the IntermediateSerializer works, or want to load XML data in some other way, you can write your own importer using any of the other XML choices provided by .NET.

Also, you should note that using the XmlImporter only affects the importer stage of the pipeline. Once the data has been imported, it is just a regular managed object like any other. XML is not involved in the processor, ContentTypeWriter, .xnb, or ContentTypeReader stages.

My next couple of posts will talk about why we decided to create this new serializer, and go into more detail about how it works.

Lock contention during load screen animations

If your game has a lot of content, your LoadContent call might take a while.

If loading takes a long time, you might want to display a "please wait" message.

If you value polish, you might even decide this message should be animated in some kind of awesomely cool way.

It is pretty easy to do that by firing up a background thread at the start of LoadContent, and having it do something like:

    while (!finishedLoading)
    {
        DrawFunkyLoadingAnimation();
        GraphicsDevice.Present();
    }

Here be dragons! The above code will work, but is liable to make your loading hundreds of times slower.

The reason is that every time you touch the graphics device from a different thread, the framework takes out a critical section, in order to avoid heisenbadness. If the animation is constantly redrawing, the animation thread will pretty much always own this lock, so any time your load function wants to create a graphics resource such as a texture, vertex buffer, or shader, it must wait until the animation thread releases it. This might take a while if the animation thread is just constantly looping over the same piece of draw code!

The solution is to slow down your animation thread by inserting a sleep call, which stops it hogging the graphics device.

The animated LoadingScreen class in our Network State Management sample shows one way to implement this.

A tale of many haggis

Once upon a time there lived a bored young aristocrat named Stanley. Growing tired of his indolent lifestyle, Stanley decided to go into the manufacturing business, so he purchased a haggis factory, which was going cheap as its previous owner had died in a tragic golfing accident.

On his first day as the new owner, Stanley arrived in front of the factory gates bright and early, eager to find out what his new investment was capable of. Here is what he saw:

  • The foreman arrived at 7:15, and unlocked the building
  • The workers arrived at 7:30 on the dot
  • The boiler was fired up at 7:50, while the rest of the staff were cleaning the machinery
  • The first sheep was delivered at 8:23
  • The boiler reached operating temperature at 8:48, and the first haggis was added to the pot
  • This haggis finished cooking at 11:55, and was moved to the cooling rack
  • It was packaged for distribution at 1:20 in the afternoon

"Yikes!", thought Stanley. "It took six hours to prepare a single haggis. Assuming I can sell this for $12, that gives an income of $2 per hour; nowhere near enough to cover payroll for my 20 staff. I fear this investment may have been a mistake."

Stanley has made the same error as many beginning graphics programmers, who render a single model (or sometimes just the default CornflowerBlue template) and then post on Internet forums complaining about the resulting framerate.

It is obviously ridiculous to judge the throughput of a factory by examining just one haggis. Sure, it takes a while to clean the equipment and heat up the cooker, but you only have to do that once in the morning. If you were to make 100 haggis, these could all cook at the same time in the same pot, so would take no longer than a single one. If you wanted 200, they might not all fit in the pot at the same time, but you could reuse the existing hot water, and the second batch of 100 haggis could be cooking at the same time as the first batch was cooling.

Graphics cards work the same way. When you see something like this:

  • CornflowerBlue runs at 800 frames per second
  • Adding a 100 triangle model gives 500 frames per second

it is easy to worry that your framerate will decrease by 300 each time you add 100 triangles. If this was true, drawing more graphics would result in:

  • 200 triangles = 200 fps
  • 300 triangles = -100 fps

Huh? A negative framerate is obviously impossible. This proves there must be something wrong with my logic.

My first mistake was to assume that framerate is a linear scale, when in fact the framerate is equal to one divided by the amount of time spent drawing each frame. To convert into linear millisecond units, we must divide 1000 by the framerate:

  • CornflowerBlue = 800 fps = 1.25 ms
  • 100 triangles = 500 fps = 2 ms

Looking at the difference between these frame times, it took 0.75 milliseconds to draw 100 triangles. Time is a linear scale, so we can predict how performance will change as we add more triangles:

  • 200 triangles = 2.75 ms = 364 fps
  • 300 triangles = 3.5 ms = 286 fps
  • 400 triangles = 4.25 ms = 235 fps

But this estimate is still too pessimistic, because graphics drawing time is not linear with regard to how many things are being drawn. In some cases, adding more triangles might be free, if the hardware is able to boil them up in the same pot it is already using to cook your previous graphics. In most cases, adding more triangles will slow you down, but by less than you would expect from measuring just a few in isolation.

It is appealing to think we might be able to predict the performance of a full game by measuring something smaller and simpler, but this is not possible, because we have no way to know how much of that small measurement represents real work, versus how much is just warming up the boiler and cleaning our equipment ready to start cooking.

In fact, measuring the framerate of a game that does only a small amount of work tells you pretty much nothing. If you want to know how long it will take to make a large number of haggis, the only accurate way to find out is to crank up the production line and actually make that many haggis!

Stephen scares me

Not content to spend all day working on the Visual Studio side of XNA Game Studio, my colleague Stephen then chooses to spend his spare time writing more Visual Studio extensions - for fun!

If only I had a more powerful blog post editor, I would choose a much bigger font to say that this is REALLY COOL.

More Posts Next page »
 
Page view tracker