Skip to content
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

[css-view-transitions-1] Define behaviour when capturing image for a large element. #8561

Closed
khushalsagar opened this issue Mar 11, 2023 · 10 comments · Fixed by #8819
Closed
Labels
css-view-transitions-1 View Transitions; Bugs only Needs Edits

Comments

@khushalsagar
Copy link
Member

khushalsagar commented Mar 11, 2023

The default behaviour for View Transitions when capturing the image of an element is to paint its entire ink overflow rectangle. But there are cases where this is not possible. A common one is the element exceeding max texture size supported by the hardware on that device. It can technically be done (by tiling the image) but is complicated. It's also likely we'll hit cases where painting the whole element will have significant memory overhead and clipping the snapshot makes sense.

We want the snapshot to include at least the intersection of the element's ink overflow rectangle and the snapshot root (a.k.a the visible viewport). Other than that, it's unclear how prescriptive the spec needs to be here.

  • The spec could recommend that for the area outside the visible viewport, or if the whole element is outside the viewport, the implementation should capture the subset closest to the viewport.
  • The snapshot should be at least as big as the snapshot root.

This issue is tracked here in the spec.

@khushalsagar khushalsagar added Agenda+ css-view-transitions-1 View Transitions; Bugs only labels Mar 11, 2023
@astearns astearns added this to Wednesday - Mar 15 in March 2023 VF2F Mar 11, 2023
@jakearchibald
Copy link
Contributor

jakearchibald commented Mar 15, 2023

Summary for CSSWG:

Problem:

  • When we capture the content of old views, we don't know how they might be animated.
  • Parts of the element currently out-of-view may animate into view during the transition.
  • Some elements are so large, we cannot feasibly capture all of it. Imagine capturing the <body> of the HTML spec.

Proposal:

  • The dimensions of the group is still based on the size of the element.
  • UAs don't need to capture all pixels of the element.
  • They must at least capture the area currently visible.
  • They should capture more than this, to account for animations involving position.

Questions:

  • Is it ok to leave the actual area of calculation down to UAs? That way, the area captured can differ depending on device constraints.
  • What should the natural width/height of the old/new views be in these cases?
  • Can this behaviour be otherwise script-visible?
@khushalsagar
Copy link
Member Author

They must at least capture the area currently within view.

This was a bit confusing for me since the content of the old/new element is also called a view. :) I think you meant to say: "capture the area currently visible"?

What should the natural width/height of the old/new views be in these cases?

Did you mean what should the natural dimensions of the old/new images be in these cases? Didn't follow what "view" was referring to here.

Can this behaviour be otherwise script-visible?

Noting from offline discussion, it can be via object-view-box. The spec states that the natural dimensions of the captured image, without clipping, is the element's ink overflow rectangle. And object-view-box takes care of using the border-box from that image as the intrinsic size for layout here. We could hide this by not exposing object-view-box's computed value to script; and making it !important so authors can't change it to prevent them from learning the value by observing effects on layout.

@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed [css-view-transitions-1] Define behaviour when capturing image for a large element., and agreed to the following:

  • RESOLVED: user agent can limit rasterization for performance limitations, but must size the element as if it was fully rasterized
  • RESOLVED: rasterization must cover at least the visible area of the viewport
The full IRC log of that discussion <emeyer> JakeA: When we capture the content of an old view, we don’t know how it might be animated yet
<emeyer> …Part of the element might be out of view, and could come in view as part of the transition
<emeyer> …Some elements are very large and we can’t feasibly capture all of it
<emeyer> …Image transitioning the body element
<emeyer> …Proposal is, we’ll base the dimensions of the transition group on the element, but say that browsers don’t have to capture every pixel, while suggesting they capture beyond the viewport
<emeyer> …If possible/performant to do so
<emeyer> …Are we okay to handwave like that, so we let UAs act as they see best?
<TabAtkins> q+
<emeyer> …Further, what should the natural width and height of the in- and out-states be when a browser chooses not to capture every pixels?
<emeyer> …Also, should those things be script-visible?
<khush> q+
<emeyer> TabAtkins: I think we should require at least the visible area and some amount outside is good, maybe with a minimum explicit margin like 50% beyond
<Rossen_> ack TabAtkins
<emeyer> …We should expose the size of the thing being captured
<emeyer> fantasai: +1 to Tab, plus the size should be the size it actually is, even if there isn’t painting inside the entire range
<flackr> +1
<emeyer> …We should require or very strongly recommend capturing beyond the viewport as well
<Rossen_> ack fantasai
<emeyer> JakeA: So if there’s a case with many elements layered on top of each other, they’re all in the viewport; if device can’t handle all that, should we just skip the transition?
<emeyer> …I think that’s a good general case, that if a UA doesn’t feel it can handle a given transition, it should skip the transition
<fantasai> s/as well/as well, probably +1 viewport in each direction as the minimum range to capture/
<emeyer> khush: +1 to fantasai, which is kind of what I was implementing anyway, so I’m okay with the spec recommending taking the root as a barometer for how much to expand beyond the visible viewport
<emeyer> …The spec says the natural size of the image is equal to what you capture and everything around it is transparent
<emeyer> …object view box exposes how much the UA decided to paint
<chrishtr> q+
<emeyer> fantasai: I don’t think you should use object view box to set this, even if you use the same computation internally
<emeyer> khush: So the computed value is meant to be ink-overflow-rectangle
<Rossen_> ack khush
<emeyer> chrishtr: We would just not raster things we can’t put into memory, so developers can’t observe anything about this behavior
<emeyer> fantasai: Exactly
<flackr> +1
<vmpstr> q?
<emeyer> JakeA: We can say the exposed value doesn’t say anything about optimizations
<emeyer> vmpstr: I think the object view box is the problem, because we need to make it the same as ink overflow, but if that’s infinite\
<emeyer> flackr: It’s the same as if you hadn’t rendered everything
<emeyer> khush: In the spec it says to paint everything in the ink overflow rectangle
<emeyer> …We pretend an image is the size of the ink overflow rect
<emeyer> chrishtr: A dev wouldn’t know whether we painted the whole thing or not, only the user can tell
<khush> q+
<Rossen_> ack khush
<emeyer> khush: Base case where the UA can raster the whole thing, and element has drop shadow, so ink overflow is bigger than object box
<chrishtr> q+
<emeyer> …UA should compute a view box it applies to the element such that when you render this, the boxes should coincide at the same origin
<emeyer> …If the UA hasn’t painted the whole thing, and the dev msses with the object view box, the effect is the same as if the whole thing was painted
<emeyer> …My question is, to implement this, can we not let devs change object view box?
<emeyer> …When they read it, we can give the the value the spec wants; also the device specs wouldn’t be exposed?
<flackr> q+
<emeyer> fantasai: What is the ink overflow rectangle of something that has a box shadow?
<emeyer> chrishtr: There’s spec language about this
<emeyer> fantasai: Do we want to expose this?
<flackr> q-
<emeyer> khush: You could call getcomputedstyle and don’t see anything, which would make implementation easier
<emeyer> fantasai: That seems better, and if the dev wants to manipulate they don’t have to know about the internals
<emeyer> chrishtr: We could take this and whether we should add a new !important to a new rule
<emeyer> fantasai: I think we agree the rasterization is not exposed to devs but the returned values are spec-consistent
<Rossen_> ack chrishtr
<fantasai> proposal: UA can limit rasterization for perf limitations, but must size the element as if it was fully rasterized
<emeyer> chrishtr: Agreed
<emeyer> JakeA: I think we generally agreed the spec should suggest an overflow amount, was it one viewport in each direction?
<fantasai> proposal: rasterization should cover at least the visible area of the viewport + one viewport in each direction
<emeyer> JakeA: Also in out-of-memory cases the transition is skipped
<fantasai> proposal: if the UA cannot performantly perform the view transition (for any reason) it must skip the transition
<emeyer> TabAtkins: We don’t usually specify memory problem recovery because it can show up whenever
<emeyer> fantasai: I think we have three proposed resolutions
<emeyer> …One (see above)
<TabAtkins> Exception is if OOM actually causes a security issue. In all other cases OOM behavior is explicitly undefined.
<emeyer> …Two look one viewport in each direction for overflow handling
<emeyer> …Three is the error fallback is to skip the transition
<khush> q+
<emeyer> …And that you never do half a transition, you either do all or none
<emeyer> khush: One viewport in each direction should be a recommendation rather than a requirement
<emeyer> …Only requirement is that what’s visible must be captured
<fantasai> proposal: UA can limit rasterization for perf limitations, but must size the element as if it was fully rasterized
<fantasai> proposal: if the UA cannot performantly perform the view transition (for any reason) it must skip the transition
<fantasai> proposal: rasterization should cover at least the visible area of the viewport + one viewport in each direction
<emeyer> RESOLVED: user agent can limit rasterization for performance limitations, but must size the element as if it was fully rasterized
<JakeA> proposal: Must: rasterization should cover at least the visible area of the viewport Should: + one viewport in each direction
<JakeA> proposal: Must: rasterization should cover at least the visible area of the viewport
<flackr> must + should = ?
<JakeA> proposal: Must: rasterization covers at least the visible area of the viewport
<emeyer> RESOLVED: rasterization must cover at least the visible area of the viewport
<emeyer> JakeA: We need to consider the directions and how much overflow SHOULD be captured async
<flackr> q+
<Rossen_> ack khush
<bkardell_> I think no
<emeyer> flackr: Skipping the transition is dev-visible so we’re exposing device capabilities
<emeyer> fantasai: You can limit rasterization but if you’re getting to the point you can’t even transition what’s on-screen, you should skip the whole thing
<bkardell_> q+
<Rossen_> ack bkardell_
<emeyer> flackr: We could not paint the transition but still run it so the dev knows it happened
<emeyer> bkardell_: You’re saying skipping the transition is dev-visible but is it? Because a device can just not support that or animations can be turned off with prefers-reduced-motion
<emeyer> Jake:We’ve decided those can be exposed, but giving away GPU memory is a fingerprint
<emeyer> bkardell_: I see
<emeyer> khush: Is it safer to say that this should silently fail by not painting things?
<emeyer> …We can already hit this with filters and blurs and such and that’s silent in Chrome
<emeyer> JakeA: I think we have to go back and come up with a plan here
<emeyer> Rossen: So we’ll have to postpone this resolution
@noamr
Copy link
Collaborator

noamr commented May 7, 2023

@khushalsagar @atanassov the end of the meeting here is:

<emeyer> JakeA: I think we have to go back and come up with a plan here
<emeyer> Rossen: So we’ll have to postpone this resolution

So I don't think this is resolved yet? Perhaps we should wait with defining this until we resolve the issue around exposing hardware limitations?

@noamr
Copy link
Collaborator

noamr commented May 8, 2023

A note about the previous discussion: it is assumed that the in-viewport rect + buffer is always small enough to be captured. This doesn't take some cases into account. Think of a 500x10000 DIV with transform: rotateX(85deg). It would have most or all of its pre-transformed 5m pixels "in viewport".

IMO the direction of the discussion was good, we should mention that the UA might not capture all the pixels due to hardware constraints, but that those constraints wouldn't affect the observable flow of the transition.

@khushalsagar
Copy link
Member Author

@noamr, the resolution we had on this issue was captured in the comment here.

  1. user agent can limit rasterization for performance limitations, but must size the element as if it was fully rasterized

    This is for the size assigned to the ::view-transition-group pseudo-element. The relevant spec text is here. We resolved that even if the full element can't be captured, this size is independent of that.

  2. rasterization must cover at least the visible area of the viewport

    This resolution needs an edit to the approach defined here. The original spec text was written with the assumption that we'll define the exact subset of the element that the UA has to capture.

    But with this resolution, the UA needs to capture the part of the element which is in the visible viewport. The extra buffer around it can be a recommendation in the spec but is left to the UA.

Think of a 500x10000 DIV with transform: rotateX(85deg). It would have most or all of its pre-transformed 5m pixels "in viewport".

I didn't follow this. Was trying the test case here but I'm likely misunderstanding what you said. This does bring up a good point that when we say "visible area in the viewport", we mean an intersection between the snapshot root and the element's quad based on its screen space transform. The transform for that is already defined in the spec here.

The end of the discussion was about a different point:

<emeyer> fantasai: You can limit rasterization but if you’re getting to the point you can’t even transition what’s on-screen, you should skip the whole thing

Assuming we do have to capture everything in the viewport to be visually correct, but we don't have enough GPU memory to do it, should we abort the transition? And if so, should that failure be visible to authors or silent. This is a rare edge case that can be discussed as a follow up. But it doesn't alter the resolution that the UA must capture what's in the visible viewport and any extra buffer beyond that is a UA detail.

@noamr
Copy link
Collaborator

noamr commented May 9, 2023

Got it. I thought the phrase "So we’ll have to postpone this resolution" meant the whole resolution. Thanks for clarifying.

@noamr
Copy link
Collaborator

noamr commented May 9, 2023

Think of a 500x10000 DIV with transform: rotateX(85deg). It would have most or all of its pre-transformed 5m pixels "in viewport".

I didn't follow this. Was trying the test case here but I'm likely misunderstanding what you said. This does bring up a good point that when we say "visible area in the viewport", we mean an intersection between the snapshot root and the element's quad based on its screen space transform.

A simpler case: a DIV with scale(0.02). It might have infinite pre-transform pixels, all of which are "visibile in the viewport". See https://codepen.io/noamr-the-selector/pen/VwEXPbR

@khushalsagar
Copy link
Member Author

Ah, ok. In this case we can downscale the element's painting so the rasterized output fits within the hardware constraints. The default rendering (outside of this feature) would already do that, elements are rasterized based on their final scale in the viewport.

Your example brings up a good point though that while the spec mandates rasterizing the visible viewport, it doesn't mandate a rasterization quality. This is reasonable. FWIW, a similar CSS feature also doesn't mandate rasterization quality here, but also renders the whole element in the image for this case.

@noamr
Copy link
Collaborator

noamr commented May 10, 2023

OK. I'll prepare a PR based on the resolution, stating that rasterizing the visible viewport is mandated but not in a particular quality.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
css-view-transitions-1 View Transitions; Bugs only Needs Edits
5 participants