[OpenJDK 2D-Dev] sun.java2D.Pisces renderer Performance and Memory enhancements

Jim Graham james.graham at oracle.com
Fri May 3 22:57:38 UTC 2013


Hi Laurent,

On 5/2/13 1:48 AM, Laurent Bourgès wrote:
>     xc = computed crossing value for the current scan line;
>     ceil(xc - 0.5);
>
>
> Agreed; however, pisces code never performs such rounding: for me,
> ceil(x - 0.5) means (int) Math.round(x).

(int) Math.round(x) is almost the same function, but the 0.5's map the 
wrong way.  3.5 gets mapped to 4 which implies that pixel #4 is the 
first on, or first off.  But, the first pixel to change is pixel #3 in 
that case.  For all other values it is the same.  ceil(x-0.5) reverses 
the "open interval of rounding" to bias halfway points down instead of up.

>     I wanted to get that out to see if we were on the same page or if
>     there was another way of doing the math that you are familiar with
>     that might also make things easier.
>
> Perfect and very clear. I will then check pisces rounding either on
> pixel coordinates (px, py) or AA (virtual) pixel coordinates (spx, spy).

With respect to applying the "center of pixel insideness" rules to the 
subpixels...

I have a vague memory of having the same discussion with Denis, but it 
seems that either he found a clever way of implementing it that I can't 
find, or he didn't find it helped on the quality vs. performance scale. 
  Keep in mind that our "pixel centers include pixels" rules are 
described in a context that is considering a binary "is this pixel 
inside or not" decision, but AA processing is a different animal and an 
attempt to represent approximate coverages.  So, applying that rule on 
the sub-pixel sample level of an AA rasterizer is on the gray line of 
blind dogmatic adherence to a philosophy from a related area.  I'm not 
convinced that one could consider Pisces "non-compliant" with that rule 
since I don't think it was ever promised that the rule would apply to 
this case.

> I am perplex and I am going to check pisces code against your given
> approach.

If for no other reason that to make sure that there aren't two parts of 
the system trying to communicate with different philosophies.  You don't 
want a caller to hand a closed interval to a method which treats the 
values as half-open, for instance.  If the rounding is "different, but 
consistent", then I think we can leave it for now and treat it as a 
future refinement to check if it makes any practical difference and 
correct.  But, if it shows up a left-hand-not-talking-to-right-hand bug, 
then that would be good to fix sooner rather than later.

I think it is OK to focus on your current task of performance and memory 
turmoil, but I wanted to give you the proper background to try to 
understand what you were reading primarily, and possibly to get you 
interested in cleaning up the code as you went as a secondary consideration.

>     Perhaps lastCrossing is misnamed.  I think it represents the end of
>     the interval which is half-open.  Does that match the rest of the
>     operations done on it?
>
>     If every coordinate has already been biased by the -0.5 then ceil is
>     just the tail end of the rounding equation I gave above.
>
>
> That's not the case => buggy:  x1, y1 and x2, y2 are directly the point
> coordinates as float values.

Then using the ceil() on both is still consistent with half-open 
intervals, it just has a different interpretation of where the sampling 
cut-off lies within the subpixel sample.  When you determine where the 
"crossings" lie, then it would be proper to do the same ceil(y +/- some 
offset) operation to compute the first crossing that is included and the 
first crossing that is excluded.  In this case it appears that the 
offset if just 0.0 which doesn't really meet my expectations, but is a 
minor issue.  These crossings then become a half-open interval of 
scanline indices in which to compute the value.

> FYI, firstCrossing is used to compute the first X intersection for that
> Y coordinate and determine the bucketIdx:
>          _edges[ptr+CURX] = x1 + (firstCrossing - y1) * slope;
> ...
>          final int bucketIdx = firstCrossing - boundsMinY;
> ...
>          _edges[ptr+NEXT] = edgeBuckets[bucketIdx];
>
> lastCrossing is used to give the YMax edge coordinate and edgedBucketCounts:
>          _edges[ptr+YMAX] = lastCrossing;
> ...
>          edgeBucketCounts[lastCrossing - boundsMinY] |= 1;
>
> I think rounding errors can lead to pixel / shape rasterization
> deformations ... ?

As long as the test is "y < _edges[ptr+YMAX]" then that is consistent 
with a half-open interval sampled at the top of every sub-pixel region, 
isn't it?  I agree with the half-open part of it, but would have 
preferred a "center of sub-pixel" offset for the actual sampling.

>     If not, then the half-open considerations still apply if you use the
>     same rounding for both the top and the bottom of the segment.  If
>     lastCrossing represents the "bottom of the shape" then it should not
>     be included.  If the next segment continues on from it, then its y1
>     will be computed in the first equation and will represent the first
>     scanline that the next segment participates in.
>
>
>         => firstCrossing / lastCrossing should use lower and upper integer
>         values (rounding issues) ?
>
>
>     Do they make sense now given my explanation above?  Perhaps they
>     should be renamed to indicate their half-openness?
>
>
> I am a bit embarrassed to verify maths performed in
> ScanLineIterator.next() which use edges and edgeBucketCounts arrays ...
> could you have a look ?
> Apparently, it uses the following for loop that respects the semi-open
> interval philosophy:
>              for (int i = 0, ecur, j, k; i < count; i++) {
> ...

I'll come back to that at a later time, but it sounds like you are 
starting to get a handle on the design here.

> However, I am not sure if the edges "linked list" is traversed correctly ...

That one took me a few tries to understand myself as well.

>               boolean endRendering() {
>                   // TODO: perform shape clipping to avoid dealing with
>         segments
>         out of bounding box
>
>                   // Ensure shape edges are within bbox:
>                   if (edgeMinX > edgeMaxX || edgeMaxX < 0f) {
>                       return false; // undefined X bounds or negative Xmax
>                   }
>                   if (edgeMinY > edgeMaxY || edgeMaxY < 0f) {
>                       return false; // undefined Y bounds or negative Ymax
>                   }
>
>
>     I'd use min >= max since if min==max then I think nothing gets
>     generated as a result of all edges having both the in and out
>     crossings on the same coordinate.  Also, why not test against the
>     clip bounds instead? The code after that will clip the edgeMinMaxXY
>     values against the boundsMinMax values.  If you do this kind of test
>     after that clipping is done (on spminmaxxy) then you can discover if
>     all of the coordinates are outside the clip or the region of interest.
>
>
> I tried here to perform few "fast" checks before doing float to int
> conversions (costly because millions are performed): I think it can be
> still improved: edgeMinX > edgeMaxX only ensures edgeMinX is defined and
> both are positive !

endRendering is called once per shape.  I don't think moving tests above 
its conversions to int will affect our throughput compared to 
calculations done per-vertex.

I'm going to have to review the rest of this email at a future time, my 
apologies...

> PS: I have more free time until sunday (children are away), so I would
> like to talk to you using skype if it is possible ?
> If yes, could you give me your skype account and work times (GMT time
> please) ?
> I am living in Grenoble, France (GMT+1)

Unfortunately, this time frame isn't good for me.  :(

			...jim



More information about the 2d-dev mailing list