[RFE] LWJGL 3.0

Started by spasi, November 08, 2012, 13:23:54

Previous topic - Next topic

kappa

Started working on getting the LWJGL2 compatibility layer working with LibGDX, noticed that the overloaded method AL10.alSourceUnqueueBuffers(int source) is missing in LWJGL3.

spasi

Latest build has ARB_multitexture, EXTPointParameters, EXTSharedTexturePalette, color index functions/constants in GL11 and fixed alSourceUnqueueBuffers.

ra4king

Does LWJGL 2 really not support color index mode? I found a usage of it in JPCSP's code after kappa mentioned: https://github.com/sum2012/jpcsp-free/blob/master/src/jpcsp/test/OpenGL.java
Look for usage of 'texture2Id'.

On Github I found no legitimate use in Java code, only clumped with other cases in utilities that check for texture type in a switch statement.
-Roi

kappa

Quote from: spasi on November 23, 2014, 23:18:04
Latest build has ARB_multitexture, EXTPointParameters, EXTSharedTexturePalette, color index functions/constants in GL11 and fixed alSourceUnqueueBuffers.
Awesome stuff, tested above and all seems to work pretty good.

Trying the LibGDX code also flagging up the missing extension EXTFramebufferObject which they use a lot in their LWJGL backend (not sure why they didn't just use the ARB version).

Also GL15.glGetBufferPointer in LWJGL3 returns a long now, while the same method in LWJGL2 returned a ByteBuffer.

Other than the above shouldn't take long to get LibGDX ported to LWJGL3 using the compatibility layer.

delt0r

I did ask this in jgo, but not sure how often you check both. For new projects, should we use 3 or stick with 2 and port later?
If you want a plot read a book and leave Hollywood out of it.

spasi

Quote from: kappa on November 24, 2014, 00:29:52Also GL15.glGetBufferPointer in LWJGL3 returns a long now, while the same method in LWJGL2 returned a ByteBuffer.

This is indeed another change that is incompatible with LWJGL 2. The GetXPointer functions were inconsistent and in some cases inflexible. For example, GL15.glGetBufferPointer had a glGetBufferParameteri(target, GL_BUFFER_SIZE) hidden inside the call, but the user might already know the size. Or they might only care about the pointer value itself and we were forcing a ByteBuffer allocation. In other cases, such as GL11.glGetPointer, there was an extra argument for the user to specify the size explicitly, but no such argument exists in the corresponding native function. This breaks the fundamental decision in LWJGL 3 to always match the native function signature perfectly.

There are two ways to use the new style:

int target = GL_ARRAY_BUFFER;
int size = glGetBufferParameteri(target, GL_BUFFER_SIZE);

// standard
PointerBuffer pb = BufferUtils.createPointerBuffer(1);
glGetBufferPointer(target, GL_BUFFER_MAP_POINTER, pb);
ByteBuffer bb = pb.getByteBuffer(0, size);
FloatBuffer fb = pb.getFloatBuffer(0, size >> 2);

// unsafe
long p = glGetBufferPointer(target, GL_BUFFER_MAP_POINTER);
ByteBuffer bb = memByteBuffer(p, size);
FloatBuffer fb = memFloatBuffer(p, size >> 2);

The "unsafe" version is basically a shortcut for:

PointerBuffer pb = BufferUtils.createPointerBuffer(1);
glGetBufferPointer(target, GL_BUFFER_MAP_POINTER, pb);
long p = pb.get(0);

but without a PointerBuffer allocation.

kappa

ah ok, will see if I can work round it.

btw LibGDX also uses GL20.glIsProgram(int) and GL20.glIsShader(int) which seem to be missing in LWJGL3.

spasi

Quote from: ra4king on November 23, 2014, 23:31:10Does LWJGL 2 really not support color index mode?

Latest nightly has all color index mode related functionality, missed some last time.

Quote from: kappa on November 24, 2014, 00:29:52Trying the LibGDX code also flagging up the missing extension EXTFramebufferObject which they use a lot in their LWJGL backend (not sure why they didn't just use the ARB version).

Latest nightly has support for EXT_framebuffer_object and other EXT_framebuffer extensions.

Quote from: kappa on November 25, 2014, 00:01:06btw LibGDX also uses GL20.glIsProgram(int) and GL20.glIsShader(int) which seem to be missing in LWJGL3.

Latest nightly has these two.

Quote from: delt0r on November 24, 2014, 14:54:19I did ask this in jgo, but not sure how often you check both. For new projects, should we use 3 or stick with 2 and port later?

See replies on JGO. But it depends on the project really, what are your requirements?

kappa

Quote from: spasi on November 25, 2014, 00:41:10
Quote from: kappa on November 24, 2014, 00:29:52Trying the LibGDX code also flagging up the missing extension EXTFramebufferObject which they use a lot in their LWJGL backend (not sure why they didn't just use the ARB version).

Latest nightly has support for EXT_framebuffer_object and other EXT_framebuffer extensions.

Quote from: kappa on November 25, 2014, 00:01:06btw LibGDX also uses GL20.glIsProgram(int) and GL20.glIsShader(int) which seem to be missing in LWJGL3.

Latest nightly has these two.
Excellent, they work as expected.

Another thing LibGDX uses from LWJGL2 but I couldn't find in LWJGL3 is the following methods:

GL15.glGetActiveUniform(int program, int index, int maxLength, java.nio.IntBuffer sizeType)

and
GL15.glGetActiveAttrib(int program, int index, int maxLength, java.nio.IntBuffer sizeType)


Lastly the following 2 methods are different
GL15.glVertexAttribPointer(int index, int size, boolean unsigned, boolean normalized, int stride, java.nio.ByteBuffer buffer);
GL15.glVertexAttribPointer(int index, int size, boolean unsigned, boolean normalized, int stride, java.nio.ShortBuffer buffer);

They no longer have the 'unsigned' boolean variable but require an int 'type' variable. This is definitely a change for the better in LWJGL3.

spasi

These are more examples of custom LWJGL 2 bindings. These methods were included for convenience, but they do not match the native function signatures and require special-case "magic" in the code generator. So they've been removed, more user-friendly versions can easily be written on top of LWJGL.

Warning: skip the following if you don't care (or don't want to know) about unsafe, direct pointer management in your Java code.

[unsafe]

The sizeType parameter in GetActiveUniform/GetActiveAtttrib was added because of how buffer arguments are used in LWJGL; it was impossible to use just one IntBuffer for both, you had to have two. So instead of (ignore the string decoding stuff):

IntBuffer lengthBuffer = BufferUtils.createIntBuffer(1);
IntBuffer sizeBuffer = BufferUtils.createIntBuffer(1);
IntBuffer typeBuffer = BufferUtils.createIntBuffer(1);
ByteBuffer nameBuffer = BufferUtils.createByteBuffer(maxLength);

glGetActiveUniform(program, index, lengthBuffer, sizeBuffer, typeBuffer, nameBuffer);

nameBuffer.limit(lengthBuffer.get(0));
String name = MemoryUtil.decodeASCII(nameBuffer);
int size = sizeBuffer.get(0);
int type = typeBuffer.get(1);

you could do:

IntBuffer sizeType = BufferUtils.createIntBuffer(2);

String name = glGetActiveUniform(program, index, maxLength, sizeType);

int size = sizeType.get(0);
int type = sizeType.get(1);

you can emulate the same thing in LWJGL 3 using the unsafe version:

IntBuffer intParams = BufferUtils.createIntBuffer(3);
ByteBuffer nameBuffer = BufferUtils.createByteBuffer(maxLength);

long int_p = memAddress(intParams);
nglGetActiveUniform( // note the 'n' prefix
	program, index, maxLength,
	int_p, // length
	int_p + 4, // size
	int_p + 8, // type 
	memAddress(nameBuffer)
);

String name = memDecodeASCII(nameBuffer, intParams.get(0)); // LWJGL 3 is more flexible, you don't have to set the limit before decoding
int size = intParams.get(1);
int type = intParams.get(2);

Note that this is not a special case in LWJGL 3. All bindings now expose such "unsafe" versions automatically and can be used when more flexibility and/or performance is required.

[/unsafe]

Btw, GetActiveUniform and GetActiveAtttrib are in GL20, not GL15.

kappa

Thx for the response, much appreciated.

Quote from: spasi on November 26, 2014, 12:25:38
Btw, GetActiveUniform and GetActiveAtttrib are in GL20, not GL15.
Sorry my bad, did mean GL20, typed GL15 by mistake.

Created a wrapper method to emulate LWJGL2's
glVertexAttribPointer(int index, int size, boolean unsigned, boolean normalized, int stride, ByteBuffer buffer)
as follows:
public static void glVertexAttribPointer(int index, int size,
                                         boolean unsigned, boolean normalized,
                                         int stride, ByteBuffer buffer) {
	int type = unsigned ? GL11.GL_UNSIGNED_BYTE : GL11.GL_BYTE;
	GL20.glVertexAttribPointer(index, size, type, normalized, stride, buffer);
}

bit more tricky to emulate glVertexAttribPointer(int index, int size, boolean unsigned, boolean normalized, int stride, ShortBuffer buffer), since LWJGL3's glVertexAttribPointer(int index, int size, boolean normalized, int stride, ShortBuffer pointer) doesn't allow specifying a GL_UNSIGNED_SHORT, any idea's how to emulate this when providing a ShortBuffer?

spasi

Hmm, I see. There are two ways:

- I can stop hiding the type argument, so the "auto-typed" versions will be simple overloads of the ByteBuffer version.
- You can use the unsafe version, like so:

int type = unsigned ? GL11.GL_UNSIGNED_SHORT : GL11.GL_SHORT;
nglVertexAttribPointer(index, size, type, normalized, stride, memAddress(shortBuffer));

spasi

Request for feedback

Background

I've been working on a major refactoring for the past few days, related to callback functions, and I finally think that the result is an improvement. Since this is a breaking change and I still have some work to do, it'd probably be useful to get some early feedback on this.

The problem with callbacks has always been that native callbacks work with function pointers and you can't create a function pointer to an arbitrary Java method. The only way native code can call into Java is through JNI functions. This has always been extremely limiting:

a) We had to write custom native code for each callback type.
b) Each callback type could only use a single native function pointer (that came precompiled with LWJGL) and everything had to be implemented from there, no matter how many different callbacks were needed.
c) For implementation reasons, LWJGL needed to use the callback's "user_data" argument. This was a problem because the 1) the user couldn't use it 2) some callback types don't even have a user_data argument (e.g. GLFW callbacks).

There are workarounds for everything and we could do better work with documentation, but in the end it was just too much work. Also, LWJGL 3 tries to be source-compatible with whatever official FFI support comes in Java 9 or 10 and the current custom callback code is fundamentally incompatible. I wanted to see if we could do better.

New implementation

We now use libFFI's "closures" to generate native callback functions at runtime. I won't tire you with details, there's a lot of "magic" involved, but here are the benefits:

a) We can have callbacks to arbitrary objects/methods.
b) All callback types are now auto-generated, there's no need for custom code.
c) user_data arguments can now be used by the user.
d) Java 8 lambdas are supported.

One drawback is that the new callbacks require manual cleanup (they hold on to a bit of memory that would leak), but it's quite easy (see below).

What does it look like

Lets start with GLFW, you can now do:

glfwSetWindowPosCallback(window, new GLFWwindowposfun() {
	@Override
	public void invoke(long window, int xpos, int ypos) {
		System.out.printf("Window 0x%X moved to %d, %d\n", window, xpos, ypos);
	}
});

which perfectly matches the native API. In Java 8 you can do:

glfwSetWindowPosCallback(window, (it, xpos, ypos) -> System.out.printf("Window 0x%X moved to %d, %d\n", it, xpos, ypos));

or even:

private static void windowMoved(long window, int xpos, int ypos) {
	System.out.printf("Window 0x%X moved to %d, %d\n", window, xpos, ypos);
}

{
	glfwSetWindowPosCallback(window, MyClass::windowMoved); // method reference
}


For cleanup, you can do:

glfwSetWindowPosCallback(window, null).release();
// or if you aren't sure
GLFWwindowposfun.Handle previous = glfwSetWindowPosCallback(window, null); // note you get a GLFWwindowposfun.Handle, not a GLFWwindowposfun.
if ( previous != null )
	previous.release();

Similarly, for OpenGL debugging:

glDebugMessageCallback(new DEBUGPROC() {
	@Override
	public void invoke(int source, int type, int id, int severity, String message, long userParam) {
		// ...
	}
}, NULL); // userParam is available

But this now has the problem that you don't get a previous callback back for cleanup. This can be solved by either using a DEBUGPROC.Handle directly and storing a reference to it, or, using this:

DEBUGPROC.Handle hnd = Closure.create(glGetPointer(GL_DEBUG_CALLBACK_FUNCTION));
glDebugMessageCallback(NULL, NULL);
hnd.release();

Yes, you can actually retrieve the instance from the raw function pointer!

Btw, we have both a DEBUGPROC and a DEBUGPROC.Handle for lambda support. DEBUGPROC.Handle is an abstract class that implements the magic and DEBUGPROC is a functional interface.

Let me know what you think.

kappa

Firstly really loving the new LWJGL3 direction, tis awesome, i.e. auto generating most of the bindings and pushing a lot of the maintenance burden upstream (the maintenance required on LWJGL2's Display was probably the most time consuming bit).

The new callback API looks really nice and modern but do think the cleanup part could be a little more fine tuned and made a bit more javaish and higher level without compromising functionality and flexibility.

Could a release() be automatically triggered internally on any previous handle if a null or new callback is passed to the method? Secondly all callbacks for a certain window could automatically be cleaned up internally when a window is destroyed?

spasi

It is possible but with quite a bit of implementation pain. Different callback types in different APIs may require very different approaches to do cleanup properly. We pay this price already with the current implementation; GLFW events, OpenGL debug output, OpenCL callbacks, even experimental functions like the WinGDI.EnumObjects callback, are all cleaned up automatically.

The new unified callback implementation may simplify things a bit, but we'll still have to write custom code. Moving the responsibility to the user removes that burden.

There's also the issue of reusing callbacks; allocating a callback instance is quite expensive and it makes sense to reuse the same instance many times (e.g. OpenCL event callbacks). Reference counting is used currently (callbacks implement org.lwjgl.system.Retainable) but this is open to misuse and race conditions if we try to hide cleanup details from the user.

Like always, it's a tradeoff we need to make. On one hand, a new user reads a single line of documentation and goes on to use LWJGL, but they have to be more careful and write a bit of extra code. On the other, we cover 99% of use cases with no impact on code, but users have to first read and understand a non-trivial memory management mechanism inside LWJGL (that may also be different in different APIs).