Add ktx
This commit is contained in:
@@ -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
|
||||
@@ -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`.
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+112
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user