jmonkeyengine icon indicating copy to clipboard operation
jmonkeyengine copied to clipboard

IllegalArgumentException in registerObject()

Open stephengold opened this issue 2 years ago • 13 comments

This issue was reported at the Discourse hub/forum: https://hub.jmonkeyengine.org/t/java-lang-illegalargumentexception-object-id-must-be-greater-than-zero-on-mac/46559

https://github.com/jMonkeyEngine/jmonkeyengine/blob/44a50de4d7797dc3dd8d46cd1f51308e9a05d627/jme3-core/src/main/java/com/jme3/util/NativeObjectManager.java#L104-L107

While JME specifically reserves the value -1 for invalid IDs:

https://github.com/jMonkeyEngine/jmonkeyengine/blob/44a50de4d7797dc3dd8d46cd1f51308e9a05d627/jme3-core/src/main/java/com/jme3/util/NativeObject.java#L47

I believe negative IDs in general don't justify throwing an exception.

The OpenGL documentation doesn't specify range of values for a texture name, only that they are GLuint:

  • https://registry.khronos.org/OpenGL-Refpages/gl4/html/glGenBuffers.xhtml
  • https://registry.khronos.org/OpenGL-Refpages/gl4/html/glGenTextures.xhtml

According to the OpenGL spec, GLuint is at least 32 bits, so values > 0x7fffffff will produce negative values in a Java int.

I think the tests in lines 105 and 132 should be specifically for == INVALID_ID instead of <= 0.

stephengold avatar Mar 07 '23 21:03 stephengold

The GLRenderer class appears to have similar issues. In some places, it checks against -1; in others, it requires > 0.

  • https://github.com/jMonkeyEngine/jmonkeyengine/blob/44a50de4d7797dc3dd8d46cd1f51308e9a05d627/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java#L1254
  • https://github.com/jMonkeyEngine/jmonkeyengine/blob/44a50de4d7797dc3dd8d46cd1f51308e9a05d627/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java#L1364
  • https://github.com/jMonkeyEngine/jmonkeyengine/blob/44a50de4d7797dc3dd8d46cd1f51308e9a05d627/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java#L1455-L1456
  • https://github.com/jMonkeyEngine/jmonkeyengine/blob/44a50de4d7797dc3dd8d46cd1f51308e9a05d627/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java#L1679
  • https://github.com/jMonkeyEngine/jmonkeyengine/blob/44a50de4d7797dc3dd8d46cd1f51308e9a05d627/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java#L1700

stephengold avatar Mar 07 '23 21:03 stephengold

re: "While JME specifically reserves the value -1 for invalid IDs:"

...and that might actually be a valid ID also. Maybe JME should track invalid IDs in a different way.

pspeed42 avatar Mar 07 '23 21:03 pspeed42

We could store IDs as long, which would open up many possibilities.

stephengold avatar Mar 07 '23 22:03 stephengold

It appears that INVALID_ID is a semaphore for "Not connected to a native-side object". Not sure if there's a better way to track this. (It feels like something left over from a time when every byte in any object was at a premium.)

Sailsman63 avatar Mar 07 '23 23:03 Sailsman63

Or Integer (capital 'i') so that it could be nullable.

pspeed42 avatar Mar 08 '23 01:03 pspeed42

I think the tests in lines 105 and 132 should be specifically for == INVALID_ID instead of <= 0.

I will try this locally and see how it works both on linux and try to test it on mac as well. Thanks for looking into this.

neph1 avatar Mar 09 '23 07:03 neph1

Got some more info from the modified build. App now loads but window is black. It seems the assertion was legit in this case:

java.lang.IllegalStateException: Some video driver error or programming error occurred. Framebuffer object status is invalid.
	at com.jme3.renderer.opengl.GLRenderer.checkFrameBufferError(GLRenderer.java:1838)
	at com.jme3.renderer.opengl.GLRenderer.updateFrameBuffer(GLRenderer.java:1996)
	at com.jme3.renderer.opengl.GLRenderer.setFrameBuffer(GLRenderer.java:2127)
	at com.jme3.renderer.RenderManager.renderViewPort(RenderManager.java:1189)
	at com.jme3.renderer.RenderManager.render(RenderManager.java:1287)
	at com.jme3.app.SimpleApplication.update(SimpleApplication.java:278)
	at com.jme3.system.lwjgl.LwjglWindow.runLoop(LwjglWindow.java:580)
	at com.jme3.system.lwjgl.LwjglWindow.run(LwjglWindow.java:669)
	at com.jme3.system.lwjgl.LwjglWindow.create(LwjglWindow.java:493)
	at com.jme3.app.LegacyApplication.start(LegacyApplication.java:490)
	at com.jme3.app.LegacyApplication.start(LegacyApplication.java:442)
	at com.jme3.app.SimpleApplication.start(SimpleApplication.java:126)

And then

SEVERE: Failed to destroy context
java.lang.IllegalArgumentException: The Image[size=1920x1080, format=Depth, id=0] NativeObject is not registered in this NativeObjectManager
	at com.jme3.util.NativeObjectManager.deleteNativeObject(NativeObjectManager.java:138)
	at com.jme3.util.NativeObjectManager.deleteUnused(NativeObjectManager.java:181)
	at com.jme3.util.NativeObjectManager.deleteAllObjects(NativeObjectManager.java:206)
	at com.jme3.renderer.opengl.GLRenderer.cleanup(GLRenderer.java:724)
	at com.jme3.system.lwjgl.LwjglWindow.destroyContext(LwjglWindow.java:442)
	at com.jme3.system.lwjgl.LwjglWindow.deinitInThread(LwjglWindow.java:646)
	at com.jme3.system.lwjgl.LwjglWindow.lambda$initInThread$12(LwjglWindow.java:518)
	at java.base/java.lang.Thread.dispatchUncaughtException(Unknown Source)

But I guess that is due to the first exception. There's also a WARNING: JmeDialogsFactory implementation not found. Both for the first exception and the second. Presumably jme want's to show the exceptions in a dialog and fails.

The graphics card info makes me believe this is maybe related to Radeon cards, and not Macs:

INFO: OpenGL Renderer Information
 * Vendor: ATI Technologies Inc.
 * Renderer: AMD Radeon Pro Vega 20 OpenGL Engine
 * OpenGL Version: 4.1 ATI-4.8.101
 * GLSL Version: 4.10
 * Profile: Core

Would it be worth downgrading lwjgl to 2.9?

neph1 avatar Mar 10 '23 12:03 neph1

There's also a WARNING: JmeDialogsFactory implementation not found. Both for the first exception and the second. Presumably jme want's to show the exceptions in a dialog and fails.

This warning can be resolved by adding the jme3-awt-dialogs module but also I believe swing + lwjgl3 is problematic on mac and I guess that is why awt dialogs were moved out in JME 3.6 in the first place.

The graphics card info makes me believe this is maybe related to Radeon cards, and not Macs:

Just in case, I am using an AMD ATI Radeon graphics card on Linux and do not have such issue.

Ali-RS avatar Mar 10 '23 13:03 Ali-RS

One thing I notice is that @param waitFor true&rarr;wait for the context to be initialized, is false in the start sequence. Could that be relevant perhaps in combination with the -XstartOnMainThread flag that is required for mac? Or just for some reason in general.

neph1 avatar Mar 11 '23 08:03 neph1

I think the waitFor flag has no effect on mac

https://github.com/jMonkeyEngine/jmonkeyengine/blob/a0c2c5ee74f87f588e539531a5c535b77b9ea4b3/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java#L533-L555

Ali-RS avatar Mar 11 '23 08:03 Ali-RS

More evidence that JME requires native-object IDs to be non-negative:

https://github.com/jMonkeyEngine/jmonkeyengine/blob/cef0d3b6862266cc284a9ce4e87a4a61b3a6a205/jme3-core/src/main/java/com/jme3/audio/AudioBuffer.java#L133-L136

If the ID were negative, sign extension during the conversion to long would obliterate the object-type information.

stephengold avatar Mar 19 '23 07:03 stephengold

Might the answer here be encapsulation? Exposing the id and using it to indicate both the id of the object and the validity of it seems to be the root of the problem. Since -1 (or other negative numbers) can be a valid ID but also a signal for an invalid ID/Object, it is contradictory.

We can encapsulate the id, and provide separate methods to indicate object validity. By removing access to id, we are able to ensure we can capture and fix every case where the id is used or updated. Instead of checking for -1, we can check the isValid method. When we delete the object, we can call an invalidate() method to clear the id and the validity flag.

Behind the scenes, we can either use a separate boolean valid field, store it in the top half of a long id, or even still use -1. How it is implement can change once encapsulated, and tbh, the JVM will probably end up inlining those getter calls to direct variable accesses anyway.

I've put in PR: #2051 as a prototype for what I mean. Perhaps anyone who can reproduce the problem can see if it fixes it.

andygibson avatar Jul 27 '23 20:07 andygibson

Encapsulation can be a breaking change for older code. Since the issue is that the id is an uint that overflows when converted to signed int, I have a different proposal:

  • store the unsigned id in a long.
  • return the long from getId().
  • add setId(long).
  • deprecate setId(int) and replace its code to convert the overflowed int to an unsigned int .
  • warn when setId(-1) is called and ask the developer to use setId(INVALID_ID) instead.

Assuming most code uses setId(INVALID_ID) and not setId(-1), this shouldn't cause any issue, and if it doesn't then there is the warning that gives a chance to figure out that there is something wrong and how to fix it.

Pseudo code:

public static final **long** INVALID_ID=-1;
private **long** id;

public long getId(){
    return id;
}

@Deprecated
public void setId(int id){
    if(id==-1){
       warn("ID was set to -1. Note: this is a valid ID now, please use setId(INVALID_ID) to reset the id to the invalid ID");
    }
    this.id=toUnsignedInt(id);    
}

public void setId(**long** id){
    this.id=id;
}


riccardobl avatar Aug 19 '23 11:08 riccardobl