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
@@ -0,0 +1,55 @@
# Copyright 2017-2020 The Khronos Group Inc.
# SPDX-License-Identifier: Apache-2.0
set(LIB_TYPE STATIC)
# Parent scope variables set in this context are not seen in it and, if there
# is a way to explicitly reference one, I've not found it. So 2-step.
set(BASISU_CBIND_LIBTYPE ${LIB_TYPE} PARENT_SCOPE)
add_library(obj_basisu_cbind ${LIB_TYPE}
inc/basisu_c_binding.h
src/basisu_c_binding.cpp
)
# Using cxx_std_17 because VS 2022 raises a set but unused variable warning
# and pre-c++-17 there is no 'unused' attribute. equivalent.
target_compile_features(obj_basisu_cbind PUBLIC c_std_99 cxx_std_17)
# Need this to make VS correctly define __cplusplus to the std in use.
target_compile_options(
obj_basisu_cbind
PRIVATE
$<$<CXX_COMPILER_ID:MSVC>:/Zc:__cplusplus>
)
target_include_directories(
obj_basisu_cbind
PUBLIC
inc
${KTX_BASISU_INCLUDE_DIRS}
PRIVATE
$<TARGET_PROPERTY:ktx,INCLUDE_DIRECTORIES>
${PROJECT_SOURCE_DIR}/utils
)
target_compile_definitions(
obj_basisu_cbind
PUBLIC
KTX_BASISU_C_BINDINGS
PRIVATE
$<TARGET_PROPERTY:ktx,INTERFACE_COMPILE_DEFINITIONS>
# We're only using this to read .basis files.
BASISD_SUPPORT_KTX2_ZSTD=0
BASISD_SUPPORT_KTX2=0
)
if(WIN32)
target_compile_definitions(
obj_basisu_cbind
PRIVATE
$<IF:$<STREQUAL:"${LIB_TYPE}","STATIC">,KHRONOS_STATIC,KTX_BASISU_API=__declspec\(dllexport\)>
)
elseif(APPLE)
if(KTX_EMBED_BITCODE)
target_compile_options(obj_basisu_cbind PRIVATE "-fembed-bitcode")
endif()
endif()
@@ -0,0 +1,70 @@
// Copyright 2019 Andreas Atteneder, All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
#pragma once
#if defined(KHRONOS_STATIC)
#define KTX_BASISU_API
#elif defined(_WIN32)
#if !defined(KTX_BASISU_API)
#define KTX_BASISU_API __declspec(dllimport)
#endif
#elif defined(__ANDROID__)
#define KTX_BASISU_API __attribute__((visibility("default")))
#else
#define KTX_BASISU_API
#endif
#include <basisu_transcoder.h>
using namespace basist;
class basis_file
{
unsigned int m_magic = 0;
basisu_transcoder m_transcoder;
const uint8_t *m_file;
uint32_t byteLength;
basisu_file_info fileinfo;
public:
basis_file()
:
m_transcoder()
{}
bool open(const uint8_t *buffer, uint32_t newByteLength);
void close();
uint32_t getHasAlpha();
uint32_t getNumImages();
uint32_t getNumLevels(uint32_t image_index);
uint32_t getImageWidth(uint32_t image_index, uint32_t level_index);
uint32_t getImageHeight(uint32_t image_index, uint32_t level_index);
uint32_t getYFlip();
uint32_t getIsEtc1s();
basis_texture_type getTextureType();
uint32_t getImageTranscodedSizeInBytes(uint32_t image_index, uint32_t level_index, uint32_t format);
uint32_t startTranscoding();
uint32_t transcodeImage(void* dst, uint32_t dst_size, uint32_t image_index, uint32_t level_index, uint32_t format, uint32_t pvrtc_wrap_addressing, uint32_t get_alpha_for_opaque_formats);
};
extern "C" {
KTX_BASISU_API void ktx_basisu_basis_init();
#ifdef KTX_BASISU_C_BINDINGS
KTX_BASISU_API basis_file* ktx_basisu_create_basis();
KTX_BASISU_API uint32_t ktx_basisu_open_basis( basis_file* basis, const uint8_t * data, uint32_t length );
KTX_BASISU_API void ktx_basisu_close_basis( basis_file* basis );
KTX_BASISU_API void ktx_basisu_delete_basis( basis_file* basis );
KTX_BASISU_API uint32_t ktx_basisu_getHasAlpha( basis_file* basis );
KTX_BASISU_API uint32_t ktx_basisu_getNumImages( basis_file* basis );
KTX_BASISU_API uint32_t ktx_basisu_getNumLevels( basis_file* basis, uint32_t image_index);
KTX_BASISU_API uint32_t ktx_basisu_getImageWidth( basis_file* basis, uint32_t image_index, uint32_t level_index);
KTX_BASISU_API uint32_t ktx_basisu_getImageHeight( basis_file* basis, uint32_t image_index, uint32_t level_index);
KTX_BASISU_API uint32_t ktx_basisu_get_y_flip( basis_file* basis );
KTX_BASISU_API uint32_t ktx_basisu_get_is_etc1s( basis_file* basis );
KTX_BASISU_API basis_texture_type ktx_basisu_get_texture_type( basis_file* basis );
KTX_BASISU_API uint32_t ktx_basisu_getImageTranscodedSizeInBytes( basis_file* basis, uint32_t image_index, uint32_t level_index, uint32_t format);
KTX_BASISU_API uint32_t ktx_basisu_startTranscoding( basis_file* basis );
KTX_BASISU_API uint32_t ktx_basisu_transcodeImage( basis_file* basis, void* dst, uint32_t dst_size, uint32_t image_index, uint32_t level_index, uint32_t format, uint32_t pvrtc_wrap_addressing, uint32_t get_alpha_for_opaque_formats);
#endif
}
@@ -0,0 +1,296 @@
// Copyright 2019 Andreas Atteneder, All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
#include <basisu_transcoder.h>
#include "basisu_c_binding.h"
#include "unused.h"
using namespace basist;
#define MAGIC 0xDEADBEE1
bool basis_file::open(const uint8_t *buffer, uint32_t newByteLength) {
m_file = buffer;
byteLength = newByteLength;
if (!m_transcoder.validate_header(buffer, newByteLength)) {
m_file = nullptr;
byteLength = 0;
return false;
}
if (!m_transcoder.get_file_info(m_file, byteLength, fileinfo))
{
return false;
}
// Initialized after validation
m_magic = MAGIC;
return true;
}
void basis_file::close() {
assert(m_magic == MAGIC);
m_file = nullptr;
byteLength = 0;
}
uint32_t basis_file::getHasAlpha() {
assert(m_magic == MAGIC);
if (m_magic != MAGIC)
return 0;
return fileinfo.m_has_alpha_slices;
}
uint32_t basis_file::getNumImages() {
assert(m_magic == MAGIC);
if (m_magic != MAGIC)
return 0;
return m_transcoder.get_total_images(m_file, byteLength);
}
uint32_t basis_file::getNumLevels(uint32_t image_index) {
assert(m_magic == MAGIC);
if (m_magic != MAGIC)
return 0;
return fileinfo.m_image_mipmap_levels[image_index];
}
uint32_t basis_file::getImageWidth(uint32_t image_index, uint32_t level_index) {
assert(m_magic == MAGIC);
if (m_magic != MAGIC)
return 0;
uint32_t orig_width, orig_height, total_blocks;
if (!m_transcoder.get_image_level_desc(m_file, byteLength,
image_index, level_index,
orig_width, orig_height,
total_blocks))
return 0;
return orig_width;
}
uint32_t basis_file::getImageHeight(uint32_t image_index, uint32_t level_index) {
assert(m_magic == MAGIC);
if (m_magic != MAGIC)
return 0;
uint32_t orig_width, orig_height, total_blocks;
if (!m_transcoder.get_image_level_desc(m_file, byteLength,
image_index, level_index,
orig_width, orig_height,
total_blocks))
return 0;
return orig_height;
}
uint32_t basis_file::getYFlip() {
assert(m_magic == MAGIC);
return fileinfo.m_y_flipped;
}
uint32_t basis_file::getIsEtc1s() {
assert(m_magic == MAGIC);
return fileinfo.m_etc1s;
}
basis_texture_type basis_file::getTextureType() {
assert(m_magic == MAGIC);
return fileinfo.m_tex_type;
}
uint32_t basis_file::getImageTranscodedSizeInBytes(uint32_t image_index, uint32_t level_index, uint32_t format) {
assert(m_magic == MAGIC);
if (m_magic != MAGIC)
return 0;
if (format >= (uint32_t) basist::transcoder_texture_format::cTFTotalTextureFormats)
return 0;
uint32_t orig_width, orig_height, total_blocks;
if (!m_transcoder.get_image_level_desc(m_file, (uint32_t)byteLength,
image_index, level_index, orig_width,
orig_height, total_blocks))
return 0;
const transcoder_texture_format transcoder_format = static_cast<transcoder_texture_format>(format);
if (basis_transcoder_format_is_uncompressed(transcoder_format))
{
// Uncompressed formats are just plain raster images.
const uint32_t bytes_per_pixel = basis_get_uncompressed_bytes_per_pixel(transcoder_format);
const uint32_t bytes_per_line = orig_width * bytes_per_pixel;
const uint32_t bytes_per_slice = bytes_per_line * orig_height;
return bytes_per_slice;
}
else
{
// Compressed formats are 2D arrays of blocks.
const uint32_t bytes_per_block = basis_get_bytes_per_block_or_pixel(transcoder_format);
if (transcoder_format == transcoder_texture_format::cTFPVRTC1_4_RGB || transcoder_format == transcoder_texture_format::cTFPVRTC1_4_RGBA)
{
// For PVRTC1, Basis only writes (or requires) total_blocks * bytes_per_block. But GL requires extra padding for very small textures:
// https://www.khronos.org/registry/OpenGL/extensions/IMG/IMG_texture_compression_pvrtc.txt
const uint32_t width = (orig_width + 3) & ~3;
const uint32_t height = (orig_height + 3) & ~3;
const uint32_t size_in_bytes = (std::max(8U, width) * std::max(8U, height) * 4 + 7) / 8;
return size_in_bytes;
}
return total_blocks * bytes_per_block;
}
}
uint32_t basis_file::startTranscoding() {
assert(m_magic == MAGIC);
if (m_magic != MAGIC)
return 0;
return m_transcoder.start_transcoding(m_file, byteLength);
}
uint32_t basis_file::transcodeImage(void* dst, uint32_t dst_size, uint32_t image_index, uint32_t level_index, uint32_t format, uint32_t /*pvrtc_wrap_addressing*/, uint32_t get_alpha_for_opaque_formats) {
assert(m_magic == MAGIC);
if (m_magic != MAGIC)
return 0;
if (format >= (uint32_t) basist::transcoder_texture_format::cTFTotalTextureFormats)
return 0;
const transcoder_texture_format transcoder_format = static_cast<transcoder_texture_format>(format);
uint32_t orig_width, orig_height, total_blocks;
if (!m_transcoder.get_image_level_desc(m_file, byteLength, image_index, level_index, orig_width, orig_height, total_blocks))
return 0;
uint32_t flags = get_alpha_for_opaque_formats ? cDecodeFlagsTranscodeAlphaDataToOpaqueFormats : 0;
uint32_t status;
if (basis_transcoder_format_is_uncompressed(transcoder_format))
{
MAYBE_UNUSED const uint32_t bytes_per_pixel = basis_get_uncompressed_bytes_per_pixel(transcoder_format);
MAYBE_UNUSED const uint32_t bytes_per_line = orig_width * bytes_per_pixel;
MAYBE_UNUSED const uint32_t bytes_per_slice = bytes_per_line * orig_height;
assert(bytes_per_slice <= dst_size);
status = m_transcoder.transcode_image_level(
m_file, byteLength, image_index, level_index,
dst, orig_width * orig_height,
transcoder_format,
flags,
orig_width,
nullptr,
orig_height);
}
else
{
uint32_t bytes_per_block = basis_get_bytes_per_block_or_pixel(transcoder_format);
MAYBE_UNUSED uint32_t required_size = total_blocks * bytes_per_block;
if (transcoder_format == transcoder_texture_format::cTFPVRTC1_4_RGB || transcoder_format == transcoder_texture_format::cTFPVRTC1_4_RGBA)
{
// For PVRTC1, Basis only writes (or requires) total_blocks * bytes_per_block. But GL requires extra padding for very small textures:
// https://www.khronos.org/registry/OpenGL/extensions/IMG/IMG_texture_compression_pvrtc.txt
// The transcoder will clear the extra bytes followed the used blocks to 0.
const uint32_t width = (orig_width + 3) & ~3;
const uint32_t height = (orig_height + 3) & ~3;
required_size = (std::max(8U, width) * std::max(8U, height) * 4 + 7) / 8;
assert(required_size >= total_blocks * bytes_per_block);
}
assert(required_size <= dst_size);
status = m_transcoder.transcode_image_level(
m_file, byteLength, image_index, level_index,
dst, dst_size / bytes_per_block,
static_cast<basist::transcoder_texture_format>(format),
flags);
}
return status;
}
extern "C" {
KTX_BASISU_API void ktx_basisu_basis_init()
{
basisu_transcoder_init();
}
#ifdef KTX_BASISU_C_BINDINGS
KTX_BASISU_API basis_file* ktx_basisu_create_basis() {
basis_file* new_basis = new basis_file();
return new_basis;
}
KTX_BASISU_API uint32_t ktx_basisu_open_basis( basis_file* basis, const uint8_t * data, uint32_t length ) {
return basis->open(data,length);
}
KTX_BASISU_API void ktx_basisu_close_basis( basis_file* basis ) {
basis->close();
}
KTX_BASISU_API void ktx_basisu_delete_basis( basis_file* basis ) {
delete basis;
}
KTX_BASISU_API uint32_t ktx_basisu_getHasAlpha( basis_file* basis ) {
assert(basis!=nullptr);
return (bool)basis->getHasAlpha();
}
KTX_BASISU_API uint32_t ktx_basisu_getNumImages( basis_file* basis ) {
return basis->getNumImages();
}
KTX_BASISU_API uint32_t ktx_basisu_getNumLevels( basis_file* basis, uint32_t image_index) {
return basis->getNumLevels(image_index);
}
KTX_BASISU_API uint32_t ktx_basisu_getImageWidth( basis_file* basis, uint32_t image_index, uint32_t level_index) {
return basis->getImageWidth(image_index,level_index);
}
KTX_BASISU_API uint32_t ktx_basisu_getImageHeight( basis_file* basis, uint32_t image_index, uint32_t level_index) {
return basis->getImageHeight(image_index,level_index);
}
KTX_BASISU_API uint32_t ktx_basisu_get_y_flip( basis_file* basis ) {
return basis->getYFlip();
}
KTX_BASISU_API uint32_t ktx_basisu_get_is_etc1s( basis_file* basis ) {
return basis->getIsEtc1s();
}
KTX_BASISU_API basis_texture_type ktx_basisu_get_texture_type( basis_file* basis ) {
return basis->getTextureType();
}
KTX_BASISU_API uint32_t ktx_basisu_getImageTranscodedSizeInBytes( basis_file* basis, uint32_t image_index, uint32_t level_index, uint32_t format) {
return basis->getImageTranscodedSizeInBytes(image_index,level_index,format);
}
KTX_BASISU_API uint32_t ktx_basisu_startTranscoding( basis_file* basis ) {
return basis->startTranscoding();
}
KTX_BASISU_API uint32_t ktx_basisu_transcodeImage( basis_file* basis, void* dst, uint32_t dst_size, uint32_t image_index, uint32_t level_index, uint32_t format, uint32_t pvrtc_wrap_addressing, uint32_t get_alpha_for_opaque_formats) {
return basis->transcodeImage(dst,dst_size,image_index,level_index,format,pvrtc_wrap_addressing,get_alpha_for_opaque_formats);
}
#endif
} // END extern "C"
+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;
}
}
+17
View File
@@ -0,0 +1,17 @@
// -*- tab-width: 4; -*-
// vi: set sw=2 ts=4 expandtab textwidth=80:
//
// Copyright 2024 Khronos Group, Inc.
// SPDX-License-Identifier: Apache-2.0
//
// Provide old name for backward compatibility.
Module.onRuntimeInitialized = function() {
Module['ktxTexture'] = Module.texture;
Module['ErrorCode'] = Module.error_code;
Module['TranscodeTarget'] = Module.transcode_fmt;
Module['TranscodeFlags'] = Module.transcode_flag_bits;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,18 @@
// -*- tab-width: 4; -*-
// vi: set sw=2 ts=4 expandtab textwidth=80:
//
// Copyright 2019-2024 Khronos Group, Inc.
// SPDX-License-Identifier: Apache-2.0
//
// Provide old module create function name for backward compatibility.
// N.B. --pre-js and --post-js code is run inside the module creation
// function so the variable holding that function is not available.
// This has to be --extern-post-js.
var LIBKTX
if (typeof createKtxReadModule === "function")
LIBKTX = createKtxReadModule;
else
LIBKTX = createKtxModule;
@@ -0,0 +1,801 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab textwidth=80: */
/*
* Copyright 2019-2020 Khronos Group, Inc.
* SPDX-License-Identifier: Apache-2.0
*/
/*
* This transcoder is DEPRECATED. Use the container independent transcoder from
* the Binomial LLC repo instead: https://github.com/BinomialLLC/basis_universal.
*/
#include <emscripten/bind.h>
#include "basisu/transcoder/basisu_transcoder.h"
using namespace emscripten;
using namespace basist;
namespace msc {
// Container independent image description. This was originally in a PR
// submitted to the BasisU repo to add container independent transcoders.
// However the code that was eventually added favored explicit parameters
// over collecting them in a struct. To maintain backward compatibility for
// msc_basisu_trancoder users, recreate the struct here.
struct basisu_image_desc {
uint32_t m_flags;
uint32_t m_rgb_byte_offset;
uint32_t m_rgb_byte_length;
uint32_t m_alpha_byte_offset;
uint32_t m_alpha_byte_length;
uint32_t m_orig_width;
uint32_t m_orig_height;
uint32_t m_num_blocks_x;
uint32_t m_num_blocks_y;
uint32_t m_level;
basisu_image_desc() {
memset(this, 0, sizeof(*this));
}
basisu_image_desc(basis_tex_format, uint32_t width, uint32_t height,
uint32_t level)
{
memset(this, 0, sizeof(*this));
m_orig_width = width;
m_orig_height = height;
// Current formats are all 4 x 4 so ignore tex format param.
const uint32_t bw = 4, bh = 4;
m_num_blocks_x = (m_orig_width + (bw - 1)) / bw;
m_num_blocks_y = (m_orig_height + (bh - 1)) / bh;
m_level = level;
}
basisu_image_desc(const basis_slice_desc* pSlice_desc,
const bool hasAlphaSlice,
uint32_t level = 0) : m_level(level)
{
m_flags = pSlice_desc->m_flags & cSliceDescFlagsFrameIsIFrame;
m_rgb_byte_offset = pSlice_desc->m_file_ofs;
m_rgb_byte_length = pSlice_desc->m_file_size;
m_orig_width = pSlice_desc->m_orig_width;
m_orig_height = pSlice_desc->m_orig_height;
m_num_blocks_x = pSlice_desc->m_num_blocks_x;
m_num_blocks_y = pSlice_desc->m_num_blocks_y;
if (hasAlphaSlice) {
++pSlice_desc;
m_alpha_byte_offset = pSlice_desc->m_file_ofs;
m_alpha_byte_length = pSlice_desc->m_file_size;
} else {
m_alpha_byte_offset = 0;
m_alpha_byte_length = 0;
}
}
};
// This is needed because the enum defining CDecode* is anonymous.
enum TranscodeFlagBits {
TranscodeAlphaDataToOpaqueFormats =
cDecodeFlagsTranscodeAlphaDataToOpaqueFormats,
HighQuality = cDecodeFlagsHighQuality
};
class BasisTranscoderState: public basisu_transcoder_state {
};
class TranscodedImage {
public:
TranscodedImage(size_t size) : image(size) { }
uint8_t* data() { return image.data(); }
size_t size() { return image.size(); }
val get_typed_memory_view() {
return val(typed_memory_view(image.size(), image.data()));
}
protected:
std::vector<uint8_t> image;
};
class ImageTranscoderHelper {
// block size calculations
static inline uint32_t getWidthInBlocks(uint32_t w, uint32_t bw)
{
return (w + (bw - 1)) / bw;
}
static inline uint32_t getHeightInBlocks(uint32_t h, uint32_t bh)
{
return (h + (bh - 1)) / bh;
} //
public:
static size_t getTranscodedImageByteLength(transcoder_texture_format format,
uint32_t width, uint32_t height)
{
uint32_t blockByteLength =
basis_get_bytes_per_block_or_pixel(format);
if (basis_transcoder_format_is_uncompressed(format)) {
return width * height * blockByteLength;
} else if (format == transcoder_texture_format::cTFPVRTC1_4_RGB
|| format == transcoder_texture_format::cTFPVRTC1_4_RGBA) {
// For PVRTC1, Basis only writes (or requires)
// blockWidth * blockHeight * blockByteLength. But GL requires
// extra padding for very small textures:
// https://www.khronos.org/registry/OpenGL/extensions/IMG/IMG_texture_compression_pvrtc.txt
const uint32_t paddedWidth = (width + 3) & ~3;
const uint32_t paddedHeight = (height + 3) & ~3;
return (std::max(8U, paddedWidth)
* std::max(8U, paddedHeight) * 4 + 7) / 8;
} else {
uint32_t blockWidth = getWidthInBlocks(width, basis_get_block_width(format));
uint32_t blockHeight = getHeightInBlocks(height, basis_get_block_height(format));
return blockWidth * blockHeight * blockByteLength;
}
}
};
class BasisLzEtc1sImageTranscoder : public basisu_lowlevel_etc1s_transcoder {
public:
BasisLzEtc1sImageTranscoder()
: basisu_lowlevel_etc1s_transcoder() { }
// Yes, code in the following functions handling data coming in from
// ArrayBuffers IS copying the data. Sigh! According to Alon Zakai:
//
// "There isn't a way to let compiled code access a new ArrayBuffer.
// The compiled code has hardcoded access to the wasm Memory it was
// instantiated with - all the pointers it can understand are
// indexes into that Memory. It can't refer to anything else,
// I'm afraid."
//
// "In the future using different address spaces or techniques with
// reference types may open up some possibilities here."
bool decode_palettes(uint32_t num_endpoints, const val& jsEndpoints,
uint32_t num_selectors, const val& jsSelectors)
{
std::vector<uint8_t> cEndpoints{}, cSelectors{};
cEndpoints.resize(jsEndpoints["byteLength"].as<size_t>());
val memory = val::module_property("HEAP8")["buffer"];
val endpointsView = jsEndpoints["constructor"].new_(memory,
reinterpret_cast<uintptr_t>(cEndpoints.data()),
jsEndpoints["length"].as<uint32_t>());
endpointsView.call<void>("set", jsEndpoints);
cSelectors.resize(jsSelectors["byteLength"].as<size_t>());
// In case the resize caused HEAP8 to grow.
memory = val::module_property("HEAP8")["buffer"];
val selectorsView = jsSelectors["constructor"].new_(memory,
reinterpret_cast<uintptr_t>(cSelectors.data()),
jsSelectors["length"].as<uint32_t>());
selectorsView.call<void>("set", jsSelectors);
return basisu_lowlevel_etc1s_transcoder::decode_palettes(num_endpoints,
cEndpoints.data(),
cEndpoints.size(),
num_selectors,
cSelectors.data(),
cSelectors.size());
}
bool decode_tables(const val& jsTableData)
{
std::vector<uint8_t> cTableData{};
cTableData.resize(jsTableData["byteLength"].as<size_t>());
val memory = val::module_property("HEAP8")["buffer"];
val TableDataView = jsTableData["constructor"].new_(memory,
reinterpret_cast<uintptr_t>(cTableData.data()),
jsTableData["length"].as<uint32_t>());
TableDataView.call<void>("set", jsTableData);
return basisu_lowlevel_etc1s_transcoder::decode_tables(
cTableData.data(),
cTableData.size());
}
// @~English
// @brief Transcode a single BasisLZ supercompressed ETC1S image.
//
// @param[in] targetFormat the format to which to transcode the image.
// This enum comes from Basis Universal.
// @param[in] jsInSlices emscripten::val of a .subarray of the
// ArrayBuffer holding the file data that
// points to the first slice for this image.
// An alpha slice, if it exists, always
// immediately follows the rgb slice.
// @param[in] imageDesc reference to a struct basisu_image_desc
// giving information about the image.
// @param[in] decodeFlags
// an OR of basisu_decode_flags bits setting decode
// options. The only one of general interest is
// @c cDecodeFlagsTranscodeAlphaDataToOpaqueFormats.
// This can be used when @p targetFormat lacks an
// alpha component. When set the alpha slice is
// transcoded into the RGB components of the target.
//
// @return An emscripten::val with 1 entries, @c transcodedImage. If
// the transcode failed, @c transcodedImage will be undefined.
//
emscripten::val transcode_image(
transcoder_texture_format targetFormat,
const val& jsInSlices,
basisu_image_desc& imageDesc,
uint32_t decodeFlags = 0,
bool isVideo = false)
{
// First of all copy in the deflated data.
std::vector <uint8_t> deflatedSlices;
uint32_t deflatedSlicesByteLength
= jsInSlices["byteLength"].as<uint32_t>();
deflatedSlices.resize(deflatedSlicesByteLength);
val memory = val::module_property("HEAP8")["buffer"];
val memoryView = jsInSlices["constructor"].new_(memory,
reinterpret_cast<uintptr_t>(deflatedSlices.data()),
deflatedSlices.size());
memoryView.call<void>("set", jsInSlices);
size_t tiByteLength =
ImageTranscoderHelper::getTranscodedImageByteLength(targetFormat,
imageDesc.m_orig_width,
imageDesc.m_orig_height);
TranscodedImage* dst = new TranscodedImage(tiByteLength);
// ETC1S texel block dimensions
const uint32_t bw = 4, bh = 4;
uint32_t numBlocksX =( imageDesc.m_orig_width + (bw - 1)) / bw;
uint32_t numBlocksY = (imageDesc.m_orig_height + (bh - 1)) / bh;
bool status = basisu_lowlevel_etc1s_transcoder::transcode_image(
targetFormat,
dst->data(),
dst->size(),
deflatedSlices.data(),
deflatedSlices.size(),
numBlocksX,
numBlocksY,
imageDesc.m_orig_width,
imageDesc.m_orig_height,
imageDesc.m_level,
imageDesc.m_rgb_byte_offset,
imageDesc.m_rgb_byte_length,
imageDesc.m_alpha_byte_offset,
imageDesc.m_alpha_byte_length,
decodeFlags,
imageDesc.m_alpha_byte_length != 0,
isVideo
// API currently doesn't have any
// way to indicate if this is an
// iFrame or pFrame.
);
val ret = val::object();
if (status) {
ret.set("transcodedImage", val(dst, allow_raw_pointers()));
}
return ret;
}
protected:
};
class UastcImageTranscoder : public basisu_lowlevel_uastc_transcoder {
public:
UastcImageTranscoder() : basisu_lowlevel_uastc_transcoder() { }
// @~English
// @brief Transcode a single UASTC encoded image.
//
// @param[in] targetFormat the format to which to transcode the image.
// This enum comes from Basis Universal.
// @param[in] jsInSlices emscripten::val of a .subarray of the
// ArrayBuffer holding the file data that
// points to the the image to transcode.
// @param[in] imageDesc reference to a struct basisu_image_desc
// giving information about the image.
// @param[in] decodeFlags
// an OR of basisu_decode_flags bits setting decode
// options. The only one of general interest is
// @c cDecodeFlagsTranscodeAlphaDataToOpaqueFormats.
// This can be used when @p targetFormat lacks an
// alpha component. When set, the alpha components
// are decoded into the RGB components of the target.
//
// @return An emscripten::val with 1 entries, @c transcodedImage. If
// the transcode failed, @c transcodedImage will be undefined.
//
emscripten::val transcode_image(
transcoder_texture_format targetFormat,
const val& jsInImage,
basisu_image_desc& imageDesc,
uint32_t decodeFlags = 0,
bool hasAlpha = false,
bool isVideo = false)
{
// Copy in the deflated image.
std::vector <uint8_t> deflatedImage;
size_t deflatedImageByteLength
= jsInImage["byteLength"].as<size_t>();
deflatedImage.resize(deflatedImageByteLength);
val memory = val::module_property("HEAP8")["buffer"];
val memoryView = jsInImage["constructor"].new_(memory,
reinterpret_cast<uintptr_t>(deflatedImage.data()),
deflatedImageByteLength);
memoryView.call<void>("set", jsInImage);
size_t tiByteLength =
ImageTranscoderHelper::getTranscodedImageByteLength(targetFormat,
imageDesc.m_orig_width,
imageDesc.m_orig_height);
TranscodedImage* dst = new TranscodedImage(tiByteLength);
// UASTC texel block dimensions
const uint32_t bw = 4, bh = 4;
uint32_t numBlocksX =( imageDesc.m_orig_width + (bw - 1)) / bw;
uint32_t numBlocksY = (imageDesc.m_orig_height + (bh - 1)) / bh;
bool status =
basisu_lowlevel_uastc_transcoder::transcode_image(
targetFormat,
dst->data(),
dst->size(),
deflatedImage.data(),
deflatedImage.size(),
numBlocksX,
numBlocksY,
imageDesc.m_orig_width,
imageDesc.m_orig_height,
imageDesc.m_level,
imageDesc.m_rgb_byte_offset,
imageDesc.m_rgb_byte_length,
decodeFlags,
hasAlpha,
isVideo
// API currently doesn't have any
// way to indicate if this is an
// iFrame or pFrame.
);
val ret = val::object();
if (status) {
ret.set("transcodedImage", val(dst, allow_raw_pointers()));
}
return ret;
}
};
}
/** @page msc_basis_transcoder Basis Universal Image Transcoder binding
@warning Deprecated. Use the container independent transcoder from
the Binomial LLC repo instead:
https://github.com/BinomialLLC/basis_universal.
This JS wrapper was designed to use an underlying C++ API that
accepted image info in a structure. The API actually added to
basis_universal uses explicit parameters so users of this transcoder
will be packing the info into a struct from which it will be
immediately unpacked before the underlying transcoder is called.
## WebIDL for the binding
@code{.unparsed}
void initTranscoders();
bool isFormatSupported(TranscodeTarget targetFormat, TextureFormat texFormat);
interface BasisTranscoderState {
void BasisTranscoderState();
};
interface TranscodedImage {
ArrayBufferView get_typed_memory_view();
};
interface TranscodeResult {
TranscodedImage transcodedImage;
};
interface BasisLzEtc1sImageTranscoder {
void BasisLzEtc1sImageTranscoder();
uint32_t getBytesPerBlock(TranscodeTarget format);
bool decode_palettes(uint32_t num_endpoints,
const Uint8Array endpoints,
uint32_t num_selectors,
const Uint8Array selectors);
bool decode_tables(const Uint8Array tableData);
TranscodeResult transcode_image(
TranscodeTarget targetFormat,
const Uint8Array jsInSlices,
ImageInfo imageInfo,
uint32_t decodeFlags = 0,
bool isVideo = false);
};
interface BasisUastcImageTranscoder {
void BasisUastcImageTranscoder();
uint32_t getBytesPerBlock(const TranscodeTarget format);
TranscodeResult transcode_image(
TranscodeTarget targetFormat,
const Uint8Array jsInImage,
basisu_image_desc& imageDesc,
uint32_t decodeFlags = 0,
bool hasAlpha = false,
bool isVideo = false);
interface ImageInfo = {
ImageInfo(TextureFormat texFormat, uint32_t width, uint32_t height,
uint32_t level);
attribute uint32_t flags;
attribute long rgbByteOffset;
attribute long rgbByteLength;
attribute long alphaByteOffset;
attribute long alphaByteLength;
attribute uint32_t width;
attribute uint32_t height;
attribute uint32_t numBlocksX;
attribute uint32_t numBlocksY;
attribute uint32_t level;
};
// Some targets may not be available depending on options used when compiling
// the web assembly.
enum TranscodeTarget = {
"ETC1_RGB",
"BC1_RGB",
"BC4_R",
"BC5_RG",
"BC3_RGBA",
"PVRTC1_4_RGB",
"PVRTC1_4_RGBA",
"BC7_RGBA",
"BC7_M6_RGB", //Deprecated. Use BC7_RGBA.
"BC7_M5_RGBA", //Deprecated. Use BC7_RGBA.
"ETC2_RGBA",
"ASTC_4x4_RGBA",
"RGBA32",
"RGB565",
"BGR565",
"RGBA4444",
"PVRTC2_4_RGB",
"PVRTC2_4_RGBA",
"EAC_R11",
"EAC_RG11"
};
enum TextureFormat = {
"ETC1S",
"UASTC4x4",
};
enum TranscodeFlagBits =
"TRANSCODE_ALPHA_DATA_TO_OPAQUE_FORMATS",
"HIGH_QUALITY",
};
enum TranscodeFlagBits = {
"TRANSCODE_ALPHA_DATA_TO_OPAQUE_FORMATS",
"HIGH_QUALITY"
};
@endcode
## How to use
Put msc_basis_transcoder.js and msc_basis_transcoder.wasm in a
directory on your server. Create a script tag with
msc_basis_tranacoder.js as the @c src as shown below, changing
the path as necessary for the relative locations of your .html
file and the script source. msc_basis_transcoder.js will
automatically load msc_basis_transcoder.wasm.
### Create an instance of the MSC_TRANSCODER module
For example, add this to the .html file to initialize the transcoder and
make it available on the main window.
@code{.unparsed}
&lt;script src="msc_transcoder_wrapper.js">&lt;/script>
&lt;script type="text/javascript">
MSC_TRANSCODER().then(module => {
window.MSC_TRANSCODER = module;
module.initTranscoders();
// Call a function to begin loading or transcoding..
&lt;/script>
@endcode
@e After the module is initialized, invoke code that will directly or indirectly cause
a function with code like the following to be executed.
## Somewhere in the loader/transcoder
Assume a KTX file is fetched via an XMLHttpRequest which deposits the data into
a Uint8Array, "buData"...
@note The names of the data items used in the following code are those
from the KTX2 specification but the actual data is not specific to that
container format.
@code{.unparsed}
const {
BasisLzEtc1sImageTranscoder,
BasisUastcImageTranscoder,
TranscodeTarget
} = MSC_TRANSCODER;
// Determine from the KTX2 header information in buData if
// the data format is BasisU or Uastc.
// supercompressionScheme value == 1, it's TextureFormat.ETC1S.
// DFD colorModel == 166, it's TextureFormat.UASTC4x4.
const texFormat = ...
// Determine appropriate transcode format from available targets,
// info about the texture, e.g. texture.numComponents, and
// expected use. Use values from TranscodeTarget.
const targetFormat = ...
if ( !MSC_TRANSCODER.isFormatSupported(targetFormat, texFormat) {
throw new Error( ... );
}
if (TextureFormat.UASTC4x4) {
var result = transcodeUastc(targetFormat);
} else {
var result = transcodeEtc1s(targetFormat);
}
if ( result.transcodedImage === undefined ) {
throw new Error( 'Unable to transcode image.' );
}
@endcode
This is the function for transcoding etc1s.
@code{.unparsed}
transcodeEtc1s(targetFormat) {
// Locate the supercompression global data and compresssed
// mip level data within buData.
var bit = new BasisLzEtc1sImageTranscoder();
// Find the index of the starts of the endpoints, selectors and tables
// data within buData...
var endpointsStart = ...
var selectorsStart = ...
var tablesStart = ...
// The numbers of endpoints & selectors and their byteLengths are items
// within buData. They are in the header of a .ktx2 file's
// supercompressionGlobalData and in the header of a .basis file.
var endpoints = new Uint8Array(buData, endpointsStart,
endpointsByteLength);
var selectors = new Uint8Array(buData, selectorsStart,
selectorsByteLength);
bit.decodePalettes(numEndpoints, endpoints,
numSelectors, selectors);
var tables = new UInt8Array(buData, tablesStart, tablesByteLength);
bit.decodeTables(tables);
// Determine if the file contains a video sequence...
var isVideo = ...
// Calculate the total number of images in the data
var numImages = ...
// Set up a subarray pointing at the deflated image descriptions
// in buData. This is for .ktx2 containers. The image descriptions
// are located in supercompressionGlobalData. .basis containers will
// require different code to locate the slice descriptions within
// the file.
var imageDescsStart = ...:
// An imageDesc has 5 uint32 values.
var imageDescs = new Uint32Data(buData, imageDescsStart,
numImages * 5 * 4);
var curImageIndex = 0;
// Pseudo code for processing the levels of a .ktx2 container...
foreach level {
var leveWidth = width of image at this level
var levelHeight = height of image at this level
imageInfo = new ImageInfo(TextureFormat::ETC1S, levelWidth, levelHeight,
level);
foreach image in level {
// In KTX2 container locate the imageDesc for this image.
var imageDesc = imageDescs[curImageIndex++];
imageInfo.flags = imageDesc.imageFlags;
imageInfo.rgbByteOffset = 0;
imageInfo.rgbByteLength = imageDesc.rgbSliceByteLength;
imageInfo.alphaByteOffset = imageDesc.alphaSliceByteOffset > 0 ? imageDesc.rgbSliceByteLength : 0;
imageInfo.alphaByteLength = imageDesc.alphaSliceByteLength;
// Determine the location in the ArrayBuffer of the start
// of the deflated data for level.
var levelOffset = ...
// Make a .subarray of the rgb slice data.
var levelData = new Uint8Array(
buData,
levelOffset + imageDesc.rgbSliceByteOffset,
imageDesc.rgbSliceByteLength + imageDesc.alphaByteLength
);
var result = bit.transcodeImage(
targetFormat,
levelData,
imageInfo,
0,
isVideo);
if ( result.transcodedImage === undefined ) {
throw new Error( ... );
}
let imgData = transcodedImage.get_typed_memory_view();
// Upload data in imgData to WebGL...
// Do not call delete() until data has been uploaded
// or otherwise copied.
transcodedImage.delete();
}
}
// For .basis containers, it is necessary to locate the slice
// description(s) for the image and set the values in imageInfo
// from them. Use of the .basis-specific transcoder is recommended.
// The definition of the basis_slice_desc struct makes it difficult
// to create JS interface for it with embind.
@endcode
This is the function for transcoding Uastc.
@code{.unparsed}
transcodeUastc(targetFormat) {
var uit = new UastcImageTranscoder();
// Determine if the data is supercompressed.
var zstd = (supercompressionScheme == 2);
// Determine if the data has alpha.
var hasAlpha = (Channel ID of sample[0] in DFD == 1);
var dctx;
if (zstd) {
// Initialize the zstd decoder. Zstd JS wrapper + wasm is
// a separate package.
dctx = ZSTD_createDCtx();
}
// Pseudo code for processing the levels of a .ktx2 container...
foreach level {
// Determine the location in the ArrayBuffer buData of the
// start of the deflated data for the level.
var levelData = ...
if (zstd) {
// Inflate the level data
levelData = ZSTD_decompressDCtx(dctx, levelData, ... );
}
var levelWidth = width of image at this level
var levelHeight = height of image at this level
var depth = depth of texture at this level
var levelImageCount = number of layers * number of faces * depth;
var imageOffsetInLevel = 0;
var imageInfo = new ImageInfo(TextureFormat::UASTC4x4,
levelWidth, levelHeight, level);
var levelImageByteLength = imageInfo.numBlocksX * imageInfo.numBlocksY * DFD bytesPlane0;
foreach image in level {
inImage = Uint8Array(levelData, imageOffsetInLevel, levelImageByteLength);
imageInfo.flags = 0;
imageInfo.rgbByteOffset = 0;
imageInfo.rgbByteLength = levelImageByteLength;
imageInfo.alphaByteOffset = 0;
imageInfo.alphaByteLength = 0;
const {transcodedImage} = uit.transcodeImage(
targetFormat,
inImage,
imageInfo,
0,
hasAlpha,
isVideo);
if ( result.transcodedImage === undefined ) {
throw new Error( ... );
}
let imgData = transcodedImage.get_typed_memory_view();
// Upload data in imgData to WebGL...
// Do not call delete() until data has been uploaded
// or otherwise copied.
transcodedImage.delete();
imageOffsetInLevel += levelImageByteLength;
}
}
// For .basis containers, as with ETC1S, it is necessary to locate
// the slice description for the image and set the values in imageInfo
// from it.
}
@endcode
*/
EMSCRIPTEN_BINDINGS(ktx_wrappers)
{
enum_<transcoder_texture_format>("TranscodeTarget")
.value("ETC1_RGB", transcoder_texture_format::cTFETC1_RGB)
.value("BC1_RGB", transcoder_texture_format::cTFBC1_RGB)
.value("BC4_R", transcoder_texture_format::cTFBC4_R)
.value("BC5_RG", transcoder_texture_format::cTFBC5_RG)
.value("BC3_RGBA", transcoder_texture_format::cTFBC3_RGBA)
.value("PVRTC1_4_RGB", transcoder_texture_format::cTFPVRTC1_4_RGB)
.value("PVRTC1_4_RGBA", transcoder_texture_format::cTFPVRTC1_4_RGBA)
.value("BC7_RGBA", transcoder_texture_format::cTFBC7_RGBA)
// Deprecated. Use BC7_RGBA.
.value("BC7_M6_RGB", transcoder_texture_format::cTFBC7_M6_RGB)
// Deprecated. Use BC7_RGBA.
.value("BC7_M5_RGBA", transcoder_texture_format::cTFBC7_M5_RGBA)
.value("ETC2_RGBA", transcoder_texture_format::cTFETC2_RGBA)
.value("ASTC_4x4_RGBA", transcoder_texture_format::cTFASTC_4x4_RGBA)
.value("RGBA32", transcoder_texture_format::cTFRGBA32)
.value("RGB565", transcoder_texture_format::cTFRGB565)
.value("BGR565", transcoder_texture_format::cTFBGR565)
.value("RGBA4444", transcoder_texture_format::cTFRGBA4444)
.value("PVRTC2_4_RGB", transcoder_texture_format::cTFPVRTC2_4_RGB)
.value("PVRTC2_4_RGBA", transcoder_texture_format::cTFPVRTC2_4_RGBA)
.value("EAC_R11", transcoder_texture_format::cTFETC2_EAC_R11)
.value("EAC_RG11", transcoder_texture_format::cTFETC2_EAC_RG11)
;
enum_<basis_tex_format>("TextureFormat")
.value("ETC1S", basis_tex_format::cETC1S)
.value("UASTC4x4", basis_tex_format::cUASTC4x4)
;
enum_<msc::TranscodeFlagBits>("TranscodeFlagBits")
.value("TRANSCODE_ALPHA_DATA_TO_OPAQUE_FORMATS",
msc::TranscodeAlphaDataToOpaqueFormats)
.value("HIGH_QUALITY", msc::HighQuality)
;
function("initTranscoders", basisu_transcoder_init);
function("isFormatSupported", basis_is_format_supported);
class_<msc::basisu_image_desc>("ImageInfo")
.constructor<basis_tex_format,uint32_t,uint32_t,uint32_t>()
.property("flags", &msc::basisu_image_desc::m_flags)
.property("rgbByteOffset", &msc::basisu_image_desc::m_rgb_byte_offset)
.property("rgbByteLength", &msc::basisu_image_desc::m_rgb_byte_length)
.property("alphaByteOffset", &msc::basisu_image_desc::m_alpha_byte_offset)
.property("alphaByteLength", &msc::basisu_image_desc::m_alpha_byte_length)
.property("width", &msc::basisu_image_desc::m_orig_width)
.property("height", &msc::basisu_image_desc::m_orig_height)
.property("numBlocksX", &msc::basisu_image_desc::m_num_blocks_x)
.property("numBlocksY", &msc::basisu_image_desc::m_num_blocks_y)
.property("level", &msc::basisu_image_desc::m_level)
;
class_<msc::BasisLzEtc1sImageTranscoder>("BasisLzEtc1sImageTranscoder")
.constructor()
.class_function("getBytesPerBlock", basis_get_bytes_per_block_or_pixel)
.function("decodePalettes",
&msc::BasisLzEtc1sImageTranscoder::decode_palettes)
.function("decodeTables",
&msc::BasisLzEtc1sImageTranscoder::decode_tables)
.function("transcodeImage",
&msc::BasisLzEtc1sImageTranscoder::transcode_image)
;
class_<msc::UastcImageTranscoder>("UastcImageTranscoder")
.constructor()
.class_function("getBytesPerBlock", basis_get_bytes_per_block_or_pixel)
.function("transcodeImage",
&msc::UastcImageTranscoder::transcode_image)
;
class_<basisu_transcoder_state>("BasisTranscoderState")
.constructor()
;
class_<msc::TranscodedImage>("TranscodedImage")
.function( "get_typed_memory_view",
&msc::TranscodedImage::get_typed_memory_view )
;
}
+242
View File
@@ -0,0 +1,242 @@
/***************************** Do not edit. *****************************
Automatically generated from vulkan_core.h version 287 by mkvkformatfiles.
*************************************************************************/
/*
** Copyright 2015-2024 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
*/
.value("UNDEFINED", VK_FORMAT_UNDEFINED)
.value("R4G4_UNORM_PACK8", VK_FORMAT_R4G4_UNORM_PACK8)
.value("R4G4B4A4_UNORM_PACK16", VK_FORMAT_R4G4B4A4_UNORM_PACK16)
.value("B4G4R4A4_UNORM_PACK16", VK_FORMAT_B4G4R4A4_UNORM_PACK16)
.value("R5G6B5_UNORM_PACK16", VK_FORMAT_R5G6B5_UNORM_PACK16)
.value("B5G6R5_UNORM_PACK16", VK_FORMAT_B5G6R5_UNORM_PACK16)
.value("R5G5B5A1_UNORM_PACK16", VK_FORMAT_R5G5B5A1_UNORM_PACK16)
.value("B5G5R5A1_UNORM_PACK16", VK_FORMAT_B5G5R5A1_UNORM_PACK16)
.value("A1R5G5B5_UNORM_PACK16", VK_FORMAT_A1R5G5B5_UNORM_PACK16)
.value("R8_UNORM", VK_FORMAT_R8_UNORM)
.value("R8_SNORM", VK_FORMAT_R8_SNORM)
.value("R8_UINT", VK_FORMAT_R8_UINT)
.value("R8_SINT", VK_FORMAT_R8_SINT)
.value("R8_SRGB", VK_FORMAT_R8_SRGB)
.value("R8G8_UNORM", VK_FORMAT_R8G8_UNORM)
.value("R8G8_SNORM", VK_FORMAT_R8G8_SNORM)
.value("R8G8_UINT", VK_FORMAT_R8G8_UINT)
.value("R8G8_SINT", VK_FORMAT_R8G8_SINT)
.value("R8G8_SRGB", VK_FORMAT_R8G8_SRGB)
.value("R8G8B8_UNORM", VK_FORMAT_R8G8B8_UNORM)
.value("R8G8B8_SNORM", VK_FORMAT_R8G8B8_SNORM)
.value("R8G8B8_UINT", VK_FORMAT_R8G8B8_UINT)
.value("R8G8B8_SINT", VK_FORMAT_R8G8B8_SINT)
.value("R8G8B8_SRGB", VK_FORMAT_R8G8B8_SRGB)
.value("B8G8R8_UNORM", VK_FORMAT_B8G8R8_UNORM)
.value("B8G8R8_SNORM", VK_FORMAT_B8G8R8_SNORM)
.value("B8G8R8_UINT", VK_FORMAT_B8G8R8_UINT)
.value("B8G8R8_SINT", VK_FORMAT_B8G8R8_SINT)
.value("B8G8R8_SRGB", VK_FORMAT_B8G8R8_SRGB)
.value("R8G8B8A8_UNORM", VK_FORMAT_R8G8B8A8_UNORM)
.value("R8G8B8A8_SNORM", VK_FORMAT_R8G8B8A8_SNORM)
.value("R8G8B8A8_UINT", VK_FORMAT_R8G8B8A8_UINT)
.value("R8G8B8A8_SINT", VK_FORMAT_R8G8B8A8_SINT)
.value("R8G8B8A8_SRGB", VK_FORMAT_R8G8B8A8_SRGB)
.value("B8G8R8A8_UNORM", VK_FORMAT_B8G8R8A8_UNORM)
.value("B8G8R8A8_SNORM", VK_FORMAT_B8G8R8A8_SNORM)
.value("B8G8R8A8_UINT", VK_FORMAT_B8G8R8A8_UINT)
.value("B8G8R8A8_SINT", VK_FORMAT_B8G8R8A8_SINT)
.value("B8G8R8A8_SRGB", VK_FORMAT_B8G8R8A8_SRGB)
.value("A8B8G8R8_UNORM_PACK32", VK_FORMAT_A8B8G8R8_UNORM_PACK32)
.value("A8B8G8R8_SNORM_PACK32", VK_FORMAT_A8B8G8R8_SNORM_PACK32)
.value("A8B8G8R8_UINT_PACK32", VK_FORMAT_A8B8G8R8_UINT_PACK32)
.value("A8B8G8R8_SINT_PACK32", VK_FORMAT_A8B8G8R8_SINT_PACK32)
.value("A8B8G8R8_SRGB_PACK32", VK_FORMAT_A8B8G8R8_SRGB_PACK32)
.value("A2R10G10B10_UNORM_PACK32", VK_FORMAT_A2R10G10B10_UNORM_PACK32)
.value("A2R10G10B10_SNORM_PACK32", VK_FORMAT_A2R10G10B10_SNORM_PACK32)
.value("A2R10G10B10_UINT_PACK32", VK_FORMAT_A2R10G10B10_UINT_PACK32)
.value("A2R10G10B10_SINT_PACK32", VK_FORMAT_A2R10G10B10_SINT_PACK32)
.value("A2B10G10R10_UNORM_PACK32", VK_FORMAT_A2B10G10R10_UNORM_PACK32)
.value("A2B10G10R10_SNORM_PACK32", VK_FORMAT_A2B10G10R10_SNORM_PACK32)
.value("A2B10G10R10_UINT_PACK32", VK_FORMAT_A2B10G10R10_UINT_PACK32)
.value("A2B10G10R10_SINT_PACK32", VK_FORMAT_A2B10G10R10_SINT_PACK32)
.value("R16_UNORM", VK_FORMAT_R16_UNORM)
.value("R16_SNORM", VK_FORMAT_R16_SNORM)
.value("R16_UINT", VK_FORMAT_R16_UINT)
.value("R16_SINT", VK_FORMAT_R16_SINT)
.value("R16_SFLOAT", VK_FORMAT_R16_SFLOAT)
.value("R16G16_UNORM", VK_FORMAT_R16G16_UNORM)
.value("R16G16_SNORM", VK_FORMAT_R16G16_SNORM)
.value("R16G16_UINT", VK_FORMAT_R16G16_UINT)
.value("R16G16_SINT", VK_FORMAT_R16G16_SINT)
.value("R16G16_SFLOAT", VK_FORMAT_R16G16_SFLOAT)
.value("R16G16B16_UNORM", VK_FORMAT_R16G16B16_UNORM)
.value("R16G16B16_SNORM", VK_FORMAT_R16G16B16_SNORM)
.value("R16G16B16_UINT", VK_FORMAT_R16G16B16_UINT)
.value("R16G16B16_SINT", VK_FORMAT_R16G16B16_SINT)
.value("R16G16B16_SFLOAT", VK_FORMAT_R16G16B16_SFLOAT)
.value("R16G16B16A16_UNORM", VK_FORMAT_R16G16B16A16_UNORM)
.value("R16G16B16A16_SNORM", VK_FORMAT_R16G16B16A16_SNORM)
.value("R16G16B16A16_UINT", VK_FORMAT_R16G16B16A16_UINT)
.value("R16G16B16A16_SINT", VK_FORMAT_R16G16B16A16_SINT)
.value("R16G16B16A16_SFLOAT", VK_FORMAT_R16G16B16A16_SFLOAT)
.value("R32_UINT", VK_FORMAT_R32_UINT)
.value("R32_SINT", VK_FORMAT_R32_SINT)
.value("R32_SFLOAT", VK_FORMAT_R32_SFLOAT)
.value("R32G32_UINT", VK_FORMAT_R32G32_UINT)
.value("R32G32_SINT", VK_FORMAT_R32G32_SINT)
.value("R32G32_SFLOAT", VK_FORMAT_R32G32_SFLOAT)
.value("R32G32B32_UINT", VK_FORMAT_R32G32B32_UINT)
.value("R32G32B32_SINT", VK_FORMAT_R32G32B32_SINT)
.value("R32G32B32_SFLOAT", VK_FORMAT_R32G32B32_SFLOAT)
.value("R32G32B32A32_UINT", VK_FORMAT_R32G32B32A32_UINT)
.value("R32G32B32A32_SINT", VK_FORMAT_R32G32B32A32_SINT)
.value("R32G32B32A32_SFLOAT", VK_FORMAT_R32G32B32A32_SFLOAT)
.value("R64_UINT", VK_FORMAT_R64_UINT)
.value("R64_SINT", VK_FORMAT_R64_SINT)
.value("R64_SFLOAT", VK_FORMAT_R64_SFLOAT)
.value("R64G64_UINT", VK_FORMAT_R64G64_UINT)
.value("R64G64_SINT", VK_FORMAT_R64G64_SINT)
.value("R64G64_SFLOAT", VK_FORMAT_R64G64_SFLOAT)
.value("R64G64B64_UINT", VK_FORMAT_R64G64B64_UINT)
.value("R64G64B64_SINT", VK_FORMAT_R64G64B64_SINT)
.value("R64G64B64_SFLOAT", VK_FORMAT_R64G64B64_SFLOAT)
.value("R64G64B64A64_UINT", VK_FORMAT_R64G64B64A64_UINT)
.value("R64G64B64A64_SINT", VK_FORMAT_R64G64B64A64_SINT)
.value("R64G64B64A64_SFLOAT", VK_FORMAT_R64G64B64A64_SFLOAT)
.value("B10G11R11_UFLOAT_PACK32", VK_FORMAT_B10G11R11_UFLOAT_PACK32)
.value("E5B9G9R9_UFLOAT_PACK32", VK_FORMAT_E5B9G9R9_UFLOAT_PACK32)
.value("D16_UNORM", VK_FORMAT_D16_UNORM)
.value("X8_D24_UNORM_PACK32", VK_FORMAT_X8_D24_UNORM_PACK32)
.value("D32_SFLOAT", VK_FORMAT_D32_SFLOAT)
.value("S8_UINT", VK_FORMAT_S8_UINT)
.value("D16_UNORM_S8_UINT", VK_FORMAT_D16_UNORM_S8_UINT)
.value("D24_UNORM_S8_UINT", VK_FORMAT_D24_UNORM_S8_UINT)
.value("D32_SFLOAT_S8_UINT", VK_FORMAT_D32_SFLOAT_S8_UINT)
.value("BC1_RGB_UNORM_BLOCK", VK_FORMAT_BC1_RGB_UNORM_BLOCK)
.value("BC1_RGB_SRGB_BLOCK", VK_FORMAT_BC1_RGB_SRGB_BLOCK)
.value("BC1_RGBA_UNORM_BLOCK", VK_FORMAT_BC1_RGBA_UNORM_BLOCK)
.value("BC1_RGBA_SRGB_BLOCK", VK_FORMAT_BC1_RGBA_SRGB_BLOCK)
.value("BC2_UNORM_BLOCK", VK_FORMAT_BC2_UNORM_BLOCK)
.value("BC2_SRGB_BLOCK", VK_FORMAT_BC2_SRGB_BLOCK)
.value("BC3_UNORM_BLOCK", VK_FORMAT_BC3_UNORM_BLOCK)
.value("BC3_SRGB_BLOCK", VK_FORMAT_BC3_SRGB_BLOCK)
.value("BC4_UNORM_BLOCK", VK_FORMAT_BC4_UNORM_BLOCK)
.value("BC4_SNORM_BLOCK", VK_FORMAT_BC4_SNORM_BLOCK)
.value("BC5_UNORM_BLOCK", VK_FORMAT_BC5_UNORM_BLOCK)
.value("BC5_SNORM_BLOCK", VK_FORMAT_BC5_SNORM_BLOCK)
.value("BC6H_UFLOAT_BLOCK", VK_FORMAT_BC6H_UFLOAT_BLOCK)
.value("BC6H_SFLOAT_BLOCK", VK_FORMAT_BC6H_SFLOAT_BLOCK)
.value("BC7_UNORM_BLOCK", VK_FORMAT_BC7_UNORM_BLOCK)
.value("BC7_SRGB_BLOCK", VK_FORMAT_BC7_SRGB_BLOCK)
.value("ETC2_R8G8B8_UNORM_BLOCK", VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK)
.value("ETC2_R8G8B8_SRGB_BLOCK", VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK)
.value("ETC2_R8G8B8A1_UNORM_BLOCK", VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK)
.value("ETC2_R8G8B8A1_SRGB_BLOCK", VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK)
.value("ETC2_R8G8B8A8_UNORM_BLOCK", VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK)
.value("ETC2_R8G8B8A8_SRGB_BLOCK", VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK)
.value("EAC_R11_UNORM_BLOCK", VK_FORMAT_EAC_R11_UNORM_BLOCK)
.value("EAC_R11_SNORM_BLOCK", VK_FORMAT_EAC_R11_SNORM_BLOCK)
.value("EAC_R11G11_UNORM_BLOCK", VK_FORMAT_EAC_R11G11_UNORM_BLOCK)
.value("EAC_R11G11_SNORM_BLOCK", VK_FORMAT_EAC_R11G11_SNORM_BLOCK)
.value("ASTC_4x4_UNORM_BLOCK", VK_FORMAT_ASTC_4x4_UNORM_BLOCK)
.value("ASTC_4x4_SRGB_BLOCK", VK_FORMAT_ASTC_4x4_SRGB_BLOCK)
.value("ASTC_5x4_UNORM_BLOCK", VK_FORMAT_ASTC_5x4_UNORM_BLOCK)
.value("ASTC_5x4_SRGB_BLOCK", VK_FORMAT_ASTC_5x4_SRGB_BLOCK)
.value("ASTC_5x5_UNORM_BLOCK", VK_FORMAT_ASTC_5x5_UNORM_BLOCK)
.value("ASTC_5x5_SRGB_BLOCK", VK_FORMAT_ASTC_5x5_SRGB_BLOCK)
.value("ASTC_6x5_UNORM_BLOCK", VK_FORMAT_ASTC_6x5_UNORM_BLOCK)
.value("ASTC_6x5_SRGB_BLOCK", VK_FORMAT_ASTC_6x5_SRGB_BLOCK)
.value("ASTC_6x6_UNORM_BLOCK", VK_FORMAT_ASTC_6x6_UNORM_BLOCK)
.value("ASTC_6x6_SRGB_BLOCK", VK_FORMAT_ASTC_6x6_SRGB_BLOCK)
.value("ASTC_8x5_UNORM_BLOCK", VK_FORMAT_ASTC_8x5_UNORM_BLOCK)
.value("ASTC_8x5_SRGB_BLOCK", VK_FORMAT_ASTC_8x5_SRGB_BLOCK)
.value("ASTC_8x6_UNORM_BLOCK", VK_FORMAT_ASTC_8x6_UNORM_BLOCK)
.value("ASTC_8x6_SRGB_BLOCK", VK_FORMAT_ASTC_8x6_SRGB_BLOCK)
.value("ASTC_8x8_UNORM_BLOCK", VK_FORMAT_ASTC_8x8_UNORM_BLOCK)
.value("ASTC_8x8_SRGB_BLOCK", VK_FORMAT_ASTC_8x8_SRGB_BLOCK)
.value("ASTC_10x5_UNORM_BLOCK", VK_FORMAT_ASTC_10x5_UNORM_BLOCK)
.value("ASTC_10x5_SRGB_BLOCK", VK_FORMAT_ASTC_10x5_SRGB_BLOCK)
.value("ASTC_10x6_UNORM_BLOCK", VK_FORMAT_ASTC_10x6_UNORM_BLOCK)
.value("ASTC_10x6_SRGB_BLOCK", VK_FORMAT_ASTC_10x6_SRGB_BLOCK)
.value("ASTC_10x8_UNORM_BLOCK", VK_FORMAT_ASTC_10x8_UNORM_BLOCK)
.value("ASTC_10x8_SRGB_BLOCK", VK_FORMAT_ASTC_10x8_SRGB_BLOCK)
.value("ASTC_10x10_UNORM_BLOCK", VK_FORMAT_ASTC_10x10_UNORM_BLOCK)
.value("ASTC_10x10_SRGB_BLOCK", VK_FORMAT_ASTC_10x10_SRGB_BLOCK)
.value("ASTC_12x10_UNORM_BLOCK", VK_FORMAT_ASTC_12x10_UNORM_BLOCK)
.value("ASTC_12x10_SRGB_BLOCK", VK_FORMAT_ASTC_12x10_SRGB_BLOCK)
.value("ASTC_12x12_UNORM_BLOCK", VK_FORMAT_ASTC_12x12_UNORM_BLOCK)
.value("ASTC_12x12_SRGB_BLOCK", VK_FORMAT_ASTC_12x12_SRGB_BLOCK)
.value("G8B8G8R8_422_UNORM", VK_FORMAT_G8B8G8R8_422_UNORM)
.value("B8G8R8G8_422_UNORM", VK_FORMAT_B8G8R8G8_422_UNORM)
.value("R10X6_UNORM_PACK16", VK_FORMAT_R10X6_UNORM_PACK16)
.value("R10X6G10X6_UNORM_2PACK16", VK_FORMAT_R10X6G10X6_UNORM_2PACK16)
.value("R10X6G10X6B10X6A10X6_UNORM_4PACK16", VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16)
.value("G10X6B10X6G10X6R10X6_422_UNORM_4PACK16", VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16)
.value("B10X6G10X6R10X6G10X6_422_UNORM_4PACK16", VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16)
.value("R12X4_UNORM_PACK16", VK_FORMAT_R12X4_UNORM_PACK16)
.value("R12X4G12X4_UNORM_2PACK16", VK_FORMAT_R12X4G12X4_UNORM_2PACK16)
.value("R12X4G12X4B12X4A12X4_UNORM_4PACK16", VK_FORMAT_R12X4G12X4B12X4A12X4_UNORM_4PACK16)
.value("G12X4B12X4G12X4R12X4_422_UNORM_4PACK16", VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16)
.value("B12X4G12X4R12X4G12X4_422_UNORM_4PACK16", VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16)
.value("G16B16G16R16_422_UNORM", VK_FORMAT_G16B16G16R16_422_UNORM)
.value("B16G16R16G16_422_UNORM", VK_FORMAT_B16G16R16G16_422_UNORM)
.value("A4R4G4B4_UNORM_PACK16", VK_FORMAT_A4R4G4B4_UNORM_PACK16)
.value("A4B4G4R4_UNORM_PACK16", VK_FORMAT_A4B4G4R4_UNORM_PACK16)
.value("ASTC_4x4_SFLOAT_BLOCK", VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK)
.value("ASTC_5x4_SFLOAT_BLOCK", VK_FORMAT_ASTC_5x4_SFLOAT_BLOCK)
.value("ASTC_5x5_SFLOAT_BLOCK", VK_FORMAT_ASTC_5x5_SFLOAT_BLOCK)
.value("ASTC_6x5_SFLOAT_BLOCK", VK_FORMAT_ASTC_6x5_SFLOAT_BLOCK)
.value("ASTC_6x6_SFLOAT_BLOCK", VK_FORMAT_ASTC_6x6_SFLOAT_BLOCK)
.value("ASTC_8x5_SFLOAT_BLOCK", VK_FORMAT_ASTC_8x5_SFLOAT_BLOCK)
.value("ASTC_8x6_SFLOAT_BLOCK", VK_FORMAT_ASTC_8x6_SFLOAT_BLOCK)
.value("ASTC_8x8_SFLOAT_BLOCK", VK_FORMAT_ASTC_8x8_SFLOAT_BLOCK)
.value("ASTC_10x5_SFLOAT_BLOCK", VK_FORMAT_ASTC_10x5_SFLOAT_BLOCK)
.value("ASTC_10x6_SFLOAT_BLOCK", VK_FORMAT_ASTC_10x6_SFLOAT_BLOCK)
.value("ASTC_10x8_SFLOAT_BLOCK", VK_FORMAT_ASTC_10x8_SFLOAT_BLOCK)
.value("ASTC_10x10_SFLOAT_BLOCK", VK_FORMAT_ASTC_10x10_SFLOAT_BLOCK)
.value("ASTC_12x10_SFLOAT_BLOCK", VK_FORMAT_ASTC_12x10_SFLOAT_BLOCK)
.value("ASTC_12x12_SFLOAT_BLOCK", VK_FORMAT_ASTC_12x12_SFLOAT_BLOCK)
.value("PVRTC1_2BPP_UNORM_BLOCK_IMG", VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG)
.value("PVRTC1_4BPP_UNORM_BLOCK_IMG", VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG)
.value("PVRTC2_2BPP_UNORM_BLOCK_IMG", VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG)
.value("PVRTC2_4BPP_UNORM_BLOCK_IMG", VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG)
.value("PVRTC1_2BPP_SRGB_BLOCK_IMG", VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG)
.value("PVRTC1_4BPP_SRGB_BLOCK_IMG", VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG)
.value("PVRTC2_2BPP_SRGB_BLOCK_IMG", VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG)
.value("PVRTC2_4BPP_SRGB_BLOCK_IMG", VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG)
.value("ASTC_3x3x3_UNORM_BLOCK_EXT", VK_FORMAT_ASTC_3x3x3_UNORM_BLOCK_EXT)
.value("ASTC_3x3x3_SRGB_BLOCK_EXT", VK_FORMAT_ASTC_3x3x3_SRGB_BLOCK_EXT)
.value("ASTC_3x3x3_SFLOAT_BLOCK_EXT", VK_FORMAT_ASTC_3x3x3_SFLOAT_BLOCK_EXT)
.value("ASTC_4x3x3_UNORM_BLOCK_EXT", VK_FORMAT_ASTC_4x3x3_UNORM_BLOCK_EXT)
.value("ASTC_4x3x3_SRGB_BLOCK_EXT", VK_FORMAT_ASTC_4x3x3_SRGB_BLOCK_EXT)
.value("ASTC_4x3x3_SFLOAT_BLOCK_EXT", VK_FORMAT_ASTC_4x3x3_SFLOAT_BLOCK_EXT)
.value("ASTC_4x4x3_UNORM_BLOCK_EXT", VK_FORMAT_ASTC_4x4x3_UNORM_BLOCK_EXT)
.value("ASTC_4x4x3_SRGB_BLOCK_EXT", VK_FORMAT_ASTC_4x4x3_SRGB_BLOCK_EXT)
.value("ASTC_4x4x3_SFLOAT_BLOCK_EXT", VK_FORMAT_ASTC_4x4x3_SFLOAT_BLOCK_EXT)
.value("ASTC_4x4x4_UNORM_BLOCK_EXT", VK_FORMAT_ASTC_4x4x4_UNORM_BLOCK_EXT)
.value("ASTC_4x4x4_SRGB_BLOCK_EXT", VK_FORMAT_ASTC_4x4x4_SRGB_BLOCK_EXT)
.value("ASTC_4x4x4_SFLOAT_BLOCK_EXT", VK_FORMAT_ASTC_4x4x4_SFLOAT_BLOCK_EXT)
.value("ASTC_5x4x4_UNORM_BLOCK_EXT", VK_FORMAT_ASTC_5x4x4_UNORM_BLOCK_EXT)
.value("ASTC_5x4x4_SRGB_BLOCK_EXT", VK_FORMAT_ASTC_5x4x4_SRGB_BLOCK_EXT)
.value("ASTC_5x4x4_SFLOAT_BLOCK_EXT", VK_FORMAT_ASTC_5x4x4_SFLOAT_BLOCK_EXT)
.value("ASTC_5x5x4_UNORM_BLOCK_EXT", VK_FORMAT_ASTC_5x5x4_UNORM_BLOCK_EXT)
.value("ASTC_5x5x4_SRGB_BLOCK_EXT", VK_FORMAT_ASTC_5x5x4_SRGB_BLOCK_EXT)
.value("ASTC_5x5x4_SFLOAT_BLOCK_EXT", VK_FORMAT_ASTC_5x5x4_SFLOAT_BLOCK_EXT)
.value("ASTC_5x5x5_UNORM_BLOCK_EXT", VK_FORMAT_ASTC_5x5x5_UNORM_BLOCK_EXT)
.value("ASTC_5x5x5_SRGB_BLOCK_EXT", VK_FORMAT_ASTC_5x5x5_SRGB_BLOCK_EXT)
.value("ASTC_5x5x5_SFLOAT_BLOCK_EXT", VK_FORMAT_ASTC_5x5x5_SFLOAT_BLOCK_EXT)
.value("ASTC_6x5x5_UNORM_BLOCK_EXT", VK_FORMAT_ASTC_6x5x5_UNORM_BLOCK_EXT)
.value("ASTC_6x5x5_SRGB_BLOCK_EXT", VK_FORMAT_ASTC_6x5x5_SRGB_BLOCK_EXT)
.value("ASTC_6x5x5_SFLOAT_BLOCK_EXT", VK_FORMAT_ASTC_6x5x5_SFLOAT_BLOCK_EXT)
.value("ASTC_6x6x5_UNORM_BLOCK_EXT", VK_FORMAT_ASTC_6x6x5_UNORM_BLOCK_EXT)
.value("ASTC_6x6x5_SRGB_BLOCK_EXT", VK_FORMAT_ASTC_6x6x5_SRGB_BLOCK_EXT)
.value("ASTC_6x6x5_SFLOAT_BLOCK_EXT", VK_FORMAT_ASTC_6x6x5_SFLOAT_BLOCK_EXT)
.value("ASTC_6x6x6_UNORM_BLOCK_EXT", VK_FORMAT_ASTC_6x6x6_UNORM_BLOCK_EXT)
.value("ASTC_6x6x6_SRGB_BLOCK_EXT", VK_FORMAT_ASTC_6x6x6_SRGB_BLOCK_EXT)
.value("ASTC_6x6x6_SFLOAT_BLOCK_EXT", VK_FORMAT_ASTC_6x6x6_SFLOAT_BLOCK_EXT)
.value("R16G16_SFIXED5_NV", VK_FORMAT_R16G16_SFIXED5_NV)
.value("A1B5G5R5_UNORM_PACK16_KHR", VK_FORMAT_A1B5G5R5_UNORM_PACK16_KHR)
.value("A8_UNORM_KHR", VK_FORMAT_A8_UNORM_KHR)
+17
View File
@@ -0,0 +1,17 @@
# Copyright (c) 2023, Shukant Pal and Contributors
# SPDX-License-Identifier: Apache-2.0
build
dist
pyktx.egg-info
venv
*.iml
.eggs
__pycache__
Release
Debug
# Sphinx
docs
_build
_templates
+242
View File
@@ -0,0 +1,242 @@
# Copyright 2025 The Khronos Group Inc.
# Copyright 2023 Shukant Pal
# SPDX-License-Identifier: Apache-2.0
option( KTX_PY_USE_VENV
"Use a Python virtual environment. Needed for externally managed python installations."
OFF
)
find_package (Python3 COMPONENTS Interpreter)
set(SOURCE_DIR ${CMAKE_SOURCE_DIR}/interface/python_binding)
file(GLOB pyktx_py_src ${SOURCE_DIR}/pyktx/*.py)
list(TRANSFORM pyktx_py_src REPLACE "${SOURCE_DIR}/pyktx/" "${KTX_BUILD_DIR}/interface/python_binding/docs/pyktx." OUTPUT_VARIABLE pyktx_py_rst_filenames)
list(TRANSFORM pyktx_py_rst_filenames REPLACE ".py$" ".rst" OUTPUT_VARIABLE pyktx_py_rst)
set(PYTHON_EXECUTABLE_SYSTEM ${Python3_EXECUTABLE})
if(DEFINED PYTHON AND NOT ${PYTHON} STREQUAL "")
set(PYTHON_EXECUTABLE_SYSTEM ${PYTHON})
message(STATUS "Override PYTHON with ${PYTHON}")
endif()
if (LINUX AND NOT Python3_FOUND)
set(PYTHON_EXECUTABLE_SYSTEM python)
message(STATUS "CMake failed to find python3. Will continue assuming it's on PATH")
endif()
if(KTX_PY_USE_VENV)
set(PYTHON_VENV_DIR "${KTX_BUILD_DIR}/interface/python_binding")
set(PYTHON_EXECUTABLE ${PYTHON_VENV_DIR}/bin/python3)
message(STATUS "Using virtual environment for Python")
else()
set(PYTHON_EXECUTABLE ${PYTHON_EXECUTABLE_SYSTEM})
endif()
# Convert Windows path to CMake path
cmake_path(SET PYTHON_PATH ${PYTHON_EXECUTABLE})
set(LIBKTX_LIB_DIR ${KTX_BUILD_DIR}/$<CONFIG>)
if(KTX_PY_USE_VENV)
add_custom_target( py-venv ALL
COMMENT
"Set up virtual environment for Python"
)
add_custom_command(
TARGET py-venv
PRE_BUILD
COMMAND
${PYTHON_EXECUTABLE_SYSTEM} -m venv ${PYTHON_VENV_DIR}
COMMENT
"Create virtual environment for Python"
)
endif()
add_custom_target( pyktx-deps ALL
COMMENT
"Python deps"
)
if(KTX_PY_USE_VENV)
add_dependencies(pyktx-deps py-venv)
endif()
add_custom_command(
TARGET pyktx-deps
PRE_BUILD
COMMAND
${PYTHON_EXECUTABLE} -m pip install --no-warn-script-location -r ${SOURCE_DIR}/requirements.txt
COMMENT
"Install dependencies for pyktx build"
)
add_custom_target( pyktx ALL
DEPENDS
ktx
${pyktx_py_src}
pyktx/ktx_texture.h
pyktx/ktx_texture1.h
pyktx/ktx_texture2.h
pyktx/ktx_texture.c
pyktx/ktx_texture1.c
pyktx/ktx_texture2.c
WORKING_DIRECTORY
${SOURCE_DIR}
COMMENT
"Python distributions"
)
add_dependencies(pyktx pyktx-deps)
add_custom_command(
TARGET pyktx
PRE_BUILD
COMMAND
${PYTHON_EXECUTABLE} clean.py
COMMENT
"Clean up pyktx build artifacts"
WORKING_DIRECTORY
${SOURCE_DIR}
)
# Normalize version number as the python toolchain does. Tweaks are reduced
# to a, b or rc immediately following the patch number. We do because names
# of the BYPRODUCTS in the following custom_command need to match the
# names with normalized version numbers the python tools would produce.
function(normalize_version _var fullver)
string(REPLACE -alpha a normalized ${fullver})
string(REPLACE -beta b normalized ${normalized})
set(${_var} "${normalized}" PARENT_SCOPE)
endfunction()
normalize_version(KTX_VERSION_NORMALIZED ${KTX_VERSION_FULL})
set(DIST_DIR ${KTX_BUILD_DIR}/interface/python_binding/dist)
set(SOURCE_ARCHIVE_BASENAME ${DIST_DIR}/pyktx-${KTX_VERSION_NORMALIZED})
add_custom_command(
TARGET pyktx
POST_BUILD
BYPRODUCTS
${SOURCE_ARCHIVE_BASENAME}.tar.gz
${SOURCE_ARCHIVE_BASENAME}.zip
COMMAND
${CMAKE_COMMAND} -E env
LIBKTX_INCLUDE_DIR=${CMAKE_SOURCE_DIR}/include
LIBKTX_LIB_DIR=${LIBKTX_LIB_DIR}
LIBKTX_VERSION=${KTX_VERSION_NORMALIZED}
# The build module by default builds in an isolated environment, i.e. it
# requires a virtual env. I have not found a way via find_package to
# ensure venv support is installed. This can be turned off with
# `--no-isolation` but such builds have not been tested.
${PYTHON_EXECUTABLE} -m build --sdist --outdir ${DIST_DIR}
COMMENT
"Build pyktx source package"
WORKING_DIRECTORY
${SOURCE_DIR}
)
add_custom_command(
TARGET pyktx
POST_BUILD
COMMAND
${CMAKE_COMMAND} -E env
LIBKTX_INCLUDE_DIR=${CMAKE_SOURCE_DIR}/include
LIBKTX_LIB_DIR=${LIBKTX_LIB_DIR}
LIBKTX_VERSION=${KTX_VERSION_NORMALIZED}
# Ditto with sdist isolated environment comment.
${PYTHON_EXECUTABLE} -m build --wheel --outdir ${DIST_DIR}
COMMENT
"Build pyktx wheel"
WORKING_DIRECTORY
${SOURCE_DIR}
)
set(pyktx_egg_info
${SOURCE_DIR}/pyktx.egg-info/dependency_links.txt
${SOURCE_DIR}/pyktx.egg-info/PKG-INFO
${SOURCE_DIR}/pyktx.egg-info/requires.txt
${SOURCE_DIR}/pyktx.egg-info/SOURCES.txt
${SOURCE_DIR}/pyktx.egg-info/top_level.txt)
add_test(NAME pyktx
COMMAND
${CMAKE_COMMAND} -E env
LIBKTX_INCLUDE_DIR=${CMAKE_SOURCE_DIR}/include
LIBKTX_LIB_DIR=${LIBKTX_LIB_DIR}
KTX_RUN_TESTS=ON
DYLD_LIBRARY_PATH=${LIBKTX_LIB_DIR}:$ENV{DYLD_LIBRARY_PATH}
LD_LIBRARY_PATH=${LIBKTX_LIB_DIR}:$ENV{LD_LIBRARY_PATH}
${PYTHON_EXECUTABLE} buildscript.py
WORKING_DIRECTORY
${SOURCE_DIR}
)
if(KTX_FEATURE_DOC)
add_custom_target(pyktx.doc ALL
DEPENDS
pyktx-deps
ktx # No way to avoid this autodoc needs compiled modules.
COMMENT
"Build Python documentation"
)
add_custom_command(
TARGET pyktx.doc
POST_BUILD
BYPRODUCTS
${KTX_BUILD_DIR}/interface/python_binding/conf.py
${KTX_BUILD_DIR}/interface/python_binding/index.rst
${pyktx_py_rst}
${KTX_BUILD_DIR}/interface/python_binding/docs/pyktx.rst
${KTX_BUILD_DIR}/interface/python_binding/docs/pyktx.native.rst
${pyktx_egg_info}
COMMAND
# This appears to build binaries of the native parts in the source pyktx
# folder. Without the binaries, autodoc can't import the modules. It
# prints warnings and fails to include any content in the documentation
# for the native interfaces in the HTML files it creates.
${CMAKE_COMMAND} -E env
LIBKTX_INCLUDE_DIR=${CMAKE_SOURCE_DIR}/include
LIBKTX_LIB_DIR=${LIBKTX_LIB_DIR}
DYLD_LIBRARY_PATH=${LIBKTX_LIB_DIR}:$ENV{DYLD_LIBRARY_PATH}
LD_LIBRARY_PATH=${LIBKTX_LIB_DIR}:$ENV{LD_LIBRARY_PATH}
${PYTHON_EXECUTABLE} buildscript.py
COMMAND
${CMAKE_COMMAND} -E copy
index.rst conf.py ${KTX_BUILD_DIR}/interface/python_binding
# Currently there are no static items to be included in the Sphinx generated
# documentation. The infrastructure is here in case of future need. If
# these command are removed, also remove html_static_path from conf.py.
COMMAND
${CMAKE_COMMAND} -E make_directory
${KTX_BUILD_DIR}/interface/python_binding/_static
COMMAND
${CMAKE_COMMAND} -E copy_directory
_static ${KTX_BUILD_DIR}/interface/python_binding/_static
COMMAND
# Sphinx Apidoc extracts docstrings from the source files and builds .rst files
# in the docs directory of the python_binding section of the build directory.
# `--separate` says to put the documentation for each module on its own page.
${CMAKE_COMMAND} -E env
LIBKTX_INCLUDE_DIR=${CMAKE_SOURCE_DIR}/include
LIBKTX_LIB_DIR=${LIBKTX_LIB_DIR}
${PYTHON_EXECUTABLE} -m sphinx.ext.apidoc -o ${KTX_BUILD_DIR}/interface/python_binding/docs ./pyktx --separate
COMMAND
# Sphinx uses autodoc to generate html files from the .rst files generated above.
# These are expected to be in a docs subdirectory of the SOURCEDIR hence the
# build directory appearing in SOURCEDIR. Sphinx invokes autodoc which wants to
# import the modules hence libktx, must be built for this to fully succeed.
#
# Autodoc is configured by the copy of `conf.py` in the build directory (copied
# there by one of the COMMANDs above). `conf.py` adds the python binding
# section of the build directory to Python's `sys.path` so Autodoc can find the
# modules. However they aren't there. The `pyktx` module is the directory
# ${SOURCE_DIR}/pyktx and the .o files from compiling the native parts are
# built there by buildscript.py when invoked above. ${SOURCE_DIR} appears in
# Python's sys.path so autodoc finds them. There is nothing explicit in `conf.py`
# to do this. It appears that Sphinx or autodoc itself adds its working directory
# to `sys.path`.
${CMAKE_COMMAND} -E env
LIBKTX_INCLUDE_DIR=${CMAKE_SOURCE_DIR}/include
LIBKTX_LIB_DIR=${LIBKTX_LIB_DIR}
SPHINXBUILD=${PYTHON_PATH}\ -m\ sphinx
make SOURCEDIR="${KTX_BUILD_DIR}/interface/python_binding" BUILDDIR="${KTX_BUILD_DIR}/interface/python_binding/docs/html/pyktx" html
WORKING_DIRECTORY
${SOURCE_DIR}
)
endif()
+178
View File
@@ -0,0 +1,178 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
Copyright 2022 Shukant Pal
+10
View File
@@ -0,0 +1,10 @@
# Copyright (c) 2023, Shukant Pal and Contributors
# SPDX-License-Identifier: Apache-2.0
include buildscript.py
include pyktx/ktx_texture.c
include pyktx/ktx_texture1.c
include pyktx/ktx_texture2.c
include pyktx/ktx_texture.h
include pyktx/ktx_texture1.h
include pyktx/ktx_texture2.h
+23
View File
@@ -0,0 +1,23 @@
# Copyright (c) 2023, Shukant Pal and Contributors
# SPDX-License-Identifier: Apache-2.0
#
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = .
BUILDDIR = _build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+35
View File
@@ -0,0 +1,35 @@
Copyright (c) 2023, Shukant Pal and Contributors \
SPDX-License-Identifier: Apache-2.0
Made with love by [Shukant Pal](https://www.shukantpal.com/about) on his way to learning Python.
# pyktx
This Python package provides a Pythonic interface to libktx. It uses CFFI to generate the C bindings.
## Usage
**You must have libktx installed on your system to use pyktx ordinarily. You can configure where libktx is installed using the `LIBKTX_INCLUDE_DIR` and `LIBKTX_LIB_DIR` environment variables too.**
To install libktx, download and run the appropriate installer from [our releases](https://github.com/KhronosGroup/KTX-Software/releases).
## Building
To build and test pyktx,
```bash
# Set LIBKTX_INSTALL_DIR if you've installed libktx at the default system location.
# Otherwise set LIBKTX_INCLUDE_DIR, LIBKTX_LIB_DIR to wherever you've built libktx.
cd ${PROJECT_DIR}/interface/python_binding
KTX_RUN_TESTS=ON python3 buildscript.py
```
If you are on a POSIX system (macOS or Linux), make sure libktx is on your `DYLD_LIBRARY_PATH` and `LD_LIBRARY_PATH`.
> When building on macOS against a universal CPython binary, such as that installed with the Xcode command-line tools (/usr/bin/python3), ld will issue a warning
>
> ```
> ld: warning: ignoring file '/usr/local/lib/libktx.4.3.0.dylib': found architecture 'arm64', required architecture 'x86_64'
> ```
>
> 'arm64' and 'x86_64' may be reversed depending on the build machine architecture. This happens because libktx is not a universal binary so only supports the current platform architecture. The message can be ignored.
+204
View File
@@ -0,0 +1,204 @@
# Copyright (c) 2023, Shukant Pal and Contributors
# SPDX-License-Identifier: Apache-2.0
from cffi import FFI
import platform
import sys
import os
import unittest
LIBKTX_INSTALL_DIR = os.getenv("LIBKTX_INSTALL_DIR")
LIBKTX_INCLUDE_DIR = os.getenv("LIBKTX_INCLUDE_DIR")
LIBKTX_LIB_DIR = os.getenv("LIBKTX_LIB_DIR")
if os.name == 'nt':
if LIBKTX_INSTALL_DIR is None:
LIBKTX_INSTALL_DIR = 'C:\\Program Files\\KTX-Software'
if LIBKTX_INCLUDE_DIR is None:
LIBKTX_INCLUDE_DIR = LIBKTX_INSTALL_DIR + '\\include'
if LIBKTX_LIB_DIR is None:
LIBKTX_LIB_DIR = LIBKTX_INSTALL_DIR + '\\lib'
elif platform.system() == 'Darwin':
if LIBKTX_INCLUDE_DIR is None:
LIBKTX_INCLUDE_DIR = '/usr/local/include'
if LIBKTX_LIB_DIR is None:
LIBKTX_LIB_DIR = '/usr/local/lib'
elif os.name == 'posix':
if LIBKTX_INCLUDE_DIR is None:
LIBKTX_INCLUDE_DIR = '/usr/include'
if LIBKTX_LIB_DIR is None:
LIBKTX_LIB_DIR = '/usr/lib'
ffibuilder = FFI()
ffibuilder.cdef(
"""
void free(void *ptr);
typedef struct ktxTexture ktxTexture;
typedef struct ktxTextureCreateInfo ktxTextureCreateInfo;
typedef struct ktxTexture1 ktxTexture1;
typedef struct ktxHashList ktxHashList;
typedef struct ktxHashListEntry ktxHashListEntry;
typedef struct {
uint32_t error;
ktxTexture* texture;
} ktxTextureMixed;
typedef struct {
void *bytes;
size_t size;
int error;
} ktxWriteToMemory;
typedef struct {
size_t offset;
int error;
} ktxImageOffset;
// Native library
int ktxTexture_WriteToNamedFile(ktxTexture *, const char* const);
uint32_t ktxTexture_GetElementSize(ktxTexture *);
uint32_t ktxTexture_GetRowPitch(ktxTexture *, uint32_t level);
size_t ktxTexture_GetImageSize(ktxTexture *, uint32_t level);
size_t ktxTexture_GetDataSize(ktxTexture *);
size_t ktxTexture_GetDataSizeUncompressed(ktxTexture *);
uint8_t *ktxTexture_GetData(ktxTexture *);
int ktxTexture_SetImageFromMemory(ktxTexture *,
uint32_t level,
uint32_t layer,
uint32_t faceSlice,
void *src,
size_t srcSize);
int ktxTexture2_TranscodeBasis(void *, int outputFormat, int transcodeFlags);
int ktxTexture2_DeflateZstd(void *, uint32_t compressionLevel);
uint32_t ktxTexture2_GetOETF(void *);
bool ktxTexture2_GetPremultipliedAlpha(void *);
bool ktxTexture2_NeedsTranscoding(void *);
int ktxHashList_AddKVPair(ktxHashList *, const char *key, unsigned int valueLen, const void *value);
int ktxHashList_DeleteKVPair(ktxHashList *, const char *key);
ktxHashListEntry *ktxHashList_Next(void *entry);
// Glue code
ktxTextureMixed PY_ktxTexture_CreateFromNamedFile(const char* const filename,
uint32_t create_flags);
ktxWriteToMemory PY_ktxTexture_WriteToMemory(ktxTexture *);
ktxImageOffset PY_ktxTexture_GetImageOffset(ktxTexture *,
uint32_t level,
uint32_t layer,
uint32_t faceSlice);
int PY_ktxTexture_get_classId(ktxTexture *);
bool PY_ktxTexture_get_isArray(ktxTexture *);
bool PY_ktxTexture_get_isCompressed(ktxTexture *);
bool PY_ktxTexture_get_isCubemap(ktxTexture *);
bool PY_ktxTexture_get_generateMipmaps(ktxTexture *);
uint32_t PY_ktxTexture_get_baseWidth(ktxTexture *);
uint32_t PY_ktxTexture_get_baseHeight(ktxTexture *);
uint32_t PY_ktxTexture_get_baseDepth(ktxTexture *);
uint32_t PY_ktxTexture_get_numDimensions(ktxTexture *);
uint32_t PY_ktxTexture_get_numLevels(ktxTexture *);
uint32_t PY_ktxTexture_get_numFaces(ktxTexture *);
uint32_t PY_ktxTexture_get_kvDataLen(ktxTexture *);
void *PY_ktxTexture_get_kvData(ktxTexture *);
ktxHashListEntry *PY_ktxHashList_get_listHead(ktxHashList *list);
ktxHashList *PY_ktxTexture_get_kvDataHead(ktxTexture *);
ktxWriteToMemory PY_ktxHashList_FindValue(ktxHashList *, const char *key);
ktxWriteToMemory PY_ktxHashListEntry_GetKey(ktxHashListEntry *);
ktxWriteToMemory PY_ktxHashListEntry_GetValue(ktxHashListEntry *);
ktxTextureMixed PY_ktxTexture1_Create(uint32_t glInternalFormat,
uint32_t vkFormat,
uint32_t *pDfd,
uint32_t baseWidth,
uint32_t baseHeight,
uint32_t baseDepth,
uint32_t numDimensions,
uint32_t numLevels,
uint32_t numLayers,
uint32_t numFaces,
bool isArray,
bool generateMipmaps,
int storageAllocation);
uint32_t PY_ktxTexture1_get_glFormat(void *);
uint32_t PY_ktxTexture1_get_glInternalformat(void *);
uint32_t PY_ktxTexture1_get_glBaseInternalformat(void *);
uint32_t PY_ktxTexture1_get_glType(void *);
ktxTextureMixed PY_ktxTexture2_Create(uint32_t glInternalFormat,
uint32_t vkFormat,
uint32_t *pDfd,
uint32_t baseWidth,
uint32_t baseHeight,
uint32_t baseDepth,
uint32_t numDimensions,
uint32_t numLevels,
uint32_t numLayers,
uint32_t numFaces,
bool isArray,
bool generateMipmaps,
int storageAllocation);
int PY_ktxTexture2_CompressAstcEx(void *texture,
bool verbose,
uint32_t threadCount,
uint32_t blockDimension,
uint32_t mode,
uint32_t quality,
bool normalMap,
bool perceptual,
char *inputSwizzle);
int PY_ktxTexture2_CompressBasisEx(void *texture,
bool uastc,
bool verbose,
bool noSSE,
uint32_t threadCount,
uint32_t compressionLevel,
uint32_t qualityLevel,
uint32_t maxEndpoints,
float endpointRDOThreshold,
uint32_t maxSelectors,
float selectorRDOThreshold,
char *inputSwizzle,
bool normalMap,
bool separateRGToRGB_A,
bool preSwizzle,
bool noEndpointRDO,
bool noSelectorRDO,
int uastcFlags,
bool uastcRDO,
float uastcRDOQualityScalar,
uint32_t uastcRDODictSize,
float uastcRDOMaxSmoothBlockErrorScale,
float uastcRDOMaxSmoothBlockStdDev,
bool uastcRDODontFavorSimplerModes,
bool uastcRDONoMultithreading);
uint32_t PY_ktxTexture2_get_vkFormat(void *);
uint32_t PY_ktxTexture2_get_supercompressionScheme(void *);
"""
)
ffibuilder.set_source(
"pyktx.native",
"""
#include <ktx.h>
#include "ktx_texture.h"
#include "ktx_texture1.h"
#include "ktx_texture2.h"
""",
include_dirs=['pyktx']
+ ([LIBKTX_INCLUDE_DIR] if LIBKTX_INCLUDE_DIR is not None else []),
sources=['pyktx/ktx_texture.c', 'pyktx/ktx_texture1.c', 'pyktx/ktx_texture2.c'],
libraries=['ktx'],
library_dirs=([LIBKTX_LIB_DIR] if LIBKTX_LIB_DIR is not None else []),
runtime_library_dirs=(([LIBKTX_LIB_DIR] if LIBKTX_LIB_DIR is not None else []) if os.name != 'nt' else None))
if __name__ == "__main__":
ffibuilder.compile(verbose=True)
if 'KTX_RUN_TESTS' in os.environ and os.environ['KTX_RUN_TESTS'] == 'ON':
suite = unittest.TestLoader().discover(os.path.join(os.path.dirname(__file__), 'tests'))
result = unittest.TextTestRunner(verbosity=2).run(suite)
if not result.wasSuccessful():
sys.exit(1)
+17
View File
@@ -0,0 +1,17 @@
# Copyright (c) 2023, Shukant Pal and Contributors
# SPDX-License-Identifier: Apache-2.0
import os
for root, directories, files in os.walk('pyktx'):
for file in files:
if (file.endswith('.o') or
file.endswith('.obj') or
file.endswith('.lib') or
file.endswith('.so') or
file.endswith('.dylib') or
file.endswith('.dll') or
file.startswith('native')):
path = os.path.join('pyktx', file)
print(f"Deleting {path}")
os.remove(path)
+53
View File
@@ -0,0 +1,53 @@
# Copyright (c) 2023, Shukant Pal and Contributors
# SPDX-License-Identifier: Apache-2.0
# Configuration file for the Sphinx documentation builder.
#
# For the full list of built-in configuration values, see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
import os
import sys
# NOTA BENE
# The compiled modules that autodoc is trying to load are in
# <ktx-source>/interface/python_binding but when autodoc is run it is
# given the build directory as its "source" directory so it uses the copy
# of this file in the build directory. Therefore the following 3 lines add
# the build directory to sys.path which does not help autodoc find the
# modules. However something is adding <ktx-source>/interface/python_binding
# to sys.path. My best guess is that sphinx/autodoc add its working
# directory which is the source directory.
current_dir = os.path.dirname(__file__)
target_dir = os.path.abspath(os.path.join(current_dir, "."))
sys.path.insert(0, target_dir)
#print("*******" + __file__ + "**********\n", file=sys.stderr)
#print(sys.path, file=sys.stderr)
project = 'pyktx'
copyright = '2025, Khronos Group, Inc. 2023, Shukant Pal'
author = 'Shukant Pal, Mark Callow'
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.viewcode',
'sphinx.ext.napoleon',
]
templates_path = ['_templates']
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
html_theme = 'sphinx_rtd_theme'
html_static_path = ['_static']
+26
View File
@@ -0,0 +1,26 @@
.. Copyright (c) 2023, Shukant Pal and Contributors
.. SPDX-License-Identifier: Apache-2.0
.. pyktx documentation master file, created by
sphinx-quickstart on Mon Jul 31 20:45:16 2023.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to pyktx's documentation!
=================================
Use your browser's back button to return to the other documentation.
.. toctree::
:maxdepth: 2
:caption: Contents:
:glob:
docs/*
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
+38
View File
@@ -0,0 +1,38 @@
:: Copyright (c) 2023, Shukant Pal and Contributors
:: SPDX-License-Identifier: Apache-2.0
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=.
set BUILDDIR=_build
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.https://www.sphinx-doc.org/
exit /b 1
)
if "%1" == "" goto help
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
:end
popd
@@ -0,0 +1,7 @@
# Copyright (c) 2023, Shukant Pal and Contributors
# SPDX-License-Identifier: Apache-2.0
native.*
*.so
*.o
pyktx.egg-info
@@ -0,0 +1,34 @@
# Copyright (c) 2023, Shukant Pal and Contributors
# SPDX-License-Identifier: Apache-2.0
import os
LIBKTX_INSTALL_DIR = os.getenv("LIBKTX_INSTALL_DIR")
LIBKTX_LIB_DIR = os.getenv("LIBKTX_LIB_DIR")
if os.name == 'nt':
if LIBKTX_INSTALL_DIR is None:
LIBKTX_INSTALL_DIR = 'C:\\Program Files\\KTX-Software'
if LIBKTX_LIB_DIR is None:
LIBKTX_LIB_DIR = LIBKTX_INSTALL_DIR + '\\bin'
os.add_dll_directory(os.path.normpath(LIBKTX_LIB_DIR))
from .gl_internalformat import *
from .ktx_astc_params import *
from .ktx_basis_params import *
from .ktx_error_code import *
from .ktx_hash_list import *
from .ktx_pack_astc_block_dimension import *
from .ktx_pack_astc_encoder_mode import *
from .ktx_pack_astc_quality_levels import *
from .ktx_pack_uastc_flag_bits import *
from .ktx_supercmp_scheme import *
from .ktx_texture import *
from .ktx_texture1 import *
from .ktx_texture2 import *
from .ktx_texture_create_flag_bits import *
from .ktx_texture_create_info import *
from .ktx_texture_create_storage import *
from .ktx_transcode_flag_bits import *
from .ktx_transcode_fmt import *
from .vk_format import *
@@ -0,0 +1,104 @@
# Copyright (c) 2021, Shukant Pal and Contributors
# SPDX-License-Identifier: Apache-2.0
from enum import IntEnum
class GlInternalformat(IntEnum):
"""OpenGL internalformat constants."""
R3_G3_B2 = 0x2A10
RGB4 = 0x804F
RGB5 = 0x8050
RGB8 = 0x8051
RGB10 = 0x8052
RGB12 = 0x8053
RGB16 = 0x8054
RGBA2 = 0x8055
RGBA4 = 0x8056
RGB5_A1 = 0x8057
RGBA8 = 0x8058
RGB10_A2 = 0x8059
RGBA12 = 0x805A
RGBA16 = 0x805B
COMPRESSED_RGB_S3TC_DXT1_EXT = 0x83F0
COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1
COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2
COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3
SRGB_EXT = 0x8C40
SRGB8_EXT = 0x8C41
SRGB_ALPHA_EXT = 0x8C42
SRGB8_ALPHA8_EXT = 0x8C43
SLUMINANCE_ALPHA_EXT = 0x8C44
SLUMINANCE8_ALPHA8_EXT = 0x8C45
SLUMINANCE_EXT = 0x8C46
SLUMINANCE8_EXT = 0x8C47
COMPRESSED_SRGB_EXT = 0x8C48
COMPRESSED_SRGB_ALPHA_EXT = 0x8C49
COMPRESSED_SLUMINANCE_EXT = 0x8C4A
COMPRESSED_SLUMINANCE_ALPHA_EXT = 0x8C4B
COMPRESSED_SRGB_S3TC_DXT1_EXT = 0x8C4C
COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT = 0x8C4D
COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT = 0x8C4E
COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT = 0x8C4F
COMPRESSED_RGB_PVRTC_4BPPV1_IMG = 0x8C00
COMPRESSED_RGB_PVRTC_2BPPV1_IMG = 0x8C01
COMPRESSED_RGBA_PVRTC_4BPPV1_IMG = 0x8C02
COMPRESSED_RGBA_PVRTC_2BPPV1_IMG = 0x8C03
ATC_RGB_AMD = 0x8C92
ATC_RGBA_EXPLICIT_ALPHA_AMD = 0x8C93
ATC_RGBA_INTERPOLATED_ALPHA_AMD = 0x87EE
COMPRESSED_LUMINANCE_LATC1_EXT = 0x8C70
COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT = 0x8C72
COMPRESSED_SIGNED_LUMINANCE_LATC1_EXT = 0x8C71
COMPRESSED_SIGNED_LUMINANCE_ALPHA_LATC2_EXT = 0x8C73
ETC1_RGB8_OES = 0x8D64
COMPRESSED_RGBA_BPTC_UNORM_EXT = 0x8E8C
COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB = 0x8E8D
COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB = 0x8E8E
COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB = 0x8E8F
COMPRESSED_RGB8_ETC2 = 0x9274
COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 = 0x9276
COMPRESSED_RGBA8_ETC2_EAC = 0x9278
COMPRESSED_SRGB8_ALPHA8_ETC2_EAC = 0x9279
COMPRESSED_R11_EAC = 0x9270
COMPRESSED_SIGNED_R11_EAC = 0x9271
COMPRESSED_RG11_EAC = 0x9272
COMPRESSED_SIGNED_RG11_EAC = 0x9273
COMPRESSED_RGBA_ASTC_4x4_KHR = 0x93B0
COMPRESSED_RGBA_ASTC_5x4_KHR = 0x93B1
COMPRESSED_RGBA_ASTC_5x5_KHR = 0x93B2
COMPRESSED_RGBA_ASTC_6x5_KHR = 0x93B3
COMPRESSED_RGBA_ASTC_6x6_KHR = 0x93B4
COMPRESSED_RGBA_ASTC_8x5_KHR = 0x93B5
COMPRESSED_RGBA_ASTC_8x6_KHR = 0x93B6
COMPRESSED_RGBA_ASTC_8x8_KHR = 0x93B7
COMPRESSED_RGBA_ASTC_10x5_KHR = 0x93B8
COMPRESSED_RGBA_ASTC_10x6_KHR = 0x93B9
COMPRESSED_RGBA_ASTC_10x8_KHR = 0x93BA
COMPRESSED_RGBA_ASTC_10x10_KHR = 0x93BB
COMPRESSED_RGBA_ASTC_12x10_KHR = 0x93BC
COMPRESSED_RGBA_ASTC_12x12_KHR = 0x93BD
COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR = 0x93D0
COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR = 0x93D1
COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR = 0x93D2
COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR = 0x93D3
COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR = 0x93D4
COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR = 0x93D5
COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR = 0x93D6
COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR = 0x93D7
COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR = 0x93D8
COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR = 0x93D9
COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR = 0x93DA
COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR = 0x93DB
COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR = 0x93DC
COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR = 0x93DD
@@ -0,0 +1,64 @@
# Copyright (c) 2023, Shukant Pal and Contributors
# SPDX-License-Identifier: Apache-2.0
import re
from dataclasses import dataclass
from .ktx_pack_astc_block_dimension import KtxPackAstcBlockDimension
from .ktx_pack_astc_encoder_mode import KtxPackAstcEncoderMode
from .ktx_pack_astc_quality_levels import KtxPackAstcQualityLevels
swizzle_regex = re.compile('^[rgba01]{4}$')
@dataclass
class KtxAstcParams:
"""Data for passing extended parameters to KtxTexture2.compress_astc()."""
verbose: bool = False
"""If true, prints Astc encoder operation details to stdout. Not recommended for GUI apps."""
thread_count: int = 1
"""Number of threads used for compression. Default is 1."""
block_dimension: KtxPackAstcBlockDimension = KtxPackAstcBlockDimension.D4x4
"""Combinations of block dimensions that astcenc supports i.e. 6x6, 8x8, 6x5 etc"""
mode: KtxPackAstcEncoderMode = KtxPackAstcEncoderMode.DEFAULT
"""Can be {ldr/hdr} from astcenc"""
quality_level: int = KtxPackAstcQualityLevels.FASTEST
"""astcenc supports -fastest, -fast, -medium, -thorough, -exhaustive"""
normal_map: bool = False
"""
Tunes codec parameters for better quality on normal maps.
In this mode normals are compressed to X,Y components
Discarding Z component, reader will need to generate Z
component in shaders.
"""
perceptual: bool = False
"""
The codec should optimize for perceptual error, instead of direct RMS error.
This aims to improves perceived image quality, but
typically lowers the measured PSNR score. Perceptual methods are
currently only available for normal maps and RGB color data.
"""
input_swizzle: bytes = bytes([0, 0, 0, 0])
"""
A swizzle to provide as input to astcenc.
It must match the regular expression /^[rgba01]{4}$/.
"""
def parse_swizzle(self, swizzle: str) -> None:
if not swizzle_regex.match(swizzle):
raise ValueError(swizzle
+ " does not match the appropriate format "
+ str(swizzle_regex)
+ " for a swizzle")
self.input_swizzle = swizzle.encode('ascii')
@@ -0,0 +1,172 @@
# Copyright (c) 2023, Shukant Pal and Contributors
# SPDX-License-Identifier: Apache-2.0
from dataclasses import dataclass
from .ktx_pack_uastc_flag_bits import KtxPackUastcFlagBits
@dataclass
class KtxBasisParams:
"""Data for passing extended params to KtxTexture2.compressBasis()."""
uastc: bool = False
"""True to use UASTC base, false to use ETC1S base."""
verbose: bool = False
"""If true, prints Basis Universal encoder operation details to stdout. Not recommended for GUI apps."""
no_sse: bool = False
"""True to forbid use of the SSE instruction set. Ignored if CPU does not support SSE."""
thread_count: int = 1
"""Number of threads used for compression. Default is 1."""
compression_level: int = 0
"""
Encoding speed vs. quality tradeoff. Range is [0,5].
Higher values are slower, but give higher quality. There is no default.
Callers must explicitly set this value. Callers can use
KTX_ETC1S_DEFAULT_COMPRESSION_LEVEL as a default value. Currently this is 2.
"""
quality_level: int = 0
"""
Compression quality. Range is [1,255].
Lower gives better compression/lower quality/faster.
Higher gives less compression/higher quality/slower.
This automatically determines values for max_endpoints, max_selectors,
endpoint_rdo_threshold, and selector_rdo_threshold for the target quality
level. Setting these parameters overrides the values determined by quality_level
which defaults to 128 if neither it nor both of max_endpoints and max_selectors
have been set.
Both of max_endpoints and max_selectors must be set for them to have any effect.
quality_level will only determine values for endpoint_rdo_threshold and selector_rdo_threshold
when its value exceeds 128, otherwise their defaults will be used.
"""
max_endpoints: int = 0
"""
Manually set the max number of color endpoint clusters.
Range is [1,16128]. Default is 0, unset. If this is set, max_selectors
must also be set, otherwise the value will be ignored.
"""
endpoint_rdo_threshold: int = 0
"""
Set endpoint RDO quality threshold. The default is 1.25.
Lower is higher quality but less quality per output bit (try [1.0,3.0].
This will override the value chosen by quality_level.
"""
max_selectors: int = 0
"""
Manually set the max number of color selector clusters. Range is [1,16128].
Default is 0, unset. If this is set, max_endpoints must also be set, otherwise
the value will be ignored.
"""
selector_rdo_threshold: int = 0
"""
Set selector RDO quality threshold. The default is 1.5.
Lower is higher quality but less quality per output bit (try [1.0,3.0]).
This will override the value chosen by @c qualityLevel.
"""
input_swizzle: bytes = bytes(4)
"""
A swizzle to apply before encoding.
It must match the regular expression /^[rgba01]{4}$/. If both this and
pre_swizzle are specified KtxTexture2.compressBasis() will raise INVALID_OPERATION.
"""
normal_map: bool = False
"""
Tunes codec parameters for better quality on normal maps (no
selector RDO, no endpoint RDO) and sets the texture's DFD appropriately.
Only valid for linear textures.
"""
pre_swizzle: bool = False
"""
If the texture has swizzle metadata, apply it before compressing.
Swizzling, like rabb may yield drastically different error metrics
if done after supercompression.
"""
separate_rg_to_rgb_a: bool = False
"""
This was and is a no-op.
2-component inputs have always been automatically separated
using an "rrrg" input_swizzle.
"""
no_endpoint_rdo: bool = False
"""
Disable endpoint rate distortion optimizations.
Slightly faster, less noisy output, but lower quality per output bit.
"""
no_selector_rdo: bool = False
"""
Disable selector rate distortion optimizations.
Slightly faster, less noisy output, but lower quality per output bit.
"""
uastc_flags: int = KtxPackUastcFlagBits.FASTEST
"""
A set of KtxPackUastcFlagBits controlling UASTC encoding.
The most important value is the level given in the
least-significant 4 bits which selects a speed vs quality tradeoff.
"""
uastc_rdo: bool = False
"""Enable Rate Distortion Optimization (RDO) post-processing."""
uastc_rdo_quality_scalar: float = 0.
"""
UASTC RDO quality scalar (lambda).
Lower values yield higher quality/larger LZ compressed files, higher
values yield lower quality/smaller LZ compressed files. A good range to
try is [.2,4]. Full range is [.001,50.0]. Default is 1.0.
"""
uastc_rdo_dict_size: int = 0
"""
UASTC RDO dictionary size in bytes. Default is 4096. Lower
values=faster, but give less compression. Range is [64,65536].
"""
uastc_rdo_max_smooth_block_error_scale: float = 10.
"""
UASTC RDO max smooth block error scale. Range is [1,300].
Default is 10.0, 1.0 is disabled. Larger values suppress more
artifacts (and allocate more bits) on smooth blocks.
"""
uastc_rdo_max_smooth_block_std_dev: float = 18.
"""
UASTC RDO max smooth block standard deviation. Range is
[.01,65536.0]. Default is 18.0. Larger values expand the range of
blocks considered smooth.
"""
uastc_rdo_dont_favor_simpler_modes: bool = False
"""Do not favor simpler UASTC modes in RDO mode."""
uastc_rdo_no_multithreading: bool = False
"""Disable RDO multithreading (slightly higher compression, deterministic)."""
@@ -0,0 +1,91 @@
# Copyright (c) 2023, Shukant Pal and Contributors
# SPDX-License-Identifier: Apache-2.0
from enum import IntEnum
class KtxErrorCode(IntEnum):
"""Error codes thrown by library functions."""
SUCCESS = 0
"""Operation was successful."""
FILE_DATA_ERROR = 1
"""The data in the file is inconsistent with the spec."""
FILE_ISPIPE = 2
"""The file is a pipe or named pipe."""
FILE_OPEN_FAILED = 3
"""The target file could not be opened."""
FILE_OVERFLOW = 4
"""The operation would exceed the max file size."""
FILE_READ_ERROR = 5
"""An error occurred while reading from the file."""
FILE_SEEK_ERROR = 6
"""An error occurred while seeking in the file."""
FILE_UNEXPECTED_EOF = 7
"""File does not have enough data to satisfy request."""
FILE_WRITE_ERROR = 8
"""An error occurred while writing to the file."""
GL_ERROR = 9
"""GL operations resulted in an error."""
INVALID_OPERATION = 10
"""The operation is not allowed in the current state."""
INVALID_VALUE = 11
"""A parameter value was not valid."""
NOT_FOUND = 12
"""Requested key was not found"""
OUT_OF_MEMORY = 13
"""Not enough memory to complete the operation."""
TRANSCODE_FAILED = 14
"""Transcoding of block compressed texture failed."""
UNKNOWN_FILE_FORMAT = 15
"""The file not a KTX file."""
UNSUPPORTED_TEXTURE_TYPE = 16
"""The KTX file specifies an unsupported texture type."""
UNSUPPORTED_FEATURE = 17
"""Feature not included in in-use library or not yet implemented."""
LIBRARY_NOT_LINKED = 18
"""Library dependency (OpenGL or Vulkan) not linked into application."""
DECOMPRESS_LENGTH_ERROR = 19
"""Decompressed byte count does not match expected byte size."""
DECOMPRESS_CHECKSUM_ERROR = 20
"""Checksum mismatch when decompressing."""
ERROR_MAX_ENUM = LIBRARY_NOT_LINKED
"""For safety checks."""
class KtxError(Exception):
"""Error thrown when native operation does not succeed."""
invocation: str
"""The C library function called."""
code: KtxErrorCode
"""The error code returned by libktx."""
def __init__(self, invocation: str, code: KtxErrorCode):
self.invocation = invocation
self.code = code
def __str__(self):
return str(self.invocation) + " returned with " + str(self.code)
@@ -0,0 +1,69 @@
# Copyright (c) 2023, Shukant Pal and Contributors
# SPDX-License-Identifier: Apache-2.0
from ctypes import c_buffer
from .ktx_error_code import KtxErrorCode, KtxError
from pyktx.native import ffi, lib
from typing import Dict, Optional
class KtxHashList:
"""Opaque handle to a ktxHashList implemented in C."""
def __init__(self, ptr):
self._ptr = ptr
def add_kv_pair(self, key: str, value: bytes) -> None:
"""Add a key value pair to a hash list."""
error = lib.ktxHashList_AddKVPair(self._ptr, key.encode('ascii'), len(value), value)
if int(error) != KtxErrorCode.SUCCESS:
raise KtxError('ktxHashList_AddKVPair', KtxErrorCode(error))
def delete_kv_pair(self, key: str) -> None:
"""Delete a key value pair in a hash list."""
error = lib.ktxHashList_DeleteKVPair(self._ptr, key.encode('ascii'))
if int(error) != KtxErrorCode.SUCCESS:
raise KtxError('ktxHashList_DeleteKVPair', KtxErrorCode(error))
def find_value(self, key: str) -> Optional[c_buffer]:
"""Looks up a key in a hash list and returns the value."""
data = lib.PY_ktxHashList_FindValue(self._ptr, key.encode('ascii'))
if data.error == KtxErrorCode.NOT_FOUND:
return None
if data.error != KtxErrorCode.SUCCESS:
raise KtxError('ktxHashList_FindValue', KtxErrorCode(data.error))
return ffi.buffer(data.bytes, data.size)
def copy(self) -> Dict[str, bytes]:
"""Copy the hash list into a dict. This is recommended if you reading the hash list."""
kv_data = {}
entry = lib.PY_ktxHashList_get_listHead(self._ptr)
while entry != ffi.NULL:
key_data = lib.PY_ktxHashListEntry_GetKey(entry)
value_data = lib.PY_ktxHashListEntry_GetValue(entry)
if int(key_data.error) != KtxErrorCode.SUCCESS:
raise KtxError('ktxHashListEntry_GetKey', KtxErrorCode(key_data.error))
if int(value_data.error) != KtxErrorCode.SUCCESS:
raise KtxError('ktxHashListEntry_GetValue', KtxErrorCode(value_data.error))
key: str = ffi.buffer(key_data.bytes, key_data.size)[:].decode(encoding='utf-8')
value = ffi.buffer(value_data.bytes, value_data.size)[:]
if key.endswith('\x00'):
key = key[:-1]
kv_data[key] = value
entry = lib.ktxHashList_Next(entry)
return kv_data
@@ -0,0 +1,85 @@
# Copyright (c) 2023, Shukant Pal and Contributors
# SPDX-License-Identifier: Apache-2.0
from enum import IntEnum
class KtxPackAstcBlockDimension(IntEnum):
"""Options specifiying ASTC encoding block dimensions."""
D4x4 = 0
"""8.00 bpp"""
D5x4 = 1
"""6.40 bpp"""
D5x5 = 2
"""5.12 bpp"""
D6x5 = 3
"""4.27 bpp"""
D6x6 = 4
"""3.56 bpp"""
D8x5 = 5
"""3.20 bpp"""
D8x6 = 6
"""2.67 bpp"""
D10x5 = 7
"""2.56 bpp"""
D10x6 = 8
"""2.13 bpp"""
D8x8 = 9
"""2.00 bpp"""
D10x8 = 10
"""1.60 bpp"""
D10x10 = 11
"""1.28 bpp"""
D12x10 = 12
"""1.07 bpp"""
D12x12 = 13
"""0.89 bpp"""
D3x3x3 = 14
"""4.74 bpp"""
D4x3x3 = 15
"""3.56 bpp"""
D4x4x3 = 16
"""2.67 bpp"""
D4x4x4 = 17
"""2.00 bpp"""
D5x4x4 = 18
"""1.60 bpp"""
D5x5x4 = 19
"""1.28 bpp"""
D5x5x5 = 20
"""1.02 bpp"""
D6x5x5 = 21
"""0.85 bpp"""
D6x6x5 = 22
"""0.71 bpp"""
D6x6x6 = 23
"""0.59 bpp"""
DMAX = D6x6x6
"Maximum supported blocks."
@@ -0,0 +1,17 @@
# Copyright (c) 2023, Shukant Pal and Contributors
# SPDX-License-Identifier: Apache-2.0
from enum import IntEnum
class KtxPackAstcEncoderMode(IntEnum):
"""
Options specifying ASTC encoder profile mode.
This and function is used later to derive the profile.
"""
DEFAULT = 0
LDR = 1
HDR = 2
MAX = HDR
@@ -0,0 +1,26 @@
# Copyright (c) 2023, Shukant Pal and Contributors
# SPDX-License-Identifier: Apache-2.0
from enum import IntEnum
class KtxPackAstcQualityLevels(IntEnum):
"""Options specifiying ASTC encoding quality levels."""
FASTEST = 0
"""Fastest compression."""
FAST = 10
"""Fast compression."""
MEDIUM = 60
"""Medium compression."""
THOROUGH = 98
"""Slower compression."""
EXHAUSTIVE = 100
"""Very slow compression."""
MAX = EXHAUSTIVE
"""Maximum supported quality level."""
@@ -0,0 +1,44 @@
# Copyright (c) 2023, Shukant Pal and Contributors
# SPDX-License-Identifier: Apache-2.0
from enum import IntEnum
class KtxPackUastcFlagBits(IntEnum):
"""Flags specifiying UASTC encoding options."""
FASTEST = 0
"""Fastest compression. 43.45dB."""
FASTER = 1
"""Faster compression. 46.49dB."""
DEFAULT = 2
"""Default compression. 47.47dB."""
SLOWER = 3
"""Slower compression. 48.01dB."""
VERY_SLOW = 4
"""Very slow compression. 48.24dB."""
MAX_LEVEL = VERY_SLOW
"""Maximum supported quality level."""
LEVEL_MASK = 0xF
"""Mask to extract the level from the other bits."""
FAVOR_UASTC_ERROR = 8
"""Optimize for lowest UASTC error."""
FAVOR_BC7_ERROR = 16
"""Optimize for lowest BC7 error."""
ETC1_FASTER_HINTS = 64
"""Optimize for faster transcoding to ETC1."""
ETC1_FASTEST_HINTS = 128
"""Optimize for fastest transcoding to ETC1."""
ETC1_DISABLE_FLIP_AND_INDIVIDUAL = 256
"""Not documented in BasisU code."""
@@ -0,0 +1,20 @@
# Copyright (c) 2023, Shukant Pal and Contributors
# SPDX-License-Identifier: Apache-2.0
from enum import IntEnum
class KtxSupercmpScheme(IntEnum):
"""Enumerators identifying the supercompression scheme."""
NONE = 0
"""No supercompression."""
BASIS_LZ = 1
"""Basis LZ supercompression."""
ZSTD = 2
"""ZStd supercompression."""
ZLIB = 3
"""ZLIB supercompression."""
@@ -0,0 +1,104 @@
/*
* Copyright (c) 2023, Shukant Pal and Contributors
* SPDX-License-Identifier: Apache-2.0
*/
#include <ktx.h>
#include "ktx_texture.h"
#include <stddef.h>
#include <stdio.h>
ktxTextureMixed PY_ktxTexture_CreateFromNamedFile(const char* const filename, ktx_uint32_t createFlags)
{
ktxTextureMixed mixed;
mixed.error = ktxTexture_CreateFromNamedFile(filename, createFlags, &mixed.texture);
return mixed;
}
ktxWriteToMemory PY_ktxTexture_WriteToMemory(ktxTexture *texture)
{
ktx_uint8_t *ppDstBytes = NULL;
ktx_size_t pSize = 0;
KTX_error_code error = ktxTexture_WriteToMemory(texture, &ppDstBytes, &pSize);
return (ktxWriteToMemory) {
.bytes = ppDstBytes,
.size = pSize,
.error = error
};
}
ktxImageOffset PY_ktxTexture_GetImageOffset(ktxTexture *texture,
ktx_uint32_t level,
ktx_uint32_t layer,
ktx_uint32_t faceSlice)
{
ktxImageOffset result;
result.error = ktxTexture_GetImageOffset(texture, level, layer, faceSlice, &result.offset);
return result;
}
ktxWriteToMemory PY_ktxHashList_FindValue(ktxHashList *list, const char *key)
{
unsigned int valueLen = 0;
void *pValue = NULL;
KTX_error_code err = ktxHashList_FindValue(list, key, &valueLen, &pValue);
return (ktxWriteToMemory) {
.bytes = pValue,
.size = valueLen,
.error = err
};
}
ktxWriteToMemory PY_ktxHashListEntry_GetKey(ktxHashListEntry *entry)
{
unsigned int keyLen = 0;
char *pKey = NULL;
KTX_error_code err = ktxHashListEntry_GetKey(entry, &keyLen, &pKey);
return (ktxWriteToMemory) {
.bytes = pKey,
.size = keyLen,
.error = err
};
}
ktxWriteToMemory PY_ktxHashListEntry_GetValue(ktxHashListEntry *entry)
{
unsigned int valueLen = 0;
void *pValue = NULL;
KTX_error_code err = ktxHashListEntry_GetValue(entry, &valueLen, &pValue);
return (ktxWriteToMemory) {
.bytes = pValue,
.size = valueLen,
.error = err
};
}
KTX_IMPL(class_id, classId);
KTX_IMPL(ktx_bool_t, isArray);
KTX_IMPL(ktx_bool_t, isCompressed);
KTX_IMPL(ktx_bool_t, isCubemap);
KTX_IMPL(ktx_bool_t, generateMipmaps);
KTX_IMPL(ktx_uint32_t, baseWidth);
KTX_IMPL(ktx_uint32_t, baseHeight);
KTX_IMPL(ktx_uint32_t, baseDepth);
KTX_IMPL(ktx_uint32_t, numDimensions);
KTX_IMPL(ktx_uint32_t, numLevels);
KTX_IMPL(ktx_uint32_t, numFaces);
KTX_IMPL(ktx_uint32_t, kvDataLen);
KTX_IMPL(ktx_uint8_t *, kvData);
ktxHashList *PY_ktxTexture_get_kvDataHead(ktxTexture *texture)
{
return &texture->kvDataHead;
}
ktxHashListEntry *PY_ktxHashList_get_listHead(ktxHashList *list)
{
return *list;
}
@@ -0,0 +1,62 @@
/*
* Copyright (c) 2023, Shukant Pal and Contributors
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef KTX_TEXTURE_H_9E005417467F4F98A33ACF592FE1D6FE
#define KTX_TEXTURE_H_9E005417467F4F98A33ACF592FE1D6FE
#include <ktx.h>
typedef struct {
KTX_error_code error;
ktxTexture *texture;
} ktxTextureMixed;
typedef struct {
void *bytes;
ktx_size_t size;
KTX_error_code error;
} ktxWriteToMemory;
typedef struct {
size_t offset;
int error;
} ktxImageOffset;
ktxTextureMixed PY_ktxTexture_CreateFromNamedFile(const char* const filename, ktx_uint32_t create_flags);
ktxWriteToMemory PY_ktxTexture_WriteToMemory(ktxTexture *);
ktxImageOffset PY_ktxTexture_GetImageOffset(ktxTexture *,
ktx_uint32_t level,
ktx_uint32_t layer,
ktx_uint32_t faceSlice);
ktxWriteToMemory PY_ktxHashList_FindValue(ktxHashList *, const char *key);
ktxWriteToMemory PY_ktxHashListEntry_GetKey(ktxHashListEntry *);
ktxWriteToMemory PY_ktxHashListEntry_GetValue(ktxHashListEntry *);
#define KTX_GETTER(type, prop) \
type PY_ktxTexture_get_##prop(ktxTexture *texture)
KTX_GETTER(class_id, classId);
KTX_GETTER(ktx_bool_t, isArray);
KTX_GETTER(ktx_bool_t, isCompressed);
KTX_GETTER(ktx_bool_t, isCubemap);
KTX_GETTER(ktx_bool_t, generateMipmaps);
KTX_GETTER(ktx_uint32_t, baseWidth);
KTX_GETTER(ktx_uint32_t, baseHeight);
KTX_GETTER(ktx_uint32_t, baseDepth);
KTX_GETTER(ktx_uint32_t, numDimensions);
KTX_GETTER(ktx_uint32_t, numLevels);
KTX_GETTER(ktx_uint32_t, numFaces);
KTX_GETTER(ktx_uint32_t, kvDataLen);
KTX_GETTER(ktx_uint8_t *, kvData);
KTX_GETTER(ktxHashList *, kvDataHead);
ktxHashListEntry *PY_ktxHashList_get_listHead(ktxHashList *list);
#define KTX_IMPL(type, prop) \
KTX_GETTER(type, prop) \
{ \
return texture->prop; \
}
#endif
@@ -0,0 +1,230 @@
# Copyright (c) 2023, Shukant Pal and Contributors
# SPDX-License-Identifier: Apache-2.0
from ctypes import *
from .ktx_error_code import KtxErrorCode, KtxError
from .ktx_hash_list import KtxHashList
from pyktx.native import ffi, lib
from typing import Dict, Literal, Optional
class KtxVersionMismatchError(Exception):
"""Error thrown when reading a file with wrong KTX version."""
pass
class KtxTexture:
"""
Base class representing a texture.
ktxTextures should be created only by one of the static factory methods,
and these fields should be considered read-only.
"""
def __init__(self, ptr: c_uint64):
self._ptr = ptr
self.__kv_data_head = KtxHashList(lib.PY_ktxTexture_get_kvDataHead(ptr))
def __del__(self):
lib.free(self._ptr)
self._ptr = ffi.NULL
@property
def class_id(self) -> int:
"""Identify the class type. 1 for KtxTexture1, 2 for KtxTexture2."""
return lib.PY_ktxTexture_get_classId(self._ptr)
@property
def is_array(self) -> bool:
"""true if the texture is an array texture, i.e, a GL_TEXTURE_*_ARRAY target is to be used."""
return lib.PY_ktxTexture_get_isArray(self._ptr)
@property
def is_compressed(self) -> bool:
"""If the texture's format is a block compressed format."""
return lib.PY_ktxTexture_get_isCompressed(self._ptr)
@property
def is_cubemap(self) -> bool:
"""
True if the texture is a cubemap or cubemap array.
"""
return lib.PY_ktxTexture_get_isCubemap(self._ptr)
@property
def generate_mipmaps(self) -> bool:
"""If mipmaps should be generated for the texture when uploading to graphics APIs."""
return lib.PY_ktxTexture_get_generateMipmaps(self._ptr)
@property
def base_width(self) -> int:
"""Width of the texture's base level."""
return lib.PY_ktxTexture_get_baseWidth(self._ptr)
@property
def base_height(self) -> int:
"""Height of the texture's base level."""
return lib.PY_ktxTexture_get_baseHeight(self._ptr)
@property
def base_depth(self) -> int:
"""Depth of the texture's base level."""
return lib.PY_ktxTexture_get_baseDepth(self._ptr)
@property
def num_dimensions(self) -> int:
"""Number of dimensions in the texture: 1, 2 or 3."""
return lib.PY_ktxTexture_get_numDimensions(self._ptr)
@property
def num_levels(self) -> int:
"""Number of mip levels in the texture."""
return lib.PY_ktxTexture_get_numLevels(self._ptr)
@property
def num_faces(self) -> int:
"""Number of faces: 6 for cube maps, 1 otherwise."""
return lib.PY_ktxTexture_get_numFaces(self._ptr)
@property
def element_size(self) -> int:
"""The element size of the texture's images."""
return lib.ktxTexture_GetElementSize(self._ptr)
@property
def kv_data_raw(self) -> Optional[c_buffer]:
"""
The raw KV data buffer.
This is available only if RAW_KVDATA_BIT was used in create-flag bits.
"""
buffer = lib.PY_ktxTexture_get_kvData(self._ptr)
if buffer == ffi.NULL:
return None
return ffi.buffer(buffer, lib.PY_ktxTexture_get_kvDataLen(self._ptr))
@property
def kv_data(self) -> KtxHashList:
"""
The metadata stored in the texture as a hash-list.
This is not available if SKIP_KVDATA_BIT was used in the create-flag bits.
"""
return self.__kv_data_head
@property
def data_size(self) -> int:
"""The total size of the texture image data in bytes."""
return lib.ktxTexture_GetDataSize(self._ptr)
@property
def data_size_uncompressed(self) -> int:
"""Byte length of the texture's uncompressed image data."""
return lib.ktxTexture_GetDataSizeUncompressed(self._ptr)
def row_pitch(self, level: int) -> int:
"""
Return pitch between rows of a texture image level in bytes.
For uncompressed textures the pitch is the number of bytes between
rows of texels. For compressed textures it is the number of bytes
between rows of blocks. The value is padded to GL_UNPACK_ALIGNMENT,
if necessary. For all currently known compressed formats padding will
not be necessary.
"""
return lib.ktxTexture_GetRowPitch(self._ptr, level)
def image_size(self, level: int) -> int:
"""
Calculate & return the size in bytes of an image at the specified mip level.
For arrays, this is the size of layer, for cubemaps, the size of a face and
for 3D textures, the size of a depth slice.
The size reflects the padding of each row to KTX_GL_UNPACK_ALIGNMENT.
"""
return lib.ktxTexture_GetImageSize(self._ptr, level)
def image_offset(self, level: int, layer: int, face_slice: int) -> int:
"""
Find the offset of an image within a ktxTexture's image data.
As there is no such thing as a 3D cubemap we make the 3rd location parameter
do double duty.
"""
data = lib.PY_ktxTexture_GetImageOffset(self._ptr, level, layer, face_slice)
if int(data.error) != KtxErrorCode.SUCCESS:
raise KtxError('ktxTexture_GetImageOffset', KtxErrorCode(data.error))
return data.offset
def data(self) -> bytes:
"""Return a buffer holding the texture image data."""
return ffi.buffer(lib.ktxTexture_GetData(self._ptr), self.data_size)
def set_image_from_memory(self, level: int, layer: int, face_slice: int, data: bytes):
"""
Set image for level, layer, faceSlice from an image in memory.
Uncompressed images in memory are expected to have their rows tightly packed
as is the norm for most image file formats. The copied image is padded as
necessary to achieve the KTX-specified row alignment. No padding is done if the
ktxTexture's is_compressed field is true. Level, layer, face_slice rather than offset
are specified to enable some validation.
"""
error = KtxErrorCode(lib.ktxTexture_SetImageFromMemory(self._ptr, level, layer, face_slice, data, len(data)))
if int(error) != KtxErrorCode.SUCCESS:
raise KtxError('ktxTexture_SetImageFromMemory', KtxErrorCode(int(error)))
def write_to_named_file(self, dst_name: str) -> None:
"""Save this texture to a named file in KTX format."""
error = KtxErrorCode(lib.ktxTexture_WriteToNamedFile(self._ptr, dst_name.encode('ascii')))
if int(error) != KtxErrorCode.SUCCESS:
raise KtxError('ktxTexture_WriteToNamedFile', KtxErrorCode(error))
def write_to_native_memory(self) -> Dict[Literal["bytes", "size"], int]:
"""Write this KTX file to block of memory in KTX format. Recommended to use write_to_memory instead."""
data = lib.PY_ktxTexture_WriteToMemory(self._ptr)
if int(data.error) != KtxErrorCode.SUCCESS:
raise KtxError('ktxTexture_WriteToMemory', KtxErrorCode(data.error))
return {"bytes": data.bytes, "size": data.size}
def write_to_memory(self) -> bytes:
"""Write this KTX file to a buffer in KTX format."""
data = self.write_to_native_memory()
native_buffer = ffi.buffer(data.get('bytes'), data.get('size'))
buffer = native_buffer[:]
lib.free(data.get('bytes'))
return buffer
@@ -0,0 +1,53 @@
/*
* Copyright (c) 2023, Shukant Pal and Contributors
* SPDX-License-Identifier: Apache-2.0
*/
#include <ktx.h>
#include "ktx_texture1.h"
#include <stddef.h>
ktxTextureMixed PY_ktxTexture1_Create(ktx_uint32_t glInternalformat,
ktx_uint32_t vkFormat,
ktx_uint32_t *pDfd,
ktx_uint32_t baseWidth,
ktx_uint32_t baseHeight,
ktx_uint32_t baseDepth,
ktx_uint32_t numDimensions,
ktx_uint32_t numLevels,
ktx_uint32_t numLayers,
ktx_uint32_t numFaces,
ktx_bool_t isArray,
ktx_bool_t generateMipmaps,
ktxTextureCreateStorageEnum storageAllocation)
{
ktxTextureCreateInfo createInfo = {
glInternalformat,
vkFormat,
pDfd,
baseWidth,
baseHeight,
baseDepth,
numDimensions,
numLevels,
numLayers,
numFaces,
isArray,
generateMipmaps
};
ktxTexture1* newTex = NULL;
KTX_error_code err = ktxTexture1_Create(&createInfo,
storageAllocation,
&newTex);
return (ktxTextureMixed) {
err,
(ktxTexture*) newTex
};
}
KTX1_IMPL(ktx_uint32_t, glFormat)
KTX1_IMPL(ktx_uint32_t, glInternalformat)
KTX1_IMPL(ktx_uint32_t, glBaseInternalformat)
KTX1_IMPL(ktx_uint32_t, glType)
@@ -0,0 +1,40 @@
/*
* Copyright (c) 2023, Shukant Pal and Contributors
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef KTX_TEXTURE1_H_9E005417467F4F98A33ACF592FE1D6FE
#define KTX_TEXTURE1_H_9E005417467F4F98A33ACF592FE1D6FE
#include <ktx.h>
#include "ktx_texture.h"
ktxTextureMixed PY_ktxTexture1_Create(ktx_uint32_t glInternalFormat,
ktx_uint32_t vkFormat,
ktx_uint32_t *pDfd,
ktx_uint32_t baseWidth,
ktx_uint32_t baseHeight,
ktx_uint32_t baseDepth,
ktx_uint32_t numDimensions,
ktx_uint32_t numLevels,
ktx_uint32_t numLayers,
ktx_uint32_t numFaces,
ktx_bool_t isArray,
ktx_bool_t generateMipmaps,
ktxTextureCreateStorageEnum storageAllocation);
#define KTX1_GETTER(type, prop) \
type PY_ktxTexture1_get_##prop(ktxTexture1 *texture)
KTX1_GETTER(ktx_uint32_t, glFormat);
KTX1_GETTER(ktx_uint32_t, glInternalformat);
KTX1_GETTER(ktx_uint32_t, glBaseInternalformat);
KTX1_GETTER(ktx_uint32_t, glType);
#define KTX1_IMPL(type, prop) \
KTX1_GETTER(type, prop) \
{ \
return texture->prop; \
}
#endif
@@ -0,0 +1,91 @@
# Copyright (c) 2023, Shukant Pal and Contributors
# SPDX-License-Identifier: Apache-2.0
from .gl_internalformat import GlInternalformat
from .ktx_error_code import KtxErrorCode, KtxError
from .ktx_texture import KtxTexture, KtxVersionMismatchError
from .ktx_texture_create_flag_bits import KtxTextureCreateFlagBits
from .ktx_texture_create_info import KtxTextureCreateInfo
from .ktx_texture_create_storage import KtxTextureCreateStorage
from pyktx.native import ffi, lib
class KtxTexture1(KtxTexture):
"""Class representing a KTX version 1 format texture."""
@staticmethod
def create(create_info: KtxTextureCreateInfo, storage_allocation: KtxTextureCreateStorage) -> 'KtxTexture1':
"""Create a new empty KtxTexture1."""
result = lib.PY_ktxTexture1_Create(create_info.gl_internal_format.value,
create_info.vk_format.value,
ffi.NULL,
create_info.base_width,
create_info.base_height,
create_info.base_depth,
create_info.num_dimensions,
create_info.num_levels,
create_info.num_layers,
create_info.num_faces,
create_info.is_array,
create_info.generate_mipmaps,
storage_allocation.value)
if int(result.error) != KtxErrorCode.SUCCESS:
raise KtxError('ktxTexture1_Create', KtxErrorCode(result.error))
return KtxTexture1(result.texture)
@staticmethod
def create_from_named_file(filename: str, create_flags: int = KtxTextureCreateFlagBits.NO_FLAGS) -> 'KtxTexture1':
"""Create a KtxTexture1 from a named KTX file according to the file contents."""
result = lib.PY_ktxTexture_CreateFromNamedFile(filename.encode("ascii"), int(create_flags))
if int(result.error) != KtxErrorCode.SUCCESS:
raise KtxError('ktxTexture1_CreateFromNamedFile', KtxErrorCode(result.error))
texture = KtxTexture1(result.texture)
if texture.class_id != 1:
raise KtxVersionMismatchError('The provided file ' + filename + ' is not a KTX1 file')
return texture
@property
def gl_format(self) -> int:
"""
Format of the texture data, e.g. GL_RGB.
You can find all OpenGL formats here: <https://registry.khronos.org/OpenGL/api/GL/glcorearb.h>
"""
return lib.PY_ktxTexture1_get_glFormat(self._ptr)
@property
def gl_internalformat(self) -> int:
"""
Internal format of the texture data. See GlInternalformat.
You can find all OpenGL internal formats here: <https://registry.khronos.org/OpenGL/api/GL/glext.h>
"""
return GlInternalformat(lib.PY_ktxTexture1_get_glInternalformat(self._ptr))
@property
def gl_baseinternalformat(self) -> int:
"""
Base format of the texture data, e.g., GL_RGB.
You can find all OpenGL formats here: <https://registry.khronos.org/OpenGL/api/GL/glcorearb.h>
"""
return lib.PY_ktxTexture1_get_glBaseInternalformat(self._ptr)
@property
def gl_type(self) -> int:
"""
Type of the texture data, e.g, GL_UNSIGNED_BYTE.
You can find all OpenGL data types here: <https://registry.khronos.org/OpenGL/api/GL/glcorearb.h>
"""
return lib.PY_ktxTexture1_get_glType(self._ptr)
@@ -0,0 +1,145 @@
/*
* Copyright (c) 2023, Shukant Pal and Contributors
* SPDX-License-Identifier: Apache-2.0
*/
#include <ktx.h>
#include "ktx_texture2.h"
#include <stddef.h>
ktxTextureMixed PY_ktxTexture2_Create(ktx_uint32_t glInternalformat,
ktx_uint32_t vkFormat,
ktx_uint32_t *pDfd,
ktx_uint32_t baseWidth,
ktx_uint32_t baseHeight,
ktx_uint32_t baseDepth,
ktx_uint32_t numDimensions,
ktx_uint32_t numLevels,
ktx_uint32_t numLayers,
ktx_uint32_t numFaces,
ktx_bool_t isArray,
ktx_bool_t generateMipmaps,
ktxTextureCreateStorageEnum storageAllocation)
{
ktxTextureCreateInfo createInfo = {
glInternalformat,
vkFormat,
pDfd,
baseWidth,
baseHeight,
baseDepth,
numDimensions,
numLevels,
numLayers,
numFaces,
isArray,
generateMipmaps
};
ktxTexture2* newTex = NULL;
KTX_error_code err = ktxTexture2_Create(&createInfo,
storageAllocation,
&newTex);
return (ktxTextureMixed) {
err,
(ktxTexture*) newTex
};
}
KTX_error_code PY_ktxTexture2_CompressAstcEx(ktxTexture2 *texture,
ktx_bool_t verbose,
ktx_uint32_t threadCount,
ktx_uint32_t blockDimension,
ktx_uint32_t mode,
ktx_uint32_t qualityLevel,
ktx_bool_t normalMap,
ktx_bool_t perceptual,
char *inputSwizzle)
{
ktxAstcParams params = {
.structSize = sizeof(ktxAstcParams),
.verbose = verbose,
.threadCount = threadCount,
.blockDimension = blockDimension,
.mode = mode,
.qualityLevel = qualityLevel,
.normalMap = normalMap,
.perceptual = perceptual,
};
params.inputSwizzle[0] = inputSwizzle[0];
params.inputSwizzle[1] = inputSwizzle[1];
params.inputSwizzle[2] = inputSwizzle[2];
params.inputSwizzle[3] = inputSwizzle[3];
KTX_error_code err = ktxTexture2_CompressAstcEx(texture, &params);
return err;
}
KTX_error_code PY_ktxTexture2_CompressBasisEx(ktxTexture2 *texture,
ktx_bool_t uastc,
ktx_bool_t verbose,
ktx_bool_t noSSE,
ktx_uint32_t threadCount,
ktx_uint32_t compressionLevel,
ktx_uint32_t qualityLevel,
ktx_uint32_t maxEndpoints,
float endpointRDOThreshold,
ktx_uint32_t maxSelectors,
float selectorRDOThreshold,
char *inputSwizzle,
ktx_bool_t normalMap,
ktx_bool_t separateRGToRGB_A,
ktx_bool_t preSwizzle,
ktx_bool_t noEndpointRDO,
ktx_bool_t noSelectorRDO,
int uastcFlags,
ktx_bool_t uastcRDO,
float uastcRDOQualityScalar,
ktx_uint32_t uastcRDODictSize,
float uastcRDOMaxSmoothBlockErrorScale,
float uastcRDOMaxSmoothBlockStdDev,
ktx_bool_t uastcRDODontFavorSimplerModes,
ktx_bool_t uastcRDONoMultithreading)
{
ktxBasisParams params = {
.structSize = sizeof(ktxBasisParams),
.uastc = uastc,
.verbose = verbose,
.noSSE = noSSE,
.threadCount = threadCount,
.compressionLevel = compressionLevel,
.qualityLevel = qualityLevel,
.maxEndpoints = maxEndpoints,
.endpointRDOThreshold = endpointRDOThreshold,
.maxSelectors = maxSelectors,
.selectorRDOThreshold = selectorRDOThreshold,
// inputSwizzle skipped here
.normalMap = normalMap,
.separateRGToRGB_A = separateRGToRGB_A,
.preSwizzle = preSwizzle,
.noEndpointRDO = noEndpointRDO,
.noSelectorRDO = noSelectorRDO,
.uastcFlags = uastcFlags,
.uastcRDO = uastcRDO,
.uastcRDOQualityScalar = uastcRDOQualityScalar,
.uastcRDOMaxSmoothBlockErrorScale = uastcRDOMaxSmoothBlockErrorScale,
.uastcRDOMaxSmoothBlockStdDev = uastcRDOMaxSmoothBlockStdDev,
.uastcRDODontFavorSimplerModes = uastcRDODontFavorSimplerModes,
.uastcRDONoMultithreading = uastcRDONoMultithreading
};
params.inputSwizzle[0] = inputSwizzle[0];
params.inputSwizzle[1] = inputSwizzle[1];
params.inputSwizzle[2] = inputSwizzle[2];
params.inputSwizzle[3] = inputSwizzle[3];
KTX_error_code err = ktxTexture2_CompressBasisEx(texture, &params);
return err;
}
KTX2_IMPL(ktx_uint32_t, vkFormat)
KTX2_IMPL(ktx_uint32_t, supercompressionScheme)
@@ -0,0 +1,74 @@
/*
* Copyright (c) 2023, Shukant Pal and Contributors
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef KTX_TEXTURE2_H_9E005417467F4F98A33ACF592FE1D6FE
#define KTX_TEXTURE2_H_9E005417467F4F98A33ACF592FE1D6FE
#include <ktx.h>
#include "ktx_texture.h"
ktxTextureMixed PY_ktxTexture2_Create(ktx_uint32_t glInternalformat,
ktx_uint32_t vkFormat,
ktx_uint32_t *pDfd,
ktx_uint32_t baseWidth,
ktx_uint32_t baseHeight,
ktx_uint32_t baseDepth,
ktx_uint32_t numDimensions,
ktx_uint32_t numLevels,
ktx_uint32_t numLayers,
ktx_uint32_t numFaces,
ktx_bool_t isArray,
ktx_bool_t generateMipmaps,
ktxTextureCreateStorageEnum storageAllocation);
KTX_error_code PY_ktxTexture2_CompressAstcEx(ktxTexture2 *texture,
ktx_bool_t verbose,
ktx_uint32_t threadCount,
ktx_uint32_t blockDimension,
ktx_uint32_t mode,
ktx_uint32_t quality,
ktx_bool_t normalMap,
ktx_bool_t perceptual,
char *inputSwizzle);
KTX_error_code PY_ktxTexture2_CompressBasisEx(ktxTexture2 *texture,
ktx_bool_t uastc,
ktx_bool_t verbose,
ktx_bool_t noSSE,
ktx_uint32_t threadCount,
ktx_uint32_t compressionLevel,
ktx_uint32_t qualityLevel,
ktx_uint32_t maxEndpoints,
float endpointRDOThreshold,
ktx_uint32_t maxSelectors,
float selectorRDOThreshold,
char *inputSwizzle,
ktx_bool_t normalMap,
ktx_bool_t separateRGToRGB_A,
ktx_bool_t preSwizzle,
ktx_bool_t noEndpointRDO,
ktx_bool_t noSelectorRDO,
int uastcFlags,
ktx_bool_t uastcRDO,
float uastcRDOQualityScalar,
ktx_uint32_t uastcRDODictSize,
float uastcRDOMaxSmoothBlockErrorScale,
float uastcRDOMaxSmoothBlockStdDev,
ktx_bool_t uastcRDODontFavorSimplerModes,
ktx_bool_t uastcRDONoMultithreading);
#define KTX2_GETTER(type, prop) \
type PY_ktxTexture2_get_##prop(ktxTexture2 *texture)
KTX2_GETTER(ktx_uint32_t, vkFormat);
KTX2_GETTER(ktx_uint32_t, supercompressionScheme);
#define KTX2_IMPL(type, prop) \
KTX2_GETTER(type, prop) \
{ \
return texture->prop; \
}
#endif
@@ -0,0 +1,212 @@
# Copyright (c) 2023, Shukant Pal and Contributors
# SPDX-License-Identifier: Apache-2.0
from .ktx_astc_params import KtxAstcParams
from .ktx_basis_params import KtxBasisParams
from .ktx_error_code import KtxErrorCode, KtxError
from .ktx_supercmp_scheme import KtxSupercmpScheme
from .ktx_texture import KtxTexture, KtxVersionMismatchError
from .ktx_texture_create_flag_bits import KtxTextureCreateFlagBits
from .ktx_texture_create_info import KtxTextureCreateInfo
from .ktx_texture_create_storage import KtxTextureCreateStorage
from .ktx_transcode_fmt import KtxTranscodeFmt
from pyktx.native import ffi, lib
from typing import Union
from .vk_format import VkFormat
class KtxTexture2(KtxTexture):
"""Class representing a KTX version 2 format texture."""
@staticmethod
def create(create_info: KtxTextureCreateInfo, storage_allocation: KtxTextureCreateStorage) -> 'KtxTexture2':
"""Create a new empty KtxTexture2."""
result = lib.PY_ktxTexture2_Create(0,
create_info.vk_format.value,
ffi.NULL,
create_info.base_width,
create_info.base_height,
create_info.base_depth,
create_info.num_dimensions,
create_info.num_levels,
create_info.num_layers,
create_info.num_faces,
create_info.is_array,
create_info.generate_mipmaps,
storage_allocation.value)
if int(result.error) != KtxErrorCode.SUCCESS:
raise KtxError('ktxTexture2_Create', KtxErrorCode(result.error))
return KtxTexture2(result.texture)
@staticmethod
def create_from_named_file(filename: str,
create_flags: int = KtxTextureCreateFlagBits.LOAD_IMAGE_DATA_BIT) -> 'KtxTexture2':
"""Create a KtxTexture2 from a named KTX file."""
result = lib.PY_ktxTexture_CreateFromNamedFile(filename.encode("ascii"), int(create_flags))
if int(result.error) != KtxErrorCode.SUCCESS:
raise KtxError('ktxTexture2_CreateFromNamedFile', KtxErrorCode(result.error))
texture = KtxTexture2(result.texture)
if texture.class_id != 2:
raise KtxVersionMismatchError('The provided file ' + filename + ' is not a KTX2 file')
return texture
@property
def vk_format(self) -> VkFormat:
"""VkFormat for texture."""
return VkFormat(lib.PY_ktxTexture2_get_vkFormat(self._ptr))
@property
def supercompression_scheme(self) -> KtxSupercmpScheme:
"""The supercompression scheme used to compress the texture data."""
return KtxSupercmpScheme(lib.PY_ktxTexture2_get_supercompressionScheme(self._ptr))
@property
def oetf(self) -> int:
"""The opto-electrical transfer function of the images."""
return lib.ktxTexture2_GetOETF(self._ptr)
@property
def premultipled_alpha(self) -> bool:
"""Whether the RGB components have been premultiplied by the alpha component."""
return lib.ktxTexture2_GetPremultipliedAlpha(self._ptr)
@property
def needs_transcoding(self) -> bool:
"""If the images are in a transcodable format."""
return lib.ktxTexture2_NeedsTranscoding(self._ptr)
def compress_astc(self, params: Union[int, KtxAstcParams]) -> None:
"""
Encode and compress a ktx texture with uncompressed images to ASTC.
The images are either encoded to ASTC block-compressed format. The
encoded images replace the original images and the texture's fields
including the dfd are modified to reflect the new state.
Such textures can be directly uploaded to a GPU via a graphics API.
"""
if isinstance(params, int):
quality = params
params = KtxAstcParams()
params.quality_level = quality
error = lib.PY_ktxTexture2_CompressAstcEx(self._ptr,
params.verbose,
params.thread_count,
int(params.block_dimension),
int(params.mode),
params.quality_level,
params.normal_map,
params.perceptual,
params.input_swizzle)
if int(error) != KtxErrorCode.SUCCESS:
raise KtxError('ktxTexture2_compressAstcEx', KtxErrorCode(error))
def compress_basis(self, params: Union[int, KtxBasisParams]) -> None:
"""
Supercompress a KTX2 texture with uncompressed images.
The images are either encoded to ETC1S block-compressed format and supercompressed
with Basis LZ or they are encoded to UASTC block-compressed format. UASTC format is
selected by setting the uastc field of params to true. The encoded images replace
the original images and the texture's fields including the DFD are modified to reflect
the new state. Such textures must be transcoded to a desired target block compressed
format before they can be uploaded to a GPU via a graphics API.
"""
if isinstance(params, int):
quality = params
params = KtxBasisParams()
params.quality_level = quality
error = lib.PY_ktxTexture2_CompressBasisEx(self._ptr,
params.uastc,
params.verbose,
params.no_sse,
params.thread_count,
params.compression_level,
params.quality_level,
params.max_endpoints,
params.endpoint_rdo_threshold,
params.max_selectors,
params.selector_rdo_threshold,
params.input_swizzle,
params.normal_map,
params.pre_swizzle,
params.separate_rg_to_rgb_a,
params.no_endpoint_rdo,
params.no_selector_rdo,
params.uastc_flags,
params.uastc_rdo,
params.uastc_rdo_quality_scalar,
params.uastc_rdo_dict_size,
params.uastc_rdo_max_smooth_block_error_scale,
params.uastc_rdo_max_smooth_block_std_dev,
params.uastc_rdo_dont_favor_simpler_modes,
params.uastc_rdo_no_multithreading)
if int(error) != KtxErrorCode.SUCCESS:
raise KtxError('ktxTexture2_CompressBasisEx', KtxErrorCode(error))
def deflate_zstd(self, compression_level: int) -> None:
"""
Deflate the data in a ktxTexture2 object using Zstandard.
The texture's level_index, data_size, dfd and supercompression_scheme will all
be updated after successful deflation to reflect the deflated data.
"""
if not 1 <= compression_level <= 22:
raise ValueError("compression_level must be between 1 and 22")
error = lib.ktxTexture2_DeflateZstd(self._ptr, compression_level)
if int(error) != KtxErrorCode.SUCCESS:
raise KtxError('ktx2_DeflateZstd', KtxErrorCode(error))
def transcode_basis(self, output_format: KtxTranscodeFmt, transcode_flags: int = 0) -> None:
"""
Transcode a KTX2 texture with BasisLZ/ETC1S or UASTC images.
If the texture contains BasisLZ supercompressed images, inflates them from back to
ETC1S then transcodes them to the specified block-compressed format. If the texture
contains UASTC images, inflates them, if they have been supercompressed with zstd, then
transcodes then to the specified format, The transcoded images replace the original images
and the texture's fields including the dfd are modified to reflect the new format.
These types of textures must be transcoded to a desired target block-compressed format
before they can be uploaded to a GPU via a graphics API.
The following block compressed transcode targets (KtxTranscodeFmt) are available: ETC1_RGB,
ETC2_RGBA, BC1_RGB, BC3_RGBA, BC4_R, BC5_RG, BC7_RGBA, PVRTC1_4_RGB, PVRTC1_4_RGBA,
PVRTC2_4_RGB, PVRTC2_4_RGBA, ASTC_4x4_RGBA, ETC2_EAC_R11, ETC2_EAC_RG11, ETC and BC1_OR_3.
ETC automatically selects between ETC1_RGB and ETC2_RGBA according to whether an alpha
channel is available. BC1_OR_3 does likewise between BC1_RGB and BC3_RGBA. Note that if
PVRTC1_4_RGBA or PVRTC2_4_RGBA is specified and there is no alpha channel PVRTC1_4_RGB
or PVRTC2_4_RGB respectively will be selected.
Transcoding to ATC & FXT1 formats is not supported by libktx as there are no equivalent Vulkan formats.
The following uncompressed transcode targets are also available: RGBA32, RGB565, BGR565 and RGBA4444.
"""
error = lib.ktxTexture2_TranscodeBasis(self._ptr, output_format, transcode_flags)
if int(error) != KtxErrorCode.SUCCESS:
raise KtxError('ktx2_TranscodeBasis', KtxErrorCode(error))
@@ -0,0 +1,19 @@
# Copyright (c) 2023, Shukant Pal and Contributors
# SPDX-License-Identifier: Apache-2.0
from enum import IntEnum
class KtxTextureCreateFlagBits(IntEnum):
"""Flags for requesting services during creation."""
NO_FLAGS = 0x00
LOAD_IMAGE_DATA_BIT = 0x01
"""Load the images from the KTX source."""
RAW_KVDATA_BIT = 0x02
"""Load the raw key-value data instead of creating a KtxHashList from it."""
SKIP_KVDATA_BIT = 0x04
"""Skip any key-value data. This overrides the RAW_KVDATA_BIT."""
@@ -0,0 +1,45 @@
# Copyright (c) 2023, Shukant Pal and Contributors
# SPDX-License-Identifier: Apache-2.0
from dataclasses import dataclass
from .gl_internalformat import GlInternalformat
from typing import Optional
from .vk_format import VkFormat
@dataclass
class KtxTextureCreateInfo:
"""Data for passing texture information to KtxTexture1.create() and KtxTexture2.create()."""
gl_internal_format: Optional[GlInternalformat]
"""Internal format for the texture, e.g., GlInteralformat.RGB8. Ignored when creating a KtxTexture2."""
base_width: int
"""Width of the base level of the texture."""
base_height: int
"""Height of the base level of the texture."""
base_depth: int
"""Depth of the base level of the texture."""
vk_format: VkFormat = VkFormat.VK_FORMAT_UNDEFINED
"""VkFormat for texture. Ignored when creating a KtxTexture1."""
num_dimensions: int = 2
"""Number of dimensions in the texture, 1, 2 or 3."""
num_levels: int = 1
"""Number of mip levels in the texture. Should be 1 if generateMipmaps is true."""
num_layers: int = 1
"""Number of array layers in the texture."""
num_faces: int = 1
"""Number of faces: 6 for cube maps, 1 otherwise."""
is_array: bool = False
"""Set to true if the texture is to be an array texture. Means OpenGL will use a GL_TEXTURE_*_ARRAY target."""
generate_mipmaps: bool = False
"""Set to true if mipmaps should be generated for the texture when loading into a 3D API."""
@@ -0,0 +1,15 @@
# Copyright (c) 2023, Shukant Pal and Contributors
# SPDX-License-Identifier: Apache-2.0
from enum import IntEnum
class KtxTextureCreateStorage(IntEnum):
"""Enum for requesting, or not, allocation of storage for images."""
NO = 0
"""Don't allocate any image storage."""
ALLOC = 1
"""Allocate image storage."""
@@ -0,0 +1,28 @@
# Copyright (c) 2023, Shukant Pal and Contributors
# SPDX-License-Identifier: Apache-2.0
from enum import IntEnum
class KtxTranscodeFlagBits(IntEnum):
"""Flags guiding transcoding of Basis Universal compressed textures."""
PVRTC_DECODE_TO_NEXT_POW2 = 2
"""
PVRTC1: decode non-pow2 ETC1S texture level to the next larger
power of 2 (not implemented yet, but we're going to support it).
Ignored if the slice's dimensions are already a power of 2.
"""
TRANSCODE_ALPHA_DATA_TO_OPAQUE_FORMATS = 4
"""
When decoding to an opaque texture format, if the Basis data has
alpha, decode the alpha slice instead of the color slice to the
output texture format. Has no effect if there is no alpha data.
"""
HIGH_QUALITY = 32
"""
Request higher quality transcode of UASTC to BC1, BC3, ETC2_EAC_R11 and
ETC2_EAC_RG11. The flag is unused by other UASTC transcoders.
"""
@@ -0,0 +1,30 @@
# Copyright (c) 2023, Shukant Pal and Contributors
# SPDX-License-Identifier: Apache-2.0
from enum import IntEnum
class KtxTranscodeFmt(IntEnum):
"""Enumerators for specifying the transcode target format."""
ETC1_RGB = 0
ETC2_RGBA = 1
BC1_RGB = 2
BC3_RGBA = 3
BC4_R = 4
BC5_RG = 5
BC7_RGBA = 6
PVRTC1_4_RGB = 8
PVRTC1_4_RGBA = 9
ASTC_4x4_RGBA = 10
PVRTC2_4_RGB = 18
PVRTC2_4_RGBA = 19
ETC2_EAC_R11 = 20
ETC2_EAC_RG11 = 21
RGBA32 = 13
RGB565 = 14
BGR565 = 15
RGBA4444 = 16
ETC = 22
BC1_OR_3 = 23
NO_SELECTION = 0x7fffffff
@@ -0,0 +1,245 @@
#******************************* Do not edit. *****************************
# Automatically generated from vulkan_core.h version 287 by mkvkformatfiles.
#***************************************************************************
# Copyright 2015-2024 The Khronos Group Inc.
#
# SPDX-License-Identifier: Apache-2.0
from enum import IntEnum
class VkFormat(IntEnum):
"""Vulkan texture format constants."""
VK_FORMAT_UNDEFINED = 0
VK_FORMAT_R4G4_UNORM_PACK8 = 1
VK_FORMAT_R4G4B4A4_UNORM_PACK16 = 2
VK_FORMAT_B4G4R4A4_UNORM_PACK16 = 3
VK_FORMAT_R5G6B5_UNORM_PACK16 = 4
VK_FORMAT_B5G6R5_UNORM_PACK16 = 5
VK_FORMAT_R5G5B5A1_UNORM_PACK16 = 6
VK_FORMAT_B5G5R5A1_UNORM_PACK16 = 7
VK_FORMAT_A1R5G5B5_UNORM_PACK16 = 8
VK_FORMAT_R8_UNORM = 9
VK_FORMAT_R8_SNORM = 10
VK_FORMAT_R8_UINT = 13
VK_FORMAT_R8_SINT = 14
VK_FORMAT_R8_SRGB = 15
VK_FORMAT_R8G8_UNORM = 16
VK_FORMAT_R8G8_SNORM = 17
VK_FORMAT_R8G8_UINT = 20
VK_FORMAT_R8G8_SINT = 21
VK_FORMAT_R8G8_SRGB = 22
VK_FORMAT_R8G8B8_UNORM = 23
VK_FORMAT_R8G8B8_SNORM = 24
VK_FORMAT_R8G8B8_UINT = 27
VK_FORMAT_R8G8B8_SINT = 28
VK_FORMAT_R8G8B8_SRGB = 29
VK_FORMAT_B8G8R8_UNORM = 30
VK_FORMAT_B8G8R8_SNORM = 31
VK_FORMAT_B8G8R8_UINT = 34
VK_FORMAT_B8G8R8_SINT = 35
VK_FORMAT_B8G8R8_SRGB = 36
VK_FORMAT_R8G8B8A8_UNORM = 37
VK_FORMAT_R8G8B8A8_SNORM = 38
VK_FORMAT_R8G8B8A8_UINT = 41
VK_FORMAT_R8G8B8A8_SINT = 42
VK_FORMAT_R8G8B8A8_SRGB = 43
VK_FORMAT_B8G8R8A8_UNORM = 44
VK_FORMAT_B8G8R8A8_SNORM = 45
VK_FORMAT_B8G8R8A8_UINT = 48
VK_FORMAT_B8G8R8A8_SINT = 49
VK_FORMAT_B8G8R8A8_SRGB = 50
VK_FORMAT_A8B8G8R8_UNORM_PACK32 = 51
VK_FORMAT_A8B8G8R8_SNORM_PACK32 = 52
VK_FORMAT_A8B8G8R8_UINT_PACK32 = 55
VK_FORMAT_A8B8G8R8_SINT_PACK32 = 56
VK_FORMAT_A8B8G8R8_SRGB_PACK32 = 57
VK_FORMAT_A2R10G10B10_UNORM_PACK32 = 58
VK_FORMAT_A2R10G10B10_SNORM_PACK32 = 59
VK_FORMAT_A2R10G10B10_UINT_PACK32 = 62
VK_FORMAT_A2R10G10B10_SINT_PACK32 = 63
VK_FORMAT_A2B10G10R10_UNORM_PACK32 = 64
VK_FORMAT_A2B10G10R10_SNORM_PACK32 = 65
VK_FORMAT_A2B10G10R10_UINT_PACK32 = 68
VK_FORMAT_A2B10G10R10_SINT_PACK32 = 69
VK_FORMAT_R16_UNORM = 70
VK_FORMAT_R16_SNORM = 71
VK_FORMAT_R16_UINT = 74
VK_FORMAT_R16_SINT = 75
VK_FORMAT_R16_SFLOAT = 76
VK_FORMAT_R16G16_UNORM = 77
VK_FORMAT_R16G16_SNORM = 78
VK_FORMAT_R16G16_UINT = 81
VK_FORMAT_R16G16_SINT = 82
VK_FORMAT_R16G16_SFLOAT = 83
VK_FORMAT_R16G16B16_UNORM = 84
VK_FORMAT_R16G16B16_SNORM = 85
VK_FORMAT_R16G16B16_UINT = 88
VK_FORMAT_R16G16B16_SINT = 89
VK_FORMAT_R16G16B16_SFLOAT = 90
VK_FORMAT_R16G16B16A16_UNORM = 91
VK_FORMAT_R16G16B16A16_SNORM = 92
VK_FORMAT_R16G16B16A16_UINT = 95
VK_FORMAT_R16G16B16A16_SINT = 96
VK_FORMAT_R16G16B16A16_SFLOAT = 97
VK_FORMAT_R32_UINT = 98
VK_FORMAT_R32_SINT = 99
VK_FORMAT_R32_SFLOAT = 100
VK_FORMAT_R32G32_UINT = 101
VK_FORMAT_R32G32_SINT = 102
VK_FORMAT_R32G32_SFLOAT = 103
VK_FORMAT_R32G32B32_UINT = 104
VK_FORMAT_R32G32B32_SINT = 105
VK_FORMAT_R32G32B32_SFLOAT = 106
VK_FORMAT_R32G32B32A32_UINT = 107
VK_FORMAT_R32G32B32A32_SINT = 108
VK_FORMAT_R32G32B32A32_SFLOAT = 109
VK_FORMAT_R64_UINT = 110
VK_FORMAT_R64_SINT = 111
VK_FORMAT_R64_SFLOAT = 112
VK_FORMAT_R64G64_UINT = 113
VK_FORMAT_R64G64_SINT = 114
VK_FORMAT_R64G64_SFLOAT = 115
VK_FORMAT_R64G64B64_UINT = 116
VK_FORMAT_R64G64B64_SINT = 117
VK_FORMAT_R64G64B64_SFLOAT = 118
VK_FORMAT_R64G64B64A64_UINT = 119
VK_FORMAT_R64G64B64A64_SINT = 120
VK_FORMAT_R64G64B64A64_SFLOAT = 121
VK_FORMAT_B10G11R11_UFLOAT_PACK32 = 122
VK_FORMAT_E5B9G9R9_UFLOAT_PACK32 = 123
VK_FORMAT_D16_UNORM = 124
VK_FORMAT_X8_D24_UNORM_PACK32 = 125
VK_FORMAT_D32_SFLOAT = 126
VK_FORMAT_S8_UINT = 127
VK_FORMAT_D16_UNORM_S8_UINT = 128
VK_FORMAT_D24_UNORM_S8_UINT = 129
VK_FORMAT_D32_SFLOAT_S8_UINT = 130
VK_FORMAT_BC1_RGB_UNORM_BLOCK = 131
VK_FORMAT_BC1_RGB_SRGB_BLOCK = 132
VK_FORMAT_BC1_RGBA_UNORM_BLOCK = 133
VK_FORMAT_BC1_RGBA_SRGB_BLOCK = 134
VK_FORMAT_BC2_UNORM_BLOCK = 135
VK_FORMAT_BC2_SRGB_BLOCK = 136
VK_FORMAT_BC3_UNORM_BLOCK = 137
VK_FORMAT_BC3_SRGB_BLOCK = 138
VK_FORMAT_BC4_UNORM_BLOCK = 139
VK_FORMAT_BC4_SNORM_BLOCK = 140
VK_FORMAT_BC5_UNORM_BLOCK = 141
VK_FORMAT_BC5_SNORM_BLOCK = 142
VK_FORMAT_BC6H_UFLOAT_BLOCK = 143
VK_FORMAT_BC6H_SFLOAT_BLOCK = 144
VK_FORMAT_BC7_UNORM_BLOCK = 145
VK_FORMAT_BC7_SRGB_BLOCK = 146
VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK = 147
VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK = 148
VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK = 149
VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK = 150
VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK = 151
VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK = 152
VK_FORMAT_EAC_R11_UNORM_BLOCK = 153
VK_FORMAT_EAC_R11_SNORM_BLOCK = 154
VK_FORMAT_EAC_R11G11_UNORM_BLOCK = 155
VK_FORMAT_EAC_R11G11_SNORM_BLOCK = 156
VK_FORMAT_ASTC_4x4_UNORM_BLOCK = 157
VK_FORMAT_ASTC_4x4_SRGB_BLOCK = 158
VK_FORMAT_ASTC_5x4_UNORM_BLOCK = 159
VK_FORMAT_ASTC_5x4_SRGB_BLOCK = 160
VK_FORMAT_ASTC_5x5_UNORM_BLOCK = 161
VK_FORMAT_ASTC_5x5_SRGB_BLOCK = 162
VK_FORMAT_ASTC_6x5_UNORM_BLOCK = 163
VK_FORMAT_ASTC_6x5_SRGB_BLOCK = 164
VK_FORMAT_ASTC_6x6_UNORM_BLOCK = 165
VK_FORMAT_ASTC_6x6_SRGB_BLOCK = 166
VK_FORMAT_ASTC_8x5_UNORM_BLOCK = 167
VK_FORMAT_ASTC_8x5_SRGB_BLOCK = 168
VK_FORMAT_ASTC_8x6_UNORM_BLOCK = 169
VK_FORMAT_ASTC_8x6_SRGB_BLOCK = 170
VK_FORMAT_ASTC_8x8_UNORM_BLOCK = 171
VK_FORMAT_ASTC_8x8_SRGB_BLOCK = 172
VK_FORMAT_ASTC_10x5_UNORM_BLOCK = 173
VK_FORMAT_ASTC_10x5_SRGB_BLOCK = 174
VK_FORMAT_ASTC_10x6_UNORM_BLOCK = 175
VK_FORMAT_ASTC_10x6_SRGB_BLOCK = 176
VK_FORMAT_ASTC_10x8_UNORM_BLOCK = 177
VK_FORMAT_ASTC_10x8_SRGB_BLOCK = 178
VK_FORMAT_ASTC_10x10_UNORM_BLOCK = 179
VK_FORMAT_ASTC_10x10_SRGB_BLOCK = 180
VK_FORMAT_ASTC_12x10_UNORM_BLOCK = 181
VK_FORMAT_ASTC_12x10_SRGB_BLOCK = 182
VK_FORMAT_ASTC_12x12_UNORM_BLOCK = 183
VK_FORMAT_ASTC_12x12_SRGB_BLOCK = 184
VK_FORMAT_G8B8G8R8_422_UNORM = 1000156000
VK_FORMAT_B8G8R8G8_422_UNORM = 1000156001
VK_FORMAT_R10X6_UNORM_PACK16 = 1000156007
VK_FORMAT_R10X6G10X6_UNORM_2PACK16 = 1000156008
VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16 = 1000156009
VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16 = 1000156010
VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16 = 1000156011
VK_FORMAT_R12X4_UNORM_PACK16 = 1000156017
VK_FORMAT_R12X4G12X4_UNORM_2PACK16 = 1000156018
VK_FORMAT_R12X4G12X4B12X4A12X4_UNORM_4PACK16 = 1000156019
VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16 = 1000156020
VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16 = 1000156021
VK_FORMAT_G16B16G16R16_422_UNORM = 1000156027
VK_FORMAT_B16G16R16G16_422_UNORM = 1000156028
VK_FORMAT_A4R4G4B4_UNORM_PACK16 = 1000340000
VK_FORMAT_A4B4G4R4_UNORM_PACK16 = 1000340001
VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK = 1000066000
VK_FORMAT_ASTC_5x4_SFLOAT_BLOCK = 1000066001
VK_FORMAT_ASTC_5x5_SFLOAT_BLOCK = 1000066002
VK_FORMAT_ASTC_6x5_SFLOAT_BLOCK = 1000066003
VK_FORMAT_ASTC_6x6_SFLOAT_BLOCK = 1000066004
VK_FORMAT_ASTC_8x5_SFLOAT_BLOCK = 1000066005
VK_FORMAT_ASTC_8x6_SFLOAT_BLOCK = 1000066006
VK_FORMAT_ASTC_8x8_SFLOAT_BLOCK = 1000066007
VK_FORMAT_ASTC_10x5_SFLOAT_BLOCK = 1000066008
VK_FORMAT_ASTC_10x6_SFLOAT_BLOCK = 1000066009
VK_FORMAT_ASTC_10x8_SFLOAT_BLOCK = 1000066010
VK_FORMAT_ASTC_10x10_SFLOAT_BLOCK = 1000066011
VK_FORMAT_ASTC_12x10_SFLOAT_BLOCK = 1000066012
VK_FORMAT_ASTC_12x12_SFLOAT_BLOCK = 1000066013
VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG = 1000054000
VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG = 1000054001
VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG = 1000054002
VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG = 1000054003
VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG = 1000054004
VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG = 1000054005
VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG = 1000054006
VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG = 1000054007
VK_FORMAT_ASTC_3x3x3_UNORM_BLOCK_EXT = 1000288000
VK_FORMAT_ASTC_3x3x3_SRGB_BLOCK_EXT = 1000288001
VK_FORMAT_ASTC_3x3x3_SFLOAT_BLOCK_EXT = 1000288002
VK_FORMAT_ASTC_4x3x3_UNORM_BLOCK_EXT = 1000288003
VK_FORMAT_ASTC_4x3x3_SRGB_BLOCK_EXT = 1000288004
VK_FORMAT_ASTC_4x3x3_SFLOAT_BLOCK_EXT = 1000288005
VK_FORMAT_ASTC_4x4x3_UNORM_BLOCK_EXT = 1000288006
VK_FORMAT_ASTC_4x4x3_SRGB_BLOCK_EXT = 1000288007
VK_FORMAT_ASTC_4x4x3_SFLOAT_BLOCK_EXT = 1000288008
VK_FORMAT_ASTC_4x4x4_UNORM_BLOCK_EXT = 1000288009
VK_FORMAT_ASTC_4x4x4_SRGB_BLOCK_EXT = 1000288010
VK_FORMAT_ASTC_4x4x4_SFLOAT_BLOCK_EXT = 1000288011
VK_FORMAT_ASTC_5x4x4_UNORM_BLOCK_EXT = 1000288012
VK_FORMAT_ASTC_5x4x4_SRGB_BLOCK_EXT = 1000288013
VK_FORMAT_ASTC_5x4x4_SFLOAT_BLOCK_EXT = 1000288014
VK_FORMAT_ASTC_5x5x4_UNORM_BLOCK_EXT = 1000288015
VK_FORMAT_ASTC_5x5x4_SRGB_BLOCK_EXT = 1000288016
VK_FORMAT_ASTC_5x5x4_SFLOAT_BLOCK_EXT = 1000288017
VK_FORMAT_ASTC_5x5x5_UNORM_BLOCK_EXT = 1000288018
VK_FORMAT_ASTC_5x5x5_SRGB_BLOCK_EXT = 1000288019
VK_FORMAT_ASTC_5x5x5_SFLOAT_BLOCK_EXT = 1000288020
VK_FORMAT_ASTC_6x5x5_UNORM_BLOCK_EXT = 1000288021
VK_FORMAT_ASTC_6x5x5_SRGB_BLOCK_EXT = 1000288022
VK_FORMAT_ASTC_6x5x5_SFLOAT_BLOCK_EXT = 1000288023
VK_FORMAT_ASTC_6x6x5_UNORM_BLOCK_EXT = 1000288024
VK_FORMAT_ASTC_6x6x5_SRGB_BLOCK_EXT = 1000288025
VK_FORMAT_ASTC_6x6x5_SFLOAT_BLOCK_EXT = 1000288026
VK_FORMAT_ASTC_6x6x6_UNORM_BLOCK_EXT = 1000288027
VK_FORMAT_ASTC_6x6x6_SRGB_BLOCK_EXT = 1000288028
VK_FORMAT_ASTC_6x6x6_SFLOAT_BLOCK_EXT = 1000288029
VK_FORMAT_R16G16_SFIXED5_NV = 1000464000
VK_FORMAT_A1B5G5R5_UNORM_PACK16_KHR = 1000470000
VK_FORMAT_A8_UNORM_KHR = 1000470001
@@ -0,0 +1,9 @@
# Copyright (c) 2023, Shukant Pal and Contributors
# SPDX-License-Identifier: Apache-2.0
build==1.0.3
cffi==1.17.1
setuptools==78.1.1
wheel>=0.38.4
sphinx==6.2.1
sphinx-rtd-theme==1.2.2
+32
View File
@@ -0,0 +1,32 @@
# Copyright (c) 2023, Shukant Pal and Contributors
# SPDX-License-Identifier: Apache-2.0
import os
from setuptools import setup
__name__ = 'pyktx'
__version__ = os.environ['LIBKTX_VERSION']
assert __version__ is not None
setup(
name=__name__,
version=__version__,
description='A Python interface to the libktx library',
author='Shukant Pal, Mark Callow',
author_email='ktx-sw-maintainer@khronosgroup.org',
cffi_modules=["buildscript.py:ffibuilder"],
classifiers=[
"Programming Language :: Python :: 3",
],
include_package_data=True,
install_requires=["cffi>=1.15.1"],
license="Apache 2.0",
license_files=['LICENSE'],
long_description_content_type="text/markdown",
long_description="This Python package provides a Pythonic interface to libktx. It uses CFFI to generate the C bindings.",
packages=['pyktx'],
package_dir={'pyktx': 'pyktx'},
setup_requires=["cffi>=1.15.1"],
url='https://github.com/KhronosGroup/KTX-Software',
)
@@ -0,0 +1,9 @@
# Copyright (c) 2023, Shukant Pal and Contributors
# SPDX-License-Identifier: Apache-2.0
from pathlib import Path
import os
__test_images__ = os.environ['KTX_IMAGES_DIR'] \
if 'KTX_IMAGES_DIR' in os.environ \
else str((Path(__file__) / Path('../../../../tests/testimages')).resolve())
@@ -0,0 +1,26 @@
# Copyright (c) 2023, Shukant Pal and Contributors
# SPDX-License-Identifier: Apache-2.0
from pyktx import *
from test_config import __test_images__
import unittest
class TestKtxTexture(unittest.TestCase):
def test_kv_data(self):
test_ktx_file = os.path.join(__test_images__, 'astc_ldr_4x4_FlightHelmet_baseColor.ktx2')
texture = KtxTexture2.create_from_named_file(test_ktx_file)
self.assertEqual(texture.kv_data.find_value('KTXorientation'), b'rd\x00')
self.assertEqual(texture.kv_data.copy(), {
'KTXorientation': b'rd\x00',
'KTXwriter': b'toktx v4.0.__default__ / libktx v4.0.__default__\x00',
'KTXwriterScParams': b'--encode astc --astc_blk_d 4x4\x00',
})
for key in ['KTXorientation', 'KTXwriter', 'KTXwriterScParams']:
texture.kv_data.delete_kv_pair(key)
texture.kv_data.add_kv_pair('KTXwriter', b'pyktx v4.0.__default__ / libktx v4.0.__default__\x00')
self.assertEqual(texture.kv_data.find_value('KTXwriter'),
b'pyktx v4.0.__default__ / libktx v4.0.__default__\x00')
@@ -0,0 +1,60 @@
# Copyright (c) 2023, Shukant Pal and Contributors
# SPDX-License-Identifier: Apache-2.0
from pyktx import *
from tempfile import NamedTemporaryFile
from test_config import __test_images__
import os
import unittest
class TestKtxTexture1(unittest.TestCase):
def test_create_from_named_file(self):
test_ktx_file = os.path.join(__test_images__, 'etc1.ktx')
texture = KtxTexture1.create_from_named_file(test_ktx_file)
self.assertEqual(texture.gl_internalformat, GlInternalformat.ETC1_RGB8_OES)
self.assertFalse(texture.is_array)
self.assertFalse(texture.generate_mipmaps)
self.assertEqual(texture.num_levels, 1)
def test_write_to_named_file(self):
test_ktx_file = os.path.join(__test_images__, 'etc2-rgb.ktx')
copy_file = NamedTemporaryFile(delete=False)
copy_file.close()
texture = KtxTexture1.create_from_named_file(test_ktx_file, KtxTextureCreateFlagBits.LOAD_IMAGE_DATA_BIT)
texture.write_to_named_file(copy_file.name)
with open(test_ktx_file, 'rb') as original, open(copy_file.name, 'rb') as copy:
self.assertEqual(original.read(), copy.read())
os.unlink(copy_file.name)
def test_write_to_memory(self):
test_ktx_file = os.path.join(__test_images__, 'etc2-rgba1.ktx')
texture = KtxTexture1.create_from_named_file(test_ktx_file, KtxTextureCreateFlagBits.LOAD_IMAGE_DATA_BIT)
texture_bytes = texture.write_to_memory()
with open(test_ktx_file, 'rb') as original:
self.assertEqual(original.read(), texture_bytes)
def test_get_data(self):
test_ktx_file = os.path.join(__test_images__, 'etc2-rgba1.ktx')
texture = KtxTexture1.create_from_named_file(test_ktx_file, KtxTextureCreateFlagBits.LOAD_IMAGE_DATA_BIT)
data = texture.data()
with open(test_ktx_file, 'rb') as file:
level_0_size = texture.image_size(0)
self.assertEqual(file.read()[-level_0_size:], data[:])
def test_create(self):
info = KtxTextureCreateInfo(
gl_internal_format=GlInternalformat.COMPRESSED_RGBA_ASTC_4x4_KHR,
base_width=10,
base_height=10,
base_depth=1)
texture = KtxTexture1.create(info, KtxTextureCreateStorage.ALLOC)
texture.set_image_from_memory(0, 0, 0, bytes(texture.data_size))
@@ -0,0 +1,104 @@
# Copyright (c) 2023, Shukant Pal and Contributors
# SPDX-License-Identifier: Apache-2.0
from pyktx import *
from test_config import __test_images__
import unittest
class TestKtxTexture2(unittest.TestCase):
def test_create_from_named_file(self):
test_ktx_file = os.path.join(__test_images__, 'astc_ldr_4x4_FlightHelmet_baseColor.ktx2')
texture = KtxTexture2.create_from_named_file(test_ktx_file)
self.assertEqual(texture.num_levels, 1)
self.assertEqual(texture.num_faces, 1)
self.assertEqual(texture.vk_format, VkFormat.VK_FORMAT_ASTC_4x4_SRGB_BLOCK)
self.assertEqual(texture.base_width, 2048)
self.assertEqual(texture.base_height, 2048)
self.assertEqual(texture.supercompression_scheme, KtxSupercmpScheme.NONE)
def test_create_from_named_file_mipmapped(self):
test_ktx_file = os.path.join(__test_images__, 'astc_mipmap_ldr_4x4_posx.ktx2')
texture = KtxTexture2.create_from_named_file(test_ktx_file, KtxTextureCreateFlagBits.NO_FLAGS)
self.assertEqual(texture.num_levels, 12)
self.assertEqual(texture.base_width, 2048)
self.assertEqual(texture.base_height, 2048)
def test_get_image_size(self):
test_ktx_file = os.path.join(__test_images__, 'astc_mipmap_ldr_4x4_posx.ktx2')
texture = KtxTexture2.create_from_named_file(test_ktx_file)
self.assertEqual(texture.image_size(0), 4194304)
def test_get_image_offset(self):
test_ktx_file = os.path.join(__test_images__, 'astc_mipmap_ldr_4x4_posx.ktx2')
texture = KtxTexture2.create_from_named_file(test_ktx_file)
level11_offset = texture.image_offset(11, 0, 0)
level0_offset = texture.image_offset(0, 0, 0)
self.assertEqual(level11_offset, 0)
self.assertEqual(level0_offset - level11_offset, 0x155790 - 0x220)
def test_get_size(self):
test_ktx_file = os.path.join(__test_images__, 'astc_mipmap_ldr_4x4_posx.ktx2')
texture = KtxTexture2.create_from_named_file(test_ktx_file)
self.assertEqual(texture.num_levels, 12)
data_size = texture.data_size
total_size = 0
for i in range(0, 12):
total_size += texture.image_size(i)
self.assertEqual(total_size, data_size)
data = texture.data()
self.assertEqual(len(data), data_size)
def test_get_data(self):
test_ktx_file = os.path.join(__test_images__, 'astc_mipmap_ldr_4x4_posx.ktx2')
texture = KtxTexture2.create_from_named_file(test_ktx_file)
self.assertEqual(texture.num_levels, 12)
level0_length = texture.image_size(0)
with open(test_ktx_file, 'rb') as file:
data = texture.data()
self.assertEqual(file.read()[-level0_length - 1:], data[-level0_length - 1:])
def test_compress_basis(self):
test_ktx_file = os.path.join(__test_images__, 'arraytex_7_mipmap_reference_u.ktx2')
texture = KtxTexture2.create_from_named_file(test_ktx_file, KtxTextureCreateFlagBits.LOAD_IMAGE_DATA_BIT)
self.assertEqual(texture.is_compressed, False)
self.assertEqual(texture.supercompression_scheme, KtxSupercmpScheme.NONE)
texture.compress_basis(KtxBasisParams(quality_level=1))
self.assertEqual(texture.is_compressed, True)
self.assertEqual(texture.supercompression_scheme, KtxSupercmpScheme.BASIS_LZ)
def test_transcode_basis(self):
test_ktx_file = os.path.join(__test_images__, 'color_grid_basis.ktx2')
texture = KtxTexture2.create_from_named_file(test_ktx_file, KtxTextureCreateFlagBits.LOAD_IMAGE_DATA_BIT)
texture.transcode_basis(KtxTranscodeFmt.ASTC_4x4_RGBA, 0)
self.assertEqual(texture.vk_format, VkFormat.VK_FORMAT_ASTC_4x4_SRGB_BLOCK)
def test_create(self):
info = KtxTextureCreateInfo(
gl_internal_format=None, # ignored
base_width=10,
base_height=10,
base_depth=1,
vk_format=VkFormat.VK_FORMAT_ASTC_4x4_SRGB_BLOCK)
texture = KtxTexture2.create(info, KtxTextureCreateStorage.ALLOC)
self.assertEqual(texture.vk_format, VkFormat.VK_FORMAT_ASTC_4x4_SRGB_BLOCK)
texture.set_image_from_memory(0, 0, 0, bytes(texture.data_size))