What are shaders?

The Basics

Shaders are mini-programs that run on the GPU, primarily responsible for rendering parts of the graphics pipeline.

Think of shaders as highly parallelized programs that run on a set of things. These things could be vertices, pixels, or other custom datasets.

The standard pipeline that Shadeup exposes contains 2 primary parts

A vertex shader and a fragment shader.

Vertex Shaders

The vertex shader is responsible for transforming 3D coordinates into 2D coordinates on your screen.

Fragment Shaders

The fragment shader is responsible for determining the color of each pixel.

Parallelism in GPUs

Unlike a CPU that’s optimized for task-switching and complex instructions, a GPU is designed for parallelism. It wants to do the same task (like running a shader) on many pieces of data simultaneously.

Think 1000s of threads running the same shader on different data at the same time. This is how GPUs paint millions of pixels on your screen in a fraction of a second.

Digging into threads

When we talk about the GPU executing tasks in parallel, we’re often referring to wavefronts (or warps, depending on the GPU manufacturer). These are groups of threads that are executed simultaneously by the GPU. For example, a wavefront in AMD’s GPUs typically consists of 64 threads, while a warp in NVIDIA GPUs consists of 32 threads.

The Execution Process

  1. Dispatch: Once a shader is called, the GPU schedules it to be executed across many threads. Depending on the type of shader and the task, each thread might work on a vertex, a pixel, or some other data.

  2. Grouping into Wavefronts: These threads are then bundled into wavefronts. Each thread in a wavefront runs the same instruction but on different data. This is called SIMD (Single Instruction, Multiple Data) execution.

  3. Execution: When a wavefront is executed, every thread in that wavefront will process its instruction at the same time. If one thread in a wavefront is stalled or waiting, the others can’t skip ahead. This is why it’s crucial to minimize deep branches or edge cases that can cause threads to diverge (some threads in the wavefront follow one path and others follow a different path).

  4. Writing Back: Once the threads in a wavefront have finished processing their instructions, they write the results back to memory. This could be a frame buffer (for fragment shaders), a vertex buffer, or other memory locations.

Moving on

I’m going to show what shaders look like in a bit, but before that let’s get the language crash course out of the way. You’ll pick it up quickly, I promise.