Skip to main content
Up and Coming
April 11, 2013

Shader Optimization.

I was playing with the shader and wondered if it could be optimized.

Lets start with the standard Shader


float2 HmdWarp(float2 in01)
{
float2 theta = (in01 - LensCenter) * ScaleIn; // Scales to [-1, 1]
float rSq= theta.x * theta.x + theta.y * theta.y;
float2 theta1 = theta * (HmdWarpParam.x + HmdWarpParam.y * rSq +
HmdWarpParam.z * rSq * rSq + HmdWarpParam.w * rSq * rSq * rSq);
return LensCenter + Scale * theta1;
}

float4 main( float2 texCoord : TEXCOORD0,float4 color : COLOR0 ) : SV_Target
{
float2 tc = HmdWarp(texCoord);
if (any(clamp(tc, ScreenCenter-float2(0.25,0.5), ScreenCenter+float2(0.25, 0.5)) - tc))
return 0;
return Texture.Sample(Linear, tc);

};


That's 11 Multiplies, 6 Additions and 3 Subtractions per pixel. (eyeballed the numbers sorry, could be off by one or two) and on my GTX670 takes 0.1447264 milliseconds to execute (based on averaging the time of a 100 frames of just the draw with the shader)

Which could be good, could be bad, nothing to compare it to, so lets compare it to a shader that does not warp and just samples the texture.


float4 pixelShader( float2 texCoord : TEXCOORD0,float4 color : COLOR0 ) : SV_Target
{
float4 c = Texture.Sample(Linear, texCoord);
return c;
}


which takes 0.0272857 milliseconds to execute (again GTX670, 100 frames)

So the warping shader is 5.3 times slower than just bare sampling.

There must be a better way, why are we recalculating the distortion map every frame single frame? seems rather wasteful. Lets render the distortion map to texture instead. We'll use the Red channel for the X distortion, and the Green channel for the Y distortion. If we make a texture of format R32G32_Float it should fit our needs nicely. And the upside is we only have to do the nasty math once!

our shader now looks like this


float2 HmdWarp(float2 in01)
{
/// Same as above, cut to keep the post size down.
}

float4 main( float2 texCoord : TEXCOORD0,float4 color : COLOR0 ) : SV_Target
{
float2 tc = HmdWarp(texCoord);
if (any(clamp(tc, ScreenCenter-float2(0.25,0.5), ScreenCenter+float2(0.25, 0.5)) - tc))
return 0;
return float4(tc.x, tc.y,0,0);
};


The resulting texture will look kinda like this (Used bad parameters, had to guess since I don't have a rift yet so the horizontal mapping is somewhat off)



Sweet, almost in business! lets update the warping shader to make use of the new texture. First of all the linear sampler has to go, a MinMagMipPoint sampler will fit our needs better.


SamplerState PointSampler : register(s0);
Texture2D Texture : register(t0);
Texture2D TextureWarp : register(t1);

float4 main( float2 texCoord : TEXCOORD0,float4 color : COLOR0 ) : SV_Target
{
float4 c = TextureWarp.Sample(PointSampler, texCoord);
float2 RealCoord = float2(c.r,c.g);
return Texture.Sample(PointSampler, RealCoord);
};


Ahh much simpler now! New execution time 0.0598547 milliseconds and no visual difference! 2.5 times speed up from the original shader! Not bad for 20 minutes of tinkering.

    80 replies

    Up and Coming
    May 14, 2013
    Couldn't something like the light pipe you suggest be accomplished by using a carefully shaped transparent piece of plastic to hold the lenses?
    (╯°□°)╯︵┻━┻
    Up and Coming
    May 14, 2013
    "jwilkins" wrote:
    Couldn't something like the light pipe you suggest be accomplished by using a carefully shaped transparent piece of plastic to hold the lenses?

    Yes, I mentioned in other threads that you could use lucite rods or custom-molded acrylic light-guides. I would roughen the inner surface inside the mask to diffuse the redirected light over a larger peripheral viewing area.

    By reusing otherwise-wasted pixels for peripheral ambient lighting inside the face-mask (using light-pipes of some sort), we save the complexity of additional hardware and cabling needed for "real" ambi-lghts. My suggestion for fiber-optics was merely for a simple retro-fit for existing Rift DK units.

    EDIT: Oops, I just realized after revisiting this post that you are discussing translucent EYECUPS. That IS an interesting idea. If the eyecups extended to the screen edges AND the insides were black, and if they outsides were translucent but roughened, and if the inside of the face-mask edges were to be painted flat WHITE, then the eyecups could guide and PROJECT the unused display borders onto the face-mask edges in the outer limits of peripheral vision. No hardware mods needed, just custom eyecups! Awesome idea, jwilkins! I *LIKE* it!
    :D
    This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
    Up and Coming
    May 14, 2013
    This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
    Up and Coming
    May 15, 2013
    To clarify, yes, I was suggesting translucent eyecups. Perhaps with some kind of carefully designed shape to gather light from the right directions.
    (╯°□°)╯︵┻━┻
    Up and Coming
    June 19, 2013
    A little extra information regarding my "notoriously large" FoV, I was just playing with the OculusWorldDemo (C++ Tuscany demo), and in the HUD it displays FoV while adjusting it with '[' and ']' keys. What I found interesting (if the values are correct) is that everything looks best for me with FoV values of about 125-degrees (not 110-degrees). While turning, everything looks anchored in place in VR. Adjusting those FoV values up or down make the VR world either drag (stretch) a bit with me, or counter-rotate in the opposite direction (like turning while looking through binoculars).

    Everything "seemed" correct before I made any adjustments though, so perhaps aproximately 125-degrees FoV was already the default value in that demo. So, apparently the in-game FoV does not necessarily match the real FoV you get when your view is partially obstructed by the edges of the eyecup lens retainer edges.

    125-degrees might be what gets rendered, but 110-degrees (the advertised value), is what many can see. Essentially, the boundaries of the light and dark areas, and the dark and magenta areas, in photos previously shown.

    EDIT: Although the outer masked-off areas were often described as "magenta" in a number of posts, those images were altered, changing magenta to black:
    field of view.png
    This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
    Participant
    June 19, 2013
    "jwilkins" wrote:
    (I do question the accuracy of some of the information, does GLSL and HLSL really say that expressions are evaluated left-to-right? That is more restrictive than C/C++ and ties the compiler's hands!)

    So what makes me sad? I find high level IEEE floating point expressions to be a very leaky abstraction that only barely covers up the assembly language it compiles to. The main problem is that you are mixing up what you want to compute with how to compute it, where to compute it, and in what order to compute it.

    The problem in this case is that in DirectX (I'm unsure about the OpenGL rules) the order of operations is very precisely defined according to the strict IEEE754 spec, and shader-compiler writers are not allowed to violate it. They are required to get correctly-signed zeros and do NAN propagation all that annoying stuff. This ties their hands more than even the standard C compiler settings.

    The intent was to get guaranteed reproducibility across GPU vendors. I think that is a nearly impossible goal, and the performance hit is worrying, but it is at least understandable. Anyway, it does indeed mean that the optimisations talked about above are still sadly necessary, because the compiler isn't always allowed to make them for you.

    The current SDK shaders are designed more for readability than performance, and we plan do a perf-tuning pass over them once we have HD integrated.
    Tom Forsyth - OculusVR coder
    Up and Coming
    June 19, 2013
    "tomf" wrote:

    The problem in this case is that in DirectX (I'm unsure about the OpenGL rules) the order of operations is very precisely defined according to the strict IEEE754 spec, and shader-compiler writers are not allowed to violate it.


    The IEEE standard specifies the order of operations for higher level languages? Even to the point of saying if the left or right side of an expression should be evaluated first? I was putting off reading through the spec, but I'm tired of these surprises and I thought I understood it well enough already.
    (╯°□°)╯︵┻━┻
    Participant
    June 19, 2013
    "jwilkins" wrote:
    "tomf" wrote:

    The problem in this case is that in DirectX (I'm unsure about the OpenGL rules) the order of operations is very precisely defined according to the strict IEEE754 spec, and shader-compiler writers are not allowed to violate it.


    The IEEE standard specifies the order of operations for higher level languages? Even to the point of saying if the left or right side of an expression should be evaluated first? I was putting off reading through the spec, but I'm tired of these surprises and I thought I understood it well enough already.

    IEEE just defines the low-level operations. I'm sure you know that for example (a+b)+c is different to a+(b+c) because of rounding, and most high-level languages have various strictness levels that allow or disallow these sort of conversions. DirectX also has these, but HLSL is compiled in various stages, and the different stages are controlled by different people, so it's unclear what will and what won't be done automatically for you, and what needs to be stated explicitly by the programmer. If in doubt, make everything explicit. There's more on this in Fabian/Ryg's article that was linked earlier.
    Tom Forsyth - OculusVR coder
    Up and Coming
    June 19, 2013
    @tomf, so what did you mean when you said:

    The problem in this case is that in DirectX the order of operations is very precisely defined according to the strict IEEE754 spec
    (╯°□°)╯︵┻━┻
    Participant
    June 19, 2013
    "jwilkins" wrote:
    @tomf, so what did you mean when you said:

    The problem in this case is that in DirectX the order of operations is very precisely defined according to the strict IEEE754 spec

    Let me say it a different way: "In DirectX the result of operations is defined as adhering to parts of the IEEE754 spec." What that means in practice is not clear. How you apply the IEEE spec to a high-level language is not that well-defined, and some HLSL constructs don't have a clear spec (e.g. what order are the additions in dot() defined to be done in?). But I do know that compilers are not free to do whatever they like, and what might seem like micro-optimsations actually do change the results and performance, so they are worthwhile looking at.
    Tom Forsyth - OculusVR coder