Skip to content

Commit

Permalink
Add GL upload function to Java interface (#959)
Browse files Browse the repository at this point in the history
(This builds upon #957,
so that should be merged first)

This adds bindings for the `ktxTexture_GLUpload` function to the Java
interface.

There certainly are a few degrees of freedom about how _exactly_ that
should be offered. And there is a trade-off between "trying to closely
resemble the existing API" and "trying to create a nice API for Java".
The main point here being that the function receives three _pointers_
(to `int` values, essentially). And pointers don't exist in Java.

A common (although not pretty) way is to emulate these with
single-element arrays. So the call currently looks like this:
```
int texture[] = { 0 };
int target[] = { 0 };
int glError[] = { 0 };
int result = ktxTexture.glUpload(texture, target, glError);
```
This will fill the given arrays with the values that are returned from
the native layer, accordingly.

Proper testing may be difficult. The basic call conditions are checked
in a unit test. But beyond that, really _using_ the function will
require an OpenGL context to be current, so that 1. requires an OpenGL
binding for the test, and 2. can hardly happen during the standard test
runs.


---

I tried it out in a _very_ basic experiment. This experiment currently
used https://www.lwjgl.org/ , but I'll probably also try it with
https://jogamp.org/ . Given that LWJGL is the library behind
https://libgdx.com/ and https://jmonkeyengine.org/ , that's likely to be
a primary goal.

The result of that experiment is shown here...

![Khronos KTX in
Java](https://github.com/user-attachments/assets/7c86d565-5fdf-4f31-a20c-f6616c2c99ed)

... and I frankly couldn't care less that it's upside down. It works 🙃
  • Loading branch information
javagl authored Nov 19, 2024
1 parent f3902db commit bd8ae31
Show file tree
Hide file tree
Showing 3 changed files with 219 additions and 0 deletions.
110 changes: 110 additions & 0 deletions interface/java_binding/src/main/cpp/KtxTexture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,116 @@ extern "C" JNIEXPORT jlong JNICALL Java_org_khronos_ktx_KtxTexture_getDataSizeUn
return ktxTexture_GetDataSizeUncompressed(texture);
}


extern "C" JNIEXPORT jint JNICALL Java_org_khronos_ktx_KtxTexture_glUpload(JNIEnv *env, jobject thiz, jintArray javaTexture, jintArray javaTarget, jintArray javaGlError)
{
ktxTexture *texture = get_ktx_texture(env, thiz);
if (texture == NULL)
{
ThrowDestroyed(env);
return 0;
}

// The target array may not be NULL, and must have
// a size of at least 1
if (javaTarget == NULL)
{
ThrowByName(env, "java/lang/NullPointerException", "Parameter 'target' is null for glUpload");
return 0;
}
jsize javaTargetSize = env->GetArrayLength(javaTarget);
if (javaTargetSize == 0)
{
ThrowByName(env, "java/lang/IllegalArgumentException", "Parameter 'target' may not have length 0");
return 0;
}

// The texture array may be NULL, but if it is not NULL,
// then it must have a length of at least 1
if (javaTexture != NULL)
{
jsize javaTextureSize = env->GetArrayLength(javaTexture);
if (javaTextureSize == 0)
{
ThrowByName(env, "java/lang/IllegalArgumentException", "Parameter 'texture' may not have length 0");
return 0;
}
}

// The GL error array may be NULL, but if it is not NULL,
// then it must have a length of at least 1
if (javaGlError != NULL)
{
jsize javaGlErrorSize = env->GetArrayLength(javaGlError);
if (javaGlErrorSize == 0)
{
ThrowByName(env, "java/lang/IllegalArgumentException", "Parameter 'glError' may not have length 0");
return 0;
}
}

GLuint textureValue = 0;
if (javaTexture != NULL)
{
jint *javaTextureArrayElements = env->GetIntArrayElements(javaTexture, NULL);
if (javaTextureArrayElements == NULL)
{
// OutOfMemoryError is already pending
return 0;
}
textureValue = static_cast<GLuint>(javaTextureArrayElements[0]);
env->ReleaseIntArrayElements(javaTexture, javaTextureArrayElements, JNI_ABORT);
}

GLenum target;
GLenum glError;

KTX_error_code result = ktxTexture_GLUpload(texture, &textureValue, &target, &glError);

// Write back the texture into the array
if (javaTexture != NULL)
{
jint *javaTextureArrayElements = env->GetIntArrayElements(javaTexture, NULL);
if (javaTextureArrayElements == NULL)
{
// OutOfMemoryError is already pending
return 0;
}
javaTextureArrayElements[0] = static_cast<jint>(textureValue);
env->ReleaseIntArrayElements(javaTexture, javaTextureArrayElements, JNI_COMMIT);
}

// Write back the target into the array
if (javaTarget != NULL)
{
jint *javaTargetArrayElements = env->GetIntArrayElements(javaTarget, NULL);
if (javaTargetArrayElements == NULL)
{
// OutOfMemoryError is already pending
return 0;
}
javaTargetArrayElements[0] = static_cast<jint>(target);
env->ReleaseIntArrayElements(javaTarget, javaTargetArrayElements, JNI_COMMIT);
}

// Write back the error into the array
if (javaGlError != NULL)
{
jint *javaGlErrorArrayElements = env->GetIntArrayElements(javaGlError, NULL);
if (javaGlErrorArrayElements == NULL)
{
// OutOfMemoryError is already pending
return 0;
}
javaGlErrorArrayElements[0] = static_cast<jint>(glError);
env->ReleaseIntArrayElements(javaGlError, javaGlErrorArrayElements, JNI_COMMIT);
}
return result;
}




extern "C" JNIEXPORT jint JNICALL Java_org_khronos_ktx_KtxTexture_getElementSize(JNIEnv *env, jobject thiz)
{
ktxTexture *texture = get_ktx_texture(env, thiz);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,61 @@ public boolean isDestroyed() {
*/
public native long getImageOffset(int level, int layer, int faceSlice);

/**
* Create a GL texture object from a {@link KtxTexture} object.<br>
* <br>
* This may only be called when a GL context is current.<br>
* <br>
* In order to ensure that the GL uploader is not linked into an application
* unless explicitly called, this is not a virtual function. It determines
* the texture type then dispatches to the correct function.<br>
* <br>
* Sets the texture object's <code>GL_TEXTURE_MAX_LEVEL</code> parameter
* according to the number of levels in the KTX data, provided the
* context supports this feature.<br>
* <br>
* Unpacks compressed {@link KtxInternalformat#GL_ETC1_RGB8_OES} and
* <code>GL_ETC2_*</code> format textures in software when the format
* is not supported by the GL context, provided the library has been
* compiled with <code>SUPPORT_SOFTWARE_ETC_UNPACK</code> defined as 1.
*
* It will also convert textures with legacy formats to their modern
* equivalents when the format is not supported by the GL context,
* provided the library has been compiled with
* <code>SUPPORT_LEGACY_FORMAT_CONVERSION</code> defined as 1.
*
* @param texture An array that is either <code>null</code>, or
* has a length of at least 1. It contains the name of the GL texture
* object to load. If it is <code>null</code> or contains 0, the
* function will generate a texture name. The function binds either
* the generated name or the name given in <code>texture</code> to
* the texture target returned in <code>target</code>, before
* loading the texture data. If pTexture is not <code>null</code>
* and a name was generated, the generated name will be returned
* in <code>texture</code>.
* @param target An array with a length of at least 1, where
* element 0 will receive the GL target value
* @param glError An array with a length of at least 1, where
* element 0 will receive any GL error information
* @return {@link KtxErrorCode#SUCCESS} on sucess.
* Returns {@link KtxErrorCode#GL_ERROR} when GL error was raised by
* <code>glBindTexture</code>, <code>glGenTextures</code> or
* <code>gl*TexImage*</code> The GL error will be returned in
* <code>glError</code> if <code>glError</code> is not
* <code>null</code>. Returns {@link KtxErrorCode#INVALID_VALUE}
* when target is <code>null</code> or the size of a mip level
* is greater than the size of the preceding level. Returns
* {@link KtxErrorCode#NOT_FOUND} when a dynamically loaded
* OpenGL function required by the loader was not found.
* Returns {@link KtxErrorCode#UNSUPPORTED_TEXTURE_TYPE} when
* the type of texture is not supported by the current OpenGL context.
* @throws NullPointerException If the given <code>target</code>
* array is <code>null</code>
* @throws IllegalArgumentException Any array that is not
* <code>null</code> has a length of 0
*/
public native int glUpload(int texture[], int target[], int glError[]);

/**
* Destroy the KTX texture and free memory image resources.<br>
* <br>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -642,4 +642,58 @@ public void testBindings() {
texture.getImageSize(0);
}

@Test
public void testGlUpload() {
Path testKtxFile = Paths.get("")
.resolve("../../tests/testimages/astc_ldr_4x4_FlightHelmet_baseColor.ktx2")
.toAbsolutePath()
.normalize();

KtxTexture2 ktxTexture = KtxTexture2.createFromNamedFile(testKtxFile.toString(),
KtxTextureCreateFlagBits.NO_FLAGS);
ktxTexture.transcodeBasis(KtxTranscodeFormat.BC1_RGB, 0);

// This test checks the error conditions that are supposed
// to be handled by the JNI layer by throwing exceptions.
// The test can NOT perform an actual, "valid" call that
// causes ktxTexture_GLUpload to be called internally,
// because that would require a GL context to be current.
int texture0[] = { };
int target0[] = { };
int glError0[] = { };
int texture1[] = { 0 };
int target1[] = { 0 };
int glError1[] = { 0 };

// Expect NullPointerException when target is null
assertThrows(NullPointerException.class,
() -> {
ktxTexture.glUpload(texture1, null, glError1);
},
"Expected to throw NullPointerException");

// Expect IllegalArgumentException when texture length is 0
assertThrows(IllegalArgumentException.class,
() -> {
ktxTexture.glUpload(texture0, target1, glError1);
},
"Expected to throw NullPointerException");

// Expect IllegalArgumentException when target length is 0
assertThrows(IllegalArgumentException.class,
() -> {
ktxTexture.glUpload(texture1, target0, glError1);
},
"Expected to throw NullPointerException");

// Expect IllegalArgumentException when glError length is 0
assertThrows(IllegalArgumentException.class,
() -> {
ktxTexture.glUpload(texture1, target1, glError0);
},
"Expected to throw NullPointerException");

ktxTexture.destroy();
}

}

0 comments on commit bd8ae31

Please sign in to comment.