Repository navigation
Data-driven styling support for *-pattern properties - #6289
Conversation
2cfe261 to
049229b
Compare
|
this is ready for an initial review, with the caveat that there are two open questions:
luckily, cross-faded properties are already interpolated along zoom-levels and thus require the same data to be passed for |
| return z > p.zoomHistory.lastIntegerZoom ? | ||
| { fromScale: 2, toScale: 1, t: fraction + (1 - fraction) * t } : | ||
| { fromScale: 0.5, toScale: 1, t: 1 - (1 - t) * fraction }; | ||
| } |
There was a problem hiding this comment.
When I implement fill-pattern and fill-extrusion-pattern dds in a follow-up PR, I will probably move this function to the parent StyleLayer class.
There was a problem hiding this comment.
👍 along with the CrossfadeParameters type
| to: T, | ||
| min: T, | ||
| mid: T, | ||
| max: T, |
There was a problem hiding this comment.
when I implement the remaining *-pattern properties, I will be able to remove the from and to components from this type in favor of the StyleLayer getter for cross fade parameters.
There was a problem hiding this comment.
It looks like it should be possible now for CrossFaded<T> to be pared down to {min: T, mid: T, max: T}. Maybe adding from: 'min' | 'max' to CrossfadeParameters so that in places where we're doing pattern.from, we can do pattern[crossfade.from] instead?
Yep, this is something that's true of all data-driven paint properties. We handle it in |
|
🤦♀️ well, that's embarrassing. I think I confused myself about this step where we determine which glyphs to add to the tile's it shouldn't be an issue to loop through each layer with data-driven |
|
I think we should aim to handle non-data-driven |
049229b to
d42aee4
Compare
d42aee4 to
3336ad6
Compare
|
Getting some diffs after moving uniform bindings to the they're tiny and I haven't looked too much into them – if they're acceptable diffs I will update the tests, and if not I will spend more time trying to fix. I also have a version of this PR that reduces the # attributes to 1, but requires dynamic checking and possibly binding of the vertex buffer on each frame/tile (there is a separate vertex buffer per tile depending on if the user is zooming in or zooming out for crossfading to work correctly) – should I pursue that route or stick with the current approach? |
3336ad6 to
6a07db0
Compare
|
Updating the tests for those diffs sounds good. Or, if you want to, minimize the tests so that they don't show those minor differences (use inline GeoJSON with simple geometries, 64x64 size, etc.). Reducing the number of attributes to one sounds great! It should be fairly cheap to switch vertex buffers on the fly, and it'll get cheaper once we have state tracking that allows us to rebind only the attributes that have changed. |
|
Thanks @anandthakker for helping me better understand floating point precision 🔢 This is ready for review, with the caveat that I still need to fix this PR for non-integer zoom stops because that is currently broken for both data-driven styling and camera functions. DIdn't notice this was broken until late this afternoon, so I will aim to address that tomorrow. |
|
|
||
| if (this.iconAtlasImage) { | ||
| this.iconAtlasTexture = new Texture(context, this.iconAtlasImage, gl.RGBA); | ||
| this.iconAtlasImage = null; |
There was a problem hiding this comment.
Looks like we were previously clearing this.iconAtlasImage to avoid creating the Texture every time. Should we do something analogous?
There was a problem hiding this comment.
yes – I had meant to come back to this. setting iconAtlas.image to null was upsetting flow but I will revisit! 👍
| } else { | ||
| self.binders[property] = new CompositeExpressionBinder(value.value, name, type, useIntegerZoom, zoom); | ||
| keys.push(`/z_${name}`); | ||
| const structArrayLayout = layoutType(property, type, 'composite'); |
There was a problem hiding this comment.
Nit: since structArrayLayout is actually a class, capitalize the var
| * @private | ||
| */ | ||
|
|
||
| export class CrossFadedDataDrivenProperty<T> extends DataDrivenProperty<?CrossFaded<T>> { |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| { from: min, to: mid, fromScale: 2, toScale: 1, t: fraction + (1 - fraction) * t } : | ||
| { from: max, to: mid, fromScale: 0.5, toScale: 1, t: 1 - (1 - t) * fraction }; | ||
| { from: min, to: mid, min: min, mid: mid, max: max, fromScale: 2, toScale: 1, t: fraction + (1 - fraction) * t } : | ||
| { from: max, to: mid, min: min, mid: mid, max: max, fromScale: 0.5, toScale: 1, t: 1 - (1 - t) * fraction }; |
There was a problem hiding this comment.
Nit: this can just be min, mid, max instead of min: min, mid: mid, max: max.
| return z > p.zoomHistory.lastIntegerZoom ? | ||
| { fromScale: 2, toScale: 1, t: fraction + (1 - fraction) * t } : | ||
| { fromScale: 0.5, toScale: 1, t: 1 - (1 - t) * fraction }; | ||
| } |
There was a problem hiding this comment.
👍 along with the CrossfadeParameters type
| ]); | ||
|
|
||
| export default lineLayoutAttributes; | ||
| export const {members, size, alignment} = lineLayoutAttributes; |
There was a problem hiding this comment.
Since this module now exports more than one set of attributes, let's drop the default export in favor of two named exports (See #6336)
| if (programConfiguration.binders && programConfiguration.binders['line-pattern'] && programConfiguration.binders['line-pattern'].isDataDriven()) { | ||
| this.dataDrivenPatternLayers.push(i); | ||
| } | ||
| } |
There was a problem hiding this comment.
Rather than storing this on this, could we compute it locally in populate()? (Also: why is this an array of indices into this.layers rather than an array of layers themselves?)
There was a problem hiding this comment.
One more question: why use binder.isDataDriven() rather than checking layer.paint.get('line-pattern')?
| } | ||
| } | ||
|
|
||
| // used if line-pattern is data-driven |
There was a problem hiding this comment.
Isn't addFeatures() also used if line-pattern is constant?
There was a problem hiding this comment.
yep, sorry – an artifact from an earlier iteration 🙈
|
|
||
| } | ||
|
|
||
| class PatternConstantBinder<T> extends ConstantBinder<T> { |
There was a problem hiding this comment.
I'm still inclined to say PatternConstantBinder should be a top-level class rather than a subclass of ConstantBinder (and likewise for PatternCompositeExpressionBinder).
ca66d46 to
40c68f5
Compare
| const programId = | ||
| layer.paint.get('line-dasharray') ? 'lineSDF' : | ||
| layer.paint.get('line-pattern') ? 'linePattern' : 'line'; | ||
| linePattern && linePattern.constantOr((1: any)) ? 'linePattern' : 'line'; |
There was a problem hiding this comment.
open to better ways of doing this check – basically if line-pattern is undefined, constantOr(1) will return undefined, but linePattern itself is no longer falsy if line-pattern is undefined because it is now a PossiblyEvaluatedPropertyValue
There was a problem hiding this comment.
I'm okay with this as-is, since it's analogous to how we do other such checks.
There was a problem hiding this comment.
Is there a way to eliminate the any cast?
40c68f5 to
43adf3d
Compare
2f8d0b4 to
fb233d7
Compare
|
@mollymerp looks like a pretty huge regression for Layout — is that spurious or reproducible with repeated runs? |
|
@mourner 😞 yep its reproducable and kind of expected because there is an additional worker-main thread round trip for *-pattern layers to fetch the icons needed to layout. I'm trying something quickly to avoid this if the pattern property is not used, and hopefully that will reduce the regression. |
| } | ||
|
|
||
| recalculate(parameters: EvaluationParameters) { | ||
| this._parameters = parameters; |
There was a problem hiding this comment.
Can we avoid adding this state?
There was a problem hiding this comment.
we can get rid of it if we pass the evaluation parameters from Map#_render to painter#render and through to the draw calls. Is that preferable?
There was a problem hiding this comment.
I took a deeper look and understand things a lot better now: CrossFadedDataDrivenProperty wants access to the same state -- fromScale, toScale, and t -- that CrossFadedProperty returns via CrossFaded. So here's what you could do support them both in a common way:
- Rename
this._parameterstothis._crossfadeParametersand change the type toCrossfadeParameters. - Move
getCrossfadeParameters()toEvaluationParameters, so the above line in the PR becomesthis._crossfadeParameters = parameters.getCrossfadeParameters(). (This isn't strictly necessary, butgetCrossfadeParameters()seems like it should live onEvaluationParametersper Feature Envy.) - Remove
fromScale,toScale, andtfromCrossFaded<T>and the duplicate calculation of these values fromCrossFadedProperty<T>::_calculate. Instead, have layers that use eitherCrossFadedPropertyorCrossFadedDataDrivenPropertyobtain the calculated values from the same source:this._crossfadeParameters.
There was a problem hiding this comment.
Seems like CrossFaded<T> should become {min: T, mid: T, max: T}. This would allow it to be used in more places, and maybe DRY up some of the duplication in the bucket classes.
There was a problem hiding this comment.
@jfirebaugh {min, mid, max} are only needed for data-driven property evaluation, and to, from is still useful for constant and camera expressions, and since the expression evaluation in the binders returns T, not CrossFaded<T> I've kept the to, from values for now.
I DRYd up the bucket code in f1a06a3 and took your suggestions for reworking getCrossfadeParameters in 9f6bac9. Thank you!!
| fillFeature.id = feature.id; | ||
| } | ||
|
|
||
| this.features.push(fillFeature); |
There was a problem hiding this comment.
Is there a reason that calculating the needed pattern images and iterating over features need to happen at the same time? If not, let's split that into separate steps, so that buckets don't need to create a temporary array of BucketFeatures.
There was a problem hiding this comment.
this follows the pattern that is also used in symbol layout. we could split it into separate steps, but this way avoids repeating the work of parsing features out of the vector layers here:
mapbox-gl-js/src/source/worker_tile.js
Lines 77 to 94 in 2d0c4bc
There was a problem hiding this comment.
I was going to say, since we're restricting *-pattern properties to values where the possible outputs are contained as literals within the expression, we can take advantage of that to calculate the needed pattern images without reference to the actual features. But it looks like you've lifted that restriction in the latest revision. Do you remember the original rationale for it? On the one hand, it's nice that it would allow this optimization, but on the other, I can definitely envision someone wanting to use e.g. "fill-pattern": ["get", "pattern-name"], since that's been so useful for icon-image.
There was a problem hiding this comment.
I think the original reason for the restriction was to avoid the per-feature evaluation before the layout step, but yes it has been lifted and I agree, the ["get", "pattern-name"] use-case is probably going to be one users will appreciate and use.
| */ | ||
|
|
||
| export class CrossFadedDataDrivenProperty<T> extends DataDrivenProperty<?CrossFaded<T>> { | ||
| possibleOutputs: Array<T>; |
There was a problem hiding this comment.
Let's figure out how to avoid adding this state. These classes are already tough to understand and reason about even though their methods avoid stateful side-effects. I fear that adding statefulness to them will add another level of complexity.
|
layout benchmark looking much better after 3cd1985 |
59d62d7 to
ad748a0
Compare
jfirebaugh
left a comment
There was a problem hiding this comment.
Great work here @mollymerp! You took an on an ambitious feature without an obvious solution, drove it to completion without compromising on performance or code quality, and in the process deepened your knowledge of key parts of the codebase and C++ template programming. 👏
I agree, the ["get", "pattern-name"] use-case is probably going to be one users will appreciate and use.
👍 Is a test for this case included?
| FilterSpecification | ||
| } from '../style-spec/types'; | ||
|
|
||
| export type CrossfadeParameters = { |
There was a problem hiding this comment.
This is duplicated in evaluation_parameters.js -- let's import it from there.
37d0687 to
8b723b4
Compare
ansis
left a comment
There was a problem hiding this comment.
I just noted a couple things that jumped out at me but I don't think any of them are clear or important enough to block this pr. Don't let them stop you from merging unless you feel like they are things that should be done before merging.
| setConstantPatternPositions(posTo: ImagePosition, posFrom: ImagePosition) { | ||
| this.patternPositions.patternTo = posTo.tlbr; | ||
| this.patternPositions.patternFrom = posFrom.tlbr; | ||
| } |
There was a problem hiding this comment.
I'm not sure if this is something we need to address in this pr, but:
I think it would be good to get rid of this state and indirect way of setting this values. It would be good if they could be calculated from the values passed to program.draw(...). Maybe by passing patternPositions or imageAtlas through? Another way to think about this could be to evaluate a PossiblyEvaluatedPropertyValue<T> for a specific tile (turn (icon_name, atlas) -> coords).
| addFeature(patternFeature, geometry, index, {}); | ||
| } | ||
|
|
||
| options.featureIndex.insert(feature, geometry, index, sourceLayerIndex, bucketIndex); |
There was a problem hiding this comment.
This method handles a couple non-pattern related things, like loading the geometry and indexing features. Is there anything here that can be separated and shared with buckets that don't have patterns (like circles?). It's very possible that doing this wouldn't make sense. Either way I don't see this as something that needs to block this pr.
…ll-extrusion-pattern properties allow multiple attributes per style-spec property add CrossFadedDataDrivenProp for line-pattern DDS" convert line_pattern shaders to use pragmas create layouts for data-driven line-pattern vertex buffers add source function support for line-pattern to line bucket population and draw code use min, mid, max images for cross-fading data-driven patterns also use tile's IconAtlas for data constant line-pattern extend Binders to support line-pattern properties add initial render test nit fix ensure all possible icons for line-pattern camera funcs are added to the icon atlas make arguments needed for ddpattern required set binder type on property make pattern attributes independent of line layer implement data-driven styling for fill-pattern add dds render test for fill-pattern eliminate black flash on setPaintProperty with a pattern value extend integer-only evaluation to CrossFadedDataDrivenProps address review comments remove getPossibleOutputs and fix rendering extend feature state updating to CrossFadedCompositeBinder use getPossibleOutputs instead of iterating over all features add 1px padding wrap to sprites separate icon and pattern sprites in ImageAtlas to fix wrapping in -pattern properties rename imageAtlas --> iconAtlas now that it holds both icons and pattern images update to use new style-spec expression schema implement fill-extrusion-pattern dds address review comments simplify imageAtlas check remove redundant CrossFaded properties backport #6665 remove unpack function for pattern attrs backport #6745 and fix rebase flubs update with uniform binding state management expose possibleOutputs() at the StylePropertyExpression level add some query tests rebase fix Don't wait for pattern images to layout layers no -pattern property set bonus: remove limitation on non-deterministic expression outputs for pattern properties and reliance on `possibleOutputs()` state remove getPossibleOutputs from CrossFadedDataDrivenProperty refactor CrossFaded and CrossfadeParameters DRY bucket code with util function refactor pattern bucket functions
8b723b4 to
53b2a01
Compare
|
Needed Otherwise, this import wasn't working for me. Just FYI @mollymerp |
|
Thanks for the heads up @charandas -- should be addressed by #7205. |
|
Thanks @jfirebaugh @mollymerp. Did something significant change as to the interface for this using I was able to get I am doing something as simple as: I can also confirm that the feature-state is loaded with the |
|
yep @charandas – thanks for the flag. this was a bug I introduced as part of a performance related refactor. working on a fix now. |








todos:
CrossFadedDataDrivenPropertyor potentially refactorCrossFadedoutline-patternpropertiesLaunch Checklist