8273972: Multi-core choke point in CMM engine (LCMSTransform.doTransform)

Reviewed-by: prr
This commit is contained in:
Sergey Bylokhov 2021-09-28 21:38:40 +00:00
parent 2072bc77b4
commit e49e5b5a7e
5 changed files with 332 additions and 40 deletions
src/java.desktop/share
classes/sun/java2d/cmm/lcms
native/liblcms
test/jdk/sun/java2d/cmm/ColorConvertOp

@ -148,7 +148,7 @@ final class LCMS implements PCMM {
}
/* methods invoked from LCMSTransform */
public static native void colorConvert(LCMSTransform trans,
public static native void colorConvert(long trans,
LCMSImageLayout src,
LCMSImageLayout dest);

@ -44,18 +44,30 @@ import java.awt.image.DataBuffer;
import java.awt.image.Raster;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
import java.lang.ref.Reference;
import sun.java2d.cmm.ColorTransform;
import static sun.java2d.cmm.lcms.LCMSImageLayout.ImageLayoutException;
final class LCMSTransform implements ColorTransform {
long ID;
private int inFormatter = 0;
private boolean isInIntPacked = false;
private int outFormatter = 0;
private boolean isOutIntPacked = false;
private final static class NativeTransform {
private long ID;
private int inFormatter;
private boolean isInIntPacked;
private int outFormatter;
private boolean isOutIntPacked;
private boolean match(LCMSImageLayout in, LCMSImageLayout out) {
return inFormatter == in.pixelType
&& isInIntPacked == in.isIntPacked
&& outFormatter == out.pixelType
&& isOutIntPacked == out.isIntPacked;
}
}
private volatile NativeTransform transform;
ICC_Profile[] profiles;
LCMSProfile[] lcmsProfiles;
int renderType;
@ -64,8 +76,6 @@ final class LCMSTransform implements ColorTransform {
private int numInComponents = -1;
private int numOutComponents = -1;
private Object disposerReferent = new Object();
public LCMSTransform(ICC_Profile profile, int renderType,
int transformType)
{
@ -122,31 +132,32 @@ final class LCMSTransform implements ColorTransform {
return numOutComponents;
}
private synchronized void doTransform(LCMSImageLayout in,
LCMSImageLayout out) {
// update native transfrom if needed
if (ID == 0L ||
inFormatter != in.pixelType || isInIntPacked != in.isIntPacked ||
outFormatter != out.pixelType || isOutIntPacked != out.isIntPacked)
{
private void doTransform(LCMSImageLayout in, LCMSImageLayout out) {
NativeTransform tfm = transform;
// update native transform if needed
if (tfm == null || !tfm.match(in, out)) {
synchronized (this) {
tfm = transform;
if (tfm == null || !tfm.match(in, out)) {
tfm = new NativeTransform();
tfm.inFormatter = in.pixelType;
tfm.isInIntPacked = in.isIntPacked;
if (ID != 0L) {
// Disposer will destroy forgotten transform
disposerReferent = new Object();
tfm.outFormatter = out.pixelType;
tfm.isOutIntPacked = out.isIntPacked;
tfm.ID = LCMS.createTransform(lcmsProfiles, renderType,
tfm.inFormatter,
tfm.isInIntPacked,
tfm.outFormatter,
tfm.isOutIntPacked, tfm);
// Disposer will destroy forgotten transform
transform = tfm;
}
}
inFormatter = in.pixelType;
isInIntPacked = in.isIntPacked;
outFormatter = out.pixelType;
isOutIntPacked = out.isIntPacked;
ID = LCMS.createTransform(lcmsProfiles, renderType,
inFormatter, isInIntPacked,
outFormatter, isOutIntPacked,
disposerReferent);
}
LCMS.colorConvert(this, in, out);
LCMS.colorConvert(tfm.ID, in, out);
Reference.reachabilityFence(tfm); // prevent deallocation of "tfm.ID"
}
/**

@ -71,7 +71,6 @@ typedef union {
} TagSignature_t, *TagSignature_p;
static jfieldID Trans_renderType_fID;
static jfieldID Trans_ID_fID;
static jfieldID IL_isIntPacked_fID;
static jfieldID IL_dataType_fID;
static jfieldID IL_pixelType_fID;
@ -510,9 +509,9 @@ void releaseILData (JNIEnv *env, void* pData, jint dataType,
* Signature: (Lsun/java2d/cmm/lcms/LCMSTransform;Lsun/java2d/cmm/lcms/LCMSImageLayout;Lsun/java2d/cmm/lcms/LCMSImageLayout;)V
*/
JNIEXPORT void JNICALL Java_sun_java2d_cmm_lcms_LCMS_colorConvert
(JNIEnv *env, jclass cls, jobject trans, jobject src, jobject dst)
(JNIEnv *env, jclass cls, jlong ID, jobject src, jobject dst)
{
cmsHTRANSFORM sTrans = NULL;
cmsHTRANSFORM sTrans = jlong_to_ptr(ID);
int srcDType, dstDType;
int srcOffset, srcNextRowOffset, dstOffset, dstNextRowOffset;
int width, height, i;
@ -533,8 +532,6 @@ JNIEXPORT void JNICALL Java_sun_java2d_cmm_lcms_LCMS_colorConvert
srcAtOnce = (*env)->GetBooleanField(env, src, IL_imageAtOnce_fID);
dstAtOnce = (*env)->GetBooleanField(env, dst, IL_imageAtOnce_fID);
sTrans = jlong_to_ptr((*env)->GetLongField (env, trans, Trans_ID_fID));
if (sTrans == NULL) {
J2dRlsTraceLn(J2D_TRACE_ERROR, "LCMS_colorConvert: transform == NULL");
JNU_ThrowByName(env, "java/awt/color/CMMException",
@ -626,11 +623,6 @@ JNIEXPORT void JNICALL Java_sun_java2d_cmm_lcms_LCMS_initLCMS
if (Trans_renderType_fID == NULL) {
return;
}
Trans_ID_fID = (*env)->GetFieldID (env, Trans, "ID", "J");
if (Trans_ID_fID == NULL) {
return;
}
IL_isIntPacked_fID = (*env)->GetFieldID (env, IL, "isIntPacked", "Z");
if (IL_isIntPacked_fID == NULL) {
return;

@ -0,0 +1,146 @@
/*
* Copyright Amazon.com Inc. 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.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorConvertOp;
/**
* @test
* @bug 8273972
* @summary Verifies that ColorConvertOp works fine if shared between threads
* @run main/othervm/timeout=600 MTTransformValidation
*/
public final class MTPerLineTransformValidation {
private volatile static BufferedImage[] lines;
public static final int SIZE = 255;
private static volatile boolean failed = false;
private static final int[] spaces = {
ColorSpace.CS_CIEXYZ, ColorSpace.CS_GRAY, ColorSpace.CS_LINEAR_RGB,
ColorSpace.CS_PYCC, ColorSpace.CS_sRGB
};
private static final int[] types = new int[]{
BufferedImage.TYPE_INT_RGB, BufferedImage.TYPE_INT_ARGB,
BufferedImage.TYPE_INT_ARGB_PRE, BufferedImage.TYPE_INT_BGR,
BufferedImage.TYPE_3BYTE_BGR, BufferedImage.TYPE_4BYTE_ABGR,
BufferedImage.TYPE_4BYTE_ABGR_PRE,
BufferedImage.TYPE_USHORT_565_RGB,
BufferedImage.TYPE_USHORT_555_RGB, BufferedImage.TYPE_BYTE_GRAY,
BufferedImage.TYPE_USHORT_GRAY, BufferedImage.TYPE_BYTE_BINARY,
BufferedImage.TYPE_BYTE_INDEXED
};
/**
* For all possible combinations of color spaces and image types, convert
* the source image using one shared ColorConvertOp per line on the
* different threads. The result is validated against images converted on
* one thread only.
*/
public static void main(String[] args) throws Exception {
for (int srcCS : spaces) {
for (int dstCS : spaces) {
if(srcCS != dstCS) {
for (int type : types) {
checkTypes(ColorSpace.getInstance(srcCS),
ColorSpace.getInstance(dstCS), type);
}
}
}
}
}
private static void checkTypes(ColorSpace srcCS, ColorSpace dstCS, int type)
throws Exception {
lines = new BufferedImage[SIZE];
ColorConvertOp goldOp = new ColorConvertOp(srcCS, dstCS, null);
BufferedImage src = createSrc(type);
BufferedImage gold = goldOp.filter(src, null);
// we do not share the goldOp since it is already initialized and used
// for the whole image, instead we will create a separate sharedOp and
// use it for each line of a different threads
ColorConvertOp sharedOp = new ColorConvertOp(srcCS, dstCS, null);
Thread[] threads = new Thread[SIZE];
for (int y = 0; y < SIZE; ++y) {
BufferedImage line = src.getSubimage(0, y, SIZE, 1);
threads[y] = test(sharedOp, line, y);
}
for (Thread t: threads) {
t.start();
}
for (Thread t: threads) {
t.join();
}
for (int y = 0; y < SIZE; ++y) {
validate(gold, lines[y], y);
}
if (failed) {
throw new RuntimeException("Unexpected exception");
}
}
private static Thread test(ColorConvertOp sharedOp,
BufferedImage line, int y){
return new Thread(() -> {
try {
BufferedImage image = sharedOp.filter(line, null);
lines[y] = image;
} catch (Throwable t) {
t.printStackTrace();
failed = true;
}
});
}
private static BufferedImage createSrc(int type) {
BufferedImage img = new BufferedImage(SIZE, SIZE, type);
fill(img);
return img;
}
private static void fill(BufferedImage image) {
for (int i = 0; i < SIZE; i++) {
for (int j = 0; j < SIZE; j++) {
image.setRGB(i, j,
(i << 24) | (i << 16) | (j << 8) | ((i + j) >> 1));
}
}
}
private static void validate(BufferedImage full, BufferedImage line, int y) {
for (int i = 0; i < SIZE; i++) {
int rgb1 = full.getRGB(i, y);
int rgb2 = line.getRGB(i, 0);
if (rgb1 != rgb2) {
System.err.println("rgb1 = " + Integer.toHexString(rgb1));
System.err.println("rgb2 = " + Integer.toHexString(rgb2));
throw new RuntimeException();
}
}
}
}

@ -0,0 +1,143 @@
/*
* Copyright Amazon.com Inc. 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.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorConvertOp;
import java.util.concurrent.CountDownLatch;
/**
* @test
* @bug 8273972
* @summary Verifies that ColorConvertOp works fine if shared between threads
* @run main/othervm/timeout=600 MTTransformValidation
*/
public final class MTTransformValidation {
public static final int SIZE = 255;
private static volatile boolean failed = false;
private static final int[] spaces = {
ColorSpace.CS_CIEXYZ, ColorSpace.CS_GRAY, ColorSpace.CS_LINEAR_RGB,
ColorSpace.CS_PYCC, ColorSpace.CS_sRGB
};
private static final int[] types = new int[]{
BufferedImage.TYPE_INT_RGB, BufferedImage.TYPE_INT_ARGB,
BufferedImage.TYPE_INT_ARGB_PRE, BufferedImage.TYPE_INT_BGR,
BufferedImage.TYPE_3BYTE_BGR, BufferedImage.TYPE_4BYTE_ABGR,
BufferedImage.TYPE_4BYTE_ABGR_PRE,
BufferedImage.TYPE_USHORT_565_RGB,
BufferedImage.TYPE_USHORT_555_RGB, BufferedImage.TYPE_BYTE_GRAY,
BufferedImage.TYPE_USHORT_GRAY, BufferedImage.TYPE_BYTE_BINARY,
BufferedImage.TYPE_BYTE_INDEXED
};
/**
* For all possible combinations of color spaces and image types, convert
* the source image using one shared ColorConvertOp. The result is validated
* against images converted on one thread only.
*/
public static void main(String[] args) throws Exception {
for (int srcCS : spaces) {
for (int dstCS : spaces) {
if(srcCS != dstCS) {
for (int type : types) {
checkTypes(ColorSpace.getInstance(srcCS),
ColorSpace.getInstance(dstCS), type);
}
}
}
}
}
private static void checkTypes(ColorSpace srcCS, ColorSpace dstCS, int type)
throws Exception {
ColorConvertOp goldOp = new ColorConvertOp(srcCS, dstCS, null);
BufferedImage gold = goldOp.filter(createSrc(type), null);
// we do not share the goldOp since it is already initialized, but
// instead we will trigger initialization/usage of the new sharedOp on
// different threads at once
ColorConvertOp sharedOp = new ColorConvertOp(srcCS, dstCS, null);
test(gold, sharedOp, type);
if (failed) {
throw new RuntimeException("Unexpected exception");
}
}
private static void test(BufferedImage gold, ColorConvertOp sharedOp,
int type) throws Exception {
Thread[] ts = new Thread[7];
CountDownLatch latch = new CountDownLatch(ts.length);
for (int i = 0; i < ts.length; i++) {
ts[i] = new Thread(() -> {
BufferedImage local = createSrc(type);
latch.countDown();
try {
latch.await();
BufferedImage image = sharedOp.filter(local, null);
validate(image, gold);
} catch (Throwable t) {
t.printStackTrace();
failed = true;
}
});
}
for (Thread t : ts) {
t.start();
}
for (Thread t : ts) {
t.join();
}
}
private static BufferedImage createSrc(int type) {
BufferedImage img = new BufferedImage(SIZE, SIZE, type);
fill(img);
return img;
}
private static void fill(BufferedImage image) {
for (int i = 0; i < SIZE; i++) {
for (int j = 0; j < SIZE; j++) {
image.setRGB(i, j,
(i << 24) | (i << 16) | (j << 8) | ((i + j) >> 1));
}
}
}
private static void validate(BufferedImage img1, BufferedImage img2) {
for (int i = 0; i < SIZE; i++) {
for (int j = 0; j < SIZE; j++) {
int rgb1 = img1.getRGB(i, j);
int rgb2 = img2.getRGB(i, j);
if (rgb1 != rgb2) {
System.err.println("rgb1 = " + Integer.toHexString(rgb1));
System.err.println("rgb2 = " + Integer.toHexString(rgb2));
throw new RuntimeException();
}
}
}
}
}