Package: xgpu
xgpuxgpuis an aggressively typed, red-squiggle-free Python binding
ofwgpu-native, autogenerated from
the upstream C headers.Not 'production ready'.InstallWheels are built for Mac (x86 only), Windows, and Linux for Python 3.7+:pip install xgpuMotivationWhyanotherwebgpu/wgpu_native binding whenwgpu-pyalready exists and is semi-mature?Typing:xgputakes full advantage of Python type annotations, enabling quality of life
features like IDE autocomplete for enum valuesUp to date:xgpuis 99% autogenerated from the headers, and aims to always be in sync with
the latestwgpu-nativereleasePerformance:xgpuis substantially faster thanwgpuConventions/Philosophyxgpuis a mostly 1-to-1 binding ofwebgpu.h(+wgpu.hfromwgpu-native).General name conventionsxgpulargely tries to maintain the names fromwebgpu.hrather than localizing
them into Python's conventions.Names keep their formatting fromwebgpu.hbut loseWGPUprefixes:WGPUTextureSampleType->TextureSampleTypeFields:WGPUAdapterProperties.vendorName->AdapterProperties.vendorNameMember functions:wgpuDeviceHasFeature->Device.hasFeatureEnum values:WGPUTextureUsage_CopySrc->TextureUsage.CopySrcNames invalid in Python are prefixed with "_":WGPUBufferUsage_None->BufferUsage._None,WGPUTextureDimension_2D->TextureDimension._2DStruct constructorswebgpu.hrequires 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=1Member functionsAs a C API,webgpu.hfollows typical C convention for member functions, which is to define
them like:uint32_twgpuTextureGetHeight(WGPUTexturetexture)Inxgputhese become genuine member functions, e.g.,classTexture:defgetHeight(self)->intArray arguments / fieldsSomewebgpu.hfunctions and structs take arrays using the convention of passing first
the 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 and FlagsEnums are translated intoIntEnums: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 combined
in the natural way:usage=xgpu.BufferUsage.MapRead|xgpu.BufferUsage.CopyDstprint(usage)# prints: 9This works becauseIntEnumsinherit all the int methods include bitwise
operations; 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.CopyDstYou can also create typed flags from bare ints:usage=xgpu.BufferUsageFlags(0b1001)print(usage)# prints: BufferUsage.MapRead | BufferUsage.CopyDstYou can test for a particular flag with the pythoninoperator:has_map_read=xgpu.BufferUsage.MapReadinmybuffer.getUsage()CallbacksCallbacks 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)Chained structsThewebgpu.hstructure chaining convention is represented byChainedStruct, whose
constructor takes a list ofChainableand automatically creates the linked chain.shader_source="""..."""shader=device.createShaderModule(nextInChain=xgpu.ChainedStruct([xgpu.shaderModuleWGSLDescriptor(code=shader_source)]),hints=[],)Byte buffers, void pointersxgpuhas two translations forvoid *:VoidPtrrepresents a pointer to
opaque data (e.g., a window handle) whileDataPtrrepresents a pointer
to asizeddata 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)Codegen/Local BuildYou will needbunto run the codegen. Denomightwork but just go ahead and install bun. You will also need to have
ruff and cffi installed in python (pip install ruff cffi).Then:python codegen/fetch_wgpu_bins.py
bun codegen/generate.ts
cd xgpu
python _build_ext.py
cd ..
pip install .