Render lines via tiles #6733
Conversation
as-cii
added
some commits
May 5, 2015
as-cii
commented on an outdated diff
May 11, 2015
|
|
nathansobo
commented on an outdated diff
May 11, 2015
| @@ -963,7 +963,7 @@ class TextEditorPresenter | ||
| hasPixelPositionRequirements: -> | ||
| @lineHeight? and @baseCharacterWidth? | ||
| - pixelPositionForScreenPosition: (screenPosition, clip=true) -> | ||
| + pixelPositionForScreenPosition: (screenPosition, clip=true, {absolute}={}) -> |
nathansobo
Contributor
|
nathansobo
and 1 other
commented on an outdated diff
May 11, 2015
| - if @state.content.lines.hasOwnProperty(line.id) | ||
| - @updateLineState(row, line) | ||
| + linesPerTile = Math.floor(@height / @lineHeight / @tileCount) | ||
| + linesPerTile = Math.max(1, linesPerTile) | ||
| + | ||
| + startIndex = Math.max( | ||
| + 0, Math.floor(@startRow / linesPerTile) - @tileOverdrawMargin | ||
| + ) | ||
| + endIndex = Math.min( | ||
| + Math.ceil(@model.getScreenLineCount() / linesPerTile), | ||
| + Math.ceil(@endRow / linesPerTile) + @tileOverdrawMargin | ||
| + ) | ||
| + | ||
| + visibleTiles = {} | ||
| + for index in [startIndex...endIndex] | ||
| + presenter = @linesPresentersByTileIndex[index] ?= new LinesPresenter(@) |
nathansobo
Contributor
|
|
I've been talking about wanting to do tiles for a year now. Thanks so much for pushing this forward! |
|
Wow, looks like a huge speedup |
Zireael07
commented
May 12, 2015
|
Always good to see speedups! |
As explained on this commit I got rid of the To make things more consistent we have two options:
I see the latter as the most natural approach but I may be overlooking/underestimating something. @nathansobo: what do you think? |
|
@as-cii I think it's fine to position tiles both vertically and horizontally. If we're already positioning every tile on the GPU vertically, doing so horizontally doesn't seem like a huge stretch. I get that it isn't strictly needed because we aren't tiling horizontally, but I also think it's the most consistent approach. We move from a world where the entire content plane was moved to a world in which the content is fragmented into tiles, each which move. One thing to watch out for is that this has implications for overlay decorations as well. They are currently absolutely positioned relative to the big lines layer, and so move automatically when it moves. Now they will need to be explicitly moved along with the tiles when they scroll. Treating both horizontal and vertical dimensions the same way just seems the simplest to me. We don't need to conceptualize the different dimensions differently. |
as-cii
added
some commits
May 13, 2015
as-cii
and 1 other
commented on an outdated diff
May 13, 2015
| - expect(lineStateForScreenRow(presenter, 9)).toBeUndefined() | ||
| - | ||
| - it "does not overdraw above the first row", -> |
as-cii
Owner
|
|
I lost your comment about breaking the presenter up into separate objects. I'm somewhat on board, at least with the idea of breaking the presenter up into objects that persist for the lifetime of the presenter. I'm less excited about objects that will come into and out of existence because that puts pressure on the GC. And whatever we do, I guess I want to make it well-encapsulated. Are there aspects of the problem that could be cleanly isolated? Right now, a single model update can update many aspects of the presenter state, which allows us to keep the view code simple. If we break things up into objects but the objects aren't cleanly encapsulated, I think we've made things worse. |
as-cii
added
some commits
May 14, 2015
|
I have rebased this branch against master and fixed Production code, on the other hand, is ready for review: there may still be a few things to refine but I feel quite good about it. I have tested it over the last days and it doesn't seem to break anything. Your mileage may vary, though, so if anyone wants to try this out I'll be happy to hear your feedback (in return you should notice some performance boosts, especially while scrolling). wrap-guideAfter "virtualizing" vertical and horizontal scrolling, I have noticed that wrap-guide sticks in a fixed place on screen, even when scrolling horizontally. This actually makes sense, because wrap-guide is declared like this: We should change it to update its position every time the editor scrolls using
Thank you! |
YurySolovyov
commented
May 18, 2015
|
Works amazing even on:
I can even turn on minmap animation flag! |
as-cii
commented on the diff
May 19, 2015
| @@ -19,11 +20,7 @@ class LinesComponent | ||
| placeholderTextDiv: null | ||
| constructor: ({@presenter, @hostElement, @useShadowDOM, visible}) -> |
as-cii
Owner
|
nathansobo
commented on the diff
May 19, 2015
| @@ -1893,20 +1848,18 @@ describe "TextEditorPresenter", -> | ||
| getLineNumberGutterState(presenter).content.lineNumbers[key] | ||
| - it "contains states for line numbers that are visible on screen, plus and minus the overdraw margin", -> | ||
| + it "contains states for line numbers that are visible on screen", -> |
nathansobo
Contributor
|
|
I'm super stoked about the apparent improvements to the Chrome compositor since I experimented with tiled rendering last year. When I tried it, leaving the tiles with transparent backgrounds was absolutely fatal to performance, so I had to give them opaque backgrounds and break selection rendering across tiles as well. I'd be curious to see how much your compositing time drops if you give the tiles opaque backgrounds. Also, Chrome is supposed to render text with subpixel anti-aliasing if it's rendered on an opaque background and translated by an integral amount. It's never done that, but perhaps there's hope it will someday work. If we leave the tile backgrounds transparent, however, it will never be possible because the text is already rasterized by the time it gets to the GPU, meaning the information required to do subpixel anti-aliasing is lost. So it's a toss up. Now that composite performance is <= 1ms for transparent tiles, it's definitely simpler to leave them that way and let the wrap guide and highlight decorations show through from underneath. But compositing may be even cheaper with opaque tiles, and with opaque tiles we theoretically have a possibility of getting subpixel AA someday. |
|
Also, what was your experience with larger tile sizes? I notice they're only 2 lines tall. The gutter is indeed doing a full repaint on every scroll now, but it's still pretty fast. But I bet you you could shave a bit more time if you could avoid that. It takes a little over .2ms for the full gutter repaint. |
|
@nathansobo: thanks for the extensive feedback!
Assigning an opaque background doesn't impact layers compositing performance:
That's very interesting and I didn't consider that: I've always assumed that the
@nathansobo: that said, I trust your judgement on this and I am totally fine if
I observed that having smaller tiles doesn't have an impact on performance and,
I couldn't spot any difference with how the gutter is painted on master: am When I first opened it, my intention was to simply let this bake for a while |
nathansobo
commented on an outdated diff
May 20, 2015
| + innerHTML += token.getValueAsHtml({hasIndentGuide}) | ||
| + | ||
| + innerHTML += @popScope(scopeStack) while scopeStack.length > 0 | ||
| + innerHTML += @buildEndOfLineHTML(id) | ||
| + innerHTML | ||
| + | ||
| + buildEndOfLineHTML: (id) -> | ||
| + {endOfLineInvisibles} = @newTileState.lines[id] | ||
| + | ||
| + html = '' | ||
| + if endOfLineInvisibles? | ||
| + for invisible in endOfLineInvisibles | ||
| + html += "<span class='invisible-character'>#{invisible}</span>" | ||
| + html | ||
| + | ||
| + updateScopeStack: (scopeStack, desiredScopeDescriptor) -> |
nathansobo
Contributor
|
|
I feel like I was seeing more than 6 tiles in my test. I'm curious about whether it's better to specify tile count or tile size, then just pay for more tiles if the window is taller. Not sure what the more important metric is. As for the opaque background, thanks for exploring that. I agree that leaving it transparent for now is way easier and probably the right move to limit ripples. I think addressing the gutter can be done in a separate PR. So basically this is looking good as soon as the tests are passing and we test it out for a bit. Nice work as usual @as-cii. The rendering layer and Atom as a whole are much better thanks to you. |
as-cii
added
some commits
May 20, 2015
as-cii
and 1 other
commented on an outdated diff
May 21, 2015
| @@ -68,48 +68,111 @@ describe "TextEditorComponent", -> | ||
| expect(nextAnimationFrame).not.toThrow() | ||
| describe "line rendering", -> | ||
| - it "renders the currently-visible lines plus the overdraw margin", -> | ||
| - wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px' | ||
| + expectLineRender = (tileNode, {screenRow, top}) -> |
as-cii
Owner
|
as-cii
commented on the diff
May 21, 2015
| +{$$} = require 'space-pen' | ||
| + | ||
| +TokenIterator = require './token-iterator' | ||
| +DummyLineNode = $$(-> @div className: 'line', style: 'position: absolute; visibility: hidden;', => @span 'x')[0] | ||
| +AcceptFilter = {acceptNode: -> NodeFilter.FILTER_ACCEPT} | ||
| +WrapperDiv = document.createElement('div') | ||
| +TokenTextEscapeRegex = /[&"'<>]/g | ||
| +MaxTokenLength = 20000 | ||
| + | ||
| +cloneObject = (object) -> | ||
| + clone = {} | ||
| + clone[key] = value for key, value of object | ||
| + clone | ||
| + | ||
| +module.exports = | ||
| +class TileComponent |
as-cii
Owner
|
I ended up managing
I wrote some new specs and fixed all the failing ones as well. I think this is now ready for a test drive! |
nathansobo
commented on an outdated diff
May 22, 2015
| @@ -68,48 +68,111 @@ describe "TextEditorComponent", -> | ||
| expect(nextAnimationFrame).not.toThrow() | ||
| describe "line rendering", -> | ||
| - it "renders the currently-visible lines plus the overdraw margin", -> | ||
| - wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px' | ||
| + expectLineRender = (tileNode, {screenRow, top}) -> | ||
| + lineNode = tileNode.querySelector("[data-screen-row='#{screenRow}']") | ||
| + tokenizedLine = editor.tokenizedLineForScreenRow(screenRow) | ||
| + | ||
| + expect(lineNode.offsetTop).toBe(top) | ||
| + if tokenizedLine.text is "" | ||
| + expect(lineNode.innerHTML).toBe(" ") | ||
| + else | ||
| + expect(lineNode.textContent).toBe(tokenizedLine.text) | ||
| + | ||
| + it "renders the currently-visible lines into a tiled fashion", -> |
|
|
nathansobo and others
added
some commits
May 22, 2015
|
So far so good after about a day. |
|
Hows the |
It works fine because Chromium's GPU compositing is fast now regardless of the opacity of the tiles, so they still have transparent backgrounds and not much has to change in that respect. Major win. |
|
Oh sweet. I was just being a jerk, but that's great. |
as-cii
added a commit
to atom/wrap-guide
that referenced
this pull request
May 23, 2015
|
|
as-cii |
5f8fb0c
|
as-cii
referenced
this pull request
in atom/wrap-guide
May 23, 2015
Merged
Relativize wrap guide position #31
|
There's actually a subtle issue which affects the Basically, the issue relates to how we deal with lines positioning and it's something I had discussed already in #6733 (comment): not sure if we should change how I guess we should leave the world as it is for now, but I thought it might have been good to point these things out. What do you think? |
|
@as-cii I'd like to merge this as soon as we have a backward-compatible fix for the wrap-guide ready to go. |
|
@as-cii How would you feel about limiting this to a property on |
|
@nathansobo: I have narrowed the scope of the flag and renamed it as well. Please, have one last look at this as well as at atom/wrap-guide#31. Thank you! |
|
Plan to merge this after the next release. |
|
Hey @as-cii, I found a regression. When saving strips whitespace at the end of the file and forces a scroll, the cursor's position isn't being updated correctly on screen until the next cursor movement. |
nathansobo
referenced
this pull request
May 28, 2015
Closed
WIP: Render editor content via a tiling strategy #3154
|
@nathansobo: thanks for spotting the issue! I have fixed it in 9b4d62b: before that commit, when a change in the model caused My initial thought was to fix this |
|
Nice. |
|
@atom/feedback Can anyone test this on Windows and Linux (without a VM)? I want to make sure there are no performance regressions on those platforms. It seems unlikely, but there may be difference in the GPU compositing pipeline and I just want to make sure before putting this on master. |
YurySolovyov
commented
May 30, 2015
|
Tested here #6733 (comment), on Ubuntu 14.04, was great! |
kevinsawicki
commented on the diff
Jun 1, 2015
| @@ -343,59 +114,18 @@ class LinesComponent | ||
| measureCharactersInNewLines: -> | ||
| @presenter.batchCharacterMeasurement => | ||
| - for id, lineState of @oldState.lines | ||
| - unless @measuredLines.has(id) | ||
| - lineNode = @lineNodesByLineId[id] | ||
| - @measureCharactersInLine(id, lineState, lineNode) | ||
| - return |
kevinsawicki
Owner
|
|
Tested on Windows and it's working correctly. |
nathansobo
merged commit dac39bd into master Jun 2, 2015
nathansobo
deleted the
as-tiled-rendering branch
Jun 2, 2015
|
Thanks very much @as-cii. People are really going to be happy to see scroll speed improvements. |
alexandernst
commented
Jun 2, 2015
|
Let's see if this helps with my #6981 |
v3ss0n
commented
Jun 3, 2015
|
@nathansobo @as-cii i am testing on linux 64bit. When scrolled to bottom part , it freeze up , taking 100% CPU and seems to be generating tiles, for 20-40 secs? Is it possible to move tiles generation to a worker process? I have tested with 205 and it rendered fast and fine. |
|
Hi @v3ss0n, thank you for your feedback! Could you please attach somewhere the file you're talking about? If it's private and you don't want to share it with everyone, it'd be awesome if you could share it via e-mail (you can find my address on my profile, https://github.com/as-cii). Thanks a lot! |
v3ss0n
commented
Jun 3, 2015
|
@as-cii thanks a lot for fast response. I restarted atom and reopened, first it stop responding while atom start , and loading the file , so i start from scratch (previously auto reload have a lot of tabs and split panes opened ). Now it rendering fine . I tested without --safe. So only problem remain is when previous session restored , and large files are opened previously atom stop responding and has to force close (can reproduce with --safe ) Rendering is smooth and fine , i copy / paste that file 10 times , making 11mb , and it dosen't slow down scrolling at all! Thanks a lot for hard work , i will upload the file i testing to gist in a bit. |
maxbrunsfeld
referenced
this pull request
Jun 3, 2015
Closed
Find remaining performance bottlenecks for large files #6692
v3ss0n
commented
Jun 3, 2015
|
Here are things i've tested so far: ###Works
##Fails
|
This was referenced Jun 4, 2015
alexandernst
commented
Jun 9, 2015
|
This looks very interesting nodejs/io.js#1159 |
|
@alexandernst That looks super interesting. Thanks for the heads up. |
ilanbiala
commented
Jun 10, 2015
|
@nathansobo does Atom already use io.js or is it still on Node? |
|
@ilanbiala Atom uses io.js – Electron uses it. |
|
@ilanbiala Yep. It could take a while for this feature to get to us however since it's not even merged to master in iojs. Then it has to be released in an official iojs release, integrated into Electron, and finally Atom has to upgrade to that version of Electron. But definitely worth keeping an eye on. It's something we could definitely make lots use of once it's available. |
v3ss0n
commented
Jun 10, 2015
|
This may be a bit offtopic but curious. |
|
@v3ss0n From what I understand atom has a packaged version. The version you have is only used to bootstrap the process. |
YurySolovyov
commented
Jun 10, 2015
|
@v3ss0n it is currently io v1.6.3 and being updated to io v2.2.1 |
v3ss0n
commented
Jun 10, 2015
|
ah i got it , it have packaged own IO.js separated from OS version ? |
v3ss0n
commented
Jun 11, 2015
|
We have a problem with tile rendering , effecting themes with background transparency : https://github.com/v3ss0n/steam-pirate-ui from styling side , should i theme |
olmokramer
commented
Jun 21, 2015
|
Hey, awesome work on the tile rendering! I've got one problem with it, however, as it breaks my block-cursor package. To have a block-cursor, I used to add a (opaque) background color to the cursor element, and set its Now, with the new tile rendering, all the lines are inside a new element with an opaque background, so I can't render the cursor behind the lines anymore (or it would also be behind the tile, and thus invisible anyway)... There's the option to set a very low alpha value on the cursor color, and render it in front of the text, but that way the cursor changes the text color, and it's tricky to find a value that produces a nice color and keeps text legible. |
v3ss0n
commented
Jun 21, 2015
|
it seems we have no choice but to embrace it ? The benefits outweighs a few styling inflexibility , but i miss my beautiful theme tho . |
olmokramer
commented
Jun 21, 2015
|
Hmmm, I just thought that highlights might suffer from the same issue, but then I found that a highlights container is added to each tile. I wonder if this is also possible for the cursors, or would it be too much of a performance issue? Or just too much work for too little gain? |
v3ss0n
commented
Jun 21, 2015
|
Highlights? the Highlight selected package ? or Editor's highlight? I am gonna give a try. I was too busy these days and haven't hack atom much last 2-3 weeks. |
Given that tiles have an opaque background, I am afraid this is the only possible solution. Moreover, that would be consistent with how we handle highlights, as you’re pointing out. I kinda overlooked it because Atom’s default cursor works just fine as it stands in front of the tiles; the implementation will definitely bring some complexity but, ultimately, I think it’s the right way of doing it and, after all, it won’t mess things up that much. @olmokramer: that said, what do you think about opening a brand new issue where we can keep track of this? I plan to work on it, but I’ll be traveling over the next days and I think I’ll be able to tackle it starting from the next month.
@v3ss0n: highlights are an abstraction useful to decorate markers; you can have a look at the related blog post for all the nitty-gritty details. |
v3ss0n
commented
Jun 22, 2015
|
Thanks , interesting , i will look into it. |
olmokramer
commented
Jun 22, 2015
|
@as-cii Thanks! That's truly awesome :) I'll submit a new issue in the evening. |



as-cii commentedMay 11, 2015
This PR introduces a change in how we render lines. It does so by organizing them into tiles, which we then move up and down through a compositing transformation. The main advantage is that paint times dramatically reduce, because now we simply need to paint a small tile, instead of all the lines. This is especially noticeable during scrolling which, as a result, will be the subject of the benchmarks I am going to show later on.
Please, note that:
TilesPresenterandLinesPresenterand they're probably going to change. Any suggestion or improvement is very welcome here.Benchmarks
Tests conducted using:
A
1920x1080windoweditor.scrollSensitivity = 130A custom code (associated to a keyboard key) to simulate
mouseWheeleventsKeyboard Key Repeat:
Fast(Mac OS X setting)Delay Until Repeat:
Short(Mac OS X setting)I needed a (more or less) repeatable way to benchmark scrolling. To try this out, you can simply scroll up and down as you would normally do.
master
as-tiled-rendering
Why this matters?
As you can notice, paint times are dramatically reduced. This may not be noticeable with your👀 , but it's nevertheless important 'cause it leaves some room for additional operations (such as a more expensive character measurement computation). As said earlier, things are going to speed up further once we introduce tiling for line numbers as well.
It would be great if you could try this out, reporting any eventual regression or glitch that I may have overlooked while implementing the tiling strategy. Moreover, any code review or additional👀 are always welcome.
Thank you!
/cc: @atom/feedback @nathansobo