Open Bug 601176 Opened 14 years ago Updated 2 years ago

Slow performance on ctx.fillRect

Categories

(Core :: Graphics: Canvas2D, defect)

x86
Other
defect

Tracking

()

Tracking Status
blocking2.0 --- -

People

(Reporter: jandem, Unassigned)

References

(Depends on 1 open bug, Blocks 3 open bugs)

Details

(Keywords: perf, Whiteboard: [painting-perf])

Attachments

(1 file)

Attached file Test case
ctx.fillRect is very slow in Firefox. Numbers for the attached test case on OS X:

Chrome: 18 ms
Safari: 48 ms
FF4 nightly: 136 ms

Shark shows we're creating a path and calling CGContextDrawPath. Safari just calls CGContextFillRect.

bz said this issue has come up before and asked to request blocking2.0.
blocking2.0: --- → ?
To clarify, FillRect performance is one of our most common Canvas bottlenecks.  We seem to be converting it to a path, etc, and then going through Cairo and CG path stuff on Mac; not sure about other OSes.

Especially optimizing 1x1 fillRect (which pages use when they should perhaps use imageData) would be really nice.
Whiteboard: [painting-perf]
I'd take a patch here, but we already have a lot of work to do for Fx4.
blocking2.0: ? → -
(In reply to comment #0)
> Shark shows we're creating a path and calling CGContextDrawPath. Safari just
> calls CGContextFillRect.

Bug 516931 is where this has come up before.
Fun times, yeah.

The point is, whatever we're doing here Chrome is managing to do it about 7x faster and Safari 3x faster....
Safari is spending almost all of its fillRect time in CGContextFillRect, whereas cairo wastes almost 50% in setup_state and teardown_state saving and restoring contexts and resetting colors. Path management in nsCanvasRenderingContext2D::DrawRect also takes another 10% before we even enter nsCanvasRenderingContext2D::DrawPath.
(The percentages above are relative to the time spent under nsCanvasRenderingContext2D::FillRect.)

The cairo setup/teardown stuff can probably be fixed inside the cairo quartz implementation with more sophisticated state tracking. Avoiding the path management overhead probably requires a new fillRect cairo API.
The cairo quartz surface should be looking for paths that form a rect, and calling FillRect if possible -- it doesn't seem to be doing that now.  That check is fairly cheap, and doesn't require adding new cairo API.  We'll still have overhead of setup_state, though even that could be optimized for some cases.

There's also the possibility of tracking state more aggressively in the quartz surface; that would be a useful thing to do.
(In reply to comment #6)
> The cairo quartz surface should be looking for paths that form a rect, and
> calling FillRect if possible -- it doesn't seem to be doing that now.  That
> check is fairly cheap, and doesn't require adding new cairo API.

This was tried in bug 516931, unsuccessfully.

The 10% path management overhead that I was referring to are things like gfxContext::Rectangle, gfxContext::CopyPath, and allocating and releasing gfxPath objects.
The plan for fixing the state management problems is cairo_backend_t http://cgit.freedesktop.org/~ickle/cairo/log/?h=cairo_backend_t
Depends on: 544357
Its also slow on Windows XP.  My numbers from the attachment:

Chrome: 28
Safari: 46
F4: 65

Peacekeeper uses this function a lot in its canvas tests so speeding it up would help there.
Can this be marked as blocking 499198?
Keywords: perf
Blocks: 582489
With FF8 Nightly "http://hg.mozilla.org/mozilla-central/rev/be4b064f1159":
On Test case:
FF8 Nightly: 30 ms
Chrome 14.0.835.15 Dev: 14 ms

Test was performed on my main profile, not on fresh profile.
Fx is much faster than it used to be thanks most likely to the new Azure backend. However were still twice as slow as Chrome. Any ideas why?
There is no Azure backend on Mac.  This bug is about a Mac-specific issue.

Please file a separate bug on any slowness you see on Windows and cc me on it?
Blocks: 919992
This should have improved significantly since this bug was filed. We now call directly into CGFillRect on OS X. That being said there's still some overhead that we have beyond what Safari would have. The bulk of that is CGContextSaveState()/CGContextRestoreState(). This can be eliminated by setting the matrix directly instead of relying on Concatenation.
(In reply to Jeff Muizelaar [:jrmuizel] from comment #14)
> This should have improved significantly since this bug was filed. We now
> call directly into CGFillRect on OS X. That being said there's still some
> overhead that we have beyond what Safari would have. The bulk of that is
> CGContextSaveState()/CGContextRestoreState(). This can be eliminated by
> setting the matrix directly instead of relying on Concatenation.

Do you know somebody who can take this bug? Although this improved a lot, on OS X the attached micro-benchmark is still 2-3x slower than Chrome and the Peacekeeper ripple tests (bug 919992) are mostly fillRect bound.. :)
Flags: needinfo?(jmuizelaar)
(In reply to Jan de Mooij [:jandem] from comment #15)
> (In reply to Jeff Muizelaar [:jrmuizel] from comment #14)
> > This should have improved significantly since this bug was filed. We now
> > call directly into CGFillRect on OS X. That being said there's still some
> > overhead that we have beyond what Safari would have. The bulk of that is
> > CGContextSaveState()/CGContextRestoreState(). This can be eliminated by
> > setting the matrix directly instead of relying on Concatenation.
> 
> Do you know somebody who can take this bug? Although this improved a lot, on
> OS X the attached micro-benchmark is still 2-3x slower than Chrome and the
> Peacekeeper ripple tests (bug 919992) are mostly fillRect bound.. :)

The peacekeeper ripple tests are not really a use case that's important for real content and this problem will only show up on OS X (which isn't a common benchmark target). So this is a pretty low priority.
Flags: needinfo?(jmuizelaar)
This is not OSX only. We're a lot slower on linux too.
OS: Mac OS X → Other

Hello everyone, in 2021 both fillRect() and clearRect() are super slow in Firefox on MacOS. I'm drawing a huge heatmap calendar that spans ~60 canvas elements. Just clearRect-ing / fillRect-ing those 60 canvases take %60 of the time compared to everything else that is needed to draw a heatmap calendar.

Here's a self-contained HTML that you can run in FF for MacOS and see the difference between fillRect() and webgl's clear(). With all plugins OFF in FF, to me it shows:

100 tests
fillRect(): 229.87ms
webgl: 0.06ms
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
</head>
<body>
<p id='results'>
    Results will appear here
</p>

<canvas style="width: 300px; height: 300px; border: 1px solid orange" id='standard_context_canvas'></canvas>
<canvas style="width: 300px; height: 300px; border: 1px solid green" id='webgl_context_canvas'></canvas>

<script>
    const num_tests = 100;

    const standard_context_canvas = document.getElementById('standard_context_canvas');
    const webgl_context_canvas = document.getElementById('webgl_context_canvas');

    const ctx = standard_context_canvas.getContext('2d');
    const gl = webgl_context_canvas.getContext("webgl") || webgl_context_canvas.getContext("experimental-webgl");

    // Preparation
    const canvas_width_height = 3000;
    standard_context_canvas.width = canvas_width_height;
    standard_context_canvas.height = canvas_width_height;
    gl.viewport(0, 0, canvas_width_height, canvas_width_height);
    console.log(`WebGL Viewport: ` + JSON.stringify(gl.getParameter(gl.VIEWPORT)));

    const measurements = [];
    function measure(name, callback_to_reset, callback_to_measure) {

        let total_time_spent_ms = 0;
        for (let i = 0; i < num_tests; i++) {
            callback_to_reset();

            const start_time = performance.now();
            callback_to_measure();
            const time_spent_ms = performance.now() - start_time;
            total_time_spent_ms += time_spent_ms;
        }


        measurements.push({name, time_spent_ms: total_time_spent_ms});
    }

    measure('fillRect()', () => {
        ctx.fillStyle = 'blue';
        ctx.fillRect(0, 0, canvas_width_height, canvas_width_height);
    }, () => {
        ctx.fillStyle = 'white';
        ctx.fillRect(0, 0, canvas_width_height, canvas_width_height);
    });

    measure('webgl', () => {
        // Set clear color to red, fully opaque
        gl.clearColor(1.0, 0.0, 0.0, 1.0);
        // Clear the color buffer with specified clear color
        gl.clear(gl.COLOR_BUFFER_BIT);
    }, () => {
        // Set clear color to white, fully opaque
        gl.clearColor(1.0, 1.0, 1.0, 1.0);
        // Clear the color buffer with specified clear color
        gl.clear(gl.COLOR_BUFFER_BIT);
    });

    // Output the measurements results
    let results_text = [];
    for (let i = 0; i < measurements.length; i++) {
        const {name, time_spent_ms} = measurements[i];
        results_text.push(name + ': ' + time_spent_ms.toFixed(2) + 'ms');
    }
    results_text = num_tests + ' tests<br/>'
        + results_text.join('<br/>')
    const results_paragraph = document.getElementById('results');
    results_paragraph.innerHTML = results_text;
</script>
</body>

</html>

Severity: normal → S3
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: