Port asymmetric viewport rendering to gl-js - #8638
Conversation
|
@arindam1993 It prevents out of memory (too many tiles loaded) with large top padding and prevents displaying horizon - aswe need to define design and interaction with horizon. |
astojilj
left a comment
There was a problem hiding this comment.
partial review. continuing tomorrow. Looks nice so far.
| this._calcMatrices(); | ||
| } | ||
|
|
||
| get padding(): EdgeInsetJSON { return this._edgeInsets.toJSON(); } |
There was a problem hiding this comment.
Serving the existing API in native, i didn't like that in Android we use "padding" and in iOS "edgeInsets". is padding here added to keep it close to native implementation?
There was a problem hiding this comment.
I just did it so the public facing term for is it simpler, but happy to change it to whatever.
Maybe @chloekraw , do you have any thoughts?
There was a problem hiding this comment.
For this feature, I think padding is the term that's going to be closest to what web developers who use CSS would expect. inset is most commonly used as a type of border style:
- https://developer.mozilla.org/en-US/docs/Web/CSS/padding
- https://developer.mozilla.org/en-US/docs/Web/CSS/border-style
I did find this https://developer.mozilla.org/en-US/docs/Web/CSS/inset "experimental technology" but it sounds like this inset is more about defining margins around blocks of text and is less analogous to a viewport. (related: https://developer.mozilla.org/en-US/docs/Web/CSS/inset-block, https://developer.mozilla.org/en-US/docs/Web/CSS/inset-inline)
@arindam1993 are there other parts of the GL-JS code base that also use this idea of padding?
There was a problem hiding this comment.
@arindam1993 , related to this - what's your opinion about propagating CSS style padding specified to Map's parent HTML container to transform?
There was a problem hiding this comment.
@chloekraw I don't think so.
@astojilj I don't think we should, I think devs waning to create floating UI elements, which this feature is designed for would want to set a padding on the container, since it'd offset all their UI elements which I assume would be appended as children fo the map container.
However I think it might be useful to push the current state of padding to our own CSS classes and DOM elements which we use for nesting NavigationControl and AttributionControl

top,left, right and bottom attributes are always 0, i think we can update them from within Map.
Having Map be the only API entrypoint seems more intuitive to me, with plugins automatically respecting it as well.
| this._calcMatrices(); | ||
| } | ||
|
|
||
| get padding(): EdgeInsetJSON { return this._edgeInsets.toJSON(); } |
There was a problem hiding this comment.
@arindam1993 , related to this - what's your opinion about propagating CSS style padding specified to Map's parent HTML container to transform?
astojilj
left a comment
There was a problem hiding this comment.
In addition to questions about PaddingOptions and usefulness of padding color shader...
I think it might be useful to add a test that is combining usage of PaddingOptions (Map fitBounds) and camera.setPadding. If we need to have both mechanisms in API , good to document the difference.
| } | ||
|
|
||
| export type EdgeInsetLike = EdgeInsets | {top?: number, bottom?: number, right?: number, left?: number} | EdgeInsetJSON; | ||
| export type EdgeInsetJSON = {top: number, bottom: number, right: number, left: number} |
There was a problem hiding this comment.
Why not using PaddingOptions?
There was a problem hiding this comment.
I agree that padding might be a better name here since that is what is exposed.
|
|
||
| export default drawDebug; | ||
|
|
||
| const topColor = new Color(1, 0, 0, 0.4); |
There was a problem hiding this comment.
Does draw_debug.js go to production code?
I'm not sure how useful is to including additional shader and this code for rendering different colors or padding values.
For manual debugging, propose to add additional debug/***.html page (doesn't go to production code AFAIK) with overlays (in the way the customers are supposed to use it)...
- manually test the debug page
and in that that page would be used for this step.
Please doublecheck my opinion with other reviewers - I might be missing something...
There was a problem hiding this comment.
draw_debug does go into the final bundle, and we do publish docs for showTileBoundaries and showCollisionBoxes. My idea was to make a similar thing for padding as well.
https://docs.mapbox.com/mapbox-gl-js/api/#map#showtileboundaries
I'm also using the in-gl rendering of the padding as a render-test, similar to tile boundaries and collision box debugging.
And regarding the debug page, I agree, I can add one.
There was a problem hiding this comment.
Does draw_debug.js go to production code?
Having this as part of the built-in debug shaders allows easily debugging issues when building/updating work in this area. I can see it being very useful for debugging flyTo animations for example.
That said, this PR adds a total of +4.13 kB to the gl-js bundle. Is there a simpler debug option that can be built?
There was a problem hiding this comment.
Thanks for adding a debug view. They can be pretty useful for understanding things. If the size and complexity of the shader another approach could be to use a gl scissor and clear to draw a line at each edge.
gl.enable(gl.SCISSOR_TEST);
gl.scissor(x, y, width, height);
context.clear({ color });
gl.disable(gl.SCISSOR_TEST);There was a problem hiding this comment.
^^ I did this, it is much simpler though it only saved half a kb of bundle size.
asheemmamoowala
left a comment
There was a problem hiding this comment.
Do custom layers have access to the modified transform that allows correctly rendering when padding values are set?
ansis
left a comment
There was a problem hiding this comment.
Thanks for porting this!
I left a couple comments in places I think could be a bit clearer. The only user facing issue I found was the interpolation one
| if (target.left != null) this.left = number(this.left, target.left, t); | ||
| if (target.right != null) this.right = number(this.right, target.right, t); | ||
|
|
||
| return this; |
There was a problem hiding this comment.
This interpolates the padding between the previous frame's value and the target instead of from the starting value to the target. Switching to the more functional approach used for other interpolation could be a good idea.
| const x = Math.min(this.left, width) + 0.5 * (width - totalXInset); | ||
| const y = Math.min(this.top, height) + 0.5 * (height - totalYInset); | ||
|
|
||
| return new Point(x, y); |
There was a problem hiding this comment.
Would this be simpler as this?
const x = (width + this.left - this.right) / 2;
const y = (height + this.top - this.bottom) / 2;
The main difference would be that in the case where left + right > width it would take equal amounts off of both sides rather than only compromising on the right side. Not sure which is preferable.
There was a problem hiding this comment.
I think the simpler one you suggested is better!
Do you think we should throw an error/warning when the overflow happens?
| } | ||
|
|
||
| export type EdgeInsetLike = EdgeInsets | {top?: number, bottom?: number, right?: number, left?: number} | EdgeInsetJSON; | ||
| export type EdgeInsetJSON = {top: number, bottom: number, right: number, left: number} |
There was a problem hiding this comment.
I agree that padding might be a better name here since that is what is exposed.
| //Apply center of perspective offset | ||
| m[8] = -offset.x * 2 / this.width; | ||
| m[9] = offset.y * 2 / this.height; | ||
|
|
There was a problem hiding this comment.
Can this be done using the methods provided by mat4 (translate, multiply, etc) rather than directly editing the matrix directly? It took me a while to understand the math here
There was a problem hiding this comment.
the only api method that allows that I think can work is the asymmetric view frustum method but that takes 4 different fov's as input, and back calculating that seemed more janky to me.
| } | ||
|
|
||
| if (paddingChanged) { | ||
| this.fire(new Event('paddingstart', eventData)) |
There was a problem hiding this comment.
What do you think these events might be used for? seems reasonable but I can't think of any clear examples right now
There was a problem hiding this comment.
Some kind of UI state synchronization?
for example, if you want a floating sidebar to be extended out exactly as much as the map's current value of padding, you can use the events to synchronize that state.
There was a problem hiding this comment.
Given the use case, I think it's an unlikely case — you set the map padding because of a floating sidebar, so the state goes in the UI -> map direction, I can't think of use cases with the other way around.
There was a problem hiding this comment.
tru, removed them for simplicity!
|
|
||
| export default drawDebug; | ||
|
|
||
| const topColor = new Color(1, 0, 0, 0.4); |
There was a problem hiding this comment.
Thanks for adding a debug view. They can be pretty useful for understanding things. If the size and complexity of the shader another approach could be to use a gl scissor and clear to draw a line at each edge.
gl.enable(gl.SCISSOR_TEST);
gl.scissor(x, y, width, height);
context.clear({ color });
gl.disable(gl.SCISSOR_TEST);…mmetric-viewport # Conflicts: # src/geo/transform.js # src/render/draw_debug.js # src/render/program/debug_program.js # src/render/program/program_uniforms.js
|
@asheemmamoowala @astojilj @ansis , I think I've addressed your previous comments, could you all please take another pass at this, I think this is ready to go now |
|
Also, @chloekraw its all called padding now |
mourner
left a comment
There was a problem hiding this comment.
Just noting that +1kb to the min-zipped bundle seems like a big bump for the seemingly small change — anything we could do to simplify the code? Pointed out a few options.
| } | ||
|
|
||
| if (paddingChanged) { | ||
| this.fire(new Event('paddingstart', eventData)) |
There was a problem hiding this comment.
Given the use case, I think it's an unlikely case — you set the map padding because of a floating sidebar, so the state goes in the UI -> map direction, I can't think of use cases with the other way around.
| context.clear({color}); | ||
| gl.disable(gl.SCISSOR_TEST); | ||
| } | ||
|
|
There was a problem hiding this comment.
Do you anticipate this debug code being used often, or did it already serve its purpose since the asymmetric viewport implementation is complete? Just noting an opportunity to potentially cut this for smaller bundle size impact.
There was a problem hiding this comment.
Not super often, no actually, but it seems like an useful tool to have for render tests, examples and debug pages. I could make it so it is only available in the dev build, but it will break our usual convention.
There was a problem hiding this comment.
I've also added some new render tests in the newest commit that represents the distortion caused by asymmetric viewport much better.
https://github.com/mapbox/mapbox-gl-js/pull/8638/files#diff-633f8e084d5e1ba0bb38d2c145e1b771
| if (target.top != null && start.top != null) this.top = number(start.top, target.top, t); | ||
| if (target.bottom != null && start.bottom != null) this.bottom = number(start.bottom, target.bottom, t); | ||
| if (target.left != null && start.left != null) this.left = number(start.left, target.left, t); | ||
| if (target.right != null && start.right != null) this.right = number(start.right, target.right, t); |
There was a problem hiding this comment.
This looks a little involved — can we get rid of the ifs by defaulting them to 0?
There was a problem hiding this comment.
this logic ensures that the if the key is not present the value does not change change from the existing value logic for adding padding. Defaulting to 0 would remove the padding. and having it here ensures that that logic does not have to be implemented in every public interface function that accepts padding as input.
| export type PaddingOptions = {top: ?number, bottom: ?number, right: ?number, left: ?number}; | ||
| export type SerializedPadding = {top: number, bottom: number, right: number, left: number}; | ||
|
|
||
| export default EdgeInsets; |
There was a problem hiding this comment.
If we simplify this class somewhat, could it be small enough to the point it's simpler to have this state as a part of transform rather than a separate class?
There was a problem hiding this comment.
It could be, but this seems easier to write unit tests for, and handles a lot of the validation and updating logic without adding additional private methods to the transform class.
Co-Authored-By: Vladimir Agafonkin <agafonkin@gmail.com>
…port with a crosshatch road pattern
|
YAY merging!!!! thanks everyone for the detailed review! 🙇 |



Addresses the following issues:
#8328
#4268
and ports @astojilj 's amazing work to the js side.
API:
Users can now specify global
left,right,topandbottompadding values (in pixels) in in apaddingfield in camera functions i.ejumpTo,easeToandflyTo.Example:
Similar to other camera options the existing padding value is retained if a parameter is unspecified in the call.
Also, similar to @astojilj 's design, the rendering changes are implemented accounting for the offset center elements 8 and 9 of
Transform#projMatrix, inTransform#_calcMatrices(), while ensuring that everything that reads fromTransform#getCenterdoesn't break with the center being offset.Testing:
EdgeInsetsclass that tests its behavior in isolationcamerafor testing if its interface oh settingpaddingbehave as expected.Left padding rendered in blue,
Bottom padding rendered in green
Right padding rendered in magenta
Top padding rendered in red
This can be turned on with the map option
showPadding.showPaddingset totrueDemo:
Launch Checklist
paddinginfitBoundsand padding for camera options for asymmetric viewportMap API functions such as
easeToandflyTonow supportpadding: PaddingOptionswhich lets developers shift the center of perspective for a map when building floating sidebars.