This commit is contained in:
2026-06-14 19:09:18 +01:00
parent 14bd1a9271
commit 13fa90a0e9
3958 changed files with 999286 additions and 4 deletions
+127
View File
@@ -0,0 +1,127 @@
# Copyright (c) 2021, Shukant Pal and Contributors
# SPDX-License-Identifier: Apache-2.0
# This ugliness is to workaround that, unless JAVA_HOME is explicitly set,
# FindJNI tries to find a Java framework even though recent Java versions,
# such as we need, are not Frameworks so the search fails. This is still the
# case in CMake 3.19.2. Even if this is fixed in the latest CMake, due to some
# of our CI build environments we can't use it.
if(DEFINED CMAKE_FIND_FRAMEWORK)
set(SAVED_CMAKE_FIND_FRAMEWORK ${CMAKE_FIND_FRAMEWORK})
endif()
set(CMAKE_FIND_FRAMEWORK NEVER)
find_package(JNI 11 REQUIRED)
if(DEFINED SAVED_CMAKE_FIND_FRAMEWORK)
set(CMAKE_FIND_FRAMEWORK ${SAVED_CMAKE_FIND_FRAMEWORK})
endif()
if(WIN32)
set(maven_cmd "mvn.cmd")
else()
set(maven_cmd "mvn")
endif()
find_program(MAVEN_EXECUTABLE ${maven_cmd}
DOC "Location of `mvn` or (on Windows) `mvn.cmd` executable"
PATHS $ENV{PATH}
)
add_library(ktx-jni SHARED
src/main/cpp/KtxTexture.cpp
src/main/cpp/KtxTexture1.cpp
src/main/cpp/KtxTexture2.cpp
src/main/cpp/KtxErrorCode.cpp
src/main/cpp/libktx-jni.cpp
${CMAKE_CURRENT_BINARY_DIR}/ktx-jni.manifest
)
configure_file(src/main/cpp/ktx-jni.manifest.in ktx-jni.manifest)
target_include_directories(ktx-jni SYSTEM PRIVATE
${JNI_INCLUDE_DIRS}
)
set_target_properties(ktx-jni PROPERTIES
LIBRARY_OUTPUT_DIRECTORY ${KTX_BUILD_DIR}/$<CONFIG>
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR}
XCODE_ATTRIBUTE_ENABLE_HARDENED_RUNTIME "YES"
)
# The location of libktx_jni must be set explicitly in java.library.path.
# This sets places to search for libktx when loading libktx_jni. Setting
# "./" here, to say look in the same directory, does not work.
if(APPLE)
set_target_properties(ktx-jni PROPERTIES
# @executable_path does not work as the executable here is the JVM.
INSTALL_RPATH "@loader_path;/usr/local/${CMAKE_INSTALL_LIBDIR}"
)
elseif(LINUX)
set_target_properties(ktx-jni PROPERTIES
# Reportedly ld.so when loading a .so with a DT_RUNPATH of $ORIGIN
# searches first in the directory of the .so then in the directory
# of the application that is loading the first .so. See
# https://stackoverflow.com/questions/23006930/the-shared-library-rpath-and-the-binary-rpath-priority/52647116#52647116
INSTALL_RPATH "$ORIGIN;/usr/local/${CMAKE_INSTALL_LIBDIR}"
)
endif()
set_code_sign(ktx-jni)
if(APPLE AND KTX_EMBED_BITCODE)
target_compile_options(ktx-jni PRIVATE "-fembed-bitcode")
endif()
target_include_directories(ktx-jni PRIVATE include)
target_link_libraries(ktx-jni ktx)
if(APPLE OR LINUX)
install(TARGETS ktx-jni
LIBRARY
DESTINATION ${CMAKE_INSTALL_LIBDIR}
COMPONENT jni
)
else()
install(TARGETS ktx-jni LIBRARY
RUNTIME
DESTINATION ${CMAKE_INSTALL_BINDIR}
COMPONENT jni
)
endif()
add_custom_command(
OUTPUT
${CMAKE_SOURCE_DIR}/interface/java_binding/target/libktx-${PROJECT_VERSION}-sources.jar
${CMAKE_SOURCE_DIR}/interface/java_binding/target/libktx-${PROJECT_VERSION}.jar
COMMAND
${MAVEN_EXECUTABLE} --quiet -Drevision=${PROJECT_VERSION} -Dmaven.test.skip=true package
DEPENDS
ktx-jni
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/interface/java_binding
)
add_custom_target( ktx-jar ALL
DEPENDS
${CMAKE_SOURCE_DIR}/interface/java_binding/target/libktx-${PROJECT_VERSION}-sources.jar
${CMAKE_SOURCE_DIR}/interface/java_binding/target/libktx-${PROJECT_VERSION}.jar
WORKING_DIRECTORY
${CMAKE_SOURCE_DIR}/interface/java_binding
COMMENT
"Java wrapper target"
)
install(FILES
${CMAKE_SOURCE_DIR}/interface/java_binding/target/libktx-${PROJECT_VERSION}.jar
TYPE LIB
COMPONENT jni
)
add_test( NAME Java-wrapper
COMMAND ${MAVEN_EXECUTABLE} --quiet -Drevision=${PROJECT_VERSION} test
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/interface/java_binding
)
set_tests_properties(
Java-wrapper
PROPERTIES
ENVIRONMENT _JAVA_OPTIONS=-Djava.library.path=$<TARGET_GENEX_EVAL:ktx-jni,$<TARGET_PROPERTY:ktx-jni,LIBRARY_OUTPUT_DIRECTORY>>
)
# vim:ai:ts=4:sts=4:sw=2:expandtab
+74
View File
@@ -0,0 +1,74 @@
Copyright (c) 2021, Shukant Pal and Contributors \
SPDX-License-Identifier: Apache-2.0
Java bindings for [libktx](https://github.com/KhronosGroup/KTX-Software), made with love by [Shukant Pal](https://github.com/ShukantPal) originally for the [Texture Compression Tool](https://compressor.shukantpal.com).
The `libktx-jni` library is built by the CMake project in the repository root. This library glues the `libktx` API with the interfaces provided in this Java library. You'll need to install `libktx` and `libktx-jni` to use the bindings. These, together with the Java archive `libktx.jar` can be installed from the packages found on the [KTX Software Releases](https://github.com/KhronosGroup/KTX-Software/releases) page.
Note: Java does not support arrays with more than 2³² elements so you should not use this library for images larger than four gigabytes in size.
## Usage
The setup is as follows:
```java
import org.khronos.ktx.KtxTexture2;
import java.nio.file.Paths;
public class App {
static {
// Load libktx-jni, which provides the JNI stubs for natively
// implemented Java methods. This should also load libktx
// automatically! If it doesn't, you may need to load libktx manually.
System.loadLibrary("ktx-jni");
}
public static void main(String[] args) {
KTXTexture2 texture = KTXTexture2.createFromNamedFile(
Paths.get("exampleInput.ktx2").toAbsolutePath().toString());
// Do something special with the texture!
texture.writeToNamedFile(
Paths.get("exampleOutput.ktx2").toAbsolutePath().toString());
}
}
```
## Build
You must have Maven installed.
Pass `-DKTX_FEATURE_JNI=ON` when configuring the CMake build for `libktx` so that `libktx-jni` and `libktx.jar` are built as well.
This will place the libraries in a sub-directory of the build directory you
configured with CMake corresponding to the configuration you are building, usually `Debug` or `Release` and the JAR in the `target` directory in `interfaces/java_binding`. When building your application, include this JAR in the build.
The installers install the JAR is the same library directory as `libktx` and `libktx-jni`. On GNU/Linux and macOS this is `/usr/local/lib`.
## Manually Build JAR
You must have Maven installed:
```
mvn package
```
The JAR is placed in the location described in the previous section.
## Run tests on macOS
It's tricky - I know.
```
_JAVA_OPTIONS=-Djava.library.path=/usr/local/lib mvn test
```
The path shown above is for the case when `libktx` and `libktx-jni` have been installed. If you have only built them then use
```
_JAVA_OPTIONS=-Djava.library.path=/path/to/your/cmake/build/<config> mvn test
```
where `<config>` is your build configuration, usually either `Debug` or `Release`.
+226
View File
@@ -0,0 +1,226 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2021, Shukant Pal and Contributors
SPDX-License-Identifier: Apache-2.0
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<properties>
<revision>4.0.0-SNAPSHOT</revision>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<groupId>org.khronos</groupId>
<artifactId>libktx</artifactId>
<version>${revision}</version> <!-- property MUST be "revision". -->
<name>libktx</name>
<description>Java Bindings for Khronos KTX Library</description>
<url>https://www.khronos.org/ktx/</url>
<licenses>
<license>
<name>The Apache License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
</license>
</licenses>
<developers>
<developer>
<name>Shukant Pal</name>
<email>shukantpal@outlook.com</email>
<organization>Khronos Group</organization>
<organizationUrl>http://www.khronos.org</organizationUrl>
</developer>
</developers>
<scm>
<connection>scm:git:git://github.com/KhronosGroup/KTX-Software.git</connection>
<developerConnection>scm:git:ssh://github.com:KhronosGroup/KTX-Software.git</developerConnection>
<url>https://github.com/KhronosGroup/KTX-Software/tree/main</url>
</scm>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.4.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
<!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.7.1</version>
</plugin>
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.0.0</version>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<version>2.5.3</version>
<configuration>
<localCheckout>true</localCheckout>
<pushChanges>false</pushChanges>
<mavenExecutorId>forked-path</mavenExecutorId>
<arguments>-Dgpg.passphrase=${gpg.passphrase}</arguments>
</configuration>
<dependencies>
<dependency>
<groupId>org.apache.maven.scm</groupId>
<artifactId>maven-scm-provider-gitexe</artifactId>
<version>1.9.5</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>1.6.7</version>
<extensions>true</extensions>
<configuration>
<serverId>ossrh</serverId>
<nexusUrl>https://oss.sonatype.org/</nexusUrl>
<autoReleaseAfterClose>true</autoReleaseAfterClose>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.0.1</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.6.3</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<!--
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>properties-maven-plugin</artifactId>
<version>1.0-alpha-1</version>
<executions>
<execution>
<phase>initialize</phase>
<goals>
<goal>read-project-properties</goal>
</goals>
<configuration>
<files>
<file>version.properties</file>
</files>
</configuration>
</execution>
</executions>
</plugin>
-->
</plugins>
</build>
<profiles>
<!-- GPG Signature on release -->
<profile>
<id>release-sign-artifacts</id>
<activation>
<property>
<name>performRelease</name>
<value>true</value>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<!-- Uncomment when we have access to Maven Central-->
<!--
<distributionManagement>
<snapshotRepository>
<id>ossrh</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</snapshotRepository>
<repository>
<id>ossrh</id>
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
</repository>
</distributionManagement>
-->
</project>
@@ -0,0 +1,53 @@
/*
* Copyright (c) 2024, Khronos Group and Contributors
* SPDX-License-Identifier: Apache-2.0
*/
package org.khronos.ktx;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.io.IOException;
import org.junit.jupiter.api.Test;
public class KtxUtilitiesTest {
@Test
public void testValidSwizzle() {
char swizzle[] = new char[] { 'a', '1', 'r', '0' };
char expected[] = swizzle;
char actual[] = KtxUtilities.validateSwizzle(swizzle);
assertArrayEquals(expected, actual, "Accepts valid swizzle and returns it");
}
@Test
public void testNullSwizzle() {
char expected[] = new char[] { 0, 0, 0, 0 };
char actual[] = KtxUtilities.validateSwizzle(null);
assertArrayEquals(expected, actual, "Accepts null swizzle (to apply no swizzle), and returns a default");
}
@Test
public void testDefaultSwizzle() {
char swizzle[] = new char[] { 0, 0, 0, 0 };
char expected[] = swizzle;
char actual[] = KtxUtilities.validateSwizzle(swizzle);
assertArrayEquals(expected, actual, "Accepts default swizzle (all zeros)");
}
@Test
public void testInvalidSwizzleLength() throws IOException {
char swizzle[] = new char[] { 'a', 'b', 'r', 'g', 'r', 'g' };
assertThrows(IllegalArgumentException.class, () -> KtxUtilities.validateSwizzle(swizzle),
"Swizzle length != 4 expected to throw IllegalArgumentException");
}
@Test
public void testInvalidSwizzleChar() throws IOException {
char swizzle[] = new char[] { 'a', 'b', 'X', 'g' };
assertThrows(IllegalArgumentException.class, () -> KtxUtilities.validateSwizzle(swizzle),
"Invalid swizzle character expected to throw IllegalArgumentException");
}
}
@@ -0,0 +1,88 @@
/*
* Copyright (c) 2023, Shukant Pal, robnugent, and Contributors
* SPDX-License-Identifier: Apache-2.0
*/
package org.khronos.ktx.test;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.khronos.ktx.*;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
@ExtendWith({ KtxTestLibraryLoader.class })
public class KtxParallelTest {
private static final int NUM_THREADS = 2;
private static final Logger logger = Logger.getLogger(KtxParallelTest.class.getCanonicalName());
@Test
public void testParallelAstcConversion() throws InterruptedException {
final Thread[] runThreads = new Thread[NUM_THREADS];
for (int i = 0; i < NUM_THREADS; i++) {
final KtxTestRun run = new KtxTestRun(i);
final Thread runThread = new Thread(run);
runThread.setDaemon(false);
runThread.start();
runThreads[i] = runThread;
}
for (Thread thread : runThreads) {
thread.join();
}
}
private static class KtxTestRun implements Runnable {
private final int id;
private final Random testRandomizer = new Random();
public KtxTestRun(int id) {
this.id = id;
}
public void run() {
// Repeatedly create a compress an image.
for (int i = 0; i < 30; i++) {
final int w = (testRandomizer.nextInt() % 512) + 1024;
final int h = w;
final int size = convertToASTC(w, h);
// Change level to INFO for logging
logger.log(Level.FINE,id + " iteration: " + i + ", size: " + w + "x" + h + ", compressed data size is " + size);
}
}
public int convertToASTC(int w, int h) {
// Create Uncompressed texture
final KtxTextureCreateInfo info = new KtxTextureCreateInfo();
info.setBaseWidth(w);
info.setBaseHeight(h);
info.setVkFormat(VkFormat.VK_FORMAT_R8G8B8_SRGB); // Uncompressed
final KtxTexture2 t = KtxTexture2.create(info, KtxTextureCreateStorage.ALLOC_STORAGE);
// Pass the uncompressed data
int bufferSize = w * h * 3;
final byte[] rgbBA = new byte[bufferSize];
t.setImageFromMemory(0, 0, 0, rgbBA);
// Compress the data
final KtxAstcParams p = new KtxAstcParams();
p.setBlockDimension(KtxPackAstcBlockDimension.D8x8);
p.setMode(KtxPackAstcEncoderMode.LDR);
p.setQualityLevel(KtxPackAstcQualityLevel.EXHAUSTIVE);
final int rc = t.compressAstcEx(p);
if (rc != KtxErrorCode.SUCCESS) {
throw new RuntimeException("ASTC error " + rc);
}
final int retDataLen = (int) t.getDataSize();
// Free things up - segfault usually occurs inside this destroy() call
t.destroy();
return retDataLen;
}
}
}
@@ -0,0 +1,112 @@
/*
* Copyright (c) 2021, Shukant Pal and Contributors
* Copyright (c) 2024, Khronos Group and Contributors
* SPDX-License-Identifier: Apache-2.0
*/
package org.khronos.ktx.test;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Locale;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
/**
* A class that will be used for extending the unit tests, and allow using the
* native <code>ktx</code> and <code>ktx-jni</code> libraries from <i>any</i>
* directory for the tests.
*
* (Usually, this will be a local build output directory)
*
* It will check the <code>LIBKTX_BINARY_DIR<code> environment variable. If this
* environment variable is a directory that contains the KTX JNI library, then
* this library will be loaded.
*
* Otherwise, it will load the KTX JNI library that was installed globally with
* the usual installation procedure.
*/
public class KtxTestLibraryLoader implements BeforeAllCallback, ExtensionContext.Store.CloseableResource {
private static boolean started = false;
@Override
public void beforeAll(final ExtensionContext context) throws Exception {
if (started) {
return;
}
started = true;
String ktxJniLibrary = findKtxJniLibraryName();
if (ktxJniLibrary != null) {
System.load(ktxJniLibrary);
} else {
System.loadLibrary("ktx-jni");
}
}
/**
* Try to find the name (full, absolute path) of the KTX JNI library that should
* be loaded.
*
* This method will search for the library in the directory that is defined via
* the <code>LIBKTX_BINARY_DIR</code> environment variable. If this variable is
* not defined, or no suitable library can be found, then <code>null</code> is
* returned.
*
* @return The KTX JNI library name
*/
private static String findKtxJniLibraryName() {
String ktxDir = System.getenv("LIBKTX_BINARY_DIR");
if (ktxDir == null) {
return null;
}
Path ktxPath = Path.of(ktxDir);
if (!ktxPath.isAbsolute() || !Files.exists(ktxPath) || !Files.isDirectory(ktxPath)) {
System.out.println(
"KTXTestLibraryLoader: The value of the LIBKTX_BINARY_DIR environment variable is invalid: "
+ ktxDir);
return null;
}
String expectedKtxJniLibraryName = isRunningOnWindows() ? "ktx-jni" : "libktx-jni";
System.out.println("KTXTestLibraryLoader: Loading KTX libraries from " + ktxDir);
File ktxDirFile = new File(ktxDir);
for (File file : ktxDirFile.listFiles()) {
if (!file.isFile()) {
continue;
}
String[] tokens = file.getName().split("\\.");
if (tokens.length == 2 && tokens[0].equals(expectedKtxJniLibraryName)) {
String ktxJniLibrary = file.getAbsolutePath();
System.out.println("KTXTestLibraryLoader: Found " + expectedKtxJniLibraryName + " at " + ktxJniLibrary);
return ktxJniLibrary;
}
}
System.out
.println("KTXTestLibraryLoader: Could not find " + expectedKtxJniLibraryName + " in given directory");
return null;
}
/**
* Returns whether the <code>os.name</code> system property indicates that the
* operating system is Windows.
*
* @return Whether the operating system is Windows
*/
private static boolean isRunningOnWindows() {
String osName = System.getProperty("os.name");
osName = osName.toLowerCase(Locale.ENGLISH);
return osName.startsWith("windows");
}
@Override
public void close() {
// NOOP
}
}
@@ -0,0 +1,119 @@
/*
* Copyright (c) 2021, Shukant Pal and Contributors
* SPDX-License-Identifier: Apache-2.0
*/
package org.khronos.ktx.test;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.khronos.ktx.*;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import static org.junit.jupiter.api.Assertions.*;
@ExtendWith({ KtxTestLibraryLoader.class })
public class KtxTexture1Test {
@Test
public void testCreateFromNamedFile() {
Path testKtxFile = Paths.get("")
.resolve("../../tests/testimages/etc1.ktx")
.toAbsolutePath()
.normalize();
KtxTexture1 texture = KtxTexture1.createFromNamedFile(testKtxFile.toString(),
KtxTextureCreateFlagBits.NO_FLAGS);
assertNotNull(texture);
assertEquals(texture.getGlInternalformat(), KtxInternalformat.GL_ETC1_RGB8_OES);
assertEquals(texture.isArray(), false);
assertEquals(texture.isCompressed(), true);
assertEquals(texture.getGenerateMipmaps(), false);
assertEquals(texture.getNumLevels(), 1);
texture.destroy();
}
@Test
public void testWriteToNamedFile() throws IOException {
Path testKtxFile = Paths.get("")
.resolve("../../tests/testimages/etc2-rgb.ktx")
.toAbsolutePath()
.normalize();
File copyFile = File.createTempFile("copyktx", ".ktx");
KtxTexture1 texture = KtxTexture1.createFromNamedFile(testKtxFile.toString(), KtxTextureCreateFlagBits.LOAD_IMAGE_DATA_BIT);
assertNotNull(texture);
int result = texture.writeToNamedFile(copyFile.getAbsolutePath().toString());
assertEquals(result, KtxErrorCode.SUCCESS);
byte[] original = Files.readAllBytes(testKtxFile);
byte[] copy = Files.readAllBytes(copyFile.toPath());
assertArrayEquals(copy, original);
texture.destroy();
}
@Test
public void testWriteToMemory() throws IOException {
Path testKtxFile = Paths.get("")
.resolve("../../tests/testimages/etc2-rgba1.ktx")
.toAbsolutePath()
.normalize();
KtxTexture1 texture = KtxTexture1.createFromNamedFile(testKtxFile.toString(), KtxTextureCreateFlagBits.LOAD_IMAGE_DATA_BIT);
assertNotNull(texture);
byte[] file = Files.readAllBytes(testKtxFile);
byte[] data = texture.writeToMemory();
assertArrayEquals(file, data);
texture.destroy();
}
@Test
public void testGetData() throws IOException {
Path testKtxFile = Paths.get("")
.resolve("../../tests/testimages/etc2-rgba1.ktx")
.toAbsolutePath()
.normalize();
KtxTexture1 texture = KtxTexture1.createFromNamedFile(testKtxFile.toString(), KtxTextureCreateFlagBits.LOAD_IMAGE_DATA_BIT);
assertNotNull(texture);
byte[] data = texture.getData();
byte[] file = Files.readAllBytes(testKtxFile);
int level0Size = texture.getImageSize(0);
for (int i = 0; i < level0Size; i++) {
assertEquals(data[data.length - 1 - i], file[file.length - 1 - i]);
}
texture.destroy();
}
@Test
public void testCreate() {
KtxTextureCreateInfo info = new KtxTextureCreateInfo();
info.setGlInternalformat(KtxInternalformat.GL_COMPRESSED_RGBA_ASTC_4x4_KHR);
info.setBaseWidth(10);
info.setBaseHeight(10);
KtxTexture1 texture = KtxTexture1.create(info, KtxTextureCreateStorage.ALLOC_STORAGE);
assertNotNull(texture);
byte[] imageData = new byte[10 * 10];
texture.setImageFromMemory(0, 0, 0, imageData);
texture.destroy();
}
}
@@ -0,0 +1,699 @@
/*
* Copyright (c) 2021, Shukant Pal and Contributors
* Copyright (c) 2024, Khronos Group and Contributors
* SPDX-License-Identifier: Apache-2.0
*/
package org.khronos.ktx.test;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.khronos.ktx.KtxBasisParams;
import org.khronos.ktx.KtxTextureCreateStorage;
import org.khronos.ktx.KtxErrorCode;
import org.khronos.ktx.KtxException;
import org.khronos.ktx.KtxSupercmpScheme;
import org.khronos.ktx.KtxTexture2;
import org.khronos.ktx.KtxTextureCreateFlagBits;
import org.khronos.ktx.KtxTextureCreateInfo;
import org.khronos.ktx.KtxTranscodeFormat;
import org.khronos.ktx.VkFormat;
@ExtendWith({ KtxTestLibraryLoader.class })
public class KtxTexture2Test {
@Test
public void testCreateFromNamedFile() {
Path testKtxFile = Paths.get("")
.resolve("../../tests/testimages/astc_ldr_4x4_FlightHelmet_baseColor.ktx2")
.toAbsolutePath()
.normalize();
KtxTexture2 texture = KtxTexture2.createFromNamedFile(testKtxFile.toString(),
KtxTextureCreateFlagBits.NO_FLAGS);
assertNotNull(texture);
assertEquals(texture.getNumLevels(), 1);
assertEquals(texture.getNumFaces(), 1);
assertEquals(texture.getVkFormat(), VkFormat.VK_FORMAT_ASTC_4x4_SRGB_BLOCK);
assertEquals(texture.getBaseWidth(), 2048);
assertEquals(texture.getBaseHeight(), 2048);
assertEquals(texture.getSupercompressionScheme(), KtxSupercmpScheme.NONE);
texture.destroy();
}
@Test
public void testCreateWithInvalidParameters() {
KtxTextureCreateInfo info = new KtxTextureCreateInfo();
info.setBaseWidth(128);
info.setBaseHeight(128);
info.setNumDimensions(-123); // Invalid!
info.setVkFormat(VkFormat.VK_FORMAT_R8G8B8A8_SRGB);
assertThrows(KtxException.class,
() -> KtxTexture2.create(info, KtxTextureCreateStorage.ALLOC_STORAGE),
"Expected to throw NullPointerException");
}
@Test
public void testCreateFromMemoryBasic() {
// Create a texture, and write it to memory
int sizeX = 512;
int sizeY = 512;
KtxTextureCreateInfo info = new KtxTextureCreateInfo();
info.setBaseWidth(sizeX);
info.setBaseHeight(sizeY);
info.setVkFormat(VkFormat.VK_FORMAT_R8G8B8A8_SRGB);
KtxTexture2 input = KtxTexture2.create(info, KtxTextureCreateStorage.ALLOC_STORAGE);
byte[] inputMemoryArray = input.writeToMemory();
// Create the texture from the exact memory
int createFlags = KtxTextureCreateFlagBits.LOAD_IMAGE_DATA_BIT;
KtxTexture2 t = KtxTexture2.createFromMemory(ByteBuffer.wrap(inputMemoryArray), createFlags);
// Ensure that the image has the same basic properties
// as the one that it was created from
assertEquals(t.getBaseWidth(), 512);
assertEquals(t.getBaseHeight(), 512);
}
@Test
public void testCreateFromMemoryWithPositionAndLimit() {
// Create a texture, and write it to memory
int sizeX = 512;
int sizeY = 512;
KtxTextureCreateInfo info = new KtxTextureCreateInfo();
info.setBaseWidth(sizeX);
info.setBaseHeight(sizeY);
info.setVkFormat(VkFormat.VK_FORMAT_R8G8B8A8_SRGB);
KtxTexture2 input = KtxTexture2.create(info, KtxTextureCreateStorage.ALLOC_STORAGE);
byte[] inputMemoryArray = input.writeToMemory();
// Create a byte buffer that is a bit larger than
// the input memory, and put the input memory
// into it, at position 50
ByteBuffer largeBuffer = ByteBuffer.wrap(new byte[inputMemoryArray.length + 100]);
largeBuffer.position(50);
largeBuffer.put(inputMemoryArray);
// Set the position and limit of the buffer to
// reflect the range that actually contains
// the real input data
largeBuffer.position(50);
largeBuffer.limit(50 + inputMemoryArray.length);
// Create the texture from the exact memory
int createFlags = KtxTextureCreateFlagBits.LOAD_IMAGE_DATA_BIT;
KtxTexture2 t = KtxTexture2.createFromMemory(largeBuffer, createFlags);
// Ensure that the image has the same basic properties
// as the one that it was created from
assertEquals(t.getBaseWidth(), 512);
assertEquals(t.getBaseHeight(), 512);
}
@Test
public void testCreateFromDirectMemoryWithPositionAndLimit() {
// Create a texture, and write it to memory
int sizeX = 512;
int sizeY = 512;
KtxTextureCreateInfo info = new KtxTextureCreateInfo();
info.setBaseWidth(sizeX);
info.setBaseHeight(sizeY);
info.setVkFormat(VkFormat.VK_FORMAT_R8G8B8A8_SRGB);
KtxTexture2 input = KtxTexture2.create(info, KtxTextureCreateStorage.ALLOC_STORAGE);
byte[] inputMemoryArray = input.writeToMemory();
// Create a DIRECT byte buffer that is a bit larger than
// the input memory, and put the input memory
// into it, at position 50
ByteBuffer largeBuffer = ByteBuffer.allocateDirect(inputMemoryArray.length + 100);
largeBuffer.position(50);
largeBuffer.put(inputMemoryArray);
// Set the position and limit of the buffer to
// reflect the range that actually contains
// the real input data
largeBuffer.position(50);
largeBuffer.limit(50 + inputMemoryArray.length);
// Create the texture from the exact memory
int createFlags = KtxTextureCreateFlagBits.LOAD_IMAGE_DATA_BIT;
KtxTexture2 t = KtxTexture2.createFromMemory(largeBuffer, createFlags);
// Ensure that the image has the same basic properties
// as the one that it was created from
assertEquals(t.getBaseWidth(), 512);
assertEquals(t.getBaseHeight(), 512);
}
@Test
public void testCreateFromNamedFileWithNull() {
assertThrows(NullPointerException.class,
() -> KtxTexture2.createFromNamedFile(null, KtxTextureCreateFlagBits.NO_FLAGS),
"Expected to throw NullPointerException");
}
@Test
public void testCreateFromNamedFileMipmapped() {
Path testKtxFile = Paths.get("")
.resolve("../../tests/testimages/astc_mipmap_ldr_4x4_posx.ktx2")
.toAbsolutePath()
.normalize();
KtxTexture2 texture = KtxTexture2.createFromNamedFile(testKtxFile.toString(),
KtxTextureCreateFlagBits.NO_FLAGS);
assertNotNull(texture);
assertEquals(texture.getNumLevels(), 12);
assertEquals(texture.getBaseWidth(), 2048);
assertEquals(texture.getBaseHeight(), 2048);
texture.destroy();
}
@Test
public void testGetImageSize() {
Path testKtxFile = Paths.get("")
.resolve("../../tests/testimages/astc_mipmap_ldr_4x4_posx.ktx2")
.toAbsolutePath()
.normalize();
KtxTexture2 texture = KtxTexture2.createFromNamedFile(testKtxFile.toString(),
KtxTextureCreateFlagBits.NO_FLAGS);
assertNotNull(texture);
assertEquals( 4194304, texture.getImageSize(0));
texture.destroy();
}
@Test
public void testGetImageOffset() {
Path testKtxFile = Paths.get("")
.resolve("../../tests/testimages/astc_mipmap_ldr_4x4_posx.ktx2")
.toAbsolutePath()
.normalize();
KtxTexture2 texture = KtxTexture2.createFromNamedFile(testKtxFile.toString(),
KtxTextureCreateFlagBits.NO_FLAGS);
assertNotNull(texture);
long level11Offset = texture.getImageOffset(11, 0, 0);
long level0Offset = texture.getImageOffset(0, 0, 0);
assertEquals(level11Offset, 0);
// ktxinfo offsets are from start of file :)
assertEquals(level0Offset - level11Offset, 0x155790 - 0x220);
texture.destroy();
}
@Test
public void testGetSize() {
Path testKtxFile = Paths.get("")
.resolve("../../tests/testimages/astc_mipmap_ldr_4x4_posx.ktx2")
.toAbsolutePath()
.normalize();
KtxTexture2 texture = KtxTexture2.createFromNamedFile(testKtxFile.toString(),
KtxTextureCreateFlagBits.LOAD_IMAGE_DATA_BIT);
assertNotNull(texture);
assertEquals(texture.getNumLevels(), 12);
long dataSize = texture.getDataSize();
long totalSize = 0;
for (int i = 0; i < 12; i++) {
totalSize += texture.getImageSize(i);
}
assertEquals(totalSize, dataSize);
byte[] data = texture.getData();
assertEquals(data.length, dataSize);
texture.destroy();
}
@Test
public void testGetData() throws IOException {
Path testKtxFile = Paths.get("")
.resolve("../../tests/testimages/astc_mipmap_ldr_4x4_posx.ktx2")
.toAbsolutePath()
.normalize();
KtxTexture2 texture = KtxTexture2.createFromNamedFile(testKtxFile.toString(),
KtxTextureCreateFlagBits.LOAD_IMAGE_DATA_BIT);
assertNotNull(texture);
assertEquals(texture.getNumLevels(), 12);
byte[] file = Files.readAllBytes(testKtxFile);
byte[] data = texture.getData();
int level0Length = texture.getImageSize(0);
for (int i = 0; i < level0Length; i++) {
assertEquals(file[file.length - i - 1], data[data.length - i - 1]);
}
texture.destroy();
}
@Test
public void testCompressBasis() {
Path testKtxFile = Paths.get("")
.resolve("../../tests/testimages/arraytex_7_mipmap_reference_u.ktx2")
.toAbsolutePath()
.normalize();
KtxTexture2 texture = KtxTexture2.createFromNamedFile(testKtxFile.toString(),
KtxTextureCreateFlagBits.LOAD_IMAGE_DATA_BIT);
assertNotNull(texture);
assertEquals(false, texture.isCompressed());
assertEquals(KtxSupercmpScheme.NONE, texture.getSupercompressionScheme());
assertEquals(KtxErrorCode.SUCCESS, texture.compressBasis(1));
assertEquals(true, texture.isCompressed());
assertEquals(KtxSupercmpScheme.BASIS_LZ, texture.getSupercompressionScheme());
texture.destroy();
}
@Test
public void testCompressBasisEx() {
Path testKtxFile = Paths.get("")
.resolve("../../tests/testimages/arraytex_7_mipmap_reference_u.ktx2")
.toAbsolutePath()
.normalize();
KtxTexture2 texture = KtxTexture2.createFromNamedFile(testKtxFile.toString(),
KtxTextureCreateFlagBits.LOAD_IMAGE_DATA_BIT);
assertNotNull(texture);
assertEquals(false, texture.isCompressed());
assertEquals(KtxSupercmpScheme.NONE, texture.getSupercompressionScheme());
assertEquals(KtxErrorCode.SUCCESS, texture.compressBasisEx(new KtxBasisParams()));
assertEquals(true, texture.isCompressed());
assertEquals(KtxSupercmpScheme.BASIS_LZ, texture.getSupercompressionScheme());
texture.destroy();
}
@Test
public void testUsingAfterDestroy() {
KtxTextureCreateInfo info = new KtxTextureCreateInfo();
info.setBaseWidth(16);
info.setBaseHeight(16);
info.setVkFormat(VkFormat.VK_FORMAT_ASTC_4x4_SRGB_BLOCK);
KtxTexture2 texture = KtxTexture2.create(info, KtxTextureCreateStorage.ALLOC_STORAGE);
// Call destroy, and then try to call a function.
// It should throw for ALL functions, and there
// should be a test for EACH function, but...
// I got stuff to do, you know...
texture.destroy();
assertThrows(IllegalStateException.class,
() -> texture.getBaseDepth(),
"Expected to throw IllegalStateException");
}
@Test
public void testDestroyMultipleTimes() {
KtxTextureCreateInfo info = new KtxTextureCreateInfo();
info.setBaseWidth(16);
info.setBaseHeight(16);
info.setVkFormat(VkFormat.VK_FORMAT_ASTC_4x4_SRGB_BLOCK);
KtxTexture2 texture = KtxTexture2.create(info, KtxTextureCreateStorage.ALLOC_STORAGE);
texture.destroy();
texture.destroy();
texture.destroy();
assertTrue(true, "Should be able to call destroy() multiple times");
}
@Test
public void testCompressBasisExWithNull() {
KtxTextureCreateInfo info = new KtxTextureCreateInfo();
info.setBaseWidth(16);
info.setBaseHeight(16);
info.setVkFormat(VkFormat.VK_FORMAT_ASTC_4x4_SRGB_BLOCK);
KtxTexture2 texture = KtxTexture2.create(info, KtxTextureCreateStorage.ALLOC_STORAGE);
assertThrows(NullPointerException.class,
() -> texture.compressBasisEx(null),
"Expected to throw NullPointerException");
texture.destroy();
}
@Test
public void testTranscodeBasis() {
Path testKtxFile = Paths.get("")
.resolve("../../tests/testimages/color_grid_basis.ktx2")
.toAbsolutePath()
.normalize();
KtxTexture2 texture = KtxTexture2.createFromNamedFile(testKtxFile.toString(),
KtxTextureCreateFlagBits.LOAD_IMAGE_DATA_BIT);
assertNotNull(texture);
texture.transcodeBasis(KtxTranscodeFormat.ASTC_4x4_RGBA, 0);
assertEquals(VkFormat.VK_FORMAT_ASTC_4x4_SRGB_BLOCK, texture.getVkFormat());
}
@Test
public void testCreate() {
KtxTextureCreateInfo info = new KtxTextureCreateInfo();
info.setBaseWidth(10);
info.setBaseHeight(10);
info.setVkFormat(VkFormat.VK_FORMAT_ASTC_4x4_SRGB_BLOCK);
KtxTexture2 texture = KtxTexture2.create(info, KtxTextureCreateStorage.ALLOC_STORAGE);
assertNotNull(texture);
byte[] imageData = new byte[10 * 10];
texture.setImageFromMemory(0, 0, 0, imageData);
texture.destroy();
}
@Test
public void testCreateWithNull() {
assertThrows(NullPointerException.class,
() -> KtxTexture2.create(null, KtxTextureCreateStorage.ALLOC_STORAGE),
"Expected to throw NullPointerException");
}
@Test
void testSetImageFromMemoryWithNull() {
KtxTextureCreateInfo info = new KtxTextureCreateInfo();
info.setBaseWidth(16);
info.setBaseHeight(16);
info.setVkFormat(VkFormat.VK_FORMAT_R8G8B8A8_SRGB);
KtxTexture2 t = KtxTexture2.create(info, KtxTextureCreateStorage.ALLOC_STORAGE);
assertThrows(NullPointerException.class,
() -> t.setImageFromMemory(0, 0, 0, null),
"Expected to throw NullPointerException");
}
@Test
void testCreateFromMemoryWithInvalidMemory() {
byte[] invalidMemory = new byte[1000];
int createFlags = KtxTextureCreateFlagBits.LOAD_IMAGE_DATA_BIT;
assertThrows(KtxException.class,
() -> KtxTexture2.createFromMemory(ByteBuffer.wrap(invalidMemory), createFlags),
"Expected to throw NullPointerException");
}
@Test
public void testInputSwizzleBasisEx() throws IOException {
int sizeX = 32;
int sizeY = 32;
int outputFormat = KtxTranscodeFormat.RGBA32;
int transcodeFlags = 0;
// Create the actual texture data:
// - create RGBA pixels
// - create texture
// - compress with BRGA input swizzling
// - obtain resulting RGBA values
// Create a RGBA pixels for an image filled with
// 8 rows of red pixels
// 8 rows of green pixels
// 8 rows of blue pixels
// 8 rows of white pixels
byte[] input = new byte[sizeX * sizeY * 4];
TestUtils.fillRows(input, sizeX, sizeY, 0, 8, 255, 0, 0, 255); // Red
TestUtils.fillRows(input, sizeX, sizeY, 8, 16, 0, 255, 0, 255); // Green
TestUtils.fillRows(input, sizeX, sizeY, 16, 24, 0, 0, 255, 255); // Blue
TestUtils.fillRows(input, sizeX, sizeY, 24, 32, 255, 255, 255, 255); // White
// Create the input texture from the pixels
KtxTextureCreateInfo inputInfo = new KtxTextureCreateInfo();
inputInfo.setBaseWidth(sizeX);
inputInfo.setBaseHeight(sizeY);
inputInfo.setVkFormat(VkFormat.VK_FORMAT_R8G8B8A8_SRGB);
KtxTexture2 inputTexture = KtxTexture2.create(inputInfo,
KtxTextureCreateStorage.ALLOC_STORAGE);
inputTexture.setImageFromMemory(0, 0, 0, input);
// Apply basis compression to the input, with an input swizzle BRGA,
// so that
// the former B channel becomes the R channel
// the former R channel becomes the G channel
// the former G channel becomes the B channel
// the former A channel remains the A channel
KtxBasisParams inputParams = new KtxBasisParams();
inputParams.setUastc(false);
inputParams.setInputSwizzle(new char[] { 'b', 'r', 'g', 'a' });
inputTexture.compressBasisEx(inputParams);
// Transcode the input texture to RGBA32
inputTexture.transcodeBasis(outputFormat, transcodeFlags);
byte[] actualRgba = inputTexture.getData();
// Create the expected reference data:
// - create RGBA pixels, swizzled with BRGA
// - create texture
// - compress without input swizzling
// - obtain resulting RGBA values
// Create "golden" reference pixels, where a BRGA
// swizzling was already applied
byte[] gold = new byte[sizeX * sizeY * 4];
TestUtils.fillRows(gold, sizeX, sizeY, 0, 8, 0, 255, 0, 255); // Green
TestUtils.fillRows(gold, sizeX, sizeY, 8, 16, 0, 0, 255, 255); // Blue
TestUtils.fillRows(gold, sizeX, sizeY, 16, 24, 255, 0, 0, 255); // Red
TestUtils.fillRows(gold, sizeX, sizeY, 24, 32, 255, 255, 255, 255); // White
// Create the reference texture from the swizzled pixels
KtxTextureCreateInfo goldInfo = new KtxTextureCreateInfo();
goldInfo.setBaseWidth(sizeX);
goldInfo.setBaseHeight(sizeY);
goldInfo.setVkFormat(VkFormat.VK_FORMAT_R8G8B8A8_SRGB);
KtxTexture2 goldTexture = KtxTexture2.create(goldInfo,
KtxTextureCreateStorage.ALLOC_STORAGE);
goldTexture.setImageFromMemory(0, 0, 0, gold);
// Apply basis compression to the reference, without swizzling
KtxBasisParams goldParams = new KtxBasisParams();
goldParams.setUastc(false);
goldTexture.compressBasisEx(goldParams);
// Transcode the reference texture to RGBA32
goldTexture.transcodeBasis(outputFormat, transcodeFlags);
byte[] expectedRgba = goldTexture.getData();
// Compare the resulting data to the expected RGBA values.
assertArrayEquals(expectedRgba, actualRgba);
inputTexture.destroy();
goldTexture.destroy();
}
@Test
public void testSupercompressionZstd() throws IOException {
int sizeX = 32;
int sizeY = 32;
// Create a dummy texture
KtxTextureCreateInfo info = new KtxTextureCreateInfo();
info.setBaseWidth(sizeX);
info.setBaseHeight(sizeY);
info.setVkFormat(VkFormat.VK_FORMAT_R8G8B8A8_SRGB);
KtxTexture2 t = KtxTexture2.create(info, KtxTextureCreateStorage.ALLOC_STORAGE);
byte[] rgba = new byte[sizeX * sizeY * 4];
t.setImageFromMemory(0, 0, 0, rgba);
// Apply default UASTC compression
KtxBasisParams p = new KtxBasisParams();
p.setUastc(true);
t.compressBasisEx(p);
// The supercompression scheme should be NONE here
int scBefore = t.getSupercompressionScheme();
assertEquals(KtxSupercmpScheme.NONE, scBefore);
// Apply Zstd compression
t.deflateZstd(10);
// The supercompression scheme should now be ZSTD
int scAfter = t.getSupercompressionScheme();
assertEquals(KtxSupercmpScheme.ZSTD, scAfter);
t.destroy();
}
@Test
public void testSupercompressionZLIB() throws IOException {
int sizeX = 32;
int sizeY = 32;
// Create a dummy texture
KtxTextureCreateInfo info = new KtxTextureCreateInfo();
info.setBaseWidth(sizeX);
info.setBaseHeight(sizeY);
info.setVkFormat(VkFormat.VK_FORMAT_R8G8B8A8_SRGB);
KtxTexture2 t = KtxTexture2.create(info, KtxTextureCreateStorage.ALLOC_STORAGE);
byte[] rgba = new byte[sizeX * sizeY * 4];
t.setImageFromMemory(0, 0, 0, rgba);
// Apply default UASTC compression
KtxBasisParams p = new KtxBasisParams();
p.setUastc(true);
t.compressBasisEx(p);
// The supercompression scheme should be NONE here
int scBefore = t.getSupercompressionScheme();
assertEquals(KtxSupercmpScheme.NONE, scBefore);
// Apply ZLIB compression
t.deflateZLIB(10);
// The supercompression scheme should now be ZLIB
int scAfter = t.getSupercompressionScheme();
assertEquals(KtxSupercmpScheme.ZLIB, scAfter);
t.destroy();
}
@Test
public void testBindings() {
Path testKtxFile = Paths.get("")
.resolve("../../tests/testimages/astc_ldr_4x4_FlightHelmet_baseColor.ktx2")
.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();
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);
}
@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,0 +1,67 @@
/*
* Copyright (c) 2024, Khronos Group and Contributors
* SPDX-License-Identifier: Apache-2.0
*/
package org.khronos.ktx.test;
import java.util.Locale;
/**
* Utilities for the test package
*/
class TestUtils {
/**
* Fill the specified range of rows of the given RGBA pixels array with the
* given RGBA components
*
* @param rgba The RGBA pixels array
* @param sizeX The size of the image in x-direction
* @param sizeY The size of the image in y-direction
* @param minRow The minimum row, inclusive
* @param maxRow The maximum row, exclusive
* @param r The red component (in [0,255])
* @param g The green component (in [0,255])
* @param b The blue component (in [0,255])
* @param a The alpha component (in [0,255])
*/
static void fillRows(byte rgba[], int sizeX, int sizeY,
int minRow, int maxRow,
int r, int g, int b, int a) {
for (int y = minRow; y < maxRow; y++) {
for (int x = 0; x < sizeX; x++) {
int index = (y * sizeX) + x;
rgba[index * 4 + 0] = (byte) r;
rgba[index * 4 + 1] = (byte) g;
rgba[index * 4 + 2] = (byte) b;
rgba[index * 4 + 3] = (byte) a;
}
}
}
/**
* Create a string representation of the RGBA components of the specified pixel
* in the given RGBA pixels array.
*
* This is mainly intended for debugging. Some details of the resulting string
* are not specified.
*
* @param rgba The RGBA pixels array
* @param pixelIndex The pixel index
* @return The string
*/
static String createRgbaString(byte rgba[], int pixelIndex) {
byte r = rgba[pixelIndex * 4 + 0];
byte g = rgba[pixelIndex * 4 + 1];
byte b = rgba[pixelIndex * 4 + 2];
byte a = rgba[pixelIndex * 4 + 3];
int ir = Byte.toUnsignedInt(r);
int ig = Byte.toUnsignedInt(g);
int ib = Byte.toUnsignedInt(b);
int ia = Byte.toUnsignedInt(a);
String s = String.format(Locale.ENGLISH, "%3d, %3d, %3d, %3d", ir, ig, ib, ia);
return s;
}
}