JDK-8094194 : [Mac, Retina] SubScene contents are blurry
  • Type: Bug
  • Component: javafx
  • Sub-Component: graphics
  • Affected Version: 8u20
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • Submitted: 2014-10-09
  • Updated: 2017-09-22
  • Resolved: 2014-10-26
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 8
8u40Fixed
Related Reports
Duplicate :  
Relates :  
Relates :  
Description
Elements that placed inside a SubScene are blurry.

Link to a preview of the sample application on my system:
  http://i.imgur.com/q5pudHP.png

Sample application:

import javafx.application.Application;
import javafx.scene.*;
import javafx.scene.layout.HBox;
import javafx.scene.text.Text;
import javafx.stage.Stage;

public class BlurrySubscene extends Application {
    public static void main(String[] args) {
        launch(args);
    }
 
    @Override
    public void start(Stage stage) {
        final Text subsceneText = new Text("Subscene Text:\n\n" + TEXT);
        subsceneText.relocate(0, 0);
        double W = subsceneText.getBoundsInLocal().getWidth();
        double H = subsceneText.getBoundsInLocal().getHeight();

        SubScene subscene = new SubScene(new Group(subsceneText), W, H);
        Scene scene = new Scene(
            new HBox(
                subscene,
                new Text("Scene Text:\n\n" + TEXT)
            )
        );
        stage.setScene(scene);

        stage.show();
    }

    private static final String TEXT =
            "O for a Muse of fire, that would ascend\n" +
            "The brightest heaven of invention,\n" +
            "A kingdom for a stage, princes to act\n" +
            "And monarchs to behold the swelling scene!\n" +
            "Then should the warlike Harry, like himself,\n" +
            "Assume the port of Mars; and at his heels,\n" +
            "Leash'd in like hounds, should famine, sword and fire\n" +
            "Crouch for employment";
}
Comments
Added a comment: // Allow the scaled size in pixels to vary by a distance approximately // large enough to affect the sampling result in a LINEAR interpolation. // If we move by 1/256th of a pixel from one color to the opposite color // then in the worst case the sample value might change by +/- 1 bit. static final double THRESHOLD = 1.0 / 256.0; Fixed in the 8u-dev repo with the following changeset: changeset: 8260:c23787b40872 date: Sun Oct 26 02:00:27 2014 -0700 summary: Fix RT-38948: Blurry text in Subscene on Mac retina http://hg.openjdk.java.net/openjfx/8u-dev/rt/rev/c23787b40872
26-10-2014

+1. Nice work! Just one minor point, THRESHOLD may need a comment as to why it is set to 1.0 / 256.0.
25-10-2014

new webrev: http://cr.openjdk.java.net/~flar/RT-38948/webrev.02/ I made a call and decided to remove the "minimum scale of 1.0" code. It includes a change to D3DContext.java that should be identical to the code in ES2Context.java to fix the "double scaling" that Kevin saw...
24-10-2014

Pretty much there, but have we come to a conclusion on the lower bound on scale? I just had a thought that sometimes a sub-scene might be used for a thumbnail of another scene and so rendering at full resolution to have it pixel-reduced would be bad. On the other hand, if it really were a thumbnail then using snapshot instead of sub-scene might make more sense and also they could install a scale on the root group in any case. On the other hand, I'm not really sure why I added that lower bound in the first place. I think I was thinking of the case where 3D transforms would have one of the X or Y scales be very small when the render plane was end-on to the eye, but now that I am doing a more complete calculation of the scale including x,y and z, we no longer have that issue (we still have the issue of z not being scaled on retina and so the z contribution to the scale only provides 1.0 rather than 2.0, but that doesn't come up on non-retina and we no longer have the x,y-based scales coming out as very small numbers near 0).
24-10-2014

Great! Do you think you are at point to generate a new webrev?
24-10-2014

One more test case BlurrySubscene3 that creates a subscene that hits all 3 of the "blit cases" in NGSubscene.
24-10-2014

The D3D "double scale" problem turned out to be the age old problem of "cameras have their projection matrix computed by the FX layer which has no idea how large the eventual rendering surface actually will be". ES2 already has code to manually adjust the camera when updating the render target, but D3D never had that workaround and copying it from the ES2 code fixes the scale on D3D...
24-10-2014

The blurriness on the original test case on Windows can be fixed by using rtWidthHeight/scaleXY in place of slWidthHeight since those more accurately indicate the true mapping of the "rounded up" dimensions of the content. The reason we likely didn't see this on Mac is probably a difference in fonts and how closely the original contentBounds.getWidth*scaleX were to a whole pixel amount. I can reproduce the "double scaling" on D3D as well. Both ES2 and SW work fine, though, so this is likely a D3D issue. Looking through the drawTexture code I don't see any red herrings so I think this is likely another of those cases where we somehow decide we don't need to update the device transform when switching from rendering the subscene with a scalex2 transform back to blitting it in the main scene with an unscaled transform. I'll dig deeper...
24-10-2014

The blurriness on Windows is probably related to using rtWidth vs slWidth for the dimensions of the source and destination rectangles in the drawTexture call... rtWidth/scaleX might work better...
24-10-2014

@Jim: Regarding you earlier comment. Yes, I noticed the difference, but I'm focusing on SubScene for now and reverted NGShape.
24-10-2014

Hi Jim, This proposed fix has a problem. I switched to Windows to compare the result between sw, es2 and d3d, and I found that the fix causes regression on all 3 pipes using the simple test program in this JIRA. The text in sub scene is blurry on my Windows in all 3 pipes. I'm attaching the result of sw pipe with and without the fix.
24-10-2014

It could be windows specific I'll check there. Another thing I forgot to ask in the comments above... In NGSubscene I clip the scale at 1.0 as a lower bound, but I don't do that in NGShape. Any thoughts on whether that adds or detracts from the strategy?
24-10-2014

I'm still reviewing this work, but just a quick comment regarding #2, I got the same result as Jim on my non-retina MacBook Pro. With either sxy or sxyz set to 2 (but not both), window and text are twice as big compared to both sxy and sxyz set to 1.0. I haven't verified it on Windows as noted by Kevin. I believe he is running on the d3d pipeline.
24-10-2014

#2 - I only set sxy = 2 (and leave sxyz=1). In this case, the subscene text is twice as large as the scene text with your patch applied. See attached image. It renders as expected, with both being the same size, without your patch applied. This is on Windows, if that makes a difference.
24-10-2014

I should mention that I see the same window sizes when I view it on retina vs. non-retina (I use the same virtual resolution in both modes comparing 1680x1050 true vs. 1680x1050 retina so that things should have the identical size when I switch back and forth).
24-10-2014

Regarding #2, when I run it with either set to 2.0 then the window is exactly twice as wide and twice as tall as it is with both set to 1.0. Also, the size of the windows before and after my fix is the same (the only difference being blurriness or not blurriness). Did you perhaps set both sxy and sxyz to 2.0? If so, then you would get a double-double since they are both applied and so one of them should be 1.0 and the other should be 2.0, never both...
24-10-2014

Regarding #2 - I think you need to do somehow apply a scale(1/scaleX, 1/scaleY) to the current transform of g when you do the drawTexture since you have rendered the image with that scale.
24-10-2014

1) The concept looks sound, and avoids messing with the transforms in Prism, which is good. 2) There is a bug somewhere in NGSubScene -- if I set either sxy or sxyz to 2 then the SubScene is rendered twice as big as it should be (as if the scale of 2 were applied twice). This doesn't happen without your patch. Do you see this on Retina? Anyway, it should be an easy fix. 3) It does seem worth testing and fixing both this and RT-39120 at the same time, although since the fix is in separate files (i.e., the patches are disjoint) you might want to create two changesets and push them at the same time. 4) More testing is needed (Chien can help with this) especially using subscenes that contain a 3D transform within the subscene. 5) One thing to consider regarding the issue of uniform versus non-uniform scale: if you are otherwise using all 2D transforms, then a uniform scale will make all transforms be 3D transforms, since is2D() will always be false. Probably not the best thing to do by default.
24-10-2014

new proposed fix: http://cr.openjdk.java.net/~flar/RT-38948/webrev.01/ Note that I have debug prints in this version of the fix so I can track when the cache is being re-rendered. Currently, running the BlurrySubScene2.java test case with this fix and a non-retina screen the cache is never re-rendered. If you run it on a retina screen or modify the sxy variable to be 2.0 then you can see that the cache is reused for 2D transforms, but is invalidated on every frame for 3D transforms because that scale does not affect the Z scaling so as the node rotates out of the 2D plane, it becomes subject to a non-uniform scale. There is also a sxyz variable that you can set to 2.0 (only set sxy or sxyz, not both) which will make the test use a uniform 2x scale along all axes, and when run with that value on non-retina then the cache is reused under all transforms. There is some question as to whether we should be using a full xyz scale on retina as a result of these investigations, but that issue is still under debate. Also note that this fix also contains a fix for RT-39120 which was discovered while developing this fix so that you can see proper behavior in both the subscene examples and the direct rendering of the Text node. These fixes use the exact same technique in both files and could be pushed together or separately. I haven't yet tried to consolidate their implementations, though - right now it is the same code duplicated in both files.
23-10-2014

Attaching a new test case BlurrySubScene2.java which tests the behavior under 3D transforms. I was using it to test my fix to see under which transform conditions I would invalidate the cached rendering of the scene. It starts rotating in 2D about the Z axis, but if you click in the window it will alternate between the Z, X, and Y axes to see how 3D transforms affect it. If you go into the code and modify the sxy variable to be 2.0 then you can see how this test behaves on retina screens. Note that this test suffers from another bug as indicated in RT-39120. If you click to switch from 2D rotations to another rotation axis when it is near 90 degrees or 270 degrees then the text node ("Scene text:") will get really blurry. Try only clicking when the node is near 0 or 180 degrees to avoid that bug for now.
23-10-2014

Discovered RT-39120 while testing the fix for this bug under 3D rotations.
23-10-2014

Here's a webrev for my prototype fix: http://cr.openjdk.java.net/~flar/RT-38948/webrev.00/
18-10-2014

I have a prototype for a fix for the problem with retina, but one of the issues I'm facing is that there is no easy way to determine if there is (retina-style) pixel scaling going on once you reach a Node. I can: - hard code always render the subscene as retina if there are any retina monitors even if the window is dragged to a non-retina screen - put in some stop gap "This value might tell you if you are on retina" mechanisms so that simple cases like the test case in the description work - just always react to the transform that we are being viewed under On the last item - right now we completely ignore the transform when we render a sub-scene and always render it at "IDENTITY". Should we? Should the rendering of the sub-scene take into account the accumulated transforms of its ancestors instead? The basic problem with determining pixel scale from a Node is that all we have is a Graphics object. It will tend to be scaled by 2.0 if we are on retina, but there is no way to distinguish if that 2.0 came from retina or from the fact that we are inside a container that was scaled. From a Graphics you can get to an "associated screen" which typically just ends up being a static screen associated with the OpenGL context which is likely the primary screen. You can also get to the "render target" which is often just an RTTexture, even if this is the top level of the scene (because a SwapChain renders content into an RTT). And all RTTs just use the context screen as their "associated screen". So, there is no chain to get back to the actual screen that the scene is being rendered on. I tried to add a makeshift "Graphics.getPixelScale()" method onto Graphics, and if I initialize it appropriately in ES2SwapChain when we are on retina, then the SubScene can detect this and adapt. The problem is that we never propagate such a value when we intercept the rendering to redirect it to a temporary image such as in node caching, effects, complex clipping, etc. There are a number of these places that would need to be taught to propagate the flag on to the Graphics they get from their temporary texture. Note that all of these pieces of code basically don't care if there is pixel scaling going on because they treat all incoming transforms generically and pass them on or work within those constraints. Only SubScene produces an isolated absolute image independent of the incoming transform (Canvas does as well, but it does so by definition since it is a drawn bucket of pixels, whereas SubScene is not necessarily defined as a "bucket of pixels"). Thoughts?
18-10-2014

Yes, I have confirmed that sub-scenes always render at normal screen resolutions which means they will be pixel-stretched on retina.
10-10-2014

I dug up an old Windows 7 machine (ATI Radeon HD 4600 graphics card) and installed Java 8u20 on it (java.runtime.version=1.8.0_20-b26) and the subscene is not blurry on the Windows machine (the contents of the subscene look identical to the main scene to my eyes): Link to a screenshot of the Windows machine running the test sample application: http://i.imgur.com/LKvzKQa.png So perhaps Kevin's supposition (3) is correct that the blurry SubScene is retina-specific.
10-10-2014

Or, now that I looked at the picture: 3) A retina-specific problem with SubScene Assign to Jim to look at for 8u40.
10-10-2014

Thank you for the test case. Without looking in more detail, I suspect one of the two following causes: 1) The text in the SubScene is being rendered as gray-scale rather than LCD 2) The SubScene is positioned on a non-integer boundary.
10-10-2014