JDK-8069348 : SunGraphics2D.copyArea() does not properly work for scaled graphics in D3D
  • Type: Bug
  • Component: client-libs
  • Sub-Component: 2d
  • Affected Version: 9
  • Priority: P2
  • Status: Resolved
  • Resolution: Fixed
  • OS: windows
  • Submitted: 2015-01-20
  • Updated: 2016-07-13
  • Resolved: 2016-03-11
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.
JDK 9
9 b114Fixed
Related Reports
Duplicate :  
Duplicate :  
Relates :  
Relates :  
Relates :  
Relates :  
Relates :  
Description
Run the SwingSet2 sample on Windows with enabled d3d option (-Dsun.java2d.d3d=true) ui scale 2 (-Dsun.java2d.uiScale=2) and drag or scroll an internal frame. There are artifacts on the internal frame (see the attached screen shot).


Below is a simple test case that allows to reproduce the issue:
- Run the code on Windows with enabled d3d (option -Dsun.java2d.d3d=true)
- The copied image is not properly shown (see the attached image): 
-------------------------------------------- 
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.VolatileImage;

public class CopyImageTest extends Frame {

    private static final int W = 800;
    private static final int H = 800;
    private static final double scale = 1.3;

    private VolatileImage vImg;

    public CopyImageTest() throws HeadlessException {

        setSize(W, H);
        vImg = createVolatileImage();

        addWindowListener(new WindowAdapter() {

            @Override
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });
    }

    VolatileImage createVolatileImage() {
        return getGraphicsConfiguration().createCompatibleVolatileImage(W, H);
    }

    // rendering to the image
    void renderOffscreen() {
        do {
            if (vImg.validate(getGraphicsConfiguration()) == VolatileImage.IMAGE_INCOMPATIBLE) {
                // old vImg doesn't work with new GraphicsConfig; re-create it
                vImg = createVolatileImage();
            }
            Graphics2D g = vImg.createGraphics();
            //
            // miscellaneous rendering commands...
            //
            g.scale(scale, scale);

            g.setColor(Color.YELLOW);
            g.fillRect(0, 0, W, H);

            g.setColor(Color.ORANGE);

            g.fillRect(50, 50, 50, 50);
            g.setColor(Color.BLUE);
            int d = 3;
            g.drawRect(50 + d, 50 + d, 50 - 2 * d, 50 - 2 * d);

            int N = 2;
            int s1 = 5;
            int s2 = 3;

            for (int i = 0; i < N; i++) {
                g.copyArea(50 + i * s1, 50 + i * s2, 50, 50, s1, s2);

            }
            g.dispose();
        } while (vImg.contentsLost());
    }

    @Override
    public void paint(Graphics gScreen) {
        super.paint(gScreen); //To change body of generated methods, choose Tools | Templates.
        // copying from the image (here, gScreen is the Graphics
        // object for the onscreen window)
        do {
            int returnCode = vImg.validate(getGraphicsConfiguration());
            if (returnCode == VolatileImage.IMAGE_RESTORED) {
                // Contents need to be restored
                renderOffscreen(); // restore contents
            } else if (returnCode == VolatileImage.IMAGE_INCOMPATIBLE) {
                // old vImg doesn't work with new GraphicsConfig; re-create it
                vImg = createVolatileImage();
                renderOffscreen();
            }
            gScreen.drawImage(vImg, 0, 0, this);
        } while (vImg.contentsLost());
    }

    public static void main(String[] args) {
        new CopyImageTest().setVisible(true);
    }
}
-------------------------------------------- 
Comments
the issue seemingly affects JTable scrolling (can see some rubbish in the table cells after scroll; Win8 + HiDPI)
18-02-2016

The proposed fix: http://cr.openjdk.java.net/~alexsch/8069348/webrev.04/ http://mail.openjdk.java.net/pipermail/2d-dev/2016-January/006249.html
29-01-2016

I returned the test case back to the description. The test case works with the provided fix: http://mail.openjdk.java.net/pipermail/2d-dev/2015-November/005885.html
20-11-2015

I don't think it was wise to remove the original test case. The problem with SwingSet2 is likely related, but the test case showed problems under different conditions than SwingSet2. It is important to investigate both SwingSet2 *AND* the original test case. In particular, running the test case with uiScale=1 showed the problem, but running SwingSet2 with uiScale=1 does not show the problem. And conversely, running the test case with uiScale>1 does not show the problem, but then SwingSet2 starts having problems. This bug cannot be closed until the original test case works. It may be that the same fix for SwingSet2 fixes the original test case, but we must test the original test case or we do the original submitter a disservice by making the assumption...
20-11-2015

Right, it is a similar thing which we discussed already. The OverlapSafeBlit can solve undefined behavior in the drawImage as well. But probably the copy via separate buffer can be faster in some cases
17-11-2015

SG2D.copyArea is adjusting for the fact that overlapping blits are not guaranteed to work correctly. Actually, it makes a further assumption that backward blits are fine (so dy < 0 is fine even if overlapping and dy==0,dx<0 is also fine), but any blit that blits "forward" needs to be done piecewise from the far end. It computes the maximum non-overlapping chunk that can be copied, so if only half of the source is overlapped then it can do the sequence in 2 blits. The worst case is doing copyArea with dx == 0, dy == +1 in which case it is done scanline by scanline. This is why the SD is consulted first since it often has access to an overlap-protected blit that can do it in one chunk (in reality it is likely doing it "backwards" as well, but from a much lower level). We could potentially write a new OverlapSafeBlit primitive that would do the tests at a lower level and if one is found then we could use that in one chunk, but we'd need to still support the overlap protection if we encounter a case where the OvSafeBlit is not found.
17-11-2015

Note that scrolling down the page is considered "safe" by the tests, but scrolling up (which copies bits downward) induces the "blit N lines at a time" code in the doCopyArea() backup code. If the overhead of doing piecewise blits is measurable then we would do better to keep the OGLSD.copyArea() code intact...
17-11-2015

Note that when CGLSurfaceData.copyarea was updated the to take care of scaled graphics(for retina), the code in SG2D.doCopyArea() thrown an exception in that time. And only later doCopyArea was updated to take care of scaled graphics. So in general in OGL the difference in performance should not be a problem because both methods are implemented via glCopyPixels. I think that the code in OGLSurfaceData.copyArea can be removed.
17-11-2015

An addendum to the previous comment - it looks like using OpenGL on Windows uses an OGLSurface2SurfaceBlit so perhaps that was fixed when we added MacOS retina support and a similar fix can be applied to the D3DS2SBlit. Also, it points out that OGLSurfaceData.copyArea (shared between Windows and MacOS) is also rejecting a scaled copyArea and punting back to the regular blit implementation. Since the blit implementation at least uses a hw-accelerated blit loop in both cases it is not clear that we are losing any performance, but it should be a simple fix to get both SD.copyArea() methods working with a scaled graphics...
16-11-2015

Running with SwingSet I see the problem with the default D3D pipeline, but not if I disabled D3D (sun.java2d.d3d=false) so the problem is likely isolated to the D3DSurfaceToSurfaceBlit... It also works fine with OpenGL (sun.java2d.opengl=true) so this isn't a hardware acceleration issue. On a related note, D3DSurfaceData.copyArea() rejects a scaled transform so we are going to be using the non-accelerated version of copyArea. If it did not reject the scale transform that might mask the bug since we would then no longer be calling the D3DSurfaceToSurfaceBlit any more, so we should make sure we fix the D3DS2SBlit even if we bypass it by fixing D3DSD.copyArea()...
16-11-2015

SunGraphics2D.copyArea() does not have artifacts with Buffered image
20-01-2015

SunGraphics2D.copyArea() has artifacts with Volatile image
20-01-2015