From 9a66b454560401d47072de7f37a15970b9a25127 Mon Sep 17 00:00:00 2001
From: Marco Hutter <javagl@javagl.de>
Date: Tue, 12 Nov 2024 23:00:16 +0100
Subject: [PATCH 1/5] Fix JNI function name for getDataSizeUncompressed

---
 .../java_binding/src/main/cpp/KtxTexture.cpp  |  2 +-
 .../org/khronos/ktx/test/KtxTexture2Test.java | 33 +++++++++++++++++++
 2 files changed, 34 insertions(+), 1 deletion(-)

diff --git a/interface/java_binding/src/main/cpp/KtxTexture.cpp b/interface/java_binding/src/main/cpp/KtxTexture.cpp
index 0a6028f613..d8faf64a48 100644
--- a/interface/java_binding/src/main/cpp/KtxTexture.cpp
+++ b/interface/java_binding/src/main/cpp/KtxTexture.cpp
@@ -188,7 +188,7 @@ extern "C" JNIEXPORT jlong JNICALL Java_org_khronos_ktx_KtxTexture_getDataSize(J
     return static_cast<jlong>(ktxTexture_GetDataSize(texture));
 }
 
-extern "C" JNIEXPORT jlong JNICALL Java_org_khronos_KTXTexture_getDataSizeUncompressed(JNIEnv *env, jobject thiz)
+extern "C" JNIEXPORT jlong JNICALL Java_org_khronos_ktx_KtxTexture_getDataSizeUncompressed(JNIEnv *env, jobject thiz)
 {
     ktxTexture *texture = get_ktx_texture(env, thiz);
     if (texture == NULL) 
diff --git a/interface/java_binding/src/test/java/org/khronos/ktx/test/KtxTexture2Test.java b/interface/java_binding/src/test/java/org/khronos/ktx/test/KtxTexture2Test.java
index 2518bc8cbd..1ba1b95bf3 100644
--- a/interface/java_binding/src/test/java/org/khronos/ktx/test/KtxTexture2Test.java
+++ b/interface/java_binding/src/test/java/org/khronos/ktx/test/KtxTexture2Test.java
@@ -599,4 +599,37 @@ public void testSupercompressionZLIB() throws IOException {
 
         t.destroy();
     }
+
+    @Test
+    public void testBindings() {
+        Path testKtxFile = Paths.get("")
+                .resolve("../../tests/testimages/astc_ldr_4x4_FlightHelmet_baseColor.ktx2")
+                .toAbsolutePath()
+                .normalize();
+
+        KtxTexture2 texture = KtxTexture2.createFromNamedFile(testKtxFile.toString(),
+                KtxTextureCreateFlagBits.NO_FLAGS);
+        texture.getOETF();
+        texture.getPremultipliedAlpha();
+        texture.needsTranscoding();
+        texture.getSupercompressionScheme();
+        texture.getVkFormat();
+
+        texture.isArray();
+        texture.isCubemap();
+        texture.isCompressed();
+        texture.getGenerateMipmaps();
+        texture.getBaseWidth();
+        texture.getBaseHeight();
+        texture.getBaseDepth();
+        texture.getNumDimensions();
+        texture.getNumLevels();
+        texture.getNumFaces();
+        texture.getDataSize();
+        texture.getDataSizeUncompressed();
+        texture.getElementSize();
+        texture.getRowPitch(0);
+        texture.getImageSize(0);
+    }
+
 }

From 5e15ad8290c8880112812c325f9b1b4d2cb2164a Mon Sep 17 00:00:00 2001
From: Marco Hutter <javagl@javagl.de>
Date: Wed, 13 Nov 2024 20:52:17 +0100
Subject: [PATCH 2/5] Add glUpload to Java interface

---
 .../java_binding/src/main/cpp/KtxTexture.cpp  | 111 ++++++++++++++++++
 .../main/java/org/khronos/ktx/KtxTexture.java |  55 +++++++++
 .../org/khronos/ktx/test/KtxTexture2Test.java |  55 +++++++++
 3 files changed, 221 insertions(+)

diff --git a/interface/java_binding/src/main/cpp/KtxTexture.cpp b/interface/java_binding/src/main/cpp/KtxTexture.cpp
index d8faf64a48..f3525be914 100644
--- a/interface/java_binding/src/main/cpp/KtxTexture.cpp
+++ b/interface/java_binding/src/main/cpp/KtxTexture.cpp
@@ -199,6 +199,117 @@ 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;
+    GLuint *pTexture = &textureValue;
+    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);
diff --git a/interface/java_binding/src/main/java/org/khronos/ktx/KtxTexture.java b/interface/java_binding/src/main/java/org/khronos/ktx/KtxTexture.java
index 7f3f7fad4c..892ab3ab21 100644
--- a/interface/java_binding/src/main/java/org/khronos/ktx/KtxTexture.java
+++ b/interface/java_binding/src/main/java/org/khronos/ktx/KtxTexture.java
@@ -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>
diff --git a/interface/java_binding/src/test/java/org/khronos/ktx/test/KtxTexture2Test.java b/interface/java_binding/src/test/java/org/khronos/ktx/test/KtxTexture2Test.java
index 1ba1b95bf3..78b19a793e 100644
--- a/interface/java_binding/src/test/java/org/khronos/ktx/test/KtxTexture2Test.java
+++ b/interface/java_binding/src/test/java/org/khronos/ktx/test/KtxTexture2Test.java
@@ -632,4 +632,59 @@ 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);
+
+        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");
+
+        // Expect no exceptions when only target is not null
+        ktxTexture.glUpload(null, target1, null);
+
+        // Expect no exceptions when all arrays have proper length
+        ktxTexture.glUpload(texture1, target1, glError1);
+
+        ktxTexture.destroy();
+    }
+
 }

From bd199ff2e9630ac71b303b06abee8bccb8ededc4 Mon Sep 17 00:00:00 2001
From: Marco Hutter <javagl@javagl.de>
Date: Thu, 14 Nov 2024 13:00:20 +0100
Subject: [PATCH 3/5] Added comments to bindings test

---
 .../java/org/khronos/ktx/test/KtxTexture2Test.java     | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/interface/java_binding/src/test/java/org/khronos/ktx/test/KtxTexture2Test.java b/interface/java_binding/src/test/java/org/khronos/ktx/test/KtxTexture2Test.java
index 1ba1b95bf3..0e00b1744c 100644
--- a/interface/java_binding/src/test/java/org/khronos/ktx/test/KtxTexture2Test.java
+++ b/interface/java_binding/src/test/java/org/khronos/ktx/test/KtxTexture2Test.java
@@ -607,14 +607,24 @@ public void testBindings() {
                 .toAbsolutePath()
                 .normalize();
 
+        // The purpose of this test is to check the bindings for the 'native'
+        // functions that only return a value. When the binding for one of
+        // these functions is not implemented properly, then trying to call
+        // it will cause an 'UnsatisfiedLinkError'.
+        // This does not cover all 'native' functions: Some of them can only
+        // sensibly be called in the context of the other tests.
+
         KtxTexture2 texture = KtxTexture2.createFromNamedFile(testKtxFile.toString(),
                 KtxTextureCreateFlagBits.NO_FLAGS);
+
+        // Native getter methods from the 'KtxTexture2' class
         texture.getOETF();
         texture.getPremultipliedAlpha();
         texture.needsTranscoding();
         texture.getSupercompressionScheme();
         texture.getVkFormat();
 
+        // Native getter methods from the 'KtxTexture' class
         texture.isArray();
         texture.isCubemap();
         texture.isCompressed();

From 9078f0ae31d05e9749a085d78f5e98be95c0ac22 Mon Sep 17 00:00:00 2001
From: Marco Hutter <javagl@javagl.de>
Date: Thu, 14 Nov 2024 16:27:47 +0100
Subject: [PATCH 4/5] Removed unused local variable

---
 interface/java_binding/src/main/cpp/KtxTexture.cpp | 1 -
 1 file changed, 1 deletion(-)

diff --git a/interface/java_binding/src/main/cpp/KtxTexture.cpp b/interface/java_binding/src/main/cpp/KtxTexture.cpp
index f3525be914..2b8cff1150 100644
--- a/interface/java_binding/src/main/cpp/KtxTexture.cpp
+++ b/interface/java_binding/src/main/cpp/KtxTexture.cpp
@@ -248,7 +248,6 @@ extern "C" JNIEXPORT jint JNICALL Java_org_khronos_ktx_KtxTexture_glUpload(JNIEn
     }
 
     GLuint textureValue = 0;
-    GLuint *pTexture = &textureValue;
     if (javaTexture != NULL) 
     {
       jint *javaTextureArrayElements = env->GetIntArrayElements(javaTexture, NULL);

From e1f656c3fb589ea0cbe63163a9a530510468fc42 Mon Sep 17 00:00:00 2001
From: Marco Hutter <javagl@javagl.de>
Date: Sat, 16 Nov 2024 13:15:27 +0100
Subject: [PATCH 5/5] Omit tests that may require a GL context

---
 .../java/org/khronos/ktx/test/KtxTexture2Test.java    | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/interface/java_binding/src/test/java/org/khronos/ktx/test/KtxTexture2Test.java b/interface/java_binding/src/test/java/org/khronos/ktx/test/KtxTexture2Test.java
index 05cc55307b..db2a1c4602 100644
--- a/interface/java_binding/src/test/java/org/khronos/ktx/test/KtxTexture2Test.java
+++ b/interface/java_binding/src/test/java/org/khronos/ktx/test/KtxTexture2Test.java
@@ -653,6 +653,11 @@ public void testGlUpload() {
                 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[] = { };
@@ -688,12 +693,6 @@ public void testGlUpload() {
             },
             "Expected to throw NullPointerException");
 
-        // Expect no exceptions when only target is not null
-        ktxTexture.glUpload(null, target1, null);
-
-        // Expect no exceptions when all arrays have proper length
-        ktxTexture.glUpload(texture1, target1, glError1);
-
         ktxTexture.destroy();
     }