Bad performance with Canvas and extensive clipping
Jim Graham
james.graham at oracle.com
Tue May 27 23:00:02 UTC 2014
I've filed RT-37300 for this:
https://javafx-jira.kenai.com/browse/RT-37300
...jim
On 5/27/14 3:54 PM, Jim Graham wrote:
> Hi Tom,
>
> There are 2 upgrades to consider. One involves new API, but is probably
> best in the long run.
>
> Without API, we'd have to detect if the path were rectangular in the
> processing of the CLIP command in NGCanvas.java. If the 4 coordinates
> are an axis aligned rectangle on integer coordinates then we could
> special case that with g.setClipRect(). There are other considerations,
> such as:
>
> - If there is already a soft non-rect clip, then it should probably not
> bother with the special case since it won't simplify anything.
> - If we have special cased the rectangle, then we must track that across
> save/restore properly.
> - If we have a special case cliprect and then we get a non-special case
> rect as the argument of a CLIP command, then we need to resolve it into
> a singular case (most likely default back to soft clipping).
> - The processing that tries to detect "are they clearing the entire
> buffer" needs to be aware of any clip in effect - those tests are done
> at the javafx.scene.canvas.GraphicsContext level.
>
> We could put that fix in with no new API so it could go in as soon as we
> are satisfied with its stability.
>
> If we want to add new API, so that you don't have to construct a path
> every time you want to do clipRect() and we don't have to decipher your
> path to figure out that it is a rectangle, then we would have to wait
> for the next opportunity to add API (FX can add API in between major JDK
> releases, but there is a process to go through and I don't think we can
> do it for 8u20 any more). The process for that would be:
>
> javafx.scene.canvas.GraphicsContext would need a new method that would
> take the rectangular clipping parameters and put them into the buffer.
> The existing fillRect() method would provide a good template. A new
> "command code constant" would have to be added to represent "This is a
> clip rectangle request".
>
> NGCanvas would then need to digest the new buffer commands and I believe
> that the existing Prism call g.setClipRect() would work to enable the
> scissor clip (fast rectangular clipping).
>
> The question is what is the proper API? If we have it take doubles,
> would that imply to developers that there would be soft clipping of the
> edges similar to if you used a rectangular path and clip()? Right now
> Node.setClipNode(Rectangle) will do the fast scissor clip
> (g.setClipRect()) if the coordinates fall on integer axis-aligned
> coordinates, but it will do soft-edged clipping if there is
> rotation/skewing, or the coordinates are not integers. That would
> probably be the best API to mimic since HTML5 doesn't have a similar
> "cliprect" method...
>
> ...jim
>
> On 5/27/14 2:57 PM, Tom Schindl wrote:
>> Is there anything I could do to help getting rectangular clipping into
>> JavaFX - I tried to find my way through the sources but I'm not sure I
>> have enough knowledge to provide a patch in this area.
>>
>> BTW it looks like I'm not alone with the clipping performance problem
>> see
>> http://tomsondev.bestsolution.at/2014/05/24/swtonfx-javafx-canvas-with-many-clipping-calls-unacceptable-slow/#comments
>>
>>
>> Tom
>>
>> On 27.05.14 23:47, Jim Graham wrote:
>>> Canvas is, essentially, a "draw pixels" mechanism. We have to bundle
>>> the requests into a command stream due to threading issues, but when the
>>> requests get to the render thread then they get turned into pixels so
>>> the command stream is a temporary intermediary. Some of the hw J2D
>>> pipelines also have a temporary command stream due to platform threading
>>> issues as well. It all depends on which pipeline you use and on which
>>> platform in the case of J2D. FX simply normalized the threading on all
>>> pipelines/platforms so that we have a separate UI and render thread in
>>> all cases, but that concept is not foreign to J2D either.
>>>
>>> I'm fairly certain that the lack of simple rectangular clipping is
>>> probably the biggest cause of your performance problems. We do AA on
>>> everything in FX, though, whereas rendering to a BufferedImage by
>>> default will be non-AA unless you requested AA using the graphics hints.
>>> But on the up-side, we hw accelerate just about every operation in FX
>>> so it should be on par with performance there, modulo the lack of
>>> rectangular clipping...
>>>
>>> ...jim
>>>
>>> On 5/23/14 5:46 PM, Tom Schindl wrote:
>>>> Hi,
>>>>
>>>> As an experiment I've now written a SWT-GC implementation using a
>>>> BufferedImage & Graphics2D and transfering the pixels over to JavaFX
>>>> and
>>>> the performance is as it is with native SWT.
>>>>
>>>> I always thought Canvas works similar to Image and one only draws
>>>> pixels
>>>> - looks like that is not the case, having a dep in my application
>>>> java.awt is not what I'm aiming at but without acceptable
>>>> performance in
>>>> conjunction with clipping it looks like i have to go this route :-(
>>>>
>>>> Tom
>>>>
>>>> On 23.05.14 23:57, Tom Schindl wrote:
>>>>> In the current usecase it is a rect all time but that's just in this
>>>>> special use case.
>>>>>
>>>>> I guess that rect clipping is the most common one so having an
>>>>> optimization for rects and a slow path for none rects might help.
>>>>>
>>>>> Tom
>>>>>
>>>>> Von meinem iPhone gesendet
>>>>>
>>>>>> Am 23.05.2014 um 23:35 schrieb Jim Graham <james.graham at oracle.com>:
>>>>>>
>>>>>> Are you clipping to an arbitrary path in all cases or just a
>>>>>> rectangle? Unfortunately we only offer the arbitrary
>>>>>> clip-to-current-path method that isn't optimized for basic
>>>>>> rectangular clipping and it implements soft clipping.
>>>>>>
>>>>>> There is an outstanding tweak that we added faster clipping support
>>>>>> for WebNode and we need to start using it for
>>>>>> Node.setClipNode(non-rectangle) and Canvas, but we haven't
>>>>>> implemented that yet.
>>>>>> (https://javafx-jira.kenai.com/browse/RT-30107) It basically is a
>>>>>> direct "render this texture through that other texture as a clip"
>>>>>> operation instead of the current code that runs it through some
>>>>>> Blend effect filters. It would definitely improve your run times,
>>>>>> but I'm not sure how much.
>>>>>>
>>>>>> Even more savings could be had for rectangular clips if we provided
>>>>>> some way to communicate them to the GC...
>>>>>>
>>>>>> ...jim
>>>>>>
>>>>>>> On 5/23/14 11:47 AM, Tom Schindl wrote:
>>>>>>> Hi,
>>>>>>>
>>>>>>> Maybe as some of you might know I've been working since sometime on
>>>>>>> SWT
>>>>>>> on JavaFX and to implement direct drawing operations we use
>>>>>>> JavaFX-Canvas.
>>>>>>>
>>>>>>> I've today tried to run a heavy direct drawing grid
>>>>>>> implementation and
>>>>>>> it performed very bad because it makes heavy use of clipping.
>>>>>>>
>>>>>>> For a grid I've counted ~1500 clipping operations the library works
>>>>>>> something like this:
>>>>>>>
>>>>>>> boolean activeClip;
>>>>>>> Canvas canvas = new Canvas();
>>>>>>>
>>>>>>> public void setClipping(PathIterator pathIterator) {
>>>>>>> GraphicsContext gc = canvas.getGraphicsContext2D();
>>>>>>> if(activeClip) {
>>>>>>> gc.restore();
>>>>>>> activeClip= false;
>>>>>>> }
>>>>>>>
>>>>>>> if( pathIterator == null ) {
>>>>>>> return;
>>>>>>> }
>>>>>>>
>>>>>>> activeClip = true;
>>>>>>> float coords[] = new float[6];
>>>>>>> gc.save();
>>>>>>> gc.beginPath();
>>>>>>>
>>>>>>> float x = 0;
>>>>>>> float y = 0;
>>>>>>>
>>>>>>>
>>>>>>> gc.moveTo(0, 0);
>>>>>>>
>>>>>>> while( ! pathIterator.isDone() ) {
>>>>>>> switch (pathIterator.currentSegment(coords)) {
>>>>>>> case PathIterator.SEG_CLOSE:
>>>>>>> gc.lineTo(x, y);
>>>>>>> break;
>>>>>>> case PathIterator.SEG_CUBICTO:
>>>>>>> gc.bezierCurveTo(coords[0], coords[1], coords[2],
>>>>>>> coords[3],
>>>>>>> coords[4], coords[5]);
>>>>>>> break;
>>>>>>> case PathIterator.SEG_LINETO:
>>>>>>> gc.lineTo(coords[0], coords[1]);
>>>>>>> break;
>>>>>>> case PathIterator.SEG_MOVETO:
>>>>>>> gc.moveTo(coords[0], coords[1]);
>>>>>>> x = coords[0];
>>>>>>> y = coords[1];
>>>>>>> break;
>>>>>>> case PathIterator.SEG_QUADTO:
>>>>>>> gc.quadraticCurveTo(coords[0], coords[1],
>>>>>>> coords[2], coords[3]);
>>>>>>> break;
>>>>>>> default:
>>>>>>> break;
>>>>>>> }
>>>>>>> pathIterator.next();
>>>>>>> }
>>>>>>>
>>>>>>> gc.clip();
>>>>>>> gc.closePath();
>>>>>>> }
>>>>>>>
>>>>>>> Am I doing something ultimately wrong, totally wrong? Has anyone an
>>>>>>> idea
>>>>>>> how I would work around the problem?
>>>>>>>
>>>>>>> Tom
>>>>>>>
>>>>
>>
More information about the openjfx-dev
mailing list