8143849: Integrate Marlin renderer per JEP 265
Reviewed-by: flar, prr
This commit is contained in:
parent
8571ce9809
commit
155cc1f5bc
@ -0,0 +1,216 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sun.java2d.marlin;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import static sun.java2d.marlin.MarlinUtils.logInfo;
|
||||||
|
|
||||||
|
public final class ArrayCache implements MarlinConst {
|
||||||
|
|
||||||
|
static final int BUCKETS = 4;
|
||||||
|
static final int MIN_ARRAY_SIZE = 4096;
|
||||||
|
static final int MAX_ARRAY_SIZE;
|
||||||
|
static final int MASK_CLR_1 = ~1;
|
||||||
|
// threshold to grow arrays only by (3/2) instead of 2
|
||||||
|
static final int THRESHOLD_ARRAY_SIZE;
|
||||||
|
static final int[] ARRAY_SIZES = new int[BUCKETS];
|
||||||
|
// dirty byte array sizes
|
||||||
|
static final int MIN_DIRTY_BYTE_ARRAY_SIZE = 32 * 2048; // 32px x 2048px
|
||||||
|
static final int MAX_DIRTY_BYTE_ARRAY_SIZE;
|
||||||
|
static final int[] DIRTY_BYTE_ARRAY_SIZES = new int[BUCKETS];
|
||||||
|
// large array thresholds:
|
||||||
|
static final long THRESHOLD_LARGE_ARRAY_SIZE;
|
||||||
|
static final long THRESHOLD_HUGE_ARRAY_SIZE;
|
||||||
|
// stats
|
||||||
|
private static int resizeInt = 0;
|
||||||
|
private static int resizeDirtyInt = 0;
|
||||||
|
private static int resizeDirtyFloat = 0;
|
||||||
|
private static int resizeDirtyByte = 0;
|
||||||
|
private static int oversize = 0;
|
||||||
|
|
||||||
|
static {
|
||||||
|
// initialize buckets for int/float arrays
|
||||||
|
int arraySize = MIN_ARRAY_SIZE;
|
||||||
|
|
||||||
|
for (int i = 0; i < BUCKETS; i++, arraySize <<= 2) {
|
||||||
|
ARRAY_SIZES[i] = arraySize;
|
||||||
|
|
||||||
|
if (doTrace) {
|
||||||
|
logInfo("arraySize[" + i + "]: " + arraySize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MAX_ARRAY_SIZE = arraySize >> 2;
|
||||||
|
|
||||||
|
/* initialize buckets for dirty byte arrays
|
||||||
|
(large AA chunk = 32 x 2048 pixels) */
|
||||||
|
arraySize = MIN_DIRTY_BYTE_ARRAY_SIZE;
|
||||||
|
|
||||||
|
for (int i = 0; i < BUCKETS; i++, arraySize <<= 1) {
|
||||||
|
DIRTY_BYTE_ARRAY_SIZES[i] = arraySize;
|
||||||
|
|
||||||
|
if (doTrace) {
|
||||||
|
logInfo("dirty arraySize[" + i + "]: " + arraySize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MAX_DIRTY_BYTE_ARRAY_SIZE = arraySize >> 1;
|
||||||
|
|
||||||
|
// threshold to grow arrays only by (3/2) instead of 2
|
||||||
|
THRESHOLD_ARRAY_SIZE = Math.max(2 * 1024 * 1024, MAX_ARRAY_SIZE); // 2M
|
||||||
|
|
||||||
|
THRESHOLD_LARGE_ARRAY_SIZE = 8L * THRESHOLD_ARRAY_SIZE; // 16M
|
||||||
|
THRESHOLD_HUGE_ARRAY_SIZE = 8L * THRESHOLD_LARGE_ARRAY_SIZE; // 128M
|
||||||
|
|
||||||
|
if (doStats || doMonitors) {
|
||||||
|
logInfo("ArrayCache.BUCKETS = " + BUCKETS);
|
||||||
|
logInfo("ArrayCache.MIN_ARRAY_SIZE = " + MIN_ARRAY_SIZE);
|
||||||
|
logInfo("ArrayCache.MAX_ARRAY_SIZE = " + MAX_ARRAY_SIZE);
|
||||||
|
logInfo("ArrayCache.ARRAY_SIZES = "
|
||||||
|
+ Arrays.toString(ARRAY_SIZES));
|
||||||
|
logInfo("ArrayCache.MIN_DIRTY_BYTE_ARRAY_SIZE = "
|
||||||
|
+ MIN_DIRTY_BYTE_ARRAY_SIZE);
|
||||||
|
logInfo("ArrayCache.MAX_DIRTY_BYTE_ARRAY_SIZE = "
|
||||||
|
+ MAX_DIRTY_BYTE_ARRAY_SIZE);
|
||||||
|
logInfo("ArrayCache.ARRAY_SIZES = "
|
||||||
|
+ Arrays.toString(DIRTY_BYTE_ARRAY_SIZES));
|
||||||
|
logInfo("ArrayCache.THRESHOLD_ARRAY_SIZE = "
|
||||||
|
+ THRESHOLD_ARRAY_SIZE);
|
||||||
|
logInfo("ArrayCache.THRESHOLD_LARGE_ARRAY_SIZE = "
|
||||||
|
+ THRESHOLD_LARGE_ARRAY_SIZE);
|
||||||
|
logInfo("ArrayCache.THRESHOLD_HUGE_ARRAY_SIZE = "
|
||||||
|
+ THRESHOLD_HUGE_ARRAY_SIZE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArrayCache() {
|
||||||
|
// Utility class
|
||||||
|
}
|
||||||
|
|
||||||
|
static synchronized void incResizeInt() {
|
||||||
|
resizeInt++;
|
||||||
|
}
|
||||||
|
|
||||||
|
static synchronized void incResizeDirtyInt() {
|
||||||
|
resizeDirtyInt++;
|
||||||
|
}
|
||||||
|
|
||||||
|
static synchronized void incResizeDirtyFloat() {
|
||||||
|
resizeDirtyFloat++;
|
||||||
|
}
|
||||||
|
|
||||||
|
static synchronized void incResizeDirtyByte() {
|
||||||
|
resizeDirtyByte++;
|
||||||
|
}
|
||||||
|
|
||||||
|
static synchronized void incOversize() {
|
||||||
|
oversize++;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dumpStats() {
|
||||||
|
if (resizeInt != 0 || resizeDirtyInt != 0 || resizeDirtyFloat != 0
|
||||||
|
|| resizeDirtyByte != 0 || oversize != 0) {
|
||||||
|
logInfo("ArrayCache: int resize: " + resizeInt
|
||||||
|
+ " - dirty int resize: " + resizeDirtyInt
|
||||||
|
+ " - dirty float resize: " + resizeDirtyFloat
|
||||||
|
+ " - dirty byte resize: " + resizeDirtyByte
|
||||||
|
+ " - oversize: " + oversize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// small methods used a lot (to be inlined / optimized by hotspot)
|
||||||
|
|
||||||
|
static int getBucket(final int length) {
|
||||||
|
for (int i = 0; i < ARRAY_SIZES.length; i++) {
|
||||||
|
if (length <= ARRAY_SIZES[i]) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int getBucketDirtyBytes(final int length) {
|
||||||
|
for (int i = 0; i < DIRTY_BYTE_ARRAY_SIZES.length; i++) {
|
||||||
|
if (length <= DIRTY_BYTE_ARRAY_SIZES[i]) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the new array size (~ x2)
|
||||||
|
* @param curSize current used size
|
||||||
|
* @param needSize needed size
|
||||||
|
* @return new array size
|
||||||
|
*/
|
||||||
|
public static int getNewSize(final int curSize, final int needSize) {
|
||||||
|
final int initial = (curSize & MASK_CLR_1);
|
||||||
|
int size;
|
||||||
|
if (initial > THRESHOLD_ARRAY_SIZE) {
|
||||||
|
size = initial + (initial >> 1); // x(3/2)
|
||||||
|
} else {
|
||||||
|
size = (initial) << 1; // x2
|
||||||
|
}
|
||||||
|
// ensure the new size is >= needed size:
|
||||||
|
if (size < needSize) {
|
||||||
|
// align to 4096:
|
||||||
|
size = ((needSize >> 12) + 1) << 12;
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the new array size (~ x2)
|
||||||
|
* @param curSize current used size
|
||||||
|
* @param needSize needed size
|
||||||
|
* @return new array size
|
||||||
|
*/
|
||||||
|
public static long getNewLargeSize(final long curSize, final long needSize) {
|
||||||
|
long size;
|
||||||
|
if (curSize > THRESHOLD_HUGE_ARRAY_SIZE) {
|
||||||
|
size = curSize + (curSize >> 2L); // x(5/4)
|
||||||
|
} else if (curSize > THRESHOLD_LARGE_ARRAY_SIZE) {
|
||||||
|
size = curSize + (curSize >> 1L); // x(3/2)
|
||||||
|
} else {
|
||||||
|
size = curSize << 1L; // x2
|
||||||
|
}
|
||||||
|
// ensure the new size is >= needed size:
|
||||||
|
if (size < needSize) {
|
||||||
|
// align to 4096:
|
||||||
|
size = ((needSize >> 12) + 1) << 12;
|
||||||
|
}
|
||||||
|
if (size >= Integer.MAX_VALUE) {
|
||||||
|
if (curSize >= Integer.MAX_VALUE) {
|
||||||
|
// hard overflow failure - we can't even accommodate
|
||||||
|
// new items without overflowing
|
||||||
|
throw new ArrayIndexOutOfBoundsException(
|
||||||
|
"array exceeds maximum capacity !");
|
||||||
|
}
|
||||||
|
// resize to maximum capacity:
|
||||||
|
size = Integer.MAX_VALUE;
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,151 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sun.java2d.marlin;
|
||||||
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import static sun.java2d.marlin.MarlinUtils.logException;
|
||||||
|
import static sun.java2d.marlin.MarlinUtils.logInfo;
|
||||||
|
|
||||||
|
final class ByteArrayCache implements MarlinConst {
|
||||||
|
|
||||||
|
private final int arraySize;
|
||||||
|
private final ArrayDeque<byte[]> byteArrays;
|
||||||
|
// stats
|
||||||
|
private int getOp = 0;
|
||||||
|
private int createOp = 0;
|
||||||
|
private int returnOp = 0;
|
||||||
|
|
||||||
|
void dumpStats() {
|
||||||
|
if (getOp > 0) {
|
||||||
|
logInfo("ByteArrayCache[" + arraySize + "]: get: " + getOp
|
||||||
|
+ " created: " + createOp + " - returned: " + returnOp
|
||||||
|
+ " :: cache size: " + byteArrays.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteArrayCache(final int arraySize) {
|
||||||
|
this.arraySize = arraySize;
|
||||||
|
// small but enough: almost 1 cache line
|
||||||
|
this.byteArrays = new ArrayDeque<byte[]>(6);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] getArray() {
|
||||||
|
if (doStats) {
|
||||||
|
getOp++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// use cache:
|
||||||
|
final byte[] array = byteArrays.pollLast();
|
||||||
|
if (array != null) {
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doStats) {
|
||||||
|
createOp++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new byte[arraySize];
|
||||||
|
}
|
||||||
|
|
||||||
|
void putDirtyArray(final byte[] array, final int length) {
|
||||||
|
if (length != arraySize) {
|
||||||
|
if (doChecks) {
|
||||||
|
System.out.println("ArrayCache: bad length = " + length);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (doStats) {
|
||||||
|
returnOp++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NO clean-up of array data = DIRTY ARRAY
|
||||||
|
|
||||||
|
if (doCleanDirty) {
|
||||||
|
// Force zero-fill dirty arrays:
|
||||||
|
Arrays.fill(array, 0, array.length, BYTE_0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// fill cache:
|
||||||
|
byteArrays.addLast(array);
|
||||||
|
}
|
||||||
|
|
||||||
|
void putArray(final byte[] array, final int length,
|
||||||
|
final int fromIndex, final int toIndex)
|
||||||
|
{
|
||||||
|
if (length != arraySize) {
|
||||||
|
if (doChecks) {
|
||||||
|
System.out.println("ArrayCache: bad length = " + length);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (doStats) {
|
||||||
|
returnOp++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// clean-up array of dirty part[fromIndex; toIndex[
|
||||||
|
fill(array, fromIndex, toIndex, BYTE_0);
|
||||||
|
|
||||||
|
// fill cache:
|
||||||
|
byteArrays.addLast(array);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fill(final byte[] array, final int fromIndex,
|
||||||
|
final int toIndex, final byte value)
|
||||||
|
{
|
||||||
|
// clear array data:
|
||||||
|
/*
|
||||||
|
* Arrays.fill is faster than System.arraycopy(empty array)
|
||||||
|
* or Unsafe.setMemory(byte 0)
|
||||||
|
*/
|
||||||
|
if (toIndex != 0) {
|
||||||
|
Arrays.fill(array, fromIndex, toIndex, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doChecks) {
|
||||||
|
check(array, 0, array.length, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void check(final byte[] array, final int fromIndex,
|
||||||
|
final int toIndex, final byte value)
|
||||||
|
{
|
||||||
|
if (doChecks) {
|
||||||
|
// check zero on full array:
|
||||||
|
for (int i = fromIndex; i < toIndex; i++) {
|
||||||
|
if (array[i] != value) {
|
||||||
|
logException("Invalid array value at " + i + "\n"
|
||||||
|
+ Arrays.toString(array), new Throwable());
|
||||||
|
|
||||||
|
// ensure array is correctly filled:
|
||||||
|
Arrays.fill(array, value);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,155 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sun.java2d.marlin;
|
||||||
|
|
||||||
|
import sun.awt.geom.PathConsumer2D;
|
||||||
|
|
||||||
|
final class CollinearSimplifier implements PathConsumer2D {
|
||||||
|
|
||||||
|
enum SimplifierState {
|
||||||
|
|
||||||
|
Empty, PreviousPoint, PreviousLine
|
||||||
|
};
|
||||||
|
// slope precision threshold
|
||||||
|
static final float EPS = 1e-4f; // aaime proposed 1e-3f
|
||||||
|
|
||||||
|
PathConsumer2D delegate;
|
||||||
|
SimplifierState state;
|
||||||
|
float px1, py1, px2, py2;
|
||||||
|
float pslope;
|
||||||
|
|
||||||
|
CollinearSimplifier() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public CollinearSimplifier init(PathConsumer2D delegate) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
this.state = SimplifierState.Empty;
|
||||||
|
|
||||||
|
return this; // fluent API
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pathDone() {
|
||||||
|
emitStashedLine();
|
||||||
|
state = SimplifierState.Empty;
|
||||||
|
delegate.pathDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void closePath() {
|
||||||
|
emitStashedLine();
|
||||||
|
state = SimplifierState.Empty;
|
||||||
|
delegate.closePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getNativeConsumer() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void quadTo(float x1, float y1, float x2, float y2) {
|
||||||
|
emitStashedLine();
|
||||||
|
delegate.quadTo(x1, y1, x2, y2);
|
||||||
|
// final end point:
|
||||||
|
state = SimplifierState.PreviousPoint;
|
||||||
|
px1 = x2;
|
||||||
|
py1 = y2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void curveTo(float x1, float y1, float x2, float y2,
|
||||||
|
float x3, float y3) {
|
||||||
|
emitStashedLine();
|
||||||
|
delegate.curveTo(x1, y1, x2, y2, x3, y3);
|
||||||
|
// final end point:
|
||||||
|
state = SimplifierState.PreviousPoint;
|
||||||
|
px1 = x3;
|
||||||
|
py1 = y3;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void moveTo(float x, float y) {
|
||||||
|
emitStashedLine();
|
||||||
|
delegate.moveTo(x, y);
|
||||||
|
state = SimplifierState.PreviousPoint;
|
||||||
|
px1 = x;
|
||||||
|
py1 = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void lineTo(final float x, final float y) {
|
||||||
|
switch (state) {
|
||||||
|
case Empty:
|
||||||
|
delegate.lineTo(x, y);
|
||||||
|
state = SimplifierState.PreviousPoint;
|
||||||
|
px1 = x;
|
||||||
|
py1 = y;
|
||||||
|
return;
|
||||||
|
|
||||||
|
case PreviousPoint:
|
||||||
|
state = SimplifierState.PreviousLine;
|
||||||
|
px2 = x;
|
||||||
|
py2 = y;
|
||||||
|
pslope = getSlope(px1, py1, x, y);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case PreviousLine:
|
||||||
|
final float slope = getSlope(px2, py2, x, y);
|
||||||
|
// test for collinearity
|
||||||
|
if ((slope == pslope) || (Math.abs(pslope - slope) < EPS)) {
|
||||||
|
// merge segments
|
||||||
|
px2 = x;
|
||||||
|
py2 = y;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// emit previous segment
|
||||||
|
delegate.lineTo(px2, py2);
|
||||||
|
px1 = px2;
|
||||||
|
py1 = py2;
|
||||||
|
px2 = x;
|
||||||
|
py2 = y;
|
||||||
|
pslope = slope;
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void emitStashedLine() {
|
||||||
|
if (state == SimplifierState.PreviousLine) {
|
||||||
|
delegate.lineTo(px2, py2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float getSlope(float x1, float y1, float x2, float y2) {
|
||||||
|
float dy = y2 - y1;
|
||||||
|
if (dy == 0f) {
|
||||||
|
return (x2 > x1) ? Float.POSITIVE_INFINITY
|
||||||
|
: Float.NEGATIVE_INFINITY;
|
||||||
|
}
|
||||||
|
return (x2 - x1) / dy;
|
||||||
|
}
|
||||||
|
}
|
306
jdk/src/java.desktop/share/classes/sun/java2d/marlin/Curve.java
Normal file
306
jdk/src/java.desktop/share/classes/sun/java2d/marlin/Curve.java
Normal file
@ -0,0 +1,306 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sun.java2d.marlin;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
final class Curve {
|
||||||
|
|
||||||
|
float ax, ay, bx, by, cx, cy, dx, dy;
|
||||||
|
float dax, day, dbx, dby;
|
||||||
|
// shared iterator instance
|
||||||
|
private final BreakPtrIterator iterator = new BreakPtrIterator();
|
||||||
|
|
||||||
|
Curve() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void set(float[] points, int type) {
|
||||||
|
switch(type) {
|
||||||
|
case 8:
|
||||||
|
set(points[0], points[1],
|
||||||
|
points[2], points[3],
|
||||||
|
points[4], points[5],
|
||||||
|
points[6], points[7]);
|
||||||
|
return;
|
||||||
|
case 6:
|
||||||
|
set(points[0], points[1],
|
||||||
|
points[2], points[3],
|
||||||
|
points[4], points[5]);
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
throw new InternalError("Curves can only be cubic or quadratic");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void set(float x1, float y1,
|
||||||
|
float x2, float y2,
|
||||||
|
float x3, float y3,
|
||||||
|
float x4, float y4)
|
||||||
|
{
|
||||||
|
ax = 3f * (x2 - x3) + x4 - x1;
|
||||||
|
ay = 3f * (y2 - y3) + y4 - y1;
|
||||||
|
bx = 3f * (x1 - 2f * x2 + x3);
|
||||||
|
by = 3f * (y1 - 2f * y2 + y3);
|
||||||
|
cx = 3f * (x2 - x1);
|
||||||
|
cy = 3f * (y2 - y1);
|
||||||
|
dx = x1;
|
||||||
|
dy = y1;
|
||||||
|
dax = 3f * ax; day = 3f * ay;
|
||||||
|
dbx = 2f * bx; dby = 2f * by;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set(float x1, float y1,
|
||||||
|
float x2, float y2,
|
||||||
|
float x3, float y3)
|
||||||
|
{
|
||||||
|
ax = 0f; ay = 0f;
|
||||||
|
bx = x1 - 2f * x2 + x3;
|
||||||
|
by = y1 - 2f * y2 + y3;
|
||||||
|
cx = 2f * (x2 - x1);
|
||||||
|
cy = 2f * (y2 - y1);
|
||||||
|
dx = x1;
|
||||||
|
dy = y1;
|
||||||
|
dax = 0f; day = 0f;
|
||||||
|
dbx = 2f * bx; dby = 2f * by;
|
||||||
|
}
|
||||||
|
|
||||||
|
float xat(float t) {
|
||||||
|
return t * (t * (t * ax + bx) + cx) + dx;
|
||||||
|
}
|
||||||
|
float yat(float t) {
|
||||||
|
return t * (t * (t * ay + by) + cy) + dy;
|
||||||
|
}
|
||||||
|
|
||||||
|
float dxat(float t) {
|
||||||
|
return t * (t * dax + dbx) + cx;
|
||||||
|
}
|
||||||
|
|
||||||
|
float dyat(float t) {
|
||||||
|
return t * (t * day + dby) + cy;
|
||||||
|
}
|
||||||
|
|
||||||
|
int dxRoots(float[] roots, int off) {
|
||||||
|
return Helpers.quadraticRoots(dax, dbx, cx, roots, off);
|
||||||
|
}
|
||||||
|
|
||||||
|
int dyRoots(float[] roots, int off) {
|
||||||
|
return Helpers.quadraticRoots(day, dby, cy, roots, off);
|
||||||
|
}
|
||||||
|
|
||||||
|
int infPoints(float[] pts, int off) {
|
||||||
|
// inflection point at t if -f'(t)x*f''(t)y + f'(t)y*f''(t)x == 0
|
||||||
|
// Fortunately, this turns out to be quadratic, so there are at
|
||||||
|
// most 2 inflection points.
|
||||||
|
final float a = dax * dby - dbx * day;
|
||||||
|
final float b = 2f * (cy * dax - day * cx);
|
||||||
|
final float c = cy * dbx - cx * dby;
|
||||||
|
|
||||||
|
return Helpers.quadraticRoots(a, b, c, pts, off);
|
||||||
|
}
|
||||||
|
|
||||||
|
// finds points where the first and second derivative are
|
||||||
|
// perpendicular. This happens when g(t) = f'(t)*f''(t) == 0 (where
|
||||||
|
// * is a dot product). Unfortunately, we have to solve a cubic.
|
||||||
|
private int perpendiculardfddf(float[] pts, int off) {
|
||||||
|
assert pts.length >= off + 4;
|
||||||
|
|
||||||
|
// these are the coefficients of some multiple of g(t) (not g(t),
|
||||||
|
// because the roots of a polynomial are not changed after multiplication
|
||||||
|
// by a constant, and this way we save a few multiplications).
|
||||||
|
final float a = 2f * (dax*dax + day*day);
|
||||||
|
final float b = 3f * (dax*dbx + day*dby);
|
||||||
|
final float c = 2f * (dax*cx + day*cy) + dbx*dbx + dby*dby;
|
||||||
|
final float d = dbx*cx + dby*cy;
|
||||||
|
return Helpers.cubicRootsInAB(a, b, c, d, pts, off, 0f, 1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tries to find the roots of the function ROC(t)-w in [0, 1). It uses
|
||||||
|
// a variant of the false position algorithm to find the roots. False
|
||||||
|
// position requires that 2 initial values x0,x1 be given, and that the
|
||||||
|
// function must have opposite signs at those values. To find such
|
||||||
|
// values, we need the local extrema of the ROC function, for which we
|
||||||
|
// need the roots of its derivative; however, it's harder to find the
|
||||||
|
// roots of the derivative in this case than it is to find the roots
|
||||||
|
// of the original function. So, we find all points where this curve's
|
||||||
|
// first and second derivative are perpendicular, and we pretend these
|
||||||
|
// are our local extrema. There are at most 3 of these, so we will check
|
||||||
|
// at most 4 sub-intervals of (0,1). ROC has asymptotes at inflection
|
||||||
|
// points, so roc-w can have at least 6 roots. This shouldn't be a
|
||||||
|
// problem for what we're trying to do (draw a nice looking curve).
|
||||||
|
int rootsOfROCMinusW(float[] roots, int off, final float w, final float err) {
|
||||||
|
// no OOB exception, because by now off<=6, and roots.length >= 10
|
||||||
|
assert off <= 6 && roots.length >= 10;
|
||||||
|
int ret = off;
|
||||||
|
int numPerpdfddf = perpendiculardfddf(roots, off);
|
||||||
|
float t0 = 0, ft0 = ROCsq(t0) - w*w;
|
||||||
|
roots[off + numPerpdfddf] = 1f; // always check interval end points
|
||||||
|
numPerpdfddf++;
|
||||||
|
for (int i = off; i < off + numPerpdfddf; i++) {
|
||||||
|
float t1 = roots[i], ft1 = ROCsq(t1) - w*w;
|
||||||
|
if (ft0 == 0f) {
|
||||||
|
roots[ret++] = t0;
|
||||||
|
} else if (ft1 * ft0 < 0f) { // have opposite signs
|
||||||
|
// (ROC(t)^2 == w^2) == (ROC(t) == w) is true because
|
||||||
|
// ROC(t) >= 0 for all t.
|
||||||
|
roots[ret++] = falsePositionROCsqMinusX(t0, t1, w*w, err);
|
||||||
|
}
|
||||||
|
t0 = t1;
|
||||||
|
ft0 = ft1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret - off;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float eliminateInf(float x) {
|
||||||
|
return (x == Float.POSITIVE_INFINITY ? Float.MAX_VALUE :
|
||||||
|
(x == Float.NEGATIVE_INFINITY ? Float.MIN_VALUE : x));
|
||||||
|
}
|
||||||
|
|
||||||
|
// A slight modification of the false position algorithm on wikipedia.
|
||||||
|
// This only works for the ROCsq-x functions. It might be nice to have
|
||||||
|
// the function as an argument, but that would be awkward in java6.
|
||||||
|
// TODO: It is something to consider for java8 (or whenever lambda
|
||||||
|
// expressions make it into the language), depending on how closures
|
||||||
|
// and turn out. Same goes for the newton's method
|
||||||
|
// algorithm in Helpers.java
|
||||||
|
private float falsePositionROCsqMinusX(float x0, float x1,
|
||||||
|
final float x, final float err)
|
||||||
|
{
|
||||||
|
final int iterLimit = 100;
|
||||||
|
int side = 0;
|
||||||
|
float t = x1, ft = eliminateInf(ROCsq(t) - x);
|
||||||
|
float s = x0, fs = eliminateInf(ROCsq(s) - x);
|
||||||
|
float r = s, fr;
|
||||||
|
for (int i = 0; i < iterLimit && Math.abs(t - s) > err * Math.abs(t + s); i++) {
|
||||||
|
r = (fs * t - ft * s) / (fs - ft);
|
||||||
|
fr = ROCsq(r) - x;
|
||||||
|
if (sameSign(fr, ft)) {
|
||||||
|
ft = fr; t = r;
|
||||||
|
if (side < 0) {
|
||||||
|
fs /= (1 << (-side));
|
||||||
|
side--;
|
||||||
|
} else {
|
||||||
|
side = -1;
|
||||||
|
}
|
||||||
|
} else if (fr * fs > 0) {
|
||||||
|
fs = fr; s = r;
|
||||||
|
if (side > 0) {
|
||||||
|
ft /= (1 << side);
|
||||||
|
side++;
|
||||||
|
} else {
|
||||||
|
side = 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean sameSign(float x, float y) {
|
||||||
|
// another way is to test if x*y > 0. This is bad for small x, y.
|
||||||
|
return (x < 0f && y < 0f) || (x > 0f && y > 0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the radius of curvature squared at t of this curve
|
||||||
|
// see http://en.wikipedia.org/wiki/Radius_of_curvature_(applications)
|
||||||
|
private float ROCsq(final float t) {
|
||||||
|
// dx=xat(t) and dy=yat(t). These calls have been inlined for efficiency
|
||||||
|
final float dx = t * (t * dax + dbx) + cx;
|
||||||
|
final float dy = t * (t * day + dby) + cy;
|
||||||
|
final float ddx = 2f * dax * t + dbx;
|
||||||
|
final float ddy = 2f * day * t + dby;
|
||||||
|
final float dx2dy2 = dx*dx + dy*dy;
|
||||||
|
final float ddx2ddy2 = ddx*ddx + ddy*ddy;
|
||||||
|
final float ddxdxddydy = ddx*dx + ddy*dy;
|
||||||
|
return dx2dy2*((dx2dy2*dx2dy2) / (dx2dy2 * ddx2ddy2 - ddxdxddydy*ddxdxddydy));
|
||||||
|
}
|
||||||
|
|
||||||
|
// curve to be broken should be in pts
|
||||||
|
// this will change the contents of pts but not Ts
|
||||||
|
// TODO: There's no reason for Ts to be an array. All we need is a sequence
|
||||||
|
// of t values at which to subdivide. An array statisfies this condition,
|
||||||
|
// but is unnecessarily restrictive. Ts should be an Iterator<Float> instead.
|
||||||
|
// Doing this will also make dashing easier, since we could easily make
|
||||||
|
// LengthIterator an Iterator<Float> and feed it to this function to simplify
|
||||||
|
// the loop in Dasher.somethingTo.
|
||||||
|
BreakPtrIterator breakPtsAtTs(final float[] pts, final int type,
|
||||||
|
final float[] Ts, final int numTs)
|
||||||
|
{
|
||||||
|
assert pts.length >= 2*type && numTs <= Ts.length;
|
||||||
|
|
||||||
|
// initialize shared iterator:
|
||||||
|
iterator.init(pts, type, Ts, numTs);
|
||||||
|
|
||||||
|
return iterator;
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class BreakPtrIterator {
|
||||||
|
private int nextCurveIdx;
|
||||||
|
private int curCurveOff;
|
||||||
|
private float prevT;
|
||||||
|
private float[] pts;
|
||||||
|
private int type;
|
||||||
|
private float[] ts;
|
||||||
|
private int numTs;
|
||||||
|
|
||||||
|
void init(final float[] pts, final int type,
|
||||||
|
final float[] ts, final int numTs) {
|
||||||
|
this.pts = pts;
|
||||||
|
this.type = type;
|
||||||
|
this.ts = ts;
|
||||||
|
this.numTs = numTs;
|
||||||
|
|
||||||
|
nextCurveIdx = 0;
|
||||||
|
curCurveOff = 0;
|
||||||
|
prevT = 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasNext() {
|
||||||
|
return nextCurveIdx <= numTs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int next() {
|
||||||
|
int ret;
|
||||||
|
if (nextCurveIdx < numTs) {
|
||||||
|
float curT = ts[nextCurveIdx];
|
||||||
|
float splitT = (curT - prevT) / (1f - prevT);
|
||||||
|
Helpers.subdivideAt(splitT,
|
||||||
|
pts, curCurveOff,
|
||||||
|
pts, 0,
|
||||||
|
pts, type, type);
|
||||||
|
prevT = curT;
|
||||||
|
ret = 0;
|
||||||
|
curCurveOff = type;
|
||||||
|
} else {
|
||||||
|
ret = curCurveOff;
|
||||||
|
}
|
||||||
|
nextCurveIdx++;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
702
jdk/src/java.desktop/share/classes/sun/java2d/marlin/Dasher.java
Normal file
702
jdk/src/java.desktop/share/classes/sun/java2d/marlin/Dasher.java
Normal file
@ -0,0 +1,702 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sun.java2d.marlin;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import sun.awt.geom.PathConsumer2D;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The <code>Dasher</code> class takes a series of linear commands
|
||||||
|
* (<code>moveTo</code>, <code>lineTo</code>, <code>close</code> and
|
||||||
|
* <code>end</code>) and breaks them into smaller segments according to a
|
||||||
|
* dash pattern array and a starting dash phase.
|
||||||
|
*
|
||||||
|
* <p> Issues: in J2Se, a zero length dash segment as drawn as a very
|
||||||
|
* short dash, whereas Pisces does not draw anything. The PostScript
|
||||||
|
* semantics are unclear.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
final class Dasher implements sun.awt.geom.PathConsumer2D, MarlinConst {
|
||||||
|
|
||||||
|
static final int recLimit = 4;
|
||||||
|
static final float ERR = 0.01f;
|
||||||
|
static final float minTincrement = 1f / (1 << recLimit);
|
||||||
|
|
||||||
|
private PathConsumer2D out;
|
||||||
|
private float[] dash;
|
||||||
|
private int dashLen;
|
||||||
|
private float startPhase;
|
||||||
|
private boolean startDashOn;
|
||||||
|
private int startIdx;
|
||||||
|
|
||||||
|
private boolean starting;
|
||||||
|
private boolean needsMoveTo;
|
||||||
|
|
||||||
|
private int idx;
|
||||||
|
private boolean dashOn;
|
||||||
|
private float phase;
|
||||||
|
|
||||||
|
private float sx, sy;
|
||||||
|
private float x0, y0;
|
||||||
|
|
||||||
|
// temporary storage for the current curve
|
||||||
|
private final float[] curCurvepts;
|
||||||
|
|
||||||
|
// per-thread renderer context
|
||||||
|
final RendererContext rdrCtx;
|
||||||
|
|
||||||
|
// dashes array (dirty)
|
||||||
|
final float[] dashes_initial = new float[INITIAL_ARRAY];
|
||||||
|
|
||||||
|
// flag to recycle dash array copy
|
||||||
|
boolean recycleDashes;
|
||||||
|
|
||||||
|
// per-thread initial arrays (large enough to satisfy most usages
|
||||||
|
// +1 to avoid recycling in Helpers.widenArray()
|
||||||
|
private final float[] firstSegmentsBuffer_initial = new float[INITIAL_ARRAY + 1];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a <code>Dasher</code>.
|
||||||
|
* @param rdrCtx per-thread renderer context
|
||||||
|
*/
|
||||||
|
Dasher(final RendererContext rdrCtx) {
|
||||||
|
this.rdrCtx = rdrCtx;
|
||||||
|
|
||||||
|
firstSegmentsBuffer = firstSegmentsBuffer_initial;
|
||||||
|
|
||||||
|
// we need curCurvepts to be able to contain 2 curves because when
|
||||||
|
// dashing curves, we need to subdivide it
|
||||||
|
curCurvepts = new float[8 * 2];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the <code>Dasher</code>.
|
||||||
|
*
|
||||||
|
* @param out an output <code>PathConsumer2D</code>.
|
||||||
|
* @param dash an array of <code>float</code>s containing the dash pattern
|
||||||
|
* @param dashLen length of the given dash array
|
||||||
|
* @param phase a <code>float</code> containing the dash phase
|
||||||
|
* @param recycleDashes true to indicate to recycle the given dash array
|
||||||
|
* @return this instance
|
||||||
|
*/
|
||||||
|
Dasher init(final PathConsumer2D out, float[] dash, int dashLen,
|
||||||
|
float phase, boolean recycleDashes)
|
||||||
|
{
|
||||||
|
if (phase < 0f) {
|
||||||
|
throw new IllegalArgumentException("phase < 0 !");
|
||||||
|
}
|
||||||
|
this.out = out;
|
||||||
|
|
||||||
|
// Normalize so 0 <= phase < dash[0]
|
||||||
|
int idx = 0;
|
||||||
|
dashOn = true;
|
||||||
|
float d;
|
||||||
|
while (phase >= (d = dash[idx])) {
|
||||||
|
phase -= d;
|
||||||
|
idx = (idx + 1) % dashLen;
|
||||||
|
dashOn = !dashOn;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dash = dash;
|
||||||
|
this.dashLen = dashLen;
|
||||||
|
this.startPhase = this.phase = phase;
|
||||||
|
this.startDashOn = dashOn;
|
||||||
|
this.startIdx = idx;
|
||||||
|
this.starting = true;
|
||||||
|
needsMoveTo = false;
|
||||||
|
firstSegidx = 0;
|
||||||
|
|
||||||
|
this.recycleDashes = recycleDashes;
|
||||||
|
|
||||||
|
return this; // fluent API
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disposes this dasher:
|
||||||
|
* clean up before reusing this instance
|
||||||
|
*/
|
||||||
|
void dispose() {
|
||||||
|
if (doCleanDirty) {
|
||||||
|
// Force zero-fill dirty arrays:
|
||||||
|
Arrays.fill(curCurvepts, 0f);
|
||||||
|
Arrays.fill(firstSegmentsBuffer, 0f);
|
||||||
|
}
|
||||||
|
// Return arrays:
|
||||||
|
if (recycleDashes && dash != dashes_initial) {
|
||||||
|
rdrCtx.putDirtyFloatArray(dash);
|
||||||
|
dash = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstSegmentsBuffer != firstSegmentsBuffer_initial) {
|
||||||
|
rdrCtx.putDirtyFloatArray(firstSegmentsBuffer);
|
||||||
|
firstSegmentsBuffer = firstSegmentsBuffer_initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void moveTo(float x0, float y0) {
|
||||||
|
if (firstSegidx > 0) {
|
||||||
|
out.moveTo(sx, sy);
|
||||||
|
emitFirstSegments();
|
||||||
|
}
|
||||||
|
needsMoveTo = true;
|
||||||
|
this.idx = startIdx;
|
||||||
|
this.dashOn = this.startDashOn;
|
||||||
|
this.phase = this.startPhase;
|
||||||
|
this.sx = this.x0 = x0;
|
||||||
|
this.sy = this.y0 = y0;
|
||||||
|
this.starting = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void emitSeg(float[] buf, int off, int type) {
|
||||||
|
switch (type) {
|
||||||
|
case 8:
|
||||||
|
out.curveTo(buf[off+0], buf[off+1],
|
||||||
|
buf[off+2], buf[off+3],
|
||||||
|
buf[off+4], buf[off+5]);
|
||||||
|
return;
|
||||||
|
case 6:
|
||||||
|
out.quadTo(buf[off+0], buf[off+1],
|
||||||
|
buf[off+2], buf[off+3]);
|
||||||
|
return;
|
||||||
|
case 4:
|
||||||
|
out.lineTo(buf[off], buf[off+1]);
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void emitFirstSegments() {
|
||||||
|
final float[] fSegBuf = firstSegmentsBuffer;
|
||||||
|
|
||||||
|
for (int i = 0; i < firstSegidx; ) {
|
||||||
|
int type = (int)fSegBuf[i];
|
||||||
|
emitSeg(fSegBuf, i + 1, type);
|
||||||
|
i += (type - 1);
|
||||||
|
}
|
||||||
|
firstSegidx = 0;
|
||||||
|
}
|
||||||
|
// We don't emit the first dash right away. If we did, caps would be
|
||||||
|
// drawn on it, but we need joins to be drawn if there's a closePath()
|
||||||
|
// So, we store the path elements that make up the first dash in the
|
||||||
|
// buffer below.
|
||||||
|
private float[] firstSegmentsBuffer; // dynamic array
|
||||||
|
private int firstSegidx;
|
||||||
|
|
||||||
|
// precondition: pts must be in relative coordinates (relative to x0,y0)
|
||||||
|
// fullCurve is true iff the curve in pts has not been split.
|
||||||
|
private void goTo(float[] pts, int off, final int type) {
|
||||||
|
float x = pts[off + type - 4];
|
||||||
|
float y = pts[off + type - 3];
|
||||||
|
if (dashOn) {
|
||||||
|
if (starting) {
|
||||||
|
int len = type - 2 + 1;
|
||||||
|
int segIdx = firstSegidx;
|
||||||
|
float[] buf = firstSegmentsBuffer;
|
||||||
|
if (segIdx + len > buf.length) {
|
||||||
|
if (doStats) {
|
||||||
|
RendererContext.stats.stat_array_dasher_firstSegmentsBuffer
|
||||||
|
.add(segIdx + len);
|
||||||
|
}
|
||||||
|
firstSegmentsBuffer = buf
|
||||||
|
= rdrCtx.widenDirtyFloatArray(buf, segIdx, segIdx + len);
|
||||||
|
}
|
||||||
|
buf[segIdx++] = type;
|
||||||
|
len--;
|
||||||
|
// small arraycopy (2, 4 or 6) but with offset:
|
||||||
|
System.arraycopy(pts, off, buf, segIdx, len);
|
||||||
|
segIdx += len;
|
||||||
|
firstSegidx = segIdx;
|
||||||
|
} else {
|
||||||
|
if (needsMoveTo) {
|
||||||
|
out.moveTo(x0, y0);
|
||||||
|
needsMoveTo = false;
|
||||||
|
}
|
||||||
|
emitSeg(pts, off, type);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
starting = false;
|
||||||
|
needsMoveTo = true;
|
||||||
|
}
|
||||||
|
this.x0 = x;
|
||||||
|
this.y0 = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void lineTo(float x1, float y1) {
|
||||||
|
float dx = x1 - x0;
|
||||||
|
float dy = y1 - y0;
|
||||||
|
|
||||||
|
float len = dx*dx + dy*dy;
|
||||||
|
if (len == 0f) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
len = (float) Math.sqrt(len);
|
||||||
|
|
||||||
|
// The scaling factors needed to get the dx and dy of the
|
||||||
|
// transformed dash segments.
|
||||||
|
final float cx = dx / len;
|
||||||
|
final float cy = dy / len;
|
||||||
|
|
||||||
|
final float[] _curCurvepts = curCurvepts;
|
||||||
|
final float[] _dash = dash;
|
||||||
|
|
||||||
|
float leftInThisDashSegment;
|
||||||
|
float dashdx, dashdy, p;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
leftInThisDashSegment = _dash[idx] - phase;
|
||||||
|
|
||||||
|
if (len <= leftInThisDashSegment) {
|
||||||
|
_curCurvepts[0] = x1;
|
||||||
|
_curCurvepts[1] = y1;
|
||||||
|
goTo(_curCurvepts, 0, 4);
|
||||||
|
|
||||||
|
// Advance phase within current dash segment
|
||||||
|
phase += len;
|
||||||
|
// TODO: compare float values using epsilon:
|
||||||
|
if (len == leftInThisDashSegment) {
|
||||||
|
phase = 0f;
|
||||||
|
idx = (idx + 1) % dashLen;
|
||||||
|
dashOn = !dashOn;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dashdx = _dash[idx] * cx;
|
||||||
|
dashdy = _dash[idx] * cy;
|
||||||
|
|
||||||
|
if (phase == 0f) {
|
||||||
|
_curCurvepts[0] = x0 + dashdx;
|
||||||
|
_curCurvepts[1] = y0 + dashdy;
|
||||||
|
} else {
|
||||||
|
p = leftInThisDashSegment / _dash[idx];
|
||||||
|
_curCurvepts[0] = x0 + p * dashdx;
|
||||||
|
_curCurvepts[1] = y0 + p * dashdy;
|
||||||
|
}
|
||||||
|
|
||||||
|
goTo(_curCurvepts, 0, 4);
|
||||||
|
|
||||||
|
len -= leftInThisDashSegment;
|
||||||
|
// Advance to next dash segment
|
||||||
|
idx = (idx + 1) % dashLen;
|
||||||
|
dashOn = !dashOn;
|
||||||
|
phase = 0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// shared instance in Dasher
|
||||||
|
private final LengthIterator li = new LengthIterator();
|
||||||
|
|
||||||
|
// preconditions: curCurvepts must be an array of length at least 2 * type,
|
||||||
|
// that contains the curve we want to dash in the first type elements
|
||||||
|
private void somethingTo(int type) {
|
||||||
|
if (pointCurve(curCurvepts, type)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
li.initializeIterationOnCurve(curCurvepts, type);
|
||||||
|
|
||||||
|
// initially the current curve is at curCurvepts[0...type]
|
||||||
|
int curCurveoff = 0;
|
||||||
|
float lastSplitT = 0f;
|
||||||
|
float t;
|
||||||
|
float leftInThisDashSegment = dash[idx] - phase;
|
||||||
|
|
||||||
|
while ((t = li.next(leftInThisDashSegment)) < 1f) {
|
||||||
|
if (t != 0f) {
|
||||||
|
Helpers.subdivideAt((t - lastSplitT) / (1f - lastSplitT),
|
||||||
|
curCurvepts, curCurveoff,
|
||||||
|
curCurvepts, 0,
|
||||||
|
curCurvepts, type, type);
|
||||||
|
lastSplitT = t;
|
||||||
|
goTo(curCurvepts, 2, type);
|
||||||
|
curCurveoff = type;
|
||||||
|
}
|
||||||
|
// Advance to next dash segment
|
||||||
|
idx = (idx + 1) % dashLen;
|
||||||
|
dashOn = !dashOn;
|
||||||
|
phase = 0f;
|
||||||
|
leftInThisDashSegment = dash[idx];
|
||||||
|
}
|
||||||
|
goTo(curCurvepts, curCurveoff+2, type);
|
||||||
|
phase += li.lastSegLen();
|
||||||
|
if (phase >= dash[idx]) {
|
||||||
|
phase = 0f;
|
||||||
|
idx = (idx + 1) % dashLen;
|
||||||
|
dashOn = !dashOn;
|
||||||
|
}
|
||||||
|
// reset LengthIterator:
|
||||||
|
li.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean pointCurve(float[] curve, int type) {
|
||||||
|
for (int i = 2; i < type; i++) {
|
||||||
|
if (curve[i] != curve[i-2]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Objects of this class are used to iterate through curves. They return
|
||||||
|
// t values where the left side of the curve has a specified length.
|
||||||
|
// It does this by subdividing the input curve until a certain error
|
||||||
|
// condition has been met. A recursive subdivision procedure would
|
||||||
|
// return as many as 1<<limit curves, but this is an iterator and we
|
||||||
|
// don't need all the curves all at once, so what we carry out a
|
||||||
|
// lazy inorder traversal of the recursion tree (meaning we only move
|
||||||
|
// through the tree when we need the next subdivided curve). This saves
|
||||||
|
// us a lot of memory because at any one time we only need to store
|
||||||
|
// limit+1 curves - one for each level of the tree + 1.
|
||||||
|
// NOTE: the way we do things here is not enough to traverse a general
|
||||||
|
// tree; however, the trees we are interested in have the property that
|
||||||
|
// every non leaf node has exactly 2 children
|
||||||
|
static final class LengthIterator {
|
||||||
|
private enum Side {LEFT, RIGHT};
|
||||||
|
// Holds the curves at various levels of the recursion. The root
|
||||||
|
// (i.e. the original curve) is at recCurveStack[0] (but then it
|
||||||
|
// gets subdivided, the left half is put at 1, so most of the time
|
||||||
|
// only the right half of the original curve is at 0)
|
||||||
|
private final float[][] recCurveStack; // dirty
|
||||||
|
// sides[i] indicates whether the node at level i+1 in the path from
|
||||||
|
// the root to the current leaf is a left or right child of its parent.
|
||||||
|
private final Side[] sides; // dirty
|
||||||
|
private int curveType;
|
||||||
|
// lastT and nextT delimit the current leaf.
|
||||||
|
private float nextT;
|
||||||
|
private float lenAtNextT;
|
||||||
|
private float lastT;
|
||||||
|
private float lenAtLastT;
|
||||||
|
private float lenAtLastSplit;
|
||||||
|
private float lastSegLen;
|
||||||
|
// the current level in the recursion tree. 0 is the root. limit
|
||||||
|
// is the deepest possible leaf.
|
||||||
|
private int recLevel;
|
||||||
|
private boolean done;
|
||||||
|
|
||||||
|
// the lengths of the lines of the control polygon. Only its first
|
||||||
|
// curveType/2 - 1 elements are valid. This is an optimization. See
|
||||||
|
// next(float) for more detail.
|
||||||
|
private final float[] curLeafCtrlPolyLengths = new float[3];
|
||||||
|
|
||||||
|
LengthIterator() {
|
||||||
|
this.recCurveStack = new float[recLimit + 1][8];
|
||||||
|
this.sides = new Side[recLimit];
|
||||||
|
// if any methods are called without first initializing this object
|
||||||
|
// on a curve, we want it to fail ASAP.
|
||||||
|
this.nextT = Float.MAX_VALUE;
|
||||||
|
this.lenAtNextT = Float.MAX_VALUE;
|
||||||
|
this.lenAtLastSplit = Float.MIN_VALUE;
|
||||||
|
this.recLevel = Integer.MIN_VALUE;
|
||||||
|
this.lastSegLen = Float.MAX_VALUE;
|
||||||
|
this.done = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset this LengthIterator.
|
||||||
|
*/
|
||||||
|
void reset() {
|
||||||
|
// keep data dirty
|
||||||
|
// as it appears not useful to reset data:
|
||||||
|
if (doCleanDirty) {
|
||||||
|
final int recLimit = recCurveStack.length - 1;
|
||||||
|
for (int i = recLimit; i >= 0; i--) {
|
||||||
|
Arrays.fill(recCurveStack[i], 0f);
|
||||||
|
}
|
||||||
|
Arrays.fill(sides, Side.LEFT);
|
||||||
|
Arrays.fill(curLeafCtrlPolyLengths, 0f);
|
||||||
|
Arrays.fill(nextRoots, 0f);
|
||||||
|
Arrays.fill(flatLeafCoefCache, 0f);
|
||||||
|
flatLeafCoefCache[2] = -1f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void initializeIterationOnCurve(float[] pts, int type) {
|
||||||
|
// optimize arraycopy (8 values faster than 6 = type):
|
||||||
|
System.arraycopy(pts, 0, recCurveStack[0], 0, 8);
|
||||||
|
this.curveType = type;
|
||||||
|
this.recLevel = 0;
|
||||||
|
this.lastT = 0f;
|
||||||
|
this.lenAtLastT = 0f;
|
||||||
|
this.nextT = 0f;
|
||||||
|
this.lenAtNextT = 0f;
|
||||||
|
goLeft(); // initializes nextT and lenAtNextT properly
|
||||||
|
this.lenAtLastSplit = 0f;
|
||||||
|
if (recLevel > 0) {
|
||||||
|
this.sides[0] = Side.LEFT;
|
||||||
|
this.done = false;
|
||||||
|
} else {
|
||||||
|
// the root of the tree is a leaf so we're done.
|
||||||
|
this.sides[0] = Side.RIGHT;
|
||||||
|
this.done = true;
|
||||||
|
}
|
||||||
|
this.lastSegLen = 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0 == false, 1 == true, -1 == invalid cached value.
|
||||||
|
private int cachedHaveLowAcceleration = -1;
|
||||||
|
|
||||||
|
private boolean haveLowAcceleration(float err) {
|
||||||
|
if (cachedHaveLowAcceleration == -1) {
|
||||||
|
final float len1 = curLeafCtrlPolyLengths[0];
|
||||||
|
final float len2 = curLeafCtrlPolyLengths[1];
|
||||||
|
// the test below is equivalent to !within(len1/len2, 1, err).
|
||||||
|
// It is using a multiplication instead of a division, so it
|
||||||
|
// should be a bit faster.
|
||||||
|
if (!Helpers.within(len1, len2, err*len2)) {
|
||||||
|
cachedHaveLowAcceleration = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (curveType == 8) {
|
||||||
|
final float len3 = curLeafCtrlPolyLengths[2];
|
||||||
|
// if len1 is close to 2 and 2 is close to 3, that probably
|
||||||
|
// means 1 is close to 3 so the second part of this test might
|
||||||
|
// not be needed, but it doesn't hurt to include it.
|
||||||
|
final float errLen3 = err * len3;
|
||||||
|
if (!(Helpers.within(len2, len3, errLen3) &&
|
||||||
|
Helpers.within(len1, len3, errLen3))) {
|
||||||
|
cachedHaveLowAcceleration = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cachedHaveLowAcceleration = 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (cachedHaveLowAcceleration == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// we want to avoid allocations/gc so we keep this array so we
|
||||||
|
// can put roots in it,
|
||||||
|
private final float[] nextRoots = new float[4];
|
||||||
|
|
||||||
|
// caches the coefficients of the current leaf in its flattened
|
||||||
|
// form (see inside next() for what that means). The cache is
|
||||||
|
// invalid when it's third element is negative, since in any
|
||||||
|
// valid flattened curve, this would be >= 0.
|
||||||
|
private final float[] flatLeafCoefCache = new float[]{0f, 0f, -1f, 0f};
|
||||||
|
|
||||||
|
// returns the t value where the remaining curve should be split in
|
||||||
|
// order for the left subdivided curve to have length len. If len
|
||||||
|
// is >= than the length of the uniterated curve, it returns 1.
|
||||||
|
float next(final float len) {
|
||||||
|
final float targetLength = lenAtLastSplit + len;
|
||||||
|
while (lenAtNextT < targetLength) {
|
||||||
|
if (done) {
|
||||||
|
lastSegLen = lenAtNextT - lenAtLastSplit;
|
||||||
|
return 1f;
|
||||||
|
}
|
||||||
|
goToNextLeaf();
|
||||||
|
}
|
||||||
|
lenAtLastSplit = targetLength;
|
||||||
|
final float leaflen = lenAtNextT - lenAtLastT;
|
||||||
|
float t = (targetLength - lenAtLastT) / leaflen;
|
||||||
|
|
||||||
|
// cubicRootsInAB is a fairly expensive call, so we just don't do it
|
||||||
|
// if the acceleration in this section of the curve is small enough.
|
||||||
|
if (!haveLowAcceleration(0.05f)) {
|
||||||
|
// We flatten the current leaf along the x axis, so that we're
|
||||||
|
// left with a, b, c which define a 1D Bezier curve. We then
|
||||||
|
// solve this to get the parameter of the original leaf that
|
||||||
|
// gives us the desired length.
|
||||||
|
final float[] _flatLeafCoefCache = flatLeafCoefCache;
|
||||||
|
|
||||||
|
if (_flatLeafCoefCache[2] < 0) {
|
||||||
|
float x = 0f + curLeafCtrlPolyLengths[0],
|
||||||
|
y = x + curLeafCtrlPolyLengths[1];
|
||||||
|
if (curveType == 8) {
|
||||||
|
float z = y + curLeafCtrlPolyLengths[2];
|
||||||
|
_flatLeafCoefCache[0] = 3f * (x - y) + z;
|
||||||
|
_flatLeafCoefCache[1] = 3f * (y - 2f * x);
|
||||||
|
_flatLeafCoefCache[2] = 3f * x;
|
||||||
|
_flatLeafCoefCache[3] = -z;
|
||||||
|
} else if (curveType == 6) {
|
||||||
|
_flatLeafCoefCache[0] = 0f;
|
||||||
|
_flatLeafCoefCache[1] = y - 2f * x;
|
||||||
|
_flatLeafCoefCache[2] = 2f * x;
|
||||||
|
_flatLeafCoefCache[3] = -y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
float a = _flatLeafCoefCache[0];
|
||||||
|
float b = _flatLeafCoefCache[1];
|
||||||
|
float c = _flatLeafCoefCache[2];
|
||||||
|
float d = t * _flatLeafCoefCache[3];
|
||||||
|
|
||||||
|
// we use cubicRootsInAB here, because we want only roots in 0, 1,
|
||||||
|
// and our quadratic root finder doesn't filter, so it's just a
|
||||||
|
// matter of convenience.
|
||||||
|
int n = Helpers.cubicRootsInAB(a, b, c, d, nextRoots, 0, 0, 1);
|
||||||
|
if (n == 1 && !Float.isNaN(nextRoots[0])) {
|
||||||
|
t = nextRoots[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// t is relative to the current leaf, so we must make it a valid parameter
|
||||||
|
// of the original curve.
|
||||||
|
t = t * (nextT - lastT) + lastT;
|
||||||
|
if (t >= 1f) {
|
||||||
|
t = 1f;
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
// even if done = true, if we're here, that means targetLength
|
||||||
|
// is equal to, or very, very close to the total length of the
|
||||||
|
// curve, so lastSegLen won't be too high. In cases where len
|
||||||
|
// overshoots the curve, this method will exit in the while
|
||||||
|
// loop, and lastSegLen will still be set to the right value.
|
||||||
|
lastSegLen = len;
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
float lastSegLen() {
|
||||||
|
return lastSegLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
// go to the next leaf (in an inorder traversal) in the recursion tree
|
||||||
|
// preconditions: must be on a leaf, and that leaf must not be the root.
|
||||||
|
private void goToNextLeaf() {
|
||||||
|
// We must go to the first ancestor node that has an unvisited
|
||||||
|
// right child.
|
||||||
|
int _recLevel = recLevel;
|
||||||
|
final Side[] _sides = sides;
|
||||||
|
|
||||||
|
_recLevel--;
|
||||||
|
while(_sides[_recLevel] == Side.RIGHT) {
|
||||||
|
if (_recLevel == 0) {
|
||||||
|
recLevel = 0;
|
||||||
|
done = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_recLevel--;
|
||||||
|
}
|
||||||
|
|
||||||
|
_sides[_recLevel] = Side.RIGHT;
|
||||||
|
// optimize arraycopy (8 values faster than 6 = type):
|
||||||
|
System.arraycopy(recCurveStack[_recLevel], 0,
|
||||||
|
recCurveStack[_recLevel+1], 0, 8);
|
||||||
|
_recLevel++;
|
||||||
|
|
||||||
|
recLevel = _recLevel;
|
||||||
|
goLeft();
|
||||||
|
}
|
||||||
|
|
||||||
|
// go to the leftmost node from the current node. Return its length.
|
||||||
|
private void goLeft() {
|
||||||
|
float len = onLeaf();
|
||||||
|
if (len >= 0f) {
|
||||||
|
lastT = nextT;
|
||||||
|
lenAtLastT = lenAtNextT;
|
||||||
|
nextT += (1 << (recLimit - recLevel)) * minTincrement;
|
||||||
|
lenAtNextT += len;
|
||||||
|
// invalidate caches
|
||||||
|
flatLeafCoefCache[2] = -1f;
|
||||||
|
cachedHaveLowAcceleration = -1;
|
||||||
|
} else {
|
||||||
|
Helpers.subdivide(recCurveStack[recLevel], 0,
|
||||||
|
recCurveStack[recLevel+1], 0,
|
||||||
|
recCurveStack[recLevel], 0, curveType);
|
||||||
|
sides[recLevel] = Side.LEFT;
|
||||||
|
recLevel++;
|
||||||
|
goLeft();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is a bit of a hack. It returns -1 if we're not on a leaf, and
|
||||||
|
// the length of the leaf if we are on a leaf.
|
||||||
|
private float onLeaf() {
|
||||||
|
float[] curve = recCurveStack[recLevel];
|
||||||
|
float polyLen = 0f;
|
||||||
|
|
||||||
|
float x0 = curve[0], y0 = curve[1];
|
||||||
|
for (int i = 2; i < curveType; i += 2) {
|
||||||
|
final float x1 = curve[i], y1 = curve[i+1];
|
||||||
|
final float len = Helpers.linelen(x0, y0, x1, y1);
|
||||||
|
polyLen += len;
|
||||||
|
curLeafCtrlPolyLengths[i/2 - 1] = len;
|
||||||
|
x0 = x1;
|
||||||
|
y0 = y1;
|
||||||
|
}
|
||||||
|
|
||||||
|
final float lineLen = Helpers.linelen(curve[0], curve[1],
|
||||||
|
curve[curveType-2],
|
||||||
|
curve[curveType-1]);
|
||||||
|
if ((polyLen - lineLen) < ERR || recLevel == recLimit) {
|
||||||
|
return (polyLen + lineLen) / 2f;
|
||||||
|
}
|
||||||
|
return -1f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void curveTo(float x1, float y1,
|
||||||
|
float x2, float y2,
|
||||||
|
float x3, float y3)
|
||||||
|
{
|
||||||
|
final float[] _curCurvepts = curCurvepts;
|
||||||
|
_curCurvepts[0] = x0; _curCurvepts[1] = y0;
|
||||||
|
_curCurvepts[2] = x1; _curCurvepts[3] = y1;
|
||||||
|
_curCurvepts[4] = x2; _curCurvepts[5] = y2;
|
||||||
|
_curCurvepts[6] = x3; _curCurvepts[7] = y3;
|
||||||
|
somethingTo(8);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void quadTo(float x1, float y1, float x2, float y2) {
|
||||||
|
final float[] _curCurvepts = curCurvepts;
|
||||||
|
_curCurvepts[0] = x0; _curCurvepts[1] = y0;
|
||||||
|
_curCurvepts[2] = x1; _curCurvepts[3] = y1;
|
||||||
|
_curCurvepts[4] = x2; _curCurvepts[5] = y2;
|
||||||
|
somethingTo(6);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void closePath() {
|
||||||
|
lineTo(sx, sy);
|
||||||
|
if (firstSegidx > 0) {
|
||||||
|
if (!dashOn || needsMoveTo) {
|
||||||
|
out.moveTo(sx, sy);
|
||||||
|
}
|
||||||
|
emitFirstSegments();
|
||||||
|
}
|
||||||
|
moveTo(sx, sy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pathDone() {
|
||||||
|
if (firstSegidx > 0) {
|
||||||
|
out.moveTo(sx, sy);
|
||||||
|
emitFirstSegments();
|
||||||
|
}
|
||||||
|
out.pathDone();
|
||||||
|
|
||||||
|
// Dispose this instance:
|
||||||
|
dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getNativeConsumer() {
|
||||||
|
throw new InternalError("Dasher does not use a native consumer");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,152 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sun.java2d.marlin;
|
||||||
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import static sun.java2d.marlin.MarlinUtils.logException;
|
||||||
|
import static sun.java2d.marlin.MarlinUtils.logInfo;
|
||||||
|
|
||||||
|
final class FloatArrayCache implements MarlinConst {
|
||||||
|
|
||||||
|
private final int arraySize;
|
||||||
|
private final ArrayDeque<float[]> floatArrays;
|
||||||
|
// stats
|
||||||
|
private int getOp = 0;
|
||||||
|
private int createOp = 0;
|
||||||
|
private int returnOp = 0;
|
||||||
|
|
||||||
|
void dumpStats() {
|
||||||
|
if (getOp > 0) {
|
||||||
|
logInfo("FloatArrayCache[" + arraySize + "]: get: " + getOp
|
||||||
|
+ " created: " + createOp + " - returned: " + returnOp
|
||||||
|
+ " :: cache size: " + floatArrays.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FloatArrayCache(final int arraySize) {
|
||||||
|
this.arraySize = arraySize;
|
||||||
|
// small but enough: almost 1 cache line
|
||||||
|
this.floatArrays = new ArrayDeque<float[]>(6);
|
||||||
|
}
|
||||||
|
|
||||||
|
float[] getArray() {
|
||||||
|
if (doStats) {
|
||||||
|
getOp++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// use cache
|
||||||
|
final float[] array = floatArrays.pollLast();
|
||||||
|
|
||||||
|
if (array != null) {
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doStats) {
|
||||||
|
createOp++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new float[arraySize];
|
||||||
|
}
|
||||||
|
|
||||||
|
void putDirtyArray(final float[] array, final int length) {
|
||||||
|
if (length != arraySize) {
|
||||||
|
if (doChecks) {
|
||||||
|
System.out.println("ArrayCache: bad length = " + length);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (doStats) {
|
||||||
|
returnOp++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NO clean-up of array data = DIRTY ARRAY
|
||||||
|
|
||||||
|
if (doCleanDirty) {
|
||||||
|
// Force zero-fill dirty arrays:
|
||||||
|
Arrays.fill(array, 0, array.length, 0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// fill cache:
|
||||||
|
floatArrays.addLast(array);
|
||||||
|
}
|
||||||
|
|
||||||
|
void putArray(final float[] array, final int length,
|
||||||
|
final int fromIndex, final int toIndex)
|
||||||
|
{
|
||||||
|
if (length != arraySize) {
|
||||||
|
if (doChecks) {
|
||||||
|
System.out.println("ArrayCache: bad length = " + length);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (doStats) {
|
||||||
|
returnOp++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// clean-up array of dirty part[fromIndex; toIndex[
|
||||||
|
fill(array, fromIndex, toIndex, 0f);
|
||||||
|
|
||||||
|
// fill cache:
|
||||||
|
floatArrays.addLast(array);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fill(final float[] array, final int fromIndex,
|
||||||
|
final int toIndex, final float value)
|
||||||
|
{
|
||||||
|
// clear array data:
|
||||||
|
/*
|
||||||
|
* Arrays.fill is faster than System.arraycopy(empty array)
|
||||||
|
* or Unsafe.setMemory(byte 0)
|
||||||
|
*/
|
||||||
|
if (toIndex != 0) {
|
||||||
|
Arrays.fill(array, fromIndex, toIndex, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doChecks) {
|
||||||
|
check(array, 0, array.length, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void check(final float[] array, final int fromIndex,
|
||||||
|
final int toIndex, final float value)
|
||||||
|
{
|
||||||
|
if (doChecks) {
|
||||||
|
// check zero on full array:
|
||||||
|
for (int i = fromIndex; i < toIndex; i++) {
|
||||||
|
if (array[i] != value) {
|
||||||
|
logException("Invalid array value at " + i + "\n"
|
||||||
|
+ Arrays.toString(array), new Throwable());
|
||||||
|
|
||||||
|
// ensure array is correctly filled:
|
||||||
|
Arrays.fill(array, value);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,223 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
package sun.java2d.marlin;
|
||||||
|
|
||||||
|
import sun.misc.DoubleConsts;
|
||||||
|
import sun.misc.FloatConsts;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Faster Math ceil / floor routines derived from StrictMath
|
||||||
|
*/
|
||||||
|
public final class FloatMath implements MarlinConst {
|
||||||
|
|
||||||
|
// overflow / NaN handling enabled:
|
||||||
|
static final boolean CHECK_OVERFLOW = true;
|
||||||
|
static final boolean CHECK_NAN = true;
|
||||||
|
|
||||||
|
private FloatMath() {
|
||||||
|
// utility class
|
||||||
|
}
|
||||||
|
|
||||||
|
// faster inlined min/max functions in the branch prediction is high
|
||||||
|
static float max(final float a, final float b) {
|
||||||
|
// no NaN handling
|
||||||
|
return (a >= b) ? a : b;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int max(final int a, final int b) {
|
||||||
|
return (a >= b) ? a : b;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int min(final int a, final int b) {
|
||||||
|
return (a <= b) ? a : b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the smallest (closest to negative infinity) {@code float} value
|
||||||
|
* that is greater than or equal to the argument and is equal to a
|
||||||
|
* mathematical integer. Special cases:
|
||||||
|
* <ul><li>If the argument value is already equal to a mathematical integer,
|
||||||
|
* then the result is the same as the argument. <li>If the argument is NaN
|
||||||
|
* or an infinity or positive zero or negative zero, then the result is the
|
||||||
|
* same as the argument. <li>If the argument value is less than zero but
|
||||||
|
* greater than -1.0, then the result is negative zero.</ul> Note that the
|
||||||
|
* value of {@code StrictMath.ceil(x)} is exactly the value of
|
||||||
|
* {@code -StrictMath.floor(-x)}.
|
||||||
|
*
|
||||||
|
* @param a a value.
|
||||||
|
* @return the smallest (closest to negative infinity) floating-point value
|
||||||
|
* that is greater than or equal to the argument and is equal to a
|
||||||
|
* mathematical integer.
|
||||||
|
*/
|
||||||
|
public static float ceil_f(final float a) {
|
||||||
|
// Derived from StrictMath.ceil(double):
|
||||||
|
|
||||||
|
// Inline call to Math.getExponent(a) to
|
||||||
|
// compute only once Float.floatToRawIntBits(a)
|
||||||
|
final int doppel = Float.floatToRawIntBits(a);
|
||||||
|
|
||||||
|
final int exponent = ((doppel & FloatConsts.EXP_BIT_MASK)
|
||||||
|
>> (FloatConsts.SIGNIFICAND_WIDTH - 1))
|
||||||
|
- FloatConsts.EXP_BIAS;
|
||||||
|
|
||||||
|
if (exponent < 0) {
|
||||||
|
/*
|
||||||
|
* Absolute value of argument is less than 1.
|
||||||
|
* floorOrceil(-0.0) => -0.0
|
||||||
|
* floorOrceil(+0.0) => +0.0
|
||||||
|
*/
|
||||||
|
return ((a == 0) ? a :
|
||||||
|
( (a < 0f) ? -0f : 1f) );
|
||||||
|
}
|
||||||
|
if (CHECK_OVERFLOW && (exponent >= 23)) { // 52 for double
|
||||||
|
/*
|
||||||
|
* Infinity, NaN, or a value so large it must be integral.
|
||||||
|
*/
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
// Else the argument is either an integral value already XOR it
|
||||||
|
// has to be rounded to one.
|
||||||
|
assert exponent >= 0 && exponent <= 22; // 51 for double
|
||||||
|
|
||||||
|
final int intpart = doppel
|
||||||
|
& (~(FloatConsts.SIGNIF_BIT_MASK >> exponent));
|
||||||
|
|
||||||
|
if (intpart == doppel) {
|
||||||
|
return a; // integral value (including 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0 handled above as an integer
|
||||||
|
// sign: 1 for negative, 0 for positive numbers
|
||||||
|
// add : 0 for negative and 1 for positive numbers
|
||||||
|
return Float.intBitsToFloat(intpart) + ((~intpart) >>> 31);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the largest (closest to positive infinity) {@code float} value
|
||||||
|
* that is less than or equal to the argument and is equal to a mathematical
|
||||||
|
* integer. Special cases:
|
||||||
|
* <ul><li>If the argument value is already equal to a mathematical integer,
|
||||||
|
* then the result is the same as the argument. <li>If the argument is NaN
|
||||||
|
* or an infinity or positive zero or negative zero, then the result is the
|
||||||
|
* same as the argument.</ul>
|
||||||
|
*
|
||||||
|
* @param a a value.
|
||||||
|
* @return the largest (closest to positive infinity) floating-point value
|
||||||
|
* that less than or equal to the argument and is equal to a mathematical
|
||||||
|
* integer.
|
||||||
|
*/
|
||||||
|
public static float floor_f(final float a) {
|
||||||
|
// Derived from StrictMath.floor(double):
|
||||||
|
|
||||||
|
// Inline call to Math.getExponent(a) to
|
||||||
|
// compute only once Float.floatToRawIntBits(a)
|
||||||
|
final int doppel = Float.floatToRawIntBits(a);
|
||||||
|
|
||||||
|
final int exponent = ((doppel & FloatConsts.EXP_BIT_MASK)
|
||||||
|
>> (FloatConsts.SIGNIFICAND_WIDTH - 1))
|
||||||
|
- FloatConsts.EXP_BIAS;
|
||||||
|
|
||||||
|
if (exponent < 0) {
|
||||||
|
/*
|
||||||
|
* Absolute value of argument is less than 1.
|
||||||
|
* floorOrceil(-0.0) => -0.0
|
||||||
|
* floorOrceil(+0.0) => +0.0
|
||||||
|
*/
|
||||||
|
return ((a == 0) ? a :
|
||||||
|
( (a < 0f) ? -1f : 0f) );
|
||||||
|
}
|
||||||
|
if (CHECK_OVERFLOW && (exponent >= 23)) { // 52 for double
|
||||||
|
/*
|
||||||
|
* Infinity, NaN, or a value so large it must be integral.
|
||||||
|
*/
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
// Else the argument is either an integral value already XOR it
|
||||||
|
// has to be rounded to one.
|
||||||
|
assert exponent >= 0 && exponent <= 22; // 51 for double
|
||||||
|
|
||||||
|
final int intpart = doppel
|
||||||
|
& (~(FloatConsts.SIGNIF_BIT_MASK >> exponent));
|
||||||
|
|
||||||
|
if (intpart == doppel) {
|
||||||
|
return a; // integral value (including 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0 handled above as an integer
|
||||||
|
// sign: 1 for negative, 0 for positive numbers
|
||||||
|
// add : -1 for negative and 0 for positive numbers
|
||||||
|
return Float.intBitsToFloat(intpart) + (intpart >> 31);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Faster alternative to ceil(float) optimized for the integer domain
|
||||||
|
* and supporting NaN and +/-Infinity.
|
||||||
|
*
|
||||||
|
* @param a a value.
|
||||||
|
* @return the largest (closest to positive infinity) integer value
|
||||||
|
* that less than or equal to the argument and is equal to a mathematical
|
||||||
|
* integer.
|
||||||
|
*/
|
||||||
|
public static int ceil_int(final float a) {
|
||||||
|
final int intpart = (int) a;
|
||||||
|
|
||||||
|
if (a <= intpart
|
||||||
|
|| (CHECK_OVERFLOW && intpart == Integer.MAX_VALUE)
|
||||||
|
|| CHECK_NAN && Float.isNaN(a)) {
|
||||||
|
return intpart;
|
||||||
|
}
|
||||||
|
return intpart + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Faster alternative to floor(float) optimized for the integer domain
|
||||||
|
* and supporting NaN and +/-Infinity.
|
||||||
|
*
|
||||||
|
* @param a a value.
|
||||||
|
* @return the largest (closest to positive infinity) floating-point value
|
||||||
|
* that less than or equal to the argument and is equal to a mathematical
|
||||||
|
* integer.
|
||||||
|
*/
|
||||||
|
public static int floor_int(final float a) {
|
||||||
|
final int intpart = (int) a;
|
||||||
|
|
||||||
|
if (a >= intpart
|
||||||
|
|| (CHECK_OVERFLOW && intpart == Integer.MIN_VALUE)
|
||||||
|
|| CHECK_NAN && Float.isNaN(a)) {
|
||||||
|
return intpart;
|
||||||
|
}
|
||||||
|
return intpart - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a floating-point power of two in the normal range.
|
||||||
|
*/
|
||||||
|
static double powerOfTwoD(int n) {
|
||||||
|
assert (n >= DoubleConsts.MIN_EXPONENT && n <= DoubleConsts.MAX_EXPONENT);
|
||||||
|
return Double.longBitsToDouble((((long) n + (long) DoubleConsts.EXP_BIAS)
|
||||||
|
<< (DoubleConsts.SIGNIFICAND_WIDTH - 1))
|
||||||
|
& DoubleConsts.EXP_BIT_MASK);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,441 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sun.java2d.marlin;
|
||||||
|
|
||||||
|
import static java.lang.Math.PI;
|
||||||
|
import static java.lang.Math.cos;
|
||||||
|
import static java.lang.Math.sqrt;
|
||||||
|
import static java.lang.Math.cbrt;
|
||||||
|
import static java.lang.Math.acos;
|
||||||
|
|
||||||
|
final class Helpers implements MarlinConst {
|
||||||
|
|
||||||
|
private Helpers() {
|
||||||
|
throw new Error("This is a non instantiable class");
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean within(final float x, final float y, final float err) {
|
||||||
|
final float d = y - x;
|
||||||
|
return (d <= err && d >= -err);
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean within(final double x, final double y, final double err) {
|
||||||
|
final double d = y - x;
|
||||||
|
return (d <= err && d >= -err);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int quadraticRoots(final float a, final float b,
|
||||||
|
final float c, float[] zeroes, final int off)
|
||||||
|
{
|
||||||
|
int ret = off;
|
||||||
|
float t;
|
||||||
|
if (a != 0f) {
|
||||||
|
final float dis = b*b - 4*a*c;
|
||||||
|
if (dis > 0f) {
|
||||||
|
final float sqrtDis = (float)Math.sqrt(dis);
|
||||||
|
// depending on the sign of b we use a slightly different
|
||||||
|
// algorithm than the traditional one to find one of the roots
|
||||||
|
// so we can avoid adding numbers of different signs (which
|
||||||
|
// might result in loss of precision).
|
||||||
|
if (b >= 0f) {
|
||||||
|
zeroes[ret++] = (2f * c) / (-b - sqrtDis);
|
||||||
|
zeroes[ret++] = (-b - sqrtDis) / (2f * a);
|
||||||
|
} else {
|
||||||
|
zeroes[ret++] = (-b + sqrtDis) / (2f * a);
|
||||||
|
zeroes[ret++] = (2f * c) / (-b + sqrtDis);
|
||||||
|
}
|
||||||
|
} else if (dis == 0f) {
|
||||||
|
t = (-b) / (2f * a);
|
||||||
|
zeroes[ret++] = t;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (b != 0f) {
|
||||||
|
t = (-c) / b;
|
||||||
|
zeroes[ret++] = t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret - off;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find the roots of g(t) = d*t^3 + a*t^2 + b*t + c in [A,B)
|
||||||
|
static int cubicRootsInAB(float d, float a, float b, float c,
|
||||||
|
float[] pts, final int off,
|
||||||
|
final float A, final float B)
|
||||||
|
{
|
||||||
|
if (d == 0f) {
|
||||||
|
int num = quadraticRoots(a, b, c, pts, off);
|
||||||
|
return filterOutNotInAB(pts, off, num, A, B) - off;
|
||||||
|
}
|
||||||
|
// From Graphics Gems:
|
||||||
|
// http://tog.acm.org/resources/GraphicsGems/gems/Roots3And4.c
|
||||||
|
// (also from awt.geom.CubicCurve2D. But here we don't need as
|
||||||
|
// much accuracy and we don't want to create arrays so we use
|
||||||
|
// our own customized version).
|
||||||
|
|
||||||
|
// normal form: x^3 + ax^2 + bx + c = 0
|
||||||
|
a /= d;
|
||||||
|
b /= d;
|
||||||
|
c /= d;
|
||||||
|
|
||||||
|
// substitute x = y - A/3 to eliminate quadratic term:
|
||||||
|
// x^3 +Px + Q = 0
|
||||||
|
//
|
||||||
|
// Since we actually need P/3 and Q/2 for all of the
|
||||||
|
// calculations that follow, we will calculate
|
||||||
|
// p = P/3
|
||||||
|
// q = Q/2
|
||||||
|
// instead and use those values for simplicity of the code.
|
||||||
|
double sq_A = a * a;
|
||||||
|
double p = (1.0/3.0) * ((-1.0/3.0) * sq_A + b);
|
||||||
|
double q = (1.0/2.0) * ((2.0/27.0) * a * sq_A - (1.0/3.0) * a * b + c);
|
||||||
|
|
||||||
|
// use Cardano's formula
|
||||||
|
|
||||||
|
double cb_p = p * p * p;
|
||||||
|
double D = q * q + cb_p;
|
||||||
|
|
||||||
|
int num;
|
||||||
|
if (D < 0.0) {
|
||||||
|
// see: http://en.wikipedia.org/wiki/Cubic_function#Trigonometric_.28and_hyperbolic.29_method
|
||||||
|
final double phi = (1.0/3.0) * acos(-q / sqrt(-cb_p));
|
||||||
|
final double t = 2.0 * sqrt(-p);
|
||||||
|
|
||||||
|
pts[ off+0 ] = (float)( t * cos(phi));
|
||||||
|
pts[ off+1 ] = (float)(-t * cos(phi + (PI / 3.0)));
|
||||||
|
pts[ off+2 ] = (float)(-t * cos(phi - (PI / 3.0)));
|
||||||
|
num = 3;
|
||||||
|
} else {
|
||||||
|
final double sqrt_D = sqrt(D);
|
||||||
|
final double u = cbrt(sqrt_D - q);
|
||||||
|
final double v = - cbrt(sqrt_D + q);
|
||||||
|
|
||||||
|
pts[ off ] = (float)(u + v);
|
||||||
|
num = 1;
|
||||||
|
|
||||||
|
if (within(D, 0.0, 1e-8)) {
|
||||||
|
pts[off+1] = -(pts[off] / 2f);
|
||||||
|
num = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final float sub = (1f/3f) * a;
|
||||||
|
|
||||||
|
for (int i = 0; i < num; ++i) {
|
||||||
|
pts[ off+i ] -= sub;
|
||||||
|
}
|
||||||
|
|
||||||
|
return filterOutNotInAB(pts, off, num, A, B) - off;
|
||||||
|
}
|
||||||
|
|
||||||
|
static float evalCubic(final float a, final float b,
|
||||||
|
final float c, final float d,
|
||||||
|
final float t)
|
||||||
|
{
|
||||||
|
return t * (t * (t * a + b) + c) + d;
|
||||||
|
}
|
||||||
|
|
||||||
|
static float evalQuad(final float a, final float b,
|
||||||
|
final float c, final float t)
|
||||||
|
{
|
||||||
|
return t * (t * a + b) + c;
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the index 1 past the last valid element remaining after filtering
|
||||||
|
static int filterOutNotInAB(float[] nums, final int off, final int len,
|
||||||
|
final float a, final float b)
|
||||||
|
{
|
||||||
|
int ret = off;
|
||||||
|
for (int i = off, end = off + len; i < end; i++) {
|
||||||
|
if (nums[i] >= a && nums[i] < b) {
|
||||||
|
nums[ret++] = nums[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static float polyLineLength(float[] poly, final int off, final int nCoords) {
|
||||||
|
assert nCoords % 2 == 0 && poly.length >= off + nCoords : "";
|
||||||
|
float acc = 0;
|
||||||
|
for (int i = off + 2; i < off + nCoords; i += 2) {
|
||||||
|
acc += linelen(poly[i], poly[i+1], poly[i-2], poly[i-1]);
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static float linelen(float x1, float y1, float x2, float y2) {
|
||||||
|
final float dx = x2 - x1;
|
||||||
|
final float dy = y2 - y1;
|
||||||
|
return (float)Math.sqrt(dx*dx + dy*dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void subdivide(float[] src, int srcoff, float[] left, int leftoff,
|
||||||
|
float[] right, int rightoff, int type)
|
||||||
|
{
|
||||||
|
switch(type) {
|
||||||
|
case 6:
|
||||||
|
Helpers.subdivideQuad(src, srcoff, left, leftoff, right, rightoff);
|
||||||
|
return;
|
||||||
|
case 8:
|
||||||
|
Helpers.subdivideCubic(src, srcoff, left, leftoff, right, rightoff);
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
throw new InternalError("Unsupported curve type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void isort(float[] a, int off, int len) {
|
||||||
|
for (int i = off + 1, end = off + len; i < end; i++) {
|
||||||
|
float ai = a[i];
|
||||||
|
int j = i - 1;
|
||||||
|
for (; j >= off && a[j] > ai; j--) {
|
||||||
|
a[j+1] = a[j];
|
||||||
|
}
|
||||||
|
a[j+1] = ai;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Most of these are copied from classes in java.awt.geom because we need
|
||||||
|
// float versions of these functions, and Line2D, CubicCurve2D,
|
||||||
|
// QuadCurve2D don't provide them.
|
||||||
|
/**
|
||||||
|
* Subdivides the cubic curve specified by the coordinates
|
||||||
|
* stored in the <code>src</code> array at indices <code>srcoff</code>
|
||||||
|
* through (<code>srcoff</code> + 7) and stores the
|
||||||
|
* resulting two subdivided curves into the two result arrays at the
|
||||||
|
* corresponding indices.
|
||||||
|
* Either or both of the <code>left</code> and <code>right</code>
|
||||||
|
* arrays may be <code>null</code> or a reference to the same array
|
||||||
|
* as the <code>src</code> array.
|
||||||
|
* Note that the last point in the first subdivided curve is the
|
||||||
|
* same as the first point in the second subdivided curve. Thus,
|
||||||
|
* it is possible to pass the same array for <code>left</code>
|
||||||
|
* and <code>right</code> and to use offsets, such as <code>rightoff</code>
|
||||||
|
* equals (<code>leftoff</code> + 6), in order
|
||||||
|
* to avoid allocating extra storage for this common point.
|
||||||
|
* @param src the array holding the coordinates for the source curve
|
||||||
|
* @param srcoff the offset into the array of the beginning of the
|
||||||
|
* the 6 source coordinates
|
||||||
|
* @param left the array for storing the coordinates for the first
|
||||||
|
* half of the subdivided curve
|
||||||
|
* @param leftoff the offset into the array of the beginning of the
|
||||||
|
* the 6 left coordinates
|
||||||
|
* @param right the array for storing the coordinates for the second
|
||||||
|
* half of the subdivided curve
|
||||||
|
* @param rightoff the offset into the array of the beginning of the
|
||||||
|
* the 6 right coordinates
|
||||||
|
* @since 1.7
|
||||||
|
*/
|
||||||
|
static void subdivideCubic(float src[], int srcoff,
|
||||||
|
float left[], int leftoff,
|
||||||
|
float right[], int rightoff)
|
||||||
|
{
|
||||||
|
float x1 = src[srcoff + 0];
|
||||||
|
float y1 = src[srcoff + 1];
|
||||||
|
float ctrlx1 = src[srcoff + 2];
|
||||||
|
float ctrly1 = src[srcoff + 3];
|
||||||
|
float ctrlx2 = src[srcoff + 4];
|
||||||
|
float ctrly2 = src[srcoff + 5];
|
||||||
|
float x2 = src[srcoff + 6];
|
||||||
|
float y2 = src[srcoff + 7];
|
||||||
|
if (left != null) {
|
||||||
|
left[leftoff + 0] = x1;
|
||||||
|
left[leftoff + 1] = y1;
|
||||||
|
}
|
||||||
|
if (right != null) {
|
||||||
|
right[rightoff + 6] = x2;
|
||||||
|
right[rightoff + 7] = y2;
|
||||||
|
}
|
||||||
|
x1 = (x1 + ctrlx1) / 2f;
|
||||||
|
y1 = (y1 + ctrly1) / 2f;
|
||||||
|
x2 = (x2 + ctrlx2) / 2f;
|
||||||
|
y2 = (y2 + ctrly2) / 2f;
|
||||||
|
float centerx = (ctrlx1 + ctrlx2) / 2f;
|
||||||
|
float centery = (ctrly1 + ctrly2) / 2f;
|
||||||
|
ctrlx1 = (x1 + centerx) / 2f;
|
||||||
|
ctrly1 = (y1 + centery) / 2f;
|
||||||
|
ctrlx2 = (x2 + centerx) / 2f;
|
||||||
|
ctrly2 = (y2 + centery) / 2f;
|
||||||
|
centerx = (ctrlx1 + ctrlx2) / 2f;
|
||||||
|
centery = (ctrly1 + ctrly2) / 2f;
|
||||||
|
if (left != null) {
|
||||||
|
left[leftoff + 2] = x1;
|
||||||
|
left[leftoff + 3] = y1;
|
||||||
|
left[leftoff + 4] = ctrlx1;
|
||||||
|
left[leftoff + 5] = ctrly1;
|
||||||
|
left[leftoff + 6] = centerx;
|
||||||
|
left[leftoff + 7] = centery;
|
||||||
|
}
|
||||||
|
if (right != null) {
|
||||||
|
right[rightoff + 0] = centerx;
|
||||||
|
right[rightoff + 1] = centery;
|
||||||
|
right[rightoff + 2] = ctrlx2;
|
||||||
|
right[rightoff + 3] = ctrly2;
|
||||||
|
right[rightoff + 4] = x2;
|
||||||
|
right[rightoff + 5] = y2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void subdivideCubicAt(float t, float src[], int srcoff,
|
||||||
|
float left[], int leftoff,
|
||||||
|
float right[], int rightoff)
|
||||||
|
{
|
||||||
|
float x1 = src[srcoff + 0];
|
||||||
|
float y1 = src[srcoff + 1];
|
||||||
|
float ctrlx1 = src[srcoff + 2];
|
||||||
|
float ctrly1 = src[srcoff + 3];
|
||||||
|
float ctrlx2 = src[srcoff + 4];
|
||||||
|
float ctrly2 = src[srcoff + 5];
|
||||||
|
float x2 = src[srcoff + 6];
|
||||||
|
float y2 = src[srcoff + 7];
|
||||||
|
if (left != null) {
|
||||||
|
left[leftoff + 0] = x1;
|
||||||
|
left[leftoff + 1] = y1;
|
||||||
|
}
|
||||||
|
if (right != null) {
|
||||||
|
right[rightoff + 6] = x2;
|
||||||
|
right[rightoff + 7] = y2;
|
||||||
|
}
|
||||||
|
x1 = x1 + t * (ctrlx1 - x1);
|
||||||
|
y1 = y1 + t * (ctrly1 - y1);
|
||||||
|
x2 = ctrlx2 + t * (x2 - ctrlx2);
|
||||||
|
y2 = ctrly2 + t * (y2 - ctrly2);
|
||||||
|
float centerx = ctrlx1 + t * (ctrlx2 - ctrlx1);
|
||||||
|
float centery = ctrly1 + t * (ctrly2 - ctrly1);
|
||||||
|
ctrlx1 = x1 + t * (centerx - x1);
|
||||||
|
ctrly1 = y1 + t * (centery - y1);
|
||||||
|
ctrlx2 = centerx + t * (x2 - centerx);
|
||||||
|
ctrly2 = centery + t * (y2 - centery);
|
||||||
|
centerx = ctrlx1 + t * (ctrlx2 - ctrlx1);
|
||||||
|
centery = ctrly1 + t * (ctrly2 - ctrly1);
|
||||||
|
if (left != null) {
|
||||||
|
left[leftoff + 2] = x1;
|
||||||
|
left[leftoff + 3] = y1;
|
||||||
|
left[leftoff + 4] = ctrlx1;
|
||||||
|
left[leftoff + 5] = ctrly1;
|
||||||
|
left[leftoff + 6] = centerx;
|
||||||
|
left[leftoff + 7] = centery;
|
||||||
|
}
|
||||||
|
if (right != null) {
|
||||||
|
right[rightoff + 0] = centerx;
|
||||||
|
right[rightoff + 1] = centery;
|
||||||
|
right[rightoff + 2] = ctrlx2;
|
||||||
|
right[rightoff + 3] = ctrly2;
|
||||||
|
right[rightoff + 4] = x2;
|
||||||
|
right[rightoff + 5] = y2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void subdivideQuad(float src[], int srcoff,
|
||||||
|
float left[], int leftoff,
|
||||||
|
float right[], int rightoff)
|
||||||
|
{
|
||||||
|
float x1 = src[srcoff + 0];
|
||||||
|
float y1 = src[srcoff + 1];
|
||||||
|
float ctrlx = src[srcoff + 2];
|
||||||
|
float ctrly = src[srcoff + 3];
|
||||||
|
float x2 = src[srcoff + 4];
|
||||||
|
float y2 = src[srcoff + 5];
|
||||||
|
if (left != null) {
|
||||||
|
left[leftoff + 0] = x1;
|
||||||
|
left[leftoff + 1] = y1;
|
||||||
|
}
|
||||||
|
if (right != null) {
|
||||||
|
right[rightoff + 4] = x2;
|
||||||
|
right[rightoff + 5] = y2;
|
||||||
|
}
|
||||||
|
x1 = (x1 + ctrlx) / 2f;
|
||||||
|
y1 = (y1 + ctrly) / 2f;
|
||||||
|
x2 = (x2 + ctrlx) / 2f;
|
||||||
|
y2 = (y2 + ctrly) / 2f;
|
||||||
|
ctrlx = (x1 + x2) / 2f;
|
||||||
|
ctrly = (y1 + y2) / 2f;
|
||||||
|
if (left != null) {
|
||||||
|
left[leftoff + 2] = x1;
|
||||||
|
left[leftoff + 3] = y1;
|
||||||
|
left[leftoff + 4] = ctrlx;
|
||||||
|
left[leftoff + 5] = ctrly;
|
||||||
|
}
|
||||||
|
if (right != null) {
|
||||||
|
right[rightoff + 0] = ctrlx;
|
||||||
|
right[rightoff + 1] = ctrly;
|
||||||
|
right[rightoff + 2] = x2;
|
||||||
|
right[rightoff + 3] = y2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void subdivideQuadAt(float t, float src[], int srcoff,
|
||||||
|
float left[], int leftoff,
|
||||||
|
float right[], int rightoff)
|
||||||
|
{
|
||||||
|
float x1 = src[srcoff + 0];
|
||||||
|
float y1 = src[srcoff + 1];
|
||||||
|
float ctrlx = src[srcoff + 2];
|
||||||
|
float ctrly = src[srcoff + 3];
|
||||||
|
float x2 = src[srcoff + 4];
|
||||||
|
float y2 = src[srcoff + 5];
|
||||||
|
if (left != null) {
|
||||||
|
left[leftoff + 0] = x1;
|
||||||
|
left[leftoff + 1] = y1;
|
||||||
|
}
|
||||||
|
if (right != null) {
|
||||||
|
right[rightoff + 4] = x2;
|
||||||
|
right[rightoff + 5] = y2;
|
||||||
|
}
|
||||||
|
x1 = x1 + t * (ctrlx - x1);
|
||||||
|
y1 = y1 + t * (ctrly - y1);
|
||||||
|
x2 = ctrlx + t * (x2 - ctrlx);
|
||||||
|
y2 = ctrly + t * (y2 - ctrly);
|
||||||
|
ctrlx = x1 + t * (x2 - x1);
|
||||||
|
ctrly = y1 + t * (y2 - y1);
|
||||||
|
if (left != null) {
|
||||||
|
left[leftoff + 2] = x1;
|
||||||
|
left[leftoff + 3] = y1;
|
||||||
|
left[leftoff + 4] = ctrlx;
|
||||||
|
left[leftoff + 5] = ctrly;
|
||||||
|
}
|
||||||
|
if (right != null) {
|
||||||
|
right[rightoff + 0] = ctrlx;
|
||||||
|
right[rightoff + 1] = ctrly;
|
||||||
|
right[rightoff + 2] = x2;
|
||||||
|
right[rightoff + 3] = y2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void subdivideAt(float t, float src[], int srcoff,
|
||||||
|
float left[], int leftoff,
|
||||||
|
float right[], int rightoff, int size)
|
||||||
|
{
|
||||||
|
switch(size) {
|
||||||
|
case 8:
|
||||||
|
subdivideCubicAt(t, src, srcoff, left, leftoff, right, rightoff);
|
||||||
|
return;
|
||||||
|
case 6:
|
||||||
|
subdivideQuadAt(t, src, srcoff, left, leftoff, right, rightoff);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,151 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sun.java2d.marlin;
|
||||||
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import static sun.java2d.marlin.MarlinUtils.logException;
|
||||||
|
import static sun.java2d.marlin.MarlinUtils.logInfo;
|
||||||
|
|
||||||
|
final class IntArrayCache implements MarlinConst {
|
||||||
|
|
||||||
|
private final int arraySize;
|
||||||
|
private final ArrayDeque<int[]> intArrays;
|
||||||
|
// stats
|
||||||
|
private int getOp = 0;
|
||||||
|
private int createOp = 0;
|
||||||
|
private int returnOp = 0;
|
||||||
|
|
||||||
|
void dumpStats() {
|
||||||
|
if (getOp > 0) {
|
||||||
|
logInfo("IntArrayCache[" + arraySize + "]: get: " + getOp
|
||||||
|
+ " created: " + createOp + " - returned: " + returnOp
|
||||||
|
+ " :: cache size: " + intArrays.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IntArrayCache(final int arraySize) {
|
||||||
|
this.arraySize = arraySize;
|
||||||
|
// small but enough: almost 1 cache line
|
||||||
|
this.intArrays = new ArrayDeque<int[]>(6);
|
||||||
|
}
|
||||||
|
|
||||||
|
int[] getArray() {
|
||||||
|
if (doStats) {
|
||||||
|
getOp++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// use cache:
|
||||||
|
final int[] array = intArrays.pollLast();
|
||||||
|
if (array != null) {
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doStats) {
|
||||||
|
createOp++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new int[arraySize];
|
||||||
|
}
|
||||||
|
|
||||||
|
void putDirtyArray(final int[] array, final int length) {
|
||||||
|
if (length != arraySize) {
|
||||||
|
if (doChecks) {
|
||||||
|
System.out.println("ArrayCache: bad length = " + length);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (doStats) {
|
||||||
|
returnOp++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NO clean-up of array data = DIRTY ARRAY
|
||||||
|
|
||||||
|
if (doCleanDirty) {
|
||||||
|
// Force zero-fill dirty arrays:
|
||||||
|
Arrays.fill(array, 0, array.length, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// fill cache:
|
||||||
|
intArrays.addLast(array);
|
||||||
|
}
|
||||||
|
|
||||||
|
void putArray(final int[] array, final int length,
|
||||||
|
final int fromIndex, final int toIndex)
|
||||||
|
{
|
||||||
|
if (length != arraySize) {
|
||||||
|
if (doChecks) {
|
||||||
|
System.out.println("ArrayCache: bad length = " + length);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (doStats) {
|
||||||
|
returnOp++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// clean-up array of dirty part[fromIndex; toIndex[
|
||||||
|
fill(array, fromIndex, toIndex, 0);
|
||||||
|
|
||||||
|
// fill cache:
|
||||||
|
intArrays.addLast(array);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fill(final int[] array, final int fromIndex,
|
||||||
|
final int toIndex, final int value)
|
||||||
|
{
|
||||||
|
// clear array data:
|
||||||
|
/*
|
||||||
|
* Arrays.fill is faster than System.arraycopy(empty array)
|
||||||
|
* or Unsafe.setMemory(byte 0)
|
||||||
|
*/
|
||||||
|
if (toIndex != 0) {
|
||||||
|
Arrays.fill(array, fromIndex, toIndex, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doChecks) {
|
||||||
|
check(array, 0, array.length, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void check(final int[] array, final int fromIndex,
|
||||||
|
final int toIndex, final int value)
|
||||||
|
{
|
||||||
|
if (doChecks) {
|
||||||
|
// check zero on full array:
|
||||||
|
for (int i = fromIndex; i < toIndex; i++) {
|
||||||
|
if (array[i] != value) {
|
||||||
|
logException("Invalid array value at " + i + "\n"
|
||||||
|
+ Arrays.toString(array), new Throwable());
|
||||||
|
|
||||||
|
// ensure array is correctly filled:
|
||||||
|
Arrays.fill(array, value);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,676 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sun.java2d.marlin;
|
||||||
|
|
||||||
|
import jdk.internal.misc.Unsafe;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object used to cache pre-rendered complex paths.
|
||||||
|
*
|
||||||
|
* @see Renderer
|
||||||
|
*/
|
||||||
|
public final class MarlinCache implements MarlinConst {
|
||||||
|
|
||||||
|
static final boolean FORCE_RLE = MarlinProperties.isForceRLE();
|
||||||
|
static final boolean FORCE_NO_RLE = MarlinProperties.isForceNoRLE();
|
||||||
|
// minimum width to try using RLE encoding:
|
||||||
|
static final int RLE_MIN_WIDTH
|
||||||
|
= Math.max(BLOCK_SIZE, MarlinProperties.getRLEMinWidth());
|
||||||
|
// maximum width for RLE encoding:
|
||||||
|
// values are stored as int [x|alpha] where alpha is 8 bits
|
||||||
|
static final int RLE_MAX_WIDTH = 1 << (24 - 1);
|
||||||
|
|
||||||
|
// 2048 (pixelSize) alpha values (width) x 32 rows (tile) = 64K bytes
|
||||||
|
// x1 instead of 4 bytes (RLE) ie 1/4 capacity or average good RLE compression
|
||||||
|
static final long INITIAL_CHUNK_ARRAY = TILE_SIZE * INITIAL_PIXEL_DIM; // 64K
|
||||||
|
|
||||||
|
// The alpha map used by this object (taken out of our map cache) to convert
|
||||||
|
// pixel coverage counts gotten from MarlinCache (which are in the range
|
||||||
|
// [0, maxalpha]) into alpha values, which are in [0,256).
|
||||||
|
static final byte[] ALPHA_MAP;
|
||||||
|
|
||||||
|
static final OffHeapArray ALPHA_MAP_UNSAFE;
|
||||||
|
|
||||||
|
static {
|
||||||
|
final byte[] _ALPHA_MAP = buildAlphaMap(MAX_AA_ALPHA);
|
||||||
|
|
||||||
|
ALPHA_MAP_UNSAFE = new OffHeapArray(_ALPHA_MAP, _ALPHA_MAP.length); // 1K
|
||||||
|
ALPHA_MAP =_ALPHA_MAP;
|
||||||
|
|
||||||
|
final Unsafe _unsafe = OffHeapArray.unsafe;
|
||||||
|
final long addr = ALPHA_MAP_UNSAFE.address;
|
||||||
|
|
||||||
|
for (int i = 0; i < _ALPHA_MAP.length; i++) {
|
||||||
|
_unsafe.putByte(addr + i, _ALPHA_MAP[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int bboxX0, bboxY0, bboxX1, bboxY1;
|
||||||
|
|
||||||
|
// 1D dirty arrays
|
||||||
|
// row index in rowAAChunk[]
|
||||||
|
final long[] rowAAChunkIndex = new long[TILE_SIZE];
|
||||||
|
// first pixel (inclusive) for each row
|
||||||
|
final int[] rowAAx0 = new int[TILE_SIZE];
|
||||||
|
// last pixel (exclusive) for each row
|
||||||
|
final int[] rowAAx1 = new int[TILE_SIZE];
|
||||||
|
// encoding mode (0=raw, 1=RLE encoding) for each row
|
||||||
|
final int[] rowAAEnc = new int[TILE_SIZE];
|
||||||
|
// coded length (RLE encoding) for each row
|
||||||
|
final long[] rowAALen = new long[TILE_SIZE];
|
||||||
|
// last position in RLE decoding for each row (getAlpha):
|
||||||
|
final long[] rowAAPos = new long[TILE_SIZE];
|
||||||
|
|
||||||
|
// dirty off-heap array containing pixel coverages for (32) rows (packed)
|
||||||
|
// if encoding=raw, it contains alpha coverage values (val) as integer
|
||||||
|
// if encoding=RLE, it contains tuples (val, last x-coordinate exclusive)
|
||||||
|
// use rowAAx0/rowAAx1 to get row indices within this chunk
|
||||||
|
final OffHeapArray rowAAChunk;
|
||||||
|
|
||||||
|
// current position in rowAAChunk array
|
||||||
|
long rowAAChunkPos;
|
||||||
|
|
||||||
|
// touchedTile[i] is the sum of all the alphas in the tile with
|
||||||
|
// x=j*TILE_SIZE+bboxX0.
|
||||||
|
int[] touchedTile;
|
||||||
|
|
||||||
|
// per-thread renderer context
|
||||||
|
final RendererContext rdrCtx;
|
||||||
|
|
||||||
|
// large cached touchedTile (dirty)
|
||||||
|
final int[] touchedTile_initial = new int[INITIAL_ARRAY]; // 1 tile line
|
||||||
|
|
||||||
|
int tileMin, tileMax;
|
||||||
|
|
||||||
|
boolean useRLE = false;
|
||||||
|
|
||||||
|
MarlinCache(final RendererContext rdrCtx) {
|
||||||
|
this.rdrCtx = rdrCtx;
|
||||||
|
|
||||||
|
rowAAChunk = new OffHeapArray(rdrCtx, INITIAL_CHUNK_ARRAY);
|
||||||
|
|
||||||
|
touchedTile = touchedTile_initial;
|
||||||
|
|
||||||
|
// tile used marks:
|
||||||
|
tileMin = Integer.MAX_VALUE;
|
||||||
|
tileMax = Integer.MIN_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void init(int minx, int miny, int maxx, int maxy, int edgeSumDeltaY)
|
||||||
|
{
|
||||||
|
// assert maxy >= miny && maxx >= minx;
|
||||||
|
bboxX0 = minx;
|
||||||
|
bboxY0 = miny;
|
||||||
|
bboxX1 = maxx;
|
||||||
|
bboxY1 = maxy;
|
||||||
|
|
||||||
|
final int width = (maxx - minx);
|
||||||
|
|
||||||
|
if (FORCE_NO_RLE) {
|
||||||
|
useRLE = false;
|
||||||
|
} else if (FORCE_RLE) {
|
||||||
|
useRLE = true;
|
||||||
|
} else {
|
||||||
|
// heuristics: use both bbox area and complexity
|
||||||
|
// ie number of primitives:
|
||||||
|
|
||||||
|
// fast check min and max width (maxx < 23bits):
|
||||||
|
if (width <= RLE_MIN_WIDTH || width >= RLE_MAX_WIDTH) {
|
||||||
|
useRLE = false;
|
||||||
|
} else {
|
||||||
|
// perimeter approach: how fit the total length into given height:
|
||||||
|
|
||||||
|
// if stroking: meanCrossings /= 2 => divide edgeSumDeltaY by 2
|
||||||
|
final int heightSubPixel
|
||||||
|
= (((maxy - miny) << SUBPIXEL_LG_POSITIONS_Y) << rdrCtx.stroking);
|
||||||
|
|
||||||
|
// check meanDist > block size:
|
||||||
|
// check width / (meanCrossings - 1) >= RLE_THRESHOLD
|
||||||
|
|
||||||
|
// fast case: (meanCrossingPerPixel <= 2) means 1 span only
|
||||||
|
useRLE = (edgeSumDeltaY <= (heightSubPixel << 1))
|
||||||
|
// note: already checked (meanCrossingPerPixel <= 2)
|
||||||
|
// rewritten to avoid division:
|
||||||
|
|| (width * heightSubPixel) >
|
||||||
|
((edgeSumDeltaY - heightSubPixel) << BLOCK_SIZE_LG);
|
||||||
|
// ((edgeSumDeltaY - heightSubPixel) * RLE_THRESHOLD);
|
||||||
|
// ((edgeSumDeltaY - heightSubPixel) << BLOCK_TH_LG);
|
||||||
|
|
||||||
|
if (doTrace && !useRLE) {
|
||||||
|
final float meanCrossings
|
||||||
|
= ((float) edgeSumDeltaY) / heightSubPixel;
|
||||||
|
final float meanDist = width / (meanCrossings - 1);
|
||||||
|
|
||||||
|
System.out.println("High complexity: "
|
||||||
|
+ " for bbox[width = " + width
|
||||||
|
+ " height = " + (maxy - miny)
|
||||||
|
+ "] edgeSumDeltaY = " + edgeSumDeltaY
|
||||||
|
+ " heightSubPixel = " + heightSubPixel
|
||||||
|
+ " meanCrossings = "+ meanCrossings
|
||||||
|
+ " meanDist = " + meanDist
|
||||||
|
+ " width = " + (width * heightSubPixel)
|
||||||
|
+ " <= criteria: " + ((edgeSumDeltaY - heightSubPixel) << BLOCK_SIZE_LG)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the ceiling of (maxy - miny + 1) / TILE_SIZE;
|
||||||
|
final int nxTiles = (width + TILE_SIZE) >> TILE_SIZE_LG;
|
||||||
|
|
||||||
|
if (nxTiles > INITIAL_ARRAY) {
|
||||||
|
if (doStats) {
|
||||||
|
RendererContext.stats.stat_array_marlincache_touchedTile
|
||||||
|
.add(nxTiles);
|
||||||
|
}
|
||||||
|
touchedTile = rdrCtx.getIntArray(nxTiles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disposes this cache:
|
||||||
|
* clean up before reusing this instance
|
||||||
|
*/
|
||||||
|
void dispose() {
|
||||||
|
// Reset touchedTile if needed:
|
||||||
|
resetTileLine(0);
|
||||||
|
|
||||||
|
// Return arrays:
|
||||||
|
if (touchedTile != touchedTile_initial) {
|
||||||
|
rdrCtx.putIntArray(touchedTile, 0, 0); // already zero filled
|
||||||
|
touchedTile = touchedTile_initial;
|
||||||
|
}
|
||||||
|
// At last: resize back off-heap rowAA to initial size
|
||||||
|
if (rowAAChunk.length != INITIAL_CHUNK_ARRAY) {
|
||||||
|
// note: may throw OOME:
|
||||||
|
rowAAChunk.resize(INITIAL_CHUNK_ARRAY);
|
||||||
|
}
|
||||||
|
if (doCleanDirty) {
|
||||||
|
// Force zero-fill dirty arrays:
|
||||||
|
rowAAChunk.fill(BYTE_0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void resetTileLine(final int pminY) {
|
||||||
|
// update bboxY0 to process a complete tile line [0 - 32]
|
||||||
|
bboxY0 = pminY;
|
||||||
|
|
||||||
|
// reset current pos
|
||||||
|
if (doStats) {
|
||||||
|
RendererContext.stats.stat_cache_rowAAChunk.add(rowAAChunkPos);
|
||||||
|
}
|
||||||
|
rowAAChunkPos = 0L;
|
||||||
|
|
||||||
|
// Reset touchedTile:
|
||||||
|
if (tileMin != Integer.MAX_VALUE) {
|
||||||
|
if (doStats) {
|
||||||
|
RendererContext.stats.stat_cache_tiles.add(tileMax - tileMin);
|
||||||
|
}
|
||||||
|
// clean only dirty touchedTile:
|
||||||
|
if (tileMax == 1) {
|
||||||
|
touchedTile[0] = 0;
|
||||||
|
} else {
|
||||||
|
IntArrayCache.fill(touchedTile, tileMin, tileMax, 0);
|
||||||
|
}
|
||||||
|
// reset tile used marks:
|
||||||
|
tileMin = Integer.MAX_VALUE;
|
||||||
|
tileMax = Integer.MIN_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doCleanDirty) {
|
||||||
|
// Force zero-fill dirty arrays:
|
||||||
|
rowAAChunk.fill(BYTE_0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearAARow(final int y) {
|
||||||
|
// process tile line [0 - 32]
|
||||||
|
final int row = y - bboxY0;
|
||||||
|
|
||||||
|
// update pixel range:
|
||||||
|
rowAAx0[row] = 0; // first pixel inclusive
|
||||||
|
rowAAx1[row] = 0; // last pixel exclusive
|
||||||
|
rowAAEnc[row] = 0; // raw encoding
|
||||||
|
|
||||||
|
// note: leave rowAAChunkIndex[row] undefined
|
||||||
|
// and rowAALen[row] & rowAAPos[row] (RLE)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy the given alpha data into the rowAA cache
|
||||||
|
* @param alphaRow alpha data to copy from
|
||||||
|
* @param y y pixel coordinate
|
||||||
|
* @param px0 first pixel inclusive x0
|
||||||
|
* @param px1 last pixel exclusive x1
|
||||||
|
*/
|
||||||
|
void copyAARowNoRLE(final int[] alphaRow, final int y,
|
||||||
|
final int px0, final int px1)
|
||||||
|
{
|
||||||
|
if (doMonitors) {
|
||||||
|
RendererContext.stats.mon_rdr_copyAARow.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip useless pixels above boundary
|
||||||
|
final int px_bbox1 = FloatMath.min(px1, bboxX1);
|
||||||
|
|
||||||
|
if (doLogBounds) {
|
||||||
|
MarlinUtils.logInfo("row = [" + px0 + " ... " + px_bbox1
|
||||||
|
+ " (" + px1 + ") [ for y=" + y);
|
||||||
|
}
|
||||||
|
|
||||||
|
final int row = y - bboxY0;
|
||||||
|
|
||||||
|
// update pixel range:
|
||||||
|
rowAAx0[row] = px0; // first pixel inclusive
|
||||||
|
rowAAx1[row] = px_bbox1; // last pixel exclusive
|
||||||
|
rowAAEnc[row] = 0; // raw encoding
|
||||||
|
|
||||||
|
// get current position (bytes):
|
||||||
|
final long pos = rowAAChunkPos;
|
||||||
|
// update row index to current position:
|
||||||
|
rowAAChunkIndex[row] = pos;
|
||||||
|
|
||||||
|
// determine need array size (may overflow):
|
||||||
|
final long needSize = pos + (px_bbox1 - px0);
|
||||||
|
|
||||||
|
// update next position (bytes):
|
||||||
|
rowAAChunkPos = needSize;
|
||||||
|
|
||||||
|
// update row data:
|
||||||
|
final OffHeapArray _rowAAChunk = rowAAChunk;
|
||||||
|
// ensure rowAAChunk capacity:
|
||||||
|
if (_rowAAChunk.length < needSize) {
|
||||||
|
expandRowAAChunk(needSize);
|
||||||
|
}
|
||||||
|
if (doStats) {
|
||||||
|
RendererContext.stats.stat_cache_rowAA.add(px_bbox1 - px0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// rowAA contains only alpha values for range[x0; x1[
|
||||||
|
final int[] _touchedTile = touchedTile;
|
||||||
|
final int _TILE_SIZE_LG = TILE_SIZE_LG;
|
||||||
|
|
||||||
|
final int from = px0 - bboxX0; // first pixel inclusive
|
||||||
|
final int to = px_bbox1 - bboxX0; // last pixel exclusive
|
||||||
|
|
||||||
|
final Unsafe _unsafe = OffHeapArray.unsafe;
|
||||||
|
final long SIZE_BYTE = 1L;
|
||||||
|
final long addr_alpha = ALPHA_MAP_UNSAFE.address;
|
||||||
|
long addr_off = _rowAAChunk.address + pos;
|
||||||
|
|
||||||
|
// compute alpha sum into rowAA:
|
||||||
|
for (int x = from, val = 0; x < to; x++) {
|
||||||
|
// alphaRow is in [0; MAX_COVERAGE]
|
||||||
|
val += alphaRow[x]; // [from; to[
|
||||||
|
|
||||||
|
// ensure values are in [0; MAX_AA_ALPHA] range
|
||||||
|
if (DO_AA_RANGE_CHECK) {
|
||||||
|
if (val < 0) {
|
||||||
|
System.out.println("Invalid coverage = " + val);
|
||||||
|
val = 0;
|
||||||
|
}
|
||||||
|
if (val > MAX_AA_ALPHA) {
|
||||||
|
System.out.println("Invalid coverage = " + val);
|
||||||
|
val = MAX_AA_ALPHA;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// store alpha sum (as byte):
|
||||||
|
if (val == 0) {
|
||||||
|
_unsafe.putByte(addr_off, (byte)0); // [0..255]
|
||||||
|
} else {
|
||||||
|
_unsafe.putByte(addr_off, _unsafe.getByte(addr_alpha + val)); // [0..255]
|
||||||
|
|
||||||
|
// update touchedTile
|
||||||
|
_touchedTile[x >> _TILE_SIZE_LG] += val;
|
||||||
|
}
|
||||||
|
addr_off += SIZE_BYTE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update tile used marks:
|
||||||
|
int tx = from >> _TILE_SIZE_LG; // inclusive
|
||||||
|
if (tx < tileMin) {
|
||||||
|
tileMin = tx;
|
||||||
|
}
|
||||||
|
|
||||||
|
tx = ((to - 1) >> _TILE_SIZE_LG) + 1; // exclusive (+1 to be sure)
|
||||||
|
if (tx > tileMax) {
|
||||||
|
tileMax = tx;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doLogBounds) {
|
||||||
|
MarlinUtils.logInfo("clear = [" + from + " ... " + to + "[");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear alpha row for reuse:
|
||||||
|
IntArrayCache.fill(alphaRow, from, px1 - bboxX0, 0);
|
||||||
|
|
||||||
|
if (doMonitors) {
|
||||||
|
RendererContext.stats.mon_rdr_copyAARow.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void copyAARowRLE_WithBlockFlags(final int[] blkFlags, final int[] alphaRow,
|
||||||
|
final int y, final int px0, final int px1)
|
||||||
|
{
|
||||||
|
if (doMonitors) {
|
||||||
|
RendererContext.stats.mon_rdr_copyAARow.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy rowAA data into the piscesCache if one is present
|
||||||
|
final int _bboxX0 = bboxX0;
|
||||||
|
|
||||||
|
// process tile line [0 - 32]
|
||||||
|
final int row = y - bboxY0;
|
||||||
|
final int from = px0 - _bboxX0; // first pixel inclusive
|
||||||
|
|
||||||
|
// skip useless pixels above boundary
|
||||||
|
final int px_bbox1 = FloatMath.min(px1, bboxX1);
|
||||||
|
final int to = px_bbox1 - _bboxX0; // last pixel exclusive
|
||||||
|
|
||||||
|
if (doLogBounds) {
|
||||||
|
MarlinUtils.logInfo("row = [" + px0 + " ... " + px_bbox1
|
||||||
|
+ " (" + px1 + ") [ for y=" + y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// get current position:
|
||||||
|
final long initialPos = startRLERow(row, px0, px_bbox1);
|
||||||
|
|
||||||
|
// determine need array size:
|
||||||
|
// pessimistic: max needed size = deltaX x 4 (1 int)
|
||||||
|
final int maxLen = (to - from);
|
||||||
|
final long needSize = initialPos + (maxLen << 2);
|
||||||
|
|
||||||
|
// update row data:
|
||||||
|
OffHeapArray _rowAAChunk = rowAAChunk;
|
||||||
|
// ensure rowAAChunk capacity:
|
||||||
|
if (_rowAAChunk.length < needSize) {
|
||||||
|
expandRowAAChunk(needSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Unsafe _unsafe = OffHeapArray.unsafe;
|
||||||
|
final long SIZE_INT = 4L;
|
||||||
|
final long addr_alpha = ALPHA_MAP_UNSAFE.address;
|
||||||
|
long addr_off = _rowAAChunk.address + initialPos;
|
||||||
|
|
||||||
|
final int[] _touchedTile = touchedTile;
|
||||||
|
final int _TILE_SIZE_LG = TILE_SIZE_LG;
|
||||||
|
final int _BLK_SIZE_LG = BLOCK_SIZE_LG;
|
||||||
|
|
||||||
|
// traverse flagged blocks:
|
||||||
|
final int blkW = (from >> _BLK_SIZE_LG);
|
||||||
|
final int blkE = (to >> _BLK_SIZE_LG) + 1;
|
||||||
|
|
||||||
|
// Perform run-length encoding and store results in the piscesCache
|
||||||
|
int val = 0;
|
||||||
|
int cx0 = from;
|
||||||
|
int runLen;
|
||||||
|
|
||||||
|
final int _MAX_VALUE = Integer.MAX_VALUE;
|
||||||
|
int last_t0 = _MAX_VALUE;
|
||||||
|
|
||||||
|
int skip = 0;
|
||||||
|
|
||||||
|
for (int t = blkW, blk_x0, blk_x1, cx, delta; t <= blkE; t++) {
|
||||||
|
if (blkFlags[t] != 0) {
|
||||||
|
blkFlags[t] = 0;
|
||||||
|
|
||||||
|
if (last_t0 == _MAX_VALUE) {
|
||||||
|
last_t0 = t;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (last_t0 != _MAX_VALUE) {
|
||||||
|
// emit blocks:
|
||||||
|
blk_x0 = FloatMath.max(last_t0 << _BLK_SIZE_LG, from);
|
||||||
|
last_t0 = _MAX_VALUE;
|
||||||
|
|
||||||
|
// (last block pixel+1) inclusive => +1
|
||||||
|
blk_x1 = FloatMath.min((t << _BLK_SIZE_LG) + 1, to);
|
||||||
|
|
||||||
|
for (cx = blk_x0; cx < blk_x1; cx++) {
|
||||||
|
if ((delta = alphaRow[cx]) != 0) {
|
||||||
|
alphaRow[cx] = 0;
|
||||||
|
|
||||||
|
// not first rle entry:
|
||||||
|
if (cx != cx0) {
|
||||||
|
runLen = cx - cx0;
|
||||||
|
|
||||||
|
// store alpha coverage (ensure within bounds):
|
||||||
|
// as [absX|val] where:
|
||||||
|
// absX is the absolute x-coordinate:
|
||||||
|
// note: last pixel exclusive (>= 0)
|
||||||
|
// note: it should check X is smaller than 23bits (overflow)!
|
||||||
|
|
||||||
|
// special case to encode entries into a single int:
|
||||||
|
if (val == 0) {
|
||||||
|
_unsafe.putInt(addr_off,
|
||||||
|
((_bboxX0 + cx) << 8)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
_unsafe.putInt(addr_off,
|
||||||
|
((_bboxX0 + cx) << 8)
|
||||||
|
| (((int) _unsafe.getByte(addr_alpha + val)) & 0xFF) // [0..255]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (runLen == 1) {
|
||||||
|
_touchedTile[cx0 >> _TILE_SIZE_LG] += val;
|
||||||
|
} else {
|
||||||
|
touchTile(cx0, val, cx, runLen, _touchedTile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addr_off += SIZE_INT;
|
||||||
|
|
||||||
|
if (doStats) {
|
||||||
|
RendererContext.stats.hist_tile_generator_encoding_runLen
|
||||||
|
.add(runLen);
|
||||||
|
}
|
||||||
|
cx0 = cx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// alpha value = running sum of coverage delta:
|
||||||
|
val += delta;
|
||||||
|
|
||||||
|
// ensure values are in [0; MAX_AA_ALPHA] range
|
||||||
|
if (DO_AA_RANGE_CHECK) {
|
||||||
|
if (val < 0) {
|
||||||
|
System.out.println("Invalid coverage = " + val);
|
||||||
|
val = 0;
|
||||||
|
}
|
||||||
|
if (val > MAX_AA_ALPHA) {
|
||||||
|
System.out.println("Invalid coverage = " + val);
|
||||||
|
val = MAX_AA_ALPHA;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (doStats) {
|
||||||
|
skip++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process remaining RLE run:
|
||||||
|
runLen = to - cx0;
|
||||||
|
|
||||||
|
// store alpha coverage (ensure within bounds):
|
||||||
|
// as (int)[absX|val] where:
|
||||||
|
// absX is the absolute x-coordinate in bits 31 to 8 and val in bits 0..7
|
||||||
|
// note: last pixel exclusive (>= 0)
|
||||||
|
// note: it should check X is smaller than 23bits (overflow)!
|
||||||
|
|
||||||
|
// special case to encode entries into a single int:
|
||||||
|
if (val == 0) {
|
||||||
|
_unsafe.putInt(addr_off,
|
||||||
|
((_bboxX0 + to) << 8)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
_unsafe.putInt(addr_off,
|
||||||
|
((_bboxX0 + to) << 8)
|
||||||
|
| (((int) _unsafe.getByte(addr_alpha + val)) & 0xFF) // [0..255]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (runLen == 1) {
|
||||||
|
_touchedTile[cx0 >> _TILE_SIZE_LG] += val;
|
||||||
|
} else {
|
||||||
|
touchTile(cx0, val, to, runLen, _touchedTile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addr_off += SIZE_INT;
|
||||||
|
|
||||||
|
if (doStats) {
|
||||||
|
RendererContext.stats.hist_tile_generator_encoding_runLen
|
||||||
|
.add(runLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
long len = (addr_off - _rowAAChunk.address);
|
||||||
|
|
||||||
|
// update coded length as bytes:
|
||||||
|
rowAALen[row] = (len - initialPos);
|
||||||
|
|
||||||
|
// update current position:
|
||||||
|
rowAAChunkPos = len;
|
||||||
|
|
||||||
|
if (doStats) {
|
||||||
|
RendererContext.stats.stat_cache_rowAA.add(rowAALen[row]);
|
||||||
|
RendererContext.stats.hist_tile_generator_encoding_ratio.add(
|
||||||
|
(100 * skip) / (blkE - blkW)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// update tile used marks:
|
||||||
|
int tx = from >> _TILE_SIZE_LG; // inclusive
|
||||||
|
if (tx < tileMin) {
|
||||||
|
tileMin = tx;
|
||||||
|
}
|
||||||
|
|
||||||
|
tx = ((to - 1) >> _TILE_SIZE_LG) + 1; // exclusive (+1 to be sure)
|
||||||
|
if (tx > tileMax) {
|
||||||
|
tileMax = tx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear alpha row for reuse:
|
||||||
|
if (px1 > bboxX1) {
|
||||||
|
alphaRow[to ] = 0;
|
||||||
|
alphaRow[to + 1] = 0;
|
||||||
|
}
|
||||||
|
if (doChecks) {
|
||||||
|
IntArrayCache.check(blkFlags, 0, blkFlags.length, 0);
|
||||||
|
IntArrayCache.check(alphaRow, 0, alphaRow.length, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doMonitors) {
|
||||||
|
RendererContext.stats.mon_rdr_copyAARow.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
long startRLERow(final int row, final int x0, final int x1) {
|
||||||
|
// rows are supposed to be added by increasing y.
|
||||||
|
rowAAx0[row] = x0; // first pixel inclusive
|
||||||
|
rowAAx1[row] = x1; // last pixel exclusive
|
||||||
|
rowAAEnc[row] = 1; // RLE encoding
|
||||||
|
rowAAPos[row] = 0L; // position = 0
|
||||||
|
|
||||||
|
// update row index to current position:
|
||||||
|
return (rowAAChunkIndex[row] = rowAAChunkPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expandRowAAChunk(final long needSize) {
|
||||||
|
if (doStats) {
|
||||||
|
RendererContext.stats.stat_array_marlincache_rowAAChunk
|
||||||
|
.add(needSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
// note: throw IOOB if neededSize > 2Gb:
|
||||||
|
final long newSize = ArrayCache.getNewLargeSize(rowAAChunk.length, needSize);
|
||||||
|
|
||||||
|
rowAAChunk.resize(newSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void touchTile(final int x0, final int val, final int x1,
|
||||||
|
final int runLen,
|
||||||
|
final int[] _touchedTile)
|
||||||
|
{
|
||||||
|
// the x and y of the current row, minus bboxX0, bboxY0
|
||||||
|
// process tile line [0 - 32]
|
||||||
|
final int _TILE_SIZE_LG = TILE_SIZE_LG;
|
||||||
|
|
||||||
|
// update touchedTile
|
||||||
|
int tx = (x0 >> _TILE_SIZE_LG);
|
||||||
|
|
||||||
|
// handle trivial case: same tile (x0, x0+runLen)
|
||||||
|
if (tx == (x1 >> _TILE_SIZE_LG)) {
|
||||||
|
// same tile:
|
||||||
|
_touchedTile[tx] += val * runLen;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int tx1 = (x1 - 1) >> _TILE_SIZE_LG;
|
||||||
|
|
||||||
|
if (tx <= tx1) {
|
||||||
|
final int nextTileXCoord = (tx + 1) << _TILE_SIZE_LG;
|
||||||
|
_touchedTile[tx++] += val * (nextTileXCoord - x0);
|
||||||
|
}
|
||||||
|
if (tx < tx1) {
|
||||||
|
// don't go all the way to tx1 - we need to handle the last
|
||||||
|
// tile as a special case (just like we did with the first
|
||||||
|
final int tileVal = (val << _TILE_SIZE_LG);
|
||||||
|
for (; tx < tx1; tx++) {
|
||||||
|
_touchedTile[tx] += tileVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// they will be equal unless x0 >> TILE_SIZE_LG == tx1
|
||||||
|
if (tx == tx1) {
|
||||||
|
final int txXCoord = tx << _TILE_SIZE_LG;
|
||||||
|
final int nextTileXCoord = (tx + 1) << _TILE_SIZE_LG;
|
||||||
|
|
||||||
|
final int lastXCoord = (nextTileXCoord <= x1) ? nextTileXCoord : x1;
|
||||||
|
_touchedTile[tx] += val * (lastXCoord - txXCoord);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int alphaSumInTile(final int x) {
|
||||||
|
return touchedTile[(x - bboxX0) >> TILE_SIZE_LG];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "bbox = ["
|
||||||
|
+ bboxX0 + ", " + bboxY0 + " => "
|
||||||
|
+ bboxX1 + ", " + bboxY1 + "]\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] buildAlphaMap(final int maxalpha) {
|
||||||
|
// double size !
|
||||||
|
final byte[] alMap = new byte[maxalpha << 1];
|
||||||
|
final int halfmaxalpha = maxalpha >> 2;
|
||||||
|
for (int i = 0; i <= maxalpha; i++) {
|
||||||
|
alMap[i] = (byte) ((i * 255 + halfmaxalpha) / maxalpha);
|
||||||
|
// System.out.println("alphaMap[" + i + "] = "
|
||||||
|
// + Byte.toUnsignedInt(alMap[i]));
|
||||||
|
}
|
||||||
|
return alMap;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sun.java2d.marlin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marlin constant holder using System properties
|
||||||
|
*/
|
||||||
|
interface MarlinConst {
|
||||||
|
// enable Logs (logger or stdout)
|
||||||
|
static final boolean enableLogs = false;
|
||||||
|
// enable Logger
|
||||||
|
static final boolean useLogger = enableLogs && MarlinProperties.isUseLogger();
|
||||||
|
|
||||||
|
// log new RendererContext
|
||||||
|
static final boolean logCreateContext = enableLogs
|
||||||
|
&& MarlinProperties.isLogCreateContext();
|
||||||
|
// log misc.Unsafe alloc/realloc/free
|
||||||
|
static final boolean logUnsafeMalloc = enableLogs
|
||||||
|
&& MarlinProperties.isLogUnsafeMalloc();
|
||||||
|
|
||||||
|
// do statistics
|
||||||
|
static final boolean doStats = enableLogs && MarlinProperties.isDoStats();
|
||||||
|
// do monitors
|
||||||
|
// disabled to reduce byte-code size a bit...
|
||||||
|
static final boolean doMonitors = enableLogs && false; // MarlinProperties.isDoMonitors();
|
||||||
|
// do checks
|
||||||
|
static final boolean doChecks = false; // MarlinProperties.isDoChecks();
|
||||||
|
|
||||||
|
// do AA range checks: disable when algorithm / code is stable
|
||||||
|
static final boolean DO_AA_RANGE_CHECK = false;
|
||||||
|
|
||||||
|
// enable logs
|
||||||
|
static final boolean doLogWidenArray = enableLogs && false;
|
||||||
|
// enable oversize logs
|
||||||
|
static final boolean doLogOverSize = enableLogs && false;
|
||||||
|
// enable traces
|
||||||
|
static final boolean doTrace = enableLogs && false;
|
||||||
|
// do flush monitors
|
||||||
|
static final boolean doFlushMonitors = true;
|
||||||
|
// use one polling thread to dump statistics/monitors
|
||||||
|
static final boolean useDumpThread = false;
|
||||||
|
// thread dump interval (ms)
|
||||||
|
static final long statDump = 5000L;
|
||||||
|
|
||||||
|
// do clean dirty array
|
||||||
|
static final boolean doCleanDirty = false;
|
||||||
|
|
||||||
|
// flag to use line simplifier
|
||||||
|
static final boolean useSimplifier = MarlinProperties.isUseSimplifier();
|
||||||
|
|
||||||
|
// flag to enable logs related bounds checks
|
||||||
|
static final boolean doLogBounds = enableLogs && false;
|
||||||
|
|
||||||
|
// Initial Array sizing (initial context capacity) ~ 512K
|
||||||
|
|
||||||
|
// 2048 pixel (width x height) for initial capacity
|
||||||
|
static final int INITIAL_PIXEL_DIM
|
||||||
|
= MarlinProperties.getInitialImageSize();
|
||||||
|
|
||||||
|
// typical array sizes: only odd numbers allowed below
|
||||||
|
static final int INITIAL_ARRAY = 256;
|
||||||
|
static final int INITIAL_SMALL_ARRAY = 1024;
|
||||||
|
static final int INITIAL_MEDIUM_ARRAY = 4096;
|
||||||
|
static final int INITIAL_LARGE_ARRAY = 8192;
|
||||||
|
static final int INITIAL_ARRAY_16K = 16384;
|
||||||
|
static final int INITIAL_ARRAY_32K = 32768;
|
||||||
|
// alpha row dimension
|
||||||
|
static final int INITIAL_AA_ARRAY = INITIAL_PIXEL_DIM;
|
||||||
|
|
||||||
|
// initial edges (24 bytes) = 24K [ints] = 96K
|
||||||
|
static final int INITIAL_EDGES_CAPACITY = 4096 * 24; // 6 ints per edges
|
||||||
|
|
||||||
|
// zero value as byte
|
||||||
|
static final byte BYTE_0 = (byte) 0;
|
||||||
|
|
||||||
|
// subpixels expressed as log2
|
||||||
|
public static final int SUBPIXEL_LG_POSITIONS_X
|
||||||
|
= MarlinProperties.getSubPixel_Log2_X();
|
||||||
|
public static final int SUBPIXEL_LG_POSITIONS_Y
|
||||||
|
= MarlinProperties.getSubPixel_Log2_Y();
|
||||||
|
|
||||||
|
// number of subpixels
|
||||||
|
public static final int SUBPIXEL_POSITIONS_X = 1 << (SUBPIXEL_LG_POSITIONS_X);
|
||||||
|
public static final int SUBPIXEL_POSITIONS_Y = 1 << (SUBPIXEL_LG_POSITIONS_Y);
|
||||||
|
|
||||||
|
public static final float NORM_SUBPIXELS
|
||||||
|
= (float)Math.sqrt(( SUBPIXEL_POSITIONS_X * SUBPIXEL_POSITIONS_X
|
||||||
|
+ SUBPIXEL_POSITIONS_Y * SUBPIXEL_POSITIONS_Y)/2.0);
|
||||||
|
|
||||||
|
public static final int MAX_AA_ALPHA
|
||||||
|
= SUBPIXEL_POSITIONS_X * SUBPIXEL_POSITIONS_Y;
|
||||||
|
|
||||||
|
public static final int TILE_SIZE_LG = MarlinProperties.getTileSize_Log2();
|
||||||
|
public static final int TILE_SIZE = 1 << TILE_SIZE_LG; // 32 by default
|
||||||
|
|
||||||
|
public static final int BLOCK_SIZE_LG = MarlinProperties.getBlockSize_Log2();
|
||||||
|
public static final int BLOCK_SIZE = 1 << BLOCK_SIZE_LG;
|
||||||
|
}
|
@ -0,0 +1,181 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sun.java2d.marlin;
|
||||||
|
|
||||||
|
import java.security.AccessController;
|
||||||
|
import static sun.java2d.marlin.MarlinUtils.logInfo;
|
||||||
|
import sun.security.action.GetPropertyAction;
|
||||||
|
|
||||||
|
public final class MarlinProperties {
|
||||||
|
|
||||||
|
private MarlinProperties() {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
|
||||||
|
// marlin system properties
|
||||||
|
|
||||||
|
public static boolean isUseThreadLocal() {
|
||||||
|
return getBoolean("sun.java2d.renderer.useThreadLocal", "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the initial pixel size used to define initial arrays
|
||||||
|
* (tile AA chunk, alpha line, buckets)
|
||||||
|
*
|
||||||
|
* @return 64 < initial pixel size < 32768 (2048 by default)
|
||||||
|
*/
|
||||||
|
public static int getInitialImageSize() {
|
||||||
|
return getInteger("sun.java2d.renderer.pixelsize", 2048, 64, 32 * 1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the log(2) corresponding to subpixel on x-axis (
|
||||||
|
*
|
||||||
|
* @return 1 (2 subpixels) < initial pixel size < 4 (256 subpixels)
|
||||||
|
* (3 by default ie 8 subpixels)
|
||||||
|
*/
|
||||||
|
public static int getSubPixel_Log2_X() {
|
||||||
|
return getInteger("sun.java2d.renderer.subPixel_log2_X", 3, 1, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the log(2) corresponding to subpixel on y-axis (
|
||||||
|
*
|
||||||
|
* @return 1 (2 subpixels) < initial pixel size < 8 (256 subpixels)
|
||||||
|
* (3 by default ie 8 subpixels)
|
||||||
|
*/
|
||||||
|
public static int getSubPixel_Log2_Y() {
|
||||||
|
return getInteger("sun.java2d.renderer.subPixel_log2_Y", 3, 1, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the log(2) corresponding to the square tile size in pixels
|
||||||
|
*
|
||||||
|
* @return 3 (8x8 pixels) < tile size < 8 (256x256 pixels)
|
||||||
|
* (5 by default ie 32x32 pixels)
|
||||||
|
*/
|
||||||
|
public static int getTileSize_Log2() {
|
||||||
|
return getInteger("sun.java2d.renderer.tileSize_log2", 5, 3, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the log(2) corresponding to the block size in pixels
|
||||||
|
*
|
||||||
|
* @return 3 (8 pixels) < block size < 8 (256 pixels)
|
||||||
|
* (5 by default ie 32 pixels)
|
||||||
|
*/
|
||||||
|
public static int getBlockSize_Log2() {
|
||||||
|
return getInteger("sun.java2d.renderer.blockSize_log2", 5, 3, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
// RLE / blockFlags settings
|
||||||
|
|
||||||
|
public static boolean isForceRLE() {
|
||||||
|
return getBoolean("sun.java2d.renderer.forceRLE", "false");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isForceNoRLE() {
|
||||||
|
return getBoolean("sun.java2d.renderer.forceNoRLE", "false");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isUseTileFlags() {
|
||||||
|
return getBoolean("sun.java2d.renderer.useTileFlags", "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isUseTileFlagsWithHeuristics() {
|
||||||
|
return isUseTileFlags()
|
||||||
|
&& getBoolean("sun.java2d.renderer.useTileFlags.useHeuristics", "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getRLEMinWidth() {
|
||||||
|
return getInteger("sun.java2d.renderer.rleMinWidth", 64, 0, Integer.MAX_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// optimisation parameters
|
||||||
|
|
||||||
|
public static boolean isUseSimplifier() {
|
||||||
|
return getBoolean("sun.java2d.renderer.useSimplifier", "false");
|
||||||
|
}
|
||||||
|
|
||||||
|
// debugging parameters
|
||||||
|
|
||||||
|
public static boolean isDoStats() {
|
||||||
|
return getBoolean("sun.java2d.renderer.doStats", "false");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isDoMonitors() {
|
||||||
|
return getBoolean("sun.java2d.renderer.doMonitors", "false");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isDoChecks() {
|
||||||
|
return getBoolean("sun.java2d.renderer.doChecks", "false");
|
||||||
|
}
|
||||||
|
|
||||||
|
// logging parameters
|
||||||
|
|
||||||
|
public static boolean isUseLogger() {
|
||||||
|
return getBoolean("sun.java2d.renderer.useLogger", "false");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isLogCreateContext() {
|
||||||
|
return getBoolean("sun.java2d.renderer.logCreateContext", "false");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isLogUnsafeMalloc() {
|
||||||
|
return getBoolean("sun.java2d.renderer.logUnsafeMalloc", "false");
|
||||||
|
}
|
||||||
|
|
||||||
|
// system property utilities
|
||||||
|
static boolean getBoolean(final String key, final String def) {
|
||||||
|
return Boolean.valueOf(AccessController.doPrivileged(
|
||||||
|
new GetPropertyAction(key, def)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int getInteger(final String key, final int def,
|
||||||
|
final int min, final int max)
|
||||||
|
{
|
||||||
|
final String property = AccessController.doPrivileged(
|
||||||
|
new GetPropertyAction(key));
|
||||||
|
|
||||||
|
int value = def;
|
||||||
|
if (property != null) {
|
||||||
|
try {
|
||||||
|
value = Integer.decode(property);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
logInfo("Invalid integer value for " + key + " = " + property);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for invalid values
|
||||||
|
if ((value < min) || (value > max)) {
|
||||||
|
logInfo("Invalid value for " + key + " = " + value
|
||||||
|
+ "; expected value in range[" + min + ", " + max + "] !");
|
||||||
|
value = def;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,465 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sun.java2d.marlin;
|
||||||
|
|
||||||
|
import sun.java2d.pipe.AATileGenerator;
|
||||||
|
import jdk.internal.misc.Unsafe;
|
||||||
|
|
||||||
|
final class MarlinTileGenerator implements AATileGenerator, MarlinConst {
|
||||||
|
|
||||||
|
private static final int MAX_TILE_ALPHA_SUM = TILE_SIZE * TILE_SIZE
|
||||||
|
* MAX_AA_ALPHA;
|
||||||
|
|
||||||
|
private final Renderer rdr;
|
||||||
|
private final MarlinCache cache;
|
||||||
|
private int x, y;
|
||||||
|
|
||||||
|
MarlinTileGenerator(Renderer r) {
|
||||||
|
this.rdr = r;
|
||||||
|
this.cache = r.cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
MarlinTileGenerator init() {
|
||||||
|
this.x = cache.bboxX0;
|
||||||
|
this.y = cache.bboxY0;
|
||||||
|
|
||||||
|
return this; // fluent API
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disposes this tile generator:
|
||||||
|
* clean up before reusing this instance
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
if (doMonitors) {
|
||||||
|
// called from AAShapePipe.renderTiles() (render tiles end):
|
||||||
|
RendererContext.stats.mon_pipe_renderTiles.stop();
|
||||||
|
}
|
||||||
|
// dispose cache:
|
||||||
|
cache.dispose();
|
||||||
|
// dispose renderer:
|
||||||
|
rdr.dispose();
|
||||||
|
// recycle the RendererContext instance
|
||||||
|
MarlinRenderingEngine.returnRendererContext(rdr.rdrCtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void getBbox(int bbox[]) {
|
||||||
|
bbox[0] = cache.bboxX0;
|
||||||
|
bbox[1] = cache.bboxY0;
|
||||||
|
bbox[2] = cache.bboxX1;
|
||||||
|
bbox[3] = cache.bboxY1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the width of the tiles that the generator batches output into.
|
||||||
|
* @return the width of the standard alpha tile
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getTileWidth() {
|
||||||
|
if (doMonitors) {
|
||||||
|
// called from AAShapePipe.renderTiles() (render tiles start):
|
||||||
|
RendererContext.stats.mon_pipe_renderTiles.start();
|
||||||
|
}
|
||||||
|
return TILE_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the height of the tiles that the generator batches output into.
|
||||||
|
* @return the height of the standard alpha tile
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getTileHeight() {
|
||||||
|
return TILE_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the typical alpha value that will characterize the current
|
||||||
|
* tile.
|
||||||
|
* The answer may be 0x00 to indicate that the current tile has
|
||||||
|
* no coverage in any of its pixels, or it may be 0xff to indicate
|
||||||
|
* that the current tile is completely covered by the path, or any
|
||||||
|
* other value to indicate non-trivial coverage cases.
|
||||||
|
* @return 0x00 for no coverage, 0xff for total coverage, or any other
|
||||||
|
* value for partial coverage of the tile
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getTypicalAlpha() {
|
||||||
|
int al = cache.alphaSumInTile(x);
|
||||||
|
// Note: if we have a filled rectangle that doesn't end on a tile
|
||||||
|
// border, we could still return 0xff, even though al!=maxTileAlphaSum
|
||||||
|
// This is because if we return 0xff, our users will fill a rectangle
|
||||||
|
// starting at x,y that has width = Math.min(TILE_SIZE, bboxX1-x),
|
||||||
|
// and height min(TILE_SIZE,bboxY1-y), which is what should happen.
|
||||||
|
// However, to support this, we would have to use 2 Math.min's
|
||||||
|
// and 2 multiplications per tile, instead of just 2 multiplications
|
||||||
|
// to compute maxTileAlphaSum. The savings offered would probably
|
||||||
|
// not be worth it, considering how rare this case is.
|
||||||
|
// Note: I have not tested this, so in the future if it is determined
|
||||||
|
// that it is worth it, it should be implemented. Perhaps this method's
|
||||||
|
// interface should be changed to take arguments the width and height
|
||||||
|
// of the current tile. This would eliminate the 2 Math.min calls that
|
||||||
|
// would be needed here, since our caller needs to compute these 2
|
||||||
|
// values anyway.
|
||||||
|
final int alpha = (al == 0x00 ? 0x00
|
||||||
|
: (al == MAX_TILE_ALPHA_SUM ? 0xff : 0x80));
|
||||||
|
if (doStats) {
|
||||||
|
RendererContext.stats.hist_tile_generator_alpha.add(alpha);
|
||||||
|
}
|
||||||
|
return alpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skips the current tile and moves on to the next tile.
|
||||||
|
* Either this method, or the getAlpha() method should be called
|
||||||
|
* once per tile, but not both.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void nextTile() {
|
||||||
|
if ((x += TILE_SIZE) >= cache.bboxX1) {
|
||||||
|
x = cache.bboxX0;
|
||||||
|
y += TILE_SIZE;
|
||||||
|
|
||||||
|
if (y < cache.bboxY1) {
|
||||||
|
// compute for the tile line
|
||||||
|
// [ y; max(y + TILE_SIZE, bboxY1) ]
|
||||||
|
this.rdr.endRendering(y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the alpha coverage values for the current tile.
|
||||||
|
* Either this method, or the nextTile() method should be called
|
||||||
|
* once per tile, but not both.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void getAlpha(final byte tile[], final int offset,
|
||||||
|
final int rowstride)
|
||||||
|
{
|
||||||
|
if (cache.useRLE) {
|
||||||
|
getAlphaRLE(tile, offset, rowstride);
|
||||||
|
} else {
|
||||||
|
getAlphaNoRLE(tile, offset, rowstride);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the alpha coverage values for the current tile.
|
||||||
|
* Either this method, or the nextTile() method should be called
|
||||||
|
* once per tile, but not both.
|
||||||
|
*/
|
||||||
|
private void getAlphaNoRLE(final byte tile[], final int offset,
|
||||||
|
final int rowstride)
|
||||||
|
{
|
||||||
|
if (doMonitors) {
|
||||||
|
RendererContext.stats.mon_ptg_getAlpha.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
// local vars for performance:
|
||||||
|
final MarlinCache _cache = this.cache;
|
||||||
|
final long[] rowAAChunkIndex = _cache.rowAAChunkIndex;
|
||||||
|
final int[] rowAAx0 = _cache.rowAAx0;
|
||||||
|
final int[] rowAAx1 = _cache.rowAAx1;
|
||||||
|
|
||||||
|
final int x0 = this.x;
|
||||||
|
final int x1 = FloatMath.min(x0 + TILE_SIZE, _cache.bboxX1);
|
||||||
|
|
||||||
|
// note: process tile line [0 - 32[
|
||||||
|
final int y0 = 0;
|
||||||
|
final int y1 = FloatMath.min(this.y + TILE_SIZE, _cache.bboxY1) - this.y;
|
||||||
|
|
||||||
|
if (doLogBounds) {
|
||||||
|
MarlinUtils.logInfo("getAlpha = [" + x0 + " ... " + x1
|
||||||
|
+ "[ [" + y0 + " ... " + y1 + "[");
|
||||||
|
}
|
||||||
|
|
||||||
|
final Unsafe _unsafe = OffHeapArray.unsafe;
|
||||||
|
final long SIZE = 1L;
|
||||||
|
final long addr_rowAA = _cache.rowAAChunk.address;
|
||||||
|
long addr;
|
||||||
|
|
||||||
|
final int skipRowPixels = (rowstride - (x1 - x0));
|
||||||
|
|
||||||
|
int aax0, aax1, end;
|
||||||
|
int idx = offset;
|
||||||
|
|
||||||
|
for (int cy = y0, cx; cy < y1; cy++) {
|
||||||
|
// empty line (default)
|
||||||
|
cx = x0;
|
||||||
|
|
||||||
|
aax1 = rowAAx1[cy]; // exclusive
|
||||||
|
|
||||||
|
// quick check if there is AA data
|
||||||
|
// corresponding to this tile [x0; x1[
|
||||||
|
if (aax1 > x0) {
|
||||||
|
aax0 = rowAAx0[cy]; // inclusive
|
||||||
|
|
||||||
|
if (aax0 < x1) {
|
||||||
|
// note: cx is the cursor pointer in the tile array
|
||||||
|
// (left to right)
|
||||||
|
cx = aax0;
|
||||||
|
|
||||||
|
// ensure cx >= x0
|
||||||
|
if (cx <= x0) {
|
||||||
|
cx = x0;
|
||||||
|
} else {
|
||||||
|
// fill line start until first AA pixel rowAA exclusive:
|
||||||
|
for (end = x0; end < cx; end++) {
|
||||||
|
tile[idx++] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// now: cx >= x0 but cx < aax0 (x1 < aax0)
|
||||||
|
|
||||||
|
// Copy AA data (sum alpha data):
|
||||||
|
addr = addr_rowAA + rowAAChunkIndex[cy] + (cx - aax0);
|
||||||
|
|
||||||
|
for (end = (aax1 <= x1) ? aax1 : x1; cx < end; cx++) {
|
||||||
|
// cx inside tile[x0; x1[ :
|
||||||
|
tile[idx++] = _unsafe.getByte(addr); // [0..255]
|
||||||
|
addr += SIZE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fill line end
|
||||||
|
while (cx < x1) {
|
||||||
|
tile[idx++] = 0;
|
||||||
|
cx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doTrace) {
|
||||||
|
for (int i = idx - (x1 - x0); i < idx; i++) {
|
||||||
|
System.out.print(hex(tile[i], 2));
|
||||||
|
}
|
||||||
|
System.out.println();
|
||||||
|
}
|
||||||
|
|
||||||
|
idx += skipRowPixels;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextTile();
|
||||||
|
|
||||||
|
if (doMonitors) {
|
||||||
|
RendererContext.stats.mon_ptg_getAlpha.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the alpha coverage values for the current tile.
|
||||||
|
* Either this method, or the nextTile() method should be called
|
||||||
|
* once per tile, but not both.
|
||||||
|
*/
|
||||||
|
private void getAlphaRLE(final byte tile[], final int offset,
|
||||||
|
final int rowstride)
|
||||||
|
{
|
||||||
|
if (doMonitors) {
|
||||||
|
RendererContext.stats.mon_ptg_getAlpha.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode run-length encoded alpha mask data
|
||||||
|
// The data for row j begins at cache.rowOffsetsRLE[j]
|
||||||
|
// and is encoded as a set of 2-byte pairs (val, runLen)
|
||||||
|
// terminated by a (0, 0) pair.
|
||||||
|
|
||||||
|
// local vars for performance:
|
||||||
|
final MarlinCache _cache = this.cache;
|
||||||
|
final long[] rowAAChunkIndex = _cache.rowAAChunkIndex;
|
||||||
|
final int[] rowAAx0 = _cache.rowAAx0;
|
||||||
|
final int[] rowAAx1 = _cache.rowAAx1;
|
||||||
|
final int[] rowAAEnc = _cache.rowAAEnc;
|
||||||
|
final long[] rowAALen = _cache.rowAALen;
|
||||||
|
final long[] rowAAPos = _cache.rowAAPos;
|
||||||
|
|
||||||
|
final int x0 = this.x;
|
||||||
|
final int x1 = FloatMath.min(x0 + TILE_SIZE, _cache.bboxX1);
|
||||||
|
|
||||||
|
// note: process tile line [0 - 32[
|
||||||
|
final int y0 = 0;
|
||||||
|
final int y1 = FloatMath.min(this.y + TILE_SIZE, _cache.bboxY1) - this.y;
|
||||||
|
|
||||||
|
if (doLogBounds) {
|
||||||
|
MarlinUtils.logInfo("getAlpha = [" + x0 + " ... " + x1
|
||||||
|
+ "[ [" + y0 + " ... " + y1 + "[");
|
||||||
|
}
|
||||||
|
|
||||||
|
final Unsafe _unsafe = OffHeapArray.unsafe;
|
||||||
|
final long SIZE_BYTE = 1L;
|
||||||
|
final long SIZE_INT = 4L;
|
||||||
|
final long addr_rowAA = _cache.rowAAChunk.address;
|
||||||
|
long addr, addr_row, last_addr, addr_end;
|
||||||
|
|
||||||
|
final int skipRowPixels = (rowstride - (x1 - x0));
|
||||||
|
|
||||||
|
int cx, cy, cx1;
|
||||||
|
int rx0, rx1, runLen, end;
|
||||||
|
int packed;
|
||||||
|
byte val;
|
||||||
|
int idx = offset;
|
||||||
|
|
||||||
|
for (cy = y0; cy < y1; cy++) {
|
||||||
|
// empty line (default)
|
||||||
|
cx = x0;
|
||||||
|
|
||||||
|
if (rowAAEnc[cy] == 0) {
|
||||||
|
// Raw encoding:
|
||||||
|
|
||||||
|
final int aax1 = rowAAx1[cy]; // exclusive
|
||||||
|
|
||||||
|
// quick check if there is AA data
|
||||||
|
// corresponding to this tile [x0; x1[
|
||||||
|
if (aax1 > x0) {
|
||||||
|
final int aax0 = rowAAx0[cy]; // inclusive
|
||||||
|
|
||||||
|
if (aax0 < x1) {
|
||||||
|
// note: cx is the cursor pointer in the tile array
|
||||||
|
// (left to right)
|
||||||
|
cx = aax0;
|
||||||
|
|
||||||
|
// ensure cx >= x0
|
||||||
|
if (cx <= x0) {
|
||||||
|
cx = x0;
|
||||||
|
} else {
|
||||||
|
// fill line start until first AA pixel rowAA exclusive:
|
||||||
|
for (end = x0; end < cx; end++) {
|
||||||
|
tile[idx++] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// now: cx >= x0 but cx < aax0 (x1 < aax0)
|
||||||
|
|
||||||
|
// Copy AA data (sum alpha data):
|
||||||
|
addr = addr_rowAA + rowAAChunkIndex[cy] + (cx - aax0);
|
||||||
|
|
||||||
|
for (end = (aax1 <= x1) ? aax1 : x1; cx < end; cx++) {
|
||||||
|
tile[idx++] = _unsafe.getByte(addr); // [0..255]
|
||||||
|
addr += SIZE_BYTE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// RLE encoding:
|
||||||
|
|
||||||
|
// quick check if there is AA data
|
||||||
|
// corresponding to this tile [x0; x1[
|
||||||
|
if (rowAAx1[cy] > x0) { // last pixel exclusive
|
||||||
|
|
||||||
|
cx = rowAAx0[cy]; // inclusive
|
||||||
|
if (cx > x1) {
|
||||||
|
cx = x1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// fill line start until first AA pixel rowAA exclusive:
|
||||||
|
for (int i = x0; i < cx; i++) {
|
||||||
|
tile[idx++] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get row address:
|
||||||
|
addr_row = addr_rowAA + rowAAChunkIndex[cy];
|
||||||
|
// get row end address:
|
||||||
|
addr_end = addr_row + rowAALen[cy]; // coded length
|
||||||
|
|
||||||
|
// reuse previous iteration position:
|
||||||
|
addr = addr_row + rowAAPos[cy];
|
||||||
|
|
||||||
|
last_addr = 0L;
|
||||||
|
|
||||||
|
while ((cx < x1) && (addr < addr_end)) {
|
||||||
|
// keep current position:
|
||||||
|
last_addr = addr;
|
||||||
|
|
||||||
|
// packed value:
|
||||||
|
packed = _unsafe.getInt(addr);
|
||||||
|
|
||||||
|
// last exclusive pixel x-coordinate:
|
||||||
|
cx1 = (packed >> 8);
|
||||||
|
// as bytes:
|
||||||
|
addr += SIZE_INT;
|
||||||
|
|
||||||
|
rx0 = cx;
|
||||||
|
if (rx0 < x0) {
|
||||||
|
rx0 = x0;
|
||||||
|
}
|
||||||
|
rx1 = cx = cx1;
|
||||||
|
if (rx1 > x1) {
|
||||||
|
rx1 = x1;
|
||||||
|
cx = x1; // fix last x
|
||||||
|
}
|
||||||
|
// adjust runLen:
|
||||||
|
runLen = rx1 - rx0;
|
||||||
|
|
||||||
|
// ensure rx1 > rx0:
|
||||||
|
if (runLen > 0) {
|
||||||
|
val = (byte)(packed & 0xFF); // [0..255]
|
||||||
|
|
||||||
|
do {
|
||||||
|
tile[idx++] = val;
|
||||||
|
} while (--runLen > 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update last position in RLE entries:
|
||||||
|
if (last_addr != 0L) {
|
||||||
|
// Fix x0:
|
||||||
|
rowAAx0[cy] = cx; // inclusive
|
||||||
|
// Fix position:
|
||||||
|
rowAAPos[cy] = (last_addr - addr_row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fill line end
|
||||||
|
while (cx < x1) {
|
||||||
|
tile[idx++] = 0;
|
||||||
|
cx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doTrace) {
|
||||||
|
for (int i = idx - (x1 - x0); i < idx; i++) {
|
||||||
|
System.out.print(hex(tile[i], 2));
|
||||||
|
}
|
||||||
|
System.out.println();
|
||||||
|
}
|
||||||
|
|
||||||
|
idx += skipRowPixels;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextTile();
|
||||||
|
|
||||||
|
if (doMonitors) {
|
||||||
|
RendererContext.stats.mon_ptg_getAlpha.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static String hex(int v, int d) {
|
||||||
|
String s = Integer.toHexString(v);
|
||||||
|
while (s.length() < d) {
|
||||||
|
s = "0" + s;
|
||||||
|
}
|
||||||
|
return s.substring(0, d);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sun.java2d.marlin;
|
||||||
|
|
||||||
|
import jdk.internal.misc.JavaLangAccess;
|
||||||
|
import jdk.internal.misc.SharedSecrets;
|
||||||
|
|
||||||
|
public final class MarlinUtils {
|
||||||
|
// TODO: use sun.util.logging.PlatformLogger once in JDK9
|
||||||
|
private static final java.util.logging.Logger log;
|
||||||
|
|
||||||
|
static {
|
||||||
|
if (MarlinConst.useLogger) {
|
||||||
|
log = java.util.logging.Logger.getLogger("sun.java2d.marlin");
|
||||||
|
} else {
|
||||||
|
log = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private MarlinUtils() {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void logInfo(final String msg) {
|
||||||
|
if (MarlinConst.useLogger) {
|
||||||
|
log.info(msg);
|
||||||
|
} else if (MarlinConst.enableLogs) {
|
||||||
|
System.out.print("INFO: ");
|
||||||
|
System.out.println(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void logException(final String msg, final Throwable th) {
|
||||||
|
if (MarlinConst.useLogger) {
|
||||||
|
// log.warning(msg, th);
|
||||||
|
log.log(java.util.logging.Level.WARNING, msg, th);
|
||||||
|
} else if (MarlinConst.enableLogs) {
|
||||||
|
System.out.print("WARNING: ");
|
||||||
|
System.out.println(msg);
|
||||||
|
th.printStackTrace(System.err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the caller's class and method's name; best effort
|
||||||
|
// if cannot infer, return the logger's name.
|
||||||
|
static String getCallerInfo(String className) {
|
||||||
|
String sourceClassName = null;
|
||||||
|
String sourceMethodName = null;
|
||||||
|
|
||||||
|
JavaLangAccess access = SharedSecrets.getJavaLangAccess();
|
||||||
|
Throwable throwable = new Throwable();
|
||||||
|
int depth = access.getStackTraceDepth(throwable);
|
||||||
|
|
||||||
|
boolean lookingForClassName = true;
|
||||||
|
for (int ix = 0; ix < depth; ix++) {
|
||||||
|
// Calling getStackTraceElement directly prevents the VM
|
||||||
|
// from paying the cost of building the entire stack frame.
|
||||||
|
StackTraceElement frame = access.getStackTraceElement(throwable, ix);
|
||||||
|
String cname = frame.getClassName();
|
||||||
|
if (lookingForClassName) {
|
||||||
|
// Skip all frames until we have found the first frame having the class name.
|
||||||
|
if (cname.equals(className)) {
|
||||||
|
lookingForClassName = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!cname.equals(className)) {
|
||||||
|
// We've found the relevant frame.
|
||||||
|
sourceClassName = cname;
|
||||||
|
sourceMethodName = frame.getMethodName();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sourceClassName != null) {
|
||||||
|
return sourceClassName + " " + sourceMethodName;
|
||||||
|
} else {
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,177 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sun.java2d.marlin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MergeSort adapted from (OpenJDK 8) java.util.Array.legacyMergeSort(Object[])
|
||||||
|
* to swap two arrays at the same time (x & y)
|
||||||
|
* and use external auxiliary storage for temporary arrays
|
||||||
|
*/
|
||||||
|
final class MergeSort {
|
||||||
|
|
||||||
|
// insertion sort threshold
|
||||||
|
public static final int INSERTION_SORT_THRESHOLD = 14;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modified merge sort:
|
||||||
|
* Input arrays are in both auxX/auxY (sorted: 0 to insertionSortIndex)
|
||||||
|
* and x/y (unsorted: insertionSortIndex to toIndex)
|
||||||
|
* Outputs are stored in x/y arrays
|
||||||
|
*/
|
||||||
|
static void mergeSortNoCopy(final int[] x, final int[] y,
|
||||||
|
final int[] auxX, final int[] auxY,
|
||||||
|
final int toIndex,
|
||||||
|
final int insertionSortIndex)
|
||||||
|
{
|
||||||
|
if ((toIndex > x.length) || (toIndex > y.length)
|
||||||
|
|| (toIndex > auxX.length) || (toIndex > auxY.length)) {
|
||||||
|
// explicit check to avoid bound checks within hot loops (below):
|
||||||
|
throw new ArrayIndexOutOfBoundsException("bad arguments: toIndex="
|
||||||
|
+ toIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort second part only using merge / insertion sort
|
||||||
|
// in auxiliary storage (auxX/auxY)
|
||||||
|
mergeSort(x, y, x, auxX, y, auxY, insertionSortIndex, toIndex);
|
||||||
|
|
||||||
|
// final pass to merge both
|
||||||
|
// Merge sorted parts (auxX/auxY) into x/y arrays
|
||||||
|
if ((insertionSortIndex == 0)
|
||||||
|
|| (auxX[insertionSortIndex - 1] <= auxX[insertionSortIndex])) {
|
||||||
|
// System.out.println("mergeSortNoCopy: ordered");
|
||||||
|
// 34 occurences
|
||||||
|
// no initial left part or both sublists (auxX, auxY) are sorted:
|
||||||
|
// copy back data into (x, y):
|
||||||
|
System.arraycopy(auxX, 0, x, 0, toIndex);
|
||||||
|
System.arraycopy(auxY, 0, y, 0, toIndex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0, p = 0, q = insertionSortIndex; i < toIndex; i++) {
|
||||||
|
if ((q >= toIndex) || ((p < insertionSortIndex)
|
||||||
|
&& (auxX[p] <= auxX[q]))) {
|
||||||
|
x[i] = auxX[p];
|
||||||
|
y[i] = auxY[p];
|
||||||
|
p++;
|
||||||
|
} else {
|
||||||
|
x[i] = auxX[q];
|
||||||
|
y[i] = auxY[q];
|
||||||
|
q++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Src is the source array that starts at index 0
|
||||||
|
* Dest is the (possibly larger) array destination with a possible offset
|
||||||
|
* low is the index in dest to start sorting
|
||||||
|
* high is the end index in dest to end sorting
|
||||||
|
*/
|
||||||
|
private static void mergeSort(final int[] refX, final int[] refY,
|
||||||
|
final int[] srcX, final int[] dstX,
|
||||||
|
final int[] srcY, final int[] dstY,
|
||||||
|
final int low, final int high)
|
||||||
|
{
|
||||||
|
final int length = high - low;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Tuning parameter: list size at or below which insertion sort
|
||||||
|
* will be used in preference to mergesort.
|
||||||
|
*/
|
||||||
|
if (length <= INSERTION_SORT_THRESHOLD) {
|
||||||
|
// Insertion sort on smallest arrays
|
||||||
|
dstX[low] = refX[low];
|
||||||
|
dstY[low] = refY[low];
|
||||||
|
|
||||||
|
for (int i = low + 1, j = low, x, y; i < high; j = i++) {
|
||||||
|
x = refX[i];
|
||||||
|
y = refY[i];
|
||||||
|
|
||||||
|
while (dstX[j] > x) {
|
||||||
|
// swap element
|
||||||
|
dstX[j + 1] = dstX[j];
|
||||||
|
dstY[j + 1] = dstY[j];
|
||||||
|
if (j-- == low) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dstX[j + 1] = x;
|
||||||
|
dstY[j + 1] = y;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively sort halves of dest into src
|
||||||
|
|
||||||
|
// note: use signed shift (not >>>) for performance
|
||||||
|
// as indices are small enough to exceed Integer.MAX_VALUE
|
||||||
|
final int mid = (low + high) >> 1;
|
||||||
|
|
||||||
|
mergeSort(refX, refY, dstX, srcX, dstY, srcY, low, mid);
|
||||||
|
mergeSort(refX, refY, dstX, srcX, dstY, srcY, mid, high);
|
||||||
|
|
||||||
|
// If arrays are inverted ie all(A) > all(B) do swap A and B to dst
|
||||||
|
if (srcX[high - 1] <= srcX[low]) {
|
||||||
|
// System.out.println("mergeSort: inverse ordered");
|
||||||
|
// 1561 occurences
|
||||||
|
final int left = mid - low;
|
||||||
|
final int right = high - mid;
|
||||||
|
final int off = (left != right) ? 1 : 0;
|
||||||
|
// swap parts:
|
||||||
|
System.arraycopy(srcX, low, dstX, mid + off, left);
|
||||||
|
System.arraycopy(srcX, mid, dstX, low, right);
|
||||||
|
System.arraycopy(srcY, low, dstY, mid + off, left);
|
||||||
|
System.arraycopy(srcY, mid, dstY, low, right);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If arrays are already sorted, just copy from src to dest. This is an
|
||||||
|
// optimization that results in faster sorts for nearly ordered lists.
|
||||||
|
if (srcX[mid - 1] <= srcX[mid]) {
|
||||||
|
// System.out.println("mergeSort: ordered");
|
||||||
|
// 14 occurences
|
||||||
|
System.arraycopy(srcX, low, dstX, low, length);
|
||||||
|
System.arraycopy(srcY, low, dstY, low, length);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge sorted halves (now in src) into dest
|
||||||
|
for (int i = low, p = low, q = mid; i < high; i++) {
|
||||||
|
if ((q >= high) || ((p < mid) && (srcX[p] <= srcX[q]))) {
|
||||||
|
dstX[i] = srcX[p];
|
||||||
|
dstY[i] = srcY[p];
|
||||||
|
p++;
|
||||||
|
} else {
|
||||||
|
dstX[i] = srcX[q];
|
||||||
|
dstY[i] = srcY[q];
|
||||||
|
q++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private MergeSort() {
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,166 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sun.java2d.marlin;
|
||||||
|
|
||||||
|
import java.lang.ref.PhantomReference;
|
||||||
|
import java.lang.ref.ReferenceQueue;
|
||||||
|
import java.security.AccessController;
|
||||||
|
import java.security.PrivilegedAction;
|
||||||
|
import java.util.Vector;
|
||||||
|
import static sun.java2d.marlin.MarlinConst.logUnsafeMalloc;
|
||||||
|
import sun.awt.util.ThreadGroupUtils;
|
||||||
|
import jdk.internal.misc.Unsafe;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author bourgesl
|
||||||
|
*/
|
||||||
|
final class OffHeapArray {
|
||||||
|
|
||||||
|
// unsafe reference
|
||||||
|
static final Unsafe unsafe;
|
||||||
|
// size of int / float
|
||||||
|
static final int SIZE_INT;
|
||||||
|
|
||||||
|
// RendererContext reference queue
|
||||||
|
private static final ReferenceQueue<Object> rdrQueue
|
||||||
|
= new ReferenceQueue<Object>();
|
||||||
|
// reference list
|
||||||
|
private static final Vector<OffHeapReference> refList
|
||||||
|
= new Vector<OffHeapReference>(32);
|
||||||
|
|
||||||
|
static {
|
||||||
|
unsafe = Unsafe.getUnsafe();
|
||||||
|
SIZE_INT = Unsafe.ARRAY_INT_INDEX_SCALE;
|
||||||
|
|
||||||
|
// Mimics Java2D Disposer:
|
||||||
|
AccessController.doPrivileged(
|
||||||
|
(PrivilegedAction<Void>) () -> {
|
||||||
|
/*
|
||||||
|
* The thread must be a member of a thread group
|
||||||
|
* which will not get GCed before VM exit.
|
||||||
|
* Make its parent the top-level thread group.
|
||||||
|
*/
|
||||||
|
final ThreadGroup rootTG
|
||||||
|
= ThreadGroupUtils.getRootThreadGroup();
|
||||||
|
final Thread t = new Thread(rootTG, new OffHeapDisposer(),
|
||||||
|
"MarlinRenderer Disposer");
|
||||||
|
t.setContextClassLoader(null);
|
||||||
|
t.setDaemon(true);
|
||||||
|
t.setPriority(Thread.MAX_PRIORITY);
|
||||||
|
t.start();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* members */
|
||||||
|
long address;
|
||||||
|
long length;
|
||||||
|
int used;
|
||||||
|
|
||||||
|
OffHeapArray(final Object parent, final long len) {
|
||||||
|
// note: may throw OOME:
|
||||||
|
this.address = unsafe.allocateMemory(len);
|
||||||
|
this.length = len;
|
||||||
|
this.used = 0;
|
||||||
|
if (logUnsafeMalloc) {
|
||||||
|
MarlinUtils.logInfo(System.currentTimeMillis()
|
||||||
|
+ ": OffHeapArray.allocateMemory = "
|
||||||
|
+ len + " to addr = " + this.address);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the phantom reference to ensure freeing off-heap memory:
|
||||||
|
refList.add(new OffHeapReference(parent, this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* As realloc may change the address, updating address is MANDATORY
|
||||||
|
* @param len new array length
|
||||||
|
* @throws OutOfMemoryError if the allocation is refused by the system
|
||||||
|
*/
|
||||||
|
void resize(final long len) {
|
||||||
|
// note: may throw OOME:
|
||||||
|
this.address = unsafe.reallocateMemory(address, len);
|
||||||
|
this.length = len;
|
||||||
|
if (logUnsafeMalloc) {
|
||||||
|
MarlinUtils.logInfo(System.currentTimeMillis()
|
||||||
|
+ ": OffHeapArray.reallocateMemory = "
|
||||||
|
+ len + " to addr = " + this.address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void free() {
|
||||||
|
unsafe.freeMemory(this.address);
|
||||||
|
if (logUnsafeMalloc) {
|
||||||
|
MarlinUtils.logInfo(System.currentTimeMillis()
|
||||||
|
+ ": OffHeapEdgeArray.free = "
|
||||||
|
+ this.length
|
||||||
|
+ " at addr = " + this.address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void fill(final byte val) {
|
||||||
|
unsafe.setMemory(this.address, this.length, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class OffHeapReference extends PhantomReference<Object> {
|
||||||
|
|
||||||
|
private final OffHeapArray array;
|
||||||
|
|
||||||
|
OffHeapReference(final Object parent, final OffHeapArray edges) {
|
||||||
|
super(parent, rdrQueue);
|
||||||
|
this.array = edges;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dispose() {
|
||||||
|
// free off-heap blocks
|
||||||
|
this.array.free();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class OffHeapDisposer implements Runnable {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
final Thread currentThread = Thread.currentThread();
|
||||||
|
OffHeapReference ref;
|
||||||
|
|
||||||
|
// check interrupted:
|
||||||
|
for (; !currentThread.isInterrupted();) {
|
||||||
|
try {
|
||||||
|
ref = (OffHeapReference)rdrQueue.remove();
|
||||||
|
ref.dispose();
|
||||||
|
|
||||||
|
refList.remove(ref);
|
||||||
|
|
||||||
|
} catch (InterruptedException ie) {
|
||||||
|
MarlinUtils.logException("OffHeapDisposer interrupted:",
|
||||||
|
ie);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1546
jdk/src/java.desktop/share/classes/sun/java2d/marlin/Renderer.java
Normal file
1546
jdk/src/java.desktop/share/classes/sun/java2d/marlin/Renderer.java
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,471 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sun.java2d.marlin;
|
||||||
|
|
||||||
|
import java.awt.geom.Path2D;
|
||||||
|
import java.lang.ref.SoftReference;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import static sun.java2d.marlin.ArrayCache.*;
|
||||||
|
import sun.java2d.marlin.MarlinRenderingEngine.NormalizingPathIterator;
|
||||||
|
import static sun.java2d.marlin.MarlinUtils.getCallerInfo;
|
||||||
|
import static sun.java2d.marlin.MarlinUtils.logInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is a renderer context dedicated to a single thread
|
||||||
|
*/
|
||||||
|
final class RendererContext implements MarlinConst {
|
||||||
|
|
||||||
|
private static final String className = RendererContext.class.getName();
|
||||||
|
// RendererContext creation counter
|
||||||
|
private static final AtomicInteger contextCount = new AtomicInteger(1);
|
||||||
|
// RendererContext statistics
|
||||||
|
static final RendererStats stats = (doStats || doMonitors)
|
||||||
|
? RendererStats.getInstance(): null;
|
||||||
|
|
||||||
|
private static final boolean USE_CACHE_HARD_REF = doStats
|
||||||
|
|| (MarlinRenderingEngine.REF_TYPE == MarlinRenderingEngine.REF_WEAK);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new renderer context
|
||||||
|
*
|
||||||
|
* @return new RendererContext instance
|
||||||
|
*/
|
||||||
|
static RendererContext createContext() {
|
||||||
|
final RendererContext newCtx = new RendererContext("ctx"
|
||||||
|
+ Integer.toString(contextCount.getAndIncrement()));
|
||||||
|
if (RendererContext.stats != null) {
|
||||||
|
RendererContext.stats.allContexts.add(newCtx);
|
||||||
|
}
|
||||||
|
return newCtx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// context name (debugging purposes)
|
||||||
|
final String name;
|
||||||
|
/*
|
||||||
|
* Reference to this instance (hard, soft or weak).
|
||||||
|
* @see MarlinRenderingEngine#REF_TYPE
|
||||||
|
*/
|
||||||
|
final Object reference;
|
||||||
|
// dirty flag indicating an exception occured during pipeline in pathTo()
|
||||||
|
boolean dirty = false;
|
||||||
|
// dynamic array caches kept using weak reference (low memory footprint)
|
||||||
|
WeakReference<ArrayCachesHolder> refArrayCaches = null;
|
||||||
|
// hard reference to array caches (for statistics)
|
||||||
|
ArrayCachesHolder hardRefArrayCaches = null;
|
||||||
|
// shared data
|
||||||
|
final float[] float6 = new float[6];
|
||||||
|
// shared curve (dirty) (Renderer / Stroker)
|
||||||
|
final Curve curve = new Curve();
|
||||||
|
// MarlinRenderingEngine NormalizingPathIterator NearestPixelCenter:
|
||||||
|
final NormalizingPathIterator nPCPathIterator;
|
||||||
|
// MarlinRenderingEngine NearestPixelQuarter NormalizingPathIterator:
|
||||||
|
final NormalizingPathIterator nPQPathIterator;
|
||||||
|
// MarlinRenderingEngine.TransformingPathConsumer2D
|
||||||
|
final TransformingPathConsumer2D transformerPC2D;
|
||||||
|
// recycled Path2D instance
|
||||||
|
Path2D.Float p2d = null;
|
||||||
|
final Renderer renderer;
|
||||||
|
final Stroker stroker;
|
||||||
|
// Simplifies out collinear lines
|
||||||
|
final CollinearSimplifier simplifier = new CollinearSimplifier();
|
||||||
|
final Dasher dasher;
|
||||||
|
final MarlinTileGenerator ptg;
|
||||||
|
final MarlinCache cache;
|
||||||
|
// flag indicating the shape is stroked (1) or filled (0)
|
||||||
|
int stroking = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param name
|
||||||
|
*/
|
||||||
|
RendererContext(final String name) {
|
||||||
|
if (logCreateContext) {
|
||||||
|
MarlinUtils.logInfo("new RendererContext = " + name);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.name = name;
|
||||||
|
|
||||||
|
// NormalizingPathIterator instances:
|
||||||
|
nPCPathIterator = new NormalizingPathIterator.NearestPixelCenter(float6);
|
||||||
|
nPQPathIterator = new NormalizingPathIterator.NearestPixelQuarter(float6);
|
||||||
|
|
||||||
|
// MarlinRenderingEngine.TransformingPathConsumer2D
|
||||||
|
transformerPC2D = new TransformingPathConsumer2D();
|
||||||
|
|
||||||
|
// Renderer:
|
||||||
|
cache = new MarlinCache(this);
|
||||||
|
renderer = new Renderer(this); // needs MarlinCache from rdrCtx.cache
|
||||||
|
ptg = new MarlinTileGenerator(renderer);
|
||||||
|
|
||||||
|
stroker = new Stroker(this);
|
||||||
|
dasher = new Dasher(this);
|
||||||
|
|
||||||
|
// Create the reference to this instance (hard, soft or weak):
|
||||||
|
switch (MarlinRenderingEngine.REF_TYPE) {
|
||||||
|
default:
|
||||||
|
case MarlinRenderingEngine.REF_HARD:
|
||||||
|
reference = this;
|
||||||
|
break;
|
||||||
|
case MarlinRenderingEngine.REF_SOFT:
|
||||||
|
reference = new SoftReference<RendererContext>(this);
|
||||||
|
break;
|
||||||
|
case MarlinRenderingEngine.REF_WEAK:
|
||||||
|
reference = new WeakReference<RendererContext>(this);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disposes this renderer context:
|
||||||
|
* clean up before reusing this context
|
||||||
|
*/
|
||||||
|
void dispose() {
|
||||||
|
stroking = 0;
|
||||||
|
// reset hard reference to array caches if needed:
|
||||||
|
if (!USE_CACHE_HARD_REF) {
|
||||||
|
hardRefArrayCaches = null;
|
||||||
|
}
|
||||||
|
// if context is maked as DIRTY:
|
||||||
|
if (dirty) {
|
||||||
|
// may happen if an exception if thrown in the pipeline processing:
|
||||||
|
// force cleanup of all possible pipelined blocks (except Renderer):
|
||||||
|
|
||||||
|
// NormalizingPathIterator instances:
|
||||||
|
this.nPCPathIterator.dispose();
|
||||||
|
this.nPQPathIterator.dispose();
|
||||||
|
// Dasher:
|
||||||
|
this.dasher.dispose();
|
||||||
|
// Stroker:
|
||||||
|
this.stroker.dispose();
|
||||||
|
|
||||||
|
// mark context as CLEAN:
|
||||||
|
dirty = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Array caches
|
||||||
|
ArrayCachesHolder getArrayCachesHolder() {
|
||||||
|
// Use hard reference first (cached resolved weak reference):
|
||||||
|
ArrayCachesHolder holder = hardRefArrayCaches;
|
||||||
|
if (holder == null) {
|
||||||
|
// resolve reference:
|
||||||
|
holder = (refArrayCaches != null)
|
||||||
|
? refArrayCaches.get()
|
||||||
|
: null;
|
||||||
|
// create a new ArrayCachesHolder if none is available
|
||||||
|
if (holder == null) {
|
||||||
|
if (logCreateContext) {
|
||||||
|
MarlinUtils.logInfo("new ArrayCachesHolder for "
|
||||||
|
+ "RendererContext = " + name);
|
||||||
|
}
|
||||||
|
|
||||||
|
holder = new ArrayCachesHolder();
|
||||||
|
|
||||||
|
if (USE_CACHE_HARD_REF) {
|
||||||
|
// update hard reference:
|
||||||
|
hardRefArrayCaches = holder;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update weak reference:
|
||||||
|
refArrayCaches = new WeakReference<ArrayCachesHolder>(holder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return holder;
|
||||||
|
}
|
||||||
|
|
||||||
|
// dirty byte array cache
|
||||||
|
ByteArrayCache getDirtyByteArrayCache(final int length) {
|
||||||
|
final int bucket = ArrayCache.getBucketDirtyBytes(length);
|
||||||
|
return getArrayCachesHolder().dirtyByteArrayCaches[bucket];
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] getDirtyByteArray(final int length) {
|
||||||
|
if (length <= MAX_DIRTY_BYTE_ARRAY_SIZE) {
|
||||||
|
return getDirtyByteArrayCache(length).getArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doStats) {
|
||||||
|
incOversize();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doLogOverSize) {
|
||||||
|
logInfo("getDirtyByteArray[oversize]: length=\t" + length
|
||||||
|
+ "\tfrom=\t" + getCallerInfo(className));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new byte[length];
|
||||||
|
}
|
||||||
|
|
||||||
|
void putDirtyByteArray(final byte[] array) {
|
||||||
|
final int length = array.length;
|
||||||
|
// odd sized array are non-cached arrays (initial arrays)
|
||||||
|
// ensure to never store initial arrays in cache:
|
||||||
|
if (((length & 0x1) == 0) && (length <= MAX_DIRTY_BYTE_ARRAY_SIZE)) {
|
||||||
|
getDirtyByteArrayCache(length).putDirtyArray(array, length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] widenDirtyByteArray(final byte[] in,
|
||||||
|
final int usedSize, final int needSize)
|
||||||
|
{
|
||||||
|
final int length = in.length;
|
||||||
|
if (doChecks && length >= needSize) {
|
||||||
|
return in;
|
||||||
|
}
|
||||||
|
if (doStats) {
|
||||||
|
incResizeDirtyByte();
|
||||||
|
}
|
||||||
|
|
||||||
|
// maybe change bucket:
|
||||||
|
// ensure getNewSize() > newSize:
|
||||||
|
final byte[] res = getDirtyByteArray(getNewSize(usedSize, needSize));
|
||||||
|
|
||||||
|
System.arraycopy(in, 0, res, 0, usedSize); // copy only used elements
|
||||||
|
|
||||||
|
// maybe return current array:
|
||||||
|
// NO clean-up of array data = DIRTY ARRAY
|
||||||
|
putDirtyByteArray(in);
|
||||||
|
|
||||||
|
if (doLogWidenArray) {
|
||||||
|
logInfo("widenDirtyByteArray[" + res.length + "]: usedSize=\t"
|
||||||
|
+ usedSize + "\tlength=\t" + length + "\tneeded length=\t"
|
||||||
|
+ needSize + "\tfrom=\t" + getCallerInfo(className));
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// int array cache
|
||||||
|
IntArrayCache getIntArrayCache(final int length) {
|
||||||
|
final int bucket = ArrayCache.getBucket(length);
|
||||||
|
return getArrayCachesHolder().intArrayCaches[bucket];
|
||||||
|
}
|
||||||
|
|
||||||
|
int[] getIntArray(final int length) {
|
||||||
|
if (length <= MAX_ARRAY_SIZE) {
|
||||||
|
return getIntArrayCache(length).getArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doStats) {
|
||||||
|
incOversize();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doLogOverSize) {
|
||||||
|
logInfo("getIntArray[oversize]: length=\t" + length + "\tfrom=\t"
|
||||||
|
+ getCallerInfo(className));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new int[length];
|
||||||
|
}
|
||||||
|
|
||||||
|
// unused
|
||||||
|
int[] widenIntArray(final int[] in, final int usedSize,
|
||||||
|
final int needSize, final int clearTo)
|
||||||
|
{
|
||||||
|
final int length = in.length;
|
||||||
|
if (doChecks && length >= needSize) {
|
||||||
|
return in;
|
||||||
|
}
|
||||||
|
if (doStats) {
|
||||||
|
incResizeInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
// maybe change bucket:
|
||||||
|
// ensure getNewSize() > newSize:
|
||||||
|
final int[] res = getIntArray(getNewSize(usedSize, needSize));
|
||||||
|
|
||||||
|
System.arraycopy(in, 0, res, 0, usedSize); // copy only used elements
|
||||||
|
|
||||||
|
// maybe return current array:
|
||||||
|
putIntArray(in, 0, clearTo); // ensure all array is cleared (grow-reduce algo)
|
||||||
|
|
||||||
|
if (doLogWidenArray) {
|
||||||
|
logInfo("widenIntArray[" + res.length + "]: usedSize=\t"
|
||||||
|
+ usedSize + "\tlength=\t" + length + "\tneeded length=\t"
|
||||||
|
+ needSize + "\tfrom=\t" + getCallerInfo(className));
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void putIntArray(final int[] array, final int fromIndex,
|
||||||
|
final int toIndex)
|
||||||
|
{
|
||||||
|
final int length = array.length;
|
||||||
|
// odd sized array are non-cached arrays (initial arrays)
|
||||||
|
// ensure to never store initial arrays in cache:
|
||||||
|
if (((length & 0x1) == 0) && (length <= MAX_ARRAY_SIZE)) {
|
||||||
|
getIntArrayCache(length).putArray(array, length, fromIndex, toIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dirty int array cache
|
||||||
|
IntArrayCache getDirtyIntArrayCache(final int length) {
|
||||||
|
final int bucket = ArrayCache.getBucket(length);
|
||||||
|
return getArrayCachesHolder().dirtyIntArrayCaches[bucket];
|
||||||
|
}
|
||||||
|
|
||||||
|
int[] getDirtyIntArray(final int length) {
|
||||||
|
if (length <= MAX_ARRAY_SIZE) {
|
||||||
|
return getDirtyIntArrayCache(length).getArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doStats) {
|
||||||
|
incOversize();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doLogOverSize) {
|
||||||
|
logInfo("getDirtyIntArray[oversize]: length=\t" + length
|
||||||
|
+ "\tfrom=\t" + getCallerInfo(className));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new int[length];
|
||||||
|
}
|
||||||
|
|
||||||
|
int[] widenDirtyIntArray(final int[] in,
|
||||||
|
final int usedSize, final int needSize)
|
||||||
|
{
|
||||||
|
final int length = in.length;
|
||||||
|
if (doChecks && length >= needSize) {
|
||||||
|
return in;
|
||||||
|
}
|
||||||
|
if (doStats) {
|
||||||
|
incResizeDirtyInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
// maybe change bucket:
|
||||||
|
// ensure getNewSize() > newSize:
|
||||||
|
final int[] res = getDirtyIntArray(getNewSize(usedSize, needSize));
|
||||||
|
|
||||||
|
System.arraycopy(in, 0, res, 0, usedSize); // copy only used elements
|
||||||
|
|
||||||
|
// maybe return current array:
|
||||||
|
// NO clean-up of array data = DIRTY ARRAY
|
||||||
|
putDirtyIntArray(in);
|
||||||
|
|
||||||
|
if (doLogWidenArray) {
|
||||||
|
logInfo("widenDirtyIntArray[" + res.length + "]: usedSize=\t"
|
||||||
|
+ usedSize + "\tlength=\t" + length + "\tneeded length=\t"
|
||||||
|
+ needSize + "\tfrom=\t" + getCallerInfo(className));
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void putDirtyIntArray(final int[] array) {
|
||||||
|
final int length = array.length;
|
||||||
|
// odd sized array are non-cached arrays (initial arrays)
|
||||||
|
// ensure to never store initial arrays in cache:
|
||||||
|
if (((length & 0x1) == 0) && (length <= MAX_ARRAY_SIZE)) {
|
||||||
|
getDirtyIntArrayCache(length).putDirtyArray(array, length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dirty float array cache
|
||||||
|
FloatArrayCache getDirtyFloatArrayCache(final int length) {
|
||||||
|
final int bucket = ArrayCache.getBucket(length);
|
||||||
|
return getArrayCachesHolder().dirtyFloatArrayCaches[bucket];
|
||||||
|
}
|
||||||
|
|
||||||
|
float[] getDirtyFloatArray(final int length) {
|
||||||
|
if (length <= MAX_ARRAY_SIZE) {
|
||||||
|
return getDirtyFloatArrayCache(length).getArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doStats) {
|
||||||
|
incOversize();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doLogOverSize) {
|
||||||
|
logInfo("getDirtyFloatArray[oversize]: length=\t" + length
|
||||||
|
+ "\tfrom=\t" + getCallerInfo(className));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new float[length];
|
||||||
|
}
|
||||||
|
|
||||||
|
float[] widenDirtyFloatArray(final float[] in,
|
||||||
|
final int usedSize, final int needSize)
|
||||||
|
{
|
||||||
|
final int length = in.length;
|
||||||
|
if (doChecks && length >= needSize) {
|
||||||
|
return in;
|
||||||
|
}
|
||||||
|
if (doStats) {
|
||||||
|
incResizeDirtyFloat();
|
||||||
|
}
|
||||||
|
|
||||||
|
// maybe change bucket:
|
||||||
|
// ensure getNewSize() > newSize:
|
||||||
|
final float[] res = getDirtyFloatArray(getNewSize(usedSize, needSize));
|
||||||
|
|
||||||
|
System.arraycopy(in, 0, res, 0, usedSize); // copy only used elements
|
||||||
|
|
||||||
|
// maybe return current array:
|
||||||
|
// NO clean-up of array data = DIRTY ARRAY
|
||||||
|
putDirtyFloatArray(in);
|
||||||
|
|
||||||
|
if (doLogWidenArray) {
|
||||||
|
logInfo("widenDirtyFloatArray[" + res.length + "]: usedSize=\t"
|
||||||
|
+ usedSize + "\tlength=\t" + length + "\tneeded length=\t"
|
||||||
|
+ needSize + "\tfrom=\t" + getCallerInfo(className));
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void putDirtyFloatArray(final float[] array) {
|
||||||
|
final int length = array.length;
|
||||||
|
// odd sized array are non-cached arrays (initial arrays)
|
||||||
|
// ensure to never store initial arrays in cache:
|
||||||
|
if (((length & 0x1) == 0) && (length <= MAX_ARRAY_SIZE)) {
|
||||||
|
getDirtyFloatArrayCache(length).putDirtyArray(array, length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* class holding all array cache instances */
|
||||||
|
static final class ArrayCachesHolder {
|
||||||
|
// zero-filled int array cache:
|
||||||
|
final IntArrayCache[] intArrayCaches;
|
||||||
|
// dirty array caches:
|
||||||
|
final IntArrayCache[] dirtyIntArrayCaches;
|
||||||
|
final FloatArrayCache[] dirtyFloatArrayCaches;
|
||||||
|
final ByteArrayCache[] dirtyByteArrayCaches;
|
||||||
|
|
||||||
|
ArrayCachesHolder() {
|
||||||
|
intArrayCaches = new IntArrayCache[BUCKETS];
|
||||||
|
dirtyIntArrayCaches = new IntArrayCache[BUCKETS];
|
||||||
|
dirtyFloatArrayCaches = new FloatArrayCache[BUCKETS];
|
||||||
|
dirtyByteArrayCaches = new ByteArrayCache[BUCKETS];
|
||||||
|
|
||||||
|
for (int i = 0; i < BUCKETS; i++) {
|
||||||
|
intArrayCaches[i] = new IntArrayCache(ARRAY_SIZES[i]);
|
||||||
|
// dirty array caches:
|
||||||
|
dirtyIntArrayCaches[i] = new IntArrayCache(ARRAY_SIZES[i]);
|
||||||
|
dirtyFloatArrayCaches[i] = new FloatArrayCache(ARRAY_SIZES[i]);
|
||||||
|
dirtyByteArrayCaches[i] = new ByteArrayCache(DIRTY_BYTE_ARRAY_SIZES[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,319 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sun.java2d.marlin;
|
||||||
|
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
|
import static sun.java2d.marlin.MarlinUtils.logInfo;
|
||||||
|
import sun.java2d.marlin.stats.Histogram;
|
||||||
|
import sun.java2d.marlin.stats.Monitor;
|
||||||
|
import sun.java2d.marlin.stats.StatLong;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class gathers global rendering statistics for debugging purposes only
|
||||||
|
*/
|
||||||
|
public final class RendererStats implements MarlinConst {
|
||||||
|
|
||||||
|
// singleton
|
||||||
|
private static volatile RendererStats singleton = null;
|
||||||
|
|
||||||
|
static RendererStats getInstance() {
|
||||||
|
if (singleton == null) {
|
||||||
|
singleton = new RendererStats();
|
||||||
|
}
|
||||||
|
return singleton;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void dumpStats() {
|
||||||
|
if (singleton != null) {
|
||||||
|
singleton.dump();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RendererContext collection as hard references
|
||||||
|
(only used for debugging purposes) */
|
||||||
|
final ConcurrentLinkedQueue<RendererContext> allContexts
|
||||||
|
= new ConcurrentLinkedQueue<RendererContext>();
|
||||||
|
// stats
|
||||||
|
final StatLong stat_cache_rowAA
|
||||||
|
= new StatLong("cache.rowAA");
|
||||||
|
final StatLong stat_cache_rowAAChunk
|
||||||
|
= new StatLong("cache.rowAAChunk");
|
||||||
|
final StatLong stat_cache_tiles
|
||||||
|
= new StatLong("cache.tiles");
|
||||||
|
final StatLong stat_rdr_poly_stack_curves
|
||||||
|
= new StatLong("renderer.poly.stack.curves");
|
||||||
|
final StatLong stat_rdr_poly_stack_types
|
||||||
|
= new StatLong("renderer.poly.stack.types");
|
||||||
|
final StatLong stat_rdr_addLine
|
||||||
|
= new StatLong("renderer.addLine");
|
||||||
|
final StatLong stat_rdr_addLine_skip
|
||||||
|
= new StatLong("renderer.addLine.skip");
|
||||||
|
final StatLong stat_rdr_curveBreak
|
||||||
|
= new StatLong("renderer.curveBreakIntoLinesAndAdd");
|
||||||
|
final StatLong stat_rdr_curveBreak_dec
|
||||||
|
= new StatLong("renderer.curveBreakIntoLinesAndAdd.dec");
|
||||||
|
final StatLong stat_rdr_curveBreak_inc
|
||||||
|
= new StatLong("renderer.curveBreakIntoLinesAndAdd.inc");
|
||||||
|
final StatLong stat_rdr_quadBreak
|
||||||
|
= new StatLong("renderer.quadBreakIntoLinesAndAdd");
|
||||||
|
final StatLong stat_rdr_quadBreak_dec
|
||||||
|
= new StatLong("renderer.quadBreakIntoLinesAndAdd.dec");
|
||||||
|
final StatLong stat_rdr_edges
|
||||||
|
= new StatLong("renderer.edges");
|
||||||
|
final StatLong stat_rdr_edges_count
|
||||||
|
= new StatLong("renderer.edges.count");
|
||||||
|
final StatLong stat_rdr_edges_resizes
|
||||||
|
= new StatLong("renderer.edges.resize");
|
||||||
|
final StatLong stat_rdr_activeEdges
|
||||||
|
= new StatLong("renderer.activeEdges");
|
||||||
|
final StatLong stat_rdr_activeEdges_updates
|
||||||
|
= new StatLong("renderer.activeEdges.updates");
|
||||||
|
final StatLong stat_rdr_activeEdges_adds
|
||||||
|
= new StatLong("renderer.activeEdges.adds");
|
||||||
|
final StatLong stat_rdr_activeEdges_adds_high
|
||||||
|
= new StatLong("renderer.activeEdges.adds_high");
|
||||||
|
final StatLong stat_rdr_crossings_updates
|
||||||
|
= new StatLong("renderer.crossings.updates");
|
||||||
|
final StatLong stat_rdr_crossings_sorts
|
||||||
|
= new StatLong("renderer.crossings.sorts");
|
||||||
|
final StatLong stat_rdr_crossings_bsearch
|
||||||
|
= new StatLong("renderer.crossings.bsearch");
|
||||||
|
final StatLong stat_rdr_crossings_msorts
|
||||||
|
= new StatLong("renderer.crossings.msorts");
|
||||||
|
// growable arrays
|
||||||
|
final StatLong stat_array_dasher_dasher
|
||||||
|
= new StatLong("array.dasher.dasher.d_float");
|
||||||
|
final StatLong stat_array_dasher_firstSegmentsBuffer
|
||||||
|
= new StatLong("array.dasher.firstSegmentsBuffer.d_float");
|
||||||
|
final StatLong stat_array_stroker_polystack_curves
|
||||||
|
= new StatLong("array.stroker.polystack.curves.d_float");
|
||||||
|
final StatLong stat_array_stroker_polystack_curveTypes
|
||||||
|
= new StatLong("array.stroker.polystack.curveTypes.d_byte");
|
||||||
|
final StatLong stat_array_marlincache_rowAAChunk
|
||||||
|
= new StatLong("array.marlincache.rowAAChunk.d_byte");
|
||||||
|
final StatLong stat_array_marlincache_touchedTile
|
||||||
|
= new StatLong("array.marlincache.touchedTile.int");
|
||||||
|
final StatLong stat_array_renderer_alphaline
|
||||||
|
= new StatLong("array.renderer.alphaline.int");
|
||||||
|
final StatLong stat_array_renderer_crossings
|
||||||
|
= new StatLong("array.renderer.crossings.int");
|
||||||
|
final StatLong stat_array_renderer_aux_crossings
|
||||||
|
= new StatLong("array.renderer.aux_crossings.int");
|
||||||
|
final StatLong stat_array_renderer_edgeBuckets
|
||||||
|
= new StatLong("array.renderer.edgeBuckets.int");
|
||||||
|
final StatLong stat_array_renderer_edgeBucketCounts
|
||||||
|
= new StatLong("array.renderer.edgeBucketCounts.int");
|
||||||
|
final StatLong stat_array_renderer_edgePtrs
|
||||||
|
= new StatLong("array.renderer.edgePtrs.int");
|
||||||
|
final StatLong stat_array_renderer_aux_edgePtrs
|
||||||
|
= new StatLong("array.renderer.aux_edgePtrs.int");
|
||||||
|
// histograms
|
||||||
|
final Histogram hist_rdr_crossings
|
||||||
|
= new Histogram("renderer.crossings");
|
||||||
|
final Histogram hist_rdr_crossings_ratio
|
||||||
|
= new Histogram("renderer.crossings.ratio");
|
||||||
|
final Histogram hist_rdr_crossings_adds
|
||||||
|
= new Histogram("renderer.crossings.adds");
|
||||||
|
final Histogram hist_rdr_crossings_msorts
|
||||||
|
= new Histogram("renderer.crossings.msorts");
|
||||||
|
final Histogram hist_rdr_crossings_msorts_adds
|
||||||
|
= new Histogram("renderer.crossings.msorts.adds");
|
||||||
|
final Histogram hist_tile_generator_alpha
|
||||||
|
= new Histogram("tile_generator.alpha");
|
||||||
|
final Histogram hist_tile_generator_encoding
|
||||||
|
= new Histogram("tile_generator.encoding");
|
||||||
|
final Histogram hist_tile_generator_encoding_dist
|
||||||
|
= new Histogram("tile_generator.encoding.dist");
|
||||||
|
final Histogram hist_tile_generator_encoding_ratio
|
||||||
|
= new Histogram("tile_generator.encoding.ratio");
|
||||||
|
final Histogram hist_tile_generator_encoding_runLen
|
||||||
|
= new Histogram("tile_generator.encoding.runLen");
|
||||||
|
// all stats
|
||||||
|
final StatLong[] statistics = new StatLong[]{
|
||||||
|
stat_cache_rowAA,
|
||||||
|
stat_cache_rowAAChunk,
|
||||||
|
stat_cache_tiles,
|
||||||
|
stat_rdr_poly_stack_types,
|
||||||
|
stat_rdr_poly_stack_curves,
|
||||||
|
stat_rdr_addLine,
|
||||||
|
stat_rdr_addLine_skip,
|
||||||
|
stat_rdr_curveBreak,
|
||||||
|
stat_rdr_curveBreak_dec,
|
||||||
|
stat_rdr_curveBreak_inc,
|
||||||
|
stat_rdr_quadBreak,
|
||||||
|
stat_rdr_quadBreak_dec,
|
||||||
|
stat_rdr_edges,
|
||||||
|
stat_rdr_edges_count,
|
||||||
|
stat_rdr_edges_resizes,
|
||||||
|
stat_rdr_activeEdges,
|
||||||
|
stat_rdr_activeEdges_updates,
|
||||||
|
stat_rdr_activeEdges_adds,
|
||||||
|
stat_rdr_activeEdges_adds_high,
|
||||||
|
stat_rdr_crossings_updates,
|
||||||
|
stat_rdr_crossings_sorts,
|
||||||
|
stat_rdr_crossings_bsearch,
|
||||||
|
stat_rdr_crossings_msorts,
|
||||||
|
hist_rdr_crossings,
|
||||||
|
hist_rdr_crossings_ratio,
|
||||||
|
hist_rdr_crossings_adds,
|
||||||
|
hist_rdr_crossings_msorts,
|
||||||
|
hist_rdr_crossings_msorts_adds,
|
||||||
|
hist_tile_generator_alpha,
|
||||||
|
hist_tile_generator_encoding,
|
||||||
|
hist_tile_generator_encoding_dist,
|
||||||
|
hist_tile_generator_encoding_ratio,
|
||||||
|
hist_tile_generator_encoding_runLen,
|
||||||
|
stat_array_dasher_dasher,
|
||||||
|
stat_array_dasher_firstSegmentsBuffer,
|
||||||
|
stat_array_stroker_polystack_curves,
|
||||||
|
stat_array_stroker_polystack_curveTypes,
|
||||||
|
stat_array_marlincache_rowAAChunk,
|
||||||
|
stat_array_marlincache_touchedTile,
|
||||||
|
stat_array_renderer_alphaline,
|
||||||
|
stat_array_renderer_crossings,
|
||||||
|
stat_array_renderer_aux_crossings,
|
||||||
|
stat_array_renderer_edgeBuckets,
|
||||||
|
stat_array_renderer_edgeBucketCounts,
|
||||||
|
stat_array_renderer_edgePtrs,
|
||||||
|
stat_array_renderer_aux_edgePtrs
|
||||||
|
};
|
||||||
|
// monitors
|
||||||
|
final Monitor mon_pre_getAATileGenerator
|
||||||
|
= new Monitor("MarlinRenderingEngine.getAATileGenerator()");
|
||||||
|
final Monitor mon_npi_currentSegment
|
||||||
|
= new Monitor("NormalizingPathIterator.currentSegment()");
|
||||||
|
final Monitor mon_rdr_addLine
|
||||||
|
= new Monitor("Renderer.addLine()");
|
||||||
|
final Monitor mon_rdr_endRendering
|
||||||
|
= new Monitor("Renderer.endRendering()");
|
||||||
|
final Monitor mon_rdr_endRendering_Y
|
||||||
|
= new Monitor("Renderer._endRendering(Y)");
|
||||||
|
final Monitor mon_rdr_copyAARow
|
||||||
|
= new Monitor("Renderer.copyAARow()");
|
||||||
|
final Monitor mon_pipe_renderTiles
|
||||||
|
= new Monitor("AAShapePipe.renderTiles()");
|
||||||
|
final Monitor mon_ptg_getAlpha
|
||||||
|
= new Monitor("MarlinTileGenerator.getAlpha()");
|
||||||
|
final Monitor mon_debug
|
||||||
|
= new Monitor("DEBUG()");
|
||||||
|
// all monitors
|
||||||
|
final Monitor[] monitors = new Monitor[]{
|
||||||
|
mon_pre_getAATileGenerator,
|
||||||
|
mon_npi_currentSegment,
|
||||||
|
mon_rdr_addLine,
|
||||||
|
mon_rdr_endRendering,
|
||||||
|
mon_rdr_endRendering_Y,
|
||||||
|
mon_rdr_copyAARow,
|
||||||
|
mon_pipe_renderTiles,
|
||||||
|
mon_ptg_getAlpha,
|
||||||
|
mon_debug
|
||||||
|
};
|
||||||
|
|
||||||
|
private RendererStats() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
Runtime.getRuntime().addShutdownHook(new Thread() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
dump();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (useDumpThread) {
|
||||||
|
final Timer statTimer = new Timer("RendererStats");
|
||||||
|
statTimer.scheduleAtFixedRate(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
dump();
|
||||||
|
}
|
||||||
|
}, statDump, statDump);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void dump() {
|
||||||
|
if (doStats) {
|
||||||
|
ArrayCache.dumpStats();
|
||||||
|
}
|
||||||
|
final RendererContext[] all = allContexts.toArray(
|
||||||
|
new RendererContext[allContexts.size()]);
|
||||||
|
for (RendererContext rdrCtx : all) {
|
||||||
|
logInfo("RendererContext: " + rdrCtx.name);
|
||||||
|
|
||||||
|
if (doMonitors) {
|
||||||
|
for (Monitor monitor : monitors) {
|
||||||
|
if (monitor.count != 0) {
|
||||||
|
logInfo(monitor.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// As getAATileGenerator percents:
|
||||||
|
final long total = mon_pre_getAATileGenerator.sum;
|
||||||
|
if (total != 0L) {
|
||||||
|
for (Monitor monitor : monitors) {
|
||||||
|
logInfo(monitor.name + " : "
|
||||||
|
+ ((100d * monitor.sum) / total) + " %");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (doFlushMonitors) {
|
||||||
|
for (Monitor m : monitors) {
|
||||||
|
m.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doStats) {
|
||||||
|
for (StatLong stat : statistics) {
|
||||||
|
if (stat.count != 0) {
|
||||||
|
logInfo(stat.toString());
|
||||||
|
stat.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// IntArrayCaches stats:
|
||||||
|
final RendererContext.ArrayCachesHolder holder
|
||||||
|
= rdrCtx.getArrayCachesHolder();
|
||||||
|
|
||||||
|
logInfo("Array caches for thread: " + rdrCtx.name);
|
||||||
|
|
||||||
|
for (IntArrayCache cache : holder.intArrayCaches) {
|
||||||
|
cache.dumpStats();
|
||||||
|
}
|
||||||
|
|
||||||
|
logInfo("Dirty Array caches for thread: " + rdrCtx.name);
|
||||||
|
|
||||||
|
for (IntArrayCache cache : holder.dirtyIntArrayCaches) {
|
||||||
|
cache.dumpStats();
|
||||||
|
}
|
||||||
|
for (FloatArrayCache cache : holder.dirtyFloatArrayCaches) {
|
||||||
|
cache.dumpStats();
|
||||||
|
}
|
||||||
|
for (ByteArrayCache cache : holder.dirtyByteArrayCaches) {
|
||||||
|
cache.dumpStats();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1388
jdk/src/java.desktop/share/classes/sun/java2d/marlin/Stroker.java
Normal file
1388
jdk/src/java.desktop/share/classes/sun/java2d/marlin/Stroker.java
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,507 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sun.java2d.marlin;
|
||||||
|
|
||||||
|
import sun.awt.geom.PathConsumer2D;
|
||||||
|
import java.awt.geom.AffineTransform;
|
||||||
|
import java.awt.geom.Path2D;
|
||||||
|
|
||||||
|
final class TransformingPathConsumer2D {
|
||||||
|
|
||||||
|
TransformingPathConsumer2D() {
|
||||||
|
// used by RendererContext
|
||||||
|
}
|
||||||
|
|
||||||
|
// recycled PathConsumer2D instance from transformConsumer()
|
||||||
|
private final Path2DWrapper wp_Path2DWrapper = new Path2DWrapper();
|
||||||
|
|
||||||
|
PathConsumer2D wrapPath2d(Path2D.Float p2d)
|
||||||
|
{
|
||||||
|
return wp_Path2DWrapper.init(p2d);
|
||||||
|
}
|
||||||
|
|
||||||
|
// recycled PathConsumer2D instances from transformConsumer()
|
||||||
|
private final TranslateFilter tx_TranslateFilter = new TranslateFilter();
|
||||||
|
private final DeltaScaleFilter tx_DeltaScaleFilter = new DeltaScaleFilter();
|
||||||
|
private final ScaleFilter tx_ScaleFilter = new ScaleFilter();
|
||||||
|
private final DeltaTransformFilter tx_DeltaTransformFilter = new DeltaTransformFilter();
|
||||||
|
private final TransformFilter tx_TransformFilter = new TransformFilter();
|
||||||
|
|
||||||
|
PathConsumer2D transformConsumer(PathConsumer2D out,
|
||||||
|
AffineTransform at)
|
||||||
|
{
|
||||||
|
if (at == null) {
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
float mxx = (float) at.getScaleX();
|
||||||
|
float mxy = (float) at.getShearX();
|
||||||
|
float mxt = (float) at.getTranslateX();
|
||||||
|
float myx = (float) at.getShearY();
|
||||||
|
float myy = (float) at.getScaleY();
|
||||||
|
float myt = (float) at.getTranslateY();
|
||||||
|
if (mxy == 0f && myx == 0f) {
|
||||||
|
if (mxx == 1f && myy == 1f) {
|
||||||
|
if (mxt == 0f && myt == 0f) {
|
||||||
|
return out;
|
||||||
|
} else {
|
||||||
|
return tx_TranslateFilter.init(out, mxt, myt);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (mxt == 0f && myt == 0f) {
|
||||||
|
return tx_DeltaScaleFilter.init(out, mxx, myy);
|
||||||
|
} else {
|
||||||
|
return tx_ScaleFilter.init(out, mxx, myy, mxt, myt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (mxt == 0f && myt == 0f) {
|
||||||
|
return tx_DeltaTransformFilter.init(out, mxx, mxy, myx, myy);
|
||||||
|
} else {
|
||||||
|
return tx_TransformFilter.init(out, mxx, mxy, mxt, myx, myy, myt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// recycled PathConsumer2D instances from deltaTransformConsumer()
|
||||||
|
private final DeltaScaleFilter dt_DeltaScaleFilter = new DeltaScaleFilter();
|
||||||
|
private final DeltaTransformFilter dt_DeltaTransformFilter = new DeltaTransformFilter();
|
||||||
|
|
||||||
|
PathConsumer2D deltaTransformConsumer(PathConsumer2D out,
|
||||||
|
AffineTransform at)
|
||||||
|
{
|
||||||
|
if (at == null) {
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
float mxx = (float) at.getScaleX();
|
||||||
|
float mxy = (float) at.getShearX();
|
||||||
|
float myx = (float) at.getShearY();
|
||||||
|
float myy = (float) at.getScaleY();
|
||||||
|
if (mxy == 0f && myx == 0f) {
|
||||||
|
if (mxx == 1f && myy == 1f) {
|
||||||
|
return out;
|
||||||
|
} else {
|
||||||
|
return dt_DeltaScaleFilter.init(out, mxx, myy);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return dt_DeltaTransformFilter.init(out, mxx, mxy, myx, myy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// recycled PathConsumer2D instances from inverseDeltaTransformConsumer()
|
||||||
|
private final DeltaScaleFilter iv_DeltaScaleFilter = new DeltaScaleFilter();
|
||||||
|
private final DeltaTransformFilter iv_DeltaTransformFilter = new DeltaTransformFilter();
|
||||||
|
|
||||||
|
PathConsumer2D inverseDeltaTransformConsumer(PathConsumer2D out,
|
||||||
|
AffineTransform at)
|
||||||
|
{
|
||||||
|
if (at == null) {
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
float mxx = (float) at.getScaleX();
|
||||||
|
float mxy = (float) at.getShearX();
|
||||||
|
float myx = (float) at.getShearY();
|
||||||
|
float myy = (float) at.getScaleY();
|
||||||
|
if (mxy == 0f && myx == 0f) {
|
||||||
|
if (mxx == 1f && myy == 1f) {
|
||||||
|
return out;
|
||||||
|
} else {
|
||||||
|
return iv_DeltaScaleFilter.init(out, 1.0f/mxx, 1.0f/myy);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
float det = mxx * myy - mxy * myx;
|
||||||
|
return iv_DeltaTransformFilter.init(out,
|
||||||
|
myy / det,
|
||||||
|
-mxy / det,
|
||||||
|
-myx / det,
|
||||||
|
mxx / det);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class TranslateFilter implements PathConsumer2D {
|
||||||
|
private PathConsumer2D out;
|
||||||
|
private float tx, ty;
|
||||||
|
|
||||||
|
TranslateFilter() {}
|
||||||
|
|
||||||
|
TranslateFilter init(PathConsumer2D out,
|
||||||
|
float tx, float ty)
|
||||||
|
{
|
||||||
|
this.out = out;
|
||||||
|
this.tx = tx;
|
||||||
|
this.ty = ty;
|
||||||
|
return this; // fluent API
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void moveTo(float x0, float y0) {
|
||||||
|
out.moveTo(x0 + tx, y0 + ty);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void lineTo(float x1, float y1) {
|
||||||
|
out.lineTo(x1 + tx, y1 + ty);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void quadTo(float x1, float y1,
|
||||||
|
float x2, float y2)
|
||||||
|
{
|
||||||
|
out.quadTo(x1 + tx, y1 + ty,
|
||||||
|
x2 + tx, y2 + ty);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void curveTo(float x1, float y1,
|
||||||
|
float x2, float y2,
|
||||||
|
float x3, float y3)
|
||||||
|
{
|
||||||
|
out.curveTo(x1 + tx, y1 + ty,
|
||||||
|
x2 + tx, y2 + ty,
|
||||||
|
x3 + tx, y3 + ty);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void closePath() {
|
||||||
|
out.closePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pathDone() {
|
||||||
|
out.pathDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getNativeConsumer() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class ScaleFilter implements PathConsumer2D {
|
||||||
|
private PathConsumer2D out;
|
||||||
|
private float sx, sy, tx, ty;
|
||||||
|
|
||||||
|
ScaleFilter() {}
|
||||||
|
|
||||||
|
ScaleFilter init(PathConsumer2D out,
|
||||||
|
float sx, float sy,
|
||||||
|
float tx, float ty)
|
||||||
|
{
|
||||||
|
this.out = out;
|
||||||
|
this.sx = sx;
|
||||||
|
this.sy = sy;
|
||||||
|
this.tx = tx;
|
||||||
|
this.ty = ty;
|
||||||
|
return this; // fluent API
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void moveTo(float x0, float y0) {
|
||||||
|
out.moveTo(x0 * sx + tx, y0 * sy + ty);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void lineTo(float x1, float y1) {
|
||||||
|
out.lineTo(x1 * sx + tx, y1 * sy + ty);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void quadTo(float x1, float y1,
|
||||||
|
float x2, float y2)
|
||||||
|
{
|
||||||
|
out.quadTo(x1 * sx + tx, y1 * sy + ty,
|
||||||
|
x2 * sx + tx, y2 * sy + ty);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void curveTo(float x1, float y1,
|
||||||
|
float x2, float y2,
|
||||||
|
float x3, float y3)
|
||||||
|
{
|
||||||
|
out.curveTo(x1 * sx + tx, y1 * sy + ty,
|
||||||
|
x2 * sx + tx, y2 * sy + ty,
|
||||||
|
x3 * sx + tx, y3 * sy + ty);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void closePath() {
|
||||||
|
out.closePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pathDone() {
|
||||||
|
out.pathDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getNativeConsumer() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class TransformFilter implements PathConsumer2D {
|
||||||
|
private PathConsumer2D out;
|
||||||
|
private float mxx, mxy, mxt, myx, myy, myt;
|
||||||
|
|
||||||
|
TransformFilter() {}
|
||||||
|
|
||||||
|
TransformFilter init(PathConsumer2D out,
|
||||||
|
float mxx, float mxy, float mxt,
|
||||||
|
float myx, float myy, float myt)
|
||||||
|
{
|
||||||
|
this.out = out;
|
||||||
|
this.mxx = mxx;
|
||||||
|
this.mxy = mxy;
|
||||||
|
this.mxt = mxt;
|
||||||
|
this.myx = myx;
|
||||||
|
this.myy = myy;
|
||||||
|
this.myt = myt;
|
||||||
|
return this; // fluent API
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void moveTo(float x0, float y0) {
|
||||||
|
out.moveTo(x0 * mxx + y0 * mxy + mxt,
|
||||||
|
x0 * myx + y0 * myy + myt);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void lineTo(float x1, float y1) {
|
||||||
|
out.lineTo(x1 * mxx + y1 * mxy + mxt,
|
||||||
|
x1 * myx + y1 * myy + myt);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void quadTo(float x1, float y1,
|
||||||
|
float x2, float y2)
|
||||||
|
{
|
||||||
|
out.quadTo(x1 * mxx + y1 * mxy + mxt,
|
||||||
|
x1 * myx + y1 * myy + myt,
|
||||||
|
x2 * mxx + y2 * mxy + mxt,
|
||||||
|
x2 * myx + y2 * myy + myt);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void curveTo(float x1, float y1,
|
||||||
|
float x2, float y2,
|
||||||
|
float x3, float y3)
|
||||||
|
{
|
||||||
|
out.curveTo(x1 * mxx + y1 * mxy + mxt,
|
||||||
|
x1 * myx + y1 * myy + myt,
|
||||||
|
x2 * mxx + y2 * mxy + mxt,
|
||||||
|
x2 * myx + y2 * myy + myt,
|
||||||
|
x3 * mxx + y3 * mxy + mxt,
|
||||||
|
x3 * myx + y3 * myy + myt);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void closePath() {
|
||||||
|
out.closePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pathDone() {
|
||||||
|
out.pathDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getNativeConsumer() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class DeltaScaleFilter implements PathConsumer2D {
|
||||||
|
private PathConsumer2D out;
|
||||||
|
private float sx, sy;
|
||||||
|
|
||||||
|
DeltaScaleFilter() {}
|
||||||
|
|
||||||
|
DeltaScaleFilter init(PathConsumer2D out,
|
||||||
|
float mxx, float myy)
|
||||||
|
{
|
||||||
|
this.out = out;
|
||||||
|
sx = mxx;
|
||||||
|
sy = myy;
|
||||||
|
return this; // fluent API
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void moveTo(float x0, float y0) {
|
||||||
|
out.moveTo(x0 * sx, y0 * sy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void lineTo(float x1, float y1) {
|
||||||
|
out.lineTo(x1 * sx, y1 * sy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void quadTo(float x1, float y1,
|
||||||
|
float x2, float y2)
|
||||||
|
{
|
||||||
|
out.quadTo(x1 * sx, y1 * sy,
|
||||||
|
x2 * sx, y2 * sy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void curveTo(float x1, float y1,
|
||||||
|
float x2, float y2,
|
||||||
|
float x3, float y3)
|
||||||
|
{
|
||||||
|
out.curveTo(x1 * sx, y1 * sy,
|
||||||
|
x2 * sx, y2 * sy,
|
||||||
|
x3 * sx, y3 * sy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void closePath() {
|
||||||
|
out.closePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pathDone() {
|
||||||
|
out.pathDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getNativeConsumer() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class DeltaTransformFilter implements PathConsumer2D {
|
||||||
|
private PathConsumer2D out;
|
||||||
|
private float mxx, mxy, myx, myy;
|
||||||
|
|
||||||
|
DeltaTransformFilter() {}
|
||||||
|
|
||||||
|
DeltaTransformFilter init(PathConsumer2D out,
|
||||||
|
float mxx, float mxy,
|
||||||
|
float myx, float myy)
|
||||||
|
{
|
||||||
|
this.out = out;
|
||||||
|
this.mxx = mxx;
|
||||||
|
this.mxy = mxy;
|
||||||
|
this.myx = myx;
|
||||||
|
this.myy = myy;
|
||||||
|
return this; // fluent API
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void moveTo(float x0, float y0) {
|
||||||
|
out.moveTo(x0 * mxx + y0 * mxy,
|
||||||
|
x0 * myx + y0 * myy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void lineTo(float x1, float y1) {
|
||||||
|
out.lineTo(x1 * mxx + y1 * mxy,
|
||||||
|
x1 * myx + y1 * myy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void quadTo(float x1, float y1,
|
||||||
|
float x2, float y2)
|
||||||
|
{
|
||||||
|
out.quadTo(x1 * mxx + y1 * mxy,
|
||||||
|
x1 * myx + y1 * myy,
|
||||||
|
x2 * mxx + y2 * mxy,
|
||||||
|
x2 * myx + y2 * myy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void curveTo(float x1, float y1,
|
||||||
|
float x2, float y2,
|
||||||
|
float x3, float y3)
|
||||||
|
{
|
||||||
|
out.curveTo(x1 * mxx + y1 * mxy,
|
||||||
|
x1 * myx + y1 * myy,
|
||||||
|
x2 * mxx + y2 * mxy,
|
||||||
|
x2 * myx + y2 * myy,
|
||||||
|
x3 * mxx + y3 * mxy,
|
||||||
|
x3 * myx + y3 * myy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void closePath() {
|
||||||
|
out.closePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pathDone() {
|
||||||
|
out.pathDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getNativeConsumer() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class Path2DWrapper implements PathConsumer2D {
|
||||||
|
private Path2D.Float p2d;
|
||||||
|
|
||||||
|
Path2DWrapper() {}
|
||||||
|
|
||||||
|
Path2DWrapper init(Path2D.Float p2d) {
|
||||||
|
this.p2d = p2d;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void moveTo(float x0, float y0) {
|
||||||
|
p2d.moveTo(x0, y0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void lineTo(float x1, float y1) {
|
||||||
|
p2d.lineTo(x1, y1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void closePath() {
|
||||||
|
p2d.closePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pathDone() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void curveTo(float x1, float y1,
|
||||||
|
float x2, float y2,
|
||||||
|
float x3, float y3)
|
||||||
|
{
|
||||||
|
p2d.curveTo(x1, y1, x2, y2, x3, y3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void quadTo(float x1, float y1, float x2, float y2) {
|
||||||
|
p2d.quadTo(x1, y1, x2, y2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getNativeConsumer() {
|
||||||
|
throw new InternalError("Not using a native peer");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sun.java2d.marlin;
|
||||||
|
|
||||||
|
public final class Version {
|
||||||
|
|
||||||
|
private static final String version = "marlin-0.7.2-Unsafe-OpenJDK";
|
||||||
|
|
||||||
|
public static String getVersion() {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Version() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sun.java2d.marlin.stats;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic histogram based on long statistics
|
||||||
|
*/
|
||||||
|
public final class Histogram extends StatLong {
|
||||||
|
|
||||||
|
static final int BUCKET = 2;
|
||||||
|
static final int MAX = 20;
|
||||||
|
static final int LAST = MAX - 1;
|
||||||
|
static final int[] STEPS = new int[MAX];
|
||||||
|
|
||||||
|
static {
|
||||||
|
STEPS[0] = 0;
|
||||||
|
STEPS[1] = 1;
|
||||||
|
|
||||||
|
for (int i = 2; i < MAX; i++) {
|
||||||
|
STEPS[i] = STEPS[i - 1] * BUCKET;
|
||||||
|
}
|
||||||
|
// System.out.println("Histogram.STEPS = " + Arrays.toString(STEPS));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int bucket(int val) {
|
||||||
|
for (int i = 1; i < MAX; i++) {
|
||||||
|
if (val < STEPS[i]) {
|
||||||
|
return i - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return LAST;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final StatLong[] stats = new StatLong[MAX];
|
||||||
|
|
||||||
|
public Histogram(final String name) {
|
||||||
|
super(name);
|
||||||
|
for (int i = 0; i < MAX; i++) {
|
||||||
|
stats[i] = new StatLong(String.format("%5s .. %5s", STEPS[i],
|
||||||
|
((i + 1 < MAX) ? STEPS[i + 1] : "~")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
super.reset();
|
||||||
|
for (int i = 0; i < MAX; i++) {
|
||||||
|
stats[i].reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void add(int val) {
|
||||||
|
super.add(val);
|
||||||
|
stats[bucket(val)].add(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void add(long val) {
|
||||||
|
add((int) val);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
final StringBuilder sb = new StringBuilder(2048);
|
||||||
|
super.toString(sb).append(" { ");
|
||||||
|
|
||||||
|
for (int i = 0; i < MAX; i++) {
|
||||||
|
if (stats[i].count != 0l) {
|
||||||
|
sb.append("\n ").append(stats[i].toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.append(" }").toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sun.java2d.marlin.stats;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic monitor ie gathers time statistics as nanos.
|
||||||
|
*/
|
||||||
|
public final class Monitor extends StatLong {
|
||||||
|
|
||||||
|
private static final long INVALID = -1L;
|
||||||
|
|
||||||
|
private long start = INVALID;
|
||||||
|
|
||||||
|
public Monitor(final String name) {
|
||||||
|
super(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() {
|
||||||
|
start = System.nanoTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
final long elapsed = System.nanoTime() - start;
|
||||||
|
if (start != INVALID && elapsed > 0l) {
|
||||||
|
add(elapsed);
|
||||||
|
}
|
||||||
|
start = INVALID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,97 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sun.java2d.marlin.stats;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Statistics as long values
|
||||||
|
*/
|
||||||
|
public class StatLong {
|
||||||
|
|
||||||
|
public final String name;
|
||||||
|
public long count = 0l;
|
||||||
|
public long sum = 0l;
|
||||||
|
public long min = Integer.MAX_VALUE;
|
||||||
|
public long max = Integer.MIN_VALUE;
|
||||||
|
|
||||||
|
public StatLong(final String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
count = 0l;
|
||||||
|
sum = 0l;
|
||||||
|
min = Integer.MAX_VALUE;
|
||||||
|
max = Integer.MIN_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(final int val) {
|
||||||
|
count++;
|
||||||
|
sum += val;
|
||||||
|
if (val < min) {
|
||||||
|
min = val;
|
||||||
|
}
|
||||||
|
if (val > max) {
|
||||||
|
max = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(final long val) {
|
||||||
|
count++;
|
||||||
|
sum += val;
|
||||||
|
if (val < min) {
|
||||||
|
min = val;
|
||||||
|
}
|
||||||
|
if (val > max) {
|
||||||
|
max = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
final StringBuilder sb = new StringBuilder(128);
|
||||||
|
toString(sb);
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public final StringBuilder toString(final StringBuilder sb) {
|
||||||
|
sb.append(name).append('[').append(count);
|
||||||
|
sb.append("] sum: ").append(sum).append(" avg: ");
|
||||||
|
sb.append(trimTo3Digits(((double) sum) / count));
|
||||||
|
sb.append(" [").append(min).append(" | ").append(max).append("]");
|
||||||
|
return sb;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adjust the given double value to keep only 3 decimal digits
|
||||||
|
*
|
||||||
|
* @param value value to adjust
|
||||||
|
* @return double value with only 3 decimal digits
|
||||||
|
*/
|
||||||
|
public static double trimTo3Digits(final double value) {
|
||||||
|
return ((long) (1e3d * value)) / 1e3d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -22,14 +22,12 @@
|
|||||||
* or visit www.oracle.com if you need additional information or have any
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
* questions.
|
* questions.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package sun.java2d.pipe;
|
package sun.java2d.pipe;
|
||||||
|
|
||||||
import java.awt.BasicStroke;
|
import java.awt.BasicStroke;
|
||||||
import java.awt.Rectangle;
|
import java.awt.Rectangle;
|
||||||
import java.awt.Shape;
|
import java.awt.Shape;
|
||||||
import java.awt.geom.Rectangle2D;
|
import java.awt.geom.Rectangle2D;
|
||||||
import java.awt.geom.PathIterator;
|
|
||||||
import sun.awt.SunHints;
|
import sun.awt.SunHints;
|
||||||
import sun.java2d.SunGraphics2D;
|
import sun.java2d.SunGraphics2D;
|
||||||
|
|
||||||
@ -45,6 +43,15 @@ public class AAShapePipe
|
|||||||
{
|
{
|
||||||
static RenderingEngine renderengine = RenderingEngine.getInstance();
|
static RenderingEngine renderengine = RenderingEngine.getInstance();
|
||||||
|
|
||||||
|
// Per-thread TileState (~1K very small so do not use any Weak Reference)
|
||||||
|
private static final ThreadLocal<TileState> tileStateThreadLocal =
|
||||||
|
new ThreadLocal<TileState>() {
|
||||||
|
@Override
|
||||||
|
protected TileState initialValue() {
|
||||||
|
return new TileState();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
CompositePipe outpipe;
|
CompositePipe outpipe;
|
||||||
|
|
||||||
public AAShapePipe(CompositePipe pipe) {
|
public AAShapePipe(CompositePipe pipe) {
|
||||||
@ -68,20 +75,6 @@ public class AAShapePipe
|
|||||||
renderPath(sg, s, null);
|
renderPath(sg, s, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Rectangle2D computeBBox(double ux1, double uy1,
|
|
||||||
double ux2, double uy2)
|
|
||||||
{
|
|
||||||
if ((ux2 -= ux1) < 0) {
|
|
||||||
ux1 += ux2;
|
|
||||||
ux2 = -ux2;
|
|
||||||
}
|
|
||||||
if ((uy2 -= uy1) < 0) {
|
|
||||||
uy1 += uy2;
|
|
||||||
uy2 = -uy2;
|
|
||||||
}
|
|
||||||
return new Rectangle2D.Double(ux1, uy1, ux2, uy2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void fillParallelogram(SunGraphics2D sg,
|
public void fillParallelogram(SunGraphics2D sg,
|
||||||
double ux1, double uy1,
|
double ux1, double uy1,
|
||||||
double ux2, double uy2,
|
double ux2, double uy2,
|
||||||
@ -90,7 +83,9 @@ public class AAShapePipe
|
|||||||
double dx2, double dy2)
|
double dx2, double dy2)
|
||||||
{
|
{
|
||||||
Region clip = sg.getCompClip();
|
Region clip = sg.getCompClip();
|
||||||
int abox[] = new int[4];
|
final TileState ts = tileStateThreadLocal.get();
|
||||||
|
final int[] abox = ts.abox;
|
||||||
|
|
||||||
AATileGenerator aatg =
|
AATileGenerator aatg =
|
||||||
renderengine.getAATileGenerator(x, y, dx1, dy1, dx2, dy2, 0, 0,
|
renderengine.getAATileGenerator(x, y, dx1, dy1, dx2, dy2, 0, 0,
|
||||||
clip, abox);
|
clip, abox);
|
||||||
@ -99,7 +94,7 @@ public class AAShapePipe
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderTiles(sg, computeBBox(ux1, uy1, ux2, uy2), aatg, abox);
|
renderTiles(sg, ts.computeBBox(ux1, uy1, ux2, uy2), aatg, abox, ts);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void drawParallelogram(SunGraphics2D sg,
|
public void drawParallelogram(SunGraphics2D sg,
|
||||||
@ -111,7 +106,9 @@ public class AAShapePipe
|
|||||||
double lw1, double lw2)
|
double lw1, double lw2)
|
||||||
{
|
{
|
||||||
Region clip = sg.getCompClip();
|
Region clip = sg.getCompClip();
|
||||||
int abox[] = new int[4];
|
final TileState ts = tileStateThreadLocal.get();
|
||||||
|
final int[] abox = ts.abox;
|
||||||
|
|
||||||
AATileGenerator aatg =
|
AATileGenerator aatg =
|
||||||
renderengine.getAATileGenerator(x, y, dx1, dy1, dx2, dy2, lw1, lw2,
|
renderengine.getAATileGenerator(x, y, dx1, dy1, dx2, dy2, lw1, lw2,
|
||||||
clip, abox);
|
clip, abox);
|
||||||
@ -122,23 +119,7 @@ public class AAShapePipe
|
|||||||
|
|
||||||
// Note that bbox is of the original shape, not the wide path.
|
// Note that bbox is of the original shape, not the wide path.
|
||||||
// This is appropriate for handing to Paint methods...
|
// This is appropriate for handing to Paint methods...
|
||||||
renderTiles(sg, computeBBox(ux1, uy1, ux2, uy2), aatg, abox);
|
renderTiles(sg, ts.computeBBox(ux1, uy1, ux2, uy2), aatg, abox, ts);
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] theTile;
|
|
||||||
|
|
||||||
private static synchronized byte[] getAlphaTile(int len) {
|
|
||||||
byte[] t = theTile;
|
|
||||||
if (t == null || t.length < len) {
|
|
||||||
t = new byte[len];
|
|
||||||
} else {
|
|
||||||
theTile = null;
|
|
||||||
}
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static synchronized void dropAlphaTile(byte[] t) {
|
|
||||||
theTile = t;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void renderPath(SunGraphics2D sg, Shape s, BasicStroke bs) {
|
public void renderPath(SunGraphics2D sg, Shape s, BasicStroke bs) {
|
||||||
@ -147,7 +128,9 @@ public class AAShapePipe
|
|||||||
boolean thin = (sg.strokeState <= SunGraphics2D.STROKE_THINDASHED);
|
boolean thin = (sg.strokeState <= SunGraphics2D.STROKE_THINDASHED);
|
||||||
|
|
||||||
Region clip = sg.getCompClip();
|
Region clip = sg.getCompClip();
|
||||||
int abox[] = new int[4];
|
final TileState ts = tileStateThreadLocal.get();
|
||||||
|
final int[] abox = ts.abox;
|
||||||
|
|
||||||
AATileGenerator aatg =
|
AATileGenerator aatg =
|
||||||
renderengine.getAATileGenerator(s, sg.transform, clip,
|
renderengine.getAATileGenerator(s, sg.transform, clip,
|
||||||
bs, thin, adjust, abox);
|
bs, thin, adjust, abox);
|
||||||
@ -156,31 +139,30 @@ public class AAShapePipe
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderTiles(sg, s, aatg, abox);
|
renderTiles(sg, s, aatg, abox, ts);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void renderTiles(SunGraphics2D sg, Shape s,
|
public void renderTiles(SunGraphics2D sg, Shape s,
|
||||||
AATileGenerator aatg, int abox[])
|
AATileGenerator aatg, int abox[], TileState ts)
|
||||||
{
|
{
|
||||||
Object context = null;
|
Object context = null;
|
||||||
byte alpha[] = null;
|
|
||||||
try {
|
try {
|
||||||
context = outpipe.startSequence(sg, s,
|
context = outpipe.startSequence(sg, s,
|
||||||
new Rectangle(abox[0], abox[1],
|
ts.computeDevBox(abox),
|
||||||
abox[2] - abox[0],
|
|
||||||
abox[3] - abox[1]),
|
|
||||||
abox);
|
abox);
|
||||||
|
|
||||||
int tw = aatg.getTileWidth();
|
final int tw = aatg.getTileWidth();
|
||||||
int th = aatg.getTileHeight();
|
final int th = aatg.getTileHeight();
|
||||||
alpha = getAlphaTile(tw * th);
|
|
||||||
|
|
||||||
|
// get tile from thread local storage:
|
||||||
|
final byte[] alpha = ts.getAlphaTile(tw * th);
|
||||||
byte[] atile;
|
byte[] atile;
|
||||||
|
|
||||||
for (int y = abox[1]; y < abox[3]; y += th) {
|
for (int y = abox[1]; y < abox[3]; y += th) {
|
||||||
|
int h = Math.min(th, abox[3] - y);
|
||||||
|
|
||||||
for (int x = abox[0]; x < abox[2]; x += tw) {
|
for (int x = abox[0]; x < abox[2]; x += tw) {
|
||||||
int w = Math.min(tw, abox[2] - x);
|
int w = Math.min(tw, abox[2] - x);
|
||||||
int h = Math.min(th, abox[3] - y);
|
|
||||||
|
|
||||||
int a = aatg.getTypicalAlpha();
|
int a = aatg.getTypicalAlpha();
|
||||||
if (a == 0x00 ||
|
if (a == 0x00 ||
|
||||||
@ -207,9 +189,56 @@ public class AAShapePipe
|
|||||||
if (context != null) {
|
if (context != null) {
|
||||||
outpipe.endSequence(context);
|
outpipe.endSequence(context);
|
||||||
}
|
}
|
||||||
if (alpha != null) {
|
|
||||||
dropAlphaTile(alpha);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tile state used by AAShapePipe
|
||||||
|
static final class TileState {
|
||||||
|
// cached tile (32 x 32 tile by default)
|
||||||
|
private byte[] theTile = new byte[32 * 32];
|
||||||
|
// dirty aabox array
|
||||||
|
final int[] abox = new int[4];
|
||||||
|
// dirty bbox rectangle
|
||||||
|
private final Rectangle dev = new Rectangle();
|
||||||
|
// dirty bbox rectangle2D.Double
|
||||||
|
private final Rectangle2D.Double bbox2D = new Rectangle2D.Double();
|
||||||
|
|
||||||
|
byte[] getAlphaTile(int len) {
|
||||||
|
byte[] t = theTile;
|
||||||
|
if (t.length < len) {
|
||||||
|
// create a larger tile and may free current theTile (too small)
|
||||||
|
theTile = t = new byte[len];
|
||||||
|
}
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle computeDevBox(final int[] abox) {
|
||||||
|
final Rectangle box = this.dev;
|
||||||
|
box.x = abox[0];
|
||||||
|
box.y = abox[1];
|
||||||
|
box.width = abox[2] - abox[0];
|
||||||
|
box.height = abox[3] - abox[1];
|
||||||
|
return box;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle2D computeBBox(double ux1, double uy1,
|
||||||
|
double ux2, double uy2)
|
||||||
|
{
|
||||||
|
if ((ux2 -= ux1) < 0.0) {
|
||||||
|
ux1 += ux2;
|
||||||
|
ux2 = -ux2;
|
||||||
|
}
|
||||||
|
if ((uy2 -= uy1) < 0.0) {
|
||||||
|
uy1 += uy2;
|
||||||
|
uy2 = -uy2;
|
||||||
|
}
|
||||||
|
final Rectangle2D.Double box = this.bbox2D;
|
||||||
|
box.x = ux1;
|
||||||
|
box.y = uy1;
|
||||||
|
box.width = ux2;
|
||||||
|
box.height = uy2;
|
||||||
|
return box;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -96,9 +96,14 @@ public abstract class RenderingEngine {
|
|||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* If no specific {@code RenderingEngine} is specified on the command
|
* If no specific {@code RenderingEngine} is specified on the command
|
||||||
* or Ductus renderer is specified, it will first attempt loading the
|
* line or the requested class fails to load, then the Marlin
|
||||||
* sun.dc.DuctusRenderingEngine class using Class.forName, if that
|
* renderer will be used as the default.
|
||||||
* is not found, then it will look for Pisces.
|
* <p>
|
||||||
|
* A printout of which RenderingEngine is loaded and used can be
|
||||||
|
* enabled by specifying the runtime flag:
|
||||||
|
* <pre>
|
||||||
|
* java -Dsun.java2d.renderer.verbose=true
|
||||||
|
* </pre>
|
||||||
* <p>
|
* <p>
|
||||||
* Runtime tracing of the actions of the {@code RenderingEngine}
|
* Runtime tracing of the actions of the {@code RenderingEngine}
|
||||||
* can be enabled by specifying the runtime flag:
|
* can be enabled by specifying the runtime flag:
|
||||||
@ -113,20 +118,23 @@ public abstract class RenderingEngine {
|
|||||||
return reImpl;
|
return reImpl;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Look first for ductus or an app-override renderer,
|
/* Look first for an app-override renderer,
|
||||||
* if not specified or present, then look for pisces.
|
* if not specified or present, then look for marlin.
|
||||||
*/
|
*/
|
||||||
final String ductusREClass = "sun.dc.DuctusRenderingEngine";
|
|
||||||
final String piscesREClass = "sun.java2d.pisces.PiscesRenderingEngine";
|
|
||||||
GetPropertyAction gpa =
|
GetPropertyAction gpa =
|
||||||
new GetPropertyAction("sun.java2d.renderer", ductusREClass);
|
new GetPropertyAction("sun.java2d.renderer");
|
||||||
String reClass = AccessController.doPrivileged(gpa);
|
String reClass = AccessController.doPrivileged(gpa);
|
||||||
try {
|
if (reClass != null) {
|
||||||
Class<?> cls = Class.forName(reClass);
|
|
||||||
reImpl = (RenderingEngine) cls.newInstance();
|
|
||||||
} catch (ReflectiveOperationException ignored0) {
|
|
||||||
try {
|
try {
|
||||||
Class<?> cls = Class.forName(piscesREClass);
|
Class<?> cls = Class.forName(reClass);
|
||||||
|
reImpl = (RenderingEngine) cls.newInstance();
|
||||||
|
} catch (ReflectiveOperationException ignored0) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (reImpl == null) {
|
||||||
|
final String marlinREClass = "sun.java2d.marlin.MarlinRenderingEngine";
|
||||||
|
try {
|
||||||
|
Class<?> cls = Class.forName(marlinREClass);
|
||||||
reImpl = (RenderingEngine) cls.newInstance();
|
reImpl = (RenderingEngine) cls.newInstance();
|
||||||
} catch (ReflectiveOperationException ignored1) {
|
} catch (ReflectiveOperationException ignored1) {
|
||||||
}
|
}
|
||||||
@ -136,6 +144,12 @@ public abstract class RenderingEngine {
|
|||||||
throw new InternalError("No RenderingEngine module found");
|
throw new InternalError("No RenderingEngine module found");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gpa = new GetPropertyAction("sun.java2d.renderer.verbose");
|
||||||
|
String verbose = AccessController.doPrivileged(gpa);
|
||||||
|
if (verbose != null && verbose.startsWith("t")) {
|
||||||
|
System.out.println("RenderingEngine = "+reImpl);
|
||||||
|
}
|
||||||
|
|
||||||
gpa = new GetPropertyAction("sun.java2d.renderer.trace");
|
gpa = new GetPropertyAction("sun.java2d.renderer.trace");
|
||||||
String reTrace = AccessController.doPrivileged(gpa);
|
String reTrace = AccessController.doPrivileged(gpa);
|
||||||
if (reTrace != null) {
|
if (reTrace != null) {
|
||||||
|
249
jdk/test/sun/java2d/marlin/CeilAndFloorTests.java
Normal file
249
jdk/test/sun/java2d/marlin/CeilAndFloorTests.java
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import sun.java2d.marlin.FloatMath;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test
|
||||||
|
* @summary Check for correct implementation of FloatMath.ceil/floor
|
||||||
|
* @run main CeilAndFloorTests
|
||||||
|
*/
|
||||||
|
public class CeilAndFloorTests {
|
||||||
|
|
||||||
|
public static String toHexString(float f) {
|
||||||
|
if (!Float.isNaN(f))
|
||||||
|
return Float.toHexString(f);
|
||||||
|
else
|
||||||
|
return "NaN(0x" + Integer.toHexString(Float.floatToRawIntBits(f)) + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int test(String testName, float input,
|
||||||
|
float result, float expected) {
|
||||||
|
if (Float.compare(expected, result) != 0) {
|
||||||
|
System.err.println("Failure for " + testName + ":\n" +
|
||||||
|
"\tFor input " + input + "\t(" + toHexString(input) + ")\n" +
|
||||||
|
"\texpected " + expected + "\t(" + toHexString(expected) + ")\n" +
|
||||||
|
"\tgot " + result + "\t(" + toHexString(result) + ").");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int test_skip_0(String testName, float input,
|
||||||
|
float result, float expected)
|
||||||
|
{
|
||||||
|
// floor_int does not distinguish +0f and -0f
|
||||||
|
// but it is not critical for Marlin
|
||||||
|
if (Float.compare(expected, result) != 0 && (expected != 0f))
|
||||||
|
{
|
||||||
|
System.err.println("Failure for " + testName + ":\n" +
|
||||||
|
"\tFor input " + input + "\t(" + toHexString(input) + ")\n" +
|
||||||
|
"\texpected " + expected + "\t(" + toHexString(expected) + ")\n" +
|
||||||
|
"\tgot " + result + "\t(" + toHexString(result) + ").");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int testCeilCase(float input, float expected) {
|
||||||
|
int failures = 0;
|
||||||
|
// float result:
|
||||||
|
failures += test("FloatMath.ceil_f", input, FloatMath.ceil_f(input), expected);
|
||||||
|
// int result:
|
||||||
|
failures += test("FloatMath.ceil_int", input, FloatMath.ceil_int(input), (int)expected);
|
||||||
|
failures += test("FloatMath.ceil_f (int)", input, (int)FloatMath.ceil_f(input), (int)expected);
|
||||||
|
return failures;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int testFloorCase(float input, float expected) {
|
||||||
|
int failures = 0;
|
||||||
|
// float result:
|
||||||
|
failures += test ("FloatMath.floor_f", input, FloatMath.floor_f(input), expected);
|
||||||
|
// ignore difference between +0f and -0f:
|
||||||
|
failures += test_skip_0("FloatMath.floor_int", input, FloatMath.floor_int(input), (int)expected);
|
||||||
|
failures += test_skip_0("FloatMath.floor_f (int)", input, (int)FloatMath.floor_f(input), (int)expected);
|
||||||
|
return failures;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int nearIntegerTests() {
|
||||||
|
int failures = 0;
|
||||||
|
|
||||||
|
float [] fixedPoints = {
|
||||||
|
-0.0f,
|
||||||
|
0.0f,
|
||||||
|
-1.0f,
|
||||||
|
1.0f,
|
||||||
|
-0x1.0p52f,
|
||||||
|
0x1.0p52f,
|
||||||
|
-Float.MAX_VALUE,
|
||||||
|
Float.MAX_VALUE,
|
||||||
|
Float.NEGATIVE_INFINITY,
|
||||||
|
Float.POSITIVE_INFINITY,
|
||||||
|
Float.NaN,
|
||||||
|
};
|
||||||
|
|
||||||
|
for(float fixedPoint : fixedPoints) {
|
||||||
|
failures += testCeilCase(fixedPoint, fixedPoint);
|
||||||
|
failures += testFloorCase(fixedPoint, fixedPoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i = Float.MIN_EXPONENT; i <= Float.MAX_EXPONENT; i++) {
|
||||||
|
float powerOfTwo = Math.scalb(1.0f, i);
|
||||||
|
float neighborDown = Math.nextDown(powerOfTwo);
|
||||||
|
float neighborUp = Math.nextUp(powerOfTwo);
|
||||||
|
|
||||||
|
if (i < 0) {
|
||||||
|
failures += testCeilCase( powerOfTwo, 1.0f);
|
||||||
|
failures += testCeilCase(-powerOfTwo, -0.0f);
|
||||||
|
|
||||||
|
failures += testFloorCase( powerOfTwo, 0.0f);
|
||||||
|
failures += testFloorCase(-powerOfTwo, -1.0f);
|
||||||
|
|
||||||
|
failures += testCeilCase( neighborDown, 1.0f);
|
||||||
|
failures += testCeilCase(-neighborDown, -0.0f);
|
||||||
|
|
||||||
|
failures += testFloorCase( neighborUp, 0.0f);
|
||||||
|
failures += testFloorCase(-neighborUp, -1.0f);
|
||||||
|
} else {
|
||||||
|
failures += testCeilCase(powerOfTwo, powerOfTwo);
|
||||||
|
failures += testFloorCase(powerOfTwo, powerOfTwo);
|
||||||
|
|
||||||
|
if (neighborDown==Math.rint(neighborDown)) {
|
||||||
|
failures += testCeilCase( neighborDown, neighborDown);
|
||||||
|
failures += testCeilCase(-neighborDown, -neighborDown);
|
||||||
|
|
||||||
|
failures += testFloorCase( neighborDown, neighborDown);
|
||||||
|
failures += testFloorCase(-neighborDown,-neighborDown);
|
||||||
|
} else {
|
||||||
|
failures += testCeilCase( neighborDown, powerOfTwo);
|
||||||
|
failures += testFloorCase(-neighborDown, -powerOfTwo);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (neighborUp==Math.rint(neighborUp)) {
|
||||||
|
failures += testCeilCase(neighborUp, neighborUp);
|
||||||
|
failures += testCeilCase(-neighborUp, -neighborUp);
|
||||||
|
|
||||||
|
failures += testFloorCase(neighborUp, neighborUp);
|
||||||
|
failures += testFloorCase(-neighborUp, -neighborUp);
|
||||||
|
} else {
|
||||||
|
failures += testFloorCase(neighborUp, powerOfTwo);
|
||||||
|
failures += testCeilCase(-neighborUp, -powerOfTwo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i = -(0x10000); i <= 0x10000; i++) {
|
||||||
|
float f = (float) i;
|
||||||
|
float neighborDown = Math.nextDown(f);
|
||||||
|
float neighborUp = Math.nextUp(f);
|
||||||
|
|
||||||
|
failures += testCeilCase( f, f);
|
||||||
|
failures += testCeilCase(-f, -f);
|
||||||
|
|
||||||
|
failures += testFloorCase( f, f);
|
||||||
|
failures += testFloorCase(-f, -f);
|
||||||
|
|
||||||
|
if (Math.abs(f) > 1.0) {
|
||||||
|
failures += testCeilCase( neighborDown, f);
|
||||||
|
failures += testCeilCase(-neighborDown, -f+1);
|
||||||
|
|
||||||
|
failures += testFloorCase( neighborUp, f);
|
||||||
|
failures += testFloorCase(-neighborUp, -f-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return failures;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int roundingTests() {
|
||||||
|
int failures = 0;
|
||||||
|
float [][] testCases = {
|
||||||
|
{ Float.MIN_VALUE, 1.0f},
|
||||||
|
{-Float.MIN_VALUE, -0.0f},
|
||||||
|
{ Math.nextDown(Float.MIN_NORMAL), 1.0f},
|
||||||
|
{-Math.nextDown(Float.MIN_NORMAL), -0.0f},
|
||||||
|
{ Float.MIN_NORMAL, 1.0f},
|
||||||
|
{-Float.MIN_NORMAL, -0.0f},
|
||||||
|
|
||||||
|
{ 0.1f, 1.0f},
|
||||||
|
{-0.1f, -0.0f},
|
||||||
|
|
||||||
|
{ 0.5f, 1.0f},
|
||||||
|
{-0.5f, -0.0f},
|
||||||
|
|
||||||
|
{ 1.5f, 2.0f},
|
||||||
|
{-1.5f, -1.0f},
|
||||||
|
|
||||||
|
{ 2.5f, 3.0f},
|
||||||
|
{-2.5f, -2.0f},
|
||||||
|
|
||||||
|
{ 12.3456789f, 13.0f},
|
||||||
|
{-12.3456789f, -12.0f},
|
||||||
|
|
||||||
|
{ Math.nextDown(1.0f), 1.0f},
|
||||||
|
{ Math.nextDown(-1.0f), -1.0f},
|
||||||
|
|
||||||
|
{ Math.nextUp(1.0f), 2.0f},
|
||||||
|
{ Math.nextUp(-1.0f), -0.0f},
|
||||||
|
|
||||||
|
{ 0x1.0p22f, 0x1.0p22f},
|
||||||
|
{-0x1.0p22f, -0x1.0p22f},
|
||||||
|
|
||||||
|
{ Math.nextDown(0x1.0p22f), 0x1.0p22f},
|
||||||
|
{-Math.nextUp(0x1.0p22f), -0x1.0p22f},
|
||||||
|
|
||||||
|
{ Math.nextUp(0x1.0p22f), 0x1.0p22f+1f},
|
||||||
|
{-Math.nextDown(0x1.0p22f), -0x1.0p22f+1f},
|
||||||
|
|
||||||
|
{ Math.nextDown(0x1.0p23f), 0x1.0p23f},
|
||||||
|
{-Math.nextUp(0x1.0p23f), -0x1.0p23f-1f},
|
||||||
|
|
||||||
|
{ Math.nextUp(0x1.0p23f), 0x1.0p23f+1f},
|
||||||
|
{-Math.nextDown(0x1.0p23f), -0x1.0p23f+1f},
|
||||||
|
};
|
||||||
|
|
||||||
|
for(float[] testCase : testCases) {
|
||||||
|
failures += testCeilCase(testCase[0], testCase[1]);
|
||||||
|
failures += testFloorCase(-testCase[0], -testCase[1]);
|
||||||
|
}
|
||||||
|
return failures;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String... args) {
|
||||||
|
int failures = 0;
|
||||||
|
|
||||||
|
System.out.println("nearIntegerTests");
|
||||||
|
failures += nearIntegerTests();
|
||||||
|
|
||||||
|
System.out.println("roundingTests");
|
||||||
|
failures += roundingTests();
|
||||||
|
|
||||||
|
if (failures > 0) {
|
||||||
|
System.err.println("Testing {FloatMath}.ceil/floor incurred "
|
||||||
|
+ failures + " failures.");
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
289
jdk/test/sun/java2d/marlin/CrashTest.java
Normal file
289
jdk/test/sun/java2d/marlin/CrashTest.java
Normal file
@ -0,0 +1,289 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.awt.BasicStroke;
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.RenderingHints;
|
||||||
|
import java.awt.geom.Path2D;
|
||||||
|
import static java.awt.geom.Path2D.WIND_NON_ZERO;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import sun.java2d.pipe.RenderingEngine;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple crash rendering test using huge GeneralPaths with marlin renderer
|
||||||
|
*
|
||||||
|
* run it with large heap (2g):
|
||||||
|
* java -Dsun.java2d.renderer=sun.java2d.marlin.MarlinRenderingEngine marlin.CrashTest
|
||||||
|
*
|
||||||
|
* @author bourgesl
|
||||||
|
*/
|
||||||
|
public class CrashTest {
|
||||||
|
|
||||||
|
static final boolean SAVE_IMAGE = false;
|
||||||
|
static boolean USE_ROUND_CAPS_AND_JOINS = true;
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
// try insane image sizes:
|
||||||
|
|
||||||
|
// subpixel coords may overflow:
|
||||||
|
// testHugeImage((Integer.MAX_VALUE >> 3) + 1, 6);
|
||||||
|
// larger than 23 bits: (RLE)
|
||||||
|
testHugeImage(8388608 + 1, 10);
|
||||||
|
|
||||||
|
test(0.1f, false, 0);
|
||||||
|
test(0.1f, true, 7f);
|
||||||
|
|
||||||
|
// Exceed 2Gb OffHeap buffer for edges:
|
||||||
|
try {
|
||||||
|
USE_ROUND_CAPS_AND_JOINS = true;
|
||||||
|
test(0.1f, true, 0.1f);
|
||||||
|
System.out.println("Exception MISSING.");
|
||||||
|
}
|
||||||
|
catch (Throwable th) {
|
||||||
|
if (th instanceof ArrayIndexOutOfBoundsException) {
|
||||||
|
System.out.println("ArrayIndexOutOfBoundsException expected.");
|
||||||
|
} else {
|
||||||
|
System.out.println("Exception occured:");
|
||||||
|
th.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void test(final float lineStroke,
|
||||||
|
final boolean useDashes,
|
||||||
|
final float dashMinLen)
|
||||||
|
throws ArrayIndexOutOfBoundsException
|
||||||
|
{
|
||||||
|
System.out.println("---\n" + "test: "
|
||||||
|
+ "lineStroke=" + lineStroke
|
||||||
|
+ ", useDashes=" + useDashes
|
||||||
|
+", dashMinLen=" + dashMinLen
|
||||||
|
);
|
||||||
|
|
||||||
|
final String renderer = RenderingEngine.getInstance().getClass().getSimpleName();
|
||||||
|
System.out.println("Testing renderer = " + renderer);
|
||||||
|
|
||||||
|
final BasicStroke stroke = createStroke(lineStroke, useDashes, dashMinLen);
|
||||||
|
|
||||||
|
// TODO: test Dasher.firstSegmentsBuffer resizing ?
|
||||||
|
// array.dasher.firstSegmentsBuffer.d_float[2] sum: 6 avg: 3.0 [3 | 3]
|
||||||
|
/*
|
||||||
|
// Marlin growable arrays:
|
||||||
|
= new StatLong("array.dasher.firstSegmentsBuffer.d_float");
|
||||||
|
= new StatLong("array.stroker.polystack.curves.d_float");
|
||||||
|
= new StatLong("array.stroker.polystack.curveTypes.d_byte");
|
||||||
|
= new StatLong("array.marlincache.rowAAChunk.d_byte");
|
||||||
|
= new StatLong("array.marlincache.touchedTile.int");
|
||||||
|
= new StatLong("array.renderer.alphaline.int");
|
||||||
|
= new StatLong("array.renderer.crossings.int");
|
||||||
|
= new StatLong("array.renderer.aux_crossings.int");
|
||||||
|
= new StatLong("array.renderer.edgeBuckets.int");
|
||||||
|
= new StatLong("array.renderer.edgeBucketCounts.int");
|
||||||
|
= new StatLong("array.renderer.edgePtrs.int");
|
||||||
|
= new StatLong("array.renderer.aux_edgePtrs.int");
|
||||||
|
*/
|
||||||
|
// size > 8192 (exceed both tile and buckets arrays)
|
||||||
|
final int size = 9000;
|
||||||
|
System.out.println("image size = " + size);
|
||||||
|
|
||||||
|
final BufferedImage image = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
|
||||||
|
final Graphics2D g2d = (Graphics2D) image.getGraphics();
|
||||||
|
try {
|
||||||
|
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||||
|
|
||||||
|
g2d.setClip(0, 0, size, size);
|
||||||
|
g2d.setBackground(Color.WHITE);
|
||||||
|
g2d.clearRect(0, 0, size, size);
|
||||||
|
|
||||||
|
g2d.setStroke(stroke);
|
||||||
|
g2d.setColor(Color.BLACK);
|
||||||
|
|
||||||
|
final long start = System.nanoTime();
|
||||||
|
|
||||||
|
paint(g2d, size - 10f);
|
||||||
|
|
||||||
|
final long time = System.nanoTime() - start;
|
||||||
|
|
||||||
|
System.out.println("paint: duration= " + (1e-6 * time) + " ms.");
|
||||||
|
|
||||||
|
if (SAVE_IMAGE) {
|
||||||
|
try {
|
||||||
|
final File file = new File("CrashTest-" + renderer + "-dash-" + useDashes + ".bmp");
|
||||||
|
|
||||||
|
System.out.println("Writing file: " + file.getAbsolutePath());
|
||||||
|
ImageIO.write(image, "BMP", file);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
System.out.println("Writing file failure:");
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
g2d.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void testHugeImage(final int width, final int height)
|
||||||
|
throws ArrayIndexOutOfBoundsException
|
||||||
|
{
|
||||||
|
System.out.println("---\n" + "testHugeImage: "
|
||||||
|
+ "width=" + width
|
||||||
|
+ ", height=" + height
|
||||||
|
);
|
||||||
|
|
||||||
|
final String renderer = RenderingEngine.getInstance().getClass().getSimpleName();
|
||||||
|
System.out.println("Testing renderer = " + renderer);
|
||||||
|
|
||||||
|
final BasicStroke stroke = createStroke(2.5f, false, 0);
|
||||||
|
|
||||||
|
// size > 24bits (exceed both tile and buckets arrays)
|
||||||
|
System.out.println("image size = " + width + " x "+height);
|
||||||
|
|
||||||
|
final BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
|
||||||
|
|
||||||
|
final Graphics2D g2d = (Graphics2D) image.getGraphics();
|
||||||
|
try {
|
||||||
|
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||||
|
|
||||||
|
g2d.setBackground(Color.WHITE);
|
||||||
|
g2d.clearRect(0, 0, width, height);
|
||||||
|
|
||||||
|
g2d.setStroke(stroke);
|
||||||
|
g2d.setColor(Color.BLACK);
|
||||||
|
|
||||||
|
final Path2D.Float path = new Path2D.Float(WIND_NON_ZERO, 32);
|
||||||
|
path.moveTo(0, 0);
|
||||||
|
path.lineTo(width, 0);
|
||||||
|
path.lineTo(width, height);
|
||||||
|
path.lineTo(0, height);
|
||||||
|
path.lineTo(0, 0);
|
||||||
|
|
||||||
|
final long start = System.nanoTime();
|
||||||
|
|
||||||
|
g2d.draw(path);
|
||||||
|
|
||||||
|
final long time = System.nanoTime() - start;
|
||||||
|
|
||||||
|
System.out.println("paint: duration= " + (1e-6 * time) + " ms.");
|
||||||
|
|
||||||
|
if (SAVE_IMAGE) {
|
||||||
|
try {
|
||||||
|
final File file = new File("CrashTest-" + renderer +
|
||||||
|
"-huge-" + width + "x" +height + ".bmp");
|
||||||
|
|
||||||
|
System.out.println("Writing file: " + file.getAbsolutePath());
|
||||||
|
ImageIO.write(image, "BMP", file);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
System.out.println("Writing file failure:");
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
g2d.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void paint(final Graphics2D g2d, final float size) {
|
||||||
|
final double halfSize = size / 2.0;
|
||||||
|
|
||||||
|
final Path2D.Float path = new Path2D.Float(WIND_NON_ZERO, 32 * 1024);
|
||||||
|
|
||||||
|
// show cross:
|
||||||
|
path.moveTo(0, 0);
|
||||||
|
path.lineTo(size, size);
|
||||||
|
|
||||||
|
path.moveTo(size, 0);
|
||||||
|
path.lineTo(0, size);
|
||||||
|
|
||||||
|
path.moveTo(0, 0);
|
||||||
|
path.lineTo(size, 0);
|
||||||
|
|
||||||
|
path.moveTo(0, 0);
|
||||||
|
path.lineTo(0, size);
|
||||||
|
|
||||||
|
path.moveTo(0, 0);
|
||||||
|
|
||||||
|
double r = size;
|
||||||
|
|
||||||
|
final int ratio = 100;
|
||||||
|
int repeats = 1;
|
||||||
|
|
||||||
|
int n = 0;
|
||||||
|
|
||||||
|
while (r > 1.0) {
|
||||||
|
repeats *= ratio;
|
||||||
|
|
||||||
|
if (repeats > 10000) {
|
||||||
|
repeats = 10000;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < repeats; i++) {
|
||||||
|
path.lineTo(halfSize - 0.5 * r + i * r / repeats,
|
||||||
|
halfSize - 0.5 * r);
|
||||||
|
n++;
|
||||||
|
path.lineTo(halfSize - 0.5 * r + i * r / repeats + 0.1,
|
||||||
|
halfSize + 0.5 * r);
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
|
||||||
|
r -= halfSize;
|
||||||
|
}
|
||||||
|
System.out.println("draw : " + n + " lines.");
|
||||||
|
g2d.draw(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BasicStroke createStroke(final float width,
|
||||||
|
final boolean useDashes,
|
||||||
|
final float dashMinLen) {
|
||||||
|
final float[] dashes;
|
||||||
|
|
||||||
|
if (useDashes) {
|
||||||
|
// huge dash array (exceed Dasher.INITIAL_ARRAY)
|
||||||
|
dashes = new float[512];
|
||||||
|
|
||||||
|
float cur = dashMinLen;
|
||||||
|
float step = 0.01f;
|
||||||
|
|
||||||
|
for (int i = 0; i < dashes.length; i += 2) {
|
||||||
|
dashes[i] = cur;
|
||||||
|
dashes[i + 1] = cur;
|
||||||
|
cur += step;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dashes = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (USE_ROUND_CAPS_AND_JOINS) {
|
||||||
|
// Use both round Caps & Joins:
|
||||||
|
return new BasicStroke(width, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 100.0f, dashes, 0.0f);
|
||||||
|
}
|
||||||
|
return new BasicStroke(width, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 100.0f, dashes, 0.0f);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user