-
Notifications
You must be signed in to change notification settings - Fork 2.2k
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
Enable property functions for {text,icon}-size #4455
Conversation
68ad56a
to
8f8e4b3
Compare
Capturing notes on this issue from the TODO above:
The concern here is that we might run into a problem due to the shaders having to evaluate composite functions using a limited number of stops, because:
Cases to consider: Normal case (
|
src/shaders/symbol_sdf.vertex.glsl
Outdated
// Thus, we compensate for this difference by calculating an adjustment | ||
// based on the scale of rendered text size relative to layout text size. | ||
mediump float adjustedSize = u_is_size_composite | ||
? evaluate_zoom_function_1(a_size, u_adjusted_size_t) / 10.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.
It is unfortunate that this breaks the abstraction provided by the #pragma system, and it's also going to need reworking to be usable in native.
233d2a7
to
23e2b51
Compare
Okay, here's a draft based on treating text/icon-size as a "pseudo" paint property; the actual DDS part is in the last three commits (the previous ones are miscellaneous cleanup, docs, and a couple bug fixes). A key difficulty is that, when text/icon-size is a composite function, we need per-vertex values for text-size at both the rendered zoom and layout zoom (the latter being I'm going to try an alternate approach next: instead of trying to make a pseudo paint property, I'll try doing it with a new layout attribute -- i.e. the second bullet here #4228 (comment) |
src/render/draw_symbol.js
Outdated
const iconScaled = size !== 1 || browser.devicePixelRatio !== painter.spriteAtlas.pixelRatio || iconsNeedLinear; | ||
const iconSizeScaled = !layer.isLayoutValueFeatureConstant('text-size') || | ||
!layer.isLayoutValueZoomConstant('text-size') || | ||
layer.getLayoutValue('text-size', { zoom: tr.zoom }, {}) !== 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.
These should be icon-size
eb8cd6c
to
8e461c4
Compare
Replaced the problematic approach described in #4455 (comment) with using a new layout attribute 👌 |
c798663
to
ae60c28
Compare
src/data/bucket/symbol_bucket.js
Outdated
function addVertex(array, x, y, ox, oy, tx, ty, minzoom, maxzoom, labelminzoom, labelangle) { | ||
array.emplaceBack( | ||
function addVertex(array, x, y, ox, oy, tx, ty, sizeData, minzoom, maxzoom, labelminzoom, labelangle) { | ||
array.emplaceBack.apply(array, |
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 think this is a pretty hot path, so this change should receive some thorough benchmarking.
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.
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.
Further profiling confirms that apply
is a bad idea here.
Timing emplaceBack()
([min, 1st quartile, median, 3rd quartile, max]
):
- master (i.e. w/o the size data added in this PR):
[ 0.001, 0.004, 0.005, 0.006, 0.778 ]
- using apply:
[ 0.002, 0.008, 0.01, 0.011, 12.945 ]
Changed to avoid using apply by always passing three (possibly undefined) args for the size data; timing for emplaceBack with that approach is basically identical to master.
src/render/draw_symbol.js
Outdated
} | ||
|
||
if (isSDF) { | ||
const haloWidthProperty = `${isText ? 'text' : 'icon'}-halo-width`; | ||
const hasHalo = !layer.isPaintValueFeatureConstant(haloWidthProperty) || layer.paint[haloWidthProperty]; | ||
const gammaScale = fontScale * (pitchWithMap ? Math.cos(tr._pitch) : 1) * tr.cameraToCenterDistance; | ||
gl.uniform1f(program.u_font_scale, fontScale); | ||
const gammaScale = (isText ? 1 / 24 : 1) * (pitchWithMap ? Math.cos(tr._pitch) : 1) * tr.cameraToCenterDistance; |
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 does fontScale
reduce to 1
elsewhere in this function but not 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.
Good question. I think I made this change before I'd introduced u_is_text
and the fontScale calculation into the shader. Now that those are in place, the (isText ? 1 / 24 : 1)
can be removed here in favor of multiplying u_gamma_scale
by fontScale
instead of by v_size
.
* | ||
* @private | ||
*/ | ||
exports.packUint8ToFloat = function pack(a, b) { |
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.
Is this used somewhere? I can only find it in the tests.
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.
Whoops. Leftover from previous attempt.
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.
Oh wait, no -- I think we'll need this, it's just still TBD: we still need to pack a_data
together with a_texture_pos
so that there's no net increase in number of attributes, and this helper will be used for that.
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's now used (see 2d3b36a)
src/style-spec/function/index.js
Outdated
@@ -98,21 +98,24 @@ function createFunction(parameters, propertySpec) { | |||
|
|||
if (zoomAndFeatureDependent) { | |||
const featureFunctions = {}; | |||
const featureFunctionStops = []; | |||
const zoomStops = []; |
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.
Can you split this into a separate fix/PR, with a test?
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.
==> #4509
src/shaders/symbol_icon.vertex.glsl
Outdated
@@ -3,11 +3,20 @@ attribute vec4 a_pos_offset; | |||
attribute vec2 a_texture_pos; | |||
attribute vec4 a_data; | |||
|
|||
// icon-size data (see symbol_sdf.vertex.glsl for more) | |||
attribute vec3 a_size; |
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.
Are we combining two attributes somewhere else or does this put us over the eight attribute limit?
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.
Yep we still need to combine #4455 (comment)
919306c
to
2d3b36a
Compare
src/data/bucket/symbol_bucket.js
Outdated
} | ||
if (!layer.isLayoutValueZoomConstant('icon-size')) { | ||
this.iconSizeCoveringZoomStops = this.layers[0].getLayoutValueCoveringZoomStops('icon-size', this.zoom, this.zoom + 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.
Hmm. This is overcomplicated. We don't really need to serialize adjusted{Text,Icon}Size
, or {text,icon}SizeCoveringZoomStops
on the bucket -- drawSymbols() can (re)compute these at render time, since they only depend on the tile's zoom level.
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 is awesome! I'm still playing with the branch locally, but if we find some extra bits for labelangle, we should be able to get the tests to start passing.
src/shaders/symbol_sdf.vertex.glsl
Outdated
|
||
mediump float zoomAdjust = log2(v_size / adjustedSize); | ||
mediump float adjustedZoom = (u_zoom - zoomAdjust) * 10.0; | ||
// result: z = 0 if a_minzoom <= adjustedZoom < a_maxzoom, and 1 otherwise |
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.
Conceptually, this means: if adjustedZoom is outside the min-max zoom range, shift the z value of this vertex by w
-- i.e. push this vertex outside of clip space so it doesn't get rendered... right? This took several reads for me to understand.
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.
Yeah, I think that's right. I wasn't 100% sure of the clip space part, though, and so didn't include it in the comment in case that was incorrect and thus misleading. But if that's your understanding, too, I think that's confirmation enough...
src/data/bucket/symbol_bucket.js
Outdated
(labelminzoom || 0) * 10, // labelminzoom | ||
labelangle, // labelangle | ||
labelangle // labelangle |
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.
Packing labelangle into 8 bits is really tight (less than 1 degree precision). Let's try to find some spare bits somewhere else.
src/data/bucket/symbol_bucket.js
Outdated
addSymbolInstance(anchor, line, shapedTextOrientations, shapedIcon, layer, addToBuffers, collisionBoxArray, featureIndex, sourceLayerIndex, bucketIndex, | ||
textBoxScale, textPadding, textAlongLine, | ||
iconBoxScale, iconPadding, iconAlongLine, globalProperties, featureProperties) { | ||
layoutTextSize, textBoxScale, textPadding, textAlongLine, |
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.
Are layoutTextSize
and layoutIconSize
vestigial from some earlier version of this code? I don't see them being used.
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.
Yep, good catch, thanks!
src/shaders/_prelude.vertex.glsl
Outdated
// packed like so: | ||
// packedValue = floor(input[0]) * 256 + input[1], | ||
vec2 unpack_float(const float packedValue) { | ||
float v0 = floor(packedValue / 256.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.
I can't actually see a problem with the math/precision handling here, but for doing this type of math I would try to do the float -> int conversion as soon as possible (where we have to worry about weird precision stuff), and then do the rest of the math with ints instead of floats. Reasoning about things like .99999 + 1 - 1 == 0.9999899999999999
is tricky.
I'm assuming glsl 100 reads UNSIGNED_SHORT vertex attributes as 32-bit floats... which I think should be able to handle up to 24 bits of integers without precision loss. I can't actually find where the behavior is specified. But I think we'd see more breakage if it were using half-floats.
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's specified in OpenGL® ES Shading Language §4.5.2 (p33). For highp
, 16 bits of precision, integer range (-2¹⁶, 2¹⁶).
@ChrisLoer I think I've addressed the review points. Still don't want to merge till -native port is done (finally making some progress there this evening), but could you check and see if this looks ready to you? |
@anandthakker It's looking good to me, and I've been using the branch locally for work on https://github.com/mapbox/mapbox-gl-js/tree/cloer_text_pitch_scale without any problems. I'm still hoping to get some time to dive into how the zoom stop interpolation works (it was the sort of thing that I read with the mindset "this is complicated... I'm sure it's fine"). No 👶 🐒 today so I probably can't get to that until tomorrow. |
@anandthakker These changes don't handle exponential interpolation consistently with other DDS styles. Here's the debug view I used to play with https://gist.github.com/ChrisLoer/a102c40ae5ad45b60c2d1b1d4bb738fc With the base set to something high, you can see how the circles will change size rapidly as you approach the stop, while the text size doesn't. Also the 'interval' property function type doesn't work. I don't really have a great solution in mind, or haven't wrapped my head around what the solution would be. If we can't make changes to support these modes, we should at least disable them and document accordingly? |
Ah, dang. Nice catch @ChrisLoer. I suspect this is probably because I'm calculating the interpolation t-value naively here, whereas for other DDS properties we use StyleDeclaration#calculateInterpolationT. We may be able to do better here... but it's going to be complicated by the fact that for text/icon-size, when we're rendering a tile at |
Playing around with this I noticed a kind of annoying problem even if you're on the "conservative" side of collision detection. Say you start at zoom |
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.
The collision issue with zooms and text-size is already something we're living with, I think this issue tracks it: #1196. Other than that, everything looks good to me. I'm suffering from attention exhaustion, but I did do one more quick run through the changes and tests. I'll leave to @jfirebaugh for final 🍏
Fix bug in packing of labelangle value * Label angles passed to addVertex() are in units of 1/256 of a circle. Because packUint8ToFloat clamped input values to [0, 255], the value 256 was clamped to 255 (== 1.4 degrees off the horizontal), rather than being wrapped to 0.
- Consolidate it under bucket.{text,icon}SizeData objects - Include function base as part of said data - Use interpolationFactor to account for base != 1 for consistency with paint properties
326e42c
to
e3b2df2
Compare
* Update gl-js and generate style code * Factor out packUint8Pair() helper function * Draft implementation of DDS for {text,icon}-size Ports mapbox/mapbox-gl-js#4455 * Fix text-size/composite-function-line-placement test * Refactor to PaintPropertyBinders-like strategy * Dedupe gl::Program construction * Use exponential function base for interpolation * Dedupe coveringZoomStops method * Fixup tests * Fix CI errors (hidden within #if block)
Closes #4228
TODO:
Fix up collision box debug shader/drawing(Doesn't look like these are affected)text-size/composite-function (symbol-placement: line, overzoomed tile - target edge case where shader's evaluated function value != layout-time function value)(no longer relevant: sending placement-level size to the shader directly via an attribute)base
property (see Enable property functions for {text,icon}-size #4455 (comment))