main: initial library
Some checks failed
CI / Code Style (autopep8) (push) Failing after 16s
CI / Tests (Python 3.12) (push) Successful in 20s
CI / Tests (Python 3.13) (push) Successful in 20s
CI / Tests (Python 3.14) (push) Successful in 19s
CI / Coverage (Python 3.12) (push) Failing after 7s

This commit is contained in:
2026-05-10 01:05:56 +03:00
commit a8740d88dd
17 changed files with 37642 additions and 0 deletions

116
.gitea/workflows/ci.yml Normal file
View File

@@ -0,0 +1,116 @@
name: CI
on:
push:
pull_request:
jobs:
style-check:
name: Code Style (autopep8)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Python 3.12
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install PDM + autopep8
run: |
python -m pip install --upgrade pip
pip install pdm autopep8
- name: Install dependencies
run: pdm install --dev
- name: Run autopep8 style check
run: |
autopep8 --recursive --diff --exit-code --ignore E501 src
continue-on-error: false
tests:
name: Tests (Python ${{ matrix.python-version }})
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.12", "3.13", "3.14"]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install PDM
run: |
python -m pip install --upgrade pip
pip install pdm
- name: Install dependencies
run: pdm install --dev
- name: Run tests
run: pdm run test
coverage:
name: Coverage (Python 3.12)
runs-on: ubuntu-latest
needs: tests
steps:
- name: Checkout (with parent)
uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Install Python 3.12
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install PDM
run: |
python -m pip install --upgrade pip
pip install pdm
- name: Run coverage on parent commit
run: |
git checkout HEAD^
pdm install --dev
pdm run coverage
pdm run python -m coverage json -o /tmp/parent-coverage.json
git checkout -
- name: Install dependencies
run: pdm install --dev
- name: Run coverage on current commit
run: |
pdm run coverage
pdm run python -m coverage json -o /tmp/current-coverage.json
- name: Check coverage did not decrease
run: |
pdm run python -c "
import json, sys
with open('/tmp/parent-coverage.json') as f:
parent = json.load(f)['totals']['percent_covered']
with open('/tmp/current-coverage.json') as f:
current = json.load(f)['totals']['percent_covered']
print(f'Parent coverage: {parent:.2f}%')
print(f'Current coverage: {current:.2f}%')
if current < parent:
print(f'ERROR: Coverage decreased by {parent - current:.2f}%')
sys.exit(1)
print(f'OK: Coverage maintained or improved by {current - parent:.2f}%')
"

14
.gitignore vendored Normal file
View File

@@ -0,0 +1,14 @@
.cache/
.coverage
.coverage-report/
.pdm.lock/
.vscode/
**/__pycache__/
**/*.pyc
.pdm-build/
dist/
build/
pytest_cache/
.venv/
var/
.env

19
.style.yapf Normal file
View File

@@ -0,0 +1,19 @@
[style]
based_on_style = pep8
column_limit = 240
blank_line_before_class_docstring = False
blank_lines_around_top_level_definition = 2
blank_line_before_nested_class_or_def = False
continuation_align_style = Fixed
continuation_indent_width = 4
dedent_closing_brackets = True
align_closing_bracket_with_visual_indent = False
split_before_closing_bracket = True
split_before_first_argument = False
coalesce_brackets = True
each_dict_entry_on_separate_line = True
spaces_around_dict_delimiters = True
spaces_before_comment = 1
split_all_comma_separated_values = True
split_before_dict_set_generator = True
allow_split_before_dict_value = False

47
README.md Normal file
View File

@@ -0,0 +1,47 @@
# dragiyski-vulkan-binding
Python bindings for the [Vulkan](https://www.vulkan.org/) graphics and compute API, generated on-demand from the official [Vulkan XML registry](https://github.com/KhronosGroup/Vulkan-Docs).
## Overview
The binding is *lazy*: the module structure is set up at import time, but individual symbols (types, constants, functions, macros) are resolved and bound only when first accessed. This keeps startup overhead minimal regardless of how large the registry is.
Bindings are derived from `dragiyski-vulkan-registry`, which parses the official Vulkan XML registry and exposes its taxonomy. The binding layer translates that taxonomy into Python-callable objects backed by `ctypes`, including:
- **Macros** — version utility macros such as `VK_MAKE_VERSION`, `VK_MAKE_API_VERSION`, and their corresponding accessors, exposed as plain Python callables with matching signatures.
- **Constants** — scalar values such as `VK_HEADER_VERSION`, exposed as Python `int` or `float`.
- **Types** — Vulkan structs, unions, handles, and enums, mapped to `ctypes` equivalents.
- **Commands** — Vulkan API commands, loadable via the standard `vkGetInstanceProcAddr` / `vkGetDeviceProcAddr` mechanism.
- **Callbacks** — Vulkan callback function pointer types, wrapped as `ctypes.CFUNCTYPE` factories.
## Requirements
- Python 3.12 or later
- [`pycparser`](https://github.com/eliben/pycparser) ≥ 2.23
- [`dragiyski-vulkan-registry`](https://git.dragiyski.org/dragiyski/vulkan-registry)
## Installation
```bash
pip install dragiyski-vulkan-binding
```
## Usage
```python
import ctypes
import dragiyski.vulkan.binding as vulkan
version = vulkan.VK_MAKE_API_VERSION(0, 1, 3, 0)
print(vulkan.VK_HEADER_VERSION)
vulkan_version = ctypes.c_uint32()
vulkan.vkEnumerateInstanceVersion(ctypes.byref(vulkan_version))
print('.'.join([
str(vulkan.VK_API_VERSION_MAJOR(vulkan_version.value)),
str(vulkan.VK_API_VERSION_MINOR(vulkan_version.value)),
str(vulkan.VK_API_VERSION_PATCH(vulkan_version.value)),
]))
```
Symbols are resolved lazily on first attribute access, so importing the module itself is fast.

302
pdm.lock generated Normal file
View File

@@ -0,0 +1,302 @@
# This file is @generated by PDM.
# It is not intended for manual editing.
[metadata]
groups = ["default", "dev"]
strategy = ["inherit_metadata"]
lock_version = "4.5.0"
content_hash = "sha256:303aec5e0f4d84b560289a07f002f0cc77a710d5c5175c76116669896742c757"
[[metadata.targets]]
requires_python = ">=3.12"
[[package]]
name = "colorama"
version = "0.4.6"
requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
summary = "Cross-platform colored terminal text."
groups = ["dev"]
marker = "sys_platform == \"win32\""
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
[[package]]
name = "coverage"
version = "7.13.5"
requires_python = ">=3.10"
summary = "Code coverage measurement for Python"
groups = ["dev"]
files = [
{file = "coverage-7.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:460cf0114c5016fa841214ff5564aa4864f11948da9440bc97e21ad1f4ba1e01"},
{file = "coverage-7.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e223ce4b4ed47f065bfb123687686512e37629be25cc63728557ae7db261422"},
{file = "coverage-7.13.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6e3370441f4513c6252bf042b9c36d22491142385049243253c7e48398a15a9f"},
{file = "coverage-7.13.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:03ccc709a17a1de074fb1d11f217342fb0d2b1582ed544f554fc9fc3f07e95f5"},
{file = "coverage-7.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f4818d065964db3c1c66dc0fbdac5ac692ecbc875555e13374fdbe7eedb4376"},
{file = "coverage-7.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:012d5319e66e9d5a218834642d6c35d265515a62f01157a45bcc036ecf947256"},
{file = "coverage-7.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8dd02af98971bdb956363e4827d34425cb3df19ee550ef92855b0acb9c7ce51c"},
{file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f08fd75c50a760c7eb068ae823777268daaf16a80b918fa58eea888f8e3919f5"},
{file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:843ea8643cf967d1ac7e8ecd4bb00c99135adf4816c0c0593fdcc47b597fcf09"},
{file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9d44d7aa963820b1b971dbecd90bfe5fe8f81cff79787eb6cca15750bd2f79b9"},
{file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7132bed4bd7b836200c591410ae7d97bf7ae8be6fc87d160b2bd881df929e7bf"},
{file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a698e363641b98843c517817db75373c83254781426e94ada3197cabbc2c919c"},
{file = "coverage-7.13.5-cp312-cp312-win32.whl", hash = "sha256:bdba0a6b8812e8c7df002d908a9a2ea3c36e92611b5708633c50869e6d922fdf"},
{file = "coverage-7.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:d2c87e0c473a10bffe991502eac389220533024c8082ec1ce849f4218dded810"},
{file = "coverage-7.13.5-cp312-cp312-win_arm64.whl", hash = "sha256:bf69236a9a81bdca3bff53796237aab096cdbf8d78a66ad61e992d9dac7eb2de"},
{file = "coverage-7.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ec4af212df513e399cf11610cc27063f1586419e814755ab362e50a85ea69c1"},
{file = "coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:941617e518602e2d64942c88ec8499f7fbd49d3f6c4327d3a71d43a1973032f3"},
{file = "coverage-7.13.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:da305e9937617ee95c2e39d8ff9f040e0487cbf1ac174f777ed5eddd7a7c1f26"},
{file = "coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3"},
{file = "coverage-7.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02ca0eed225b2ff301c474aeeeae27d26e2537942aa0f87491d3e147e784a82b"},
{file = "coverage-7.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:04690832cbea4e4663d9149e05dba142546ca05cb1848816760e7f58285c970a"},
{file = "coverage-7.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0590e44dd2745c696a778f7bab6aa95256de2cbc8b8cff4f7db8ff09813d6969"},
{file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d7cfad2d6d81dd298ab6b89fe72c3b7b05ec7544bdda3b707ddaecff8d25c161"},
{file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e092b9499de38ae0fbfbc603a74660eb6ff3e869e507b50d85a13b6db9863e15"},
{file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:48c39bc4a04d983a54a705a6389512883d4a3b9862991b3617d547940e9f52b1"},
{file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2d3807015f138ffea1ed9afeeb8624fd781703f2858b62a8dd8da5a0994c57b6"},
{file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee2aa19e03161671ec964004fb74b2257805d9710bf14a5c704558b9d8dbaf17"},
{file = "coverage-7.13.5-cp313-cp313-win32.whl", hash = "sha256:ce1998c0483007608c8382f4ff50164bfc5bd07a2246dd272aa4043b75e61e85"},
{file = "coverage-7.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:631efb83f01569670a5e866ceb80fe483e7c159fac6f167e6571522636104a0b"},
{file = "coverage-7.13.5-cp313-cp313-win_arm64.whl", hash = "sha256:f4cd16206ad171cbc2470dbea9103cf9a7607d5fe8c242fdf1edf36174020664"},
{file = "coverage-7.13.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0428cbef5783ad91fe240f673cc1f76b25e74bbfe1a13115e4aa30d3f538162d"},
{file = "coverage-7.13.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e0b216a19534b2427cc201a26c25da4a48633f29a487c61258643e89d28200c0"},
{file = "coverage-7.13.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:972a9cd27894afe4bc2b1480107054e062df08e671df7c2f18c205e805ccd806"},
{file = "coverage-7.13.5-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4b59148601efcd2bac8c4dbf1f0ad6391693ccf7a74b8205781751637076aee3"},
{file = "coverage-7.13.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:505d7083c8b0c87a8fa8c07370c285847c1f77739b22e299ad75a6af6c32c5c9"},
{file = "coverage-7.13.5-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:60365289c3741e4db327e7baff2a4aaacf22f788e80fa4683393891b70a89fbd"},
{file = "coverage-7.13.5-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1b88c69c8ef5d4b6fe7dea66d6636056a0f6a7527c440e890cf9259011f5e606"},
{file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5b13955d31d1633cf9376908089b7cebe7d15ddad7aeaabcbe969a595a97e95e"},
{file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f70c9ab2595c56f81a89620e22899eea8b212a4041bd728ac6f4a28bf5d3ddd0"},
{file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:084b84a8c63e8d6fc7e3931b316a9bcafca1458d753c539db82d31ed20091a87"},
{file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ad14385487393e386e2ea988b09d62dd42c397662ac2dabc3832d71253eee479"},
{file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7f2c47b36fe7709a6e83bfadf4eefb90bd25fbe4014d715224c4316f808e59a2"},
{file = "coverage-7.13.5-cp313-cp313t-win32.whl", hash = "sha256:67e9bc5449801fad0e5dff329499fb090ba4c5800b86805c80617b4e29809b2a"},
{file = "coverage-7.13.5-cp313-cp313t-win_amd64.whl", hash = "sha256:da86cdcf10d2519e10cabb8ac2de03da1bcb6e4853790b7fbd48523332e3a819"},
{file = "coverage-7.13.5-cp313-cp313t-win_arm64.whl", hash = "sha256:0ecf12ecb326fe2c339d93fc131816f3a7367d223db37817208905c89bded911"},
{file = "coverage-7.13.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fbabfaceaeb587e16f7008f7795cd80d20ec548dc7f94fbb0d4ec2e038ce563f"},
{file = "coverage-7.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9bb2a28101a443669a423b665939381084412b81c3f8c0fcfbac57f4e30b5b8e"},
{file = "coverage-7.13.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bd3a2fbc1c6cccb3c5106140d87cc6a8715110373ef42b63cf5aea29df8c217a"},
{file = "coverage-7.13.5-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6c36ddb64ed9d7e496028d1d00dfec3e428e0aabf4006583bb1839958d280510"},
{file = "coverage-7.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:380e8e9084d8eb38db3a9176a1a4f3c0082c3806fa0dc882d1d87abc3c789247"},
{file = "coverage-7.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e808af52a0513762df4d945ea164a24b37f2f518cbe97e03deaa0ee66139b4d6"},
{file = "coverage-7.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e301d30dd7e95ae068671d746ba8c34e945a82682e62918e41b2679acd2051a0"},
{file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:800bc829053c80d240a687ceeb927a94fd108bbdc68dfbe505d0d75ab578a882"},
{file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:0b67af5492adb31940ee418a5a655c28e48165da5afab8c7fa6fd72a142f8740"},
{file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c9136ff29c3a91e25b1d1552b5308e53a1e0653a23e53b6366d7c2dcbbaf8a16"},
{file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:cff784eef7f0b8f6cb28804fbddcfa99f89efe4cc35fb5627e3ac58f91ed3ac0"},
{file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:68a4953be99b17ac3c23b6efbc8a38330d99680c9458927491d18700ef23ded0"},
{file = "coverage-7.13.5-cp314-cp314-win32.whl", hash = "sha256:35a31f2b1578185fbe6aa2e74cea1b1d0bbf4c552774247d9160d29b80ed56cc"},
{file = "coverage-7.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:2aa055ae1857258f9e0045be26a6d62bdb47a72448b62d7b55f4820f361a2633"},
{file = "coverage-7.13.5-cp314-cp314-win_arm64.whl", hash = "sha256:1b11eef33edeae9d142f9b4358edb76273b3bfd30bc3df9a4f95d0e49caf94e8"},
{file = "coverage-7.13.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:10a0c37f0b646eaff7cce1874c31d1f1ccb297688d4c747291f4f4c70741cc8b"},
{file = "coverage-7.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b5db73ba3c41c7008037fa731ad5459fc3944cb7452fc0aa9f822ad3533c583c"},
{file = "coverage-7.13.5-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:750db93a81e3e5a9831b534be7b1229df848b2e125a604fe6651e48aa070e5f9"},
{file = "coverage-7.13.5-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ddb4f4a5479f2539644be484da179b653273bca1a323947d48ab107b3ed1f29"},
{file = "coverage-7.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8a7a2049c14f413163e2bdabd37e41179b1d1ccb10ffc6ccc4b7a718429c607"},
{file = "coverage-7.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1c85e0b6c05c592ea6d8768a66a254bfb3874b53774b12d4c89c481eb78cb90"},
{file = "coverage-7.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:777c4d1eff1b67876139d24288aaf1817f6c03d6bae9c5cc8d27b83bcfe38fe3"},
{file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6697e29b93707167687543480a40f0db8f356e86d9f67ddf2e37e2dfd91a9dab"},
{file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8fdf453a942c3e4d99bd80088141c4c6960bb232c409d9c3558e2dbaa3998562"},
{file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:32ca0c0114c9834a43f045a87dcebd69d108d8ffb666957ea65aa132f50332e2"},
{file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8769751c10f339021e2638cd354e13adeac54004d1941119b2c96fe5276d45ea"},
{file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cec2d83125531bd153175354055cdb7a09987af08a9430bd173c937c6d0fba2a"},
{file = "coverage-7.13.5-cp314-cp314t-win32.whl", hash = "sha256:0cd9ed7a8b181775459296e402ca4fb27db1279740a24e93b3b41942ebe4b215"},
{file = "coverage-7.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:301e3b7dfefecaca37c9f1aa6f0049b7d4ab8dd933742b607765d757aca77d43"},
{file = "coverage-7.13.5-cp314-cp314t-win_arm64.whl", hash = "sha256:9dacc2ad679b292709e0f5fc1ac74a6d4d5562e424058962c7bb0c658ad25e45"},
{file = "coverage-7.13.5-py3-none-any.whl", hash = "sha256:34b02417cf070e173989b3db962f7ed56d2f644307b2cf9d5a0f258e13084a61"},
{file = "coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179"},
]
[[package]]
name = "coverage"
version = "7.13.5"
extras = ["toml"]
requires_python = ">=3.10"
summary = "Code coverage measurement for Python"
groups = ["dev"]
dependencies = [
"coverage==7.13.5",
"tomli; python_full_version <= \"3.11.0a6\"",
]
files = [
{file = "coverage-7.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:460cf0114c5016fa841214ff5564aa4864f11948da9440bc97e21ad1f4ba1e01"},
{file = "coverage-7.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e223ce4b4ed47f065bfb123687686512e37629be25cc63728557ae7db261422"},
{file = "coverage-7.13.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6e3370441f4513c6252bf042b9c36d22491142385049243253c7e48398a15a9f"},
{file = "coverage-7.13.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:03ccc709a17a1de074fb1d11f217342fb0d2b1582ed544f554fc9fc3f07e95f5"},
{file = "coverage-7.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f4818d065964db3c1c66dc0fbdac5ac692ecbc875555e13374fdbe7eedb4376"},
{file = "coverage-7.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:012d5319e66e9d5a218834642d6c35d265515a62f01157a45bcc036ecf947256"},
{file = "coverage-7.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8dd02af98971bdb956363e4827d34425cb3df19ee550ef92855b0acb9c7ce51c"},
{file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f08fd75c50a760c7eb068ae823777268daaf16a80b918fa58eea888f8e3919f5"},
{file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:843ea8643cf967d1ac7e8ecd4bb00c99135adf4816c0c0593fdcc47b597fcf09"},
{file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9d44d7aa963820b1b971dbecd90bfe5fe8f81cff79787eb6cca15750bd2f79b9"},
{file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7132bed4bd7b836200c591410ae7d97bf7ae8be6fc87d160b2bd881df929e7bf"},
{file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a698e363641b98843c517817db75373c83254781426e94ada3197cabbc2c919c"},
{file = "coverage-7.13.5-cp312-cp312-win32.whl", hash = "sha256:bdba0a6b8812e8c7df002d908a9a2ea3c36e92611b5708633c50869e6d922fdf"},
{file = "coverage-7.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:d2c87e0c473a10bffe991502eac389220533024c8082ec1ce849f4218dded810"},
{file = "coverage-7.13.5-cp312-cp312-win_arm64.whl", hash = "sha256:bf69236a9a81bdca3bff53796237aab096cdbf8d78a66ad61e992d9dac7eb2de"},
{file = "coverage-7.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ec4af212df513e399cf11610cc27063f1586419e814755ab362e50a85ea69c1"},
{file = "coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:941617e518602e2d64942c88ec8499f7fbd49d3f6c4327d3a71d43a1973032f3"},
{file = "coverage-7.13.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:da305e9937617ee95c2e39d8ff9f040e0487cbf1ac174f777ed5eddd7a7c1f26"},
{file = "coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3"},
{file = "coverage-7.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02ca0eed225b2ff301c474aeeeae27d26e2537942aa0f87491d3e147e784a82b"},
{file = "coverage-7.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:04690832cbea4e4663d9149e05dba142546ca05cb1848816760e7f58285c970a"},
{file = "coverage-7.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0590e44dd2745c696a778f7bab6aa95256de2cbc8b8cff4f7db8ff09813d6969"},
{file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d7cfad2d6d81dd298ab6b89fe72c3b7b05ec7544bdda3b707ddaecff8d25c161"},
{file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e092b9499de38ae0fbfbc603a74660eb6ff3e869e507b50d85a13b6db9863e15"},
{file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:48c39bc4a04d983a54a705a6389512883d4a3b9862991b3617d547940e9f52b1"},
{file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2d3807015f138ffea1ed9afeeb8624fd781703f2858b62a8dd8da5a0994c57b6"},
{file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee2aa19e03161671ec964004fb74b2257805d9710bf14a5c704558b9d8dbaf17"},
{file = "coverage-7.13.5-cp313-cp313-win32.whl", hash = "sha256:ce1998c0483007608c8382f4ff50164bfc5bd07a2246dd272aa4043b75e61e85"},
{file = "coverage-7.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:631efb83f01569670a5e866ceb80fe483e7c159fac6f167e6571522636104a0b"},
{file = "coverage-7.13.5-cp313-cp313-win_arm64.whl", hash = "sha256:f4cd16206ad171cbc2470dbea9103cf9a7607d5fe8c242fdf1edf36174020664"},
{file = "coverage-7.13.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0428cbef5783ad91fe240f673cc1f76b25e74bbfe1a13115e4aa30d3f538162d"},
{file = "coverage-7.13.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e0b216a19534b2427cc201a26c25da4a48633f29a487c61258643e89d28200c0"},
{file = "coverage-7.13.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:972a9cd27894afe4bc2b1480107054e062df08e671df7c2f18c205e805ccd806"},
{file = "coverage-7.13.5-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4b59148601efcd2bac8c4dbf1f0ad6391693ccf7a74b8205781751637076aee3"},
{file = "coverage-7.13.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:505d7083c8b0c87a8fa8c07370c285847c1f77739b22e299ad75a6af6c32c5c9"},
{file = "coverage-7.13.5-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:60365289c3741e4db327e7baff2a4aaacf22f788e80fa4683393891b70a89fbd"},
{file = "coverage-7.13.5-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1b88c69c8ef5d4b6fe7dea66d6636056a0f6a7527c440e890cf9259011f5e606"},
{file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5b13955d31d1633cf9376908089b7cebe7d15ddad7aeaabcbe969a595a97e95e"},
{file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f70c9ab2595c56f81a89620e22899eea8b212a4041bd728ac6f4a28bf5d3ddd0"},
{file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:084b84a8c63e8d6fc7e3931b316a9bcafca1458d753c539db82d31ed20091a87"},
{file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ad14385487393e386e2ea988b09d62dd42c397662ac2dabc3832d71253eee479"},
{file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7f2c47b36fe7709a6e83bfadf4eefb90bd25fbe4014d715224c4316f808e59a2"},
{file = "coverage-7.13.5-cp313-cp313t-win32.whl", hash = "sha256:67e9bc5449801fad0e5dff329499fb090ba4c5800b86805c80617b4e29809b2a"},
{file = "coverage-7.13.5-cp313-cp313t-win_amd64.whl", hash = "sha256:da86cdcf10d2519e10cabb8ac2de03da1bcb6e4853790b7fbd48523332e3a819"},
{file = "coverage-7.13.5-cp313-cp313t-win_arm64.whl", hash = "sha256:0ecf12ecb326fe2c339d93fc131816f3a7367d223db37817208905c89bded911"},
{file = "coverage-7.13.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fbabfaceaeb587e16f7008f7795cd80d20ec548dc7f94fbb0d4ec2e038ce563f"},
{file = "coverage-7.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9bb2a28101a443669a423b665939381084412b81c3f8c0fcfbac57f4e30b5b8e"},
{file = "coverage-7.13.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bd3a2fbc1c6cccb3c5106140d87cc6a8715110373ef42b63cf5aea29df8c217a"},
{file = "coverage-7.13.5-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6c36ddb64ed9d7e496028d1d00dfec3e428e0aabf4006583bb1839958d280510"},
{file = "coverage-7.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:380e8e9084d8eb38db3a9176a1a4f3c0082c3806fa0dc882d1d87abc3c789247"},
{file = "coverage-7.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e808af52a0513762df4d945ea164a24b37f2f518cbe97e03deaa0ee66139b4d6"},
{file = "coverage-7.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e301d30dd7e95ae068671d746ba8c34e945a82682e62918e41b2679acd2051a0"},
{file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:800bc829053c80d240a687ceeb927a94fd108bbdc68dfbe505d0d75ab578a882"},
{file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:0b67af5492adb31940ee418a5a655c28e48165da5afab8c7fa6fd72a142f8740"},
{file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c9136ff29c3a91e25b1d1552b5308e53a1e0653a23e53b6366d7c2dcbbaf8a16"},
{file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:cff784eef7f0b8f6cb28804fbddcfa99f89efe4cc35fb5627e3ac58f91ed3ac0"},
{file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:68a4953be99b17ac3c23b6efbc8a38330d99680c9458927491d18700ef23ded0"},
{file = "coverage-7.13.5-cp314-cp314-win32.whl", hash = "sha256:35a31f2b1578185fbe6aa2e74cea1b1d0bbf4c552774247d9160d29b80ed56cc"},
{file = "coverage-7.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:2aa055ae1857258f9e0045be26a6d62bdb47a72448b62d7b55f4820f361a2633"},
{file = "coverage-7.13.5-cp314-cp314-win_arm64.whl", hash = "sha256:1b11eef33edeae9d142f9b4358edb76273b3bfd30bc3df9a4f95d0e49caf94e8"},
{file = "coverage-7.13.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:10a0c37f0b646eaff7cce1874c31d1f1ccb297688d4c747291f4f4c70741cc8b"},
{file = "coverage-7.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b5db73ba3c41c7008037fa731ad5459fc3944cb7452fc0aa9f822ad3533c583c"},
{file = "coverage-7.13.5-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:750db93a81e3e5a9831b534be7b1229df848b2e125a604fe6651e48aa070e5f9"},
{file = "coverage-7.13.5-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ddb4f4a5479f2539644be484da179b653273bca1a323947d48ab107b3ed1f29"},
{file = "coverage-7.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8a7a2049c14f413163e2bdabd37e41179b1d1ccb10ffc6ccc4b7a718429c607"},
{file = "coverage-7.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1c85e0b6c05c592ea6d8768a66a254bfb3874b53774b12d4c89c481eb78cb90"},
{file = "coverage-7.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:777c4d1eff1b67876139d24288aaf1817f6c03d6bae9c5cc8d27b83bcfe38fe3"},
{file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6697e29b93707167687543480a40f0db8f356e86d9f67ddf2e37e2dfd91a9dab"},
{file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8fdf453a942c3e4d99bd80088141c4c6960bb232c409d9c3558e2dbaa3998562"},
{file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:32ca0c0114c9834a43f045a87dcebd69d108d8ffb666957ea65aa132f50332e2"},
{file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8769751c10f339021e2638cd354e13adeac54004d1941119b2c96fe5276d45ea"},
{file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cec2d83125531bd153175354055cdb7a09987af08a9430bd173c937c6d0fba2a"},
{file = "coverage-7.13.5-cp314-cp314t-win32.whl", hash = "sha256:0cd9ed7a8b181775459296e402ca4fb27db1279740a24e93b3b41942ebe4b215"},
{file = "coverage-7.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:301e3b7dfefecaca37c9f1aa6f0049b7d4ab8dd933742b607765d757aca77d43"},
{file = "coverage-7.13.5-cp314-cp314t-win_arm64.whl", hash = "sha256:9dacc2ad679b292709e0f5fc1ac74a6d4d5562e424058962c7bb0c658ad25e45"},
{file = "coverage-7.13.5-py3-none-any.whl", hash = "sha256:34b02417cf070e173989b3db962f7ed56d2f644307b2cf9d5a0f258e13084a61"},
{file = "coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179"},
]
[[package]]
name = "dragiyski-vulkan-registry"
version = "0.0.1"
requires_python = ">=3.12"
url = "https://git.dragiyski.org/dragiyski/vulkan-registry/releases/download/v0.0.1/dragiyski_vulkan_registry-0.0.1-py3-none-any.whl"
summary = "Extracts and parses the Vulkan API registry XML file, providing a Python interface to access the data."
groups = ["default"]
files = [
{file = "dragiyski_vulkan_registry-0.0.1-py3-none-any.whl", hash = "sha256:b77dc42606d2a06db1ca4b00a2de46c590c0a42b75082115eda2bb09e9492005"},
]
[[package]]
name = "iniconfig"
version = "2.3.0"
requires_python = ">=3.10"
summary = "brain-dead simple config-ini parsing"
groups = ["dev"]
files = [
{file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"},
{file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"},
]
[[package]]
name = "packaging"
version = "26.2"
requires_python = ">=3.8"
summary = "Core utilities for Python packages"
groups = ["dev"]
files = [
{file = "packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e"},
{file = "packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661"},
]
[[package]]
name = "pluggy"
version = "1.6.0"
requires_python = ">=3.9"
summary = "plugin and hook calling mechanisms for python"
groups = ["dev"]
files = [
{file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"},
{file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"},
]
[[package]]
name = "pycparser"
version = "3.0"
requires_python = ">=3.10"
summary = "C parser in Python"
groups = ["default"]
files = [
{file = "pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992"},
{file = "pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29"},
]
[[package]]
name = "pygments"
version = "2.20.0"
requires_python = ">=3.9"
summary = "Pygments is a syntax highlighting package written in Python."
groups = ["dev"]
files = [
{file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"},
{file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"},
]
[[package]]
name = "pytest"
version = "9.0.3"
requires_python = ">=3.10"
summary = "pytest: simple powerful testing with Python"
groups = ["dev"]
dependencies = [
"colorama>=0.4; sys_platform == \"win32\"",
"exceptiongroup>=1; python_version < \"3.11\"",
"iniconfig>=1.0.1",
"packaging>=22",
"pluggy<2,>=1.5",
"pygments>=2.7.2",
"tomli>=1; python_version < \"3.11\"",
]
files = [
{file = "pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9"},
{file = "pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c"},
]
[[package]]
name = "pytest-cov"
version = "7.1.0"
requires_python = ">=3.9"
summary = "Pytest plugin for measuring coverage."
groups = ["dev"]
dependencies = [
"coverage[toml]>=7.10.6",
"pluggy>=1.2",
"pytest>=7",
]
files = [
{file = "pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678"},
{file = "pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2"},
]

49
pyproject.toml Normal file
View File

@@ -0,0 +1,49 @@
[build-system]
requires = ["pdm-backend", "build"]
build-backend = "pdm.backend"
[project]
name = "dragiyski-vulkan-binding"
version = "0.0.1"
description = "Lazy Vulkan bindings for Python, generated from the Vulkan XML registry."
readme = "README.md"
authors = [ { name = "Plamen Dragiyski", email = "plamen@dragiyski.org" } ]
requires-python = ">=3.12"
dependencies = [
"pycparser>=2.23",
"dragiyski-vulkan-registry @ https://git.dragiyski.org/dragiyski/vulkan-registry/releases/download/v0.0.1/dragiyski_vulkan_registry-0.0.1-py3-none-any.whl",
]
[dependency-groups]
dev = [
"pytest>=9.0.1",
"pytest-cov>=7.0.0",
]
[tool.pdm]
package-dir = "src"
[tool.pdm.build]
includes = ["src"]
excludes = ["tests"]
[tool.pdm.scripts]
test = "pytest"
coverage = "pytest --cov=dragiyski.vulkan --cov-report=html"
[tool.pytest]
minversion="0.9"
testpaths = [
"tests"
]
python_files = [
"test_*.py"
]
pythonpath = ["src"]
[tool.coverage.run]
branch = true
[tool.coverage.html]
directory = ".coverage-report/html"

View File

@@ -0,0 +1,6 @@
import sys
from ._implementation import Binding
from dragiyski.vulkan.registry import taxonomy
from ._lazy import create_lazy_module
sys.modules[__name__] = create_lazy_module(sys.modules[__name__], Binding)(taxonomy)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,990 @@
from collections import OrderedDict
from collections.abc import Callable, Collection, Mapping, Sequence
import ctypes
import inspect
import pycparser.c_ast as c_ast
from pycparser.c_parser import ParseError
from ...registry import Taxonomy
from ...registry.xml import Node
from .exception import UndefinedBindingException, CUnknownTypeException
from .cpreprocessor import CPreprocessor
from .cparser import CParser
from .cgenerator import CGenerator
from .ccompiler import CCompiler
from .c import python_ctype_map, c_type_map, platform_ctypes, python_ctype_signature_map
class VulkanBindingData(dict):
__slots__ = ('ctype', 'node', 'cdecl')
class VulkanTypeBindingData(VulkanBindingData):
__slots__ = ('members',)
class VulkanFunctionBindingData(VulkanBindingData):
__slots__ = ('result', 'arguments', 'signature')
class VulkanCallbackBindingData(VulkanFunctionBindingData):
__slots__ = ('user_data',)
class VulkanCommandBindingData(VulkanFunctionBindingData):
__slots__ = ('output', 'handle')
class VulkanElementData(dict):
__slots__ = ('ctype', 'node', 'cdecl')
class VulkanHandleData(dict):
__slots__ = ('ctype', 'node', 'define', 'parent')
class LazyBindingAccess(Mapping):
__slots__ = ('binding', 'names')
def __init__(self, binding, /, *names: str):
self.binding = binding
self.names = names
def __contains__(self, name: str):
return name in self.names
def __getitem__(self, name: str):
return self.binding[name]
def __len__(self):
return len(self.names)
def __iter__(self):
return iter(self.names)
def keys(self):
yield from self.names
def values(self):
for name in self.names:
yield self.binding[name]
def items(self):
for name in self.names:
yield name, self.binding[name]
def get(self, name: str, default=None):
if name in self.names:
try:
return self.binding[name]
except KeyError:
pass
return default
def __eq__(self, other):
if not isinstance(other, 'LazyBindingAccess'):
return NotImplemented
return self.binding is other.binding and set(self.names) == set(other.names)
def __ne__(self, other):
result = self.__eq__(other)
if result is NotImplemented:
return NotImplemented
return not result
class BindingImplementation:
class CFunction(Callable):
def __init__(self, binding: 'BindingImplementation', ast: c_ast.Node, args: Collection[str]):
self._binding = binding
self._ast = ast
parameters = [inspect.Parameter(arg, inspect.Parameter.POSITIONAL_OR_KEYWORD) for arg in args]
self.__signature__ = inspect.Signature(parameters)
def __call__(self, *args, **kwargs):
bound = self.__signature__.bind(*args, **kwargs)
bound.apply_defaults()
variables = dict(bound.arguments)
compiler = self._binding.CCompiler(self._binding, self._binding.cparser, variables=variables)
result = compiler.compile_value_expr(self._ast)
if type(result) in python_ctype_map:
return result.value
return result
preprocessor_method = {
CPreprocessor.CValueMacro: 'compile_define_value',
CPreprocessor.CFunctionMacro: 'compile_define_function',
}
c_complex_keyword = {
ctypes.Structure: 'struct',
ctypes.Union: 'union',
}
vulkan_handle_types = {
'VK_DEFINE_HANDLE': ctypes.c_void_p,
'VK_DEFINE_NON_DISPATCHABLE_HANDLE': ctypes.c_uint64
}
ctype_intentionally_none = {
'vkVoidFunction',
}
vulkan_handle_object_type_prefix = 'VK_OBJECT_TYPE_'
vulkan_debug_report_object_type_prefix = 'VK_DEBUG_REPORT_OBJECT_TYPE_'
def __init__(self, taxonomy: Taxonomy, *, cache: Mapping = dict(), undefined: Collection[str] = ()):
self.taxonomy = taxonomy
self.undefined = set(undefined)
self.exception = {}
self.cparser = CParser(self.taxonomy.ctypes)
self.cgenerator = CGenerator()
self.cpreprocessor = CPreprocessor(self.cparser, self.cgenerator)
self.CCompiler = CCompiler
self.cache = cache
self.ctypes_map = {
**c_type_map,
**platform_ctypes,
}
self.compile_map = {}
for category in self.taxonomy.vulkan_node_info:
if hasattr(self, f'compile_{category}'):
method = getattr(self, f'compile_{category}')
if callable(method):
self.compile_map[category] = method
self.get_vulkan_ctype_map = {
'bitmask': self.get_hidden_ctype,
'enum': self.get_hidden_ctype,
'struct': self.get_metadata_ctype,
'union': self.get_metadata_ctype,
'command': self.get_metadata_ctype,
'handle': self.get_metadata_ctype,
'callback': self.get_metadata_ctype,
}
self.init_preprocessor()
def __getitem__(self, name: str):
if name in self.cache:
return self.cache[name]
if name in self.undefined:
raise KeyError(name)
if name not in self.taxonomy.vulkan_names:
if name in self.taxonomy.alias_map:
alias = self.taxonomy.alias_map[name]
try:
value = self[alias]
except KeyError:
self.undefined.add(name)
raise KeyError(name)
self.cache[name] = value
return value
raise KeyError(name)
if name in self.exception:
raise self.exception[name]
try:
value = self.compile(name)
except UndefinedBindingException as exception:
self.undefined.add(name)
raise KeyError(name) from exception
except Exception as exception:
self.exception[name] = exception
raise exception
self.cache[name] = value
return value
def keys(self):
for name in self.taxonomy.vulkan_names:
if name not in self.undefined:
yield name
def __contains__(self, name):
return name in self.cache or name in self.taxonomy.vulkan_names and name not in self.undefined
def init_preprocessor(self):
for name in self.taxonomy.vulkan_node_info['define']:
node: Node
for node in self.taxonomy.nodes[name]:
if 'name' not in node.children or '#define' not in node.get_text():
continue
name_node: Node
name_node = node.get('name')
before_text = node.get_text_before(name_node)
after_text = node.get_text_after(name_node)
define_index = before_text.rfind('#define')
before_text = before_text[define_index:]
text = before_text + name_node.get_text() + after_text
text = text.splitlines()
for last_line_index in range(len(text))[::-1]:
if not text[last_line_index].endswith('\\'):
break
text = text[:last_line_index + 1]
text = '\n'.join(text)
self.cpreprocessor.define_macro(text, name=name_node.get_text())
for name in set(self.cpreprocessor.keys()):
macro = self.cpreprocessor[name]
code = ['int _ = ', name]
if isinstance(macro, self.cpreprocessor.CFunctionMacro):
code.append('(')
code.append(', '.join(macro.arguments))
code.append(')')
code.append(';')
code = ''.join(code)
try:
self.cpreprocessor.preprocess_code_ast(code)
except ParseError:
self.undefined.add(name)
del self.cpreprocessor[name]
def resolve_alias(self, name: str):
while name in self.taxonomy.alias_map:
name = self.taxonomy.alias_map[name]
return name
def get_ctype_internal(self, name: str):
if name in self.ctype_intentionally_none:
return None
if name in self.taxonomy.alias_map:
return self.get_ctype(self.taxonomy.alias_map[name])
if name in self.taxonomy.bit_bitmask_map:
return self.get_ctype(self.taxonomy.bit_bitmask_map[name])
if name.startswith('PFN_'):
return ctypes.POINTER(self.get_ctype(name[4:]))
if name in self.taxonomy.vulkan_names:
return self.get_ctype_vulkan(name)
if name in self.taxonomy.vulkan_node_info['basetype']:
return self.compile_basetype(name)
return None
def get_hidden_ctype(self, name: str):
return getattr(self[name], '__vulkan_ctype__')
def get_metadata_ctype(self, name: str):
return self[name].ctype
def get_ctype_vulkan(self, name: str):
return self.get_vulkan_ctype_map[self.taxonomy.vulkan_names[name]](name)
def get_ctype(self, name: str):
if name not in self.ctypes_map:
self.ctypes_map[name] = self.get_ctype_internal(name)
return self.ctypes_map[name]
def compile(self, name: str):
type = self.taxonomy.vulkan_names[name]
return self.compile_map[type](name)
def compile_define(self, name: str):
if name not in self.cpreprocessor:
raise UndefinedBindingException(f'Undefined binding for define {name}')
macro = self.cpreprocessor[name]
for macro_type in macro.__class__.__mro__:
if macro_type in self.preprocessor_method:
method_name = self.preprocessor_method[macro_type]
method = getattr(self, method_name)
return method(macro)
raise UndefinedBindingException(f'Unsupported macro type for define {name}: {type(macro)}')
def compile_basetype(self, name: str):
for node in self.taxonomy.nodes[name]:
node: Node
if 'name' not in node.children:
continue
code = [
node.get_text_before(node.get('name')).splitlines()[-1],
node.get('name').get_text(),
node.get_text_after(node.get('name')).splitlines()[0],
]
code = ' '.join(code)
try:
ast = self.cpreprocessor.preprocess_code_ast(code)
except ParseError as error:
raise CUnknownTypeException(f'Cannot parse basetype {name}: {error}') from error
self.ctypes_map[name] = None
compiler = self.CCompiler(self, self.cparser)
return compiler.compile_type(ast.ext[0].type)
raise CUnknownTypeException(f'Missing definition for CType: {name}')
def compile_value(self, name: str):
if name not in self.taxonomy.value_group_map:
raise UndefinedBindingException(f'Undefined value: {name}')
group = self.taxonomy.value_group_map[name]
if isinstance(group, str):
if group in self.taxonomy.bit_bitmask_map:
group = self.taxonomy.bit_bitmask_map[group]
enum = self[group]
if not hasattr(enum, name):
raise UndefinedBindingException(f'Undefined value: {name}')
return getattr(enum, name)
elif name in self.taxonomy.nodes:
for node in self.taxonomy.nodes[name]:
node: Node
if node.has_attribute('value') or node.has_attribute('bitpos'):
value = self.parse_enum_value_node(node)
if value is not None:
return value
raise UndefinedBindingException(f'Undefined value: {name}')
def compile_define_value(self, macro: CPreprocessor.CValueMacro):
ast = self.cpreprocessor.preprocess_code_ast(f'int _ = {macro.name};')
compiler = self.CCompiler(self, self.cparser)
result = compiler.compile_value_expr(ast.ext[0].init)
if type(result) in python_ctype_map:
return result.value
return result
def compile_define_function(self, macro: CPreprocessor.CFunctionMacro):
args = ', '.join(macro.arguments)
code = f'int _ = {macro.name}({args});'
ast = self.cpreprocessor.preprocess_code_ast(code)
return self.CFunction(self, ast.ext[0].init, macro.arguments)
def compile_enum(self, name: str):
from enum import IntEnum
return self.create_python_enum(name, IntEnum)
def compile_bitmask(self, name: str):
from enum import IntFlag
return self.create_python_enum(name, IntFlag)
def create_python_enum(self, name: str, base_class: type):
from enum import EnumType
bitmask_name = name
if name in self.taxonomy.bitmask_bit_map:
bitmask_name = self.taxonomy.bitmask_bit_map[name]
namespace = EnumType.__prepare__(name, (base_class,))
if bitmask_name in self.taxonomy.group_value_map:
for value_name in self.taxonomy.group_value_map[bitmask_name]:
value = None
for node in self.taxonomy.nodes[value_name]:
node: Node
if not self.taxonomy.is_valid_api(node):
continue
if node.get_attribute('extends') == bitmask_name:
value = self.parse_enum_value_extension_node(node)
if value is not None:
break
if node.has_attribute('value') or node.has_attribute('bitpos'):
value = self.parse_enum_value_node(node)
if value is not None:
break
if value is None:
raise RuntimeError(f'Failed to parse value for enum {name} value {value_name}')
namespace[value_name] = value
result = EnumType(name, (base_class,), namespace)
if bitmask_name in self.taxonomy.group_value_map:
for value_name in self.taxonomy.group_value_map[bitmask_name]:
value = getattr(result, value_name)
self.cache[value_name] = value
if value_name not in self.taxonomy.alias_target_map:
continue
for alias_name in self.taxonomy.alias_target_map[value_name]:
setattr(result, alias_name, value)
self.cache[alias_name] = value
ctype = ctypes.c_int
for node in self.taxonomy.nodes[name]:
node: Node
if 'type' in node.children:
ctype = self.get_ctype(node.get('type').get_text())
setattr(result, '__vulkan_ctype__', ctype)
return result
def parse_enum_value_extension_node(self, node: Node, *, allowed_type: Collection[type] | None = None):
if node.has_attribute('offset'):
extnumber = None
if node.has_attribute('extnumber'):
extnumber = self.cparser.parse_c_int(node.get_attribute('extnumber'))
else:
if node.path[-3:] == ['extension', 'require', 'enum']:
extension_node = node.parent_node.parent_node
if extension_node.has_attribute('number'):
extnumber = self.cparser.parse_c_int(extension_node.get_attribute('number'))
if extnumber is None:
return None
value = 1000000000 + (extnumber - 1) * 1000 + self.parse_c_code_int(node.get_attribute('offset'))
elif node.has_attribute('bitpos'):
bitpos = self.cparser.parse_c_int(node.get_attribute('bitpos'))
value = 1 << bitpos
elif node.has_attribute('value'):
value = self.parse_c_code_value(node.get_attribute('value'))
if node.get_attribute('dir') == '-':
value = -value
return value
def parse_enum_value_node(self, node: Node, *, allowed_type: Collection[type] | None = None):
value = None
if node.has_attribute('bitpos'):
bitpos = self.cparser.parse_c_int(node.get_attribute('bitpos'))
value = 1 << bitpos
elif node.has_attribute('value'):
value = self.parse_c_code_value(node.get_attribute('value'))
if value is None:
return None
if node.get_attribute('dir') == '-':
value = -value
if allowed_type is not None and not isinstance(value, tuple(allowed_type)):
raise ValueError(f'Invalid value type for enum value: expected ({", ".join([t.__name__ for t in allowed_type])}), got {type(value).__name__}')
return value
def parse_c_code_int(self, code: str):
value = self.parse_c_code_value(code)
if not isinstance(value, int):
raise ValueError(f'Expected integer value, got {type(value)} compiling code: {code}')
return value
def parse_c_code_value(self, code: str):
ast = self.cpreprocessor.preprocess_code_ast(f'int _ = {code};')
compiler = self.CCompiler(self, self.cparser)
result = compiler.compile_value_expr(ast.ext[0].init)
if type(result) in python_ctype_map:
return result.value
return result
def compile_struct(self, name: str):
return self.create_complex(name, ctypes.Structure)
def compile_union(self, name: str):
return self.create_complex(name, ctypes.Union)
def get_type_class(self, name: str):
if name.startswith('PFN_'):
callback_name = name[4:]
callback_name = self.resolve_alias(callback_name)
if callback_name in self.taxonomy.vulkan_node_info['callback']:
return callback_name, 'callback'
real_name = self.resolve_alias(name)
if real_name in self.taxonomy.bit_bitmask_map:
real_name = self.taxonomy.bit_bitmask_map[real_name]
real_name = self.resolve_alias(real_name)
if real_name in self.taxonomy.vulkan_names:
member_class = self.taxonomy.vulkan_names[real_name]
elif real_name in self.taxonomy.ctypes:
member_class = 'ctype'
else:
member_class = 'external'
return real_name, member_class
def create_complex(self, name: str, base_class: type):
binding_data = VulkanTypeBindingData()
binding_data.ctype = type(name, (base_class,), {})
self.cache[name] = binding_data
self.ctypes_map[name] = binding_data.ctype
complex_code = ['%s %s {' % (self.c_complex_keyword[base_class], name)]
members = OrderedDict()
fields = []
member_processed_attributes = {}
processed_attributes = {'api', 'name', 'category', 'structextends'}
for node in self.taxonomy.nodes[name]:
node: Node
if 'member' not in node.children:
continue
if not self.taxonomy.is_valid_api(node):
continue
binding_data.node = node
for member_node in node.get_all('member'):
member_node: Node
if not self.taxonomy.is_valid_api(member_node):
continue
if 'name' not in member_node.children:
raise ValueError(f'Member node for struct {name} is missing name attribute')
member_info = VulkanElementData()
member_info.node = member_node
member_info['is_string'] = False
member_name = member_node.get('name').get_text()
members[member_name] = member_info
member_processed_attributes[member_name] = {'api'}
if 'type' in member_node.children:
member_type = member_node.get('type').get_text()
member_info['type'], member_info['class'] = self.get_type_class(member_type)
if member_node.has_attribute('altlen'):
length_code = f'int _ = {member_node.get_attribute("altlen")};'
length_ast = self.cpreprocessor.preprocess_code_ast(length_code)
member_info['length'] = [length_ast.ext[0].init]
member_processed_attributes[member_name].update({'altlen', 'len'})
elif member_node.has_attribute('len'):
length_items = [s.strip() for s in member_node.get_attribute('len').split(',')]
if length_items[-1] == 'null-terminated':
member_info['is_string'] = True
length_items = length_items[:-1]
if len(length_items) > 0:
length_code = 'int _ = (%s);' % ', '.join(length_items)
length_ast = self.cpreprocessor.preprocess_code_ast(length_code).ext[0].init
length = []
if isinstance(length_ast, c_ast.ExprList):
for expr in length_ast.exprs:
if isinstance(expr, c_ast.ID):
length.append(expr.name)
else:
length.append(expr)
else:
if isinstance(length_ast, c_ast.ID):
length.append(length_ast.name)
else:
length.append(length_ast)
member_info['length'] = length
member_processed_attributes[member_name].add('len')
if (
member_node.has_attribute('values')
and
member_name == 'sType'
and
'type' in member_info
and
member_info['type'] == 'VkStructureType'
and
member_info['class'] == 'enum'
):
if ',' in member_node.get_attribute('values'):
raise ValueError(f'Multiple values not supported for sType member of struct {name}')
member_info['structure_type'] = self[member_node.get_attribute('values').strip()]
member_processed_attributes[member_name].add('values')
if member_node.has_attribute('optional'):
optional = []
for optional_value in member_node.get_attribute('optional').split(','):
optional_value = optional_value.strip().lower()
if optional_value == 'true':
optional.append(True)
elif optional_value == 'false':
optional.append(False)
else:
raise ValueError(f'Invalid optional value for member {member_name} of struct {name}: {optional_value}')
member_info['optional'] = optional[0] if len(optional) == 1 else optional
member_processed_attributes[member_name].add('optional')
complex_code.append('%s;' % member_node.get_text())
complex_code.append('};')
complex_code = '\n'.join(complex_code)
ast = self.cpreprocessor.preprocess_code_ast(complex_code)
compiler = self.CCompiler(self, self.cparser)
for decl in ast.ext[0].type.decls:
decl: c_ast.Decl
member_name = decl.name
member_info = members[member_name]
member_info.ctype = compiler.compile_type(decl.type)
member_info.cdecl = decl
if decl.bitsize is not None:
compiler = self.CCompiler(self, self.cparser)
bit_size = compiler.compile_value_expr(decl.bitsize)
if type(bit_size) in python_ctype_map:
bit_size = bit_size.value
if not isinstance(bit_size, int):
raise ValueError(f'Expected integer bit size for member {member_name} of struct {name}, got {type(bit_size)}')
fields.append((member_name, member_info.ctype, bit_size))
pass
else:
fields.append((member_name, member_info.ctype))
ptr_decl = decl.type
member_info['is_pointer'] = 0
while isinstance(ptr_decl, c_ast.PtrDecl):
member_info['is_pointer'] += 1
ptr_decl = ptr_decl.type
if member_info['is_pointer'] > 0 and 'optional' not in member_processed_attributes[member_name]:
member_info['optional'] = [False] * member_info['is_pointer'] if member_info['is_pointer'] > 1 else False
member_processed_attributes[member_name].add('optional')
for attribute_name in member_info.node.attributes:
if attribute_name not in member_processed_attributes[member_name]:
member_info[attribute_name] = member_info.node.get_attribute(attribute_name)
if base_class is ctypes.Structure and 'sType' in members and 'pNext' in members and members['sType'].get('structure_type') is not None:
binding_data['extends'] = self.taxonomy.vulkan_node_info[self.taxonomy.vulkan_names[name]][name]['extends']
binding_data['extended_by'] = LazyBindingAccess(self, *self.taxonomy.vulkan_node_info[self.taxonomy.vulkan_names[name]][name]['extended_by'])
binding_data['returned_only'] = False
if binding_data.node.get_attribute('returnedonly') == 'true':
binding_data['returned_only'] = True
processed_attributes.add('returnedonly')
binding_data['required_limit_type'] = False
if binding_data.node.get_attribute('requiredlimittype') == 'true':
binding_data['required_limit_type'] = True
processed_attributes.add('requiredlimittype')
if binding_data.node.has_attribute('allowduplicate'):
value = binding_data.node.get_attribute('allowduplicate').strip().lower()
if value == 'true':
binding_data['allow_duplicate'] = True
elif value == 'false':
binding_data['allow_duplicate'] = False
else:
raise ValueError(f'Invalid value for @allowduplicate in "{name}": {value}')
processed_attributes.add('allowduplicate')
for attribute_name in binding_data.node.attributes:
if attribute_name not in processed_attributes:
binding_data[attribute_name] = binding_data.node.get_attribute(attribute_name)
binding_data.cdecl = ast.ext[0]
binding_data.ctype._fields_ = fields
binding_data.members = members
return binding_data
def compile_callback(self, name: str):
binding_data = VulkanCallbackBindingData()
Constructor = ctypes.CFUNCTYPE
if hasattr(ctypes, 'WINFUNCTYPE'):
Constructor = ctypes.WINFUNCTYPE
code = {}
args = OrderedDict()
for node in self.taxonomy.nodes[name]:
node: Node
if not self.taxonomy.is_valid_api(node):
continue
if 'proto' not in node.children:
continue
binding_data.node = node
code['return'] = node.get('proto').get_text()
code['params'] = []
for param_node in node.get_all('param'):
param_info = VulkanElementData()
param_info.node = param_node
args[param_node.get('name').get_text()] = param_info
code['params'].append(param_node.get_text())
if 'type' in param_node.children:
param_type = param_node.get('type').get_text()
param_info['type'], param_info['class'] = self.get_type_class(param_type)
if len(code) == 0:
raise UndefinedBindingException(f'Undefined callback: {name}')
code = ' '.join([code['return'], '(', ', '.join(code['params']), ');'])
ast = self.cpreprocessor.preprocess_code_ast(code).ext[0].type
compiler = self.CCompiler(self, self.cparser)
ctype_params = [compiler.compile_type(ast.type)]
return_info = VulkanElementData()
return_info.ctype = ctype_params[0]
return_info.cdecl = ast.type
proto_node = binding_data.node.get('proto')
return_info.node = proto_node
if 'type' in proto_node.children:
return_type = proto_node.get('type').get_text()
return_info['type'], return_info['class'] = self.get_type_class(return_type)
binding_data.result = return_info
if isinstance(ast.args, c_ast.ParamList):
for param in ast.args.params:
param_info = args[param.name]
param_type = compiler.compile_type(param.type)
if param_type is None:
raise CUnknownTypeException(f'Unknown type for parameter {param.name} of callback {name}')
param_info.ctype = param_type
param_info.cdecl = param
ctype_params.append(param_type)
try:
binding_data.user_data = list(args.keys()).index('pUserData')
except ValueError:
binding_data.user_data = None
binding_data.arguments = args
binding_data.ctype = Constructor(*ctype_params)
binding_data.cdecl = ast
self.cache[name] = binding_data
self.create_signature(name)
return binding_data
def compile_command(self, name: str):
binding_data = VulkanCommandBindingData()
Constructor = ctypes.CFUNCTYPE
if hasattr(ctypes, 'WINFUNCTYPE'):
Constructor = ctypes.WINFUNCTYPE
code = {}
args = OrderedDict()
param_processed_attributes = {}
processed_attributes = {'export', 'api'}
for node in self.taxonomy.nodes[name]:
node: Node
if not self.taxonomy.is_valid_api(node):
continue
if 'proto' not in node.children:
continue
binding_data.node = node
code['return'] = node.get('proto').get_text()
code['params'] = []
for param_node in node.get_all('param'):
if not self.taxonomy.is_valid_api(param_node):
continue
param_name = param_node.get('name').get_text()
param_info = VulkanElementData()
param_info.node = param_node
args[param_name] = param_info
code['params'].append(param_node.get_text())
param_processed_attributes[param_name] = {'api'}
if 'type' in param_node.children:
param_type = param_node.get('type').get_text()
param_info['type'], param_info['class'] = self.get_type_class(param_type)
if param_node.has_attribute('altlen'):
length_code = f'int _ = {param_node.get_attribute("altlen")};'
length_ast = self.cpreprocessor.preprocess_code_ast(length_code)
param_info['length'] = [length_ast.ext[0].init]
param_processed_attributes[param_name].update({'altlen', 'len'})
elif param_node.has_attribute('len'):
length_items = [s.strip() for s in param_node.get_attribute('len').split(',')]
if length_items[-1] == 'null-terminated':
param_info['is_string'] = True
length_items = length_items[:-1]
if len(length_items) > 0:
length_code = 'int _ = (%s);' % ', '.join(length_items)
length_ast = self.cpreprocessor.preprocess_code_ast(length_code).ext[0].init
length = []
if isinstance(length_ast, c_ast.ExprList):
for expr in length_ast.exprs:
if isinstance(expr, c_ast.ID):
length.append(expr.name)
else:
length.append(expr)
else:
if isinstance(length_ast, c_ast.ID):
length.append(length_ast.name)
else:
length.append(length_ast)
param_info['length'] = length
param_processed_attributes[param_name].add('len')
if param_node.has_attribute('optional'):
optional = []
for optional_value in param_node.get_attribute('optional').split(','):
optional_value = optional_value.strip().lower()
if optional_value == 'true':
optional.append(True)
elif optional_value == 'false':
optional.append(False)
else:
raise ValueError(f'Invalid optional value for param {param_name} of command {name}: {optional_value}')
param_info['optional'] = optional[0] if len(optional) == 1 else optional
param_processed_attributes[param_name].add('optional')
if param_node.has_attribute('validstructs'):
validstructs = [s.strip() for s in param_node.get_attribute('validstructs').split(',')]
if not all(name in self.taxonomy.vulkan_node_info['struct'] for name in validstructs):
raise ValueError(f'Missing validstructs required by param {param_name} of command {name}: {', '.join(name for name in validstructs if name not in self.taxonomy.vulkan_node_info['struct'])}')
param_info['valid_structs'] = validstructs
param_processed_attributes[param_name].add('validstructs')
if len(code) == 0:
raise UndefinedBindingException(f'Undefined callback: {name}')
code = ' '.join([code['return'], '(', ', '.join(code['params']), ');'])
ast = self.cpreprocessor.preprocess_code_ast(code).ext[0].type
compiler = self.CCompiler(self, self.cparser)
ctype_params = [compiler.compile_type(ast.type)]
return_info = VulkanElementData()
return_info.ctype = ctype_params[0]
return_info.cdecl = ast.type
proto_node = binding_data.node.get('proto')
return_info.node = proto_node
if 'type' in proto_node.children:
return_type = proto_node.get('type').get_text()
return_info['type'], return_info['class'] = self.get_type_class(return_type)
binding_data.result = return_info
if isinstance(ast.args, c_ast.ParamList):
for decl in ast.args.params:
param_info = args[decl.name]
param_type = compiler.compile_type(decl.type)
if param_type is None:
raise CUnknownTypeException(f'Unknown type for parameter {decl.name} of callback {name}')
param_info.ctype = param_type
param_info.cdecl = decl
ctype_params.append(param_type)
ptr_decl = decl.type
param_info['is_pointer'] = 0
while isinstance(ptr_decl, c_ast.PtrDecl):
param_info['is_pointer'] += 1
ptr_decl = ptr_decl.type
if param_info['is_pointer'] > 0 and 'optional' not in param_processed_attributes[decl.name]:
param_info['optional'] = [False] * param_info['is_pointer'] if param_info['is_pointer'] > 1 else False
param_processed_attributes[decl.name].add('optional')
for attribute_name in param_info.node.attributes:
if attribute_name not in param_processed_attributes[decl.name]:
param_info[attribute_name] = param_info.node.get_attribute(attribute_name)
binding_data.arguments = args
binding_data.ctype = Constructor(*ctype_params)
binding_data.cdecl = ast
for source_attribute, target_attribute, target_type in [
('successcodes', 'success_codes', 'VkResult'),
('errorcodes', 'error_codes', 'VkResult'),
('queues', 'queues', 'VkQueueFlagBits'),
]:
if binding_data.node.has_attribute(source_attribute):
enum_values = [self.resolve_alias(s.strip()) for s in binding_data.node.get_attribute(source_attribute).split(',')]
if not all(name in self.taxonomy.group_value_map[target_type] for name in enum_values):
raise ValueError(f'Missing enum "{target_type}" values for command {name}: {', '.join(name for name in enum_values if name not in self.taxonomy.group_value_map[target_type])}')
binding_data[target_attribute] = [self[name] for name in enum_values]
processed_attributes.add(source_attribute)
for source_attribute, target_attribute in [
('queues', 'queues'),
('tasks', 'tasks'),
('cmdbufferlevel', 'command_buffer_level'),
]:
if binding_data.node.has_attribute(source_attribute):
values = [s.strip().lower() for s in binding_data.node.get_attribute(source_attribute).split(',')]
binding_data[target_attribute] = values
processed_attributes.add(source_attribute)
for source_attribute, target_attribute in [
('allownoqueues', 'allow_no_queues'),
('conditionalrendering', 'conditional_rendering'),
]:
if binding_data.node.has_attribute(source_attribute):
value = binding_data.node.get_attribute(source_attribute).strip().lower()
if value == 'true':
binding_data[target_attribute] = True
elif value == 'false':
binding_data[target_attribute] = False
else:
raise ValueError(f'Invalid value for "{source_attribute}" in command {name}: {value}')
processed_attributes.add(source_attribute)
for attribute_name in binding_data.node.attributes:
if attribute_name not in processed_attributes:
binding_data[attribute_name] = binding_data.node.get_attribute(attribute_name)
outputs = self.search_for_command_output(binding_data, 'arguments', const_scope=False)
handles = {}
for arg_name, arg_info in binding_data.arguments.items():
if arg_name in outputs:
continue
if arg_info['class'] == 'handle' and arg_info.ctype in (ctypes.c_void_p, ctypes.c_uint64):
handles[arg_name] = arg_info['type']
binding_data.handle = self.resolve_command_handle(handles)
if len(outputs) == 0:
if binding_data.result.ctype is not None and not (binding_data.result['class'] == 'enum' and binding_data.result['type'] == 'VkResult'):
# "return" is a C keyword and cannot be parameter name
outputs = 'return'
else:
outputs = None
else:
outputs = [output[0] if len(output) == 1 else output for output in outputs]
outputs = outputs[0] if len(outputs) == 1 else outputs
binding_data.output = outputs
self.cache[name] = binding_data
self.create_signature(name)
return binding_data
def resolve_command_handle(self, handles: dict[str, str]) -> str | None:
if len(handles) == 0:
return None
if len(handles) == 1:
return next(iter(handles.keys()))
selected_name = None
selected_length = float('Infinity')
for param_name, handle_type in handles.items():
handle_path = self.get_handle_path(handle_type)
if len(handle_path) < selected_length:
selected_name = param_name
selected_length = len(handle_path)
return selected_name
def get_handle_path(self, handle_type: str) -> list[str]:
path = [handle_type]
while True:
handle_data = self[handle_type]
if not isinstance(handle_data, VulkanHandleData):
raise ValueError(f'Invalid handle type: {handle_type}')
if handle_data.parent is None:
break
path.append(handle_data.parent)
handle_type = handle_data.parent
return path
def search_for_command_output(self, binding_data, element_name: str, const_scope=True):
outputs = []
for name, info in getattr(binding_data, element_name).items():
if not isinstance(info.cdecl.type, c_ast.PtrDecl):
continue
if 'const' in info.cdecl.quals:
# if info['class'] == 'struct':
# struct_outputs = self.search_for_command_output(self[info['type']], 'members', const_scope=True)
# outputs.extend([name, *path] for path in struct_outputs)
continue
if not const_scope:
outputs.append([name])
return outputs
def compile_handle(self, name: str):
binding_data = VulkanHandleData()
self.cache[name] = binding_data
for node in self.taxonomy.nodes[name]:
node: Node
if not self.taxonomy.is_valid_api(node):
continue
if 'type' not in node.children or 'name' not in node.children or not node.has_attribute('objtypeenum'):
continue
handle_type = node.get('type').get_text()
if handle_type not in self.vulkan_handle_types:
raise ValueError(f'Unknown handle type for handle {name}: {handle_type}')
binding_data.ctype = self.vulkan_handle_types[handle_type]
binding_data.node = node
binding_data.define = handle_type
if node.has_attribute('parent'):
parent_name = node.get_attribute('parent')
binding_data.parent = parent_name
else:
binding_data.parent = None
object_type_name = node.get_attribute('objtypeenum')
if not object_type_name.startswith(self.vulkan_handle_object_type_prefix):
raise ValueError(f'Invalid objtypeenum for handle {name}: {object_type_name}')
binding_data['VkObjectType'] = self[object_type_name]
object_type_name = self.vulkan_debug_report_object_type_prefix + object_type_name[len(self.vulkan_handle_object_type_prefix):]
if object_type_name in self.taxonomy.vulkan_names:
binding_data['VkDebugReportObjectTypeEXT'] = self[object_type_name]
elif f'{object_type_name}_EXT' in self.taxonomy.vulkan_names:
binding_data['VkDebugReportObjectTypeEXT'] = self[f'{object_type_name}_EXT']
else:
for tag in self.taxonomy.tags:
debug_object_name = f'{object_type_name}_{tag}'
if debug_object_name in self.taxonomy.vulkan_names:
binding_data['VkDebugReportObjectTypeEXT'] = self[debug_object_name]
break
return binding_data
def create_signature(self, name: str):
binding_data = self[name]
binding_data: VulkanFunctionBindingData
parameters = []
for arg_name in binding_data.arguments:
parameter = inspect.Parameter(arg_name, inspect.Parameter.POSITIONAL_OR_KEYWORD)
parameters.append(parameter)
binding_data.signature = inspect.Signature(parameters)
class BindingNames(Sequence):
def __init__(self, names: Collection[str], undefined: Collection[str]):
self.names = names
self.undefined = set(undefined)
def __contains__(self, name: str):
return name in self.names and name not in self.undefined
def __iter__(self):
for name in self.names:
if name not in self.undefined:
yield name
class BindAll:
def __get__(self, instance, owner):
if instance is None:
return self
if '__all__' not in instance.__dict__:
instance.__dict__['__all__'] = list(self.enumrate_all(instance))
return instance.__dict__['__all__']
def enumrate_all(self, instance):
for name in instance._implementation_.keys():
if name not in instance._implementation_.undefined:
try:
instance[name]
yield name
except KeyError:
continue
class Binding:
locals()['__all__'] = BindAll()
__slots__ = ('_implementation_', 'taxonomy', '__dict__', '__weakref__')
def __init__(self, taxonomy: Taxonomy, *, undefined: Collection[str] = ()):
self.__dict__['VK_NULL_HANDLE'] = 0
self.taxonomy = taxonomy
self._implementation_ = self._create_implementation(taxonomy, undefined)
def _create_implementation(self, taxonomy: Taxonomy, undefined: Collection[str]):
return BindingImplementation(taxonomy, cache=self.__dict__, undefined=undefined)
@property
def undefined(self):
return self._implementation_.undefined
def __getattr__(self, name: str):
try:
return self._implementation_[name]
except KeyError:
raise AttributeError(name)
def __getitem__(self, name: str):
return self._implementation_[name]

View File

@@ -0,0 +1,122 @@
import re
import ctypes
from collections.abc import Collection
import pycparser.c_ast
import pycparser.c_generator
c_type_map = {
'void': None,
'char': ctypes.c_char,
'signed char': ctypes.c_byte,
'unsigned char': ctypes.c_ubyte,
'short': ctypes.c_short,
'unsigned short': ctypes.c_ushort,
'int': ctypes.c_int,
'unsigned int': ctypes.c_uint,
'long': ctypes.c_long,
'long int': ctypes.c_long,
'unsigned long': ctypes.c_ulong,
'unsigned long int': ctypes.c_ulong,
'long long': ctypes.c_longlong,
'long long int': ctypes.c_longlong,
'unsigned long long': ctypes.c_ulonglong,
'unsigned long long int': ctypes.c_ulonglong,
'float': ctypes.c_float,
'double': ctypes.c_double,
'string': ctypes.c_char_p,
'int8_t': ctypes.c_int8,
'int16_t': ctypes.c_int16,
'int32_t': ctypes.c_int32,
'int64_t': ctypes.c_int64,
'uint8_t': ctypes.c_uint8,
'uint16_t': ctypes.c_uint16,
'uint32_t': ctypes.c_uint32,
'uint64_t': ctypes.c_uint64,
'size_t': ctypes.c_size_t,
'ssize_t': ctypes.c_ssize_t,
}
platform_ctypes = {
'VisualID': ctypes.c_uint32, # X11/Xlib.h: CARD32
'Window': ctypes.c_uint32, # X11/Xlib.h: CARD32 => XID
'RROutput': ctypes.c_uint32, # X11/extensions/Xrandr.h
'xcb_window_t': ctypes.c_uint32, # xcb/xcb.h
'xcb_visualid_t': ctypes.c_uint32, # xcb/xcb.h
'HINSTANCE': ctypes.c_void_p, # windows.h
'HWND': ctypes.c_void_p, # windows.h
'HMONITOR': ctypes.c_void_p, # windows.h
'HANDLE': ctypes.c_void_p, # windows.h
'DWORD': ctypes.c_uint32, # windows.h
'LPCSTR': ctypes.c_char_p, # windows.h
'LPCTSTR': ctypes.c_char_p, # windows.h
'LPCWSTR': ctypes.c_wchar_p, # windows.h
'zx_handle_t': ctypes.c_uint32, # zircon/types.h (Fuschia?)
'GgpStreamDescriptor': ctypes.c_uint32, # Google games platform?
'GgpFrameToken': ctypes.c_uint32, # Google games platform?
'NvSciSyncAttrList': ctypes.c_void_p, # NV Sci Platform
'NvSciSyncObj': ctypes.c_void_p, # NV Sci Platform
'NvSciSyncFence': ctypes.c_uint64 * 6, # NV Sci Platform
'NvSciBufAttrList': ctypes.c_void_p, # NV Sci Platform
'NvSciBufObj': ctypes.c_void_p, # NV Sci Platform
}
c_string_subst_table = {
'a': 0x07,
'b': 0x08,
'e': 0x1B,
'f': 0x0C,
'n': 0x0A,
'r': 0x0D,
't': 0x09,
'v': 0x0B,
'\\': 0x5C,
"'": 0x27,
'"': 0x22,
'?': 0x3F,
}
c_string_subst_rtable = {v: k for k, v in c_string_subst_table.items()}
python_ctype_map = {}
python_ctype_int_map = {}
python_ctype_uint_map = {}
python_ctype_sint_map = {}
python_ctype_float_map = {}
python_ctype_struct_map = {}
python_ctype_signature_map = {}
def __init__():
for name in dir(ctypes):
if not name.startswith('c_'):
continue
ctype = getattr(ctypes, name)
try:
size = ctypes.sizeof(ctype)
except TypeError:
continue
python_ctype_map[ctype] = size
if hasattr(ctype, '_type_') and isinstance(ctype._type_, str):
if ctype._type_ in 'bBhHiIlLqQnNP':
python_ctype_signature_map[ctype] = int
python_ctype_int_map[ctype] = size
if str.isupper(ctype._type_):
python_ctype_uint_map[ctype] = size
else:
python_ctype_sint_map[ctype] = size
elif ctype._type_ in 'efdg':
python_ctype_signature_map[ctype] = float
python_ctype_float_map[ctype] = size
elif ctype._type_ in 'FDG':
python_ctype_signature_map[ctype] = complex
python_ctype_struct_map[ctype._type_] = ctype
python_ctype_signature_map['P'] = int | None
python_ctype_signature_map['c'] = bytes
python_ctype_signature_map['z'] = bytes | None
python_ctype_signature_map['Z'] = str | None
python_ctype_signature_map['u'] = str
python_ctype_signature_map['?'] = bool
__init__()
del __init__

View File

@@ -0,0 +1,300 @@
import ctypes
import operator
from collections.abc import Mapping
from typing import Any
import pycparser.c_ast as c_ast
from .c import c_type_map, python_ctype_map, python_ctype_uint_map, python_ctype_sint_map, python_ctype_float_map, python_ctype_struct_map
from .exception import CUnknownTypeException
from .cparser import CParser
def c_div_operator(result_type, left, right):
if result_type in python_ctype_float_map or result_type is float:
return result_type(py_truediv(result_type, left, right))
return result_type(py_floordiv(result_type, left, right))
def boolean_and(result_type, left, right):
return result_type(left and right)
def boolean_or(result_type, left, right):
return result_type(left or right)
def unary_operator_wrapper(operator_func):
def unary_op(result_type, operand):
if type(operand) in python_ctype_map:
operand = operand.value
return result_type(operator_func(operand))
return unary_op
def binary_operator_wrapper(operator_func):
def binary_op(result_type, left, right):
if type(left) in python_ctype_map:
left = left.value
if type(right) in python_ctype_map:
right = right.value
return result_type(operator_func(left, right))
return binary_op
py_truediv = binary_operator_wrapper(operator.truediv)
py_floordiv = binary_operator_wrapper(operator.floordiv)
binary_operators = {
'+': binary_operator_wrapper(operator.add),
'-': binary_operator_wrapper(operator.sub),
'*': binary_operator_wrapper(operator.mul),
'/': c_div_operator,
'%': binary_operator_wrapper(operator.mod),
'<<': binary_operator_wrapper(operator.lshift),
'>>': binary_operator_wrapper(operator.rshift),
'&': binary_operator_wrapper(operator.and_),
'|': binary_operator_wrapper(operator.or_),
'^': binary_operator_wrapper(operator.xor),
'&&': boolean_and,
'||': boolean_or,
}
unary_operators = {
'+': unary_operator_wrapper(operator.pos),
'-': unary_operator_wrapper(operator.neg),
'~': unary_operator_wrapper(operator.invert),
'!': unary_operator_wrapper(operator.not_),
}
def select_max_type_by_size_factory(map: Mapping[type, int]):
def selector(left_type, right_type):
left_size = map.get(left_type, 0)
right_size = map.get(right_type, 0)
if left_size > right_size:
return left_type
return right_type
return selector
def select_max_type_re_sign(unsigned_map: Mapping[type, int], signed_map: Mapping[type, int], signed_index):
def selector(*args):
signed = args[signed_index]
unsigned = args[1 - signed_index]
signed_size = signed_map.get(signed, 0)
unsigned_size = unsigned_map.get(unsigned, 0)
if signed_size >= unsigned_size:
return signed
return python_ctype_struct_map[unsigned._type_.lower()]
return selector
def select_argument_factory(index: int):
def selector(*args):
return args[index]
return selector
class CCompiler:
class CompileException(Exception):
pass
compile_python_types = {int, float, bool, str, bytes}
compile_binary_type_map = {
'c_float:c_float': select_max_type_by_size_factory(python_ctype_float_map),
'c_float:c_uint': select_argument_factory(0),
'c_float:c_sint': select_argument_factory(0),
'c_float:py_float': select_argument_factory(0),
'c_float:py_int': select_argument_factory(0),
'c_uint:c_float': select_argument_factory(1),
'c_sint:c_float': select_argument_factory(1),
'py_float:c_float': select_argument_factory(1),
'py_int:c_float': select_argument_factory(1),
'c_uint:c_uint': select_max_type_by_size_factory(python_ctype_uint_map),
'c_sint:c_uint': select_max_type_re_sign(python_ctype_uint_map, python_ctype_sint_map, 0),
'c_uint:c_sint': select_max_type_re_sign(python_ctype_uint_map, python_ctype_sint_map, 1),
'c_sint:c_sint': select_max_type_by_size_factory(python_ctype_sint_map),
'c_uint:py_int': select_argument_factory(0),
'c_sint:py_int': select_argument_factory(0),
'py_int:c_uint': select_argument_factory(1),
'py_int:c_sint': select_argument_factory(1),
'py_float:py_int': select_argument_factory(0),
'py_int:py_float': select_argument_factory(1),
}
compile_value_map = {
c_ast.BinaryOp: 'compile_value_binary_op',
c_ast.UnaryOp: 'compile_value_unary_op',
c_ast.Cast: 'compile_value_cast',
c_ast.Constant: 'compile_value_constant',
c_ast.ID: 'compile_value_id',
}
compile_type_map = {
c_ast.PtrDecl: 'compile_type_ptr',
c_ast.ArrayDecl: 'compile_type_array',
c_ast.TypeDecl: 'compile_type_decl',
c_ast.Struct: 'compile_type_decl_complex',
c_ast.Union: 'compile_type_decl_complex',
}
compile_type_decl_map = {
c_ast.IdentifierType: 'compile_type_decl_identifier',
c_ast.Struct: 'compile_type_decl_complex',
c_ast.Union: 'compile_type_decl_complex',
}
compile_type_ptr_map = {
None: ctypes.c_void_p,
ctypes.c_char: ctypes.c_char_p,
ctypes.c_wchar: ctypes.c_wchar_p,
}
def __init__(
self,
binding,
cparser: CParser,
*,
variables: Mapping[str, Any] = dict()
):
self.binding = binding
self.cparser = cparser
self.variables = variables
def compile_value_cast(self, node: c_ast.Cast):
ctype = self.compile_type(node.to_type.type)
value = self.compile_value_expr(node.expr)
if type(value) in python_ctype_map:
value = value.value
return ctype(value)
def compile_value_id(self, node: c_ast.ID):
if node.name in self.variables:
value = self.variables[node.name]
if callable(value):
return value(self, node)
return value
try:
return self.binding[node.name]
except KeyError:
raise self.CompileException(f'Undefined variable: {node.name}')
def compile_value_constant(self, node: c_ast.Constant):
if node.type not in c_type_map:
raise self.CompileException(f'Unsupported constant type: {node.type}')
return c_type_map[node.type](self.parse_value_constant(node))
def parse_value_constant(self, node: c_ast.Constant):
if 'int' in node.type:
return self.cparser.parse_c_int(node.value)
if node.type in ['float', 'double']:
return self.cparser.parse_c_float(node.value)
if node.type == 'string':
return self.cparser.parse_c_string(node.value)
raise self.CompileException(f'Unsupported constant type: {node.type}')
def compile_type_ptr(self, node: c_ast.PtrDecl):
ctype = self.compile_type(node.type)
if ctype in self.compile_type_ptr_map:
return self.compile_type_ptr_map[ctype]
ctype: 'type[ctypes._SimpleCData]'
return ctypes.POINTER(ctype)
def compile_type_array(self, node: c_ast.ArrayDecl):
ctype = self.compile_type(node.type)
ctype: 'type[ctypes._SimpleCData]'
length = self.compile_value_expr(node.dim)
if type(length) in python_ctype_map:
length = length.value
if not isinstance(length, int):
raise self.CompileException('Unsupported array dimension type: %s' % length.__class__.__name__)
return ctypes.ARRAY(ctype, length)
def compile_type_decl(self, node: c_ast.TypeDecl):
for type in node.type.__class__.__mro__:
if type in self.compile_type_decl_map:
return getattr(self, self.compile_type_decl_map[type])(node.type)
raise self.CompileException(f'Unsupported AST node type: {node.type.__class__.__name__}')
def compile_type_decl_identifier(self, node: c_ast.IdentifierType):
type_name = ' '.join(node.names)
return self.binding.get_ctype(type_name)
def compile_type_decl_complex(self, node: c_ast.Struct | c_ast.Union):
try:
value = self.binding.get_ctype(node.name)
except CUnknownTypeException as exception:
raise self.CompileException(f'Unsupported complex type: {node.name}') from exception
if value is None:
return None
if isinstance(node, c_ast.Struct) and issubclass(value, ctypes.Structure):
return value
if isinstance(node, c_ast.Union) and issubclass(value, ctypes.Union):
return value
raise self.CompileException(f'Unsupported complex type: {node.name}')
def type_deduction_binary(self, left, right):
args = [left, right]
arg_types = [type(arg) for arg in args]
# Fast path: no type conversion
if arg_types[0] == arg_types[1]:
return arg_types[0]
# Slow path: type conversion required
key = []
for arg_index in range(2):
arg_type = arg_types[arg_index]
for mro_type in arg_type.__mro__:
if mro_type in self.compile_python_types or mro_type in python_ctype_map:
arg_type = arg_types[arg_index] = mro_type
break
arg_key = [
'c' if arg_type in python_ctype_map else 'py'
]
if arg_key[0] == 'c':
if arg_type in python_ctype_float_map:
arg_key.append('float')
elif arg_type in python_ctype_sint_map:
arg_key.append('sint')
elif arg_type in python_ctype_uint_map:
arg_key.append('uint')
else:
arg_key.append(arg_type._type_)
else:
arg_key.append(arg_type.__name__)
arg_key = '_'.join(arg_key)
key.append(arg_key)
key = ':'.join(key)
if key not in self.compile_binary_type_map:
raise self.CompileException(f'Unsupported binary operator operand types: {arg_types[0].__name__}, {arg_types[1].__name__}')
return self.compile_binary_type_map[key](*arg_types)
def compile_value_binary_op(self, node: c_ast.BinaryOp):
left = self.compile_value_expr(node.left)
right = self.compile_value_expr(node.right)
result_type = self.type_deduction_binary(left, right)
try:
value = binary_operators[node.op](result_type, left, right)
except KeyError:
raise self.CompileException(f'Unsupported binary operator: {node.op}')
return value
def compile_value_unary_op(self, node: c_ast.UnaryOp):
operand = self.compile_value_expr(node.expr)
result_type = type(operand)
try:
value = unary_operators[node.op](result_type, operand)
except KeyError:
raise self.CompileException(f'Unsupported unary operator: {node.op}')
return value
def compile_type(self, node: c_ast.Node):
for type in node.__class__.__mro__:
if type in self.compile_type_map:
return getattr(self, self.compile_type_map[type])(node)
raise self.CompileException(f'Unsupported AST node type: {node.__class__.__name__}')
def compile_value_expr(self, node: c_ast.Node):
for type in node.__class__.__mro__:
if type in self.compile_value_map:
return getattr(self, self.compile_value_map[type])(node)
raise self.CompileException(f'Unsupported AST node type: {node.__class__.__name__}')

View File

@@ -0,0 +1,45 @@
import re
import pycparser
class CGenerator(pycparser.c_generator.CGenerator):
from .c import c_string_subst_table, c_string_subst_rtable
REGEXP_SUBST_TABLE = re.compile('[%s]' % ''.join(r'\x%02X' % x for x in c_string_subst_table.values()))
def visit_Code(self, node):
return node.code
class Code(pycparser.c_ast.Node):
__slots__ = ('code', 'coord', '__weakref__')
def __init__(self, code):
self.code = code
def c_string_subst_unicode_char(match):
char = match.group(0)
code = ord(char)
if code < 65535:
return r'\u%04X' % code
else:
return r'\U%08X' % code
@classmethod
def string_subst_c_table(cls, match):
char = match.group(0)
code = ord(char)
return '\\%s' % cls.c_string_subst_rtable[code]
@staticmethod
def string_subst_unicode_char(match):
char = match.group(0)
code = ord(char)
if code < 65535:
return r'\u%04X' % code
else:
return r'\U%08X' % code
@classmethod
def generate_c_string(cls: 'CGenerator', value: str):
value = cls.REGEXP_SUBST_TABLE.sub(cls.string_subst_c_table, value)
value = re.sub(r'[^\u0000-\u007F]', cls.string_subst_unicode_char, value)
return '"%s"' % value

View File

@@ -0,0 +1,93 @@
from collections.abc import Collection
import re
import pycparser
class CParser(pycparser.CParser):
from .c import c_string_subst_table, c_string_subst_rtable
REGEXP_PARSE_HEX_INT = re.compile(r'(-?)0x([0-9A-Fa-f]+)')
REGEXP_PARSE_DEC_INT = re.compile(r'-?[0-9]+')
REGEXP_PARSE_FLOAT = re.compile(r'-?[0-9]+(?:\.[0-9]+)?')
REGEXP_PARSE_UNICODE_SUBST_8 = re.compile(r'\\U([0-9A-Fa-f]{8})')
REGEXP_PARSE_UNICODE_SUBST_4 = re.compile(r'\\u([0-9A-Fa-f]{4})')
REGEXP_PARSE_HEX_SUBST = re.compile(r'\\x([0-9A-Fa-f]{2})')
REGEXP_PARSE_OCT_SUBST = re.compile(r'\\([0-7]{3})')
REGEXP_PARSE_SLASH_SUBST = re.compile(r'\\(.)')
def __init__(self, type_names: Collection):
super().__init__()
self._type_names = type_names
def _lex_type_lookup_func(self, name):
if super()._lex_type_lookup_func(name):
return True
return name in self._type_names
@classmethod
def parse_c_int(cls, value):
match = cls.REGEXP_PARSE_HEX_INT.match(value)
if match:
return (-1 if len(match.group(1)) else 1) * int(match.group(2), 16)
match = cls.REGEXP_PARSE_DEC_INT.match(value)
if match:
return int(match.group(0), 10)
raise ValueError('Invalid integer value: %r' % value)
@classmethod
def parse_c_float(cls, value):
match = cls.REGEXP_PARSE_FLOAT.match(value)
if match:
return float(match.group(0))
raise ValueError('Invalid float value: %r' % value)
@classmethod
def parse_c_string(cls, value):
if len(value) < 2 or value[0] != '"' or value[-1] != '"':
raise ValueError('Invalid string value (missing open or close quotes): %s' % value)
value = value[1:-1]
value = cls.REGEXP_PARSE_UNICODE_SUBST_8.sub(cls.subst_unicode_hex, value)
value = cls.REGEXP_PARSE_UNICODE_SUBST_4.sub(cls.subst_unicode_hex, value)
value = cls.REGEXP_PARSE_HEX_SUBST.sub(cls.subst_unicode_hex, value)
value = cls.REGEXP_PARSE_OCT_SUBST.sub(cls.subst_unicode_oct, value)
value = cls.REGEXP_PARSE_SLASH_SUBST.sub(cls.subst_slash_escape, value)
return value.encode('utf-8')
@classmethod
def parse_c_char(cls, value):
if len(value) < 3 or value[0] != "'" or value[-1] != "'":
raise ValueError('Invalid char value (missing open or close single quotes): %s' % value)
value = value[1:-1]
value = cls.REGEXP_PARSE_UNICODE_SUBST_8.sub(cls.subst_unicode_hex, value)
value = cls.REGEXP_PARSE_UNICODE_SUBST_4.sub(cls.subst_unicode_hex, value)
value = cls.REGEXP_PARSE_HEX_SUBST.sub(cls.subst_unicode_hex, value)
value = cls.REGEXP_PARSE_OCT_SUBST.sub(cls.subst_unicode_oct, value)
value = cls.REGEXP_PARSE_SLASH_SUBST.sub(cls.subst_slash_escape, value)
char_bytes = value.encode('utf-8')
if len(char_bytes) != 1:
raise ValueError('Invalid char value (must be a single byte): %s' % value)
return ord(char_bytes[0])
class ParseError(RuntimeError):
pass
@staticmethod
def subst_unicode_hex(match):
return chr(int(match.group(1), 16))
@staticmethod
def subst_unicode_oct(match):
return chr(int(match.group(1), 8))
@classmethod
def subst_string_c_table(cls, match):
char = match.group(0)
code = ord(char)
return '\\%s' % cls.c_string_subst_rtable[code]
@classmethod
def subst_slash_escape(cls, match):
seq = match.group(1)
if seq in cls.c_string_subst_table:
return chr(cls.c_string_subst_table[seq])
return match.group(0)

View File

@@ -0,0 +1,162 @@
import re
import pycparser.c_ast
from .cparser import CParser
from .cgenerator import CGenerator
class CPreprocessor(dict):
REGEXP_MULTILINE_COMMENT = re.compile(r'\/\*.*\*\/')
REGEXP_SINGLELINE_COMMENT = re.compile(r'\/\/.*')
REGEXP_FUNC_MACRO = re.compile(r'\s*#\s*define\s+(\w+)\(([^)]+)\)(.*)')
REGEXP_VALUE_MACRO = re.compile(r'\s*#\s*define\s+(\w+)\s+(.*)')
REGEXP_C_AST_CHILD_NAME = re.compile(r'([a-zA-Z_][a-zA-Z0-9_]*)(?:\[([0-9+])\])?')
class CFunctionMacro:
def __init__(self, name: str, arguments: list[str], template: list):
self.name = name
self.arguments = arguments
self.template = template
class CValueMacro:
def __init__(self, name: str, value: str):
self.name = name
self.value = value
def __init__(self, parser: CParser, generator: CGenerator):
self.parser = parser
self.generator = generator
def define_macro(self, code: str, *, name: str = None):
macro = self._parse_define_code(code)
if macro is None:
return
if macro.name in self:
raise ValueError('Macro %r is already defined.' % macro.name)
if name is not None and macro.name != name:
raise ValueError('Macro name %r does not match expected name %r.' % (macro.name, name))
self[macro.name] = macro
def preprocess_code_ast(self, code: str):
ast = self.parser.parse(code)
while self.preprocess_ast(ast):
code = self.generator.visit(ast)
ast = self.parser.parse(code)
return ast
def preprocess_code(self, code: str):
ast = self.preprocess_code_ast(code)
return self.generator.visit(ast)
def preprocess_ast(self, node: pycparser.c_ast.Node):
has_substitution = False
for name, child_node in node.children():
if isinstance(child_node, pycparser.c_ast.ID):
if child_node.name in self and isinstance(self[child_node.name], self.CValueMacro):
self._ast_set_attribute(node, name, self.generator.Code(self[child_node.name].value))
has_substitution = True
continue
if isinstance(child_node, pycparser.c_ast.FuncCall):
if child_node.name.name in self and isinstance(self[child_node.name.name], self.CFunctionMacro):
assert isinstance(self[child_node.name.name], self.CFunctionMacro), """isinstance(self[child_node.name.name], self.CFunctionMacro)"""
args = [self.generator.visit(x) for x in child_node.args]
macro = self[child_node.name.name]
macro: 'CPreprocessor.CFunctionMacro'
if len(macro.arguments) != len(args):
raise self.parser.ParseError('Macro "%s" accept "%d" arguments, but called with "%d" arguments' % (child_node.name.name, len(macro.arguments), len(args)))
code = []
for part in macro.template:
if isinstance(part, str):
code.append(part)
elif part['string']:
code.append(self.generator.generate_c_string(args[part['index']]))
else:
code.append(args[part['index']])
code = ''.join(code)
self._ast_set_attribute(node, name, self.generator.Code(code))
has_substitution = True
continue
has_descendant_substitution = self.preprocess_ast(child_node)
has_substitution = has_substitution or has_descendant_substitution
return has_substitution
@classmethod
def _preprocessor_get_lines(cls, code) -> list[str]:
lines = []
code = cls.REGEXP_MULTILINE_COMMENT.sub('', code)
code = code.splitlines()
is_continuation_line = False
for line in code:
line = cls.REGEXP_SINGLELINE_COMMENT.sub('', line)
if len(line.strip()) <= 0:
is_continuation_line = False
continue
if is_continuation_line:
lines[-1] += ' ' + line
if lines[-1].endswith('\\'):
lines[-1] = lines[-1][:-1]
is_continuation_line = True
else:
is_continuation_line = False
else:
if line.endswith('\\'):
is_continuation_line = True
line = line[:-1]
else:
is_continuation_line = False
lines.append(line)
return lines
@classmethod
def _parse_define_code(cls, code):
code = cls._preprocessor_get_lines(code)
if len(code) > 1:
raise ValueError('Unable to define macro with more than one preprocessor line.')
if len(code) == 0:
return None
code = code[0]
is_func_macro = cls.REGEXP_FUNC_MACRO.fullmatch(code)
if is_func_macro is not None:
return cls._parser_preprocessor_func(is_func_macro)
is_value_macro = cls.REGEXP_VALUE_MACRO.fullmatch(code)
if is_value_macro is not None:
return cls._parser_preprocessor_value(is_value_macro)
@classmethod
def _parser_preprocessor_func(cls, data: re.Match):
arguments = [arg.strip() for arg in data.group(2).split(',')]
code = data.group(3).strip()
# This might not properly handle if argument name appear in a C string, but it is good enough for now.
# pycparser.cparser cannot be used, as it does not handle #-prefix and ## operator
words = [y for x in code.split('##') for y in re.split(r'\b', x)]
template = []
for word in words:
if word in arguments:
if len(template) > 0 and template[-1] == '#':
template[-1] = {'name': word, 'index': arguments.index(word), 'string': True}
else:
template.append({'name': word, 'index': arguments.index(word), 'string': False})
else:
template.append(word)
return cls.CFunctionMacro(name=data.group(1), arguments=arguments, template=template)
@classmethod
def _parser_preprocessor_value(cls, data: re.Match):
return cls.CValueMacro(name=data.group(1), value=data.group(2))
@classmethod
def _ast_set_attribute(cls, target, name, value):
if isinstance(name, int):
target[name] = value
pattern = cls.REGEXP_C_AST_CHILD_NAME.fullmatch(name)
if pattern is None:
raise AttributeError(target, name)
index = pattern.group(2)
if index is not None:
if len(index) > 0:
index = int(index, 10)
else:
index = None
if index is not None:
getattr(target, pattern.group(1))[index] = value
else:
setattr(target, pattern.group(1), value)

View File

@@ -0,0 +1,10 @@
class UndefinedBindingException(Exception):
pass
class CCompileException(Exception):
pass
class CUnknownTypeException(Exception):
pass

View File

@@ -0,0 +1,11 @@
def create_lazy_module(module, BaseClass):
ignore_names = set().union(*list(set(dir(Class)) for Class in module.__class__.__mro__))
transfer_names = {name for name in set(dir(module)) - ignore_names if name.startswith('__') and name.endswith('__')}
def __repr__(self):
return '<module %r from %r>' % (self.__name__, self.__file__)
return type(module.__name__, (BaseClass,), {
'__repr__': __repr__,
**{name: getattr(module, name) for name in transfer_names},
})

View File

@@ -0,0 +1,49 @@
import importlib
import inspect
import unittest
class TestLazyBinding(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.binding = importlib.import_module("dragiyski.vulkan.binding")
def assert_callable_macro(self, name: str, params: list[str]):
self.assertTrue(hasattr(self.binding, name))
macro = getattr(self.binding, name)
self.assertTrue(callable(macro))
sig = inspect.signature(macro)
self.assertEqual(list(sig.parameters.keys()), params)
for param in sig.parameters.values():
self.assertEqual(param.kind, inspect.Parameter.POSITIONAL_OR_KEYWORD)
def test_define_vk_make_version(self):
self.assert_callable_macro('VK_MAKE_VERSION', ['major', 'minor', 'patch'])
def test_define_vk_version_major(self):
self.assert_callable_macro('VK_VERSION_MAJOR', ['version'])
def test_define_vk_version_minor(self):
self.assert_callable_macro('VK_VERSION_MINOR', ['version'])
def test_define_vk_version_patch(self):
self.assert_callable_macro('VK_VERSION_PATCH', ['version'])
def test_define_vk_make_api_version(self):
self.assert_callable_macro('VK_MAKE_API_VERSION', ['variant', 'major', 'minor', 'patch'])
def test_define_vk_api_version_variant(self):
self.assert_callable_macro('VK_API_VERSION_VARIANT', ['version'])
def test_define_vk_api_version_major(self):
self.assert_callable_macro('VK_API_VERSION_MAJOR', ['version'])
def test_define_vk_api_version_minor(self):
self.assert_callable_macro('VK_API_VERSION_MINOR', ['version'])
def test_define_vk_api_version_patch(self):
self.assert_callable_macro('VK_API_VERSION_PATCH', ['version'])
def test_define_vk_header_version(self):
self.assertTrue(hasattr(self.binding, 'VK_HEADER_VERSION'))
self.assertIsInstance(self.binding.VK_HEADER_VERSION, int)