Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Multiple Monitors in jme-LWJGL3 #2030 #2031

Open
wants to merge 21 commits into
base: master
Choose a base branch
from

Conversation

bob0bob
Copy link
Contributor

@bob0bob bob0bob commented Jun 5, 2023

Add support for multiple monitors.
Add a feature so that when a "Full Screen" window is created, that you can tell it which monitor to create the window on.
Add a feature so that the application can call context to get a list of monitors that OPENGL found. It returns them in an ArrayList so that the programmers can select a monitor from the list. JME will take the pos of the monitor from the arraylist to get its handle. So if you have 2 monitors, you will have 2 in the list. So to tell JME which monitor to create the window on it would be 0 or 1. The array position in the list.

The thought behind this is the program gets a list of monitors and then they can use that list in their settings for the user to select which monitor to us.
Since the ID of the monitor changes between each launch, I went with the position from the arraylist that it returned. So many if user changes the order of the monitors then the program will launch on a different screen. Minor.

Added in AppSettings a way to get/set Monitor. Monitor value is used only when creating a Full Screen.

Add support for multiple monitors.
Add a feature so that when a "Full Screen" window is created, that you can tell it which monitor to create the window on.
Add a feature so that the application can call context to get a list of monitors that OPENGL found. It returns them in an ArrayList so that the programmers can select a monitor from the list. JME will take the pos of the monitor from the arraylist to get its handle. So if you have 2 monitors, you will have 2 in the list. So to tell JME which monitor to create the window on it would be 0 or 1. The array position in the list.

The thought behind this is the program gets a list of monitors and then they can use that list in their settings for the user to select which monitor to us.
Since the ID of the monitor changes between each launch, I went with the position from the arraylist that it returned. So many if user changes the order of the monitors then the program will launch on a different screen. Minor.

Added in AppSettings a way to get/set Monitor. Monitor value is used only when creating a Full Screen.
Add support for multiple monitors.
Add a feature so that when a "Full Screen" window is created, that you can tell it which monitor to create the window on.
Add a feature so that the application can call context to get a list of monitors that OPENGL found. It returns them in an ArrayList so that the programmers can select a monitor from the list. JME will take the pos of the monitor from the arraylist to get its handle. So if you have 2 monitors, you will have 2 in the list. So to tell JME which monitor to create the window on it would be 0 or 1. The array position in the list.

The thought behind this is the program gets a list of monitors and then they can use that list in their settings for the user to select which monitor to us.
Since the ID of the monitor changes between each launch, I went with the position from the arraylist that it returned. So many if user changes the order of the monitors then the program will launch on a different screen. Minor.

Added in AppSettings a way to get/set Monitor. Monitor value is used only when creating a Full Screen.
@stephengold
Copy link
Member

Thank you for implementing the feature for us.

I don't have a multi-monitor setup myself, so it didn't occur to me that this might be wanted. I assumed it would usually be easier to create windows in the primary display and use a window manager to move them to where they're wanted. Is that not the case?

I don't understand why this PR modifies TestResizableApp. Please explain.

There are style violations in the added files: javadoc issues, braces missing or positioned unconventionally, 3-space indentation of code blocks, extra blank lines in import sections, and so on. Please review our coding style and revise accordingly.

Also I think the added files should have 2023 copyright dates.

@tonihele
Copy link
Contributor

tonihele commented Jun 5, 2023

I don't have a multi-monitor setup myself, so it didn't occur to me that this might be wanted. I assumed it would usually be easier to create windows in the primary display and use a window manager to move them to where they're wanted. Is that not the case?

I think it is impossible in many windowing systems. This is the way to go.

* number is the number in the list of monitors GlfwGetMonitors returns.
*
* <p>This setting is used only with LWJGL3, it defines which monitor
* to use when creating a OpenGL window. its default value is -1.
Copy link
Member

@Ali-RS Ali-RS Jun 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its default value is -1.

defaults.put("Monitor", 0);

But looks like it is 0?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to update comment.

@Ali-RS
Copy link
Member

Ali-RS commented Jun 6, 2023

Do modern games usually let you select the monitor from the game settings?

Edit:
Also, from a quick look at this PR seems the selected monitor will be used only when running in fullscreen while using the primary monitor for non-fullscreen modes?

@stephengold stephengold added this to the Future Release milestone Jun 7, 2023
@bob0bob
Copy link
Contributor Author

bob0bob commented Jun 8, 2023

Yes, this is for full screen only. You can't select a monitor for "WINDOW", it is forced to "primary OS monitor". When doing full screen, if you don't select it will do it on primary. If your primary is the window you want, then it is fine. But for it to launch and then move a FULL SCREEN is not nice. When you can cause it to launch on the correct window.

I'll update the copyright.

@bob0bob
Copy link
Contributor Author

bob0bob commented Jun 8, 2023

Forgot.. TestResizableMonitor was failing after creating a window in full screen when using a different monitor.
"reshape" function was called before "SimpleInitApp()" was called when creating a window other than "NULL". I put a check in there to not update label. So 'txt' variable was "NULL" because it had not called simpleInitApp(), yet.

I didn't look into way JME was calling reship before it did the initialization.

@bob0bob
Copy link
Contributor Author

bob0bob commented Jun 8, 2023

I updated the comments. Let me know if you find anything else.

@bob0bob
Copy link
Contributor Author

bob0bob commented Jun 8, 2023

I missed the LegacyApplication update for getMonitors()

Copy link
Contributor

@pavly-gerges pavly-gerges left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello Kevin, thanks for this feature, I would like to request some changes as a part of consistency with jMonkeyEngine, please correct me if you think there are untrue statements or you disagree with something, many of these requested changes have duplicates across the PR that I am not referring to it here, so please pay attention to them as far as you can.

* @return returns a list of monitors and their information.
*/
public Monitors getMonitors()
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello Kevin, thanks for this work, according to the Google style of code (that we are following here), the curly brackets of a code block shouldn't be on a new line, so please, Can you refactor the K&R bracket style to Java Style on all the code involved in this PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure no problem

settings.setWindowYPosition(0);

try
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use the Java Google coding style here too, it's also the style used in OpenJDK.

You can refer to:
https://google.github.io/styleguide/javaguide.html

@Override
public Monitors getMonitors()
{
// TODO Auto-generated method stub
Copy link
Contributor

@pavly-gerges pavly-gerges Jun 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's far better to throw a UnSupportedOperation Execption here instead of returning a null pointer.
For example:

public Monitors getMonitors()  {
       // paranoia, feature not supported!
       throw new UnSupportedOperationException("Multiple Monitors feature is not supported on LwjglCanvas!");
       return new Monitors(); // return empty monitors
}

Please refactor the K&R bracket style to the Java bracket style.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it doesn't really matter if you return a null or an empty object, what matters here is there should be a transparent message indicating that this feature is not supported.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And you don't really need a return statement, it's an unreachable code anyway.


@Override
public int getPrimaryMonitor()
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think an UnSupportedOperationException should be also thrown here.

* @see #setMonitor(long)
*/
public int getMonitor() {
return getInteger("Monitor");
Copy link
Contributor

@pavly-gerges pavly-gerges Jun 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's better to wrap the "Monitor" string key in a constant object for future refactoring and testing purposes, you can use the MonitorInfo to define default static Keys there.

*
* @author Kevin Bales
*/
public class MonitorInfo {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be marked as final, and I don't know if it needs to use the conventional style of getters and setters since their advantage here is not very obvious, but I think encapsulation should be the rule here, in case direct changes need to be applied to the member variables in the future.

@pspeed42
Copy link
Contributor

As a general comment, this new API feels a little weird to me. Having a Monitors class that is essentially just a list of monitor infos with one odd control method feels off. Even weirder if this is ever extended to provide display information about the monitors other than "what it is right now".

What I would have expected from this feature is some way to ask JME what monitors are available that returns a List of info objects... then some method to set the current monitor on the same thing I asked for the list of monitors.

something like:

JmeSystem.getMonitorInfos() : List<MonitorInfo>
JmeSystem.setCurrentMonitor(MonitorInfo)

And even that might be a dead-end design if we ever want to support changing things about a particular monitor (like current display settings or whatever).

Instead of MonitorInfo we could have a Monitor interface with the getters (no setters necessary) then different system implementations can implement the Monitor interface as they see fit. (An abstract version could be provided to make this easier for the system implementations that only support one display... and perhaps Display is a better name than Monitor.)

So:

JmeSystem.getAvailableDisplays() : List<Display>
JmeSystem.setCurrentDisplay(Display)

Regardless of all that, a MonitorInfo class with all public fields becomes a maintenance burden forever and should be avoided.

@bob0bob
Copy link
Contributor Author

bob0bob commented Jun 14, 2023

@pspeed42,
I thought about just capturing the list of all the available mode that monitor support and add them to the list. I didn't do this because when I run it on my both of my 2k monitors, it shows the max being 800x600. when it is 2k. It doesn't report back (At least my monitors) accurately.
Also, the monitor names come from Windows and even though I have Acer monitors, the driver comes up generic monitor.

The monitors do 2k 170hz on both of them, but they report is not even close. So I thought if we listed resolution, people might think that is the only option because it doesn't report correctly.

I can change the Monitor classes, I could create getter/setters and even a interface class. That would all be easy.

Doing a setCurrentDisplay() would be very hard to do. The monitor selection is a "WINDOW CREATION" so this is before you get into the App simpleInitApp(). So the only option to set which monitor in full screen is through the ini. A restart of the application is require after you allow user to select a monitor. I didn't see an LWJGL command to move monitor to another monitor, just during window creation.

Let me know if someone knows something I don't and I can look into it and change it.

@pspeed42
Copy link
Contributor

The point about the resolution settings is that someday the OS/drivers/etc. may fix those issues and those APIs would work like we expect... so it's better not to automatically design dead-ends into JME's API.

As to setCurrentDisplay(), I was confused by this which seemed to be essentially doing the same thing:
public void setPrimaryMonitor(int monPos)

But maybe change what I said to setPrimaryDisplay() and it makes more sense? Maybe it should be on AppSettings?

Either way, I prefer a "request the available options" and "set the option I want" API versus a "get the available options and somehow call something on those options to set my option"

And having setters on MonitorInfo is probably confusing when they don't really do anything except let callers mess up the values.

Thanks for the work so far. I don't mean to be picking on every thing. I'm just trying to forestall some future maintenance burden.

@bob0bob
Copy link
Contributor Author

bob0bob commented Jun 14, 2023

@pspeed42,
Those things make sense, The Monitors::setPrimaryMonitor() is misleading. It is more "Mark monitor as the active monitor". I can change it to be setActiveMonitor(). Which is more accurate.

There is no way to do a "setPrimaryDisplay()" sense it is before the simpleInitApp().

All the calls to getMonitors() and such is "AFTER" the fact. It only use is for programmers to get a list of monitors if they wish to use it in "settings" gui to list the monitor to use on "Next" launch.

I have no problem with you making suggestions or even demands to fit JME. I don't write JME and don't know the true layout.

So I can change
Monitors:setPrimaryMonitor() to Monitors::setActiveMonitor(). That way if the game grabs monitors again, it will reflect the current monitor that game uses on boot and they could display that if they wish.

Calling AppSettings::setMonitor() updates the "App Ini settings". The game could then capture the close and write out ini so it is updated, or could write the ini in the gui control when it updates the monitor.

@pspeed42
Copy link
Contributor

The objects returned from getMonitors/Displays/Whatever should be immutable objects with no setters on them. There should be no Monitors class as it is unnecessary. A list is fine. "Monitor" is also a limiting name since a Monitor is a Display but not all Displays are "monitors".

We should find a way to fix JME so that we can make platform specific queries before starting JME. Else we end up with cases where the user never gets to see the app because the OS chose to put it on a display that isn't even turned on.

Setting which display to use should be done on AppSettings. That is the "JME way", I guess... but we need a platform-independent way of reliably querying the available displays (and even the available resolutions... which we currently do not have either). That last bit is kind of a larger issue with how JME is setup.

@bob0bob
Copy link
Contributor Author

bob0bob commented Jun 14, 2023

I used "Monitor" because that is the reference inside OPENGL. It is called a monitor in the code.

I could change everything related to "Monitor" to "Display" to fix that idea of "Not a Monitor"
Selecting which display to use is through "AppSettings", calling setMonitor() (change it to setDisplay). Since it is part of the AppSettings those values can be saved and reused on launch.

I have a check inside jme3-lwjgl3::LwjglWindow::createContext(). it checks if the number of the display doesn't show up in the list of available displays then use the "primaryMonitor" instead.

Now since OPENGL lwjgl3 call to get monitors sends a new number each time that application is launched and programmers will never be able to guarantee it is the same monitor. So if a person changed their primary monitor. Then next time, it will open on another monitor. We only get a list of monitor and the ID changes all the time. So we can't determine which monitor it is.

@pspeed42
Copy link
Contributor

Probably even OpenGL doesn't call it a monitor on mobile devices... but I haven't looked.

Is the last paragraph new information or in response to something we were discussing?

@bob0bob
Copy link
Contributor Author

bob0bob commented Jun 14, 2023

@bob0bob
Copy link
Contributor Author

bob0bob commented Jun 14, 2023

The comment is related to you comments about a window opening up on a monitor that is not there anymore or turned on. The best we could do is if you select monitor 1 (0-1) and when game launches, we could default it to 0. But if you have 3 monitors 0-2 and you select 1. Then remove your 2nd monitor, so the 3rd monitor becomes the 2nd monitor. There is no way in code, to determine that is a different monitor now.

@pspeed42
Copy link
Contributor

GLFW = "for windows" not mobile. So is still going to be very PC-monitor specific. FWIW, OpenGL-ES calls it a "surface".

@bob0bob
Copy link
Contributor Author

bob0bob commented Jun 14, 2023

Interesting, but this is not for Android. The only support is for PC. I do not code for mobile and don't know enough to write something that would support mobile.

But again, I do not see support for multiple monitors in mobiles. So this feature is basically for PC not other devices. That is why if you call monitor functions, you get either NULL or 0, because it is not supported.

@bob0bob
Copy link
Contributor Author

bob0bob commented Sep 22, 2023

Sorry, been away for sometime. Work and the farm taking up all the spare time. I just uploaded the changes to "Devices".

…bility for other devices."

This reverts commit 8594c1d.
@bob0bob
Copy link
Contributor Author

bob0bob commented Sep 23, 2023

I didn't mean to close this.

@bob0bob bob0bob reopened this Sep 23, 2023
@bob0bob
Copy link
Contributor Author

bob0bob commented Sep 23, 2023

My branch was old, and merged it to current, but not sure if I missed something. Please let me know, and I'll fix it.

# Conflicts:
#	jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglDisplay.java
@tonihele
Copy link
Contributor

What is the difference between Display and Monitor? They look copy paste to me.

@pspeed42
Copy link
Contributor

A Display is something capable of displaying graphics. A Monitor is the big giant display sitting on your desk... which is by definition a subset of Display.

Was this about code or the naming conversation?

@pspeed42
Copy link
Contributor

Did github autoformat everything again? Going to take some extra time to review with all of the non-changes mixed in.

@tonihele
Copy link
Contributor

tonihele commented Oct 31, 2023

A Display is something capable of displaying graphics. A Monitor is the big giant display sitting on your desk... which is by definition a subset of Display.

Was this about code or the naming conversation?

Sorry, I'll try to be more specific. My question is that why both Java code classes are needed, Displays & DisplayInfo and Monitors & MonitorInfo:
image

What is the difference between the two in that context? They look exactly the same to me. If both classes and terms are needed, should there be some interface/abstract class rather than just copy paste code. Design-wise.

@tonihele
Copy link
Contributor

Did github autoformat everything again? Going to take some extra time to review with all of the non-changes mixed in.

Looks that way, there are two separate auto-format commits.

@bob0bob
Copy link
Contributor Author

bob0bob commented Nov 1, 2023

Sorry about this. When we decided to change from Monitor to Display the 2 old files related to Monitors was not deleted. I removed them.

@tonihele
Copy link
Contributor

tonihele commented Jan 7, 2024

Looks ok to me. We should finally either accept, refuse or otherwise make this go forward. It is not fair to keep this hanging for so long, rather trivial addition too.

@stephengold
Copy link
Member

It is not fair to keep this hanging for so long

@tonihele We have a new release manager in training. Please be patient.

@scenemax3d
Copy link
Contributor

@pspeed42 @stephengold @tonihele
reading the comments I saw concerns about the structure of the API (Paul's comments). Are we OK with the API then?
If yes, I will add it to v3.7.0
10X :)

*
* @author Kevin Bales
*/
public class DisplayInfo {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it correct that the variables of the DisplayInfo class all have the public access modifier? I would like to know what the core members think about this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For effective encapsulation, public fields should be avoided.

Copy link
Contributor

@pavly-gerges pavly-gerges Jan 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It really comes down to the style of code, and this style is almost the same as the C-struct-style. While, jMonkeyEngine and Java style doesn't use public fields as outside accessors, I recommend to stick to the style too. I am really now bothered more with thread-safety.

EDIT:
Also, maybe we should consider using a record instead, I guess this might be better for thread-safety, this object should be immutable by itself.

Copy link
Contributor

@capdevon capdevon Jan 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest privatizing the variables and adding getter/setter methods. Don't use 'record' otherwise you break the engine's compatibility with java 8 and all applications currently out there.

If you think the values of the variables should be immutable, then I would suggest writing the class like this:

public class DisplayInfo {
    
    private long displayID = 0;
    private int width = 1080;
    private int height = 1920;
    private int rate = 60;
    private boolean primary = false;
    private String name = "Generic Monitor";
    
    public DisplayInfo() {}

    public DisplayInfo(long displayID, int width, int height, int rate, boolean primary, String name) {
        this.displayID = displayID;
        this.width = width;
        this.height = height;
        this.rate = rate;
        this.primary = primary;
        this.name = name;
    }

    public long getDisplayID() {
        return displayID;
    }

    public int getWidth() {
        return width;
    }

    public int getHeight() {
        return height;
    }

    public int getRate() {
        return rate;
    }

    public boolean isPrimary() {
        return primary;
    }

    public String getName() {
        return name;
    }
    
}

Copy link
Contributor

@pavly-gerges pavly-gerges Jan 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with this. I propose to add a final keyword to all the fields and to the class definition, as well. By doing this, we can immitate a record or an immutable data structure anyway, it's also best to try to privatize the constructor, so only the Displays utility can instantiate it to be used as only an immutable data structure.

EDIT:
It's not applicable here to add final to the fields as they have default values, but anyway my point is to build a fully immutable data structure.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants