Oni: Improve scroll performance

Created on 12 Dec 2016  路  9Comments  路  Source: onivim/oni

The incremental scroll performance is poor (& jarring) - you get a sparkler-like effect due to the incremental rendering strategy.

scroll-perf

bug help wanted performance

All 9 comments

Where exactly do you think the culprit is? I'd be happy to take a poke around

I've done a few investigations here. One helpful thing to do before testing out the perf here is to max out the maxCellsToRender setting in Config.ts:

    "prototype.editor.maxCellsToRender": Math.max,

The canvas is drawn ~60 FPS (each tick of requestAnimationFrame), and this variable controls how many cells to re-draw each frame. This is why we get the 'sparkler' effect - only a subset of the cells are drawn. Setting this to max makes it easier to see the issues come up in the profiler.

This setting was added as a hack basically because drawing the cells is pretty expensive. We redraw cells aggressively in CanvasRenderer.ts. The only optimization at the moment is to cache individual letters as images, because it turns out the fillText' method on canvas is actually more expensive thandrawImage` - at least on the machines I've tested.

A good investigation around this is here:
http://simonsarris.com/blog/322-canvas-drawtext-considered-harmful

The other optimization that has been implemented is to only keep track of cells that have changed, which is what deltaRegionTracker.getModifiedCells() gives us. However, it turns out that when scrolling the whole buffer, pretty much everything changes on the screen, so the delta tracking doesn't help much there :)

In terms of next steps for optimizations:
1) It'd be great to get a fresh profile of the bottlenecks when drawing - the profiler in the Electron debug tool is very helpful for this
2) If the drawText/drawImage is still the bottleneck, we can potentially do more aggressive caching - we could cache entire words instead of just letters. We'd need to make sure to have a fixed size for this cache as it could potentially grow in memory, but this would likely speed up rendering significantly, because it turns out code/text tends to have a lot of repeated words.
3) If the clearRect is the bottleneck, we can potentially coalesce those calls and clear a larger rectangle.

There's also some other tools the canvas gives us that might help - we can use the getImageData to cache a chunk of the screen and scroll it just by rendering that in a different place.

In addition, we could ditch the canvas altogether and test out a DOM based approach to rendering, too.

Lots of options to explore. Let me know if you have questions about any of those ideas or need help getting started

Some ideas:

  • when we scroll down, we know for fact that the text that is moving upward is not changing, the only real new stuff to render is the stuff that comes in at the bottom. If we render stuff at the bottom one new line at a time, that should perform almost the same speed at just moving the cursorline, plus the overhead of moving the whole upper portion upward while clipping it at the top.
  • Another high-level idea is to make the actual buffer height larger than the viewport, and using the default config, you can make things like H and L move to the top and bottom of the visible viewport rather than the top and bottom of the buffer. Then, you can render new chunks of the buffer only when the end of the buffer gets near the end of the viewport. This would allow the implementation of smooth inertial scrolling with mouse or touch, as well as tweened transitions (with easing curves) when scrolling line by line with the keyboard at the edge of the view port.

    • To further explain (and lacking visual diagrams while on mobile), imagine the buffer is always 40 lines taller than the viewport, and 40 columns wider than the viewport. By default, when the user isn't scrolling, the viewport is centered in the buffer (except on edge cases when there is no buffer left and the viewport is fully scrolled to the edge). For this example, imagine we have 20 lines or columns of buffer beyond each edge of the viewport. When the user begins to scroll down, the renderer simply transforms the entire buffer texture with translation along the Y axis, and the only thing it needs to re-render inside the buffer is the cursorline update. When the bottom edge of the buffer gets close to the bottom edge of the viewport, then neovim is scrolled down (text moves up) 20 lines at once (f.e. 20<c-e>). To maintain the effect of the cursor still being at the bottom edge of the viewport, the buffer is translated instantly 20 lines downward and the cursorline will be exactly where it was before. The end result of this is that now there is a new 20-line area below the viewport that can be scrolled into view smoothly. The process is repeated every time the bottom edge of the buffer gets close to the bottom edge of the viewport. This would mean that a new chunk of text only needs to be rendered every so often, and most of the time only the current line-by-line rendering would happen. The same concept applies around all four edges of the viewport and buffer, for each direction of scrolling. The actual size of the offscreen area may be bigger than 20 lines or columns, may not be the same number of lines as columns, and we would need to experiment to find good values.

Thanks for the suggestions, @trusktr !

I've actually looked through both these options. Unfortunately, at least with the current CanvasRenderer implementation, even though only the new stuff is the line at the bottom, we still need to re-render all the above rows, because they've all changed (it's not like the terminal where we'd get that shift for free in some cases).

The buffer height being larger would be really nice, inertial scrolling would be awesome to have. The problem here is that with the current Neovim msgpack-RPC interface (in lieue of some of the enhancements in PR), we're not only rendering the buffer, but also the status lines and other pieces of UI. So if we artificially inflated the viewport height, we'd actually be rendering the statusline offscreen. In addition, horizontal splits would be broken too, along with various cursor motions and movements. You could try and workaround each of these problems, but it would end up being pretty complicated. There's a PR in progress in Neovim to decouple window rendering, which potentially could help us to remove some of those limitations.

PR #140 helps a lot with this - the DOM rendering implementation is actually much faster than the canvas, so we no longer need the incremental rendering. This removes the 'sparkler' effect.

Scrolling can still be made faster, by adjusting the elements in the scroll region when the scroll action comes in.

Not sure what you're doing for the react stuff, but if you key lines by line number, then when scrolling, React dom diff will only remove the line at the top and add a new line at the bottom, without touching the DOM in the middle (it will re-use the DOM in the middle without making new instances of either DOM objects or React Components), so scrolling might be really fast.

The only slower parts would be (for example) deleteing a line or inserting new lines, because then all the lines below the modified part of the screen would be re-keyed. React will be smart enough to actually re-use the DOM still though, it's gotten really smart over the years. For example, after deleting a line (while lines are keyed), React will keep the DOM elements that were below the deleted line, but it will replace their content (new string characters are placed into the re-used elements).

It seems much faster now though anyways!! Haven't tried on a vertical monitor yet, I'll try when I get to one...

I think long-term, the best approach here, is to see if we can decouple from some of the constraints we have in Neovim (#405) tracks this - we could get better performance _and_ smooth-scrolling.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

bryphe picture bryphe  路  3Comments

IvRRimum picture IvRRimum  路  3Comments

badosu picture badosu  路  3Comments

bfulop picture bfulop  路  3Comments

rgehan picture rgehan  路  3Comments