## Control every pixel of your Web application flexibly keeping performance high with GLSL components

5 years ago shaders transformed game graphics and became the technology behind all amazing VFX we see in computer games. Now they are ready to rock the Web. Shaders are little programs written in C-like language **GLSL (OpenGL Shading Language)** which are aimed for defining the state of vertices (**vertex shaders**) and pixels (**fragment shaders**) in OpenGL / WebGL context using math equations. GLSL compiles and runs at GPU achieving **unprecedented performance for HTML/CSS** world. Shaders are widely used in game development and 3d graphics apps providing unlimited abilities for implementing special effects and rendering techniques however for Web development GLSL is still underutilized despite wide browsers support. This article reviews real world **shaders usage for Web UI development** and provides some how-to`s on **integrating GLSL component into your Web application**.

First of all let`s get some clarity as for why shaders were introduced, how they look like and how do they work...

### What are GLSL shaders?

With advance of videocards and raise of GPU power developers started to feel lack of customization capabilities for hardcoded OpenGL rendering algorithm. Shaders became programmable blocks of OpenGL rendering pipeline which allow to modify data coming through in order to implement special effects and customize rendering. They were added with **OpenGL version 2.0** which was released in 2004 and was the biggest change to OpenGL specification since first one introduced in 1992.

As it was already mentioned there are two types of shaders: (**vertex shaders**) and **fragment shaders**. We see their position in a pipeline at a chart following.

Vertex shader executes first and modify geometry. After geometry is rasterized **fragment shader is being executed for every fragment (pixel) of image defining it`s color**. As Web UIs are mostly 2d we are not that interested in modifying geometry and will focus on fragment shaders.

Let`s have a step by step look at fragment shader which renders a circle:

First we define constants for `radius, center`

, then goes `main`

function declaration which is a standard entry point for any shader. In the beginning of `main`

we determine if current pixel is inside of circle. To achieve this let`s find distance from current pixel `gl_FragCoord.xy`

to circle center (100, 100) and compare if it is less than radius. Thereafter inCircle going to be 1 if distance is less than radius and 0 if distance is greater. In last line we define color by assigning it`s value to a system variable (`gl_FragColor`

). GLSL accepts RGBA as a color format, where every channel value is represented by number kept into range between 0 and 1. So when `inCircle`

is equal to 0 we have 0 for all channels which means black pixel and vice versa if `inCircle`

is 1 then we have a white pixel R=1, G=1, B=1, A=1.

Let`s break the barrier between you and GLSL. In the editor above **try to change radius, center coordinates and the last line of main function to look like that gl_FragColor = vec4(.75, 1., .2, 1.0) * inCircle;**.

This is how shaders work in a nutshell. Now we move on to a story about practical use case for GLSL web components.

### Optimizing with CSS and it`s downsides

Once I was asked to optimize spinner in operation system we develop. Spinner lagged a lot when there were some processes running in background and pretty a lot of frames were missing. Of course having this kind of janks on spinner caused really bad user experience. I quickly figured out that component was implemented via **spritesheet** with **background-position** animated which **caused component repaint** on every frame.

Obvious, that this kind of animation can not be fast (especially running on low-end devices many of our clients own) because new texture was painted and uploaded to GPU on every frame and we definitely should optimize it by changing the way animation is implemented. Basic approach would be to reimplement spinner animation using GPU accelerated CSS properties: `scale / translate / skew / opacity`

). From these we can use `opacity`

since every frame can be turned into separate GPU accelerated layer and then displayed / hidden by changing opacity. As a result we`ve got 150 div`s uploaded to GPU (with `translateZ(0)`

hack) and displayed one by one by changing `opacity`

from 0 to 1 and vice versa.

Not bad! We got rid of repaints and QA department confirmed performance improvement.

Few days later I was asked to apply the same optimization for similar spinner having another color and size. That moment it became clear that CSS solution is not flexible at all. We need to load **one more spritesheet** with correct size / color, and also we need to keep additionally **150 textures in video memory which is 150*120*120*4=8,64 mb** based on spinner 150 frames and 120px dimensions multiplied by 4 bytes per pixel (32bit RGBA). If we will need one more size or color - we create third spritesheet and load 8.6 mb more to GPU and so on... Sounds like a problem which can be solved by using GLSL shader which provides way better flexibility.

### GLSL implementation

**Advantage of GLSL - ability to control every pixel** of surface and **wide parametrization abilities**, and of course shaders run on GPU by default which means no hacks there.
Let`s have a look at step by step implementation of such a spinner in GLSL to have a taste of water.
To render such a spinner we need to draw an arc with dynamically changing angle and some rotation. For this we will need a bit of math.
Let`s start with circle we already did by making this piece of code a bit more scalable.

Now we use uniform variable `u_resolution`

sent from JS which reflects size of current WebGL context which makes it possible to calculate center of current surface and distance from center to boundaries in order to draw the biggest circle possible for a current canvas. Also we moved detection of is point in circle to a separate function and inverted colors by substracting
`isFilled`

from 1.

Further on this circle we should cut a sector defining start angle and end angle for it. Essentially here we should determine "**If current point lies between angles A and B**". This is not that easy to determine as it looks like at the first sight. Pitfalls and solution are described well here, I saw more elegant solutions but they have more disadvantages than this one.

So we define two new functions - `isAngleBetween`

and `sector`

which calculates angle between X axis and line from center to current pixel and return 1 if this angle is between start and end angles of sector required. Now by multiplying result of `circle`

and `sector`

we`ve got an arc.

Next let`s cut inner radius of circle and move **arc rendering to a separate function**.

Arc is quite pixelated. This happens because `step`

function sharply discard values below given. Let`s change it for `smoothstep`

to get some **antialiasing** there.

Now we are about to start with implementing animation which really need us to keep eye on details. **Spinner animation can be decomposed into two: arc edges movement and whole spinner rotation**. Let`s begin with moving arc edges. As we see from original spinner:

- Front edge accelerate during whole round.
- At the moment when front edge ends it`s turn rear edge start moving immediately having maximum speed (same as front edge had) and then decelerates at the end of round.

**These smooth acceleration / deceleration easings could be implemented by using sine function. Passing reminder from dividing current time by Pi and substrating Pi/2 as argument. In this way time will always be in the range from -Pi/2 to Pi/2.**

Let`s keep time in variable named `periodicTime`

. For front edge of arc we are interested in limting time in the range from -Pi/2 to 0, and for rear edge from 0 to Pi/2. Let`s implement this clamping of periodicTime using GLSL function `clamp`

and save it to variables `startX`

, `endX`

which we pass to sine function. Also for negative sine values expected for startX according to graph we add 1 to avoid negative angles. Start / end angles in degrees we get by multiplying sine results by 360.

Wow. Spinner is almost there! **Next we add a rotation. Rotation accelerates in the beginning of the round and breaks in the end which corresponds to sine graph in the range of -Pi/2 to Pi/2**, however with two times longer period than we have for edges. Also let`s rotate spinner in order animation to start at top of round by substracting 90 degrees from startAngle/endAngle .

So what is left is to set correct color. We can use RGB color value (45,121,184) for this but should write `rgb`

function which converts byte - length channels to channels in the range of 0 - 1. Also let`s make `backgroundColor`

customizable without forgetting about colors inversion.

**Try to change value of width variable and arguments passed to rgb function**, this will immediately lead to changes in spinner size and color. So, now we have customizable spinner animated with GPU which is already more than we can achieve with CSS however let`s go further in order to understand the power of GLSL to control every pixel flexibly keeping performance high.

Let`s make pixel`s color dependent on current angle of rotation and arc width dependent on distance to mouse pointer.

Try moving mouse over spinner. This kind of things is not possible to implement with CSS.

### Deploying to project

GLSL-Component was created as a solution for integration GLSL components into Web application. Just place your shader code between **glsl-component** tags.

### Conclusion

Some components can be implemented using GLSL way more efficiently than they can be made with CSS. This is also promoted by wide support of WebGL across all modern browsers. Flexibility, control over every pixel, high performance of GLSL are advantages which may inspire you to contribute into developing GLSL components for Web applications. GLSL - is not only a good addition to your UI developer profile but also a great treatment against JavaScript fatigue.

### What is next?

Reading The Book of Shaders may be a good start.