Using Pointers with LWJGL3

Started by vorbis, October 15, 2015, 22:34:40

Previous topic - Next topic

vorbis

Hi,

I am trying to understand how to read C struct arrays with LWJGL3.

For Example LWJGL3's STBTruetype.stbtt_GetGlyphShape() provides a pointer to a STBTTVertex array.

This method returns the number of STBTTVertex's and a pointer to an array of STBTTVertex, but how would I read its values?

1) I've got the following which returns the number of STBTTVertex's but can't figure out how to read the values of the STBTTVertex array?

ByteBuffer vertices = STBTTVertex.malloc();
int count = STBTruetype.stbtt_GetGlyphShape(fontInfo, 65, vertices);


2) I also notice there is a variation of STBTruetype.stbtt_GetGlyphShape() which accepts PointerBuffer, how does its usage differ and how do I use it?

I've tried googling for example usage but haven't been able to find anything.

Any help would be appreciated.

spasi

Hey vorbis,

I began typing a reply this morning but it got unnecessarily complicated and I then spent the rest of the day working on improvements. I also found two bugs in the process. So, first of all, make sure you download the latest nightly build (3.0.0b #49), everything below applies to that build only (it looks like you're still on 3.0.0a or the stable 3.0.0b build that doesn't have the new struct APIs).

So, lets start with the native signature:

int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices);


This function allocates an array of stbtt_vertex internally and returns its address to the vertices parameter. That is, vertices is an output parameter and we must provide some memory for the address value to be written. We cannot use the stack in Java, so we must use an off-heap buffer that has enough space for a single 4 (32bit) or 8 (64 bit) byte value. Normally that would mean either an IntBuffer or a LongBuffer, but LWJGL provides the PointerBuffer class that abstracts away the pointer size. This is how to you would normally use the above function:

PointerBuffer verticesAddress = BufferUtils.createPointerBuffer(1);
int vertexCount = stbtt_GetGlyphShape(info, 'a', verticesAddress);
STBTTVertex.Buffer vertices = STBTTVertex.createBuffer(verticesAddress.get(0), vertexCount);


After the call, verticesAddress.get(0) contains the address to an array of vertexCount structs. These can be passed to STBTTVertex.createBuffer that returns a struct buffer instance.

The stbtt_GetGlyphShape overload that accepts a ByteBuffer is similar to the above, except you need to manually ensure that it has enough space for the pointer address. Such overloads exist for all methods in all LWJGL bindings, there's always a "typed" version and an "untyped" (ByteBuffer) one.

With the latest nightly build, I have added yet another overload that hides the above complexity and returns a STBTTVertex.Buffer directly. This transformation is usually done only with methods that normally return void, but I think it's quite useful for this case too. Full example:

STBTTFontinfo info = STBTTFontinfo.malloc();
stbtt_InitFont(info, ttf); // ttf is a buffer with the data of a .ttf file

STBTTVertex.Buffer vertices = stbtt_GetGlyphShape(info, 'a');
for ( int i = 0; i < vertices.capacity(); i++ ) {
	STBTTVertex v = vertices.get(i);

	switch ( v.getType() ) {
		case STBTT_vmove:
			System.out.println("MOVE: " + v.getX() + " - " + v.getY());
			break;
		case STBTT_vline:
			System.out.println("LINE: " + v.getX() + " - " + v.getY());
			break;
		case STBTT_vcurve:
			System.out.println("CURV: " + v.getCx() + " - " + v.getCy() + " - " + v.getX() + " - " + v.getY());
			break;
	}
}

stbtt_FreeShape(info, vertices);
info.free();

vorbis

Thank you so much for your explanation and changes to the library. The new API is much nicer and easier to use.

Just one last question, I note that in your example you are passing a Java char, is it safe to use for all Java char's? Would there be any issue of UTF16 vs UTF8 (maybe LWJGL internally handles this conversion?).

Although the above code works, glyph_index is not always the same as the unicode codepoint so it should use:
stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, 'a'));

or

stbtt_GetCodepointShape(info, 'a'); // which is same as above done internally in STB

spasi

Quote from: vorbis on October 17, 2015, 09:36:44Although the above code works, glyph_index is not always the same as the unicode codepoint so the example should actually be:
stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, 'a'));

or

stbtt_GetCodepointShape(info, 'a'); // which is same as above done internally in STB

You're correct, I should have used stbtt_FindGlyphIndex there.

Quote from: vorbis on October 17, 2015, 09:36:44Just one last question, I note that in your example you are passing a Java char, is it safe to use for all Java char's? Would there be any issue of UTF16 vs UTF8 (maybe LWJGL internally handles this conversion?).

LWJGL does no conversion internally, it's just standard Java char to int promotion. This works fine up to Character.MAX_VALUE, but you must use integers directly (e.g. via Character.toCodePoint) for the full Unicode range.

vorbis

excellent, thanks for all your help.

vorbis

When calling stbtt_GetGlyphShape() with the space char ' ' (glyphIndex 3 on the arialbd.ttf font), I'm getting a null pointer exception:

Quotejava.lang.NullPointerException
   at org.lwjgl.system.Checks.checkBuffer(Checks.java:209)
   at org.lwjgl.system.StructBuffer.<init>(StructBuffer.java:25)
   at org.lwjgl.stb.STBTTVertex$Buffer.<init>(STBTTVertex.java:158)
   at org.lwjgl.stb.STBTTVertex.createBuffer(STBTTVertex.java:125)
   at org.lwjgl.stb.STBTruetype.stbtt_GetGlyphShape(STBTruetype.java:1084)

I know the space character doesn't have any vertices but might be nice if it returned a null or an empty array of STBTTVertex.Buffer instead of an exception.

spasi

Hey vorbis,

Fixed in build 3.0.0b #50, the createBuffer methods will now return null if the address is 0L.

vorbis

thank you, tested the nightly and it works as expected now.