# Optimized linear to sRGB GLSL

I'm currently using a simple implementation of linear to sRGB transform:

float sRGB(float x) {
if (x <= 0.00031308)
return 12.92 * x;
else
return 1.055*pow(x,(1.0 / 2.4) ) - 0.055;
}
vec3 sRGB_v3(vec3 c) {
return vec3(sRGB(c.x),sRGB(c.y),sRGB(c.z));
}


Possibly, that the correction for small values (x <= 0.00031308) will not matter in practice.

I considered to just let ´pow(x, 1.0 / 2.4) but I'm not sure how much impact it will have.

Is there any way to optimize it? Can be vectorization help? Can some polynomal fitting be better?

A few days ago, I ran into this when messing around on Shadertoy. I was curious and some solutions to the various problems I faced, so I may as well post what I've made here for you.

Note, however, that I have never benchmarked this and I don't know how fast it works. But I've heard that different drivers/hardware/etc. implement sRGB conversion differently, so I wanted to make my own, correct implementation... And as far as I can tell, I succeeded. It does not use ifs and operates on entire color vectors at a time.

The trick with bvecs was learned from here. He says it won't work on most GLSL ES versions, but it seems to work well in the browsers I've tested (including Chrome on my Pixel). Still something to consider, especially since my uses are pretty limited (just making test images for my desktop, though I did test on my phone too).

I'm sure somebody could make a more optimal version that's just as accurate, especially as I'm very new to GLSL in general. However, since nobody was really giving you actual code (except to suggest x1/2.2 and the like), and my own code seemed to follow the general advice of avoiding branches, I figured I may as well post it.

// Converts a color from linear light gamma to sRGB gamma
vec4 fromLinear(vec4 linearRGB)
{
bvec4 cutoff = lessThan(linearRGB, vec4(0.0031308));
vec4 higher = vec4(1.055)*pow(linearRGB, vec4(1.0/2.4)) - vec4(0.055);
vec4 lower = linearRGB * vec4(12.92);

return mix(higher, lower, cutoff);
}

// Converts a color from sRGB gamma to linear light gamma
vec4 toLinear(vec4 sRGB)
{
bvec4 cutoff = lessThan(sRGB, vec4(0.04045));
vec4 higher = pow((sRGB + vec4(0.055))/vec4(1.055), vec4(2.4));
vec4 lower = sRGB/vec4(12.92);

return mix(higher, lower, cutoff);
}


If you're worried about optimization then use vectors more, the optimizer may or may not do that for you.
Avoid conditionals, all branches of them will be executed.
But the major bottleneck on modern GPUs is from samplers, pow and log are pretty negligible.

If possible then just use sRGB internalformat in your framebuffer, most gpus will do the conversion in hardware.

• Is there a practical reason for do POW approximation with fitting or polynomials ? – Alexsey Shestacov Jan 11 '15 at 14:17
• Balance between speed and accuracy: pow(x, 2.2) for textures and pow(x, 1.0/2.2) for output
• More speed, less accuracy: x*x for textures and sqrt(x) for output
• sRGB is not a simple x^2.2 curve. It is linear at small value, then x^2.4 at larger value, resulting an average of 2.2 gamma. – Star Brilliant Mar 15 '17 at 3:34
• @StarBrilliant not sure what I was thinking when I wrote that but you're right, so I rewrote the answer without the inaccurate statements. – snake5 Mar 21 '17 at 6:31