New issue
Advanced search Search tips

Issue 741063 link

Starred by 2 users

Issue metadata

Status: WontFix
Owner: ----
Closed: Jul 2017
Cc:
Components:
EstimatedDays: ----
NextAction: ----
OS: Mac
Pri: 2
Type: Bug



Sign in to add a comment

Is SVG rendering GPU hardware accelerated?

Reported by trusktr@gmail.com, Jul 11 2017

Issue description

UserAgent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36

Steps to reproduce the problem:
I'm just curious to know if SVG rendering is hardware accelerated, or does it run on the CPU?

What is the expected behavior?

What went wrong?
It is slow compared to 2D webgl drawing libraries like Pixi.js and Two.js. I am curious why a similar approach can not be taken.

Did this work before? N/A 

Does this work in other browsers? N/A

Chrome version: 58.0.3029.110  Channel: stable
OS Version: OS X 10.12.5
Flash Version: 

It would be great to have a faster JavaScript/WebGL solution built into the browser because user-space libraries prove performance can be achieved, and writing in JavaScript is easier.
 

Comment 1 by pdr@chromium.org, Jul 11 2017

Cc: pdr@chromium.org
Status: WontFix (was: Unconfirmed)
SVG is typically rasterized with gpu acceleration, though it will depend on the specific hardware (about:gpu has the details). There are some caveats but for static content, svg and webgl should be similar. I'd expect svg to be slower because it is more general.

For dynamic content, we do not have good mechanisms to specify grouping yet, where content is rasterized once and then moved around. We're working on a large refactoring of the code which will let us do that for SVG. If you use html to break up your content with will-change, performance should be similar (here's an example: https://output.jsbin.com/fubuloc/quiet)

I'm marking this as wontfix so this isn't in our bug queue, but I'd be happy to continue discussing this. Do you have a concrete example we could discuss?

Comment 2 by trusktr@gmail.com, Jul 11 2017

If no acceleration yet, is it planned? Part of slimming-paint? If planned, why does it take so long?

Comment 3 by pdr@chromium.org, Jul 11 2017

There is acceleration today. It requires compatible hardware and most users have it enabled. If you go to "chrome://gpu", you can see if it is enabled by checking for the line "Rasterization: Hardware accelerated".

Comment 4 Deleted

Comment 5 by trusktr@gmail.com, Jul 14 2017

I missed your last comment before I posted last. My about:flags says rasterization is accelerated.

In that last example you posted, it is `div` elements that are being transformed, the SVGs aren't being modified, they are being used as static textures.

I am curious to know, for example, if I draw the balls inside a single <svg> element and animate the `cx` and `cy` attributes of the SVGCircleElements, will that be hardware accelerated?

Also, if I take that example, and change it to 1000 balls instead of 100, the frame rate drops drastically.

In comparison, I can animate 1000 circles using Pixi.js buttery smooth with no lag.

If SVG drawing is accelerated (animating SVGElements inside a single SVGSVGElement), then I am wondering why SVG can't simply be accelerated the way that Pixi.js can be.

What are the bottlenecks? Is it the mere fact that numbers in the JavaScript have to be converted to strings when passed as attribute into the DOM elements, and then the HTML engine has to convert from string back to numbers?

Comment 6 by trusktr@gmail.com, Jul 14 2017

I'm just curious about all this, because I notice that I can draw similar things with Pixi.js or Two.js, and they can handle many more objects without dropping frames. I'm just wondering why the native implementation can't do this, and why it has been this way for so long.

Here's a Pixi.js bunnymark: http://www.goodboydigital.com/pixijs/bunnymark_v3/

Click on it a bunch of times.

I've got a basic SVG lib on top of Pixi.js (proprietary, at work) and I can use it to make a scene graph of Pixi.js objects from reading an SVG tree out of the browser's DOM, and then I can grab nodes in that scene graph and animate them really fast (like in that bunnymark).

Why isn't native rendering that fast?

Comment 7 by pdr@chromium.org, Jul 14 2017

The bottleneck is basically that pixi.js is an "immediate" form of graphics whereas our pipeline is "retained". For example, our system is optimized for things like being able to hit test the balls whereas pixi.js doesn't need to keep that around.

Once you're looking at 1000 moving objects, I think something less general such as pixi.js is a great choice.

Comment 8 by trusktr@gmail.com, Jul 14 2017

The thing is, SVG itself is a nice API. It would be great to have this API, but still have speed.

I don't see how hit testing can matter. That should be optional. Most people don't need hit testing unless they have mouse event handlers set up, for example. If there are no event handlers, or even if there is no mouse movement (for example) no hitting testing ever needs to happen.

Can you explain retained vs immediate? I read this: https://msdn.microsoft.com/en-us/library/windows/desktop/ff684178(v=vs.85).aspx but I don't understand how that would make such a huge performance difference.

If I understand at all, SVG is retained because there is a DOM? There is also a scene graph in Pixi apps. The user modifies the scene graph, then Pixi updates the rendering.

A pixi scene graph can be just as deep and complex the same as SVG, there's no limit of complexity on a Pixi.js scene graph in the same way as SVG DOM, so I don't understand how that should affect performance.

Comment 9 by trusktr@gmail.com, Jul 14 2017

Based on this article, https://en.wikipedia.org/wiki/Immediate_mode_(computer_graphics), it says that the immediate-mode application must re-issue all drawing commands for every frame. To me, that sounds slower than an alternative that doesn't need to re-issue all drawing commands (retained mode?).

And based on https://en.wikipedia.org/wiki/Retained_mode,

> [retained mode] allows the library to optimize when actual rendering takes place along with the processing of related objects.

So this makes it seem like "retained mode" wouldn't be a reason for SVG to be slower.

Comment 10 by pdr@chromium.org, Jul 15 2017

Pixi has a very simple scene graph which is ideal for moving around lots of static images. Chrome/blink's scene graph is enormous (dom tree, layout tree, main thread layer tree, compositor thread layer tree, etc) and is good at rendering existing uses of the SVG API.

Sadly, I think it would take a book to describe in much more detail :/ You can poke around the code at https://cs.chromium.org/chromium/src/third_party/WebKit/Source/core/paint/SVGShapePainter.cpp to kinda get a feel for one part of the graphics pipeline. I think your links about retained vs immediate give a good flavor of the kinds of tradeoffs that need to be made in a large pipeline.

Comment 11 by trusktr@gmail.com, Jul 15 2017

In my own lib (completely alpha WIP, webgl features are very incomplete and messy), https://github.com/trusktr/infamous, the end user makes a scene graph, and updates that scene graph as necessary. My lib keeps track of which things in the scene graph have been updated, and will recalculate transforms/etc only for the nodes that were modified. But in the end, I still re-render the whole tree of objects in WebGL with one drawArrays call per object/mesh in the scene graph (maybe I can combine into a single drawArrays calls if possible). As far as I know, I have to clear the canvas, then redraw every object, so it is inevitable that with WebGL I have to redraw the whole scene graph, which is effectively like "immediate" mode then. Yet the user uses the scene graph and transform updates are optimized because world transforms are calculated only for modified sub trees, which seems to me like "retained" mode from the user's point of view.

So if SVG is hardware accelerated, then it seems to me that drawing on the GPU (if that is what Chrome's SVG engine does) would be "immediate" mode in the sense I just described because that is how GL works, yet modifying an SVG DOM tree would be "retained" mode from the end user POV.

To me, this would seem to be exactly was fast libraries like Two.js, Pixi.js, and Three.js do. So I still don't see why the SVG DOM model should be so much slower.

Here's an example animating the circles inside a single SVG, not separate SVGs inside DIV elements: https://jsbin.com/bajibubuto/edit?css,js,output

With just 1000 circles, the frame rate drops considerably on my brand new state-of-the-art MacBook Pro.

In contrast, I can do the same thing with a Pixi.js or Two.js Scene Graph much faster. Here's three examples for comparison:

- https://jsbin.com/nohovowupa/edit?html,js,output (Two.js)
- https://jsbin.com/vizuwemibe/1/edit?js,output (Pixi.js)
- https://jsbin.com/xocarayepi/edit?js,output (browser SVG)

The Two.js example is faster than Pixi.js, but both are clearly much much faster than SVG.

Based on this, I don't see why the browser implementation can't use similar techniques.

Comment 12 by trusktr@gmail.com, Jul 15 2017

Oops, I was writing that last one while you posted yours. That's lots of trees.

It seems to me that the SVG renderer only needs to read the "dom tree". 

If reading a single tree and rendering it quickly can be done in Two.js, then it can also be done in CPP. It may be a matter of replacing what is there with new stuff.

Such a change would attract those "native" lovers over.  

¯\_(ツ)_/¯

Comment 13 by trusktr@gmail.com, Jul 15 2017

'attract those "native" lovers over to the web', I mean.

Comment 14 by trusktr@gmail.com, Jul 18 2017

I just realized that JSBin automatically breaks for loops that take too long, and that Two.js renders to SVG by default (not WebGL), so my above comparisons were not accurate.

Here's new comparisons that are not limited by JSBin, using "noprotect" mode, and using WebGL rendering for Two.js. Each example actually renders 2000 circles this time:

- https://jsbin.com/gukutebava/1/edit?js,output (Two.js)
- https://jsbin.com/cocuhivomu/edit?js,output (Pixi.js)
- https://jsbin.com/puzetodira/edit?js,output (SVG)

The SVG example is slowest when animating circle positions.

But the tides change when animating radii of the circles, in which case SVG is faster in the following two examples:

- https://jsbin.com/xicowaheto/1/edit?js,output (Two.js)
- https://jsbin.com/kolejamogo/1/edit?js,output (SVG)

I left out Pixi.js because it was too difficult to modify to add radius animation.

Comment 15 by trusktr@gmail.com, Jul 19 2017

Okay, okay, LOOK AT THIS, here's the same demo with 3000 shrinking/growing/moving circles, running buttery smooth:

- https://codepen.io/osublake/pen/MoMeMg

It is a combination of Pixi.js for animating the positions of each circle texture in WebGL, and the textures are made from... wait for it... 3000 off-screen `<canvas>` elements.

And it is FAST.

Background here: https://github.com/pixijs/pixi.js/issues/4128#issuecomment-316307985

Maybe implementors of native SVG can take away something from this? The speed is nice, much better than Two.js, Pixi.js, or native SVG by them selves. It turns out canvas 2d context + webgl does the trick.

To me, this is an obvious hint that there is now TONs of room for improvement of the SVG rendering.

Comment 16 by trusktr@gmail.com, Jul 19 2017

Sorry, that was not 3000 canvases, it is as many canvases as the largest circle radius, then the colorless circles are colored separately on the GPU.

The question is, considering we don't know what the end user will draw (with SVG), how can we make this optimization?

F.e., a user makes a `<circle>`. We can make a white circle on a single canvas (or whatever native uses internally), then the user applies a fill color, so we can easily "tint" the color on the GPU. The user can specify any number of circles of the same radius, of different color, and we can use a single texture and change it's color on the GPU (as the example).

Then what about gradients? It seems like gradients might be doable on the GPU too. It might take some math, because the texture is on a quad, and we gotta figure out where in the quad we are relative to the circle and the origin of the gradient. I haven't done something like that before, I'm just guessing.

It seems like there might be room for improvement.

Comment 17 by trusktr@gmail.com, Jul 19 2017

In the example,

https://jsbin.com/zasihuzupu/edit?js,output

we know:

- there are 2000 circles.
- They are all the same color.
- Many of them have the same radius and same stroke.

Maybe there's a way to assume that the circles with the same color, radius, and stroke can at lease be composited from the same texture.

In this example,

https://jsbin.com/coxurijenu/1/edit?js,output

we know that there are 2000 of the same exact circle, so at the very least a single texture can be composited 2000 times and quickly animated on the GPU.

True, at any moment, the user's code might cause a circle to change color, change radius, have a gradient, etc, but maybe there's a way we can detect the simpler cases, then move to the more complicated cases. How likely is it that when an SVG document contains 2000 of the same exact thing, that they will suddenly change and all be completely different? Maybe based on some statistic we can safely assume that most items remains the same?

It does seem tricky to guess what the user will do next, but at the very least I think SVG has some room for improvement.

Here's some ideas:

- A <circle> element was defined and added to DOM by a user, having only cx, cy, and r.
- Browser creates a texture for the circle, storing the circle's drawing (draw it the fastest way possible too).
- If at any point only color or cx,cy changes, then that can be super fast, on the GPU (already proven with above examples).
- If radius becomes smaller, maybe it can be downsampled on the GPU? Otherwise, a new texture can be drawn. Either way, cache this texture.
- If any more circles are created, and are the same complexity (f.e. using only cx, cy, r, and solid fill color), then we at least know there's a chance we can re-use an existing texture. Most applications have some limit on the size of their shapes. In many applications (UIs, Games) there are many shapes of similar size.

This takes more memory, but I'd honestly I'd like to use more memory if it means 60fps, then revert only to the less-memory approach as needed when there is too much happening.

To help make optimizations easier for the browser, maybe there can be some standard way for SVG users to specify what will change, therefore making it easy for the browser to prepare optimizations.

There already exists the `will-change` attribute, but it seems to apply only to basic CSS properties, not SVG properties. Maybe there can be something standardized for SVG elements?

The `shape-rendering` attribute has no effect. Here's an example using the `optimizeSpeed` value, but it is still the same slowness:

https://jsbin.com/zusabitoni/1/edit?js,output

Maybe there can be something similar to `will-change` (or update `will-change` so it can accept SVG-specific values), for example:

```html
<svg>
  <circle cx="0" cy="0" r="50" fill="blue" will-change="cx cy"></circle>
  <circle cx="0" cy="0" r="50" fill="blue" will-change="cx cy"></circle>
  <circle cx="0" cy="0" r="50" fill="blue" will-change="cx cy"></circle>
  ... 2000 more circles ...
</svg>
```

and then the browser can know that the user intends to animate only cx and cy, so we can prepare for that by setting up a texture that will be animated on the GPU, assuming the shape, fill, stroke, etc, won't be modified.

A designer using Illustrator or Inkscape could even design such SVGs with animations, and specify `will-change`, so the browser knows how to optimize.

Not everything can be optimized easily, f.e. modifying every attribute possible, animating gradients, etc. But at the very least there are some simple cases that should perform better. For example, animating 2000 circular nodes in a force directed dataviz graph.

Comment 18 by f...@opera.com, Jul 19 2017

First off, thanks for showing interest in this issue! However, I think you may be jumping to conclusions a bit: You seem to be under the impression that painting ("rasterization" [1]) is the bottleneck. I would posit that that is not the case! [2] I would encourage you to record a timeline (the "Performance" tab nowadays) in DevTools and look at how the time is divided between the various parts of the rendering pipeline.
As for special-casing (fast-paths), it is done but it also comes at cost - both in terms of maintenance and increased complexity (something we have quite a bit of already...) Adding low-level geometry fast-path all the way up in Blink is not really practical - Skia is the right place for that (and as described in [2] it already knows a bunch of tricks in that area.) Also, knowing that geometry will change ("will-change: cx") is unlikely to yield anything solid. More on this below.

So why the slowness? Well, as it happens, all of the properties ('cx', 'cy' and 'r') are actually not "SVG properties" but CSS properties! This means we are at the mercy of a large part of the CSS/style machinery when it comes to the actual update of the geometry. I'm convinced there are some ways to improve cases like this, but it's a delicate path to walk to avoid adding complexity and undue maintenance burden (or breaking stuff for that matter.) Playing well with all the bits that make up (the rendering pipeline of) a browser engine is no cakewalk I can assure you...
In your particular example I'd also expect that the setAttribute() overhead will add up (the call itself as well as the number->string conversion brought on by the fact that 'value' needs to be a DOMString) - feel free to play with the SVG DOM properties though to see if they make a difference: <property>.baseVal.valueInSpecifiedUnits is probably the best option there (where <property> is one of cx, cy or r.)

If we'd be in the happy place of actually being bottlenecked on rasterization, then we'd probably start hitting other unpleasantries, such as that browsers tend to be optimized for scrolling - an operation that is rarely useful for your average SVG, but that indirectly will add additional slowdown to the rasterization step (multiple tiles => multiple render target switches.)

[1] ...as the operation producing actual pixels is usually referred to within Blink and Chromium.
[2] FWIW, Skia - the graphics library we use - has a range of options when it comes to rasterization of circle shapes - SDF, "implicit geometry", tessellation, raster-and-upload and of course regular software rendering.

Sign in to add a comment