jMonkeyEngine / jmonkeyengine

A complete 3-D game development suite written in Java.
http://jmonkeyengine.org
BSD 3-Clause "New" or "Revised" License
3.78k stars 1.12k forks source link

Canvas embedded in Swing application renders at wrong scale on HiDPI #1393

Open remcopoelstra opened 4 years ago

remcopoelstra commented 4 years ago

I am trying to embed JMonkeyEngine into a Swing application and noticed the Canvas is rendering at a different scale on HiDPI displays, the screenshot below is on a 200% scale display. I set a custom background color on the canvas to make sure it has the correct dimensions.

jme3tests

I combined the TetsGltfLoading class with some sample code for embedding the canvas:

package nl.rp.ddd.jme;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;

import javax.swing.JFrame;
import javax.swing.SwingUtilities;

import com.jme3.system.AppSettings;
import com.jme3.system.JmeCanvasContext;

public class JmeTests {

    public JmeTests() {

        final JFrame frame = new JFrame();
        frame.setTitle("JME 3 Tests");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(800, 600);

        AppSettings settings = new AppSettings(true);
        settings.setWidth(800);
        settings.setHeight(600);
        settings.setVSync(true);

        TestGltfLoading canvasApplication = new TestGltfLoading();
        canvasApplication.setSettings(settings);
        canvasApplication.setDisplayStatView(false);
        canvasApplication.setDisplayFps(false);
        canvasApplication.createCanvas();
        JmeCanvasContext ctx = (JmeCanvasContext) canvasApplication.getContext();
        ctx.setSystemListener(canvasApplication);
        Dimension dim = new Dimension(800, 600);
        ctx.getCanvas().setPreferredSize(dim);
        ctx.getCanvas().setBackground(new Color(50, 50, 100));

        frame.getContentPane().setLayout(new BorderLayout());
        frame.getContentPane().add(ctx.getCanvas(), BorderLayout.CENTER);

        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);

    }

    public static void main(String[] args) {
        System.setProperty("org.lwjgl.opengl.Display.enableHighDPI", "true");
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                new JmeTests();
            }
        });
    }

}

I tested this with 3.3.2-stable, jme3-lwjgl (embedding a canvas failed with jme3-lwjgl3), java 10 and java 11 runtimes.

pavly-gerges commented 3 years ago

Hi @remcopoelstra , Have you resolved this problem ?

remcopoelstra commented 3 years ago

I managed to work around it for now by resizing the canvas myself (zooming in on the scene in the upper left corner), for this I override doLayout() of the parent container:

@Override
public void doLayout() {
    if (canvas != null && scale != null) {
        canvas.setBounds(0, 0, (int)(getWidth() * scale), (int)(getHeight() * scale));
    } else {
        super.doLayout();
    }
}

This does have some issues though, overlay text is rendered at wrong scale for example. I wouldn't consider this workaround a solution to the problem.

I also did some experimenting with embedding a canvas with lwjgl3 (because I would prefer to use this instead of lwjgl2). I was able to embed a canvas on Windows with glfwAttachWin32Window() but I still have to try to get keyboard/mouse input working correctly. See this issue for more info on attaching glfw to an existing handle: https://github.com/glfw/glfw/issues/25

pavly-gerges commented 3 years ago

I was facing similar issues , so my approach was to :

Create a JPanel instance & initialize it -> add the JPanel to the JFrame content pane -> attach the jme3 canvas on top of the JPanel not the Jframe.

Check this :

https://hub.jmonkeyengine.org/t/integrate-jmonkey-canvas-into-swing-frame/44018/4?u=pavl_g

This example resembles AWTPanel from paul's SPiX library , but.you can use javax.swing.JPanel as well.

glowlux commented 1 year ago

Extending on @remcopoelstra response, this is an alright workaround for Windows.

// Custom JPanel to correct for Windows OS display scaling.
    // If you have scaling set to something different from 100% in Windows display settings, the canvas will render at the incorrect size.
    // This fixes this issue, by scaling the canvas bounds by the Windows scaling amount.
JPanel panel = new JPanel(new BorderLayout()) {
    @Override
    public void doLayout() {
        if (JmeSystem.getPlatform().getOs() == Platform.Os.Windows) {
            GraphicsConfiguration graphicsConfiguration = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
            AffineTransform displayTransform = graphicsConfiguration.getDefaultTransform();
            double scaleX = displayTransform.getScaleX(); // Windows OS Display X scaling.
            double scaleY = displayTransform.getScaleY(); // Windows OS Display Y scaling.
            canvas.setBounds(0, 0, (int)(getWidth() * scaleX), (int)(getHeight() * scaleY));
        }

    }
};
panel.add(canvas, BorderLayout.CENTER);
glowlux commented 1 year ago

This seems related... solution looks good. https://forum.jogamp.org/GLcanvas-vs-NEWT-on-Hi-DPI-Screens-tp4041191p4041642.html

I think returning scaled sizes from the getHeight() and getWidth() methods of the canvas is better and seems to break less things. Modified LwjglCanvas.java

Not submitting this as change though, because I'm unsure if this workaround breaks anything else...

gbburkhardt commented 1 year ago

Why isn't this a bug in AWT? Seems that for 'Canvas' ought to expand in size to fix the parent JPanel. I think Canvas is using physical pixel for its size, as opposed to scaled, logical pixels. Perhaps it really can't, since Canvas would have to resample any image it contains. But then, I'd want Canvas to increase its size the way your code does. Do images in Canvas always display in the current screen resolution?