I read somewhere online that Hue changes can't be done in pixel shader 2.0, due to limitation of 64 instructions per slot.

Here's the sample that proves otherwise:

Indeed the RGB-to-HSL-to-RGB conversion takes about 100 instructions in its typical implementation. PS 2.0 which is the shader model supported by Silverlight 3 only allows for 64 arithmetic instructions, as outlined in this comparison between pixel shaders on Wikipedia

How can we optimize it?

Optimizing pixel shader instruction slots is nice - in a typical C# world, you'd be adding if() statements to make your code run faster like this:

if ( HSV.y != 0 ){

QUAD_REAL var_h = HSV.x * 6;

QUAD_REAL var_i = floor(var_h); // Or ... var_i = floor( var_h )

QUAD_REAL var_1 = HSV.z * (1.0 - HSV.y);

QUAD_REAL var_2 = HSV.z * (1.0 - HSV.y * (var_h-var_i));

QUAD_REAL var_3 = HSV.z * (1.0 - HSV.y * (1-(var_h-var_i)));

if (var_i == 0) { RGB = QUAD_REAL3(HSV.z, var_3, var_1); }

else if (var_i == 1) { RGB = QUAD_REAL3(var_2, HSV.z, var_1); }

else if (var_i == 2) { RGB = QUAD_REAL3(var_1, HSV.z, var_3); }

else if (var_i == 3) { RGB = QUAD_REAL3(var_1, var_2, HSV.z); }

else if (var_i == 4) { RGB = QUAD_REAL3(var_3, var_1, HSV.z); }

else { RGB = QUAD_REAL3(HSV.z, var_1, var_2); }

}

Not with pixel shaders. If you look carefully at the bold if statement, removing it does not change the program logic. It just takes an extra instruction slot. In reality, I think the pixel shader code will run with the same speed with or without the if() (not 100% sure so correct me if needed).

With this knowledge, I decided to do these optimizations:

1. Instead of HSL-to-RGB, use HSV-to-RGB. The reference NVidia Shader Library implementation (source code here) of HSV-RGB-HSV takes ~70 or so slots.

2. Combine the min_channel() and max_channel() functions into 1 - saves a couple if() statements

3. Take out the if (x < 0) (x += 1) checks in the RGB-HSV function, and execute them once instead of twice, after the hue is modified.

4. Remove the "obsolete" if()-s like the one above

I was very happy to see that it just fit in the 64-instruction slot of PS 2.0! Note that it hits the limit and more complex Hue stuff may need further optimizations! :) If you do so, please let me know! Anyway hue tricks that don't use more slots are OK.

Here's the complete Shazzam-friendly source of the .fx file (also included in the sample project source above).

/// <summary>Hue shift</summary>

/// <minValue>0</minValue>

/// <maxValue>1</maxValue>

/// <defaultValue>0</defaultValue>

float HueShift : register(c0);

sampler2D Samp : register(S0);

#define QUAD_REAL float

#define QUAD_REAL3 float3

QUAD_REAL3 rgb_to_hsv_no_clip(QUAD_REAL3 RGB)

{

QUAD_REAL3 HSV;

float minChannel, maxChannel;

if (RGB.x > RGB.y) {

maxChannel = RGB.x;

minChannel = RGB.y;

}

else {

maxChannel = RGB.y;

minChannel = RGB.x;

}

if (RGB.z > maxChannel) maxChannel = RGB.z;

if (RGB.z < minChannel) minChannel = RGB.z;

HSV.xy = 0;

HSV.z = maxChannel;

QUAD_REAL delta = maxChannel - minChannel; //Delta RGB value

if (delta != 0) { // If gray, leave H & S at zero

HSV.y = delta / HSV.z;

QUAD_REAL3 delRGB;

delRGB = (HSV.zzz - RGB + 3*delta) / (6.0*delta);

if ( RGB.x == HSV.z ) HSV.x = delRGB.z - delRGB.y;

else if ( RGB.y == HSV.z ) HSV.x = ( 1.0/3.0) + delRGB.x - delRGB.z;

else if ( RGB.z == HSV.z ) HSV.x = ( 2.0/3.0) + delRGB.y - delRGB.x;

}

return (HSV);

}

QUAD_REAL3 hsv_to_rgb(QUAD_REAL3 HSV)

{

QUAD_REAL3 RGB = HSV.z;

//if ( HSV.y != 0 ) { // we don't really need this since it just adds an obsolete instruction slot

QUAD_REAL var_h = HSV.x * 6;

QUAD_REAL var_i = floor(var_h); // Or ... var_i = floor( var_h )

QUAD_REAL var_1 = HSV.z * (1.0 - HSV.y);

QUAD_REAL var_2 = HSV.z * (1.0 - HSV.y * (var_h-var_i));

QUAD_REAL var_3 = HSV.z * (1.0 - HSV.y * (1-(var_h-var_i)));

if (var_i == 0) { RGB = QUAD_REAL3(HSV.z, var_3, var_1); }

else if (var_i == 1) { RGB = QUAD_REAL3(var_2, HSV.z, var_1); }

else if (var_i == 2) { RGB = QUAD_REAL3(var_1, HSV.z, var_3); }

else if (var_i == 3) { RGB = QUAD_REAL3(var_1, var_2, HSV.z); }

else if (var_i == 4) { RGB = QUAD_REAL3(var_3, var_1, HSV.z); }

else { RGB = QUAD_REAL3(HSV.z, var_1, var_2); }

//}

return (RGB);

}

float4 main(float2 uv : TEXCOORD) : COLOR

{

float4 col = tex2D(Samp, uv);

float3 hsv = rgb_to_hsv_no_clip(col.xyz);

hsv.x+=HueShift;

//if ( hsv.x < 0.0 ) { hsv.x += 1.0; }

if ( hsv.x > 1.0 ) { hsv.x -= 1.0; }

return float4(hsv_to_rgb(hsv),col.w);

}

btw, Visual Studio 2010 RC is out for MSDN subsribers (public tomorrow) and I'm going to publish all samples in VS 2010 from now on :)

Hope you like it!