diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml new file mode 100644 index 0000000..a7839ac --- /dev/null +++ b/.github/workflows/doc.yml @@ -0,0 +1,57 @@ +# clone of: https://github.com/mitmproxy/pdoc/blob/main/.github/workflows/docs.yml +name: astc-encoder-py docs + +# build the documentation whenever there are new commits on main +on: + push: + branches: + - main + # Alternative: only build for tags. + # tags: + # - '*' + +# security: restrict permissions for CI jobs. +permissions: + contents: read + +jobs: + # Build the documentation and upload the static HTML files as an artifact. + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install local package + run: pip install -e . + + - name: Install pdoc + run: pip install pdoc pillow + + # ADJUST THIS: build your documentation into docs/. + # We use a custom build script for pdoc itself, ideally you just run `pdoc -o docs/ ...` here. + - run: pdoc --docformat numpy -o docs/ ./astc_encoder + + - uses: actions/upload-pages-artifact@v3 + with: + path: docs/ + + # Deploy the artifact to GitHub pages. + # This is a separate job so that only actions/deploy-pages has the necessary permissions. + deploy: + needs: build + runs-on: ubuntu-latest + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml deleted file mode 100644 index bcee21f..0000000 --- a/.github/workflows/python-package.yml +++ /dev/null @@ -1,81 +0,0 @@ -name: Build & Publish wheels -on: - push: - branches: - - master - pull_request: - branches: - - master - -jobs: - build_sdist: - name: Build source distribution - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Build sdist - run: pipx run build --sdist - - - uses: actions/upload-artifact@v3 - with: - path: dist/*.tar.gz - - build_wheels: - strategy: - matrix: - # macos-13 is an intel runner, macos-14 is apple silicon - os: [ubuntu-latest, windows-latest, macos-13] - cp: ["cp37", "cp38", "cp39", "cp310", "cp311", "cp312", "cp313"] - - name: Build ${{ matrix.cp }} wheels on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Set up QEMU - if: runner.os == 'Linux' - uses: docker/setup-qemu-action@v3 - with: - platforms: all - - - name: Build wheels - uses: joerick/cibuildwheel@v2.20 - env: - CIBW_ARCHS_LINUX: auto aarch64 - CIBW_ARCHS_MACOS: x86_64 arm64 - CIBW_BUILD: | - ${{ matrix.cp }}-manylinux_x86_64 - ${{ matrix.cp }}-manylinux_aarch64 - ${{ matrix.cp }}-win_amd64 - ${{ matrix.cp }}-win32 - ${{ matrix.cp }}-macosx_x86_64 - ${{ matrix.cp }}-macosx_arm64 - CIBW_TEST_REQUIRES: setuptools pytest pillow imagehash texture2ddecoder - CIBW_TEST_COMMAND: pytest -v -s {package}/tests - CIBW_TEST_SKIP: "*-macosx* *-manylinux_i686 *-win32" - - - uses: actions/upload-artifact@v3 - with: - path: ./wheelhouse/*.whl - - upload_pypi: - name: Publish to PyPI - needs: [build_wheels, build_sdist] - runs-on: ubuntu-latest - steps: - - uses: actions/download-artifact@v3 - with: - name: artifact - path: dist - - - uses: pypa/gh-action-pypi-publish@release/v1 - with: - user: __token__ - password: ${{ secrets.PYPI_TOKEN }} - skip_existing: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..380fa76 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,135 @@ +name: Build & Publish wheels +on: + workflow_dispatch + + +jobs: + build_sdist: + name: Build source distribution + runs-on: ubuntu-latest + environment: release + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + + - name: Build sdist + run: pipx run build --sdist + + - name: Install sdist + run: pip install dist/*.tar.gz + + - uses: actions/upload-artifact@v3 + with: + path: dist/*.tar.gz + + build_wheels: + name: Build wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + environment: release + strategy: + fail-fast: true + matrix: + os: [windows-latest, macos-latest] + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Build wheels + uses: joerick/cibuildwheel@v2.21.2 + env: + CIBW_TEST_SKIP: "*" + + - uses: actions/upload-artifact@v3 + with: + path: ./wheelhouse/*.whl + + build_wheels_ubuntu_manylinux: + name: Build x86 manylinux wheels on ubuntu-latest + runs-on: ubuntu-latest + environment: release + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Build wheels + uses: joerick/cibuildwheel@v2.21.2 + env: + CIBW_ENVIRONMENT_LINUX: CFLAGS="-D_mm256_cvtsi256_si32(x)=_mm256_extract_epi32((x),0)" + CIBW_SKIP: "*aarch64 *ppc64le *s390x *armv7l *-musllinux*" + CIBW_TEST_SKIP: "*" + + - uses: actions/upload-artifact@v3 + with: + path: ./wheelhouse/*.whl + + build_wheels_ubuntu_musllinux: + name: Build x86 musllinux wheels on ubuntu-latest + runs-on: ubuntu-latest + environment: release + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Build wheels + uses: joerick/cibuildwheel@v2.21.2 + env: + CIBW_SKIP: "*aarch64 *ppc64le *s390x *armv7l *-manylinux*" + CIBW_TEST_SKIP: "*" + + - uses: actions/upload-artifact@v3 + with: + path: ./wheelhouse/*.whl + + build_wheels_qemu: + name: Build wheels qemu on ubuntu-latest + runs-on: ubuntu-latest + environment: release + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + with: + platforms: all + + - name: Build wheels + uses: joerick/cibuildwheel@v2.21.2 + env: + CIBW_SKIP: "*x86_64 *i686" + CIBW_TEST_SKIP: "*" + + - uses: actions/upload-artifact@v3 + with: + path: ./wheelhouse/*.whl + + + upload_pypi: + name: Publish to PyPI + needs: [build_wheels, build_wheels_qemu, build_wheels_ubuntu_manylinux, build_wheels_ubuntu_musllinux, build_sdist] + runs-on: ubuntu-latest + environment: release + permissions: + id-token: write + steps: + - uses: actions/download-artifact@v3 + with: + name: artifact + path: dist + + - name: Publish package distributions to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..e618d99 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,29 @@ +name: Test +on: + push + +jobs: + build_wheels: + name: Build wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Set up Python + uses: actions/setup-python@v5 + if: matrix.os == 'ubuntu-latest' + with: + python-version: '3.x' + + - name: Install + run: pip install .[tests] + + - name: Run tests + run: pytest -vs ./tests diff --git a/etcpak/__init__.py b/etcpak/__init__.py index 43abefa..a2f1ca1 100644 --- a/etcpak/__init__.py +++ b/etcpak/__init__.py @@ -1,9 +1,28 @@ -from ._cpufeatures import check_cpufeatures +from archspec.cpu import host -if check_cpufeatures(): - from ._etcpak_simd import * # noqa: F403 -else: - from ._etcpak import * +local_host = host() + +__version__ = "0.9.10" + +try: + # pypi wheels won't have these for now + if local_host.family.name == "aarch64": + # archspec doesn't detect the relevant features for arm + # so we assume neon is available, + # as it's unlikely for a device using the lib to not have it + from ._etcpak_neon import * # type: ignore + elif local_host.family.name == "x86_64": + if "avx512bw" in local_host.features and "avx512vl" in local_host.features: + from ._etcpak_avx512 import * # type: ignore + elif "avx2" in local_host.features: + from ._etcpak_avx2 import * # type: ignore + elif "sse4_1" in local_host.features: + from ._etcpak_sse41 import * # type: ignore +except ImportError: + pass + +if "compress_bc1" not in locals(): + from ._etcpak_none import * # type: ignore # legacy mappings for backwards compatibility compress_to_dxt1 = compress_bc1 # noqa: F405 @@ -32,3 +51,45 @@ def set_use_heuristics(use_heuristics: bool) -> None: def get_use_multithreading() -> bool: raise NotImplementedError("This function was removed in etcpak 0.9.9") + + +__all__ = ( + "__version__", + "compress_bc1", + "compress_bc1_dither", + "compress_bc3", + "compress_bc5", + "compress_bc7", + "compress_etc1_rgb", + "compress_etc1_rgb_dither", + "compress_etc2_rgb", + "compress_etc2_rgba", + "compress_eac_r", + "compress_eac_rg", + "decompress_etc1_rgb", + "decompress_etc2_rgb", + "decompress_etc2_rgba", + "decompress_etc2_r11", + "decompress_etc2_rg11", + "decompress_bc1", + "decompress_bc3", + "decompress_bc4", + "decompress_bc5", + "decompress_bc7", + "BC7CompressBlockParams", + # legacy mappings for backwards compatibility + "compress_to_dxt1", + "compress_to_dxt1_dither", + "compress_to_dxt5", + "compress_to_etc1", + "compress_to_etc1_dither", + "compress_to_etc2", + "compress_to_etc2_rgba", + "decode_dxt1", + "decode_dxt5", + "decode_etc_rgba", + "compress_to_etc1_alpha", + "compress_to_etc2_alpha", + "set_use_heuristics", + "get_use_multithreading", +) diff --git a/etcpak/__init__.pyi b/etcpak/__init__.pyi index bed5513..8dea446 100644 --- a/etcpak/__init__.pyi +++ b/etcpak/__init__.pyi @@ -3,34 +3,51 @@ from typing import Optional class BC7CompressBlockParams: """BC7 compression block parameters. - Attributes: - m_mode_mask (int): The mode mask. - m_max_partitions (int): The maximum partitions. + Attributes + ---------- + m_mode_mask: int + The mode mask. + m_max_partitions: int + The maximum partitions. m_max_partitions may range from 0 (disables mode 1) to BC7ENC_MAX_PARTITIONS (64). The higher this value, the slower the compressor, but the higher the quality. - m_weights (list[int]): The weights. + m_weights: List[int] + The weights. Relative RGBA or YCbCrA weights. - m_uber_level (int): The uber level. + m_uber_level: int + The uber level. m_uber_level may range from 0 to BC7ENC_MAX_UBER_LEVEL (4). The higher this value, the slower the compressor, but the higher the quality. - m_perceptual (bool): Perceptual. + m_perceptual: bool + Perceptual. If m_perceptual is true, colorspace error is computed in YCbCr space, otherwise RGB. - m_try_least_squares (bool): Try least squares. + m_try_least_squares: bool + Try least squares. Set m_try_least_squares to false for slightly faster/lower quality compression. - m_mode17_partition_estimation_filterbank (bool): Mode 17 partition estimation filterbank. + m_mode17_partition_estimation_filterbank: bool + Mode 17 partition estimation filterbank. When m_mode17_partition_estimation_filterbank, the mode1 partition estimator skips lesser used partition patterns unless they are strongly predicted to be potentially useful. There's a slight loss in quality with this enabled (around .08 dB RGB PSNR or .05 dB Y PSNR), but up to a 11% gain in speed depending on the other settings. - m_force_selectors (bool): Force selectors. - m_force_alpha (bool): Force alpha. - m_quant_mode6_endpoints (bool): Quant mode 6 endpoints. - m_bias_mode1_pbits (bool): Bias mode 1 pbits. - m_pbit1_weigh (float): Pbit 1 weigh. - m_mode1_error_weight (float): Mode 1 error weight. - m_mode5_error_weight (float): Mode 5 error weight. - m_mode6_error_weight (float): Mode 6 error weight. - m_mode7_error_weight (float): Mode 7 error weight. + m_force_selectors: bool + Force selectors. + m_force_alpha: bool + Force alpha. + m_quant_mode6_endpoints: bool + Quant mode 6 endpoints. + m_bias_mode1_pbits: bool + Bias mode 1 pbits. + m_pbit1_weight: float + Pbit 1 weight. + m_mode1_error_weight: float + Mode 1 error weight. + m_mode5_error_weight: float + Mode 5 error weight. + m_mode6_error_weight: float + Mode 6 error weight. + m_mode7_error_weight: float + Mode 7 error weight. """ m_mode_mask: int = 2**32 - 1 diff --git a/etcpak/_cpufeatures.pyi b/etcpak/_cpufeatures.pyi deleted file mode 100644 index dc71265..0000000 --- a/etcpak/_cpufeatures.pyi +++ /dev/null @@ -1,3 +0,0 @@ -def check_cpufeatures() -> bool: - """Checks if the local CPU supports the SIMD instruction set required by the etcpak simd build.""" - ... diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..b5bb065 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,76 @@ +[build-system] +requires = [ + "setuptools<72.2.0", # pp38 fails with higher setuptools + "wheel", + "archspec >= 0.2", +] +build-backend = "setuptools.build_meta" + +[project] +name = "etcpak" +authors = [{ name = "Rudolf Kolbe", email = "rkolbe96@gmail.com" }] +description = "python wrapper for etcpak" +readme = "README.md" +license = { file = "LICENSE" } +requires-python = ">=3.7" +keywords = ["astc", "texture"] +classifiers = [ + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Intended Audience :: Developers", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Topic :: Multimedia :: Graphics", + "Topic :: Software Development :: Libraries :: Python Modules", +] +dependencies = ["archspec >= 0.2"] +dynamic = ["version"] + +[project.urls] +"Homepage" = "https://github.com/K0lb3/etcpak" +"Bug Tracker" = "https://github.com/K0lb3/etcpak" + +[tool.setuptools.dynamic] +version = { attr = "etcpak.__version__" } + +[tool.cibuildwheel.linux] +archs = ["x86_64", "i686", "aarch64", "ppc64le", "s390x", "armv7l"] +build = "cp37-* pp3*" + +[tool.cibuildwheel.macos] +archs = ["x86_64", "arm64"] +build = "cp37-macosx_x86_64 cp38-macosx_arm64 pp3*" +before-build = "pip install delocate" +repair-wheel-command = "delocate-wheel -w {dest_dir} -v {wheel}" + +[tool.cibuildwheel.windows] +archs = ["AMD64", "x86", "ARM64"] +build = "cp37-win_amd64 cp37-win32 cp39-win_arm64 pp3*" + +# tests +[project.optional-dependencies] +tests = [ + "pytest >= 8.3.3", + "pillow >=10.4.0", + "imagehash >=4.3.1", + "texture2ddecoder", +] + +[tool.ruff] +include = ["pyproject.toml", "etcpak/*.py", "etcpak/*.pyi", "tests/*.py"] + +[tool.ruff.lint] +select = ["D"] + +[tool.ruff.lint.pydocstyle] +convention = "numpy" + +[tool.pytest.ini_options] +testpaths = ["tests"] diff --git a/setup.py b/setup.py index 5ac5339..12dedce 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,12 @@ -from setuptools import setup, Extension +from __future__ import annotations + +import os +from typing import List + +from archspec.cpu import host +from setuptools import Extension, setup from setuptools.command.build_ext import build_ext +from wheel.bdist_wheel import bdist_wheel ETCPAK_SOURCES = [ "bc7enc.cpp", @@ -21,121 +28,216 @@ "Timing.cpp", ] -with open("README.md", "r") as fh: - long_description = fh.read() +ETCPAK_HEADERS = [ + "b7enc.h", + "bcdec.h", + "Bitmap.hpp", + "BitmapDownsampled.hpp", + "BlockData.hpp", + "ColorSpace.hpp", + "DataProvider.hpp", + "Debug.hpp", + "Dither.hpp", + "Error.hpp", + "ForceInline.hpp", + "Math.hpp", + "MipMap.hpp", + "mmap.hpp", + "ProcessCommon.hpp", + "ProcessDxtc.hpp", + "ProcessRGB.hpp", + "Semaphore.hpp", + "System.hpp", + "Tables.hpp", + "TaskDispatch.hpp", + "Timing.hpp", + "Vector.hpp", + # lz4 + "lz4/lz4.h", + # png + "libpng/png.h", + "libpng/pngconf.h", +] + +class BuildConfig: + __SSE4_1__: bool = False + __AVX2__: bool = False + __AVX512BW__: bool = False + __AVX512VL__: bool = False + __ARM_NEON: bool = False + msvc_flags: List[str] = [] + unix_flags: List[str] = [] + def __init__(self, **kwargs): + for key, value in kwargs.items(): + setattr(self, key, value) + + def items(self): + return self.__dict__.items() -def add_msvc_flags(ext: Extension, plat_name: str, enable_simd: bool): - ext.extra_compile_args.extend( - [ - "/std:c++20", - "/Zc:strictStrings-", - "/DNOMINMAX", - "/GL", - ] +configs = { + "sse41": BuildConfig( + __SSE4_1__=True, + msvc_flags=["/arch:SSE4.1"], + unix_flags=["-msse4.1"] + ), + "avx2": BuildConfig( + __SSE4_1__=True, + __AVX2__=True, + msvc_flags=["/arch:AVX2"], + unix_flags=["-msse4.1", "-mavx2"] + ), + "avx512": BuildConfig( + __SSE4_1__=True, + __AVX2__=True, + __AVX512BW__=True, + __AVX512VL__=True, + msvc_flags=["/arch:AVX512"], + unix_flags=["-msse4.1", "-mavx2", "-mavx512vl", "-mavx512bw"] + ), + "neon": BuildConfig( + __ARM_NEON=True, ) - ext.extra_link_args = ["/LTCG:incremental"] - if enable_simd and "-amd64" in plat_name: - ext.extra_compile_args.extend(["/D__SSE4_1__", "/D__AVX2__", "/arch:AVX2"]) - - -def add_gcc_flags(ext: Extension, plat_name: str, enable_simd: bool): - ext.extra_compile_args.append("-std=c++20") - ext.extra_link_args = ["-flto"] - if enable_simd: - if "-arm" in plat_name or "-aarch64" in plat_name: - if "macosx" in plat_name: - native_arg = "-mcpu=apple-m1" - else: - native_arg = "-mcpu=native" - else: - native_arg = "-march=native" - ext.extra_compile_args.append(native_arg) +} class CustomBuildExt(build_ext): def build_extensions(self): - compiler_type = self.compiler.compiler_type + extra_compile_args: List[str] = [] + extra_link_args: List[str] = [] + + # check for cibuildwheel + cibuildwheel = os.environ.get("CIBUILDWHEEL", False) + + msvc: bool = False + if self.compiler.compiler_type == "msvc": + extra_compile_args = [ + "/std:c++20", + "/Zc:strictStrings-", + "/DNOMINMAX", + "/GL", + ] + extra_link_args = ["/LTCG:incremental"] + msvc = True + else: + extra_compile_args = [ + "-std=c++20", + "-flto", + ] + extra_link_args = ["-flto"] + + if not cibuildwheel: + # do some native optimizations for the current machine + # can't be used for generic builds + if "-arm" in self.plat_name or "-aarch64" in self.plat_name: + native_arg = "-mcpu=native" + else: + native_arg = "-march=native" + extra_compile_args.append(native_arg) + + local_host = host() + + if self.plat_name.endswith(("amd64", "x86_64")): + if cibuildwheel: + self.extensions.extend( + [ + ETCPAKExtension("none", BuildConfig()), + ETCPAKExtension("sse41", configs["sse41"]), + ETCPAKExtension("avx2", configs["avx2"]), + ETCPAKExtension("avx512", configs["avx512"]), + ] + ) + elif "avx512bw" in local_host.features and "avx512vl" in local_host.features: + self.extensions.append(ETCPAKExtension("avx512", configs["avx512"])) + elif "avx2" in local_host.features: + self.extensions.append(ETCPAKExtension("avx2", configs["avx2"])) + elif "sse4_1" in local_host.features: + self.extensions.append(ETCPAKExtension("sse41", configs["sse41"])) + + elif self.plat_name.endswith(("arm64", "aarch64")): + # TODO: somehow detect neon, sve128, sve256 + # atm assume neon is always available + self.extensions.append(ETCPAKExtension("neon", configs["neon"])) + elif self.plat_name.endswith("armv7l"): + # TODO: detect neon + pass + for ext in self.extensions: - enable_simd = ext.name != "etcpak._etcpak" - if compiler_type == "msvc": - add_msvc_flags(ext, self.plat_name, enable_simd) + ext: ETCPAKExtension + ext.extra_compile_args.extend(extra_compile_args) + ext.extra_link_args.extend(extra_link_args) + + build_config = ext.build_config + if msvc: + ext.extra_compile_args.extend(build_config.msvc_flags) else: - add_gcc_flags(ext, self.plat_name, enable_simd) + ext.extra_compile_args.extend(build_config.unix_flags) + + for key, value in build_config.items(): + if key.startswith("__"): + if value: + ext.define_macros.append((key, None)) + else: + ext.undef_macros.append(key) super().build_extensions() -def create_etcpak_extension(enable_simd: bool): - module_name = "_etcpak" - if enable_simd: - module_name += "_simd" - return Extension( - f"etcpak.{module_name}", - [ +class ETCPAKExtension(Extension): + build_config: BuildConfig + _needs_stub: bool = False + + def __init__(self, name: str, build_config: BuildConfig): + module_name = f"_etcpak_{name}" + super().__init__( + f"etcpak.{module_name}", + sources=[ "src/pylink.cpp", "src/dummy.cpp", *[f"src/etcpak/{src}" for src in ETCPAK_SOURCES], - ], - language="c++", - include_dirs=[ - "src/etcpak", - ], - extra_compile_args=[ - "-DNDEBUG", - "-DNO_GZIP", - # Mac fix due to .c problem - "-DBCDEC_IMPLEMENTATION=1", - f'-DMODULE_NAME="{module_name}"', - f"-DINIT_FUNC_NAME=PyInit_{module_name}", - ], - ) + ], + depends=[ + "src/pybc7params.hpp", + *[ + f"src/etcpak/{header}" + for header in ETCPAK_HEADERS + ], + ], + include_dirs=["src/etcpak"], + language="c++", + extra_compile_args=[ + "-DNDEBUG", + "-DNO_GZIP", + ], + define_macros=[ + ("Py_LIMITED_API", "0x03070000"), + ("MODULE_NAME", '"module_name"'), + ("INIT_FUNC_NAME", f"PyInit_{module_name}"), + # Mac fix due to .c problem + ("BCDEC_IMPLEMENTATION", "1"), + ], + py_limited_api=True, + ) + + self.build_config = build_config + +class bdist_wheel_abi3(bdist_wheel): + def get_tag(self): + python, abi, plat = super().get_tag() + + if python.startswith("cp"): + # on CPython, our wheels are abi3 and compatible back to 3.6 + return "cp37", "abi3", plat + + return python, abi, plat + setup( name="etcpak", - description="python wrapper for etcpak", - author="K0lb3", - version="0.9.12", packages=["etcpak"], - package_data={ - "etcpak": ["__init__.py", "__init__.pyi", "py.typed", "_cpufeatures.pyi"] - }, - keywords=["etc", "dxt", "texture", "python-c"], - classifiers=[ - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - "Intended Audience :: Developers", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", - "Topic :: Multimedia :: Graphics", - ], - url="https://github.com/K0lb3/etcpak", - download_url="https://github.com/K0lb3/etcpak/tarball/master", - long_description=long_description, - long_description_content_type="text/markdown", - ext_modules=[ - create_etcpak_extension(False), - create_etcpak_extension(True), - Extension( - "etcpak._cpufeatures", - [ - "src/cpufeatures.cpp", - ], - language="c++", - include_dirs=[ - "src/cpufeature/cpufeature", - ], - ), - ], - cmdclass={"build_ext": CustomBuildExt}, -) + package_data={"etcpak": ["*.py", "*.pyi", "py.typed"]}, + ext_modules=[ETCPAKExtension("none", BuildConfig())], + cmdclass={"build_ext": CustomBuildExt, "bdist_wheel": bdist_wheel_abi3}, +) \ No newline at end of file diff --git a/src/cpufeature b/src/cpufeature deleted file mode 160000 index 810a603..0000000 --- a/src/cpufeature +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 810a603980f36d16704740425d902d4395a33766 diff --git a/src/cpufeatures.cpp b/src/cpufeatures.cpp deleted file mode 100644 index 5b65193..0000000 --- a/src/cpufeatures.cpp +++ /dev/null @@ -1,126 +0,0 @@ -#include -// copied from: https://github.com/robbmcleod/cpufeature/blob/master/cpufeature/cpu_x86.c -#include -#if defined(__x86_64__) || defined(_M_X64) || defined(__i386) || defined(_M_IX86) -#if _WIN32 -#include "cpu_x86_windows.c" -#elif defined(__GNUC__) || defined(__clang__) -#include "cpu_x86_linux.c" -#elif -#error "Unsupported compiler" -#endif -#endif - -#ifndef __ARM_NEON -extern void cpuid(int32_t out[4], int32_t level, int32_t count); - -bool detect_OS_AVX(void) -{ - // Copied from: http://stackoverflow.com/a/22521619/922184 - - bool avxSupported = false; - - int cpuInfo[4]; - cpuid(cpuInfo, 1, 0); - - bool osUsesXSAVE_XRSTORE = (cpuInfo[2] & (1 << 27)) != 0; - bool cpuAVXSuport = (cpuInfo[2] & (1 << 28)) != 0; - - if (osUsesXSAVE_XRSTORE && cpuAVXSuport) - { - uint64_t xcrFeatureMask = xgetbv(_XCR_XFEATURE_ENABLED_MASK); - avxSupported = (xcrFeatureMask & 0x6) == 0x6; - } - - return avxSupported; -} - -bool detect_OS_AVX512(void) -{ - if (!detect_OS_AVX()) - return false; - - uint64_t xcrFeatureMask = xgetbv(_XCR_XFEATURE_ENABLED_MASK); - return (xcrFeatureMask & 0xe6) == 0xe6; -} -#endif - -PyObject *check_cpufeatures(PyObject *self) -{ -#ifdef __ARM_NEON - return Py_True; -#else - int info[4]; // [EAX, EBX, ECX, EDX] - cpuid(info, 0, 0); - int nIds = info[0]; - - if (nIds < 0x00000007) - { - return Py_False; - } - - cpuid(info, 0x80000000, 0); - uint32_t nExIds = info[0]; - - cpuid(info, 0x00000001, 0); -#ifdef __SSE4_1__ - if ((info[2] & ((int)1 << 19)) == 0) - return Py_False; -#endif -#ifdef __AVX__ - if (((info[2] & ((int)1 << 28)) == 0) || !detect_OS_AVX()) - return Py_False; -#endif - - cpuid(info, 0x00000007, 0); -#ifdef __AVX2__ - if ((info[1] & ((int)1 << 5)) == 0) - return Py_False; -#endif -#ifdef __AVX512__ - if (!detect_OS_AVX512()) - return Py_False; -#endif -#ifdef __AVX512BW__ - if ((info[1] & ((int)1 << 30)) == 0) - return Py_False; -#endif -#ifdef __AVX512VL__ - if ((info[1] & ((int)1 << 31)) == 0) - return Py_False; -#endif - - return Py_True; -#endif -} - -// Exported methods are collected in a table -static struct PyMethodDef method_table[] = { - {"check_cpufeatures", - (PyCFunction)check_cpufeatures, - METH_NOARGS, - ""}, - {NULL, - NULL, - 0, - NULL} // Sentinel value ending the table -}; - -// A struct contains the definition of a module -static PyModuleDef cpufeatures_module = { - PyModuleDef_HEAD_INIT, - "_cpufeatures", //"_etcpak", // Module name - "", - -1, // Optional size of the module state memory - method_table, - NULL, // Optional slot definitions - NULL, // Optional traversal function - NULL, // Optional clear function - NULL // Optional module deallocation function -}; - -// The module init function -PyMODINIT_FUNC PyInit__cpufeatures(void) -{ - return PyModule_Create(&cpufeatures_module); -} \ No newline at end of file diff --git a/src/pybc7params.hpp b/src/pybc7params.hpp index 97dd57a..9dd22cb 100644 --- a/src/pybc7params.hpp +++ b/src/pybc7params.hpp @@ -3,6 +3,8 @@ #include "structmember.h" #include "bc7enc.h" +PyObject *PyBC7CompressBlockParamsObject = nullptr; + typedef struct { PyObject_HEAD @@ -11,19 +13,15 @@ typedef struct static void PyBC7CompressBlockParams_dealloc(PyBC7CompressBlockParams *self) { - Py_TYPE(self)->tp_free((PyObject *)self); + PyObject_Del(self); } -static PyObject *PyBC7CompressBlockParams_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +static int PyBC7CompressBlockParams_init(PyBC7CompressBlockParams *self, PyObject *args, PyObject *kwds) { - PyBC7CompressBlockParams *self; - self = (PyBC7CompressBlockParams *)type->tp_alloc(type, 0); - if (self != NULL) - { - memset(&self->params, 0, sizeof(bc7enc_compress_block_params)); - bc7enc_compress_block_params_init(&self->params); - } - return (PyObject *)self; + + memset(&self->params, 0, sizeof(bc7enc_compress_block_params)); + bc7enc_compress_block_params_init(&self->params); + return 0; } // MEMBER DEFINITIONS............................................. @@ -145,42 +143,21 @@ static PyMethodDef PyBC7CompressBlockParams_methods[] = { }; // TYPE DEFINITION............................................. -static PyTypeObject PyBC7CompressBlockParamsType = { - PyVarObject_HEAD_INIT(NULL, 0) "etcpak.BC7CompressBlockParams", /* tp_name */ - sizeof(PyBC7CompressBlockParams), /* tp_basicsize */ - 0, /* tp_itemsize */ - (destructor)PyBC7CompressBlockParams_dealloc, /* tp_dealloc */ - 0, /* tp_print */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_reserved */ - 0, /* tp_repr */ - 0, /* tp_as_number */ - 0, /* tp_as_sequence */ - 0, /* tp_as_mapping */ - 0, /* tp_hash */ - 0, /* tp_call */ - 0, /* tp_str */ - 0, /* tp_getattro */ - 0, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ - "BC7 Compress Block Params", /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - PyBC7CompressBlockParams_methods, /* tp_methods */ - PyBC7CompressBlockParams_members, /* tp_members */ - PyBC7CompressBlockParams_getseters, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - 0, /* tp_init */ - 0, /* tp_alloc */ - PyBC7CompressBlockParams_new, /* tp_new */ +static PyType_Slot PyBC7CompressBlockParams_slots[] = { + {Py_tp_dealloc, (void *)PyBC7CompressBlockParams_dealloc}, + {Py_tp_doc, (void *)"BC7 Compress Block Params"}, + {Py_tp_init, (void *)PyBC7CompressBlockParams_init}, + {Py_tp_members, PyBC7CompressBlockParams_members}, + {Py_tp_getset, PyBC7CompressBlockParams_getseters}, + {Py_tp_methods, PyBC7CompressBlockParams_methods}, + {Py_tp_new, (void *)PyType_GenericNew}, + {0, NULL}, +}; + +static PyType_Spec PyBC7CompressBlockParamsType_Spec = { + "etcpak.BC7CompressBlockParams", // const char* name; + sizeof(PyBC7CompressBlockParams), // int basicsize; + 0, // int itemsize; + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, // unsigned int flags; + PyBC7CompressBlockParams_slots // PyType_Slot *slots; }; diff --git a/src/pylink.cpp b/src/pylink.cpp index 81fb2f7..742fb42 100644 --- a/src/pylink.cpp +++ b/src/pylink.cpp @@ -98,7 +98,7 @@ static PyObject *compress_bc7(PyObject *self, PyObject *args) } else { - if (!PyObject_IsInstance(params, (PyObject *)&PyBC7CompressBlockParamsType)) + if (!PyObject_IsInstance(params, PyBC7CompressBlockParamsObject)) { PyErr_SetString(PyExc_ValueError, "params must be an instance of BC7CompressBlockParams"); free(buf); @@ -273,12 +273,12 @@ static PyModuleDef etcpak_module = { NULL // Optional module deallocation function }; -static void add_type(PyObject *m, PyTypeObject *obj, const char *name) +static void add_type(PyObject *m, PyObject *obj, const char *name) { - if (PyType_Ready(obj) < 0) + if (PyType_Ready((PyTypeObject *)obj) < 0) return; Py_INCREF(obj); - PyModule_AddObject(m, name, (PyObject *)obj); + PyModule_AddObject(m, name, obj); } // The module init function @@ -287,6 +287,8 @@ PyMODINIT_FUNC INIT_FUNC_NAME(void) PyObject *m = PyModule_Create(&etcpak_module); if (m == NULL) return NULL; - add_type(m, &PyBC7CompressBlockParamsType, "BC7CompressBlockParams"); + + PyBC7CompressBlockParamsObject = PyType_FromSpec(&PyBC7CompressBlockParamsType_Spec); + add_type(m, PyBC7CompressBlockParamsObject, "BC7CompressBlockParams"); return m; } \ No newline at end of file