XBox:Framework:StitchUp

From MadoxLabs

Template:Downloads:XBox

Edit Downloads:XBox (http://www.madox.ca/mediawiki2/index.php?title=Template:Downloads:XBox&action=edit)

StitchUp is a brilliant HLSL preprocessor written by Tim Jones, based on a Shawn Hargreaves article. I have made slight style changes, and added support for conditional blocks, but most of my version of StitchUp remains as Tim wrote it. You can find out more about StitchUp and download it yourself at www.roastedamoeba.com (http://www.roastedamoeba.com/blog/archive/2010/11/28/stitchup---support-for-multiple-techniques).

StitchUp allows you to write special shader fragments that do a specific task, and combine them via preprocessing into a single fx file. Each fragment contains a main() function for vertex and pixel shader code. It also defines any parameters settable from XNA, textures, vertex input fields, pixel input fields and final outputs that it uses. A fragment can also share variables with other fragments when combined into a complete shader.

An example of a fragment I made is:

fragment BasicShader;

[params]
float4x4 View : View;
float4x4 Projection : Projection;

[vs]
__hlsl__
void main(INPUT vertex, inout OUTPUT output)
{
  import float4 worldpos;

  gPosition = mul(mul(worldpos, View), Projection);
}
__hlsl__

[ps : !textured]
__hlsl__
void main(INPUT pixel, inout OUTPUT output)
{
  import float4 color = float4(1,1,1,1); 

  gColor = color;
}
__hlsl__

[ps : textured]
__hlsl__
void main(INPUT pixel, inout OUTPUT output)
{
  import float4 color = float4(1,1,1,1);
  import float4 texel = float4(1,1,1,1);

  gColor = color;
  if (IsTextured()) gColor = gColor * texel;
}
__hlsl__

This fragment applies the View and Projection to a World Position that it expects to be made available by another fragment. The pixel shader simple outputs the color that was computed by another fragment. If a texturing fragment is in use, it will use the main() that applies the texture value.

An example of the texturing fragment is

fragment HasTexture;

[params]
bool HasTexture : HasTexture; 

[define]
textured

[textures]
texture2D Texture : Texture  = { MinFilter = Linear; 
                                 MagFilter = Linear;
                                 MipFilter = Linear; }

[vertex]
float2 tex      : TEXCOORD0;

[interpolators]
float2 tex      : TEXCOORD0;

[vs]
__hlsl__
void main(INPUT vertex, inout OUTPUT output)
{
  output.tex = vertex.tex;
}

bool IsTextured()
{
  return HasTexture;
}
__hlsl__

[ps]
__hlsl__
void main(INPUT pixel, inout OUTPUT output)
{
  float4 texel = tex2D(Texture, pixel.tex);
  export texel;
}
__hlsl__

This fragment add a texture for XNA to set, and also add the 'tex' fields to the vertex and pixel input structure. It is responsible for sampling the texture and exporting the value to the other fragments.

When StitchUp combines all the fragments, it unifies all the vertex input fields, pixel input fields and final outputs from all fragments into combined structures. However, each fragment function still is passed only the subset of the structure that it is designed to work on. It does this by creating composite structures like so:

// -------- vertex input structures --------
struct SingleObject0_VERTEXINPUT
{
	float4 position : POSITION;
};

struct HasTexture1_VERTEXINPUT
{ 
	float2 tex : TEXCOORD0;
};

struct ShadowWriter2_VERTEXINPUT
{
}; 

struct Lighting3_VERTEXINPUT
{
	float3 normal : NORMAL;
};

struct BasicShader4_VERTEXINPUT
{
};
 
struct VERTEXINPUT_PerPixel_P0
{
	SingleObject0_VERTEXINPUT SingleObject0;
	HasTexture1_VERTEXINPUT HasTexture1;
	Lighting3_VERTEXINPUT Lighting3;
	BasicShader4_VERTEXINPUT BasicShader4;
};

To make the final fx file, all fragments are massaged and strung together. A stitchedeffect file defines what fragments participate in what technique and pass. The code generated for a pass makes calls to the various main() functions of the fragments and passes in the sub-struct it needs.

An example file is:

[library MxL = ..\..\..\MxLTools\MxLTools\Content\Fragments\]

[fragment MxL\SingleObject.fragment]
[fragment ShadowWriter.fragment]
[fragment Lighting.fragment]
[fragment MxL\BasicShader.fragment]

technique PerPixel
{
  pass P0
  < string state = "Default"; >
  {
    [use SingleObject]
    [use Lighting]
    [use BasicShader]
  } 
}

technique ShadowMap
{
  pass P0
  < string state = "Default"; >
  {
    [use SingleObject]
    [use BasicShader]
    [use ShadowWriter]
  } 
}

All of the extra function calls, structure manipulating and variable copying that StitchUp does seems like a lot of work on the surface. To look into this, I compared the generated ASM file for a StitchUp shader and my original hand written shader. The result is that the StitchUp shader is smaller or equal to the original shader. In two places, the StitchUp shader avoided an if statement. In one place, it avoided an rsq call. There is also less preshader blocks in the ASM files. In all, the StitchUp shader is about 17 instructions less.

If you want to see the files for yourself, you can get them here (http://madox.ca/mediawiki/images/1/10/Effects.zip).

Personal tools
Toolbox