3D Effect Class for Silverlight

silverlight_detail

Summary

This article explains key differences between shaders and effects and describes how to implement high-level “effect” functionality and why you might want to.

If you just want the code:

Named Parameters vs. Registers

Silverlight provides pixel and vertex shader support in the core runtime instead of a high-level “effect” class. The shader model requires that you carefully assign constant parameters to specific registers in your HLSL so that you can set them from your application code. Consider the following HLSL.

By assigning specific registers to these variables, the constants are guaranteed to be in the specified positions so that they can be set from the application side.

Wouldn’t it be nice if you didn’t have to manually allocate and track registers and could just refer to the parameter name “WorldViewProjectionMatrix” in your application?

Your HLSL could then look like this and the compiler could allocate registers as it sees fit:

In order to make this work, you would need information about how the compiler is allocating registers to constant parameters. As it turns out this information can be obtained at compile-time from the shader compiler. However, Silverlight doesn’t ship a shader compiler with the runtime or the install size would increase considerably. A solution to this dilemma is to extract the information at compile-time into a format your application can read and then embed this as a resource alongside your compiled shader.

Export the Named Parameter to Register Map

For the first part of the process we need to do the following:

  1. Compile HLSL to shader byte code
  2. Extract the constants table during compilation and write it to an application readable format
  3. Embed the shader byte code and the constants table file as resources in an assembly

Conveniently, I posted the previous article on the HLSL Build Task with Visual Studio Integration which does all of those. The constant table is exported to an XML file which looks like the following.

This XML can be easily parsed by application code to determine which registers were allocated to a specific constant, and that’s what the rest of the article is about.

Effect Class

We’re going to create a new class to do the following:

  • Load a vertex and pixel shader pair from an assembly – Implementing an effect typically means configuring both a vertex and pixel shader which collaborate to produce the final result, so it makes sense to group these together under a single “effect”.
    • Load shader byte code
    • Load shader constants map
  • Set shader constants by name
  • Configure the graphics device to use the loaded shaders

David Catuhe did exactly this for the Effect class in his Babylon Engine, and so I’m going to walk through what that looks like.

Effect

Create the Effect and Load the Shaders

This constructor loads the shader byte code from the named assembly and loads the constants XML file.

Parse Constants Table

The constants file contains all of the information needed to determine which register a named parameter is in. This implementation stores the values in a dictionary for later reference when setting a named constant. It also allows the parameter to be referenced directly using the EffectParameter pattern to avoid dictionary lookups for greater performance.

Set Constant

With the mapping loaded, it becomes very easy to set a constant by  name.

Configure Device

Finally, we can configure the graphics device to use the pixel and vertex shader in the effect and apply the constants.

Example

Putting it all together, you can use the effect like this.

So there you go, no more messing about with manual register allocation!

About Aaron

Aaron is the founder of Spicy Pixel and works in the technology industry on nifty projects that he likes to write about. The contents of this website represent personal opinion and not necessarily those of his employers or sponsors.