Silverlight Custom Bitmap Effects - more HLSL
Written by Mike James   
Monday, 26 July 2010
Article Index
Silverlight Custom Bitmap Effects - more HLSL
Constants and samplers
The default sampler
Embossed effect

In the article Silverligh Custom Bitmap Effects - Getting started we discovered how to work with HLSL. Now we are in a position to write more sophisticated shaders and  this means learning some more HLSL.


In general shaders can work on vertexes - i.e. the basic geometry of a 3D object - or the pixels that are about to be displayed. That is HLSL is a language that has commands that are about manipulating geometry and pixels.

In WPF and Silverlight you can only use HLSL to write pixel shaders and the rest of this article concentrates on writing a pixel shader. If you look up HLSL in the help or a manual you will encounter lots of commands that you can't use to create an effect.

The same ideas, however, apply to vertex shaders and you can use this as an introduction to shaders in general - but you will have to use DirectX to make use of a compiled vertex shader.

Basic syntax

In the previous article on getting started with custom bitmap effects a very simple shader was used as an example and it is now time to examine it in closer detail.

The shader simply returned the color red every time it was called:

 float4 main(float2 uv:TEXCOORD):COLOR
return color;

This may be a very simple shader but it illustrates several important ideas. The first is that HLSL is much like C or C# but it is much simpler. You can look up the syntax in the DirectX help file (installed with the SDK).

The main function is defined in a way that might seem familiar but there are a few new ideas. The first is that the return type is float4, i.e. a four-element array or vector. There are two different ways to declare a vector. You can use the shortcuts similar to:


or you can use the more general syntax:

vector<type,size> name={initial values};

for example float4 and vector<float,4> mean the same thing.

As another example the parameter of the main function is a float2 i.e. a two-element array.

The other new feature is the use of a semantic - the keyword following the variable or function definition which indicates to the compiler how the variable should be used.

You can think of this as allowing the compiler to "wire up" the connection between the variable and the GPU hardware.

In the case of this example, the semantic TEXCOORD means texture coordinates - essentially an (x,y) position specification - and COLOR means that the function returns a variable that should be treated as the colour of the pixel.

The rest of the function should be fairly obvious - it simply declares and initialises a variable and returns the result. This, because of the use of the COLOR semantic, is used by the GPU to set the color of the pixel that the shader is computing.

If you compile and run this HLSL program you will discover that the result is a red block in the area of the screen where the control that you applied it to would have rendered. What happens is that for each pixel in the render area of the control your custom shader is called and it can perform a computation to determine what displays in the area.

As the GPU has multiple processing units it is likely that your main function will be active on multiple pixels within the render area at any given time - this is the reason that GPU hardware acceleration is faster than simple software. It is as if your main function was implicitly within a pair of nested for loops:



but the loop is implemented in parallel. The main function is often called the kernel of the shader.

Notice that the range of the implicit for loops, i.e. 0 to 1, corresponds to the usual range for texture co-ordinates. These are mapped to pixels by the system using a scaling function. In HLSL most coordinates run from 0 to 1 irrespective of the size of the bitmap.

Normally the area of the screen scanned by the for loops is the size of the area needed to render the control that the effect has been applied to. If you need to modify the area that you are working with you can use the Padding properties of the ShaderEffect class - PaddingTop, PaddingBottom etc. Setting these increases the area that the shader is called on by the appropriate amount. This is how the suppiled drop shadow effect can change pixels outside of the usual render area of a control.

What we need to do now is to find out how to implement more complicated computations to determine the color of the pixel.

Connecting with C#

We have already seen how a shader program can be loaded and used in C# but there are ways of passing C# variables and objects into the shader program. The basic idea is that the GPU has a number of "registers" - standard storage locations - that can be loaded automatically when the shader is run.

The simplest registers correspond to constants that are the same value for every time the kernel is run to compute a pixel i.e. they don't vary with the position of a pixel.

Pixel shader 2.0 supports:

  • c0 to c31 float4 registers
  • i0 to i15  integer4 registers
  • b0 to b15 boolean4 registers

WPF and Silverlight only support float4 registers but this is usually sufficient. So from here on we will restrict our attention to 32 constant float4 registers, c0 to c31.

There are two parts to using a register in a shader program. First you have to declare a variable that is bound to the register in the HLSL program. For example suppose we decide to set the color of the pixel outside of the shader then we can declare the variable as:

float4 pixelcolor:register(c0);

which makes the connection between the HLSL variable pixelcolor and the register c0.

The next part of the process is setting the appropriate register from C#/ and this is done by creating a special type of dependency property that specifies the register that its value is transfered to.

This is done using the special


function which adds the information that the GPU register cr is to be used to the dependency properties metadata.

If you need to brush up on dependency properties see Inside Dependency Properties

That is for each constant register you want to use to pass data to your shader you have to set up a dependency property with the register name in its metadata.



<ASIN:1430224258 >


<ASIN:0672330628 >

<ASIN:0470524650 >

<ASIN:1430229888 >

Last Updated ( Tuesday, 27 July 2010 )