ValveSoftware/steam-for-linux — pull request #2572 by SiPlus (2013-06-08 09:36:01+00:00)
Title: Heavily optimized Linux/Mac YUV and particle shaders for Big Picture

In this pull request, I optimized a couple of GLSL shaders used by the Big Picture mode on Linux (and, I think, Mac).

This is a re-request of the pull request closed by John Drinkwater.

# PERSONALLY TO JOHN DRINKWATER

Pull requests **ARE** issues, but they have code attached to them. It's more convenient to have patch code in commits than in Gists. And a pull request **looks like a code contribution**, not yet another bug report.

This pull request is **NOT** for the repo, it's for the Steam client itself.

And please let Valve themselves choose the fate of this pull request.

# YUV shader

In the YUV shader I replaced scalar operations with vector ones, because vector operations are mostly single instructions and are faster than their unrolled scalar equivalents.

Here is the original code (uniforms, varyings and compiler directives are omitted):

```c
void main (void)
{
vec2 texHalf = tex.st/2;
float y = texture2DRect( Texture0, tex.st ).r;
float u = texture2DRect( Texture1, texHalf ).r;
float v = texture2DRect( Texture2, texHalf ).r;

y = 1.1643*(y-0.0625);
u = u-0.5;
v = v-0.5;

gl_FragColor.r = y+1.5958*v;
gl_FragColor.g = y-0.39173*u-0.81290*v;
gl_FragColor.b = y+2.017*u;
gl_FragColor.a = 1.0;
}
```

And here is the vectorized code:

```c
const vec3 mulRed = vec3( 1.0, 0.0, 1.5958 );
const vec3 mulGreen = vec3( 1.0, -0.39173, -0.8129 );
const vec3 mulBlue = vec3( 1.0, 2.017, 0.0 );

void main (void)
{
vec2 texHalf = tex.st * 0.5;
vec3 yuv = vec3(
1.1643 * texture2DRect( Texture0, tex.st ).r + 0.42723,
texture2DRect( Texture1, texHalf ).r,
texture2DRect( Texture2, texHalf ).r, 1.0 ) - 0.5;
gl_FragColor = vec4( dot( yuv, mulRed ), dot( yuv, mulGreen ), dot( yuv, mulBlue ), 1.0 );
}
```

As you see, I removed multiple passes for `y`, `u` and `v`. `dot` also usually takes one instruction (and one cycle), so it is used instead of manual multiply-accumulate.

The number `0.42723` is `0.5 - 1.1643 * 0.0625`, precalculated so `- 0.5` can be used on the entire vector.

**The same unoptimized code (with some very little differences in texture lookups) is also used in `fancyquaduber.frag` at `#elif defined(TEXTURETYPE_YUV)`, so the same optimization can apply there.** Maybe I'll optimize the shader later too, but it's really huge.

# Particle shader

The particle shader in the pull request is optimized much higher than the YUV shader.

13 statement lines with branching were replaced with 4 lines without branching.

The original code:

```c
vec4 texcol = color;

vec2 uv = tex.st - 0.5;

float radius = sqrt( dot( uv, uv ) );

float flSharpRadius = ( clamp( particleSharpness, 0.0, 0.98 ) ) / 2.0;
float alpha = 1.0;
if ( radius < flSharpRadius )
{
alpha = 1.0;
}
else
{
alpha = clamp( (1.0 - ( (radius - flSharpRadius) / (0.5 - flSharpRadius ) ) ), 0.0, 1.0 );
}

gl_FragColor.r = color.r * color.a * alpha;
gl_FragColor.g = color.g * color.a * alpha;
gl_FragColor.b = color.b * color.a * alpha;
gl_FragColor.a = color.a * alpha;
```

The optimized code:

```c
vec2 uv = tex.st - 0.5;
float radius = sqrt( dot( uv, uv ) );
float flSharpRadius = clamp( particleSharpness, 0.0, 0.98 ) * 0.5;
gl_FragColor = color * vec4( color.aaa, 1.0 ) *
mix( 1.0, clamp( 1.0 - ( radius - flSharpRadius ) / ( 0.5 - flSharpRadius ), 0.0, 1.0 ), step( flSharpRadius, radius ) );
```

Branching is a **very** heavy performance dropper, because pixels are processed in batches in parallel. It's much heavier than redundant arithmetic and clamping.

If there's no branching, the GPU can decode and use one instruction for multiple pixels.

If there is branching, the GPU will need to execute different instructions for different pixels.