-
Notifications
You must be signed in to change notification settings - Fork 75
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
Conversation
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.
I'm marking this as a WIP until I add an example and add some additional tests. |
Current coverage is 88.87% (diff: 100%)@@ 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
|
…one in the fragment shader.
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.
2f528e9
to
e9e7f10
Compare
This still needs a WebGL image comparison test.
This is ready for review. |
Don't set body css to hidden on image tests, as it messes up the karma debug.html output.
👏 👏 started the review today...hoping to get it done in next few days. |
Clean up after some tests.
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.
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.
…t function for screen tests.
Test examples
There was a problem hiding this 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) + ')'; |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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/)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant in chrome
@aashish24 One question I meant to ask before: do we want to set |
+2 for a value of 2. I think thank makes sense. |
e2f7ed8
to
b8c9e26
Compare
I've pushed a change to make the default antialiasing 2. |
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;', |
There was a problem hiding this comment.
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;', |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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);', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
worth using a function?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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:
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:
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.