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

fix(IText): **NO** clear context top during rendering #8560

Merged
merged 20 commits into from Jan 6, 2023
Merged

Conversation

ShaMan123
Copy link
Member

@ShaMan123 ShaMan123 commented Jan 2, 2023

Motivation

There is an edge case where while using context top (free drawing) and a render occurs text instances render and clear their bbox from context top.
They shouldn't
Take a look at the video ending in a mouse up.

fabric.js.sandbox.-.Google.Chrome.2023-01-02.15-26-25_Trim.mp4

Description

clearContextTop suffers from a built in problem.
It clears the context with the known width/height values.
Efforts were made to overcome that (quite a lot it seems) and most of cases (distinct case no. 1, see below) were indeed fixed but not all (e.g. transforming). It did cause a need to track every case and clear the context before the change propagates to the instance, which is folly. More interaction, more cases, a mess.

There are 2 distinct cases in which we should consider how to act:

  1. changing selection only (which is not part of the rendering cycle due to perf, imperative in sight of context state)
  2. changing visuals (part of rendering, declarative in sight of context state)

Seems that efforts were focused on clearing context for the first case but the second case was neglected (rendering, transforming and many others). It can be understood since the first case was/is the main dev effort for making itext work. The second case is an integration thing, app level.

Changes

removed clearing context top:

  • vpt change
  • size change: initDimensions

Gist

The render method calls renderCursorOrSelection that runs only if in editing mode.
If executed it marks top context as dirty for the canvas rendering cycle.

Looking at what brush does and applying it to itext:
If the entire canvas is rendered it is 100% proof since the top context will be cleared by the canvas and then selection will be drawn by the instance that is being edited.
If we mess with the context w/o rendering canvas to handle selection as we do with key/input events then we must clear context top as part of logic and we must take into account any changes to size of instance and we should mark top context as dirty as well.
So the rule of thumb should be if we mess with selection alone then we call clearContextTop.

initDimensions is a good example for the confusion around this. The context was cleared before changes to size were committed to avoid leaving traces, but even though it was not 100% proof. Since canvas must be rendered in order for the new size to be visible it is a pure no. 2 distinct case so we can safely hook to the rendering cycle.

In Action

@ShaMan123 ShaMan123 added the text label Jan 2, 2023
@github-actions
Copy link
Contributor

github-actions bot commented Jan 2, 2023

Build Stats

file / KB (diff) bundled minified
fabric 931.649 (-0.400) 300.759 (-0.213)

@github-actions
Copy link
Contributor

github-actions bot commented Jan 2, 2023

Coverage after merging itext-render-fix into master will be

83.35%

Coverage Report
FileStmtsBranchesFuncsLinesUncovered Lines
index.js35%12.50%0%54.55%430, 432, 432, 432, 432, 432, 434–435, 437, 437, 437–438
src
   cache.ts96.97%90%100%100%56
   config.ts77.27%66.67%66.67%84.62%130, 138–140, 151–153
   constants.ts100%100%100%100%
   env.ts72.73%53.33%100%79.17%22, 22–23, 23, 23, 25, 25, 27, 29, 31–32, 62
   intersection.class.ts100%100%100%100%
   pattern.class.ts92.19%85.71%100%96.30%118, 124, 135, 144, 96
   point.class.ts100%100%100%100%
   shadow.class.ts98.48%96%100%100%156
src/brushes
   base_brush.class.ts100%100%100%100%
   circle_brush.class.ts0%0%0%0%100, 102–104, 113, 113, 113, 115, 117, 119–121, 123–126, 134, 141, 143, 23, 28–29, 37–41, 45–49, 56–59, 67–71, 73, 81, 81, 81, 81, 81–82, 84, 84, 84–87, 89, 97–98
   pattern_brush.class.ts97.06%87.50%100%100%21
   pencil_brush.class.ts91.86%85.42%100%93.58%122–123, 152, 152–154, 276, 280, 285–286, 68–69, 84–85
   spray_brush.class.ts0%0%0%0%100–101, 103–104, 112, 112, 112, 112, 112–113, 115–116, 123–124, 126, 128–132, 141, 145–146, 146, 154, 154, 154–157, 159–162, 166–167, 169, 171–174, 177, 184–185, 187, 189–190, 192, 199–200, 202–203, 206, 206, 213, 213, 217, 22–23, 25–27, 27, 27–29, 33, 42, 49, 56, 63, 70, 77, 89–91, 99
src/canvas
   canvas.class.ts93.15%89.14%94%95.93%1153, 1153–1154, 1157, 1177, 1177, 1239, 1275–1276, 1295–1296, 1304–1305, 1326, 1334, 1447–1448, 1450–1451, 1471–1472, 509, 576–577, 582, 592, 721–722, 724–725, 725, 725, 771–772, 833–834, 887–889, 919, 924–925, 954–955
   canvas_events.ts78.22%75.45%82.81%79.45%1007–1008, 1008, 1008–1010, 1012–1013, 1013, 1013, 1015, 1023, 1023, 1023–1025, 1025, 1025, 1031–1032, 1040–1041, 1041, 1041–1042, 1047, 1049, 1079–1081, 1084–1085, 1089–1090, 1188, 1208–1210, 1213–1214, 1286, 1406, 145, 1500–1501, 1507, 1511–1512, 1528, 1550, 1597, 1602, 1641, 170, 318–319, 319, 319–320, 320, 323–327, 332, 334, 347–350, 353, 372, 372, 372–373, 373, 373–374, 382, 387–388, 388, 388–389, 391, 400, 406–407, 407, 407, 443, 447, 447, 447, 447, 447–448, 450, 525, 527, 527, 527–529, 549, 551, 551, 551–552, 552, 552, 555, 555, 555–556, 559, 568–569, 571–572, 574, 574–575, 577–578, 590–591, 591, 591–592, 594–598, 604, 611, 651, 651, 651, 653, 655–659, 665, 671, 671, 671–672, 674, 677, 682, 695, 722, 778–779, 779, 779–780, 782, 785–786, 786, 786–787, 789–790, 793, 793–795, 798–799, 809, 891, 905, 912, 933, 965, 986–987, 993
   static_canvas.class.ts94.56%88.97%97.92%97.11%1125–1126, 1126, 1126–1127, 1247, 1257, 1311–1312, 1315, 1350–1351, 1429, 1438, 1443, 1492–1493, 1721, 1721–1722, 1772, 1775, 1778, 1778, 1778, 1781, 1784, 1784, 1784, 309, 345, 358, 411–412, 414–415, 424, 428–429, 432–433, 885
src/color
   color.class.ts92.16%86.49%100%94.29%330–331, 335–336, 339–340, 58, 88–89, 89, 91, 91, 91–92, 94–95
   color_map.ts100%100%100%100%
   constants.ts100%100%100%100%
   util.ts100%100%100%100%
src/controls
   changeWidth.ts100%100%100%100%
   control.class.ts93.90%88.89%90.91%97.73%235, 319, 319, 354
   controls.render.ts81.63%78%100%84.78%106, 111, 121, 121, 45, 50, 61, 61, 65–72, 81–82
   default_controls.ts86.67%66.67%100%100%122, 129
   drag.ts100%100%100%100%
   rotate.ts20%12.50%50%22.22%45, 51, 51, 51–52, 55–57, 59, 59, 59, 59, 59–61, 61, 61–63, 65, 65, 65–67, 67, 67–68, 73, 73, 73–74, 76, 78, 80–81
   scale.ts93.41%92.68%100%93.59%129–130, 132–134, 148–149, 181–183, 42
   scaleSkew.ts78.79%64.29%100%85.71%27, 29, 29, 29, 31, 33, 35
   skew.ts91.03%79.31%100%97.67%130–131, 162–163, 170, 176, 178
   util.ts100%100%100%100%
   wrapWithFireEvent.ts100%100%100%100%
   wrapWithFixedAnchor.ts100%100%100%100%
src/filters
   2d_backend.class.ts92%83.33%100%93.75%35–36
   FilterBackend.ts88.89%88.89%100%85.71%15–16
   WebGLProbe.ts40.54%40%60%36.36%28–30, 30, 30–31, 33–35, 43, 46–48, 48, 48–51, 53, 58
   base_filter.class.ts20.83%20.83%33.33%18.18%102–104, 104, 104–105, 112–115, 115, 115–116, 122, 122, 122–125, 143, 173–178, 182–183, 183, 183–186, 186, 186, 186, 186–188,

@ShaMan123 ShaMan123 requested a review from asturur January 2, 2023 13:25
@@ -273,7 +273,7 @@ export class IText extends ITextClickBehaviorMixin<ITextEvents> {
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
render(ctx: CanvasRenderingContext2D) {
this.clearContextTop();
// this.isEditing && this.clearContextTop();
Copy link
Member Author

@ShaMan123 ShaMan123 Jan 2, 2023

Choose a reason for hiding this comment

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

Thoughts?

Copy link
Member

Choose a reason for hiding this comment

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

Let's use blame and lets' see when it was introduced and in which context.

Copy link
Member

Choose a reason for hiding this comment

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

image

6 years ago....

Copy link
Member

@asturur asturur Jan 3, 2023

Choose a reason for hiding this comment

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

Linked to those 2 issues:
#3378
and
#3386
In the PR it was linked to this clearContextTop
https://github.com/fabricjs/fabric.js/pull/3388/files#diff-b8bf422caf940e8585d2c4dac83f8ef76ceb2a1c0ffbf986202492dad12a7cfaR335-R348
That was already safeguarded for isEditing and this.active ( that does not exist anymore )

Copy link
Member

Choose a reason for hiding this comment

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

in this PR here
https://github.com/fabricjs/fabric.js/pull/7802/files#diff-b8bf422caf940e8585d2c4dac83f8ef76ceb2a1c0ffbf986202492dad12a7cfaL281-L291
you removed clearContextTop from iText and you added it to generic object, but you lost the isEditing boolean.
So i would argue that the isEditing boolean should come back. no?

Copy link
Member Author

Choose a reason for hiding this comment

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

I am to blame indeed.
Why did I move it there in the first place? Does it make sense??
#3386 seems unrelated.
#3378 is, however the bug wasn't solved for all cases. I will add tests proving that.
I think I understand my confusion now.
https://github.com/fabricjs/fabric.js/blame/d6f9c8f98813717372c1510193edae123dbfba11/src/shapes/itext.class.js#L303-L320

Copy link
Member

Choose a reason for hiding this comment

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

i think it was moved to support dragging and in the process we did not notice the isEditing.
Is enough to add it back i suppose and add a test for isEditing.

Copy link
Member Author

@ShaMan123 ShaMan123 left a comment

Choose a reason for hiding this comment

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

next topic is clearContextTop under initDimensions
I would argue this is not needed since canvas will have to render in order for visuals to update and now since contextTopDirty is flagged this is taken care of.
Thoughts?

tests after this

this.clearContextTop();
if (this.isEditing) {
this.initDelayedCursor();
this.clearContextTop();
Copy link
Member Author

Choose a reason for hiding this comment

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

and here

this.clearContextTop();
if (this.isEditing) {
this.initDelayedCursor();
this.clearContextTop();
Copy link
Member Author

Choose a reason for hiding this comment

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

here

src/shapes/itext.class.ts Show resolved Hide resolved
src/canvas/canvas.class.ts Show resolved Hide resolved
@asturur
Copy link
Member

asturur commented Jan 3, 2023

it become a large change to understand at a glance.
Can you describe it at your best with words?

Copy link
Member Author

@ShaMan123 ShaMan123 left a comment

Choose a reason for hiding this comment

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

Explaining what is going on:
clearContextTop suffers from a built in problem.
It clears the context with the known width/height values.
Efforts were made to overcome that (quite a lot it seems) and most of cases (distinct case no. 1, see below) were indeed fixed but not all (e.g. transforming). It did cause a need to track every case and clear the context before the change propagates to the instance, which is folly. More interaction, more cases, a mess.

The are 2 distinct cases in which we should consider how to act:

  1. changing selection only (which is not part of the rendering cycle due to perf, imperative in sight of context state)
  2. changing visuals (part of rendering, declarative in sight of context state)

Seems that efforts were focused on clearing context for the first case but the second case was neglected (rendering, transforming and many others). It can be understood since the first case was/is the main dev effort for making itext work. The second case is an integration thing, app level.

It is confusing. Some parts must be handled by the instance for perf reasons, same as brush.
But then we must have a mechanism that handles rendering.

Is that a good enough explanation?

The only immune way to cases is to mark the context as dirty and let the rendering cycle handle it. Same a brush does.
initDimensions is a good example. The context was cleared before changes to size were committed to avoid leaving traces, but even though it is not 100% proof. Since canvas must be rendered in order for the new size to but visible we can safely hook to the rendering cycle.

@ShaMan123
Copy link
Member Author

ShaMan123 commented Jan 3, 2023

Another thing that came to me is that Brush#needsFullRender should have kicked in and rendered the entire path while drawing. Looking into the code, top context is rendered before main context and since text render method clears the top context this is what happened

There is an edge case where while using context top (free drawing) and a render occurs text instances render and clear their bbox from context top. They shouldn't Take a look at the video ending in a mouse up.

fabric.js.sandbox.-.Google.Chrome.2023-01-02.15-26-25_Trim.mp4

@ShaMan123
Copy link
Member Author

updated my explanation a bit

if (shouldClear) {
this.clearContextTop();
}
shouldClear && this.clearContextTop();
Copy link
Member

Choose a reason for hiding this comment

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

we should investigate if the minifier does this for us and in case stop doing it.

Copy link
Member Author

Choose a reason for hiding this comment

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

in case it doesn't, u mean?

Copy link
Member Author

Choose a reason for hiding this comment

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

how? everything is unreadable

Copy link
Member

Choose a reason for hiding this comment

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

i think the minifier swap if (condition) { single line } for condition && singleline automatically

Copy link
Member

Choose a reason for hiding this comment

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

not an issue of this pr but i don't think it make sense to be terse manualy now

@@ -306,19 +296,10 @@ export class IText extends ITextClickBehaviorMixin<ITextEvents> {
} else {
this.renderSelection(ctx, boundaries);
}
this.canvas!.contextTopDirty = true;
Copy link
Member

@asturur asturur Jan 3, 2023

Choose a reason for hiding this comment

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

Ok the difference here is that swapping this with the one from our render function here https://github.com/fabricjs/fabric.js/pull/8560/files#diff-876b5f345ecc11628b120c60dfe0b3fde14630cc3940551bcb4ed23f41c580bcL275 we are swapping a partial contextTop clear with a full contextTop clear.

That is fine if it is needed and solves bugs.
Is that the case?

Copy link
Member Author

Choose a reason for hiding this comment

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

indeed
re read my explanation. it is now clearer I hope. redone it

Copy link
Member Author

Choose a reason for hiding this comment

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

The render method calls renderCursorOrSelection that runs only if in editing mode.
If the entire canvas is rendered it is 100% proof since the top context will be cleared by the canvas and then selection will be drawn by the instance that is being edited.
If we mess with the context outside w/o rendering canvas as we do with key/input events then we must clear context top as part of logic and we must take into account any changes to size of instance

Copy link
Member Author

Choose a reason for hiding this comment

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

I will add this to the decription

CHANGELOG.md Outdated Show resolved Hide resolved
@ShaMan123
Copy link
Member Author

ShaMan123 commented Jan 3, 2023

@asturur I have updated description and I am proud of it now.
Tests reveal the fix isn't complete Tests are a shit storm

@ShaMan123
Copy link
Member Author

ShaMan123 commented Jan 3, 2023

@asturur I am baffled
The goldens I've committed are correct.
But... freak show time.
When testing in my machine some weird shit is going on.
Look
image

I was thinking it is retina scaling, but I am not sure where the f*** it is

@ShaMan123
Copy link
Member Author

So I am fed up with this shit and in reality I fixed it 🤬😡😖😱.
Please help 😩😔😵

@ShaMan123
Copy link
Member Author

ShaMan123 commented Jan 3, 2023

Found the regression causing this. see #8520 cba9325

@ShaMan123
Copy link
Member Author

ShaMan123 commented Jan 3, 2023

after merging #8520 or #8565 tests pass!

@asturur
Copy link
Member

asturur commented Jan 5, 2023

Sorry i m a bit busy today but i m free tomorrow.
I ll try to see if i understand what is going on.

@asturur
Copy link
Member

asturur commented Jan 5, 2023

Merged the retina fix.

@asturur
Copy link
Member

asturur commented Jan 6, 2023

Merging now, great to have added tests.

I had to revert the pure stylistic change i couldn't help myself.

@asturur asturur merged commit 99ff2d5 into master Jan 6, 2023
@ShaMan123 ShaMan123 deleted the itext-render-fix branch February 5, 2023 05:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants