Command Palette

Search for a command to run...

Hectal
Course outline
Topic 17.5Advanced ~22 min · step 5 of 7

Pixels and Fill Rate

In one line

The GPU's work grows with the number of pixels it shades, and every transparent layer shades them again. Cap the pixel ratio, avoid stacked transparency, and draw only when the picture changed.

0/7 · 0%

Think of it like this

Painting a wall. Doubling the wall's width and height means four times the paint. Painting five coats means five times the work, even if you never see the first four.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

pixel ratio
Device pixels per CSS pixel. A canvas drawn at ratio 2 has 4 times the pixels of one at ratio 1.
fill rate
How many pixels per second the GPU can shade.
overdraw
Shading the same screen pixel several times, often because of stacked transparent layers.
render on demand
Drawing a new picture only when something changed, instead of every refresh.
MSAA
Multisample antialiasing: smooth edges by sampling several points per pixel at the edges, at a cost in work and memory.

Step by step

01See what the ratio costs

Run the first example: a 300 x 150 canvas at four ratios, then a phone-sized canvas at 1x, 2x, 3x and capped.

02Count layers

The second example turns transparent layers into a number you can read from one pixel.

03Stop drawing when nothing changes

The third example draws only when dirty is set. Then orbit the knot, let go, and watch the frames figure in the status bar stop growing.

04Decide a policy

Cap the ratio at 2 or lower, avoid large transparent sheets, draw on demand when nothing moves.

Try it yourself

  1. 1

    Tighten the cap

    In the first example, change cap(3) to cap(3, 1.5). The row labelled "3x phone, capped at 2" now prints ratio 1.5 -> 740,610 pixels per frame: a quarter of the 2,962,440 pixels at ratio 3.

  2. 2

    Stack more sheets

    In the overdraw example, change sheets(12) to sheets(40) in the "12 sheets" line. It prints draw calls: 40 | layers blended over the middle pixel: 40: overdraw and draw calls grow together.

Code & diagrams

What the pixel ratio costs New tab

Sizes are computed from numbers, so the output is the same on every device.

idle

Console

Run the scene to see its console.

Expected console output

A 300 x 150 canvas:
  pixel ratio 1 -> drawing buffer 300 x 150 = 45,000 pixels, 1 x the work of ratio 1
  pixel ratio 1.5 -> drawing buffer 450 x 225 = 101,250 pixels, 2.25 x the work of ratio 1
  pixel ratio 2 -> drawing buffer 600 x 300 = 180,000 pixels, 4 x the work of ratio 1
  pixel ratio 3 -> drawing buffer 900 x 450 = 405,000 pixels, 9 x the work of ratio 1
A 390 x 844 phone viewport, full-screen canvas:
  1x laptop screen         ratio 1   ->    329,160 pixels per frame
  2x phone                 ratio 2   ->  1,316,640 pixels per frame
  3x phone                 ratio 3   ->  2,962,440 pixels per frame
  3x phone, capped at 2    ratio 2   ->  1,316,640 pixels per frame
  3x phone, capped at 1.5  ratio 1.5 ->    740,610 pixels per frame
Overdraw you can read from one pixel New tab

Each layer adds 1/255 of red. The value is read straight after render().

idle

Console

Run the scene to see its console.

Expected console output

1 sheet                            draw calls:  1 | layers blended over the middle pixel: 1
12 sheets                          draw calls: 12 | layers blended over the middle pixel: 12
12 sheets behind an opaque wall    draw calls: 13 | layers blended over the middle pixel: 0
Render on demand: count the frames New tab

The loop is driven by hand for 180 turns so the count is exact. The last two lines then start the real loop.

idle

Console

Run the scene to see its console.

Expected console output

60 frames, nobody touching anything: frames drawn = 1
after one change, 60 more frames:    frames drawn = 2
with autoRotate on, 60 more frames:  frames drawn = 61

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

A transparent wall hides nothing

In the overdraw example, change the wall's material to new THREE.MeshBasicMaterial({ color: "#000000", transparent: true, opacity: 0.5 }).

terminal
$ Run
── what you'll see ──
12 sheets behind an opaque wall draw calls: 13 | layers blended over the middle pixel: 6
All 12 sheets are drawn and blended first, then the half-clear wall halves their red: 12 becomes 6. The wall hid only half of the work.

Myth vs fact

Myth

A sharper canvas is always better.

Fact

Beyond a ratio of about 2 the eye can rarely tell, but the GPU pays for every extra pixel. Cap it, and lower it further when the frame rate drops.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Dynamic resolution: watch the frame time, and when it stays over budget, lower setPixelRatio a step (never below about 0.75); raise it again when there is room. It is the cheapest way to keep a heavy scene at a steady frame rate on weak phones.

Remember this

  1. 1

    The problem this solves: a phone with a pixel ratio of 3 has nine device pixels for each CSS pixel. A full-screen canvas on such a screen makes the GPU shade nine times as many pixels as a standard screen, for a difference few people can see. It is a very common reason for a page to be GPU-bound on a good phone.

  2. 2

    Pixel ratio. The first example prints the drawing buffer at four ratios: 1, 1.5, 2 and 3 are 1x, 2.25x, 4x and 9x the work. The standard cap, Math.min(devicePixelRatio, 2), is already in the showroom's renderer.js (Phase 3). A lower cap such as 1.5 saves nearly half again. Antialiasing (antialias: true) multiplies the work at the edges on top.

  3. 3

    Fill rate and overdraw. Fill rate is how many pixels the GPU can shade per second. Overdraw is shading the same screen pixel more than once. Opaque objects are sorted near to far, so the depth test throws away pixels hidden behind them. Transparent objects must be drawn far to near and blended, so each layer is shaded again.

  4. 4

    Measure overdraw. The second example uses a shader that adds 1/255 of red per layer, so the red value of a pixel is the number of layers blended there. Twelve transparent sheets give 12 at the middle pixel (1 sheet gives 1). Put an opaque wall in front and it is 0: the sheets behind it are rejected by the depth test. Whether the GPU skips their shader as well is the hardware's choice, but the picture is guaranteed.

  5. 5

    Half-resolution effects. A blur or glow does not need full sharpness. Render it into a target with half the width and height, and it shades a quarter of the pixels: at 1920 x 1080 that is 518,400 instead of 2,073,600. The postprocessing library's KawaseBlurPass defaults to a resolutionScale of 0.5 for this reason (read in its 6.39.5 source). Phase 15's chain holds full-size half-float targets, so every full-screen effect there pays the whole pixel bill.

  6. 6

    Render on demand. A model viewer that nobody touches does not need 60 pictures a second. Draw only when something changed: the camera moved, the canvas resized, a colour was chosen. The third example counts the frames drawn in 60 loop turns: 1 when nothing happens, 2 after one change, 61 when something moves all the time (autoRotate). Damping needs frames until it settles, so controls.update() tells you when it is still moving. If a visitor asked for reduced motion (matchMedia("(prefers-reduced-motion: reduce)").matches), leave autoRotate off: the viewer then draws only on their own input.

Explain it without notes

01

Why is a phone at pixel ratio 3 often slower than a laptop, even with a faster chip?

02

How does an opaque object in front reduce the work, and why does a transparent one not?

Practice

01

Write a function pickRatio(devicePixelRatio, canvasWidth, canvasHeight, maxPixels) that lowers the ratio so the buffer never exceeds maxPixels.

02

A viewer with autoRotate freezes after you switch to render on demand. Why?

Done when you can

  • Say how the pixel ratio changes the work, and cap it

  • Explain overdraw, and why transparency causes it

  • Draw only when something changed, and keep animating things drawing

  • Lower the cost of a blur by rendering it at half size