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
+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))