Rendering performance
Inside Out
@g33konaut
Hi, I’m Martin
Head of Engineering @ Archilogic, from Zurich
@g33konaut
Hi, I’m Martin
Head of Engineering @ Archilogic, from Zurich
GDE, W3C, Mozillian, I <3 The Web
@g33konaut
Whhhhhyyyyyy?
Time
Emotion
@g33konaut
Whhhhhyyyyyy?
Time: Get out of the way of your users...
Emotion
@g33konaut
Whhhhhyyyyyy?
Time: Get out of the way of your users...
Emotion: Make people happy, not frustrated!
@g33konaut
Agenda
@g33konaut
Agenda
@g33konaut
Part 1
Computer Graphics 101
@g33konaut
Pipeline (simplified)
Layer
Layer
Shader
@g33konaut
Pipeline (simplified)
Wat?
Wat?
Shader
Shader
?!
@g33konaut
Let’s start with pixels
@g33konaut
Pixels...
@g33konaut
Pixels...
100% red
66% green
33% blue
@g33konaut
Pixels...
100% red
66% green
33% blue
255
170
85
FF
AA
55
@g33konaut
Pixels...
100% red
66% green
33% blue
255
170
85
FF
AA
55
A bunch of numbers
@g33konaut
Pixels...
100% red
66% green
33% blue
255
170
85
FF
AA
55
A bunch of bytes
@g33konaut
Pixels = Memory
const pixels = [ 255, 170, 85,
255, 255, 255,
255, 0, 0 ]
@g33konaut
Drawing = Writing
const pixels = [
0, 0, 0,
0, 0, 0,
0, 0, 0 ]
| | |
@g33konaut
Drawing = Writing
const pixels = [
255, 0, 0,
0, 0, 0,
0, 0, 0 ]
| | |
@g33konaut
Drawing = Writing
const pixels = [
255, 0, 0,
0, 255, 0,
0, 0, 0 ]
| | |
@g33konaut
Drawing = Writing
const pixels = [
255, 0, 0,
0, 255, 0,
0, 0, 255 ]
| | |
@g33konaut
Drawing = Writing
const pixels = [
255, 255, 0,
0, 255, 255,
255, 0, 255 ]
| | |
@g33konaut
Concurrency ftw!
const pixels = [
255, 0, 0,
0, 255, 0,
0, 0, 255 ]
The pixels are independent!
@g33konaut
This can get complex
255, 000, 000, 255, 255, 255, 255, 255, 255, 255, 000, 000, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 000, 000, 255, 255, 255, 255, 255, 255, 255, 000, 000, 255, 255, 255
@g33konaut
These are the writes...
255, 000, 000, 255, 255, 255, 255, 255, 255, 255, 000, 000, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 000, 000, 255, 255, 255, 255, 255, 255, 255, 000, 000, 255, 255, 255
@g33konaut
Sprite
Layer
Texture
@g33konaut
Sprites ftw!
Sprite 0
Sprite 1
@g33konaut
Sprites (layers) ftw!
Sprite 0
Sprite 1
Transparency =
A fourth number...
@g33konaut
Drawing Step 0:
Copy Sprite 0
onto the screen
@g33konaut
Drawing Step 1:
Copy Sprite 1
onto the screen
@g33konaut
Drawing Step 2:
Copy Sprite 0
onto the screen
(again)
@g33konaut
Drawing Step 3:
Copy Sprite 1
onto the screen
(at new position)
@g33konaut
Drawing: Repeat!
@g33konaut
Drawing: Repeat!
This is also known as compositing
@g33konaut
Compositing?
Input: - Screen memory before (destination)
@g33konaut
Compositing?
Input: - Screen memory before (destination)
- Sprite to be added (source)
@g33konaut
Compositing?
Input: - Screen memory before (destination)
- Sprite to be added (source)
Output: Screen memory after (destination)
@g33konaut
How to composite?
&
?
@g33konaut
How to composite?
||
@g33konaut
How to composite?
||
@g33konaut
How to composite?
@g33konaut
How to composite?
@g33konaut
CSS blend modes
@g33konaut
Example: “screen”
color(x,y) = 1 - (1 - cat(x,y)) * (1 - sky(x,y))
@g33konaut
More compositing!
output[x, y] = source[x + a, y + b] + dest[x, y]
@g33konaut
More compositing!
output[x, y] = source[x + a, y + b] + dest[x, y]
output[x, y] = source[x*sx, y*sy] + dest[x, y]
@g33konaut
More compositing!
output[x, y] = source[x + a, y + b] + dest[x, y]
output[x, y] = source[x*sx, y*sy] + dest[x, y]
@g33konaut
Rotation!
cos(a) -sin(a)
sin(a) cos(a)
(ox, oy) = (x, y) *
@g33konaut
Composite only
@g33konaut
Composite only
@g33konaut
Composite only
@g33konaut
Composite only
*) only if the element is on its own layer
**) not all of them, not in all browsers
@g33konaut
Composite-only filters
@g33konaut
Composite-only filters
@g33konaut
Composite-only filters
@g33konaut
Composite-only filters
@g33konaut
Composite-only filters
@g33konaut
Composite-only filters
@g33konaut
Compositing:
let shader = (x, y, sprites, blend, filter) => {� return filter((blend(x, y, sprites)) // colour�}
@g33konaut
Return of the pipeline
Numbers
Numbers
Composite
Numbers
@g33konaut
Recap Part 1:
@g33konaut
Recap Part 1:
@g33konaut
Recap Part 1:
@g33konaut
Recap Part 1:
@g33konaut
Recap Part 1:
@g33konaut
Part 2:
Browser rendering
Illustrations: @pandatology
@g33konaut
The art of performance
“Performance is the art of avoiding work”
@g33konaut
Different paths through
@g33konaut
Parsing
<style>
.box { … }
</style>
<h2>Warning</h2>�<div class=”box”>� Hello world�</div>
CSSOM entry
DOM entry
DOM entry
@g33konaut
Layouting
<h2>
HTMLHeadingElement
<div>
HTMLDivElement
@g33konaut
Layouting
<h2>
HTMLHeadingElement
<div>
HTMLDivElement
<h2>
HTMLHeadingElement
<div>
HTMLDivElement
CSSOM
@g33konaut
Layouting
@g33konaut
Paint
@g33konaut
But now: JavaScript
var elem = document.querySelector(‘div’)�elem.style.width = ‘100%’;
@g33konaut
But now: JavaScript
var elem = document.querySelector(‘div’)�elem.style.width = ‘100%’;
@g33konaut
Let’s play a game...
#transform {
transform: translateX(0);
}
@g33konaut
Let’s play a game...
#transform {
transform: translateX(0);
}
setTimeout(() => { � el.style.transform = 'translateX(200px)' �}, 2000)
@g33konaut
Will it paint?
@g33konaut
Yes.
@g33konaut
WHY U NO COMPOSITE?
@g33konaut
Why does it paint?
@g33konaut
Why does it paint?
@g33konaut
Why does it paint?
@g33konaut
Why does it paint?
@g33konaut
Why does it paint?
@g33konaut
Why does it paint?
@g33konaut
Let’s play again...
#transform { transform: translate3d(0, 0, 0); }
setTimeout(() => { � el.style.transform = 'translate3d(200px, 0, 0)' �}, 2000)
@g33konaut
Will it paint?
@g33konaut
No. 3D transform
@g33konaut
Round 3:
#transform { animation: move 2s infinite; }�@keyframes move {� 0% { transform: translateX(0); }� 100% { transform: translateX(200px); }�}
@g33konaut
Will it paint?
@g33konaut
No. Composite-only anim
@g33konaut
Yea, I know...
@g33konaut
Using will-change
@keyframes move {� 0% { left: 0; }� 100% { left: 200px; } }
@g33konaut
Using will-change
@keyframes move {� 0% { left: 0; }� 100% { left: 200px; } }
@g33konaut
Using will-change
@keyframes move {� 0% { left: 0; }� 100% { left: 200px; } }
#transform {
will-change: left;
animation: move 2s infinite; }
@g33konaut
Using will-change
@keyframes move {� 0% { left: 0; }� 100% { left: 200px; } }
#transform {
will-change: left;
animation: move 2s infinite; }
@g33konaut
Using will-change
@g33konaut
Using will-change
This only works if the element
isn’t in the layout flow
(e.g. position: absolute)
@g33konaut
Browser summary
@g33konaut
Browser summary
@g33konaut
Browser summary
@g33konaut
Browser summary
@g33konaut
Browser summary
@g33konaut
Measure
then
optimise
@g33konaut
Thank you!
Slides: bit.ly/ft17-renderperf
Web: spaces.archilogic.com
50linesofco.de
Twitter: @g33konaut
Illustrations: @pandatology
@g33konaut
Part 3:
Canvas2D & WebGL
@g33konaut
So <canvas> is a layer...
@g33konaut
Canvas 2D test drive
for(var i=0; i<NUM_OBJECTS; i++) {� var x = Math.random() * HD_WIDTH, � y = Math.random() * HD_HEIGHT,� size = Math.random() * 512� ctx.drawImage(img,
x - HALF_SIZE, y - HALF_SIZE, size, size
)�}
@g33konaut
Canvas 2D result
@g33konaut
Canvas 2D result
Yay
Meh
Bah!
@g33konaut
What about WebGL?
<!DOCTYPE html>�<html>�<body>� <canvas width="250" height="250"></canvas>� <script type="text/plain" id="vs">� attribute vec3 avPos;� void main(void) {� gl_Position = vec4(avPos, 1.0);� }� </script>� <script type="text/plain" id="fs">� void main(void) {� gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);� }� </script>� <script>� var gl = document.querySelector('canvas').getContext('webgl')�� gl.viewport(0, 0, 250, 250)� gl.clearColor(0.0, 0.0, 0.0, 1.0)� gl.clear(gl.COLOR_BUFFER_BIT)�� var vertices = [-1.0, -1.0, 0.0, 1.0, -1.0, 0.0, 0, 1.0, 0.0]��
var vertexBuf = gl.createBuffer()� gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuf)� gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW)�
var program = gl.createProgram()� var vs = gl.createShader(gl.VERTEX_SHADER)� var fs = gl.createShader(gl.FRAGMENT_SHADER)�� gl.shaderSource(vs, document.getElementById('vs').textContent)� gl.shaderSource(fs, document.getElementById('fs').textContent)�� gl.compileShader(vs)� console.log('Vertex shader compilation:', gl.getShaderInfoLog(vs))�� gl.compileShader(fs)� console.log('Fragment shader compilation:', gl.getShaderInfoLog(fs))�� gl.attachShader(program, vs)� gl.attachShader(program, fs)�� gl.linkProgram(program)� if(!gl.getProgramParameter(program, gl.LINK_STATUS)) {� alert('Linking has failed.')� }�
gl.useProgram(program)�� var vertexPosAttrib = gl.getAttribLocation(program, 'avPos')� gl.enableVertexAttribArray(vertexPosAttrib)� gl.vertexAttribPointer(vertexPosAttrib, 3, gl.FLOAT, false, 0, 0)�� requestAnimationFrame(function render() {� gl.drawArrays(gl.TRIANGLES, 0, 3)� requestAnimationFrame(render)� })� </script>�</body>�</html>
@g33konaut
What about WebGL?
<!DOCTYPE html>�<html>�<body>� <canvas width="250" height="250"></canvas>� <script type="text/plain" id="vs">� attribute vec3 avPos;� void main(void) {� gl_Position = vec4(avPos, 1.0);� }� </script>� <script type="text/plain" id="fs">� void main(void) {� gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);� }� </script>� <script>� var gl = document.querySelector('canvas').getContext('webgl')�� gl.viewport(0, 0, 250, 250)� gl.clearColor(0.0, 0.0, 0.0, 1.0)� gl.clear(gl.COLOR_BUFFER_BIT)�� var vertices = [-1.0, -1.0, 0.0, 1.0, -1.0, 0.0, 0, 1.0, 0.0]��
var vertexBuf = gl.createBuffer()� gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuf)� gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW)�
var program = gl.createProgram()� var vs = gl.createShader(gl.VERTEX_SHADER)� var fs = gl.createShader(gl.FRAGMENT_SHADER)�� gl.shaderSource(vs, document.getElementById('vs').textContent)� gl.shaderSource(fs, document.getElementById('fs').textContent)�� gl.compileShader(vs)� console.log('Vertex shader compilation:', gl.getShaderInfoLog(vs))�� gl.compileShader(fs)� console.log('Fragment shader compilation:', gl.getShaderInfoLog(fs))�� gl.attachShader(program, vs)� gl.attachShader(program, fs)�� gl.linkProgram(program)� if(!gl.getProgramParameter(program, gl.LINK_STATUS)) {� alert('Linking has failed.')� }�
gl.useProgram(program)�� var vertexPosAttrib = gl.getAttribLocation(program, 'avPos')� gl.enableVertexAttribArray(vertexPosAttrib)� gl.vertexAttribPointer(vertexPosAttrib, 3, gl.FLOAT, false, 0, 0)�� requestAnimationFrame(function render() {� gl.drawArrays(gl.TRIANGLES, 0, 3)� requestAnimationFrame(render)� })� </script>�</body>�</html>
Hahaha, no, don’t try to read this!
@g33konaut
WebGL result
@g33konaut
Accelerated Canvas2D
“But did you measure it with GPU acceleration?”
@g33konaut
Yes.
@g33konaut
Yes.
“Canvas 2D turns my computer into a hovering hot stove at 50k objects, btw”
@g33konaut
WebGL all the things?
@g33konaut
No.
@g33konaut
Use the right tool
@g33konaut
Use the right tool
@g33konaut
Use the right tool
@g33konaut
Use the right tool
@g33konaut
Thank you!
Slides: bit.ly/ft17-renderperf
Web: spaces.archilogic.com
50linesofco.de
Twitter: @g33konaut
Illustrations: @pandatology
@g33konaut