Rendering Art on the Web - A Performance compendium

Raimon Ràfols
Raimon RàfolsEngineering Manager at AXA Group Solutions
@rrafols
Rendered art on the
web
A performance compendium
@rrafols
Disclaimer
Content is my own experimentation and might
differ on other environments / systems or as soon
as there is a browser update.
@rrafols
Uses
• Game development
• Customized widgets
• Interactivity
• It’s fun & creative
@rrafols
• JS1k - https://js1k.com/
• Demoscene - https://en.wikipedia.org/wiki/Demoscene
• P01 - http://www.p01.org/about/
• JS13k games - https://js13kgames.com/
• Creative JS - http://creativejs.com/
@rrafols
Let’s build something simple to begin with……
@rrafols
Step 0 - Recursive tree
• Set up a <canvas>
• Initialize the rendering loop with requestAnimationFrame()
@rrafols
Step 0 - Recursive tree
let a = document.getElementsByTagName('canvas')[0]
let c = a.getContext('2d')
let w = window.innerWidth
let h = window.innerHeight
render = ts => {
requestAnimationFrame(render)
}
requestAnimationFrame(render)
@rrafols
Step 1 - Recursive tree
• Clear the <canvas> on each iteration
• Draw the initial line
@rrafols
Step 1 - Recursive tree
a.width = w
a.height = h
c.strokeStyle = '#fff'
c.beginPath()
c.moveTo(w/2, h)
c.lineTo(w/2, h - h/3)
c.stroke()
@rrafols
Step 2 - Recursive tree
• Convert the drawing into a parametrized function
• Lines direction modified by an angle
@rrafols
Step 2 - Recursive tree
split(w / 2, h, h / 3, Math.PI / 2)
split = (x, y, length, angle) => {
let x1 = x + length * Math.cos(angle)
let y1 = y - length * Math.sin(angle)
c.beginPath()
c.moveTo(x, y)
c.lineTo(x1, y1)
c.stroke()
}
@rrafols
Step 3 - Recursive tree
• Call the split function recursively
• Modify the angle in each iteration to build the tree structure
@rrafols
Step 3 - Recursive tree
c.beginPath()
c.moveTo(x, y)
c.lineTo(x1, y1)
c.stroke()
split(x1, y1, length/1.5, angle - Math.PI/4, it + 1)
split(x1, y1, length/1.5, angle + Math.PI/4, it + 1)
@rrafols
Step 4 - Recursive tree
• Add more branches / bifurcations
• Increase max number of iterations
• Count the number of lines drawn so we can estimate drawing
complexity
@rrafols
Step 4 - Recursive tree
lines++
…
split(x1, y1, length/1.5, angle - Math.PI/3, it + 1)
split(x1, y1, length/1.5, angle - Math.PI/8, it + 1)
split(x1, y1, length/1.5, angle + Math.PI/3, it + 1)
split(x1, y1, length/1.5, angle + Math.PI/8, it + 1)
…
c.fillStyle = '#fff'
c.fillText("lines: " + lines, w - 200, 20)
@rrafols
Step 5 - Recursive tree
• Animate tree movement
• Recursively displace branches for intensified effect
• Apply alpha for smoother effect
@rrafols
Step 5 - Recursive tree
c.globalAlpha = 0.2
c.strokeStyle = '#fff'
let angle = Math.PI * Math.sin(ts * 0.0003)*0.15 +
Math.PI/2
split(w/2, h, h/3, angle, Math.cos(ts * 0.0004) *
Math.PI/8, 0)
c.globalAlpha = 1
@rrafols
Step 6 - Recursive tree
• Increase maximum number of iterations and see what happens…
@rrafols
<code>
@rrafols
How can we measure performance?
Feels slower right?
To measure the frames per second we can do any of the following:
• Implement a simple frame counter
• Web Developer tools in Chrome & Firefox
• Available open source libraries such as stats.js
• https://github.com/mrdoob/stats.js/
• Use jsPerf to create test & run cases
• https://jsperf.com/
@rrafols
@rrafols
@rrafols
Using stats.js
1) Use npm to install it:
rrafols$ npm install stats.js
2) Include into the source file
<script src="node_modules/stats.js/build/stats.min.js"/>
3) Add it to the render function:
@rrafols
Adding stats.js
var stats = new Stats()
stats.showPanel(0)
document.body.appendChild( stats.dom )
…
render = ts => {
stats.begin()
…
stats.end()
requestAnimationFrame(render)
}
@rrafols
Step 7 – Recursive tree
Now that we know it is slower, what we can do about it? How
can we optimize it?
• …
@rrafols
Step 7 – Recursive tree
Now that we know it is slower, what we can do about it? How
can we optimize it?
• Draw one single path with all the lines in it instead of several
paths with one single line.
• …
@rrafols
Step 7 - Recursive tree
// c.beginPath()
c.moveTo(x, y)
c.lineTo(x1, y1)
// c.stroke()
…
c.beginPath()
let angle = Math.PI*Math.sin(ts * 0.0003)*0.15…
split(w/2, h, h/3, angle, Math.cos(ts * 0.0004)…
c.stroke()
@rrafols
Step 7 – Recursive tree
Now that we know it is slower, what we can do about it? How
can we optimize it?
• Draw one single path with all the lines in it instead of several
paths with one single line.
• Let’s increase the number of iterations to see how it behaves
now
• …
@rrafols
<demo>
@rrafols
Performance Profiling
@rrafols
Performance Profiling
There might be a CPU bottleneck when calling recursively the
split function.
Let’s check if that is the issue…
@rrafols
Performance profiling
let path = new Path2D()
…
path.moveTo(x, y)
path.lineTo(x1, y1)
…
c.strokeStyle = '#fff'
c.stroke(path)
@rrafols
Performance Profiling
There is an impact in the
frame rate by function calling
overhead, but rendering
seems to be the bottleneck
@rrafols
Let’s try something else
@rrafols
Grid
We have several ways of drawing a grid, let’s see some of
them:
• Using strokeRect directly on the context
• Adding rect to a path and stroking that path
• Generating a path with moveTo / lineTo instead of rect
• ...
@rrafols
Grid - strokeRect
for(let i = 0; i < h / GRID_SIZE; i++) {
for(let j = 0; j < w / GRID_SIZE; j++) {
let x = j * GRID_SIZE
let y = i * GRID_SIZE
c.strokeRect(x, y, GRID_SIZE, GRID_SIZE)
}
}
@rrafols
Grid – path & rect
let path = new Path2D()
…
for(let i = 0; i < h / GRID_SIZE; i++) {
for(let j = 0; j < w / GRID_SIZE; j++) {
let x = j * GRID_SIZE
let y = i * GRID_SIZE
path.rect(x, y, GRID_SIZE, GRID_SIZE)
}
}
…
c.stroke(path)
@rrafols
Grid – moveTo/lineTo
c.beginPath()
for(let i = 0; i < h / GRID_SIZE; i++) {
for(let j = 0; j < w / GRID_SIZE; j++) {
let x = j * GRID_SIZE
let y = i * GRID_SIZE
c.moveTo(x, y)
c.lineTo(x + GRID_SIZE, y)
c.lineTo(x + GRID_SIZE, y + GRID_SIZE)
c.lineTo(x, y + GRID_SIZE)
c.lineTo(x, y)
}
}
c.stroke()
@rrafols
Grid – moveTo/lineTo - path
let path = new Path2D()
for(let i = 0; i < h / GRID_SIZE; i++) {
for(let j = 0; j < w / GRID_SIZE; j++) {
let x = j * GRID_SIZE
let y = i * GRID_SIZE
path.moveTo(x, y)
path.lineTo(x + GRID_SIZE, y)
path.lineTo(x + GRID_SIZE, y + GRID_SIZE)
path.lineTo(x, y + GRID_SIZE)
path.lineTo(x, y)
}
}
c.stroke(path)
@rrafols
<demo>
@rrafols
Grid – transformation
c.save()
c.translate(w / 2, h / 2)
c.rotate(angle)
c.translate(-w / 2, -h / 2)
…
c.restore()
@rrafols
Grid – transformation
//c.save()
c.translate(w / 2, h / 2)
c.rotate(angle)
c.translate(-w / 2, -h / 2)
…
//c.restore()
c.setTransform(1, 0, 0, 1, 0, 0)
@rrafols
<code>
@rrafols
Grid
@rrafols
Grid – transformation
rotate = (x, y, angle) => {
x -= w/2
y -= h/2
return [
x * Math.cos(angle) - y * Math.sin(angle) + w/2,
y * Math.cos(angle) + x * Math.sin(angle) + h/2
]
}
@rrafols
<demo>
@rrafols
OKNOK
strokeRect
path.rect
path.lineT
o
c.lineTo
@rrafols
Grid
What about fill operations instead of stroke? Let’s fill the
rects to see the differences.
@rrafols
<demo>
@rrafols
OK NOK
fillRect
path.rect
path.lineT
o
c.lineTo
@rrafols
Grid
What about images?
ImageAtlas vs single images
Images: https://toen.itch.io/toens-medieval-strategy
@rrafols
ImageAtlas – drawing & clipping
c.save()
c.beginPath()
c.rect(j * GRID_SIZE, i * GRID_SIZE, GRID_SIZE, GRID_SIZE)
c.clip()
c.drawImage(image, j * GRID_SIZE - 64, i * GRID_SIZE)
@rrafols
<demo>
@rrafols
Grid – ImageAtlas vs single images
@rrafols
Grid – image smoothing
Browsers smooth images when drawing on decimal positions:
c.drawImage(image, 5.24, 10.23)
@rrafols
ImageAtlas – drawing on exact pixels
c.drawImage(imageLarge,
(w / 2 - imageLarge.naturalWidth / 2) |0,
(h / 2 - imageLarge.naturalHeight / 2) |0)
@rrafols
<demo>
@rrafols
Images: https://dragosha.com/free/adventure-tileset.html
@rrafols
Conclusions
• Avoid allocating memory inside render loop – GC is “evil”!
• Group paths together rather than drawing multiple small paths
• Pre-calculate & store as much as possible
• Values & numbers
• Paths, Gradients, …
• Reduce render state machine changes
• Careful with canvas transformations
• Reset transformation with setTransform instead of save/restore.
• Always measure & profile. Check differences on several browsers.
@rrafols
Thank you
• More information:
https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial
• Source code:
https://github.com/rrafols/html_canvas_performance
• Contact: https://www.linkedin.com/in/raimonrafols/
@rrafols
<?>
1 of 61

Recommended

Numpy tutorial(final) 20160303 by
Numpy tutorial(final) 20160303Numpy tutorial(final) 20160303
Numpy tutorial(final) 20160303Namgee Lee
1K views17 slides
Functional Scala 2020 by
Functional Scala 2020Functional Scala 2020
Functional Scala 2020Alexander Ioffe
128 views105 slides
Scientific Computing with Python - NumPy | WeiYuan by
Scientific Computing with Python - NumPy | WeiYuanScientific Computing with Python - NumPy | WeiYuan
Scientific Computing with Python - NumPy | WeiYuanWei-Yuan Chang
516 views77 slides
Deep learning study 3 by
Deep learning study 3Deep learning study 3
Deep learning study 3San Kim
52 views18 slides
The Gremlin Graph Traversal Language by
The Gremlin Graph Traversal LanguageThe Gremlin Graph Traversal Language
The Gremlin Graph Traversal LanguageMarko Rodriguez
21.4K views50 slides
Coscup2021-rust-toturial by
Coscup2021-rust-toturialCoscup2021-rust-toturial
Coscup2021-rust-toturialWayne Tsai
126 views84 slides

More Related Content

What's hot

Enter The Matrix by
Enter The MatrixEnter The Matrix
Enter The MatrixMike Anderson
20.1K views64 slides
Introduction to NumPy for Machine Learning Programmers by
Introduction to NumPy for Machine Learning ProgrammersIntroduction to NumPy for Machine Learning Programmers
Introduction to NumPy for Machine Learning ProgrammersKimikazu Kato
6.6K views26 slides
Coscup2021 - useful abstractions at rust and it's practical usage by
Coscup2021 - useful abstractions at rust and it's practical usageCoscup2021 - useful abstractions at rust and it's practical usage
Coscup2021 - useful abstractions at rust and it's practical usageWayne Tsai
182 views68 slides
Running Free with the Monads by
Running Free with the MonadsRunning Free with the Monads
Running Free with the Monadskenbot
13.2K views125 slides
Numpy Talk at SIAM by
Numpy Talk at SIAMNumpy Talk at SIAM
Numpy Talk at SIAMEnthought, Inc.
4.6K views39 slides
λ | Lenses by
λ | Lensesλ | Lenses
λ | LensesOpen-IT
839 views30 slides

What's hot(20)

Introduction to NumPy for Machine Learning Programmers by Kimikazu Kato
Introduction to NumPy for Machine Learning ProgrammersIntroduction to NumPy for Machine Learning Programmers
Introduction to NumPy for Machine Learning Programmers
Kimikazu Kato6.6K views
Coscup2021 - useful abstractions at rust and it's practical usage by Wayne Tsai
Coscup2021 - useful abstractions at rust and it's practical usageCoscup2021 - useful abstractions at rust and it's practical usage
Coscup2021 - useful abstractions at rust and it's practical usage
Wayne Tsai182 views
Running Free with the Monads by kenbot
Running Free with the MonadsRunning Free with the Monads
Running Free with the Monads
kenbot13.2K views
λ | Lenses by Open-IT
λ | Lensesλ | Lenses
λ | Lenses
Open-IT839 views
Gremlin's Graph Traversal Machinery by Marko Rodriguez
Gremlin's Graph Traversal MachineryGremlin's Graph Traversal Machinery
Gremlin's Graph Traversal Machinery
Marko Rodriguez8.3K views
30 分鐘學會實作 Python Feature Selection by James Huang
30 分鐘學會實作 Python Feature Selection30 分鐘學會實作 Python Feature Selection
30 分鐘學會實作 Python Feature Selection
James Huang404 views
Statistical inference for (Python) Data Analysis. An introduction. by Piotr Milanowski
Statistical inference for (Python) Data Analysis. An introduction.Statistical inference for (Python) Data Analysis. An introduction.
Statistical inference for (Python) Data Analysis. An introduction.
Piotr Milanowski2K views
1 seaborn introduction by YuleiLi3
1 seaborn introduction 1 seaborn introduction
1 seaborn introduction
YuleiLi3247 views
ECMAScript 6 major changes by hayato
ECMAScript 6 major changesECMAScript 6 major changes
ECMAScript 6 major changes
hayato704 views
Intoduction to numpy by Faraz Ahmed
Intoduction to numpyIntoduction to numpy
Intoduction to numpy
Faraz Ahmed497 views
Optics with monocle - Modeling the part and the whole by Ilan Godik
Optics with monocle - Modeling the part and the wholeOptics with monocle - Modeling the part and the whole
Optics with monocle - Modeling the part and the whole
Ilan Godik1.6K views
Functional Patterns for the non-mathematician by Brian Lonsdorf
Functional Patterns for the non-mathematicianFunctional Patterns for the non-mathematician
Functional Patterns for the non-mathematician
Brian Lonsdorf6.9K views
Abstracting over the Monad yielded by a for comprehension and its generators by Philip Schwarz
Abstracting over the Monad yielded by a for comprehension and its generatorsAbstracting over the Monad yielded by a for comprehension and its generators
Abstracting over the Monad yielded by a for comprehension and its generators
Philip Schwarz259 views

Similar to Rendering Art on the Web - A Performance compendium

Introduction to Coding by
Introduction to CodingIntroduction to Coding
Introduction to CodingFabio506452
12 views30 slides
Proposals for new function in Java SE 9 and beyond by
Proposals for new function in Java SE 9 and beyondProposals for new function in Java SE 9 and beyond
Proposals for new function in Java SE 9 and beyondBarry Feigenbaum
945 views401 slides
FreeBSD 2014 Flame Graphs by
FreeBSD 2014 Flame GraphsFreeBSD 2014 Flame Graphs
FreeBSD 2014 Flame GraphsBrendan Gregg
17.8K views66 slides
Clojure And Swing by
Clojure And SwingClojure And Swing
Clojure And SwingSkills Matter
3.6K views44 slides
Refactoring to Macros with Clojure by
Refactoring to Macros with ClojureRefactoring to Macros with Clojure
Refactoring to Macros with ClojureDmitry Buzdin
3.5K views51 slides
Raphaël and You by
Raphaël and YouRaphaël and You
Raphaël and YouTrotter Cashion
2.3K views39 slides

Similar to Rendering Art on the Web - A Performance compendium(20)

Introduction to Coding by Fabio506452
Introduction to CodingIntroduction to Coding
Introduction to Coding
Fabio50645212 views
Proposals for new function in Java SE 9 and beyond by Barry Feigenbaum
Proposals for new function in Java SE 9 and beyondProposals for new function in Java SE 9 and beyond
Proposals for new function in Java SE 9 and beyond
Barry Feigenbaum945 views
FreeBSD 2014 Flame Graphs by Brendan Gregg
FreeBSD 2014 Flame GraphsFreeBSD 2014 Flame Graphs
FreeBSD 2014 Flame Graphs
Brendan Gregg17.8K views
Refactoring to Macros with Clojure by Dmitry Buzdin
Refactoring to Macros with ClojureRefactoring to Macros with Clojure
Refactoring to Macros with Clojure
Dmitry Buzdin3.5K views
Swift for tensorflow by 규영 허
Swift for tensorflowSwift for tensorflow
Swift for tensorflow
규영 허311 views
ComputeFest 2012: Intro To R for Physical Sciences by alexstorer
ComputeFest 2012: Intro To R for Physical SciencesComputeFest 2012: Intro To R for Physical Sciences
ComputeFest 2012: Intro To R for Physical Sciences
alexstorer470 views
Getting Started with Keras and TensorFlow - StampedeCon AI Summit 2017 by StampedeCon
Getting Started with Keras and TensorFlow - StampedeCon AI Summit 2017Getting Started with Keras and TensorFlow - StampedeCon AI Summit 2017
Getting Started with Keras and TensorFlow - StampedeCon AI Summit 2017
StampedeCon1.4K views
Emerging Languages: A Tour of the Horizon by Alex Payne
Emerging Languages: A Tour of the HorizonEmerging Languages: A Tour of the Horizon
Emerging Languages: A Tour of the Horizon
Alex Payne6.3K views
Lock? We don't need no stinkin' locks! by Michael Barker
Lock? We don't need no stinkin' locks!Lock? We don't need no stinkin' locks!
Lock? We don't need no stinkin' locks!
Michael Barker23.1K views
Locks? We Don't Need No Stinkin' Locks - Michael Barker by JAX London
Locks? We Don't Need No Stinkin' Locks - Michael BarkerLocks? We Don't Need No Stinkin' Locks - Michael Barker
Locks? We Don't Need No Stinkin' Locks - Michael Barker
JAX London962 views
Threaded Programming by Sri Prasanna
Threaded ProgrammingThreaded Programming
Threaded Programming
Sri Prasanna718 views
Groovy by Zen Urban
GroovyGroovy
Groovy
Zen Urban1.1K views
Scala Refactoring for Fun and Profit by Tomer Gabel
Scala Refactoring for Fun and ProfitScala Refactoring for Fun and Profit
Scala Refactoring for Fun and Profit
Tomer Gabel985 views
Introduction To PostGIS by mleslie
Introduction To PostGISIntroduction To PostGIS
Introduction To PostGIS
mleslie6.5K views
Building an ML Platform with Ray and MLflow by Databricks
Building an ML Platform with Ray and MLflowBuilding an ML Platform with Ray and MLflow
Building an ML Platform with Ray and MLflow
Databricks763 views

More from Raimon Ràfols

The bytecode gobbledygook by
The bytecode gobbledygookThe bytecode gobbledygook
The bytecode gobbledygookRaimon Ràfols
695 views178 slides
The bytecode mumbo-jumbo by
The bytecode mumbo-jumboThe bytecode mumbo-jumbo
The bytecode mumbo-jumboRaimon Ràfols
2.5K views173 slides
The Digital Evolution of Dinosaurs - MWCS 2017 by
The Digital Evolution of Dinosaurs - MWCS 2017The Digital Evolution of Dinosaurs - MWCS 2017
The Digital Evolution of Dinosaurs - MWCS 2017Raimon Ràfols
1.2K views25 slides
Android Custom Views by
Android Custom ViewsAndroid Custom Views
Android Custom ViewsRaimon Ràfols
381 views38 slides
Iterate & Learn 2017 by
Iterate & Learn 2017Iterate & Learn 2017
Iterate & Learn 2017Raimon Ràfols
273 views68 slides
Iterate & Learn 2017 (català) by
Iterate & Learn 2017 (català)Iterate & Learn 2017 (català)
Iterate & Learn 2017 (català)Raimon Ràfols
333 views67 slides

More from Raimon Ràfols(12)

The Digital Evolution of Dinosaurs - MWCS 2017 by Raimon Ràfols
The Digital Evolution of Dinosaurs - MWCS 2017The Digital Evolution of Dinosaurs - MWCS 2017
The Digital Evolution of Dinosaurs - MWCS 2017
Raimon Ràfols1.2K views
Iterate & Learn 2017 (català) by Raimon Ràfols
Iterate & Learn 2017 (català)Iterate & Learn 2017 (català)
Iterate & Learn 2017 (català)
Raimon Ràfols333 views
The bytecode hocus pocus - JavaOne 2016 by Raimon Ràfols
The bytecode hocus pocus - JavaOne 2016The bytecode hocus pocus - JavaOne 2016
The bytecode hocus pocus - JavaOne 2016
Raimon Ràfols1.6K views
Iterate + learn - February 2016 by Raimon Ràfols
Iterate + learn - February 2016Iterate + learn - February 2016
Iterate + learn - February 2016
Raimon Ràfols104 views
Playing with camera preview buffers on BlackBerry 10 by Raimon Ràfols
Playing with camera preview buffers on BlackBerry 10Playing with camera preview buffers on BlackBerry 10
Playing with camera preview buffers on BlackBerry 10
Raimon Ràfols137 views
Improving Android Performance at Mobiconf 2014 by Raimon Ràfols
Improving Android Performance at Mobiconf 2014Improving Android Performance at Mobiconf 2014
Improving Android Performance at Mobiconf 2014
Raimon Ràfols242 views
Improving Android Performance at Droidcon UK 2014 by Raimon Ràfols
Improving Android Performance at Droidcon UK 2014Improving Android Performance at Droidcon UK 2014
Improving Android Performance at Droidcon UK 2014
Raimon Ràfols129 views
Improving Java performance at JBCNConf 2015 by Raimon Ràfols
Improving Java performance at JBCNConf 2015Improving Java performance at JBCNConf 2015
Improving Java performance at JBCNConf 2015
Raimon Ràfols115 views

Recently uploaded

Five Things You SHOULD Know About Postman by
Five Things You SHOULD Know About PostmanFive Things You SHOULD Know About Postman
Five Things You SHOULD Know About PostmanPostman
40 views43 slides
The Research Portal of Catalonia: Growing more (information) & more (services) by
The Research Portal of Catalonia: Growing more (information) & more (services)The Research Portal of Catalonia: Growing more (information) & more (services)
The Research Portal of Catalonia: Growing more (information) & more (services)CSUC - Consorci de Serveis Universitaris de Catalunya
136 views25 slides
CloudStack and GitOps at Enterprise Scale - Alex Dometrius, Rene Glover - AT&T by
CloudStack and GitOps at Enterprise Scale - Alex Dometrius, Rene Glover - AT&TCloudStack and GitOps at Enterprise Scale - Alex Dometrius, Rene Glover - AT&T
CloudStack and GitOps at Enterprise Scale - Alex Dometrius, Rene Glover - AT&TShapeBlue
56 views34 slides
Business Analyst Series 2023 - Week 4 Session 7 by
Business Analyst Series 2023 -  Week 4 Session 7Business Analyst Series 2023 -  Week 4 Session 7
Business Analyst Series 2023 - Week 4 Session 7DianaGray10
80 views31 slides
Migrating VMware Infra to KVM Using CloudStack - Nicolas Vazquez - ShapeBlue by
Migrating VMware Infra to KVM Using CloudStack - Nicolas Vazquez - ShapeBlueMigrating VMware Infra to KVM Using CloudStack - Nicolas Vazquez - ShapeBlue
Migrating VMware Infra to KVM Using CloudStack - Nicolas Vazquez - ShapeBlueShapeBlue
96 views20 slides
ESPC 2023 - Protect and Govern your Sensitive Data with Microsoft Purview in ... by
ESPC 2023 - Protect and Govern your Sensitive Data with Microsoft Purview in ...ESPC 2023 - Protect and Govern your Sensitive Data with Microsoft Purview in ...
ESPC 2023 - Protect and Govern your Sensitive Data with Microsoft Purview in ...Jasper Oosterveld
28 views49 slides

Recently uploaded(20)

Five Things You SHOULD Know About Postman by Postman
Five Things You SHOULD Know About PostmanFive Things You SHOULD Know About Postman
Five Things You SHOULD Know About Postman
Postman40 views
CloudStack and GitOps at Enterprise Scale - Alex Dometrius, Rene Glover - AT&T by ShapeBlue
CloudStack and GitOps at Enterprise Scale - Alex Dometrius, Rene Glover - AT&TCloudStack and GitOps at Enterprise Scale - Alex Dometrius, Rene Glover - AT&T
CloudStack and GitOps at Enterprise Scale - Alex Dometrius, Rene Glover - AT&T
ShapeBlue56 views
Business Analyst Series 2023 - Week 4 Session 7 by DianaGray10
Business Analyst Series 2023 -  Week 4 Session 7Business Analyst Series 2023 -  Week 4 Session 7
Business Analyst Series 2023 - Week 4 Session 7
DianaGray1080 views
Migrating VMware Infra to KVM Using CloudStack - Nicolas Vazquez - ShapeBlue by ShapeBlue
Migrating VMware Infra to KVM Using CloudStack - Nicolas Vazquez - ShapeBlueMigrating VMware Infra to KVM Using CloudStack - Nicolas Vazquez - ShapeBlue
Migrating VMware Infra to KVM Using CloudStack - Nicolas Vazquez - ShapeBlue
ShapeBlue96 views
ESPC 2023 - Protect and Govern your Sensitive Data with Microsoft Purview in ... by Jasper Oosterveld
ESPC 2023 - Protect and Govern your Sensitive Data with Microsoft Purview in ...ESPC 2023 - Protect and Govern your Sensitive Data with Microsoft Purview in ...
ESPC 2023 - Protect and Govern your Sensitive Data with Microsoft Purview in ...
NTGapps NTG LowCode Platform by Mustafa Kuğu
NTGapps NTG LowCode Platform NTGapps NTG LowCode Platform
NTGapps NTG LowCode Platform
Mustafa Kuğu141 views
VNF Integration and Support in CloudStack - Wei Zhou - ShapeBlue by ShapeBlue
VNF Integration and Support in CloudStack - Wei Zhou - ShapeBlueVNF Integration and Support in CloudStack - Wei Zhou - ShapeBlue
VNF Integration and Support in CloudStack - Wei Zhou - ShapeBlue
ShapeBlue85 views
2FA and OAuth2 in CloudStack - Andrija Panić - ShapeBlue by ShapeBlue
2FA and OAuth2 in CloudStack - Andrija Panić - ShapeBlue2FA and OAuth2 in CloudStack - Andrija Panić - ShapeBlue
2FA and OAuth2 in CloudStack - Andrija Panić - ShapeBlue
ShapeBlue50 views
Why and How CloudStack at weSystems - Stephan Bienek - weSystems by ShapeBlue
Why and How CloudStack at weSystems - Stephan Bienek - weSystemsWhy and How CloudStack at weSystems - Stephan Bienek - weSystems
Why and How CloudStack at weSystems - Stephan Bienek - weSystems
ShapeBlue111 views
Centralized Logging Feature in CloudStack using ELK and Grafana - Kiran Chava... by ShapeBlue
Centralized Logging Feature in CloudStack using ELK and Grafana - Kiran Chava...Centralized Logging Feature in CloudStack using ELK and Grafana - Kiran Chava...
Centralized Logging Feature in CloudStack using ELK and Grafana - Kiran Chava...
ShapeBlue48 views
Transitioning from VMware vCloud to Apache CloudStack: A Path to Profitabilit... by ShapeBlue
Transitioning from VMware vCloud to Apache CloudStack: A Path to Profitabilit...Transitioning from VMware vCloud to Apache CloudStack: A Path to Profitabilit...
Transitioning from VMware vCloud to Apache CloudStack: A Path to Profitabilit...
ShapeBlue57 views
State of the Union - Rohit Yadav - Apache CloudStack by ShapeBlue
State of the Union - Rohit Yadav - Apache CloudStackState of the Union - Rohit Yadav - Apache CloudStack
State of the Union - Rohit Yadav - Apache CloudStack
ShapeBlue145 views
Backroll, News and Demo - Pierre Charton, Matthias Dhellin, Ousmane Diarra - ... by ShapeBlue
Backroll, News and Demo - Pierre Charton, Matthias Dhellin, Ousmane Diarra - ...Backroll, News and Demo - Pierre Charton, Matthias Dhellin, Ousmane Diarra - ...
Backroll, News and Demo - Pierre Charton, Matthias Dhellin, Ousmane Diarra - ...
ShapeBlue83 views
DRBD Deep Dive - Philipp Reisner - LINBIT by ShapeBlue
DRBD Deep Dive - Philipp Reisner - LINBITDRBD Deep Dive - Philipp Reisner - LINBIT
DRBD Deep Dive - Philipp Reisner - LINBIT
ShapeBlue62 views
Setting Up Your First CloudStack Environment with Beginners Challenges - MD R... by ShapeBlue
Setting Up Your First CloudStack Environment with Beginners Challenges - MD R...Setting Up Your First CloudStack Environment with Beginners Challenges - MD R...
Setting Up Your First CloudStack Environment with Beginners Challenges - MD R...
ShapeBlue54 views
Developments to CloudStack’s SDN ecosystem: Integration with VMWare NSX 4 - P... by ShapeBlue
Developments to CloudStack’s SDN ecosystem: Integration with VMWare NSX 4 - P...Developments to CloudStack’s SDN ecosystem: Integration with VMWare NSX 4 - P...
Developments to CloudStack’s SDN ecosystem: Integration with VMWare NSX 4 - P...
ShapeBlue82 views
Backup and Disaster Recovery with CloudStack and StorPool - Workshop - Venko ... by ShapeBlue
Backup and Disaster Recovery with CloudStack and StorPool - Workshop - Venko ...Backup and Disaster Recovery with CloudStack and StorPool - Workshop - Venko ...
Backup and Disaster Recovery with CloudStack and StorPool - Workshop - Venko ...
ShapeBlue77 views

Rendering Art on the Web - A Performance compendium

  • 1. @rrafols Rendered art on the web A performance compendium
  • 2. @rrafols Disclaimer Content is my own experimentation and might differ on other environments / systems or as soon as there is a browser update.
  • 3. @rrafols Uses • Game development • Customized widgets • Interactivity • It’s fun & creative
  • 4. @rrafols • JS1k - https://js1k.com/ • Demoscene - https://en.wikipedia.org/wiki/Demoscene • P01 - http://www.p01.org/about/ • JS13k games - https://js13kgames.com/ • Creative JS - http://creativejs.com/
  • 5. @rrafols Let’s build something simple to begin with……
  • 6. @rrafols Step 0 - Recursive tree • Set up a <canvas> • Initialize the rendering loop with requestAnimationFrame()
  • 7. @rrafols Step 0 - Recursive tree let a = document.getElementsByTagName('canvas')[0] let c = a.getContext('2d') let w = window.innerWidth let h = window.innerHeight render = ts => { requestAnimationFrame(render) } requestAnimationFrame(render)
  • 8. @rrafols Step 1 - Recursive tree • Clear the <canvas> on each iteration • Draw the initial line
  • 9. @rrafols Step 1 - Recursive tree a.width = w a.height = h c.strokeStyle = '#fff' c.beginPath() c.moveTo(w/2, h) c.lineTo(w/2, h - h/3) c.stroke()
  • 10. @rrafols Step 2 - Recursive tree • Convert the drawing into a parametrized function • Lines direction modified by an angle
  • 11. @rrafols Step 2 - Recursive tree split(w / 2, h, h / 3, Math.PI / 2) split = (x, y, length, angle) => { let x1 = x + length * Math.cos(angle) let y1 = y - length * Math.sin(angle) c.beginPath() c.moveTo(x, y) c.lineTo(x1, y1) c.stroke() }
  • 12. @rrafols Step 3 - Recursive tree • Call the split function recursively • Modify the angle in each iteration to build the tree structure
  • 13. @rrafols Step 3 - Recursive tree c.beginPath() c.moveTo(x, y) c.lineTo(x1, y1) c.stroke() split(x1, y1, length/1.5, angle - Math.PI/4, it + 1) split(x1, y1, length/1.5, angle + Math.PI/4, it + 1)
  • 14. @rrafols Step 4 - Recursive tree • Add more branches / bifurcations • Increase max number of iterations • Count the number of lines drawn so we can estimate drawing complexity
  • 15. @rrafols Step 4 - Recursive tree lines++ … split(x1, y1, length/1.5, angle - Math.PI/3, it + 1) split(x1, y1, length/1.5, angle - Math.PI/8, it + 1) split(x1, y1, length/1.5, angle + Math.PI/3, it + 1) split(x1, y1, length/1.5, angle + Math.PI/8, it + 1) … c.fillStyle = '#fff' c.fillText("lines: " + lines, w - 200, 20)
  • 16. @rrafols Step 5 - Recursive tree • Animate tree movement • Recursively displace branches for intensified effect • Apply alpha for smoother effect
  • 17. @rrafols Step 5 - Recursive tree c.globalAlpha = 0.2 c.strokeStyle = '#fff' let angle = Math.PI * Math.sin(ts * 0.0003)*0.15 + Math.PI/2 split(w/2, h, h/3, angle, Math.cos(ts * 0.0004) * Math.PI/8, 0) c.globalAlpha = 1
  • 18. @rrafols Step 6 - Recursive tree • Increase maximum number of iterations and see what happens…
  • 20. @rrafols How can we measure performance? Feels slower right? To measure the frames per second we can do any of the following: • Implement a simple frame counter • Web Developer tools in Chrome & Firefox • Available open source libraries such as stats.js • https://github.com/mrdoob/stats.js/ • Use jsPerf to create test & run cases • https://jsperf.com/
  • 23. @rrafols Using stats.js 1) Use npm to install it: rrafols$ npm install stats.js 2) Include into the source file <script src="node_modules/stats.js/build/stats.min.js"/> 3) Add it to the render function:
  • 24. @rrafols Adding stats.js var stats = new Stats() stats.showPanel(0) document.body.appendChild( stats.dom ) … render = ts => { stats.begin() … stats.end() requestAnimationFrame(render) }
  • 25. @rrafols Step 7 – Recursive tree Now that we know it is slower, what we can do about it? How can we optimize it? • …
  • 26. @rrafols Step 7 – Recursive tree Now that we know it is slower, what we can do about it? How can we optimize it? • Draw one single path with all the lines in it instead of several paths with one single line. • …
  • 27. @rrafols Step 7 - Recursive tree // c.beginPath() c.moveTo(x, y) c.lineTo(x1, y1) // c.stroke() … c.beginPath() let angle = Math.PI*Math.sin(ts * 0.0003)*0.15… split(w/2, h, h/3, angle, Math.cos(ts * 0.0004)… c.stroke()
  • 28. @rrafols Step 7 – Recursive tree Now that we know it is slower, what we can do about it? How can we optimize it? • Draw one single path with all the lines in it instead of several paths with one single line. • Let’s increase the number of iterations to see how it behaves now • …
  • 31. @rrafols Performance Profiling There might be a CPU bottleneck when calling recursively the split function. Let’s check if that is the issue…
  • 32. @rrafols Performance profiling let path = new Path2D() … path.moveTo(x, y) path.lineTo(x1, y1) … c.strokeStyle = '#fff' c.stroke(path)
  • 33. @rrafols Performance Profiling There is an impact in the frame rate by function calling overhead, but rendering seems to be the bottleneck
  • 35. @rrafols Grid We have several ways of drawing a grid, let’s see some of them: • Using strokeRect directly on the context • Adding rect to a path and stroking that path • Generating a path with moveTo / lineTo instead of rect • ...
  • 36. @rrafols Grid - strokeRect for(let i = 0; i < h / GRID_SIZE; i++) { for(let j = 0; j < w / GRID_SIZE; j++) { let x = j * GRID_SIZE let y = i * GRID_SIZE c.strokeRect(x, y, GRID_SIZE, GRID_SIZE) } }
  • 37. @rrafols Grid – path & rect let path = new Path2D() … for(let i = 0; i < h / GRID_SIZE; i++) { for(let j = 0; j < w / GRID_SIZE; j++) { let x = j * GRID_SIZE let y = i * GRID_SIZE path.rect(x, y, GRID_SIZE, GRID_SIZE) } } … c.stroke(path)
  • 38. @rrafols Grid – moveTo/lineTo c.beginPath() for(let i = 0; i < h / GRID_SIZE; i++) { for(let j = 0; j < w / GRID_SIZE; j++) { let x = j * GRID_SIZE let y = i * GRID_SIZE c.moveTo(x, y) c.lineTo(x + GRID_SIZE, y) c.lineTo(x + GRID_SIZE, y + GRID_SIZE) c.lineTo(x, y + GRID_SIZE) c.lineTo(x, y) } } c.stroke()
  • 39. @rrafols Grid – moveTo/lineTo - path let path = new Path2D() for(let i = 0; i < h / GRID_SIZE; i++) { for(let j = 0; j < w / GRID_SIZE; j++) { let x = j * GRID_SIZE let y = i * GRID_SIZE path.moveTo(x, y) path.lineTo(x + GRID_SIZE, y) path.lineTo(x + GRID_SIZE, y + GRID_SIZE) path.lineTo(x, y + GRID_SIZE) path.lineTo(x, y) } } c.stroke(path)
  • 41. @rrafols Grid – transformation c.save() c.translate(w / 2, h / 2) c.rotate(angle) c.translate(-w / 2, -h / 2) … c.restore()
  • 42. @rrafols Grid – transformation //c.save() c.translate(w / 2, h / 2) c.rotate(angle) c.translate(-w / 2, -h / 2) … //c.restore() c.setTransform(1, 0, 0, 1, 0, 0)
  • 45. @rrafols Grid – transformation rotate = (x, y, angle) => { x -= w/2 y -= h/2 return [ x * Math.cos(angle) - y * Math.sin(angle) + w/2, y * Math.cos(angle) + x * Math.sin(angle) + h/2 ] }
  • 48. @rrafols Grid What about fill operations instead of stroke? Let’s fill the rects to see the differences.
  • 51. @rrafols Grid What about images? ImageAtlas vs single images Images: https://toen.itch.io/toens-medieval-strategy
  • 52. @rrafols ImageAtlas – drawing & clipping c.save() c.beginPath() c.rect(j * GRID_SIZE, i * GRID_SIZE, GRID_SIZE, GRID_SIZE) c.clip() c.drawImage(image, j * GRID_SIZE - 64, i * GRID_SIZE)
  • 54. @rrafols Grid – ImageAtlas vs single images
  • 55. @rrafols Grid – image smoothing Browsers smooth images when drawing on decimal positions: c.drawImage(image, 5.24, 10.23)
  • 56. @rrafols ImageAtlas – drawing on exact pixels c.drawImage(imageLarge, (w / 2 - imageLarge.naturalWidth / 2) |0, (h / 2 - imageLarge.naturalHeight / 2) |0)
  • 59. @rrafols Conclusions • Avoid allocating memory inside render loop – GC is “evil”! • Group paths together rather than drawing multiple small paths • Pre-calculate & store as much as possible • Values & numbers • Paths, Gradients, … • Reduce render state machine changes • Careful with canvas transformations • Reset transformation with setTransform instead of save/restore. • Always measure & profile. Check differences on several browsers.
  • 60. @rrafols Thank you • More information: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial • Source code: https://github.com/rrafols/html_canvas_performance • Contact: https://www.linkedin.com/in/raimonrafols/