opengl-basisu 0.1.0a1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. opengl_basisu-0.1.0a1/CHANGELOG.md +8 -0
  2. opengl_basisu-0.1.0a1/LICENSE +60 -0
  3. opengl_basisu-0.1.0a1/MANIFEST.in +11 -0
  4. opengl_basisu-0.1.0a1/NOTICE +27 -0
  5. opengl_basisu-0.1.0a1/PKG-INFO +182 -0
  6. opengl_basisu-0.1.0a1/README.md +142 -0
  7. opengl_basisu-0.1.0a1/docs/API.md +101 -0
  8. opengl_basisu-0.1.0a1/pyproject.toml +173 -0
  9. opengl_basisu-0.1.0a1/setup.cfg +4 -0
  10. opengl_basisu-0.1.0a1/setup.py +77 -0
  11. opengl_basisu-0.1.0a1/src/opengl_basisu/__init__.py +50 -0
  12. opengl_basisu-0.1.0a1/src/opengl_basisu/_shim.hpp +230 -0
  13. opengl_basisu-0.1.0a1/src/opengl_basisu/_transcoder.cpp +34958 -0
  14. opengl_basisu-0.1.0a1/src/opengl_basisu/_transcoder.pyi +51 -0
  15. opengl_basisu-0.1.0a1/src/opengl_basisu/_transcoder.pyx +204 -0
  16. opengl_basisu-0.1.0a1/src/opengl_basisu/errors.py +17 -0
  17. opengl_basisu-0.1.0a1/src/opengl_basisu/py.typed +0 -0
  18. opengl_basisu-0.1.0a1/src/opengl_basisu/targets.py +368 -0
  19. opengl_basisu-0.1.0a1/src/opengl_basisu/texture.py +307 -0
  20. opengl_basisu-0.1.0a1/src/opengl_basisu.egg-info/PKG-INFO +182 -0
  21. opengl_basisu-0.1.0a1/src/opengl_basisu.egg-info/SOURCES.txt +71 -0
  22. opengl_basisu-0.1.0a1/src/opengl_basisu.egg-info/dependency_links.txt +1 -0
  23. opengl_basisu-0.1.0a1/src/opengl_basisu.egg-info/requires.txt +11 -0
  24. opengl_basisu-0.1.0a1/src/opengl_basisu.egg-info/top_level.txt +1 -0
  25. opengl_basisu-0.1.0a1/tests/blocks.py +73 -0
  26. opengl_basisu-0.1.0a1/tests/fixtures/colours.json +72 -0
  27. opengl_basisu-0.1.0a1/tests/fixtures/etc1s_cube.ktx2 +0 -0
  28. opengl_basisu-0.1.0a1/tests/fixtures/etc1s_rgba.basis +0 -0
  29. opengl_basisu-0.1.0a1/tests/fixtures/etc1s_srgb_mips.ktx2 +0 -0
  30. opengl_basisu-0.1.0a1/tests/fixtures/uastc_hdr.ktx2 +0 -0
  31. opengl_basisu-0.1.0a1/tests/fixtures/uastc_odd.ktx2 +0 -0
  32. opengl_basisu-0.1.0a1/tests/fixtures/uastc_rgba.ktx2 +0 -0
  33. opengl_basisu-0.1.0a1/tests/samples.py +27 -0
  34. opengl_basisu-0.1.0a1/tests/test_errors.py +134 -0
  35. opengl_basisu-0.1.0a1/tests/test_open.py +97 -0
  36. opengl_basisu-0.1.0a1/tests/test_targets.py +93 -0
  37. opengl_basisu-0.1.0a1/tests/test_transcode.py +174 -0
  38. opengl_basisu-0.1.0a1/tools/make_fixtures.py +140 -0
  39. opengl_basisu-0.1.0a1/tools/vendor_basisu.py +85 -0
  40. opengl_basisu-0.1.0a1/tox.ini +56 -0
  41. opengl_basisu-0.1.0a1/vendor/basis_universal/LICENSE +201 -0
  42. opengl_basisu-0.1.0a1/vendor/basis_universal/NOTICE +15 -0
  43. opengl_basisu-0.1.0a1/vendor/basis_universal/VERSION +3 -0
  44. opengl_basisu-0.1.0a1/vendor/basis_universal/transcoder/basisu.h +821 -0
  45. opengl_basisu-0.1.0a1/vendor/basis_universal/transcoder/basisu_astc_cfgs.inl +648 -0
  46. opengl_basisu-0.1.0a1/vendor/basis_universal/transcoder/basisu_astc_hdr_core.h +211 -0
  47. opengl_basisu-0.1.0a1/vendor/basis_universal/transcoder/basisu_astc_helpers.h +5335 -0
  48. opengl_basisu-0.1.0a1/vendor/basis_universal/transcoder/basisu_containers.h +4354 -0
  49. opengl_basisu-0.1.0a1/vendor/basis_universal/transcoder/basisu_containers_impl.h +818 -0
  50. opengl_basisu-0.1.0a1/vendor/basis_universal/transcoder/basisu_dds_transcoder.inl +1372 -0
  51. opengl_basisu-0.1.0a1/vendor/basis_universal/transcoder/basisu_etc1_mods.inl +258 -0
  52. opengl_basisu-0.1.0a1/vendor/basis_universal/transcoder/basisu_file_headers.h +264 -0
  53. opengl_basisu-0.1.0a1/vendor/basis_universal/transcoder/basisu_idct.h +1446 -0
  54. opengl_basisu-0.1.0a1/vendor/basis_universal/transcoder/basisu_transcoder.cpp +45962 -0
  55. opengl_basisu-0.1.0a1/vendor/basis_universal/transcoder/basisu_transcoder.h +1786 -0
  56. opengl_basisu-0.1.0a1/vendor/basis_universal/transcoder/basisu_transcoder_internal.h +4044 -0
  57. opengl_basisu-0.1.0a1/vendor/basis_universal/transcoder/basisu_transcoder_tables_astc.inc +481 -0
  58. opengl_basisu-0.1.0a1/vendor/basis_universal/transcoder/basisu_transcoder_tables_astc_0_255.inc +481 -0
  59. opengl_basisu-0.1.0a1/vendor/basis_universal/transcoder/basisu_transcoder_tables_atc_55.inc +481 -0
  60. opengl_basisu-0.1.0a1/vendor/basis_universal/transcoder/basisu_transcoder_tables_atc_56.inc +481 -0
  61. opengl_basisu-0.1.0a1/vendor/basis_universal/transcoder/basisu_transcoder_tables_bc7_m5_alpha.inc +49 -0
  62. opengl_basisu-0.1.0a1/vendor/basis_universal/transcoder/basisu_transcoder_tables_bc7_m5_color.inc +481 -0
  63. opengl_basisu-0.1.0a1/vendor/basis_universal/transcoder/basisu_transcoder_tables_dxt1_5.inc +494 -0
  64. opengl_basisu-0.1.0a1/vendor/basis_universal/transcoder/basisu_transcoder_tables_dxt1_6.inc +494 -0
  65. opengl_basisu-0.1.0a1/vendor/basis_universal/transcoder/basisu_transcoder_tables_pvrtc2_45.inc +481 -0
  66. opengl_basisu-0.1.0a1/vendor/basis_universal/transcoder/basisu_transcoder_tables_pvrtc2_alpha_33.inc +481 -0
  67. opengl_basisu-0.1.0a1/vendor/basis_universal/transcoder/basisu_transcoder_uastc.h +289 -0
  68. opengl_basisu-0.1.0a1/vendor/basis_universal/transcoder/basisu_xbc7_decoder.h +1467 -0
  69. opengl_basisu-0.1.0a1/vendor/basis_universal/transcoder/basisu_xbc7_decoder.inl +1515 -0
  70. opengl_basisu-0.1.0a1/vendor/basis_universal/zstd/LICENSE +30 -0
  71. opengl_basisu-0.1.0a1/vendor/basis_universal/zstd/zstd.h +3198 -0
  72. opengl_basisu-0.1.0a1/vendor/basis_universal/zstd/zstd_errors.h +107 -0
  73. opengl_basisu-0.1.0a1/vendor/basis_universal/zstd/zstddeclib.c +22242 -0
@@ -0,0 +1,8 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0a1
4
+
5
+ - `open()` reads a KTX 2 file with an ETC1S or UASTC payload, or a `.basis` file, from a path or bytes.
6
+ - `Texture.transcode()` writes BC1, BC3, BC4, BC5, BC7, ETC1, ETC2, ASTC 4x4, RGBA8 or RGB565 as a NumPy array.
7
+ - `Target.info` carries each format's GL enums; `choose_target()` picks one from a context's extensions and version.
8
+ - Transcoder: basis_universal v2_50 (commit 9bebe167).
@@ -0,0 +1,60 @@
1
+ MIT License
2
+
3
+ Python wrapper Copyright (c) 2026 Mike C. Fletcher and Contributors
4
+
5
+ All rights reserved except as granted under the [Apache 2.0 license](https://github.com/BinomialLLC/basis_universal/blob/master/LICENSE).
6
+
7
+ NOTE ON AUTHORSHIP
8
+
9
+ This wrapper was generated by a large language model under human direction.
10
+ Jurisdictions differ on whether such material attracts copyright at all: some
11
+ require human authorship for a work to be protected, some extend protection to
12
+ computer-generated works, and in others the question is unsettled. Where no
13
+ copyright subsists in part or all of this software, the permission granted
14
+ above has nothing to operate on there and that material may be used freely;
15
+ where copyright does subsist, the terms above govern it. The disclaimer of
16
+ warranty and limitation of liability apply in either case.
17
+
18
+ Basis Universal™ Supercompressed GPU Texture Compression Library
19
+
20
+ Copyright © 2016–2026 Binomial LLC.
21
+ All rights reserved except as granted under the [Apache 2.0 license](https://github.com/BinomialLLC/basis_universal/blob/master/LICENSE).
22
+ "Basis Universal" is a trademark of Binomial LLC.
23
+
24
+ The documents in the Basis Universal wiki, and the Basis Universal library, example, and tool source code, fall under the Apache 2.0 license, unless otherwise explicitly indicated.
25
+
26
+ Redistributions or derivative works must include a readable copy of the attribution notices from this NOTICE file (see Apache License 2.0 §4(d)).
27
+
28
+ If you modify the Basis Universal source code, specifications, or wiki documents and redistribute the files, you must cause any modified files to carry prominent notices stating that you changed the files (see Apache 2.0 §4(b)).
29
+
30
+ **This software, documentation and specifications are provided "as is", without warranty of any kind (see Apache 2.0 §§7–8).**
31
+
32
+ For Zstandard software
33
+
34
+ Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
35
+
36
+ Redistribution and use in source and binary forms, with or without modification,
37
+ are permitted provided that the following conditions are met:
38
+
39
+ * Redistributions of source code must retain the above copyright notice, this
40
+ list of conditions and the following disclaimer.
41
+
42
+ * Redistributions in binary form must reproduce the above copyright notice,
43
+ this list of conditions and the following disclaimer in the documentation
44
+ and/or other materials provided with the distribution.
45
+
46
+ * Neither the name Facebook nor the names of its contributors may be used to
47
+ endorse or promote products derived from this software without specific
48
+ prior written permission.
49
+
50
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
51
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
52
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
53
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
54
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
55
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
56
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
57
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
58
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
59
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
60
+
@@ -0,0 +1,11 @@
1
+ # The vendored transcoder is compiled by every source build, so the sdist
2
+ # carries all of it, with its licence and notice files.
3
+ graft vendor
4
+ include NOTICE
5
+ include tox.ini
6
+ include CHANGELOG.md
7
+ recursive-include docs *.md
8
+ recursive-include tests *.py
9
+ recursive-include tests/fixtures *.ktx2 *.basis *.json
10
+ recursive-include tools *.py
11
+ include src/opengl_basisu/_shim.hpp
@@ -0,0 +1,27 @@
1
+ opengl_basisu
2
+ Copyright (c) 2026 Mike C. Fletcher and Contributors
3
+ Licensed under the MIT License; see LICENSE.
4
+
5
+ The compiled extension contains third-party code, copied unmodified into
6
+ vendor/basis_universal from basis_universal, tag v2_50
7
+ (commit 9bebe16726b3a61c8c213eeee3b7cffb462ef34e,
8
+ https://github.com/BinomialLLC/basis_universal):
9
+
10
+ * The Basis Universal transcoder (vendor/basis_universal/transcoder).
11
+ Licensed under the Apache License, Version 2.0; see
12
+ vendor/basis_universal/LICENSE. Its attribution notice, reproduced as
13
+ Apache 2.0 section 4(d) requires:
14
+
15
+ Basis Universal(TM) Supercompressed GPU Texture Compression Library
16
+ Copyright (c) 2016-2026 Binomial LLC.
17
+ All rights reserved except as granted under the Apache 2.0 license.
18
+ "Basis Universal" is a trademark of Binomial LLC.
19
+
20
+ The full notice is vendor/basis_universal/NOTICE.
21
+
22
+ * The Zstandard single-file decoder (vendor/basis_universal/zstd).
23
+ Copyright (c) Meta Platforms, Inc. and affiliates. Zstandard is offered
24
+ under a choice of the BSD license or the GPLv2; this package takes it under
25
+ the BSD license in vendor/basis_universal/zstd/LICENSE.
26
+
27
+ No basis_universal encoder code is included.
@@ -0,0 +1,182 @@
1
+ Metadata-Version: 2.4
2
+ Name: opengl_basisu
3
+ Version: 0.1.0a1
4
+ Summary: Basis Universal KTX 2 and .basis transcoding to GPU texture formats, as NumPy arrays
5
+ Author-email: "Mike C. Fletcher" <mcfletch@vrplumber.com>
6
+ License-Expression: MIT AND Apache-2.0 AND BSD-3-Clause
7
+ Project-URL: Homepage, https://github.com/mcfletch/opengl_basisu
8
+ Project-URL: Repository, https://github.com/mcfletch/opengl_basisu
9
+ Keywords: basis,basisu,ktx2,KHR_texture_basisu,etc1s,uastc,texture,compression,glTF,numpy,BC7,ASTC
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Programming Language :: Python :: Implementation :: CPython
20
+ Classifier: Programming Language :: C++
21
+ Classifier: Topic :: Multimedia :: Graphics :: 3D Rendering
22
+ Classifier: Topic :: Multimedia :: Graphics :: Graphics Conversion
23
+ Requires-Python: >=3.10
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ License-File: NOTICE
27
+ License-File: vendor/basis_universal/LICENSE
28
+ License-File: vendor/basis_universal/NOTICE
29
+ License-File: vendor/basis_universal/zstd/LICENSE
30
+ Requires-Dist: numpy>=1.24
31
+ Provides-Extra: test
32
+ Requires-Dist: pytest; extra == "test"
33
+ Provides-Extra: dev
34
+ Requires-Dist: opengl_basisu[test]; extra == "dev"
35
+ Requires-Dist: mypy; extra == "dev"
36
+ Requires-Dist: ruff; extra == "dev"
37
+ Requires-Dist: tox>=4.0; extra == "dev"
38
+ Requires-Dist: Cython>=3.0; extra == "dev"
39
+ Dynamic: license-file
40
+
41
+ # opengl_basisu: Basis Universal transcoding as NumPy arrays
42
+
43
+ Transcode a KTX 2 file with a Basis Universal payload (ETC1S or UASTC, the two
44
+ codecs glTF's `KHR_texture_basisu` allows), or a `.basis` file, to a GPU
45
+ texture format: BC1, BC3, BC4, BC5, BC7, ETC1, ETC2 or ASTC 4x4 blocks, or
46
+ RGBA8 or RGB565 pixels. Input is a path or bytes; output is a NumPy array with
47
+ the OpenGL enums to upload it, as integers. Nothing here imports OpenGL.
48
+
49
+ ```python
50
+ import opengl_basisu
51
+
52
+ texture = opengl_basisu.open('albedo.ktx2')
53
+ texture.width, texture.height, texture.levels # 1024, 1024, 11
54
+ texture.codec, texture.srgb, texture.has_alpha # Codec.ETC1S, True, False
55
+
56
+ target = opengl_basisu.choose_target(
57
+ extensions, # the names the GL context reports
58
+ codec=texture.codec,
59
+ has_alpha=texture.has_alpha,
60
+ srgb=texture.srgb,
61
+ version=(4, 6),
62
+ )
63
+ for image in texture.transcode_levels(target):
64
+ if image.compressed:
65
+ glCompressedTexImage2D(
66
+ GL_TEXTURE_2D, image.level, image.gl_internal_format,
67
+ image.width, image.height, 0, image.nbytes, image.data,
68
+ )
69
+ else:
70
+ glTexImage2D(
71
+ GL_TEXTURE_2D, image.level, image.gl_internal_format,
72
+ image.width, image.height, 0, image.gl_format, image.gl_type, image.data,
73
+ )
74
+ ```
75
+
76
+ NumPy is the only runtime dependency.
77
+
78
+ ## Install
79
+
80
+ ```
81
+ pip install opengl_basisu
82
+ ```
83
+
84
+ Wheels are published for CPython 3.10 to 3.14 on Linux, Windows and macOS
85
+ (arm64 and x86_64). A source build needs Cython and a C++17 compiler.
86
+
87
+ ## What a texture reports
88
+
89
+ | Attribute | Meaning |
90
+ |---|---|
91
+ | `container` | `'ktx2'` or `'basis'` |
92
+ | `width`, `height` | level 0, in pixels |
93
+ | `levels` | mip levels, at least 1 |
94
+ | `faces` | 1, or 6 for a cube map |
95
+ | `layers`, `is_array` | array layers (at least 1), and whether the file declares an array |
96
+ | `codec` | a `Codec`: `ETC1S`, `UASTC_LDR_4x4`, or a newer basis_universal codec |
97
+ | `has_alpha` | the file stores alpha |
98
+ | `srgb` | the colour space is sRGB rather than linear |
99
+ | `is_video`, `y_flipped` | a video's frames are its layers; `y_flipped` is a `.basis` flag |
100
+
101
+ `texture.transcode(target, level=0, face=0, layer=0)` returns an `Image`.
102
+ `transcode_levels(target, face, layer)` returns the mip chain, largest first,
103
+ and `images(target)` yields every image in the file.
104
+
105
+ ## Targets
106
+
107
+ | `Target` | Array | GL internal format (linear / sRGB) | Provided by |
108
+ |---|---|---|---|
109
+ | `BC1` | (rows, cols, 8) | `0x83F0` / `0x8C4C` | `GL_EXT_texture_compression_s3tc`; sRGB also `GL_EXT_texture_sRGB` |
110
+ | `BC3` | (rows, cols, 16) | `0x83F3` / `0x8C4F` | as BC1 |
111
+ | `BC4` | (rows, cols, 8) | `0x8DBB` / none | GL 3.0, `GL_ARB_texture_compression_rgtc` |
112
+ | `BC5` | (rows, cols, 16) | `0x8DBD` / none | GL 3.0, `GL_ARB_texture_compression_rgtc` |
113
+ | `BC7` | (rows, cols, 16) | `0x8E8C` / `0x8E8D` | GL 4.2, `GL_ARB_texture_compression_bptc`, `GL_EXT_texture_compression_bptc` |
114
+ | `ETC1` | (rows, cols, 8) | `0x9274` / `0x9275` (ETC2 RGB8) | GL 4.3, GLES 3.0, `GL_ARB_ES3_compatibility` |
115
+ | `ETC2` | (rows, cols, 16) | `0x9278` / `0x9279` | GL 4.3, GLES 3.0, `GL_ARB_ES3_compatibility` |
116
+ | `ASTC_4x4` | (rows, cols, 16) | `0x93B0` / `0x93D0` | GLES 3.2, `GL_KHR_texture_compression_astc_ldr` |
117
+ | `RGBA8` | (height, width, 4) uint8 | `GL_RGBA8` / `GL_SRGB8_ALPHA8` | always |
118
+ | `RGB565` | (height, width) uint16 | `GL_RGB565` / none | GL 4.1, GLES 2.0 |
119
+
120
+ Block arrays are uint8, one 4x4 block per `(row, column)`, rows of blocks top
121
+ to bottom; `rows` and `cols` round the pixel size up to whole blocks.
122
+ `Target.X.info` is the `TargetFormat` with these values, and
123
+ `TargetFormat.available(extensions, version, es, srgb)` answers whether a
124
+ context provides it.
125
+
126
+ `choose_target()` takes the extension names, version and `es` flag of a
127
+ context, and the texture's codec, alpha and colour space. It walks
128
+ `preferences(codec, es, channels)` and returns the first target the context
129
+ provides, or `RGBA8`. On desktop GL the BC formats come before ETC because
130
+ desktop drivers commonly decode ETC2 to RGBA8 at upload. UASTC prefers ASTC
131
+ 4x4, which it transcodes to without re-encoding; ETC1S on GLES prefers ETC1
132
+ for the same reason. `channels='r'` or `'rg'` puts BC4 or BC5 first, for
133
+ single and two-channel data.
134
+
135
+ ## Errors
136
+
137
+ - `BasisuError` (a `ValueError`): truncated or corrupt data, or bytes that are
138
+ neither KTX 2 nor `.basis`.
139
+ - `UnsupportedError` (a `BasisuError`): a KTX 2 file whose payload is not a
140
+ Basis Universal codec (an uncompressed or a plain BCn KTX 2, for example), or
141
+ a high dynamic range codec asked for an 8-bit target.
142
+ - `ValueError`: an unknown target name. `IndexError`: a level, face or layer
143
+ the texture does not have.
144
+
145
+ The test suite truncates every fixture at every length and transcodes 5,000
146
+ randomly corrupted copies of each; corrupt input raises. `docs/API.md` has the
147
+ AddressSanitizer run of the same tests.
148
+
149
+ ## Limits
150
+
151
+ - High dynamic range codecs (UASTC HDR 4x4, ASTC HDR 6x6) open and report
152
+ their header, and have no target here.
153
+ - The layers of an ETC1S video are P-frames: transcode them in order from
154
+ layer 0.
155
+ - One `Texture` transcodes one image at a time (a lock serialises threads);
156
+ separate textures transcode in parallel with the GIL released.
157
+ - The transcoder addresses files of up to 4 GiB.
158
+
159
+ ## The transcoder
160
+
161
+ The compiled extension is Binomial's
162
+ [basis_universal](https://github.com/BinomialLLC/basis_universal) transcoder,
163
+ tag `v2_50` (commit `9bebe167`), with the Zstandard decoder it bundles. Both
164
+ are copied unmodified into `vendor/basis_universal`; `vendor/basis_universal/VERSION`
165
+ records the tag and commit, and `tools/vendor_basisu.py` repeats the copy for
166
+ another tag. No encoder code is included.
167
+
168
+ ## Test fixtures
169
+
170
+ `tests/fixtures` holds six files of under 600 bytes each, written by the
171
+ basis_universal `v2_50` encoder from flat-colour images by
172
+ `tools/make_fixtures.py`; its docstring has the build and the encoder flags
173
+ for each file. `tests/fixtures/colours.json` lists the colours the tests
174
+ compare against.
175
+
176
+ ## Licence
177
+
178
+ This package is Apache License 2.0. The extension also contains the
179
+ basis_universal transcoder under the Apache License 2.0
180
+ (`vendor/basis_universal/LICENSE`, `vendor/basis_universal/NOTICE`) and the
181
+ Zstandard decoder under the BSD licence (`vendor/basis_universal/zstd/LICENSE`).
182
+ `NOTICE` lists both.
@@ -0,0 +1,142 @@
1
+ # opengl_basisu: Basis Universal transcoding as NumPy arrays
2
+
3
+ Transcode a KTX 2 file with a Basis Universal payload (ETC1S or UASTC, the two
4
+ codecs glTF's `KHR_texture_basisu` allows), or a `.basis` file, to a GPU
5
+ texture format: BC1, BC3, BC4, BC5, BC7, ETC1, ETC2 or ASTC 4x4 blocks, or
6
+ RGBA8 or RGB565 pixels. Input is a path or bytes; output is a NumPy array with
7
+ the OpenGL enums to upload it, as integers. Nothing here imports OpenGL.
8
+
9
+ ```python
10
+ import opengl_basisu
11
+
12
+ texture = opengl_basisu.open('albedo.ktx2')
13
+ texture.width, texture.height, texture.levels # 1024, 1024, 11
14
+ texture.codec, texture.srgb, texture.has_alpha # Codec.ETC1S, True, False
15
+
16
+ target = opengl_basisu.choose_target(
17
+ extensions, # the names the GL context reports
18
+ codec=texture.codec,
19
+ has_alpha=texture.has_alpha,
20
+ srgb=texture.srgb,
21
+ version=(4, 6),
22
+ )
23
+ for image in texture.transcode_levels(target):
24
+ if image.compressed:
25
+ glCompressedTexImage2D(
26
+ GL_TEXTURE_2D, image.level, image.gl_internal_format,
27
+ image.width, image.height, 0, image.nbytes, image.data,
28
+ )
29
+ else:
30
+ glTexImage2D(
31
+ GL_TEXTURE_2D, image.level, image.gl_internal_format,
32
+ image.width, image.height, 0, image.gl_format, image.gl_type, image.data,
33
+ )
34
+ ```
35
+
36
+ NumPy is the only runtime dependency.
37
+
38
+ ## Install
39
+
40
+ ```
41
+ pip install opengl_basisu
42
+ ```
43
+
44
+ Wheels are published for CPython 3.10 to 3.14 on Linux, Windows and macOS
45
+ (arm64 and x86_64). A source build needs Cython and a C++17 compiler.
46
+
47
+ ## What a texture reports
48
+
49
+ | Attribute | Meaning |
50
+ |---|---|
51
+ | `container` | `'ktx2'` or `'basis'` |
52
+ | `width`, `height` | level 0, in pixels |
53
+ | `levels` | mip levels, at least 1 |
54
+ | `faces` | 1, or 6 for a cube map |
55
+ | `layers`, `is_array` | array layers (at least 1), and whether the file declares an array |
56
+ | `codec` | a `Codec`: `ETC1S`, `UASTC_LDR_4x4`, or a newer basis_universal codec |
57
+ | `has_alpha` | the file stores alpha |
58
+ | `srgb` | the colour space is sRGB rather than linear |
59
+ | `is_video`, `y_flipped` | a video's frames are its layers; `y_flipped` is a `.basis` flag |
60
+
61
+ `texture.transcode(target, level=0, face=0, layer=0)` returns an `Image`.
62
+ `transcode_levels(target, face, layer)` returns the mip chain, largest first,
63
+ and `images(target)` yields every image in the file.
64
+
65
+ ## Targets
66
+
67
+ | `Target` | Array | GL internal format (linear / sRGB) | Provided by |
68
+ |---|---|---|---|
69
+ | `BC1` | (rows, cols, 8) | `0x83F0` / `0x8C4C` | `GL_EXT_texture_compression_s3tc`; sRGB also `GL_EXT_texture_sRGB` |
70
+ | `BC3` | (rows, cols, 16) | `0x83F3` / `0x8C4F` | as BC1 |
71
+ | `BC4` | (rows, cols, 8) | `0x8DBB` / none | GL 3.0, `GL_ARB_texture_compression_rgtc` |
72
+ | `BC5` | (rows, cols, 16) | `0x8DBD` / none | GL 3.0, `GL_ARB_texture_compression_rgtc` |
73
+ | `BC7` | (rows, cols, 16) | `0x8E8C` / `0x8E8D` | GL 4.2, `GL_ARB_texture_compression_bptc`, `GL_EXT_texture_compression_bptc` |
74
+ | `ETC1` | (rows, cols, 8) | `0x9274` / `0x9275` (ETC2 RGB8) | GL 4.3, GLES 3.0, `GL_ARB_ES3_compatibility` |
75
+ | `ETC2` | (rows, cols, 16) | `0x9278` / `0x9279` | GL 4.3, GLES 3.0, `GL_ARB_ES3_compatibility` |
76
+ | `ASTC_4x4` | (rows, cols, 16) | `0x93B0` / `0x93D0` | GLES 3.2, `GL_KHR_texture_compression_astc_ldr` |
77
+ | `RGBA8` | (height, width, 4) uint8 | `GL_RGBA8` / `GL_SRGB8_ALPHA8` | always |
78
+ | `RGB565` | (height, width) uint16 | `GL_RGB565` / none | GL 4.1, GLES 2.0 |
79
+
80
+ Block arrays are uint8, one 4x4 block per `(row, column)`, rows of blocks top
81
+ to bottom; `rows` and `cols` round the pixel size up to whole blocks.
82
+ `Target.X.info` is the `TargetFormat` with these values, and
83
+ `TargetFormat.available(extensions, version, es, srgb)` answers whether a
84
+ context provides it.
85
+
86
+ `choose_target()` takes the extension names, version and `es` flag of a
87
+ context, and the texture's codec, alpha and colour space. It walks
88
+ `preferences(codec, es, channels)` and returns the first target the context
89
+ provides, or `RGBA8`. On desktop GL the BC formats come before ETC because
90
+ desktop drivers commonly decode ETC2 to RGBA8 at upload. UASTC prefers ASTC
91
+ 4x4, which it transcodes to without re-encoding; ETC1S on GLES prefers ETC1
92
+ for the same reason. `channels='r'` or `'rg'` puts BC4 or BC5 first, for
93
+ single and two-channel data.
94
+
95
+ ## Errors
96
+
97
+ - `BasisuError` (a `ValueError`): truncated or corrupt data, or bytes that are
98
+ neither KTX 2 nor `.basis`.
99
+ - `UnsupportedError` (a `BasisuError`): a KTX 2 file whose payload is not a
100
+ Basis Universal codec (an uncompressed or a plain BCn KTX 2, for example), or
101
+ a high dynamic range codec asked for an 8-bit target.
102
+ - `ValueError`: an unknown target name. `IndexError`: a level, face or layer
103
+ the texture does not have.
104
+
105
+ The test suite truncates every fixture at every length and transcodes 5,000
106
+ randomly corrupted copies of each; corrupt input raises. `docs/API.md` has the
107
+ AddressSanitizer run of the same tests.
108
+
109
+ ## Limits
110
+
111
+ - High dynamic range codecs (UASTC HDR 4x4, ASTC HDR 6x6) open and report
112
+ their header, and have no target here.
113
+ - The layers of an ETC1S video are P-frames: transcode them in order from
114
+ layer 0.
115
+ - One `Texture` transcodes one image at a time (a lock serialises threads);
116
+ separate textures transcode in parallel with the GIL released.
117
+ - The transcoder addresses files of up to 4 GiB.
118
+
119
+ ## The transcoder
120
+
121
+ The compiled extension is Binomial's
122
+ [basis_universal](https://github.com/BinomialLLC/basis_universal) transcoder,
123
+ tag `v2_50` (commit `9bebe167`), with the Zstandard decoder it bundles. Both
124
+ are copied unmodified into `vendor/basis_universal`; `vendor/basis_universal/VERSION`
125
+ records the tag and commit, and `tools/vendor_basisu.py` repeats the copy for
126
+ another tag. No encoder code is included.
127
+
128
+ ## Test fixtures
129
+
130
+ `tests/fixtures` holds six files of under 600 bytes each, written by the
131
+ basis_universal `v2_50` encoder from flat-colour images by
132
+ `tools/make_fixtures.py`; its docstring has the build and the encoder flags
133
+ for each file. `tests/fixtures/colours.json` lists the colours the tests
134
+ compare against.
135
+
136
+ ## Licence
137
+
138
+ This package is Apache License 2.0. The extension also contains the
139
+ basis_universal transcoder under the Apache License 2.0
140
+ (`vendor/basis_universal/LICENSE`, `vendor/basis_universal/NOTICE`) and the
141
+ Zstandard decoder under the BSD licence (`vendor/basis_universal/zstd/LICENSE`).
142
+ `NOTICE` lists both.
@@ -0,0 +1,101 @@
1
+ # opengl_basisu API
2
+
3
+ Everything below is importable from `opengl_basisu`.
4
+
5
+ ## Reading a file
6
+
7
+ `open(source) -> Texture`
8
+ : `source` is a path (`str` or `os.PathLike`) or the file's bytes (`bytes`,
9
+ `bytearray`, `memoryview`; the data is copied). Raises `BasisuError` for data
10
+ that is not a KTX 2 or `.basis` file the transcoder accepts, and
11
+ `UnsupportedError` for a KTX 2 file whose `vkFormat` is set and whose payload
12
+ the transcoder does not read.
13
+
14
+ `container_of(data) -> 'ktx2' | 'basis' | None`
15
+ : The container named by the first 12 bytes, without parsing the rest.
16
+
17
+ `Texture`
18
+ : `container`, `width`, `height`, `levels`, `faces`, `layers`, `is_array`,
19
+ `codec`, `has_alpha`, `srgb`, `is_video`, `y_flipped`, as in the README.
20
+
21
+ `level_size(level=0) -> (width, height)` in pixels.
22
+
23
+ `supports(target) -> bool`: whether the codec transcodes to `target`.
24
+
25
+ `transcode(target, level=0, face=0, layer=0) -> Image`. `target` is a
26
+ `Target` or its value (`'bc7'`). Raises `IndexError` for an index out of
27
+ range, `ValueError` for an unknown target name, `UnsupportedError` for a
28
+ codec and target the transcoder does not pair (any HDR codec with any target
29
+ here), and `BasisuError` when the transcoder rejects the image data.
30
+
31
+ `transcode_levels(target, face=0, layer=0) -> list[Image]`, largest first.
32
+
33
+ `images(target) -> Iterator[Image]`, by layer, then face, then level.
34
+
35
+ `Image`
36
+ : A frozen dataclass: `target`, `width`, `height` (pixels), `level`, `face`,
37
+ `layer`, `srgb` and `data` (a NumPy array). Properties: `compressed`,
38
+ `gl_internal_format` (the sRGB enum when `srgb` and the target has one),
39
+ `gl_format` and `gl_type` (uncompressed targets only, else `None`) and
40
+ `nbytes` (the `imageSize` argument of `glCompressedTexImage2D`).
41
+
42
+ ## Choosing a target
43
+
44
+ `Target`
45
+ : `BC1`, `BC3`, `BC4`, `BC5`, `BC7`, `ETC1`, `ETC2`, `ASTC_4x4`, `RGBA8`,
46
+ `RGB565`. A `str` enum: `Target('etc2') is Target.ETC2`. `Target.X.info` is
47
+ its `TargetFormat`.
48
+
49
+ `TargetFormat`
50
+ : `basis` (the transcoder's format number), `block` (`(4, 4)`, or `(1, 1)` for
51
+ pixels), `bytes_per_block` (per pixel when uncompressed), `channels`,
52
+ `gl_internal_format`, `gl_srgb_internal_format`, `extensions`, `gl_version`,
53
+ `gles_version`, `srgb_extensions`, `gl_format`, `gl_type`. Properties
54
+ `compressed` and `has_alpha`. `internal_format(srgb=False)` returns the enum
55
+ to upload with. `available(extensions, version=(0, 0), es=False, srgb=False)`
56
+ returns whether a context with those extensions and that version provides
57
+ the format.
58
+
59
+ `preferences(codec, es=False, channels='rgba') -> tuple[Target, ...]`
60
+ : The order `choose_target` tries, ending with `RGBA8`.
61
+
62
+ `choose_target(extensions, codec=Codec.ETC1S, has_alpha=False, version=(0, 0), es=False, srgb=False, channels='rgba') -> Target`
63
+ : The first target in `preferences` the context provides, skipping formats
64
+ without alpha when `has_alpha` is set and formats without an sRGB enum when
65
+ `srgb` is set. Returns `RGBA8` when nothing compressed is available.
66
+
67
+ `Codec`
68
+ : An `IntEnum` of the transcoder's `basis_tex_format` values. `Codec.X.is_hdr`
69
+ is true for `UASTC_HDR_4x4`, `ASTC_HDR_6x6` and `UASTC_HDR_6x6`.
70
+
71
+ ## Errors
72
+
73
+ `BasisuError(ValueError)`, and its subclass `UnsupportedError`.
74
+
75
+ ## Versions
76
+
77
+ `__version__` is this package's version. `BASISU_VERSION` is the
78
+ basis_universal tag compiled in; `vendor/basis_universal/VERSION` has the
79
+ commit.
80
+
81
+ ## Sanitizer run
82
+
83
+ The fuzz tests in `tests/test_errors.py` corrupt and truncate every fixture.
84
+ To run them against an AddressSanitizer and UndefinedBehaviorSanitizer build,
85
+ copy the project to a scratch directory, change the non-Windows `CXX_FLAGS`
86
+ and `C_FLAGS` in its `setup.py` to `-O1 -g -fsanitize=address,undefined
87
+ -fno-omit-frame-pointer`, then:
88
+
89
+ ```
90
+ LDFLAGS=-fsanitize=address,undefined python setup.py build_ext --inplace
91
+ LD_PRELOAD="$(gcc -print-file-name=libasan.so) $(gcc -print-file-name=libubsan.so)" \
92
+ ASAN_OPTIONS=detect_leaks=0 \
93
+ UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1:suppressions=ubsan.supp \
94
+ PYTHONPATH=src python -m pytest -s tests -o addopts=
95
+ ```
96
+
97
+ `ubsan.supp` holds one line, `alignment:basisu_transcoder.cpp`. The UASTC
98
+ bit reader in the transcoder loads 16-bit words from odd addresses inside a
99
+ block, which x86-64 and arm64 execute; UBSan reports each such load. With that
100
+ suppression, 30,000 corrupted copies of each fixture run clean on basis_universal
101
+ `v2_50`.