Khan / live-editor

A browser-based live coding environment.
Other
764 stars 183 forks source link

Rendering Error in Chrome (ProcessingJS Program) #712

Open GregStanton opened 5 years ago

GregStanton commented 5 years ago

Hi! I’m experiencing an issue with a ProcessingJS project I’m developing. It appears to be browser-related: on Windows 7, my code works perfectly in Firefox but not in Chrome.

Problem Description

The project is a software library for differential equations, and the problem occurs when plotting function graphs: some of the graphs seem to be filled in as if they were 2D shapes, rather than curves as they should be. I have used noFill() to prevent this, but it is not working in Chrome.

Chrome Rendering (Incorrect) vs. Firefox Rendering (Correct)

scene 29 with error in Chrome - visualODE Demo version 0-16-9

scene 29 without error in Firefox - visualODE Demo version 0-16-9

Where Problem Occurs

The two main sections of the code are the library and the demo. Each feature of the library is demonstrated in a demo scene, and these can be navigated via "Back" and "Next" buttons on the canvas.

The scenes that demonstrate new features are labeled at the bottom of the canvas. For example, in the screenshots above, the 30th scene is labeled as scene[29], and its code is located in a function stored at index 29 of an array called scene (this array serves as a lookup table).

The problematic behavior can be viewed by navigating in the canvas to the scenes labeled scene[29], scene[30], scene[41], and scene[43].

The function I've named drawFunc() draws the graphs (i.e. the curves) in question, and it's called in all four of these scenes. This may be a good place to start troubleshooting, but the code looks okay to me (it does run perfectly in Firefox).

In case it helps to see how drawFunc() is being called, the definitions for all scenes begin on line 4433. (The project consists of over 7000 lines, but that's largely because I've embedded complete library documentation into the program, as comments.)

What I've Tried

I've tried clearing my browser cache, running the program in incognito mode, and manually disabling all Chrome extensions. Still, it would be helpful to know if others can reproduce the problem.

Also, another user on Khan Academy pointed out that the error only occurs as a special case. Specifically, the problem appears to occur only when the stroke weight exceeds 1. (For example, the problem does not occur when 1.75 is changed to 1 on line 5673. The problem does occur with a value of 2.)

Possible Causes

I'm not certain, but I believe this problem occurred between early January and now, without any changes in the code. So, it may be related to a Chrome update.

A Possibly Related Problem

In Chrome, it appears that if the program is run and no action is taken by the user for more than five minutes (i.e. the user does not click "Next"), then "Oh noes!" guy says that a function is taking too long to run. Before five minutes elapse, he's happy. Like the main problem described above, this does not occur in Firefox.

Thank you!

To anyone taking a look at this, thank you so much for your time!! I would really appreciate any guidance you can give.

GregStanton commented 5 years ago

Update: No complete fix yet, but a very helpful Khan Academy user has unearthed some more clues. See the Help Requests tab underneath the Khan Academy project for details.

MatthiasPortzel commented 5 years ago

I've gotten to the point where I'm relatively confident that this behavior is the result of a floating point rounding error. Adjusting things like the canvas size or removing every other point from the graph seem to eliminate issue. Rounding every point to the nearest thousandth (or maybe even less), seems like a very good way of eliminating the problem in your program, @GregStanton.

I don't know exactly what the underlying browser issue is; I didn't whittle it down that far.

My simplest repo case is basically 50 lines, here.

GregStanton commented 5 years ago

I really appreciate you taking a look @MatthiasSaihttam ! Your comments and 50 lines of code helped me to make some progress, but I've encountered a problem.

I modified your fix a little (I rounded canvas coordinates instead of custom graph coordinates, so that precision can be set in terms of pixels). It fixes the examples in my demo (woohoo!). However, I had to do some guesswork to find the right precision, so I wondered if the fix would cover all cases.

To test it further, I came up with a minimal reproduction that's only around ten lines (neglecting comments and variable declarations). Using this, I found that the rounding fix does not always work.

Something strange seems to be going on at a basic level, but I don't know what it is yet. The minimal code reproduction is copied below, with a list of test values that yield different behaviors. Leaving curveMode undefined is intentional, since the default behavior of beginShape() / endShape() / vertex() is preferable for my use case. Any ideas about what is going on?

/* 
 * For (stepSize, stepCount), try (0.1, 3000), (0.18, 3000), (0.7, 3000), and (0.7, 300).
 * Rounding to 2 decimals (not 3) solves all problematic cases EXCEPT (0.7, 3000).
 * Some errors only occur for thickness greater than 1.
 * Setting curveMode to POINTS fixes problems in test pairs above, but curve is pixelated.
*/
var stepSize = 0.1;
var stepCount = 3000;
var curveThickness = 2;
var curveMode;

var x0 = 50;        //initial x-value on graph
var xi;             //ith x-value on graph
var points = [];    //array of points on graph

//example function to be plotted in canvas coordinates
var f = function(x) {
    return 250 - 5 * exp(x / 100);
};

//generate and store graph as array
for (var i = 0; i < stepCount; i++) {
    xi = x0 + i * stepSize;
    points[i] = [xi, f(xi)];
}

//plot graph
strokeWeight(curveThickness);
beginShape(curveMode);
for (var i = 0; i < points.length; i++) {
    vertex(points[i][0], points[i][1]);
}
endShape();
GregStanton commented 5 years ago

After considering helpful suggestions here and on Khan Academy, I think I've come up with a complete workaround (two, actually).

The simpler workaround roughly does the following two things:

(1) it removes points that are extremely far from the canvas (billions of pixels away), which prevents the bug that causes the entire curve to disappear; and

(2) it rounds the coordinates of the remaining points, as suggested by @MatthiasSaihttam, which prevents the bug that causes curves to appear as two-dimensional shapes.

In case it helps anyone else, here's a project I made with minimal reproductions of the two main bugs (a few lines each), code for the workarounds, and tests.

I may just do some extra testing and then go with the simpler workaround. Since we haven't solved the underlying browser issue, I'll leave the issue open, but I think a workaround will be good enough for my current needs.

Thanks again for the help!

GregStanton commented 5 years ago

Update: I went in to implement my workaround in the main library today, and I found that the main bug ("the 2D curve bug" in which a curve appears as a 2D shape) has been fixed! It looks like some patches have been made to Chrome.

The only bug that seems to remain in Chrome Version 75.0.3770.100 (Official Build) (64-bit) is the "disappearing curve bug," in which the curve disappears if it contains a vertex very far to the left or very far above the canvas (billions of pixels).

I'm not as concerned about the disappearing curve bug, since it should be triggered very rarely and is easily worked around. The behavior is still unexpected, though, so I'll leave the issue open in case it leads to someone figuring out the source of that bug.