- Notifications
You must be signed in to change notification settings - Fork0
WebGPU (webgpu.h) Python bindings
License
PyryM/xgpu
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
xgpu
is an aggressively typed, red-squiggle-free Python bindingofwgpu-native, autogenerated fromthe upstream C headers.
Not 'production ready'.
Wheels are built for Mac (x86 only), Windows, and Linux for Python 3.7+:
pip install xgpu
Whyanother webgpu/wgpu_native binding whenwgpu-pyalready exists and is semi-mature?
- Typing:
xgpu
takes full advantage of Python type annotations, enabling quality of lifefeatures like IDE autocomplete for enum values - Up to date:
xgpu
is 99% autogenerated from the headers, and aims to always be in sync withthe latestwgpu-native
release - Performance:
xgpu
is substantially faster thanwgpu
xgpu
is a mostly 1-to-1 binding ofwebgpu.h
(+wgpu.h
fromwgpu-native
).
xgpu
largely tries to maintain the names fromwebgpu.h
rather than localizingthem into Python's conventions.
- Names keep their formatting from
webgpu.h
but loseWGPU
prefixes:WGPUTextureSampleType
->TextureSampleType
- Fields:
WGPUAdapterProperties.vendorName
->AdapterProperties.vendorName
- Member functions:
wgpuDeviceHasFeature
->Device.hasFeature
- Enum values:
WGPUTextureUsage_CopySrc
->TextureUsage.CopySrc
- Names invalid in Python are prefixed with "_":
WGPUBufferUsage_None
->BufferUsage._None
,WGPUTextureDimension_2D
->TextureDimension._2D
- Names invalid in Python are prefixed with "_":
webgpu.h
requires constructing various structs, for exampleWGPUExtent3D
. These can be created in two ways:
# Recommended: create explicit initialized struct (note lowercase name)extents=xgpu.extent3D(width=100,height=100,depthOrArrayLayers=1)# Alternative: create 0-initialized struct and then mutate valuesextents=xgpu.Extent3D()extents.width=100extents.height=100extents.depthOrArrayLayers=1
As a C API,webgpu.h
follows typical C convention for member functions, which is to definethem like:
uint32_twgpuTextureGetHeight(WGPUTexturetexture)
Inxgpu
these become genuine member functions, e.g.,
classTexture:defgetHeight(self)->int
Somewebgpu.h
functions and structs take arrays using the convention of passing firstthe array item count, and then the array pointer, e.g.,
voidwgpuQueueSubmit(WGPUQueuequeue,size_tcommandCount,WGPUCommandBufferconst*commands)typedefstructWGPUPipelineLayoutDescriptor {// ...size_tbindGroupLayoutCount;WGPUBindGroupLayoutconst*bindGroupLayouts;}WGPUPipelineLayoutDescriptor;
These are translated to take lists:
classQueue:defsubmit(self,commands:List[CommandBuffer]])defpipelineLayoutDescriptor(*,bindGroupLayouts:List["BindGroupLayout"])
Enums are translated intoIntEnum
s:
mode=xgpu.AddressMode.MirrorRepeatprint(int(mode))# 2print(mode.name)# "MirrorRepeat"mode=xgpu.AddressMode(2)print(mode.name)# "ClampToEdge"
Some enums are meant to be ORed together into bitflags. These can be combinedin the natural way:
usage=xgpu.BufferUsage.MapRead|xgpu.BufferUsage.CopyDstprint(usage)# prints: 9
This works becauseIntEnums
inherit all the int methods include bitwiseoperations; however, this discards the type information.A slightly more annoying but type-safer way is:
usage=xgpu.BufferUsage.MapRead.asflag()|xgpu.BufferUsage.CopyDstprint(usage)# prints: BufferUsage.MapRead | BufferUsage.CopyDst
You can also create typed flags from bare ints:
usage=xgpu.BufferUsageFlags(0b1001)print(usage)# prints: BufferUsage.MapRead | BufferUsage.CopyDst
You can test for a particular flag with the pythonin
operator:
has_map_read=xgpu.BufferUsage.MapReadinmybuffer.getUsage()
Callbacks must be explicitly wrapped in the appropriate callback type:
defmy_adapter_cb(status:xgpu.RequestAdapterStatus,gotten:xgpu.Adapter,msg:str):print(f"Got adapter with msg:'{msg}', status:{status.name}")cb=xgpu.RequestAdapterCallback(my_adapter_cb)
Thewebgpu.h
structure chaining convention is represented byChainedStruct
, whoseconstructor takes a list ofChainable
and automatically creates the linked chain.
shader_source="""..."""shader=device.createShaderModule(nextInChain=xgpu.ChainedStruct( [xgpu.shaderModuleWGSLDescriptor(code=shader_source)] ),hints=[],)
xgpu
has two translations forvoid *
:VoidPtr
represents a pointer toopaque data (e.g., a window handle) whileDataPtr
represents a pointerto asized data structure (e.g., texture data you want to upload).
For example,
# Note use of VoidPtr.NULL and VoidPtr.raw_castsurf_desc=xgpu.surfaceDescriptorFromWindowsHWND(hinstance=xgpu.VoidPtr.NULL,hwnd=xgpu.VoidPtr.raw_cast(self.window_handle),)# DataPtr.wrap can wrap anything supporting the 'buffer' interfacebytedata=bytearray(100)wrapped=xgpu.DataPtr.wrap(bytedata)queue.writeBuffer(buffer=some_buffer,bufferOffset=0,data=wrapped)# This includes numpy arraysmy_array=np.ones(100,dtype=np.float32)wrapped=xgpu.DataPtr.wrap(my_array)
You will needbun to run the codegen. Denomightwork but just go ahead and install bun. You will also need to haveruff and cffi installed in python (pip install ruff cffi
).
Then:
python codegen/fetch_wgpu_bins.pybun codegen/generate.tscd xgpupython _build_ext.pycd ..pip install .