changeset 5600:79894a464923

RT-31807: SwingInterop look blurry on hidpi display Reviewed-by: art, serb
author ant <anton.tarasov@oracle.com>
date Thu, 31 Oct 2013 18:14:02 +0400
parents a630648b31c2
children 9d8097dbe045
files modules/graphics/src/main/java/com/sun/glass/ui/Pixels.java modules/graphics/src/main/java/com/sun/javafx/embed/EmbeddedSceneInterface.java modules/graphics/src/main/java/com/sun/javafx/tk/quantum/EmbeddedScene.java modules/graphics/src/main/java/com/sun/javafx/tk/quantum/EmbeddedState.java modules/graphics/src/main/java/com/sun/javafx/tk/quantum/UploadingPainter.java modules/graphics/src/main/java/com/sun/javafx/tk/quantum/ViewPainter.java modules/swing/src/main/java/javafx/embed/swing/JFXPanel.java modules/swt/src/main/java/javafx/embed/swt/FXCanvas.java
diffstat 8 files changed, 203 insertions(+), 50 deletions(-) [+]
line wrap: on
line diff
--- a/modules/graphics/src/main/java/com/sun/glass/ui/Pixels.java	Thu Oct 31 09:26:29 2013 -0400
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/Pixels.java	Thu Oct 31 18:14:02 2013 +0400
@@ -125,17 +125,29 @@
         Application.checkEventThread();
         return this.scale;
     }
+    
+    public final float getScaleUnsafe() {
+        return this.scale;
+    }
 
     public final int getWidth() {
         Application.checkEventThread();
         return this.width;
     }
 
+    public final int getWidthUnsafe() {
+        return this.width;
+    }
+
     public final int getHeight() {
         Application.checkEventThread();
         return this.height;
     }
     
+    public final int getHeightUnsafe() {
+        return this.height;
+    }
+    
     public final int getBytesPerComponent() {
         Application.checkEventThread();
         return this.bytesPerComponent;
--- a/modules/graphics/src/main/java/com/sun/javafx/embed/EmbeddedSceneInterface.java	Thu Oct 31 09:26:29 2013 -0400
+++ b/modules/graphics/src/main/java/com/sun/javafx/embed/EmbeddedSceneInterface.java	Thu Oct 31 18:14:02 2013 +0400
@@ -28,7 +28,6 @@
 import java.nio.IntBuffer;
 
 import com.sun.javafx.scene.traversal.Direction;
-import com.sun.javafx.tk.TKSceneListener;
 import javafx.collections.ObservableList;
 import javafx.event.EventType;
 import javafx.scene.input.InputMethodEvent;
@@ -48,6 +47,11 @@
      * A notification about the embedded container is resized.
      */
     public void setSize(int width, int height);
+    
+    /*
+     * A notification about the scale factor is changed.
+     */
+    public void setPixelScaleFactor(float scale);
 
     /*
      * A request to fetch all the FX scene pixels into a offscreen buffer.
--- a/modules/graphics/src/main/java/com/sun/javafx/tk/quantum/EmbeddedScene.java	Thu Oct 31 09:26:29 2013 -0400
+++ b/modules/graphics/src/main/java/com/sun/javafx/tk/quantum/EmbeddedScene.java	Thu Oct 31 18:14:02 2013 +0400
@@ -49,6 +49,7 @@
 import com.sun.javafx.tk.Toolkit;
 import com.sun.prism.paint.Color;
 import com.sun.prism.paint.Paint;
+import com.sun.glass.ui.Pixels;
 
 final class EmbeddedScene extends GlassScene implements EmbeddedSceneInterface {
 
@@ -57,12 +58,13 @@
 
     private UploadingPainter        painter;
     private PaintRenderJob          paintRenderJob;
+    
+    private final EmbeddedSceneDnD dndDelegate;    
 
-    volatile IntBuffer textureBits;
-    volatile int bitsLineStride;
-
-    private final EmbeddedSceneDnD dndDelegate;
-
+    volatile IntBuffer  texBits;
+    volatile int        texLineStride; // pre-scaled
+    volatile float      texScaleFactor = 1.0f;
+ 
     public EmbeddedScene(HostInterface host, boolean depthBuffer, boolean antiAliasing) {
         super(depthBuffer, antiAliasing);
         sceneState = new EmbeddedState(this);
@@ -124,11 +126,18 @@
             System.err.println("EmbeddedScene.finishInputMethodComposition");
         }
     }
+    
+    @Override
+    public void setPixelScaleFactor(float scale) {
+        painter.setPixelScaleFactor(scale);
+        entireSceneNeedsRepaint();
+    }
 
     // Called by EmbeddedPainter on the render thread under renderLock
-    void uploadPixels(IntBuffer pixels, int stride) {
-        textureBits = pixels;
-        bitsLineStride = stride;
+    void uploadPixels(Pixels pixels) {
+        texBits = (IntBuffer)pixels.getPixels();
+        texLineStride = pixels.getWidthUnsafe();
+        texScaleFactor = pixels.getScaleUnsafe();
         if (host != null) {
             host.repaint();
         }
@@ -170,31 +179,44 @@
         });
     }
 
+    /**
+     * @param dest the destination buffer
+     * @param width the logical width of the buffer
+     * @param height the logical height of the buffer
+     * @param scale the scale factor
+     * @return 
+     */
     @Override
     public boolean getPixels(IntBuffer dest, int width, int height) {
         ViewPainter.renderLock.lock();
         try {
-            if (textureBits == null) return false;
+            // The dest buffer scale factor is expected to match painter.getPixelScaleFactor().
+            if (painter.getPixelScaleFactor() != texScaleFactor || texBits == null) {
+                return false;
+            }
+            width = (int)Math.round(width * texScaleFactor);
+            height = (int)Math.round(height * texScaleFactor);
+        
             dest.rewind();
-            textureBits.rewind();            
-            if (dest.capacity() != textureBits.capacity()) {
+            texBits.rewind();
+            if (dest.capacity() != texBits.capacity()) {
                 // Calculate the intersection of the dest & src images.
-                int w = Math.min(width, bitsLineStride);
-                int h = Math.min(height, textureBits.capacity() / bitsLineStride);
+                int w = Math.min(width, texLineStride);
+                int h = Math.min(height, texBits.capacity() / texLineStride);
 
                 // Copy the intersection to the dest.
                 // The backed array of the textureBits may not be available,
                 // so not relying on it.
                 int[] linebuf = new int[w];
                 for (int i = 0; i < h; i++) {
-                    textureBits.position(i * bitsLineStride);
-                    textureBits.get(linebuf, 0, w);
+                    texBits.position(i * texLineStride);
+                    texBits.get(linebuf, 0, w);
                     dest.position(i * width);
                     dest.put(linebuf);
                 }
                 return true;
             }
-            dest.put(textureBits);
+            dest.put(texBits);
             return true;
         } finally {
             ViewPainter.renderLock.unlock();
--- a/modules/graphics/src/main/java/com/sun/javafx/tk/quantum/EmbeddedState.java	Thu Oct 31 09:26:29 2013 -0400
+++ b/modules/graphics/src/main/java/com/sun/javafx/tk/quantum/EmbeddedState.java	Thu Oct 31 18:14:02 2013 +0400
@@ -51,7 +51,7 @@
         if (isValid()) {
             EmbeddedScene escene = (EmbeddedScene) scene;
             // Pixels are always stored in an IntBuffer for uploading
-            escene.uploadPixels((IntBuffer)pixels.getPixels(), getWidth());
+            escene.uploadPixels(pixels);
             if (uploadCount != null) {
                 uploadCount.decrementAndGet();
             }
--- a/modules/graphics/src/main/java/com/sun/javafx/tk/quantum/UploadingPainter.java	Thu Oct 31 09:26:29 2013 -0400
+++ b/modules/graphics/src/main/java/com/sun/javafx/tk/quantum/UploadingPainter.java	Thu Oct 31 18:14:02 2013 +0400
@@ -36,7 +36,6 @@
 import com.sun.prism.impl.BufferUtil;
 import com.sun.prism.impl.Disposer;
 import com.sun.prism.impl.ManagedResource;
-import com.sun.prism.impl.PrismSettings;
 
 /**
  * UploadingPainter is used when we need to render into an offscreen buffer.
@@ -50,6 +49,8 @@
     private IntBuffer   pixBits; // Users for RTTs that are backed by a SW array
     private final AtomicInteger uploadCount = new AtomicInteger(0);
     private RTTexture   rttexture;
+    
+    private volatile float pixScaleFactor = 1.0f;
 
     UploadingPainter(GlassScene view) {
         super(view);
@@ -61,6 +62,15 @@
             rttexture = null;
         }
     }
+    
+    public void setPixelScaleFactor(float scale) {
+        pixScaleFactor = scale;
+    }
+    
+    @Override
+    public float getPixelScaleFactor() {
+        return pixScaleFactor;
+    }    
 
     @Override public void run() {
         renderLock.lock();
@@ -82,9 +92,13 @@
             if (factory == null || !factory.isDeviceReady()) {
                 return;
             }
-
-            boolean needsReset = (rttexture == null) || (viewWidth != penWidth) || (viewHeight != penHeight);
-
+            
+            float scale = pixScaleFactor;
+            
+            boolean needsReset = (pix == null) ||
+                                 (scale != pix.getScaleUnsafe()) ||
+                                 (viewWidth != penWidth) || (viewHeight != penHeight);
+            
             if (!needsReset) {
                 rttexture.lock();
                 if (rttexture.isSurfaceLost()) {
@@ -92,10 +106,13 @@
                     needsReset = true;
                 }
             }
-
+            
+            int bufWidth = (int)Math.round(viewWidth * scale);
+            int bufHeight = (int)Math.round(viewHeight * scale);
+            
             if (needsReset) {
                 disposeRTTexture();
-                rttexture = factory.createRTTexture(viewWidth, viewHeight, WrapMode.CLAMP_NOT_NEEDED);
+                rttexture = factory.createRTTexture(bufWidth, bufHeight, WrapMode.CLAMP_NOT_NEEDED);
                 if (rttexture == null) {
                     return;
                 }
@@ -110,24 +127,25 @@
                 sceneState.getScene().entireSceneNeedsRepaint();
                 return;
             }
+            g.scale(scale, scale);
             paintImpl(g);
 
             int rawbits[] = rttexture.getPixels();
-
+            
             if (rawbits != null) {
                 if (pixBits == null || uploadCount.get() > 0) {
-                    pixBits = IntBuffer.allocate(viewWidth * viewHeight);
+                    pixBits = IntBuffer.allocate(bufWidth * bufHeight);
                 }
-                System.arraycopy(rawbits, 0, pixBits.array(), 0, viewWidth * viewHeight);
-                pix = app.createPixels(viewWidth, viewHeight, pixBits);
+                System.arraycopy(rawbits, 0, pixBits.array(), 0, bufWidth * bufHeight);
+                pix = app.createPixels(bufWidth, bufHeight, pixBits, scale);
             } else {
                 if (textureBits == null || uploadCount.get() > 0) {
-                    textureBits = BufferUtil.newIntBuffer(viewWidth * viewHeight);
+                    textureBits = BufferUtil.newIntBuffer(bufWidth * bufHeight);
                 }
                 
                 if (textureBits != null) {
                     if (rttexture.readPixels(textureBits)) {
-                        pix = app.createPixels(viewWidth, viewHeight, textureBits);
+                        pix = app.createPixels(bufWidth, bufHeight, textureBits, scale);
                     } else {
                         /* device lost */
                         sceneState.getScene().entireSceneNeedsRepaint();
--- a/modules/graphics/src/main/java/com/sun/javafx/tk/quantum/ViewPainter.java	Thu Oct 31 09:26:29 2013 -0400
+++ b/modules/graphics/src/main/java/com/sun/javafx/tk/quantum/ViewPainter.java	Thu Oct 31 18:14:02 2013 +0400
@@ -176,7 +176,7 @@
         // might be reassigned to the sceneBuffer graphics.
         Graphics g = backBufferGraphics;
         // Take into account the pixel scale factor for retina displays
-        final float pixelScale = presentable == null ? 1.0f : presentable.getPixelScaleFactor();
+        final float pixelScale = getPixelScaleFactor();
         // Initialize renderEverything based on various conditions that will cause us to render
         // the entire scene every time.
         boolean renderEverything = renderOverlay ||
@@ -435,6 +435,10 @@
 
         return sceneState.isWindowVisible() && !sceneState.isWindowMinimized();
     }
+    
+    protected float getPixelScaleFactor() {
+        return presentable == null ? 0.1f : presentable.getPixelScaleFactor();
+    }
 
     private void doPaint(Graphics g, NodePath renderRootPath) {
         // Null path indicates that occlusion culling is not used
--- a/modules/swing/src/main/java/javafx/embed/swing/JFXPanel.java	Thu Oct 31 09:26:29 2013 -0400
+++ b/modules/swing/src/main/java/javafx/embed/swing/JFXPanel.java	Thu Oct 31 18:14:02 2013 +0400
@@ -70,6 +70,7 @@
 import java.util.concurrent.atomic.AtomicInteger;
 import sun.awt.CausedFocusEvent;
 import sun.awt.SunToolkit;
+import sun.java2d.SunGraphics2D;
 
 /**
 * {@code JFXPanel} is a component to embed JavaFX content into
@@ -138,9 +139,13 @@
     private EmbeddedStageInterface stagePeer;
     private EmbeddedSceneInterface scenePeer;
 
-    // Dimensions of back buffer used to draw FX content
+    // The logical size of the FX content
     private int pWidth;
     private int pHeight;
+    
+    // The scale factor, used to translate b/w the logical (the FX content dimension)
+    // and physical (the back buffer's dimension) coordinate spaces
+    private int scaleFactor = 1;
 
     // Preferred size set from FX
     private volatile int pPreferredWidth = -1;
@@ -490,7 +495,7 @@
             pHeight -= (i.top + i.bottom);
         }        
         if (oldWidth != pWidth || oldHeight != pHeight) {
-            resizePixels();
+            resizePixelBuffer(scaleFactor);
             sendResizeEventToFX();
         }
     }
@@ -580,23 +585,30 @@
     }
 
     // called on EDT only
-    private void resizePixels() {
+    private void resizePixelBuffer(int newScaleFactor) {
         if ((pWidth <= 0) || (pHeight <= 0)) {
-            pixelsIm = null;
+             pixelsIm = null;
         } else {
-            BufferedImage oldIm = pixelsIm;
-            pixelsIm = new BufferedImage(pWidth, pHeight, BufferedImage.TYPE_INT_ARGB);
+            BufferedImage oldIm = pixelsIm;                        
+            pixelsIm = new BufferedImage(pWidth * newScaleFactor,
+                                         pHeight * newScaleFactor,
+                                         BufferedImage.TYPE_INT_ARGB);
             if (oldIm != null) {
+                double ratio = newScaleFactor / scaleFactor;
+                // Transform old size to the new coordinate space.
+                int oldW = (int)Math.round(oldIm.getWidth() * ratio);
+                int oldH = (int)Math.round(oldIm.getHeight() * ratio);
+                 
                 Graphics g = pixelsIm.getGraphics();
                 try {
-                    g.drawImage(oldIm, 0, 0, null);
+                    g.drawImage(oldIm, 0, 0, oldW, oldH, null);
                 } finally {
                     g.dispose();
                 }
             }
         }
     }
-
+    
     @Override
     protected void processInputMethodEvent(InputMethodEvent e) {
         if (e.getID() == InputMethodEvent.INPUT_METHOD_TEXT_CHANGED) {
@@ -630,13 +642,12 @@
         if ((scenePeer == null) || (pixelsIm == null)) {
             return;
         }
-
+        
         DataBufferInt dataBuf = (DataBufferInt)pixelsIm.getRaster().getDataBuffer();
         int[] pixelsData = dataBuf.getData();
-        IntBuffer buf = IntBuffer.wrap(pixelsData);
+        IntBuffer buf = IntBuffer.wrap(pixelsData);           
         if (!scenePeer.getPixels(buf, pWidth, pHeight)) {
-            // May happen during early Quantum initialization
-            return;
+            // In this case we just render what we have so far in the buffer.
         }
 
         Graphics gg = null;
@@ -651,7 +662,18 @@
                 Insets i = getBorder().getBorderInsets(this);
                 gg.translate(i.left, i.top);
             }
-            gg.drawImage(pixelsIm, 0, 0, null);
+            gg.drawImage(pixelsIm, 0, 0, pWidth, pHeight, null);
+
+            int newScaleFactor = scaleFactor;
+            if (g instanceof SunGraphics2D) {
+                newScaleFactor = ((SunGraphics2D)g).surfaceData.getDefaultScale();
+            }            
+            if (scaleFactor != newScaleFactor) {
+                resizePixelBuffer(newScaleFactor);
+                // The scene will request repaint.
+                scenePeer.setPixelScaleFactor(newScaleFactor);
+                scaleFactor = newScaleFactor;
+            }
         } catch (Throwable th) {
             th.printStackTrace();
         } finally {
--- a/modules/swt/src/main/java/javafx/embed/swt/FXCanvas.java	Thu Oct 31 09:26:29 2013 -0400
+++ b/modules/swt/src/main/java/javafx/embed/swt/FXCanvas.java	Thu Oct 31 18:14:02 2013 +0400
@@ -52,6 +52,7 @@
 import com.sun.javafx.embed.EmbeddedStageInterface;
 import com.sun.javafx.embed.HostInterface;
 import com.sun.javafx.stage.EmbeddedWindow;
+import java.lang.reflect.Method;
 
 import org.eclipse.swt.dnd.DND;
 import org.eclipse.swt.dnd.DragSource;
@@ -91,6 +92,8 @@
 import org.eclipse.swt.graphics.PaletteData;
 import org.eclipse.swt.graphics.Point;
 import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.graphics.GCData;
+import org.eclipse.swt.graphics.GC;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.events.ControlListener;
 import org.eclipse.swt.events.FocusListener;
@@ -166,6 +169,8 @@
         }
     };
     
+    private double scaleFactor = 1.0;
+    
     private DropTarget dropTarget;
     
     static Transfer [] StandardTransfers = new Transfer [] {
@@ -199,6 +204,34 @@
         return transfer;
     }
     
+    private static Field viewField;
+    private static Method windowMethod;
+    private static Method screenMethod;
+    private static Method backingScaleFactorMethod;
+    
+    static {
+        if (SWT.getPlatform().equals("cocoa")) {
+            try {
+                viewField = GCData.class.getDeclaredField("view");
+                viewField.setAccessible(true);
+
+                Class nsViewClass = Class.forName("org.eclipse.swt.internal.cocoa.NSView");            
+                windowMethod = nsViewClass.getDeclaredMethod("window");
+                windowMethod.setAccessible(true);
+
+                Class nsWindowClass = Class.forName("org.eclipse.swt.internal.cocoa.NSWindow");
+                screenMethod = nsWindowClass.getDeclaredMethod("screen");
+                screenMethod.setAccessible(true);
+
+                Class nsScreenClass = Class.forName("org.eclipse.swt.internal.cocoa.NSScreen");
+                backingScaleFactorMethod = nsScreenClass.getDeclaredMethod("backingScaleFactor");
+                backingScaleFactorMethod.setAccessible(true);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+    }
+    
     /**
      * @inheritDoc
      */
@@ -466,6 +499,8 @@
             height = lastHeight;
             buffer = lastPixelsBuf;
         }
+        width = (int)Math.round(width * scaleFactor);
+        height = (int)Math.round(height * scaleFactor);
 
         // Consider optimizing this
         ImageData imageData = null;
@@ -486,14 +521,43 @@
             }
             /*ImageData*/ imageData = new ImageData(width, height, 32, palette, 4, dstData);
         } else {
+            if (width * height > buffer.array().length) {
+                // We shouldn't be here...
+                System.err.println("FXCanvas.paintControl: scale mismatch!");
+                return;
+            }
             PaletteData palette = new PaletteData(0x00ff0000, 0x0000ff00, 0x000000ff);
             /*ImageData*/  imageData = new ImageData(width, height, 32, palette);
             imageData.setPixels(0, 0,width * height, buffer.array(), 0);
         }
 
         Image image = new Image(Display.getDefault(), imageData);
-        pe.gc.drawImage(image, 0, 0);
+        pe.gc.drawImage(image, 0, 0, width, height, 0, 0, pWidth, pHeight);
         image.dispose();
+        
+        double newScaleFactor = getScaleFactor(pe.gc);
+        if (scaleFactor != newScaleFactor) {
+            resizePixelBuffer(newScaleFactor);
+            // The scene will request repaint.
+            scenePeer.setPixelScaleFactor((float)newScaleFactor);
+            scaleFactor = newScaleFactor;
+        }
+    }
+    
+    private double getScaleFactor(GC gc) {
+        double scale = 1.0;
+        if (SWT.getPlatform().equals("cocoa")) {
+            try {
+                Object nsWindow = windowMethod.invoke(viewField.get(gc.getGCData()));
+                Object nsScreen = screenMethod.invoke(nsWindow);
+                Object bsFactor = backingScaleFactorMethod.invoke(nsScreen);
+
+                scale = ((Double)bsFactor).doubleValue();
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+        return scale;
     }
     
     private void sendMoveEventToFX() {
@@ -593,11 +657,7 @@
         pWidth = getClientArea().width;
         pHeight = getClientArea().height;
 
-        if ((pWidth <= 0) || (pHeight <= 0)) {
-            pixelsBuf = lastPixelsBuf = null;
-        } else {
-            pixelsBuf = IntBuffer.allocate(pWidth * pHeight);
-        }
+        resizePixelBuffer(scaleFactor);
 
         if (scenePeer == null) {
             return;
@@ -606,6 +666,17 @@
         stagePeer.setSize(pWidth, pHeight);
         scenePeer.setSize(pWidth, pHeight);
     }
+    
+    private void resizePixelBuffer(double newScaleFactor) {
+        lastPixelsBuf = null;
+        if ((pWidth <= 0) || (pHeight <= 0)) {
+            pixelsBuf = null;
+        } else {
+            pixelsBuf = IntBuffer.allocate((int)Math.round(pWidth * newScaleFactor) *
+                                           (int)Math.round(pHeight * newScaleFactor));
+            // TODO: render old pixels to the new buffer, see JFXPanel.resizePixelBuffer
+        }        
+    }
 
     private void sendFocusEventToFX(FocusEvent fe, boolean focused) {
         if ((stage == null) || (stagePeer == null)) {