-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
Canvas2D layers with filter support #8476
Comments
Tagging folks: @whatwg/canvas. This is the follow up from our conversation earlier in the year about bringing Layers and Filters. Given that Filters was unshipped we still have the opportunity to discuss it further, within this new context of layers. |
WebKit certainly has interesting in a |
Thank you very much for the effort put in this. One thing I'd like to be discussed though is this |
I think the layer should be a general addition to the Canvas and not necessarily tied to the filters. So I have these questions:
Does this mean, the layer dimension will be the current clipped rect of the canvas? Or is it the minimum bounding box of the drawings which are called between |
Hi all, thanks for the feedback!
I understand your position. This is a hard problem and there are no obvious best solutions. What behavior would you expect as a web developer? I'd also be interested in hearing other implementer's opinions on this. The next best option I could think of is to hold on unclosed layer content, across frames, until all layers are closed. But this means that nothing would get rendered at all, which can also be surprising. This option is more complicated to implement. Chromium batches draw calls using a paint op buffer. We would need to implement partial flushing, which doesn't exist right now (to draw previous calls up the first beginLayer). It might also remove some optimization opportunities. For instance, direct-mode rendering (skipping the layer's temporary texture) wouldn't be possible anymore since calls like putImageData would have to be able to update the main canvas without rendering any of the layer’s draw ops. Unless we made these cases raise an exception?
nicer than:
Adding filters to the global context would also add overhead for all the canvas, even though they are only ever used by filters. Take for instance:
This has been discussed in depth in the previous layer proposal (#7329). The key takeaway is that replaying draw commands multiple times is an orthogonal concern to grouping draw calls for filtering purposes. We really should have both: we should be able to create a display list, which contains layers, which apply filters on a group of draw calls. Display list comes with a whole set of different challenges and problems to be resolved. They will also incur runtime overhead not required and desirable for the use cases addressed by layers. Non-display-list layers can unlock high performance code paths not currently accessible via temporary canvas. The display list idea is being tracked separately and has been suggested in the Recorded Picture proposal.
This is exactly how the filter feature was originally suggested, but that option never got full approval. The problem with doing Perhaps a better option that fits along your line of thought would be Kaiido's suggestion in #8476 (comment) to use
That's an implementation detail, left for the browser implementers to figure out. The key point here is that "The browser will produce the drawing equivalent to having a temporary canvas with the minimum size required, given the current transform/clip." In other words, browsers can choose to optimize the temporary texture size as long as users can't tell the difference. |
The new proposal https://github.com/fserb/canvas2D/blob/master/spec/layers.md says that
So if I have
I would expect |
Yes, that's exactly the plan. Although, the part about |
Hi, I would like to gather feedback on some API design decisions. This proposal is currently suggesting a
The CanvasFilter proposal has been approved (see pull request), but it got reverted because of concerns around the Here's an example of the of what the current syntax look like:
Any good API should pass the test of time, it should therefore be flexible and expansible. What if we ever wanted to add new parameters [1] to the
But now, we have two nested objects with I'm curious to hear your thoughts. What would you like the layer+filter API to look like? Do we even want to plan for adding more parameters to [1] I used |
Personally I really think it should be one or the other:
Anything in between is just too complex and unclear. But I'd really like to hear implementers's thoughts on this. |
This adds new beginLayer and endLayer functions to open and close layers in the canvas. While layers are active, draw calls operate on a separate texture that gets composited to the parent output bitmap when the layer is closed. An optional filter can be specified in beginLayer, allowing effects to be applied to the layer's texture when it's composited its parent. Tests: https://github.com/web-platform-tests/wpt/tree/master/html/canvas/element/layers https://github.com/web-platform-tests/wpt/tree/master/html/canvas/offscreen/layers Fixes whatwg#8476
I have made good progress on this proposal. I wrote the specification for this feature, available in this pull request. The spec differs from the original proposal in a few points. The proposal has been updated to reflect these. The changes are:
Everything from the original proposal is implemented in Chromium (behind the
I will start updating Chromium to reflect the changes above, unless there are objections. |
@graveljp did you consider specifying a filter as a CSS |
We could certainly add support for CSS filters, by having the IDL accept either a string or a dictionary. This doesn't have to be part of the MVP however, we could add that support as a follow up. For now, we wanted to see whether we could use this proposal as an opportunity to break free from legacy APIs and do things in a more modern way. |
So to be clear, the "more modern way" here is the setting of the context properties through a dictionary instead of setting each property one by one, right? In that case I don't think it does much improvement if there is a single property that can be set this way and if you still must set all the others in the legacy way. Once again, I strongly believe all the canvas layer state attributes should be defined inside But if by "more modern" you were talking about the opaque canvas state model already in use by As for Anne's point about CSS filters, at the time of the CanvasFilter proposal we could wait for a later iteration since we still could use these in Now, regarding the other points, I still have to make a thorough review of the PR (impressive work btw), but it's interesting that you talk here about the eventuality of disabling antialiasing while |
Thanks for the feedback! There's a lot to unpack in here. Let's break it down. Add
|
Add alpha and compositeOperation arguments in beginLayer()?What I had in mind about passing all the layer rendering states in the So for your example if you wanted all of the fancy button to have its alpha set to 0.5 you'd do ctx.beginLayer({alpha: 0.5}):
drawFancyButton();
ctx.endLayer(); and all the button component would be rendered with the desired alpha. It would be sure that its first Should beginLayer accept CSS filter strings?I wasn't arguing against the use of dictionaries and I don't think nobody was. The thing is that currently, we can't use the simple, and well known CSS ctx.beginLayer({
filter: [
"brightness(1.5)",
{ name: "colorMatrix", type: "luminanceToAlpha" },
"blur(5px)",
"invert(1)",
],
composite: "destination-in"
}); instead of having super complex My point about imageSmoothingEnabled vs. antialiasingI see your point, thanks. I think I was hoping for the canvas layer API to be able to replace your use of the offscreen raster entirely in that example, as this is something that comes up very often. Unclosed layer handlingA thanks for the update, indeed I misread the explainer. I believe there is still an issue with this method in the current PR, but we can handle it there. |
At WebKit our thinking has been the other way around. We'd like the MVP to center around CSS (as most of |
@annevk, are you suggesting to remove the dictionary syntax from this proposal and create a separate proposal for it? Or can I update the current proposal to accept either a CSS string or a dictionary? The CanvasFilter proposal originally had traction and even got submitted, but it got reverted only because using the global @Kaiido, looking back at the list of rendering states, we could interpret them all as applying to layers' output:
Therefore, there is no confusion: all states apply to layers (if you exclude those specific to text, lines, images, etc.) As to whether we should reset the whole context to it's default state inside layers, that might be surprising and inefficient. Whether we like it or not, the canvas rendering context is stateful. |
Yes and that's great, hence why I propose we do the same with all the layer rendering states, not just this one. Having some picked from the context's state and one (for now) as a clear argument is confusing.
But it does exist, it is being used by authors even though WebKit never supported it and can't be removed from the web now. We have to deal with it at least for backward compatibility. I'm ok with ignoring it in layers though. But authors will have to remember it.
Sorry but here I don't follow.
:-). You spent maybe a year of hard thinking about these rules to come up with explanations of why they should or should not be included. Every choice you made had multiple possible alternatives that you did document. I'm pretty confident that your choices are the right ones, but you can't say that "there is no confusion". A web-author will not have spent as much time as you did thinking about these issues. Asking them to do that same reflection process in order to use that feature doesn't seem like great UX. Having three levels of rules about which properties are reset or not in a layer is confusing.
If one uses As for inefficiency I can't tell, but if resetting the context state to their default values is that bad, then it's probably a bad thing that the current PR already asks to do so (step 10).
No. Maybe a compromise could be found through a field added to |
@annevk, the agreement from our March meeting was indeed to start with CSS filter support, sorry I didn't connect the dots before. But wasn't WebKit also interested in supporting color matrix filters? Can we make this part of this first layer spec version?
Maybe this step 10 could be phrased differently. The my intent was to precisely describe which states should be observed in step 11, which invokes the "steps outlined in the drawing model." In practice, implementations don't really have to reset all states. Writing specs is hard! ;) |
Besides the concern that @annevk has voiced (that we should support specifying filters as CSS |
Happy to hear that you started developing this features for WebKit! That's a good point. I agree that auto-closing the layer could trick web developers to leave layers open and they would never notice. This could lead to a proliferation of bugs similar to crbug.com/1299201. Just to confirm, when you say "leave them open", do you mean that frames will be presented without the layer content, until the layer is closed, after which the whole layer is finally presented as a whole? This is option 4 (hold on to incomplete layers, render them once completed), described here, is that correct? I'll have to see if we can implement this in an efficient way in Chromium. We would need to do a partial flush of the canvas (flushing up to the first beginLayer) and copy the remaining paint ops to the next frame's buffer. This would add a cost for sure, but if that cost only happens for unclosed layers and we can print a console message, it would be easily fixed by web developers. Another alternatives we considered was to just drop unclosed layers entirely and not draw any of its content. This could solve the issue in a similar way, without the need to carry over draw calls to future frames. I'm curious to hear your thoughts on this one. |
Yeah that is correct. At least on our side (which uses Core Graphics drawing contexts), we don't have to do any sort of special handling (like partial flush as you said), we just issue draw commands to CG like normally.
I'm not aware of how to implement this in Core Graphics. CG provides two layer-related APIs which we use: one to start a transparency layer, one to end a transparency layer and composite it back to the previous layer. I don't think we have such an API for "throw away the current topmost layer and do not composite it back". |
This adds new beginLayer and endLayer functions to open and close layers in the canvas. While layers are active, draw calls operate on a separate texture that gets composited to the parent output bitmap when the layer is closed. An optional filter can be specified in beginLayer, allowing effects to be applied to the layer's texture when it's composited its parent. Tests: https://github.com/web-platform-tests/wpt/tree/master/html/canvas/element/layers https://github.com/web-platform-tests/wpt/tree/master/html/canvas/offscreen/layers Fixes whatwg#8476
Hi all, I have finalized the Canvas 2D layer specification (#9537). I have added support for CSS filter strings and updated the way unclosed layers are handled according what was agreed in #8476 (comment). @domenic reviewed the spec and I addressed all editorial feedback. We would like to move forward with the approval process. This API could be included in Interop 2024 if it's approved in time. I think that the only remaining open question is around a concerned raised by @Kaiido about whether global rendering states ( What is the opinion of other browser implementers? Our opinion is that if layers didn't observe global rendering states, it would likely cause more confusion than the alternative (e.g. developer will complain that their layer isn't blended, assuming that this certainly must be a bug). In addition, @annevk & @tuankiet65, could you review/approve the spec on behalf of WebKit? @kdashg, does this spec looks good from Gecko's perspective? |
I'll have to delegate to someone else since I'm no longer involved with WebKit. |
Well, to summarize the points I developed in the previous comments I'd say there are 2 very different ways to see this new feature.
ctx.fillStyle = ctx.shadowColor = "red";
ctx.fillRect(30, 30, 40, 40);
ctx.beginLayer();
ctx.fillRect(10, 10, 50, 50); // This one is red, because fillStyle isn't in the list of stuff that get reset in the layer
ctx.shadowBlur = 3;
ctx.strokeStyle = "green";
ctx.strokeRect(0, 0, 20, 20); // green stroke, but no shadow, because shadowColor is in the list and has been reset to transparent
ctx.endLayer();
ctx.strokeRect(70, 70, 20, 20); // black, because even though it's not reset in the layer, it is reset to what it was when we called beginLayer()
As for the argument that compositing states are more states of the parent I do agree, but it seems that it's an argument that would hold for filters too, no? If this is that much of an issue, I'd be ok with having these settings passed in the closing call (current |
Hi Kaiido, Thanks for expanding on your ideas. Just a little note regarding your example for the first point. On the line that says:
Regarding the second point, I think there are two independent questions to consider:
For the analogy you mentioned of using a secondary canvas and drawing it to the main one, the global states would be defaulted inside the secondary canvas, but the main canvas' global states would be used when drawing the secondary canvas to the main one. Your proposal appears to suggest otherwise by saying that global states shouldn't be applied to the layer's output. The analogy seems to support the idea that it would come as a surprise if the global states didn't apply to the layer's output. As for whether the global states should be reset inside the layer, I was saying this in #8476 (comment):
|
This example was to highlight how confusing it is when we do not reset the state inside the layer, and I think it demonstrates this pretty well because, no,there won't be any shadow at all. To have a shadow drawn from a default context state we need to set both As for my point about the second canvas, I too would have preferred a But I'd really like someone else to step in the discussion with us and hear others thoughts on all this, both from implementers and from potential users. |
There is no fundamental reasons why The reason why this initial spec proposal excludes |
This adds new beginLayer and endLayer functions to open and close layers in the canvas. While layers are active, draw calls operate on a separate texture that gets composited to the parent output bitmap when the layer is closed. An optional filter can be specified in beginLayer, allowing effects to be applied to the layer's texture when it's composited its parent. Tests: https://github.com/web-platform-tests/wpt/tree/master/html/canvas/element/layers https://github.com/web-platform-tests/wpt/tree/master/html/canvas/offscreen/layers Fixes whatwg#8476
This is a proposal for adding layers with filter support in the Canvas2D.
Layer support has previously been proposed in #7329. A new CanvasFilter API has also been presented in #5621. Both proposals failed due to concerns around the suggest API and it's implication on performance.
This new proposal aims at addressing these concerns with an API unifying both layers and filters.
https://github.com/fserb/canvas2D/blob/master/spec/layers.md
The proposal discusses corner cases and interactions with other APIs. A detailed analysis of the different options considered is provided. Depending on the outcome of this discussion, we might have to revisit details of the CanvaFilter proposal, which will have to launch simultaneously with the layer API.
The text was updated successfully, but these errors were encountered: