Skip to content

Port asymmetric viewport rendering to gl-js - #8638

Merged
arindam1993 merged 33 commits into
masterfrom
feat/asymmetric-viewport
Feb 13, 2020
Merged

Port asymmetric viewport rendering to gl-js#8638
arindam1993 merged 33 commits into
masterfrom
feat/asymmetric-viewport

Conversation

@arindam1993

@arindam1993 arindam1993 commented Aug 15, 2019

Copy link
Copy Markdown
Contributor

Addresses the following issues:
#8328
#4268
and ports @astojilj 's amazing work to the js side.

API:

Users can now specify global left, right, top and bottom padding values (in pixels) in in a padding field in camera functions i.e jumpTo, easeTo and flyTo.
Example:

map.easeTo({{
    padding: {
         left: 400,
         right: 100
    } 
)};

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, in Transform#_calcMatrices(), while ensuring that everything that reads from Transform#getCenter doesn't break with the center being offset.

Testing:

  • Unit tests for EdgeInsets class that tests its behavior in isolation
  • Unit tests in camera for testing if its interface oh setting padding behave as expected.
  • Added debug rendering for padding:
    Screen Shot 2019-08-19 at 3 21 36 PM
    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.
  • Add render tests with showPadding set to true

Demo:

2019-08-14 18 34 46

Launch Checklist

  • briefly describe the changes in this PR
  • write tests for all new functionality
  • document any changes to public APIs
  • manually test the debug page
  • address overlap between padding in fitBounds and padding for camera options for asymmetric viewport
    Map API functions such as easeTo and flyTo now support padding: PaddingOptions which lets developers shift the center of perspective for a map when building floating sidebars.

@arindam1993
arindam1993 requested review from ansis and astojilj August 15, 2019 01:35
@astojilj

astojilj commented Aug 19, 2019

Copy link
Copy Markdown
Contributor

@arindam1993
There is a follow up work that needs to be ported:
mapbox/mapbox-gl-native#15195

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 astojilj left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

partial review. continuing tomorrow. Looks nice so far.

Comment thread src/geo/edge_insets.js Outdated
Comment thread src/geo/transform.js Outdated
this._calcMatrices();
}

get padding(): EdgeInsetJSON { return this._edgeInsets.toJSON(); }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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?

@chloekraw chloekraw Aug 19, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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:

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?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@arindam1993 , related to this - what's your opinion about propagating CSS style padding specified to Map's parent HTML container to transform?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@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
Screen Shot 2019-08-20 at 3 21 37 PM
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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Here's a demo of what I mean.

2019-08-20 19 33 01

Comment thread test/unit/ui/camera.test.js
Comment thread src/geo/transform.js Outdated
this._calcMatrices();
}

get padding(): EdgeInsetJSON { return this._edgeInsets.toJSON(); }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@arindam1993 , related to this - what's your opinion about propagating CSS style padding specified to Map's parent HTML container to transform?

@arindam1993 arindam1993 mentioned this pull request Aug 21, 2019
7 tasks

@astojilj astojilj left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

Comment thread src/geo/edge_insets.js Outdated
}

export type EdgeInsetLike = EdgeInsets | {top?: number, bottom?: number, right?: number, left?: number} | EdgeInsetJSON;
export type EdgeInsetJSON = {top: number, bottom: number, right: number, left: number}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why not using PaddingOptions?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I agree that padding might be a better name here since that is what is exposed.

Comment thread src/render/draw_debug.js Outdated

export default drawDebug;

const topColor = new Color(1, 0, 0, 0.4);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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...

@arindam1993 arindam1993 Aug 27, 2019

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

^^ I did this, it is much simpler though it only saved half a kb of bundle size.

@arindam1993 arindam1993 self-assigned this Aug 27, 2019

@asheemmamoowala asheemmamoowala left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do custom layers have access to the modified transform that allows correctly rendering when padding values are set?

@asheemmamoowala asheemmamoowala changed the title Port asymmetric viewport rendering to gl-js [dnm] Port asymmetric viewport rendering to gl-js Aug 29, 2019

@ansis ansis left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Comment thread src/geo/edge_insets.js
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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

Comment thread src/geo/edge_insets.js
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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think the simpler one you suggested is better!
Do you think we should throw an error/warning when the overflow happens?

Comment thread src/geo/edge_insets.js Outdated
}

export type EdgeInsetLike = EdgeInsets | {top?: number, bottom?: number, right?: number, left?: number} | EdgeInsetJSON;
export type EdgeInsetJSON = {top: number, bottom: number, right: number, left: number}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I agree that padding might be a better name here since that is what is exposed.

Comment thread src/geo/transform.js
//Apply center of perspective offset
m[8] = -offset.x * 2 / this.width;
m[9] = offset.y * 2 / this.height;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

Comment thread src/ui/camera.js Outdated
}

if (paddingChanged) {
this.fire(new Event('paddingstart', eventData))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What do you think these events might be used for? seems reasonable but I can't think of any clear examples right now

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

tru, removed them for simplicity!

Comment thread src/render/draw_debug.js Outdated

export default drawDebug;

const topColor = new Color(1, 0, 0, 0.4);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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);

Arindam Bose added 4 commits September 9, 2019 14:33
…mmetric-viewport

# Conflicts:
#	src/geo/transform.js
#	src/render/draw_debug.js
#	src/render/program/debug_program.js
#	src/render/program/program_uniforms.js
@arindam1993

Copy link
Copy Markdown
Contributor Author

@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

@arindam1993

arindam1993 commented Feb 6, 2020

Copy link
Copy Markdown
Contributor Author

Also, @chloekraw its all called padding now

@mourner mourner left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

Comment thread src/ui/camera.js Outdated
}

if (paddingChanged) {
this.fire(new Event('paddingstart', eventData))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

Comment thread src/render/draw_debug.js
context.clear({color});
gl.disable(gl.SCISSOR_TEST);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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

Comment thread src/geo/edge_insets.js
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);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This looks a little involved — can we get rid of the ifs by defaulting them to 0?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

Comment thread src/geo/edge_insets.js
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;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

Comment thread src/geo/transform.js Outdated
@arindam1993

Copy link
Copy Markdown
Contributor Author

Added some fresh render tests that visualize the effect of the distortion better.
expected
expected

@arindam1993

Copy link
Copy Markdown
Contributor Author

YAY merging!!!! thanks everyone for the detailed review! 🙇

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.

7 participants