The Power of Shader - Part One

|

.NET Framework 3.5 SP1 Beta1 has been released, this release is not simply a service pack release as we've known for many years of what  SP actually means. It seems that Microsoft has changed its strategy, and start to include new features in SP release.

One of the most exciting feature of this release is a new hardware accelerated, pixel shader based custom effect architecture which drives the software, WIC based bitmap effect architecture into obsolete. This new architecture has tackle many of the performance problems inherent with previous stale architecture. On one hand, the new custom effect will run in composition thread rather than UI thread, which means that it will not tie up the UI thread as the previous effect does, on the other hand, it will be hardware accelerated if your GPU supports pixel shader 2.0 or above. If you don't have decent GPU, the effect will be emulated using software, RenderTargetBitmap is a special case, if any visual which has effect applied to it needs to be rasterized into the RenderTargetBitmap, it will go through software rendering path.

If you have read the blog series written by Greg Schechter about the new shader effect feature, you could have been familiar with how to create simple effects. But those shader effect expects you to pass in the pre-compiled HLSL bytecode, which really requires you to use the shader compiler (fxc.exe) which is shipped with the DirectX SDK to compile the shader first, then pass it to the ShaderEffect derivative's constructor. How about if I want to directly pass in the source shader string, and have it get compiled and executed at runtime, this is called online compilation as compared with offline compilation with fxc.exe tool. Fortunately, I could use the Microsoft.DirectX.Direct3D.ShaderLoader class which is part of the managed DirectX API which can do exactly what I want, Here is the ShaderEffect implementation which supports online compilation:

public class SourceShaderEffect : ShaderEffect
{
    // The custom effect expects pixel shader 2.0 support.
    public static readonly String PixelShaderProfile = "ps_2_0";

    public static readonly ShaderFlags ShaderFlags = ShaderFlags.None;

    // We use "main" as the default function name for the shader.
    public static readonly String DefaultFunctionName = "main";

    /// <summary>
    /// Constructs a new SourceShaderEffect instance.
    /// </summary>
    /// <param name="shaderFilePath">path to the source shader file</param>
    /// <param name="functionName">shader entry function name</param>
    public SourceShaderEffect(String shaderFilePath, String functionName)
    {
        GraphicsStream stream = ShaderHelper.TryThrowCriticalException(() =>
        {
            return ShaderLoader.CompileShaderFromFile(
                shaderFilePath,
                functionName,
                null,
                PixelShaderProfile,
                ShaderFlags);
        });

        if (null != stream)
        {
            base.PixelShader = new PixelShader();
            base.PixelShader.SetStreamSource(stream);
        }
    }

    /// <summary>
    /// Constructs a new SourceShaderEffect instance.
    /// </summary>
    /// <param name="shaderStream">stream which contains the shader source code.</param>
    /// <param name="functionName">shader entry function name</param>
    public SourceShaderEffect(Stream shaderStream, String functionName)
    {
        GraphicsStream stream = ShaderHelper.TryThrowCriticalException(() =>
       {
           return ShaderLoader.CompileShaderFromStream(
               shaderStream,
               functionName,
               null,
               PixelShaderProfile,
               ShaderFlags);
       });

        if (stream != null)
        {
            base.PixelShader = new PixelShader();
            base.PixelShader.SetStreamSource(stream);
        }
    }

    /// <summary>
    /// Constructs a new SourceShaderEffect instance with default shader entry function name.
    /// </summary>
    /// <param name="shaderFilePath">path to the source shader file</param>
    public SourceShaderEffect(String shaderFilePath) : this(shaderFilePath, DefaultFunctionName) { }

    /// <summary>
    /// Constructs a new SourceShaderEffect instance with default shader entry function name.
    /// </summary>
    /// <param name="shaderStream">stream which contains the shader source code.</param>
    public SourceShaderEffect(Stream shaderStream) : this(shaderStream, DefaultFunctionName) { }
}

 

There is no fancy stuff in this code, with the above code, you could simply pass a file path or stream to the shader source code, and you are done with it. Let's create a simple effect to see if it actually works, the following is a Sepia shader which I learnt from this nice article:

sampler2D s0 : register(s0);
float4 main(float2 tex : TEXCOORD0) : COLOR
{
    float4 color = tex2D(s0, tex);
    float value = color[0] * 0.299f + color[1] * 0.587f + color[2] * 0.114f;
    return tex2D(s0, value);
}

Let's save it into a file called Sepia.fx, and add it as a resource file into the WPF project you create from visual studio. In XAML, you could place an Image element, and at the code behind, you could write something like the following:

image.Effect = CreateSourceShaderEffect("Sepia");


public static ShaderEffect CreateSourceShaderEffect(String shaderLocation)
{
    ShaderEffect effect = null;
    shaderLocation = "pack://application:,,,/Shaders/" + shaderLocation + ".fx";
    Stream shaderStream = Application.GetResourceStream(new Uri(shaderLocation, UriKind.Absolute)).Stream;
    if (null != shaderStream)
    {
        effect = new SourceShaderEffect(shaderStream);
    }

    return effect;
}

 

Then, it seems that you've got everything ready to run, but actually you don't, if you are running under 64 bit Windows, you need to set the visual studio build system to target X86 architecture instead, otherwise you will get a BadImageFormatException. This means that to enable online compilation, your Application should be 32 bit, and it will be emulated when running under 64 bit Windows through WOW64 layer, which might not be what you expect. so if you are targeting 64 bit Windows, and you want your WPF application to be 64 bit, this technique is not the way to go, otherwise, it could be an option for you.

The following is a full sample code with another two simple shader effects such as Invert, and Grayscale. On the next installment, I will try to take adventure on shader effects with input parameters and with multi-sampler blending when I learnt how to do them:)

Attachment:ShaderEffectDemo.zip

1 comments:

gondola display said...

oh,thank you for share