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(); }