Render lines via tiles #6733

Merged
merged 68 commits into from Jun 2, 2015

Conversation

Projects
None yet
Owner

as-cii commented May 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:

  • Specs are broken. The solution you can see here was achieved by spiking a bunch of ideas, and therefore I didn't pay attention to change specs/code gradually. As a result, I plan to adapt previous specs and write some new ones as soon as possible.
  • There's some evident structural duplication in code. However, I'd like to start refactoring that stuff as soon as I have unit tests to prevent naive errors.
  • Naming-wise I am not entirely happy with TilesPresenter and LinesPresenter and they're probably going to change. Any suggestion or improvement is very welcome here.
  • I have intentionally left out line numbers from using this new approach. I'd like to keep this PR focused and work on that right after 🚢ping this one.

🐎 And now some benchmarks! 🐎

Benchmarks

Tests conducted using:

  • A 1920x1080 window

  • editor.scrollSensitivity = 130

  • A custom code (associated to a keyboard key) to simulate mouseWheel events

    document.querySelector("html /deep/ .editor-contents--private").dispatchEvent(new WheelEvent("mousewheel", wheelDeltaY: -250))
  • Keyboard 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

master

as-tiled-rendering

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

as-cii added some commits May 5, 2015

@as-cii as-cii wip 55a7508
@as-cii as-cii 🔥 Delete line handling from TextEditorPresenter ee85abc
@as-cii as-cii wip 3d3d5d0
@as-cii as-cii wip 3e78fe3
@as-cii as-cii wip 414d5d1
@as-cii as-cii Fix char measurement de53e1d
@as-cii as-cii Draw highlight components appropriately 40cde49
@as-cii as-cii Always subtract @scrollTop 936133c
@as-cii as-cii Fix clicking on line 3a1bb89
@as-cii as-cii Better defaults 9aaa8b4
@as-cii as-cii Keep scrolling tile around for mousewheel 216b757
@as-cii as-cii wip daa4b33
@as-cii as-cii Recycle tiles 02838ad
@as-cii as-cii Hide scrolling tile c99ffa0
@as-cii as-cii Merge branch 'master' into as-tiled-rendering 9581202
@as-cii as-cii Scroll exactly as we did before tiling
Except that now we store the scrolling tile, instead of the scrolling
row.
a6e0fa6
@as-cii as-cii Ensure we have at least one line per tile
This will prevent, for example, a mini editor (which has no `@height`
by default) from not showing characters.
8aaac87
@as-cii as-cii Position overlays absolutely
We did so by introducing an `absolute` (optional) parameter in
`pixelPositionForScreenPosition`.
a6dab48

@as-cii as-cii commented on an outdated diff May 11, 2015

src/lines-presenter.coffee
@@ -0,0 +1,41 @@
+module.exports =
+class LinesPresenter
@as-cii

as-cii May 11, 2015

Owner

This should really be TilePresenter.

Contributor

maxbrunsfeld commented May 11, 2015

Really exciting work.

@nathansobo nathansobo commented on an outdated diff May 11, 2015

src/text-editor-presenter.coffee
@@ -963,7 +963,7 @@ class TextEditorPresenter
hasPixelPositionRequirements: ->
@lineHeight? and @baseCharacterWidth?
- pixelPositionForScreenPosition: (screenPosition, clip=true) ->
+ pixelPositionForScreenPosition: (screenPosition, clip=true, {absolute}={}) ->
@nathansobo

nathansobo May 11, 2015

Contributor

Talk to me a little about this absolute flag... seems like you should always just choose the position relative to the viewport now since the lines layer is no longer moving. Either way, I think we should avoid allocating the options object on this hot path. I'm paranoid about garbage objects these days.

@nathansobo nathansobo and 1 other commented on an outdated diff May 11, 2015

src/text-editor-presenter.coffee
- 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

nathansobo May 11, 2015

Contributor

Is it too nasty to just build out the tile in a subroutine of the current presenter?

@as-cii

as-cii May 13, 2015

Owner

I have inlined it for the time being 🔥

The main idea with having a separate object, though, was to push the design of presenters/components in another direction. This object, as it is right now, tends to couple different behaviors in the same place and I can feel a lot of cognitive overhead every time I add new features or fix 🐛s (e.g. when I change how lines construction behave I have to think about the details of how cursors work, and vice versa).

💡 By separating each responsibility, I think we could be able to understand each presenter at a glance and, as a result, feel more confident to change it.

Probably I have been a bit too abstract here, but I'd love to hear your opinion about moving towards more cohesive presenters in the future 💭

Contributor

nathansobo commented May 11, 2015

I've been talking about wanting to do tiles for a year now. Thanks so much for pushing this forward!

Contributor

benogle commented May 11, 2015

Wow, looks like a huge speedup

Always good to see speedups!

@as-cii as-cii Relativize `pixelPositionForScreenPosition`.`top`
This commit makes the method return a `top` value which is relative to the
viewport. However, we still need to return an absolute value for `left` because
lines are moved horizontally through a single transform on their parent node.

I would prefer being consistent (i.e. make everything relative), but moving
every single tile to simulate scrolling, on the other hand, seems overkill and
it's not really intuitive given that we are not tiling horizontally.

/cc: @nathansobo
19a9565
Owner

as-cii commented on 19a9565 May 13, 2015

Talk to me a little about this absolute flag... seems like you should always just choose the position relative to the viewport now since the lines layer is no longer moving.

As explained on this commit I got rid of the absolute flag, but I am not happy with the inconsistent result of @pixelPositionForScreenPosition: top returns a relative value, whereas left is still computed as an absolute value. That's because as of now, in order to scroll horizontally, we simply apply an horizontal transform on the container node.

To make things more consistent we have two options:

  • Simulate horizontal scrolling by moving each tile as we do for vertical scrolling. I have experimented with this and I don't find this to be particularly intuitive, as there is no real reason for moving tiles one by one for such scenario.
  • Put lines in a separate container. The way in which we position lines shouldn't affect how other components are rendered.

I see the latter as the most natural approach but I may be overlooking/underestimating something. @nathansobo: what do you think?

Contributor

nathansobo replied May 13, 2015

@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 as-cii Merge branch 'master' into as-tiled-rendering
cde632c
@as-cii as-cii 🎨 Inline LinesPresenter 63e2764
@as-cii as-cii Remove require to LinesPresenter 74a0ce0
@as-cii as-cii 🎨 Clarify `@updateTilesState` 4c2683a
@as-cii as-cii Remember to fix scrolling for line-numbers 948b8f9
@as-cii as-cii Make `tileSize` a field rather than a fn 94373e4
@as-cii as-cii updateTileSize on start 11a0fa7
@as-cii as-cii 💚 Start fixing presenter specs 29c6e9d
@as-cii as-cii 💚 Finish fixing tiles/lines specs 49c4823
@as-cii as-cii 🔥 Remove leftover code 6d38d1c
@as-cii as-cii 🎨 Simplify conditional 467f4a3
@as-cii as-cii 🎨 `tileId` -> `tileRow`
25acaf2

@as-cii as-cii and 1 other commented on an outdated diff May 13, 2015

spec/text-editor-presenter-spec.coffee
- expect(lineStateForScreenRow(presenter, 9)).toBeUndefined()
-
- it "does not overdraw above the first row", ->
@as-cii

as-cii May 13, 2015

Owner

@nathansobo: is line-overdrawing strictly a performance "feature"? Overdrawing tiles doesn't seem to provide any significant speedup and, therefore, I simply got rid of them. What do you think?

@nathansobo

nathansobo May 13, 2015

Contributor

You're correct to drop it with tiling. It used to be required to prevent a full screen repaint when adding/removing lines at the top/bottom. If the repainted regions were too close together they would be grouped. This approach bypasses all such nonsense.

Contributor

nathansobo commented May 13, 2015

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

@as-cii as-cii Merge branch 'master' into as-tiled-rendering c39b1d2
@as-cii as-cii Scroll every single tile left/right 6be88fd
@as-cii as-cii Add `@scrollLeft` when calculating `@contentWidth`
c4dff15
@as-cii as-cii 💚 Fix position specs 5b23a00
@as-cii as-cii 💚 Fix all TextEditorPresenter specs
e7ddb3d
@as-cii as-cii Reimplement `lineNodeForScreenRow`
723bf5a
@as-cii as-cii 🔥 Get rid of unused `tileId` property 26b7c5e
@as-cii as-cii Store tile state in a instance variable 265090e
@as-cii as-cii 🎨
5201e45
@as-cii as-cii Merge branch 'master' into as-tiled-rendering
Conflicts:
	spec/text-editor-presenter-spec.coffee
b66fdca
@as-cii as-cii 🔥 Remove obsolete node pool dbc57ab
@as-cii as-cii 🔥
d116a33
Owner

as-cii commented May 18, 2015

I have rebased this branch against master and fixed TextEditorPresenter specs. To make everything green I still need to polish TextEditorComponent's tests, which are currently failing (I presume this won't take long, though).

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-guide

After "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:

screen shot 2015-05-18 at 14 01 28

We should change it to update its position every time the editor scrolls using pixelPositionForScreenPosition instead. This, however, makes me think about a few questions:

  1. How can we make sure we don't break existing packages which rely on absolute positions?
  2. DisplayBuffer#pixelPositionForScreenPosition still returns the absolute pixel position for a given screen position. @nathansobo: I think this method should be changed to use relative positions, as TextEditorPresenter#pixelPositionForScreenPosition does. Do you think we can finally move all these duplicated position-related methods into TextEditorPresenter?

Thank you! 🙇

as-cii added some commits May 18, 2015

@as-cii as-cii 🎨
dc6938a
@as-cii as-cii Avoid allocating an extra array
ba0df9f

Works amazing even on:

Intel® Core™ i5 CPU 650 @ 3.20GHz
Intel® Ironlake Desktop

I can even turn on minmap animation flag!

as-cii added some commits May 19, 2015

@as-cii as-cii Merge branch 'master' into as-tiled-rendering
350b306
@as-cii as-cii 🎨
b913896
@as-cii as-cii 🐎 Bump tileCount to 6
90b1228

@as-cii as-cii commented on the diff May 19, 2015

src/lines-component.coffee
@@ -19,11 +20,7 @@ class LinesComponent
placeholderTextDiv: null
constructor: ({@presenter, @hostElement, @useShadowDOM, visible}) ->
@as-cii

as-cii May 19, 2015

Owner

We may still introduce a couple of improvements here:

  1. Avoid allocating a TileComponent object whenever new tiles need to be rendered. This will remove some pressure from GC and will improve performance as well. I somewhat liked the idea of having a tile encapsulated in its own component, but this area of the code is particularly performance sensitive and we should pay attention to each allocation.
  2. When more than one tile gets rendered on screen for a given frame, we observe multiple Parse HTML events (one for each new tile). An alternative approach could be to parse all the lines at the same time and append them later to their respective tile. Not sure if the performance implications here are very serious, but this could make us save some cycles.
  3. Avoid removing tile nodes while scrolling. Removing a tile node from the DOM takes between 0.5 and 1.0 ms: for performance sensitive scenarios like scrolling, we may simply put display: none and cleanup dangling tiles once scrolling ends.

Not sure if we should tackle 3), but probably 1) and 2) need to be addressed.

@nathansobo

nathansobo May 19, 2015

Contributor

Like I said in chat, I'm skeptical about 1. We're allocating plenty of objects in the typical render frame. One per tile doesn't seem to represent a significant increase.

I would like to see you try out 2, because in the timeline it definitely seems like there's overhead for the discrete parse calls. You could try out the wrapper div approach and just generate all the DOM nodes in masse, then populate the tiles. Not sure it will be a win by the time we do all that, but worth a look.

Not sure about 3. Probably easy to experiment with and see how much it helps?

@as-cii

as-cii May 20, 2015

Owner

@nathansobo: I experimented with 2) and apparently we're not saving a significant amount of time. Moreover, batching HTML parsing requires us to add stuff in a two-step fashion: namely, HTML accumulation and rendering. Not a huge boost in complexity, but neither a "free lunch" (TANSTAAFL), therefore I'd say it's not worth going further down that path.

As for 3) it seems like we may be able to save ~1ms if we do this for gutter as well. What about addressing it after merging this?

@nathansobo

nathansobo May 20, 2015

Contributor

👍 on both.

@as-cii as-cii 🔥 Remove comment
9fee62d

@nathansobo nathansobo commented on the diff May 19, 2015

spec/text-editor-presenter-spec.coffee
@@ -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

nathansobo May 19, 2015

Contributor

I'm worried about the repaint behavior of the gutter if it doesn't overdraw and isn't tiled. Who knows what's changed in Chromium's graphics implementation, but previously when I didn't overdraw the red paint rectangle always covered the entire gutter when scrolling.

Contributor

nathansobo commented May 19, 2015

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.

Contributor

nathansobo commented May 19, 2015

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.

@as-cii as-cii Reduce tile count to 4
ffc73f3
Owner

as-cii commented May 20, 2015

@nathansobo: thanks for the extensive feedback!

I'd be curious to see how much your compositing time drops if you give the
tiles opaque backgrounds.

Assigning an opaque background doesn't impact layers compositing performance:
some experiments I conducted seem to report the same time for transparent tiles
(both ~ 0.400ms - 0.500ms)

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.

That's very interesting and I didn't consider that: I've always assumed that the
background color on .lines was enough to maintain the original behavior. Now
that I think about it, however, I'd prefer to wait until Chrome supports AA for
two reasons:

  • It would require changing how wrap-guide and highlights are rendered on
    screen, as you're pointing out. Probably not a drastic change, but will still
    introduce some complexity;
  • It enables us to explicitly opt-in sub pixel anti-aliasing in the future: the
    advantage here is avoiding to inadvertently introduce new behaviors (and bugs)
    when we upgrade Chrome to a new version.

@nathansobo: that said, I trust your judgement on this and I am totally fine if
you insist we should give tiles an opaque background.

Also, what was your experience with larger tile sizes?

I observed that having smaller tiles doesn't have an impact on performance and,
given I primarily work on a 1920x1080 window, I arbitrarily chose 6 as the
default value. This may be too large when the screen is smaller, so I think we
can safely decrease that to 4. That said, however, I am not a huge fan of
magic constants and I would ❤️ to analyze performance metrics of other
devices so that we can make a more informed decision. Is anyone on
@atom/feedback interested in trying this out? Your help would be much
appreciated! 🙇

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.

I couldn't spot any difference with how the gutter is painted on master: am
I missing anything there? Do you mean we should introduce tiling for gutter as
well in this PR?

When I first opened it, my intention was to simply let this bake for a while
after 🚢ping it, and proceed to tile the gutter after we validated the
quality of this strategy. If that sounds unnecessary, I am ok to handle line
numbers tiling here as well 👍

@as-cii as-cii Merge branch 'master' into as-tiled-rendering
329b5b4

@nathansobo nathansobo commented on an outdated diff May 20, 2015

src/tile-component.coffee
+ 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

nathansobo May 20, 2015

Contributor

You're going to need to integrate my changes on #6757 unfortunately but it shouldn't be a huge deal.

Contributor

nathansobo commented May 20, 2015

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 as-cii Manually config tileSize
Dealing with a manually entered `tileSize` is actually easier to reason about,
therefore we no longer calculate it based on `tileCount.`
752dbf2
@as-cii as-cii 💚 Fix remaining specs 1a18cda
@as-cii as-cii wip f49078d
@as-cii as-cii Merge branch 'master' into as-tiled-rendering
# Conflicts:
#	spec/text-editor-presenter-spec.coffee
#	src/lines-component.coffee
#	src/text-editor-presenter.coffee
299ee5d
@as-cii as-cii Rename LinesComponent to TileComponent efeb129
@as-cii as-cii 🎨
1a5e2fe
@as-cii as-cii 💚
Conflicts:
	spec/text-editor-presenter-spec.coffee
	src/lines-component.coffee
	src/text-editor-presenter.coffee
490ab2c

@as-cii as-cii and 1 other commented on an outdated diff May 21, 2015

spec/text-editor-component-spec.coffee
@@ -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

as-cii May 21, 2015

Owner

This is minor, but I am not very happy with how this test helper is named. Probably this would look better with a matcher?

expect(tilesNodes[0]).toHaveRenderedLine(screenRow: 0, top: 10)
@nathansobo

nathansobo May 22, 2015

Contributor

This helper doesn't represent a clear concept to me, which is why it's tough to name. If I could articulate in a sentence I think it means this:

"Expect this tile node to contain a line node at the following offset."

What about expectTileContainsRow and passing the tile and the row as positional args. Then you could follow with top: 10. I think that might tell the story a bit better.

@as-cii as-cii commented on the diff May 21, 2015

src/tile-component.coffee
+{$$} = 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

as-cii May 21, 2015

Owner

This diff is the result of an unfortunate false positive: ideally, git should have recognized LinesComponent as a whole new file, and TileComponent as a rename from the previous lines-component.coffee.

@as-cii as-cii 🔥 Remove unused requires
c9a159a
Owner

as-cii commented May 21, 2015

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.

👍

I ended up managing tileSize as a configuration parameter, rather than an automatically calculated value (inferred from tileCount): I think it's just easier to reason about, and we save a few cycles as well.

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.

I wrote some new specs and fixed all the failing ones as well. I think this is now ready for a test drive! 🏁

@nathansobo nathansobo commented on an outdated diff May 22, 2015

spec/text-editor-component-spec.coffee
@@ -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("&nbsp;")
+ else
+ expect(lineNode.textContent).toBe(tokenizedLine.text)
+
+ it "renders the currently-visible lines into a tiled fashion", ->
@nathansobo

nathansobo May 22, 2015

Contributor

in a tiled fashion

nathansobo and others added some commits May 22, 2015

@nathansobo nathansobo Merge branch 'master' into as-tiled-rendering c02294c
@nathansobo nathansobo Fix spec failure
@as-cii this was just a typo, but there’s a failure later in the spec
that you’re probably in a better position to fix quickly.
1783415
@as-cii as-cii 🎨 Better test naming 538fcfe
@as-cii as-cii 💚 Fix wrong spec
@nathansobo: I just forgot to include an actually visible tile in this
test which, therefore, complained. “Production code”-wise the behavior
was consistent and correct.
80a3294
Contributor

nathansobo commented May 22, 2015

So far so good after about a day.

Contributor

benogle commented May 22, 2015

Hows the wrap-guide? You know the wrap-guide has to work. Muy importante.

Contributor

nathansobo commented May 22, 2015

Hows the wrap-guide?

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.

Contributor

benogle commented May 22, 2015

Oh sweet. I was just being a jerk, but that's great.

@as-cii as-cii added a commit to atom/wrap-guide that referenced this pull request May 23, 2015

@as-cii as-cii Relativize wrap guide position
This change is needed in order to support the new tiled editor. You can
find further information about this on
atom/atom#6733, but basically we don’t use
absolute coordinates to position lines on screen anymore.

This commit changes the wrap guide to listen to “scroll left changed”
events, updating its position with a relative value which takes into
account the current scroll left position.
5f8fb0c

as-cii referenced this pull request in atom/wrap-guide May 23, 2015

Merged

Relativize wrap guide position #31

Owner

as-cii commented May 23, 2015

There's actually a subtle issue which affects the wrap-guide during horizontal scrolling. I have already made a PR to fix that (you can find it above this comment 👆) and ideally we should release it as soon as we release this one. :shipit:

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 DisplayBuffer#pixelPositionForScreenPosition works (i.e. use relative positioning). Also removing the duplication I pointed out in that comment doesn't seem like a good idea anymore to me (e.g. are DisplayBuffer#pixelPositionForScreenPosition and TextEditorPresenter#pixelPositionForScreenPosition really the same thing? Probably the latter should use the former, but they may not be exactly the same).

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?

Contributor

nathansobo commented May 26, 2015

@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 as-cii Declare `atom.hasTiledEditor = true`
This way packages that are affected by relative coordinates will be
able to smoothly upgrade using a feature toggle.
7926ca5
Contributor

nathansobo commented on 7926ca5 May 27, 2015

@as-cii How would you feel about limiting this to a property on TextEditorElement, like ::hasTiledRendering just to limit its scope?

@as-cii as-cii Narrow scope and rename flag
a0ce1e6
Owner

as-cii commented May 27, 2015

@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! 🙇

Contributor

nathansobo commented May 27, 2015

Plan to merge this after the next release.

Contributor

nathansobo commented May 27, 2015

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.

cursor bug

nathansobo referenced this pull request May 28, 2015

Closed

WIP: Render editor content via a tiling strategy #3154

4 of 23 tasks complete

as-cii added some commits May 29, 2015

@as-cii as-cii Merge branch 'master' into as-tiled-rendering b4dfb2a
@as-cii as-cii 🐛 Update cursors after model changes
9b4d62b
Owner

as-cii commented May 29, 2015

@nathansobo: thanks for spotting the issue! 🙏 🙏 🙏

I have fixed it in 9b4d62b: before that commit, when a change in the model caused scrollTop to change, TextEditorPresenter didn't update cursor state (which now use relative coordinates and needs to be recalculated).

My initial thought was to fix this 🐛 by updating cursors right inside updateScrollTop, updateScrollLeft, etc., but those methods do not really deal with state right now and eventually I went with adding one more state update in model.onDidChange, which happen to solve the problem as well.

Contributor

nathansobo commented on 9b4d62b May 29, 2015

Nice.

Contributor

nathansobo commented May 29, 2015

@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.

Tested here #6733 (comment), on Ubuntu 14.04, was great!

@kevinsawicki kevinsawicki commented on the diff Jun 1, 2015

src/lines-component.coffee
@@ -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

kevinsawicki Jun 1, 2015

Owner

Should this return be retained so that the results of the for loop aren't built up in an array and returned?

@kevinsawicki

kevinsawicki Jun 1, 2015

Owner

Nevermind, looks like it is, just way down in the diff, sorry.

@as-cii

as-cii Jun 2, 2015

Owner

No worries about that. I am actually sorry this diff got so ugly 🙏

Contributor

nathansobo commented Jun 2, 2015

Tested on Windows and it's working correctly.

nathansobo merged commit dac39bd into master Jun 2, 2015

2 checks passed

continuous-integration/travis-ci/pr The Travis CI build passed
Details
continuous-integration/travis-ci/push The Travis CI build passed
Details

nathansobo deleted the as-tiled-rendering branch Jun 2, 2015

Contributor

nathansobo commented Jun 2, 2015

Thanks very much @as-cii. People are really going to be happy to see scroll speed improvements.

Let's see if this helps with my #6981

v3ss0n commented Jun 3, 2015

@nathansobo @as-cii i am testing on linux 64bit.
I opened up a compiled js file , with long lines and 1.2MB.
Selection is smooth, editing is a bit laggy
Scrolling is fine until it goes to bottom.

When scrolled to bottom part , it freeze up , taking 100% CPU and seems to be generating tiles, for 20-40 secs?
That bottom part is 1.1 mil chars , within 2 lines only.
It happens everytime when scroll back to top and bottom part again

Is it possible to move tiles generation to a worker process? I have tested with 205 and it rendered fast and fine.

Owner

as-cii commented Jun 3, 2015

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

17 of 20 tasks complete

v3ss0n commented Jun 3, 2015

Here are things i've tested so far:

###Works

  • 11 MB file
  • More than 100k char per line
  • Compiled Javascript file (with a lot of tokens )
  • Just characters
  • Select word , Select Line , Select Multiple line , Select all

##Fails

  • Reopen session with many tabs and one huge file.

This looks very interesting nodejs/io.js#1159
I wonder if Atom could use it for heavy tasks? @nathansobo

Contributor

nathansobo commented Jun 9, 2015

@alexandernst That looks super interesting. Thanks for the heads up.

@nathansobo does Atom already use io.js or is it still on Node?

Member

mnquintana commented Jun 10, 2015

@ilanbiala Atom uses io.js – Electron uses it.

Contributor

nathansobo commented Jun 10, 2015

@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.
@mnquintana atom is built using node 0.12 on my machine , is that already using IO.js ?
Atom current version is not using electron , yet right?

Contributor

mehcode commented Jun 10, 2015

@v3ss0n From what I understand atom has a packaged version. The version you have is only used to bootstrap the process.

@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
It renders tiles in more darkened color than my theme's color , i had compared it against atom version without tiled rendering. See attached. The text area part of the editor is darkened vs the bottom part (the actual theme's color)

tile_rendering_theme

from styling side , should i theme .lines or .tile

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 z-index to -1. That way, the cursor would render behind the text, and text would still be legible.

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 .

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.

Owner

as-cii commented Jun 22, 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?

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.

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.

@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.

@as-cii Thanks! That's truly awesome :) I'll submit a new issue in the evening.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment