Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor GL line rendering #649

Merged
merged 33 commits into from
Jan 20, 2017
Merged

Refactor GL line rendering #649

merged 33 commits into from
Jan 20, 2017

Conversation

manthey
Copy link
Contributor

@manthey manthey commented Nov 21, 2016

This resolves issues #204 and #596.

Previously, lines were not rendered correctly for several conditions: (a) miter locations were sometimes miscalculated, resulting in inconsistent line widths. (b) when the miter limit was exceeded, an attempt to prevent an excessively long line produced skewed segments. (c) On the inside of acute angles on short line segments, some of the triangles used to render lines were twisted. (d) when lines overlap, the overlap was inconsistent.

All of this has been fixed. Additionally, we now support:

  • line caps: butt (default and what was avaulable before), round, square. These can vary by point.
  • line joins: miter (default), round, bevel, miter-clip. These can vary by point. Miter and miter-clip have a configurable miter limit (one value for the whole feature) which, if the miter would be longer than this, switches to using a bevel or a clipped miter.
  • antialiasing on edges and end caps. This can be specified for the whole feature.
  • strokeOffset: This can vary from -1 (shift left) to 1 (shift right), where 0 is the line centered on the vertices, and 1 is with the edge of the line on the vertices.

All of this is done by rendering two triangles per line segment and performing much of the computation in the vertex and fragment shaders.

There are a few limitations to the current implementation:

  • there is finite precision in the calculations, resulting in occasional artifacts along miter joins. These are mostly only noticeable on very wide lines using stroke offsets, and, even then, are subtle.
  • When the line width changes per vertex, sometimes the inside joins are not technically correct. Instead of using the angle of the lines meeting, the angle of the edges of the lines meeting would need to be calculated.
  • On short line segments, one edge of the miter can be by itself, which would ideally either be antialiased or wouldn't be trimmed. Currently, on wide short lines with multiple segments, the mitered edge can look blocky in limited instances.
  • When a line segment has zero length in screen space, it is not drawn at all. Adjacent line segments may not be drawn exactly as expected. Also, if line caps are used, perhaps zero-length segments should still be visible.

This method uses 18/15 as much memory as lines used before.

There is a debug flag to show all of the pixels sent to the fragment shader. If debug is not specified when the feature is created, the debug code is not even compiled into the fragment shader.

There could be some efficiency improvements in the shaders. For instance, it might worth it to have a quick test for simple line segments without joins.

The new features and changes are exposed in one of the selenium tests (test/selenium/glLines/?wide=true), which can take a variety of query options, such as strokeOffset, lineCap, lineJoin, miterLimit, antialiasing, strokeWidth, strokeColor, strokeOpacity, and debug. This should be turned into an example that also tests number of lines.

This resolves issues #204 and #596.

Previously, lines were not rendered correctly for several conditions: (a) miter locations were sometimes miscalculated, resulting in inconsistent line widths.  (b) when the miter limit was exceeded, an attempt to prevent an excessively long line produced skewed segments.  (c) On the inside of acute angles on short line segments, some of the triangles used to render lines were twisted.  (d) when lines overlap, the overlap was inconsistent.

All of this has been fixed.  Additionally, we now support:
- line caps: butt (default and what was avaulable before), round, square.  These can vary by point.
- line joins: miter (default), round, bevel, miter-clip.  These can vary by point.  Miter and miter-clip have a configurable miter limit (one value for the whole feature) which, if the miter would be longer than this, switches to using a bevel or a clipped miter.
- antialiasing on edges and end caps.  This can be specified for the whole feature.
- strokeOffset: This can vary from -1 (shift left) to 1 (shift right), where 0 is the line centered on the vertices, and 1 is with the edge of the line on the vertices.

All of this is done by rendering two triangles per line segment and performing much of the computation in the vertex and fragment shaders.

There are a few limitations to the current implementation:
- there is finite precision in the calculations, resulting in occasional artifacts along miter joins.  These are mostly only noticeable on very wide lines using stroke offsets, and, even then, are subtle.
- When the line width changes per vertex, sometimes the inside joins are not technically correct.  Instead of using the angle of the lines meeting, the angle of the edges of the lines meeting would need to be calculated.
- On short line segments, one edge of the miter can be by itself, which would ideally either be antialiased or wouldn't be trimmed.  Currently, on wide short lines with multiple segments, the mitered edge can look blocky in limited instances.
- When a line segment has zero length in screen space, it is not drawn at all.  Adjacent line segments may not be drawn exactly as expected.  Also, if line caps are used, perhaps zero-length segments should still be visible.

This method uses 18/15 as much memory as lines used before.

There is a debug flag to show all of the pixels sent to the fragment shader.  If debug is not specified when the feature is created, the debug code is not even compiled into the fragment shader.

There could be some efficiency improvements in the shaders.  For instance, it might worth it to have a quick test for simple line segments without joins.

The new features and changes are exposed in one of the selenium tests (test/selenium/glLines/?wide=true), which can take a variety of query options, such as strokeOffset, lineCap, lineJoin, miterLimit, antialiasing, strokeWidth, strokeColor, strokeOpacity, and debug.  This should be turned into an example that also tests number of lines.
@manthey manthey changed the title Refactor GL line rendering [WIP] Refactor GL line rendering Nov 21, 2016
@manthey
Copy link
Contributor Author

manthey commented Nov 21, 2016

I'm marking this as a WIP until I add an example and add some additional tests.

@codecov-io
Copy link

codecov-io commented Nov 21, 2016

Current coverage is 88.87% (diff: 100%)

Merging #649 into master will decrease coverage by 1.10%

@@             master       #649   diff @@
==========================================
  Files            83         84     +1   
  Lines          8365       8497   +132   
  Methods           0          0          
  Messages          0          0          
  Branches          0          0          
==========================================
+ Hits           7527       7552    +25   
- Misses          838        945   +107   
  Partials          0          0          

Powered by Codecov. Last update f55e449...dd7b0a6

Better comments, another preset, the show map option is explicit.
Allow the selectionAPI to be turned on or off.
This caches line positions and widths, and avoids using functions and exceptions within the core loop.  For 500,000 line segments a point search was taking around 1500 ms, and now takes 20 ms or so.
This was added to compare how canvas, svg, and our gl code render lines.  It could probably be made more efficient by not calculating the geometry on every update.
Lines were not removed when data got shorter.  svgs could cause scrollbars to appear.
@manthey manthey changed the title [WIP] Refactor GL line rendering Refactor GL line rendering Dec 19, 2016
@manthey
Copy link
Contributor Author

manthey commented Dec 19, 2016

This is ready for review.

Don't set body css to hidden on image tests, as it messes up the karma debug.html output.
@aashish24
Copy link
Member

👏 👏 started the review today...hoping to get it done in next few days.

Eventually, this will facilitate testing examples.
Test the lines example.

Add screenshot capabilities.  This requires ImageMagick and expects the test to be run on the same computer or VM as the karma test server.
Example tests must have the examples built first.

This required upgrading the node-fs-extra package.
@manthey manthey mentioned this pull request Jan 3, 2017
This removes the network dependency to openstreetmap.org in the tests.

As additional examples are added, the tiles.tgz file will need to be updated.
Copy link
Member

@aashish24 aashish24 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made my first pass on the review. Will make another pass.

cur.opacity = strokeOpacityFunc(line[0], 0, d, i);
cur.linecap = lineCapFunc(line[0], 0, d, i);
cur.linejoin = lineJoinFunc(line[0], 0, d, i);
cur.strokeStyle = 'rgba(' + (cur.color.g * 255) + ', ' + (cur.color.g * 255) + ', ' + (cur.color.b * 255) + ', ' + (cur.opacity !== undefined ? cur.opacity : 1) + ')';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@manthey should we wrap these long lines?

@@ -105,6 +106,10 @@ var d3_lineFeature = function (arg) {

m_renderer._drawFeatures(m_style);
});
for (i = data.length; i < m_maxIdx; i += 1) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this is required now?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was always required; not having it was a bug. In the past, if you reduced the number of lines you had in the d3 renderer, they wouldn't be taken away.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah thanks

@@ -31,7 +31,7 @@ var feature = function (arg) {

var m_this = this,
s_exit = this._exit,
m_selectionAPI = arg.selectionAPI === undefined ? false : arg.selectionAPI,
m_selectionAPI = arg.selectionAPI === undefined ? false : !!arg.selectionAPI,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have heard good and bad about using double negation (https://www.sitepoint.com/javascript-double-negation-trick-trouble/)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could use Boolean() instead. !! is substantially faster than Boolean() in Chrome, though, and, it is a common enough javascript idiom, that I think it is fine.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right !! Is fast and i read about that earlier. Inam fine with !! For now

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant in chrome

@manthey
Copy link
Contributor Author

manthey commented Jan 13, 2017

@aashish24 One question I meant to ask before: do we want to set antialiasing to a default value? Currently, it defaults to 0. I think lines actually look best when it is 2 (though mathematically, I feel that sqrt(2) should look as good, it doesn't on nearly vertical lines).

@aashish24
Copy link
Member

+2 for a value of 2. I think thank makes sense.

@manthey
Copy link
Contributor Author

manthey commented Jan 16, 2017

I've pushed a change to make the default antialiasing 2.

@aashish24
Copy link
Member

Roger that @manthey working on my review

' int nearMode = int(mod(floor(flags / 4.0), 8.0));',
' int farMode = int(mod(floor(flags / 32.0), 8.0));',
' float offset = mod(floor(flags / 256.0), 2048.0) / 1023.0;',
' if (offset > 1.0) offset -= 2048.0 / 1023.0;',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be nice to have explanation for 2048/1023.0 division

'varying vec4 info;', /* near mode, far mode, offset */
'varying vec4 angles;', /* near angle cos, sin, far angle cos, sin */

'const float PI = 3.14159265358979323846264;',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps we can define it some where else (utils?) so that we don't have to define it multiple times.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's do that when we address issue #655 (in a different PR).

Alternately, we could use the value in javascript, i. e., 'const float PI = ' + Math.PI.toFixed(20) + ';'. We aren't guaranteed the same precision choices in javascript and WebGL, though. The differences almost certainly don't matter much here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good to me.

* calculate the angles between the lines formed by prev-pos and
* pos-next, and between pos-next and next-far, plus know the angle
* (prev)---(pos)---(next)---(far) => A---B---C---D */
' vec4 A = projectionMatrix * modelViewMatrix * vec4(prev.xyz, 1);',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

worth using a function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure.

Use a function for coordinate transformation in the vertex shader.
Copy link
Member

@aashish24 aashish24 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍 👏 I did some basic check and as discussed with @manthey will do the shader review together as de-coding every math / logic is time consuming. Thank you for the patience and hard work @manthey

@manthey manthey merged commit d7b72e9 into master Jan 20, 2017
@manthey manthey deleted the line-rendering branch January 20, 2017 15:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants