JDK-8017247 : [macosx] Bi-cubic and Bi-linear rendering are slow and BiCubic renders twice.
  • Type: Bug
  • Component: client-libs
  • Sub-Component: 2d
  • Affected Version: 7,7u15
  • Priority: P3
  • Status: Open
  • Resolution: Unresolved
  • OS: os_x
  • Submitted: 2013-06-21
  • Updated: 2021-07-13
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
tbdUnresolved
Related Reports
Relates :  
Description
FULL PRODUCT VERSION :
java version  " 1.7.0_15 " 
Java(TM) SE Runtime Environment (build 1.7.0_15-b03)
Java HotSpot(TM) 64-Bit Server VM (build 23.7-b01, mixed mode)


ADDITIONAL OS VERSION INFORMATION :
Darwin slytherin 11.4.2 Darwin Kernel Version 11.4.2: Thu Aug 23 16:25:48 PDT 2012; root:xnu-1699.32.7~1/RELEASE_X86_64 x86_64

(Mac OSX 10.7.5)


EXTRA RELEVANT SYSTEM CONFIGURATION :
Mac Pro 2.66 GHz Quad-Core Intel Xeon with 3 GB 1066 Mhz DDR3 memory. (I don't know if this is relevant.)

A DESCRIPTION OF THE PROBLEM :
Rendering an image with RenderingHints.KEY_INTERPOLATION set to
VALUE_INTERPOLATION_BICUBIC or VALUE_INTERPOLATION_BILINEAR causes
rendering to run very slowly, and renders twice when using
VALUE_INTERPOLATION_BICUBIC.


REGRESSION.  Last worked in version 6u45

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
  To maximize the effect, run this on the largest screen you can feasibly
use. Run the enclosed test case, which displays a test image in a
window. The repaint button lets you repaint the test image, and the
three radio buttons let you choose which type of interpolation to use
when repainting. Hit the repaint button to repaint using the default
nearest-neighbor algorithm. Then choose the Bi-linear interpolation
and hit repaint again. Observe the repainting speed. (The quality of
the black circle improves when you switch to Bi-linear painting, which
is how you can tell the painting is complete.) Then try this again
after choosing BiCubic interpolation.
Do all of this under JRE 1.6 and 1.7, and compare the results


EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
For all three interpolation modes, painting of the image should be fast, and should not flicker.

ACTUAL -
I ran this on an Apple Cinema display, with the resolution set to 1920 x 1200.

Under JRE 1.6, I get the expected result. All three modes paint nearly
instantaneously. Under JRE 1.7, both Bi-linear and Bi-cubic paint
slowly. Bi-linear takes about 3 seconds, and Bi-cubic paints the image
twice, and takes about 5 seconds for both paints.


REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.image.BufferedImage;
import java.awt.image.FilteredImageSource;
import java.awt.image.ImageFilter;
import java.awt.image.RGBImageFilter;
import java.util.HashMap;
import java.util.Map;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JToolBar;
import javax.swing.SwingConstants;
import javax.swing.WindowConstants;

@SuppressWarnings({ " HardCodedStringLiteral " ,  " MagicNumber " })
public class BicubicFlickerBug6 extends JPanel {
private static final int RADIUS = 300;
private static final int DIAMETER = RADIUS * 2;
private static final int MARGIN = 10;
private static final int arcCount = 36;
private static final int radiiCount = 8;
private static final Shape outerCircle = new Arc2D.Double(0, 0, DIAMETER, DIAMETER, 0.0, 360.0, Arc2D.OPEN);
private static final Arc2D colorArc =
new Arc2D.Double(-RADIUS, -RADIUS, DIAMETER, DIAMETER, 0.0, -360.0 / arcCount, Arc2D.PIE);

private final ScaledImage scalingImage = new ScaledImage(null);
private final JComponent photoView;
private final Image loadedImage = makeTestImage();

private Image makeTestImage() {

int size = DIAMETER + (2 * MARGIN);

BufferedImage image = new BufferedImage(size, size, BufferedImage.TYPE_4BYTE_ABGR);
Graphics2D g2 = (Graphics2D) image.getGraphics();
setRenderHints(g2);
final Stroke s2 = new BasicStroke(2.0f);
g2.setStroke(s2);
AffineTransform savedTransform = g2.getTransform();

// move to the center
g2.setTransform(savedTransform);
for (int saturation = 0; saturation < radiiCount; ++saturation) {
g2.translate(MARGIN + RADIUS, MARGIN + RADIUS);
float sat = (radiiCount - saturation) / (float) radiiCount;
g2.scale(sat, sat);
g2.rotate(-Math.PI / arcCount);
for (int ii = 0; ii < arcCount; ++ii) {
g2.setColor(Color.getHSBColor((ii / (float) arcCount), sat, 1.0f));
g2.fill(colorArc);
g2.rotate((Math.PI * 2) / arcCount);
}
g2.setTransform(savedTransform);
}
g2.translate(MARGIN, MARGIN);
g2.setColor(Color.black);
g2.draw(outerCircle);
g2.setTransform(savedTransform);
g2.dispose();
return image;
}

private static final Map<RenderingHints.Key, Object> hintMap = makeRenderingHints();

private static Map<RenderingHints.Key, Object> makeRenderingHints() {
Map<RenderingHints.Key, Object> map = new HashMap<RenderingHints.Key, Object>();
map.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
map.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
map.put(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
return map;
}

public static void main(String[] args) {
makeFrame();
}

private static void makeFrame() {
final JFrame mainFrame = new JFrame( " BicubicFlickerBug " );
mainFrame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
mainFrame.add(new BicubicFlickerBug6(), BorderLayout.CENTER);
mainFrame.setExtendedState(Frame.MAXIMIZED_BOTH);
mainFrame.setVisible(true);
}

public BicubicFlickerBug6() {
super(new BorderLayout());

JToolBar toolBar = new JToolBar();
toolBar.setOrientation(SwingConstants.VERTICAL);
add(toolBar, BorderLayout.WEST);
toolBar.setFloatable(false);

JButton repaintButton = new JButton( " RePaint " );
toolBar.add(repaintButton);
repaintButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
doRepaint();
}
});

ButtonGroup bGroup = new ButtonGroup();

JRadioButton biCubicButton = new JRadioButton( " BiCubic " );
bGroup.add(biCubicButton);
JRadioButton biLinearButton = new JRadioButton( " BiLinear " );
bGroup.add(biLinearButton);
JRadioButton nearestNeighborButton = new JRadioButton( " Nearest Neighbor " );
bGroup.add(nearestNeighborButton);
nearestNeighborButton.setSelected(true);

biCubicButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
hintMap.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
}
});
biLinearButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
hintMap.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
}
});
nearestNeighborButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
hintMap.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
}
});

toolBar.add(nearestNeighborButton);
toolBar.add(biLinearButton);
toolBar.add(biCubicButton);

photoView = createPhotoView();
add(photoView, BorderLayout.CENTER);
}

private void doRepaint() {
//photoView.removeAll();
Image filteredImage = doFilter();
scalingImage.setImage(filteredImage);
//photoView.add(scalingImage);
scalingImage.repaint();
}

private JComponent createPhotoView() {
scalingImage.setImage(loadedImage);
JPanel photoView = new JPanel(new GridLayout(0, 1));
photoView.add(scalingImage);
return photoView;
}

private class ScaledImage extends Canvas {
private Image image;

public ScaledImage(Image image) {
this.image = image;

addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(final ComponentEvent e) {
BicubicFlickerBug6.this.revalidate();
}
});
}

public void setImage(Image image) {
this.image = image;
repaint();
}

@Override
public void paint(final Graphics g) {

if (image == null) { return; }
int displayWidth = getWidth();
int imageWidth = image.getWidth(this);
double widthScale = ((double) displayWidth) / imageWidth;
int displayHeight = getHeight();
int imageHeight = image.getHeight(this);
double heightScale = ((double) displayHeight) / imageHeight;

boolean useWidth;
double scale;
if (widthScale < heightScale) {
useWidth = true;
scale = widthScale;
} else {
useWidth = false;
scale = heightScale;
}
int scaledWidth = (int) Math.round(scale * imageWidth);
int scaledHeight = (int) Math.round(scale * imageHeight);

Graphics2D g2 = (Graphics2D) g;
setRenderHints(g2);
if (useWidth) {
long ht = Math.round(scaledHeight);
int deltaY = (int) ((displayHeight - ht) / 2);
g2.drawImage(image, 0, deltaY, scaledWidth, scaledHeight, this);
} else {
long wd = Math.round(scaledWidth);
int deltaX = (int) ((displayWidth - wd) / 2);
g2.drawImage(image, deltaX, 0, scaledWidth, scaledHeight, this);
}
}

}

private Image doFilter() {
ImageFilter filter = new NullFilter();
FilteredImageSource imageSource = new FilteredImageSource(loadedImage.getSource(), filter);
return photoView.createImage(imageSource);
}


private static void setRenderHints(Graphics2D g2) {
g2.setRenderingHints(hintMap);
}

private static class NullFilter extends RGBImageFilter {

public NullFilter() { }

@Override
public int filterRGB(final int x, final int y, final int rgb) {
return rgb;
}
}

}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
The only workaround is to use JRE 1.6
Comments
The criteria for 3rd deferral bulk request bugs: - Not P2 - Not tck-red labeled - Not regression labeled - Not findbugs, parfait, eht, fuzzing labeled -------------------------------------------------------------------- We reviewed these today. We're OK with deferring them. I assume these are for deferral to JDK 9. Please update these so the label says 8-defer-approved and then set FixVersion to 9. Thanks! Kind regards, Mathias
27-09-2013

jdk8: SQE OK to defer
26-09-2013

The regression in performance happens during the transition from Apple's Java SE 6 to Mac OS X JDK7 with the introduction of layers and opengl for graphics rendering. Before that, Mac OS X Java SE 6 was using SPI (shared memory) to render graphics. See also https://wiki.openjdk.java.net/display/MacOSXPort/Java2D+Graphics+Design+Plan (look for Q: Why use OpenGL?). The slowdown as evidenced from the sample program submitted can be mitigated by using a JFrame instead of a Canvas, although Java SE 6 still performs better than JDK7/8 with JFrame. I'm requesting to defer this by adding 8-client-defer-candidate label.
20-09-2013

if you suggest to defer, please add the label and write the justification for SQE here
20-09-2013

Hi Victor, This most likely was introduced ever since JDK7, i.e., not JDK8-specific, with the mac-osx-port's introduction of using layers and opengl for rendering graphics. Before that, Mac OS X Java SE 6 was using SPI (shared memory) to render graphics. See also https://wiki.openjdk.java.net/display/MacOSXPort/Java2D+Graphics+Design+Plan (look for Q: Why use OpenGL?). Thanks.
19-09-2013

what is introduced in release? is it jdk8 affected?
13-09-2013

My thesis on the slowness of ScaledImage extending Canvas vs. JComponent: The java.awt.Canvas <=> CGLLayerSurfaceData vs. the javax.swing.JComponent <=> CGLOffScreenSurfaceData correspondence is the difference between the slow rendering when ScaledImage extends Canvas. Notice the following snippet of code at the end of the function Java_sun_java2d_opengl_OGLRenderQueue_flushBuffer within shared/native/sun/java2d/opengl/OGLRenderQueue.c: if (oglc != NULL) { RESET_PREVIOUS_OP(); if (sync) { j2d_glFinish(); } else { j2d_glFlush(); } OGLSD_Flush(env); } The OGLSD_Flush() call is platform-dependent (actually no-op on other platforms) and for Mac OS X is defined within macosx/native/sun/java2d/opengl/CGLSurfaceData.m: static uint32_t OGLSD_Flush_isLayer_count = 0; static uint32_t OGLSD_Flush_isNotLayer_count = 0; void OGLSD_Flush(JNIEnv *env) { OGLSDOps *dstOps = OGLRenderQueue_GetCurrentDestination(); if (dstOps != NULL) { CGLSDOps *dstCGLOps = (CGLSDOps *)dstOps->privOps; CGLLayer *layer = (CGLLayer*)dstCGLOps->layer; if (layer != NULL) { ++OGLSD_Flush_isLayer_count; [JNFRunLoop performOnMainThreadWaiting:NO withBlock:^(){ AWT_ASSERT_APPKIT_THREAD; [layer setNeedsDisplay]; #ifdef REMOTELAYER /* If there's a remote layer (being used for testing) * then we want to have that also receive the texture. * First sync. up its dimensions with that of the layer * we have attached to the local window and tell it that * it also needs to copy the texture. */ if (layer.remoteLayer != nil) { CGLLayer* remoteLayer = layer.remoteLayer; remoteLayer.target = GL_TEXTURE_2D; remoteLayer.textureID = layer.textureID; remoteLayer.textureWidth = layer.textureWidth; remoteLayer.textureHeight = layer.textureHeight; [remoteLayer setNeedsDisplay]; } #endif /* REMOTELAYER */ }]; } else ++OGLSD_Flush_isNotLayer_count; } } Note that I intentionally add two counter variables to keep account of the CGLLayer vs. non-CGLLayer calls. Also of interest is the instance method blitTexture of CGLLayer as defined within macosx/native/sun/java2d/opengl/CGLLayer.m. I also add a counter to record the call count. static uint32_t blitCount = 0; // use texture (intermediate buffer) as src and blit it to the layer - (void) blitTexture { if (textureID == 0) { return; } glEnable(target); glBindTexture(target, textureID); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); // srccopy float swid = 1.0f, shgt = 1.0f; if (target == GL_TEXTURE_RECTANGLE_ARB) { swid = textureWidth; shgt = textureHeight; } glBegin(GL_QUADS); glTexCoord2f(0.0f, 0.0f); glVertex2f(-1.0f, -1.0f); glTexCoord2f(swid, 0.0f); glVertex2f( 1.0f, -1.0f); glTexCoord2f(swid, shgt); glVertex2f( 1.0f, 1.0f); glTexCoord2f(0.0f, shgt); glVertex2f(-1.0f, 1.0f); glEnd(); glBindTexture(target, 0); glDisable(target); ++blitCount; } The -[CGLLayer blitTexture] is important since it implements the native callback for the Core Animation OpenGL Layer drawing, as the following backtrace from gdb demonstrates: (gdb) bt #0 -[CGLLayer blitTexture] (self=0x1001ad2a0, _cmd=0x10f58f5cb) at CGLLayer.m:126 #1 0x000000010f521bca in Java_sun_java2d_opengl_CGLLayer_blitTexture (env=0x1008caa18, cls=0x7fff5fbfb018, layerPtr=4296725152) at CGLLayer.m:213 #2 0x000000010582495b in ?? () #3 0x0000000105806298 in ?? () #4 0x0000000105800681 in ?? () #5 0x00000001017e90a4 in JavaCalls::call_helper (result=0x7fff5fbfb748, m=0x7fff5fbfb658, args=0x7fff5fbfb510, __the_thread__=0x1008ca800) at javaCalls.cpp:402 #6 0x0000000101b8a0b0 in os::os_exception_wrapper (f=0x1017e87ce <JavaCalls::call_helper(JavaValue*, methodHandle*, JavaCallArguments*, Thread*)>, value=0x7fff5fbfb748, method=0x7fff5fbfb658, args=0x7fff5fbfb510, thread=0x1008ca800) at os_bsd.cpp:3762 #7 0x00000001017e87c8 in JavaCalls::call (result=0x7fff5fbfb748, method=@0x7fff5fbfb658, args=0x7fff5fbfb510, __the_thread__=0x1008ca800) at javaCalls.cpp:307 #8 0x0000000101886c48 in jni_invoke_nonstatic (env=0x1008caa18, result=0x7fff5fbfb748, receiver=0x103689ba0, call_type=JNI_VIRTUAL, method_id=0x10063a488, args=0x7fff5fbfb6f8, __the_thread__=0x1008ca800) at jni.cpp:1387 #9 0x00000001018530a9 in jni_CallVoidMethodV (env=0x1008caa18, obj=0x103689ba0, methodID=0x10063a488, args=0x7fff5fbfb8f0) at jni.cpp:1931 #10 0x00000001035e8512 in JNFCallVoidMethod () #11 0x000000010f52107b in -[CGLLayer drawInCGLContext:pixelFormat:forLayerTime:displayTime:] (self=0x1001ad2a0, _cmd=0x7fff8f27a100, glContext=0x10d869c00, pixelFormat=0x100648ac0, timeInterval=436709.90068866702, timeStamp=0x0) at CGLLayer.m:151 #12 0x00007fff94f9fa36 in CAOpenGLLayerDraw () #13 0x00007fff94f9f622 in -[CAOpenGLLayer _display] () #14 0x00007fff94ea1425 in CA::Layer::display_if_needed () #15 0x00007fff94ea0c3f in CA::Layer::layout_and_display_if_needed () #16 0x00007fff94e96417 in CA::Context::commit_transaction () #17 0x00007fff94e961e7 in CA::Transaction::commit () #18 0x00007fff94e96003 in CA::Transaction::observer_callback () #19 0x00007fff941c5417 in __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ () #20 0x00007fff941c5381 in __CFRunLoopDoObservers () #21 0x00007fff941a0104 in CFRunLoopRunSpecific () #22 0x00007fff8cdcbeb4 in RunCurrentEventLoopInMode () #23 0x00007fff8cdcbb94 in ReceiveNextEventCommon () #24 0x00007fff8cdcbae3 in BlockUntilNextEventMatchingListInMode () #25 0x00007fff8e9c0563 in _DPSNextEvent () #26 0x00007fff8e9bfe22 in -[NSApplication nextEventMatchingMask:untilDate:inMode:dequeue:] () #27 0x00000001047d3419 in -[NSApplicationAWT nextEventMatchingMask:untilDate:inMode:dequeue:] (self=0x100149920, _cmd=0x7fff8f1ee3f4, mask=18446744073709551615, expiration=0x422d63c37f00000d, mode=0x7fff7cf341c0, deqFlag=1 '\001') at NSApplicationAWT.m:330 #28 0x00007fff8e9b71d3 in -[NSApplication run] () #29 0x00000001047d2fe8 in +[NSApplicationAWT runAWTLoopWithApp:] (self=0x1047da2e8, _cmd=0x10f599d91, app=0x100149920) at NSApplicationAWT.m:295 #30 0x000000010f58203a in -[AWTStarter starter:] (self=0x103607e00, _cmd=0x10f599d33, args=0x103613840) at awt.m:374 #31 0x00007fff8c88cd3a in __NSThreadPerformPerform () #32 0x00007fff9417db31 in __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ () #33 0x00007fff9417d455 in __CFRunLoopDoSources0 () #34 0x00007fff941a07f5 in __CFRunLoopRun () #35 0x00007fff941a00e2 in CFRunLoopRunSpecific () #36 0x0000000100009318 in ParkEventLoop () at java_md_macosx.c:333 #37 0x0000000100009426 in MacOSXStartup (argc=2, argv=0x10010fe30) at java_md_macosx.c:366 #38 0x0000000100009936 in CreateExecutionEnvironment (pargc=0x7fff5fbff55c, pargv=0x7fff5fbff550, jrepath=0x7fff5fbfecb0 "/Volumes/data/projects/java/jdk/jdk8/jdk8/build/macosx-x86_64-normal-server-slowdebug/jdk", so_jrepath=1024, jvmpath=0x7fff5fbff0b0 "/Volumes/data/projects/java/jdk/jdk8/jdk8/build/macosx-x86_64-normal-server-slowdebug/jdk/lib/server/libjvm.dylib", so_jvmpath=1024, jvmcfg=0x7fff5fbfe8b0 "/Volumes/data/projects/java/jdk/jdk8/jdk8/build/macosx-x86_64-normal-server-slowdebug/jdk/lib/jvm.cfg", so_jvmcfg=1024) at java_md_macosx.c:491 #39 0x0000000100001677 in JLI_Launch (argc=2, argv=0x10010fe50, jargc=1, jargv=0x0, appclassc=1, appclassv=0x0, fullversion=0x100014d40 "1.8.0-internal-debug-johnny_2013_06_24_11_04-b00", dotversion=0x100014d71 "1.8", pname=0x100014d36 "java", lname=0x100014d36 "java", javaargs=0 '\000', cpwildcard=1 '\001', javaw=0 '\000', ergo=0) at java.c:236 #40 0x000000010000ecca in main (argc=2, argv=0x7fff5fbff698) at main.c:125 (gdb) -------------------------------------------------------------------------------- Ok, now let's show the metrics for Canvas vs. JComponent. For the metrics, I run the sample program with the modified jdk8 build under gdb, I first repaint with Bilinear, then with Bicubic, interrupt the inferior, and then collect the metrics: o Canvas: (gdb) print OGLSD_Flush_isLayer_count $5 = 2821 (gdb) print OGLSD_Flush_isNotLayer_count $6 = 12 (gdb) print blitCount $7 = 1487 (gdb) o JComponent: (gdb) print OGLSD_Flush_isLayer_count $8 = 37 (gdb) print OGLSD_Flush_isNotLayer_count $9 = 2798 (gdb) print blitCount $10 = 30 (gdb) Note that the total OGLSD_Flush count for both cases are about equal (2833 vs. 2835), but the CGLLayerSurfaceData-originated drawing of the Canvas case results in a large number of Core Animation OpenGL Layer drawing, which also corroborates the 2013-07-09 17:43 comments quoted below: The CGLFlushDrawable and glFlsuhRenderAPPLE account for a big total time differences according to the OpenGL Profile (in usec): (159783 + 114476) = 274,259 for Canvas vs. (721 + 25494) = 26,215 for JComponent
05-08-2013

My suspicion of excessive glFlush() for the Canvas case did not pan out. The number of glFlush() calls for the JComponent case is about equal to the Canvas case. But there does exist big differences between the call counts for CGLFlushDrawable, glFlushRenderAPPLE, and glBindTexture, etc. The CGLFlushDrawable and glFlsuhRenderAPPLE account for a big total time differences according to the OpenGL Profile (in usec): (159783 + 114476) = 274,259 for Canvas vs. (721 + 25494) = 26,215 for JComponent Note that CGLFlushDrawable calls happen on the Mac OS X system thread, related to Core Video worker thread (most likely Open GL related as well), while the glFlushRenderAPPLE calls happen on the AppKit main thread called from [CAOpenGLLayer _display] (again Open GL related).
10-07-2013

Attaching two screen shots taken using the 'OpenGL Profiler' on Mac OS X. This is running the sample program with ScaledImage extending Canvas with my locally built jdk8. The collection period corresponds to me clicking on the Repaint with Bicubic interpolation. I have a strong suspicion that the CGLayerSurfaceData that is associated with the Canvas heavyweight component is what is causing the excessively glFlush() calls and the perceived rendering delay. I will measure the ScaledImage extending JComponent case next.
09-07-2013

The results for the previous two comments dated 2013-07-08 are obtained with ScaledImage extends JComponent, not Canvas. With Canvas and jdk7/8, the rendering is noticeably slow, as the submitter pointed out. Jdk7/8 started to use the opnegl pipeline to render things. For Canvas, the sun.java2d.pipe.BufferedMaskBlit.MaskBlit routine eventually delegates to OGLBlitLoops.Blit() static function, with oglDst an instance of CGLSurfaceData$CGLayerSurfaceData. Highly suspect that it is the CGLLayerSurfaceData which makes it so much slow compared to JComponent, where oglDst is an instance of CGLSurfaceData$CGLOffScreenSurfaceData.
08-07-2013

This shows (on my MacBook Air with 10.8.4) that jdk6 from Apple renders about twice as fast as jdk7. The output for 1.6.0 is first with repaint/bilinear, followed by repaint/bicubic. The output for jdk7 is first with repaint/bilinear, followed by repaint/bicubic. [15:59:25] johnny:/Volumes/johnny/Developer/java/JDK-8017247/src $ /Library/Java/JavaVirtualMachines/1.6.0_45-b06-451.jdk/Contents/Home/bin/java BicubicFlickerBug6 g2.drawImage(image=BufferedImage@5a02c35e: type = 6 ColorModel: #pixelBits = 32 numComponents = 4 color space = java.awt.color.ICC_ColorSpace@6b687e7c transparency = 3 has alpha = true isAlphaPre = false ByteInterleavedRaster: width = 620 height = 620 #numDataElements 4 dataOff[0] = 3, deltaX=215, 0, scaledWidth=852, scaledHeight=852, BicubicFlickerBug6$ScaledImage[,0,0,1282x852,alignmentX=0.0,alignmentY=0.0,border=,flags=0,maximumSize=,minimumSize=,preferredSize=]) paint() takes 8 ms. g2.drawImage(image=apple.awt.OSXImage@9ba6076, deltaX=215, 0, scaledWidth=852, scaledHeight=852, BicubicFlickerBug6$ScaledImage[,0,0,1282x852,alignmentX=0.0,alignmentY=0.0,border=,flags=0,maximumSize=,minimumSize=,preferredSize=]) paint() takes 133 ms. [15:59:52] johnny:/Volumes/johnny/Developer/java/JDK-8017247/src $ /Library/Java/JavaVirtualMachines/1.6.0_45-b06-451.jdk/Contents/Home/bin/java BicubicFlickerBug6 g2.drawImage(image=BufferedImage@5a02c35e: type = 6 ColorModel: #pixelBits = 32 numComponents = 4 color space = java.awt.color.ICC_ColorSpace@6b687e7c transparency = 3 has alpha = true isAlphaPre = false ByteInterleavedRaster: width = 620 height = 620 #numDataElements 4 dataOff[0] = 3, deltaX=215, 0, scaledWidth=852, scaledHeight=852, BicubicFlickerBug6$ScaledImage[,0,0,1282x852,alignmentX=0.0,alignmentY=0.0,border=,flags=0,maximumSize=,minimumSize=,preferredSize=]) paint() takes 10 ms. g2.drawImage(image=apple.awt.OSXImage@20968fda, deltaX=215, 0, scaledWidth=852, scaledHeight=852, BicubicFlickerBug6$ScaledImage[,0,0,1282x852,alignmentX=0.0,alignmentY=0.0,border=,flags=0,maximumSize=,minimumSize=,preferredSize=]) paint() takes 186 ms. [15:59:59] johnny:/Volumes/johnny/Developer/java/JDK-8017247/src $ java BicubicFlickerBug6 g2.drawImage(image=BufferedImage@5fe82a70: type = 6 ColorModel: #pixelBits = 32 numComponents = 4 color space = java.awt.color.ICC_ColorSpace@33a5041 transparency = 3 has alpha = true isAlphaPre = false ByteInterleavedRaster: width = 620 height = 620 #numDataElements 4 dataOff[0] = 3, deltaX=215, 0, scaledWidth=852, scaledHeight=852, BicubicFlickerBug6$ScaledImage[,0,0,1282x852,alignmentX=0.0,alignmentY=0.0,border=,flags=2,maximumSize=,minimumSize=,preferredSize=]) paint() takes 39 ms. g2.drawImage(image=sun.awt.image.ToolkitImage@4a5821d0, deltaX=215, 0, scaledWidth=852, scaledHeight=852, BicubicFlickerBug6$ScaledImage[,0,0,1282x852,alignmentX=0.0,alignmentY=0.0,border=,flags=2,maximumSize=,minimumSize=,preferredSize=]) paint() takes 273 ms. [16:00:15] johnny:/Volumes/johnny/Developer/java/JDK-8017247/src $ java BicubicFlickerBug6 g2.drawImage(image=BufferedImage@29a260e7: type = 6 ColorModel: #pixelBits = 32 numComponents = 4 color space = java.awt.color.ICC_ColorSpace@6387f30b transparency = 3 has alpha = true isAlphaPre = false ByteInterleavedRaster: width = 620 height = 620 #numDataElements 4 dataOff[0] = 3, deltaX=215, 0, scaledWidth=852, scaledHeight=852, BicubicFlickerBug6$ScaledImage[,0,0,1282x852,alignmentX=0.0,alignmentY=0.0,border=,flags=2,maximumSize=,minimumSize=,preferredSize=]) paint() takes 29 ms. g2.drawImage(image=sun.awt.image.ToolkitImage@7b65c301, deltaX=215, 0, scaledWidth=852, scaledHeight=852, BicubicFlickerBug6$ScaledImage[,0,0,1282x852,alignmentX=0.0,alignmentY=0.0,border=,flags=2,maximumSize=,minimumSize=,preferredSize=]) paint() takes 304 ms. [16:00:27] johnny:/Volumes/johnny/Developer/java/JDK-8017247/src $
08-07-2013

The following stack trace running under NetBeans shows that that MaskBlit being performed 1392 times on a transformed image (with scaled height = 1392), scanline by scanline, which seems to be the bottleneck of jdk7/8 compared to jdk6. "AWT-EventQueue-0" sun.java2d.pipe.BufferedMaskBlit.MaskBlit(BufferedMaskBlit.java:98) sun.java2d.pipe.DrawImage.renderImageXform(DrawImage.java:532) sun.java2d.opengl.OGLDrawImage.renderImageXform(OGLDrawImage.java:89) sun.java2d.pipe.DrawImage.transformImage(DrawImage.java:264) <================ Not acceleratable sun.java2d.pipe.DrawImage.scaleImage(DrawImage.java:124) sun.java2d.pipe.DrawImage.scaleImage(DrawImage.java:1049) sun.java2d.SunGraphics2D.drawImage(SunGraphics2D.java:3144) sun.awt.image.ImageRepresentation.drawToBufImage(ImageRepresentation.java:845) sun.java2d.pipe.DrawImage.scaleImage(DrawImage.java:1056) sun.java2d.pipe.ValidatePipe.scaleImage(ValidatePipe.java:207) sun.java2d.SunGraphics2D.drawImage(SunGraphics2D.java:3144) sun.java2d.SunGraphics2D.drawImage(SunGraphics2D.java:3084) BicubicFlickerBug6$ScaledImage.paint(BicubicFlickerBug6.java:227) javax.swing.JComponent.paintChildren(JComponent.java:887) javax.swing.JComponent.paint(JComponent.java:1063) javax.swing.JComponent.paintToOffscreen(JComponent.java:5219) javax.swing.RepaintManager$PaintManager.paintDoubleBuffered(RepaintManager.java:1536) javax.swing.RepaintManager$PaintManager.paint(RepaintManager.java:1459) javax.swing.RepaintManager.paint(RepaintManager.java:1252) javax.swing.JComponent._paintImmediately(JComponent.java:5167) javax.swing.JComponent.paintImmediately(JComponent.java:4978) javax.swing.RepaintManager$3.run(RepaintManager.java:811) javax.swing.RepaintManager$3.run(RepaintManager.java:794) java.security.AccessController.doPrivileged(AccessController.java) java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:75) javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:794) javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:769) javax.swing.RepaintManager.prePaintDirtyRegions(RepaintManager.java:718) javax.swing.RepaintManager.access$1100(RepaintManager.java:62) javax.swing.RepaintManager$ProcessingRunnable.run(RepaintManager.java:1684) java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:251) java.awt.EventQueue.dispatchEventImpl(EventQueue.java:735) java.awt.EventQueue.access$400(EventQueue.java:97) java.awt.EventQueue$3.run(EventQueue.java:688) java.awt.EventQueue$3.run(EventQueue.java:685) java.security.AccessController.doPrivileged(AccessController.java) java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:75) java.awt.EventQueue.dispatchEvent(EventQueue.java:705) java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:220) java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:135) java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:123) java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:119) java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:111) java.awt.EventDispatchThread.run(EventDispatchThread.java:97) A code snippet inside the DrawImage.renderImageXform() method: /* * Now copy the results, scanline by scanline, into the dest. * The edges array helps us minimize the work. */ int index = 2; for (int y = edges[0]; y < edges[1]; y++) { int relx1 = edges[index++]; int relx2 = edges[index++]; if (relx1 >= relx2) { continue; } if (maskblit != null) { maskblit.MaskBlit(tmpData, dstData, sg.composite, clip, relx1, y, dx1+relx1, dy1+y, relx2 - relx1, 1, null, 0, 0); } else { blit.Blit(tmpData, dstData, sg.composite, clip, relx1, y, dx1+relx1, dy1+y, relx2 - relx1, 1); } } shows that MaskBlit being performed 1392 times for Bicubic interpolation.
08-07-2013

For both jdk6 and jdk8, all the three interpolation methods are rendered twice, as can be verified by doing a Thread.dumpStack() within the paint(g) method. This happens whether ScaledImage is extending Canvas or JComponent. The problem looks to be application-specific and only obvious to the submitter's eyes because BiCubic rendering is slow with Canvas. If I change the following instances of method calls within paint(g): 1. int imageWidth = image.getWidth(null /* this */); 2. int imageHeight = image.getHeight(null /* this */); 3. g2.drawImage(image, 0, deltaY, scaledWidth, scaledHeight, null /* this */); 4. g2.drawImage(image, deltaX, 0, scaledWidth, scaledHeight, null /* this */); to pass null, instead of (ScaledImage)this, as the ImageObserver, the double rendering goes away. This looks most likely to be the interaction between the ScaledImage.paint(g) and Component.imageUpdate() which causes an extra paint. Adding the above changes to the sample program, in addition to the ScaledImage extends JComponent makes the sample program more responsive.
28-06-2013

Sounds like this may have used "Core Image" API. Needs more investigation to confirm that and see if there's anything similar we can do. JComponent will draw to a back buffer which could hide the flickering. Why is bicubic being rendered twice ? Or is that not reproducible?
27-06-2013

The attached file Comparison.txt shows running my locally built jdk8 compared to the Apple jdk6 with ScaledImage extends Canvas (a heavyweight component) first, then with ScaledImage extends JComponent. The output messages are a result of the System.out.println() statements added to the paint(Graphics g) method of the ScaledImage class. From the output, we can see that with jdk6, the ScaledImage.image is an instance of apple.awt.OSXImage, while post-jdk6 (jdk7/jdk8), the ScaledImage.image is an instance of sun.awt.image.ToolkitImage. Without having access to the Apple jdk6 source code, I can only imagine the apple.awt.OSXImage class as a special kind of Image with possible hardware acceleration for java.awt.Canvas drawing. And that the Mac OS X JDK port took out the apple.awt.OSXImage. The result is the slowness that the submitter sees when embedding the Canvas within a JPanel. As explained in the previous comment, if we have ScaledImage extending JComponent, the slowness and flickering as observed by the submitter is gone. Although it is still slower than jdk6 if I measure the time it takes for the ScaledImage.paint(Graphics g) method. The special apple.awt.OSXImage in Apple's jdk6 and its pipeline might be the reason.
27-06-2013

The attached file BicubicFlickerBug6.java modifies the original program a little bit: instead of ScaledImage extends Canvas, ScaledImage now extends JComponent. On my Built-in iMac Display, 27-inch (2560 x 1440), NVIDIA GeForce GTX 660M 512 MB graphics, 10.8.3, I do not feel the flicker when clicking on the Repaint with BiCubic set, using a locally built debugged jdk8. Such is also the case on a 1440x900 MacBook Air running 10.8.4 with jdk7.
27-06-2013