├── dawn ├── dawn-git-tag.txt └── webgpu-raii.hpp ├── emscripten ├── emscripten-git-tag.txt ├── webgpu-raii.hpp └── webgpu.template.hpp ├── emdawn ├── emdawn-git-tag.txt └── webgpu-raii.hpp ├── wgpu-native ├── wgpu-native-git-tag.txt ├── webgpu-raii.hpp └── wgpu.h ├── .gitignore ├── .github └── FUNDING.yml ├── LICENSE.txt ├── extra-defaults.txt ├── defaults.txt ├── webgpu-raii.template.hpp ├── webgpu.template.hpp ├── fetch_default_values.py ├── README.md ├── spec.json └── generate.py /dawn/dawn-git-tag.txt: -------------------------------------------------------------------------------- 1 | chromium/7187 2 | -------------------------------------------------------------------------------- /emscripten/emscripten-git-tag.txt: -------------------------------------------------------------------------------- 1 | 4.0.5 2 | -------------------------------------------------------------------------------- /emdawn/emdawn-git-tag.txt: -------------------------------------------------------------------------------- 1 | v20250517.163311 2 | -------------------------------------------------------------------------------- /wgpu-native/wgpu-native-git-tag.txt: -------------------------------------------------------------------------------- 1 | v24.0.3.1 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | spec.html 3 | spec.json 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: eliemichel 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | polar: # Replace with a single Polar username 13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username 14 | custom: ['https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=DNEEF8GDX2EV6¤cy_code=EUR&source=url'] 15 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | Copyright (c) 2022-2024 Élie Michel 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /extra-defaults.txt: -------------------------------------------------------------------------------- 1 | WGPUBindGroupLayoutEntry::buffer.type = "undefined"; 2 | WGPUBindGroupLayoutEntry::sampler.type = "undefined"; 3 | WGPUBindGroupLayoutEntry::storageTexture.access = "undefined"; 4 | WGPUBindGroupLayoutEntry::texture.sampleType = "undefined"; 5 | 6 | WGPULimits::maxTextureDimension1D = WGPU_LIMIT_U32_UNDEFINED; 7 | WGPULimits::maxTextureDimension2D = WGPU_LIMIT_U32_UNDEFINED; 8 | WGPULimits::maxTextureDimension3D = WGPU_LIMIT_U32_UNDEFINED; 9 | WGPULimits::maxTextureArrayLayers = WGPU_LIMIT_U32_UNDEFINED; 10 | WGPULimits::maxBindGroups = WGPU_LIMIT_U32_UNDEFINED; 11 | WGPULimits::maxBindGroupsPlusVertexBuffers = WGPU_LIMIT_U32_UNDEFINED; 12 | WGPULimits::maxBindingsPerBindGroup = WGPU_LIMIT_U32_UNDEFINED; 13 | WGPULimits::maxDynamicUniformBuffersPerPipelineLayout = WGPU_LIMIT_U32_UNDEFINED; 14 | WGPULimits::maxDynamicStorageBuffersPerPipelineLayout = WGPU_LIMIT_U32_UNDEFINED; 15 | WGPULimits::maxSampledTexturesPerShaderStage = WGPU_LIMIT_U32_UNDEFINED; 16 | WGPULimits::maxSamplersPerShaderStage = WGPU_LIMIT_U32_UNDEFINED; 17 | WGPULimits::maxStorageBuffersPerShaderStage = WGPU_LIMIT_U32_UNDEFINED; 18 | WGPULimits::maxStorageTexturesPerShaderStage = WGPU_LIMIT_U32_UNDEFINED; 19 | WGPULimits::maxUniformBuffersPerShaderStage = WGPU_LIMIT_U32_UNDEFINED; 20 | WGPULimits::maxUniformBufferBindingSize = WGPU_LIMIT_U64_UNDEFINED; 21 | WGPULimits::maxStorageBufferBindingSize = WGPU_LIMIT_U64_UNDEFINED; 22 | WGPULimits::minUniformBufferOffsetAlignment = WGPU_LIMIT_U32_UNDEFINED; 23 | WGPULimits::minStorageBufferOffsetAlignment = WGPU_LIMIT_U32_UNDEFINED; 24 | WGPULimits::maxVertexBuffers = WGPU_LIMIT_U32_UNDEFINED; 25 | WGPULimits::maxBufferSize = WGPU_LIMIT_U64_UNDEFINED; 26 | WGPULimits::maxVertexAttributes = WGPU_LIMIT_U32_UNDEFINED; 27 | WGPULimits::maxVertexBufferArrayStride = WGPU_LIMIT_U32_UNDEFINED; 28 | WGPULimits::maxInterStageShaderComponents = WGPU_LIMIT_U32_UNDEFINED; 29 | WGPULimits::maxInterStageShaderVariables = WGPU_LIMIT_U32_UNDEFINED; 30 | WGPULimits::maxColorAttachments = WGPU_LIMIT_U32_UNDEFINED; 31 | WGPULimits::maxColorAttachmentBytesPerSample = WGPU_LIMIT_U32_UNDEFINED; 32 | WGPULimits::maxComputeWorkgroupStorageSize = WGPU_LIMIT_U32_UNDEFINED; 33 | WGPULimits::maxComputeInvocationsPerWorkgroup = WGPU_LIMIT_U32_UNDEFINED; 34 | WGPULimits::maxComputeWorkgroupSizeX = WGPU_LIMIT_U32_UNDEFINED; 35 | WGPULimits::maxComputeWorkgroupSizeY = WGPU_LIMIT_U32_UNDEFINED; 36 | WGPULimits::maxComputeWorkgroupSizeZ = WGPU_LIMIT_U32_UNDEFINED; 37 | WGPULimits::maxComputeWorkgroupsPerDimension = WGPU_LIMIT_U32_UNDEFINED; 38 | -------------------------------------------------------------------------------- /defaults.txt: -------------------------------------------------------------------------------- 1 | WGPURequestAdapterOptions::forceFallbackAdapter = false; 2 | 3 | WGPUBufferDescriptor::mappedAtCreation = false; 4 | 5 | WGPUTextureDescriptor::mipLevelCount = 1; 6 | WGPUTextureDescriptor::sampleCount = 1; 7 | WGPUTextureDescriptor::dimension = "2d"; 8 | 9 | WGPUTextureViewDescriptor::aspect = "all"; 10 | WGPUTextureViewDescriptor::baseMipLevel = 0; 11 | WGPUTextureViewDescriptor::baseArrayLayer = 0; 12 | 13 | WGPUSamplerDescriptor::addressModeU = "clamp-to-edge"; 14 | WGPUSamplerDescriptor::addressModeV = "clamp-to-edge"; 15 | WGPUSamplerDescriptor::addressModeW = "clamp-to-edge"; 16 | WGPUSamplerDescriptor::magFilter = "nearest"; 17 | WGPUSamplerDescriptor::minFilter = "nearest"; 18 | WGPUSamplerDescriptor::mipmapFilter = "nearest"; 19 | WGPUSamplerDescriptor::lodMinClamp = 0; 20 | WGPUSamplerDescriptor::lodMaxClamp = 32; 21 | 22 | WGPUBufferBindingLayout::type = "uniform"; 23 | WGPUBufferBindingLayout::hasDynamicOffset = false; 24 | WGPUBufferBindingLayout::minBindingSize = 0; 25 | 26 | WGPUSamplerBindingLayout::type = "filtering"; 27 | 28 | WGPUTextureBindingLayout::sampleType = "float"; 29 | WGPUTextureBindingLayout::viewDimension = "2d"; 30 | WGPUTextureBindingLayout::multisampled = false; 31 | 32 | WGPUStorageTextureBindingLayout::access = "write-only"; 33 | WGPUStorageTextureBindingLayout::viewDimension = "2d"; 34 | 35 | WGPUBindGroupEntry::offset = 0; 36 | 37 | WGPUPrimitiveState::topology = "triangle-list"; 38 | WGPUPrimitiveState::frontFace = "ccw"; 39 | WGPUPrimitiveState::cullMode = "none"; 40 | WGPUPrimitiveState::unclippedDepth = false; 41 | 42 | WGPUMultisampleState::count = 1; 43 | WGPUMultisampleState::mask = 0xFFFFFFFF; 44 | WGPUMultisampleState::alphaToCoverageEnabled = false; 45 | 46 | WGPUBlendComponent::operation = "add"; 47 | WGPUBlendComponent::srcFactor = "one"; 48 | WGPUBlendComponent::dstFactor = "zero"; 49 | 50 | WGPUDepthStencilState::stencilReadMask = 0xFFFFFFFF; 51 | WGPUDepthStencilState::stencilWriteMask = 0xFFFFFFFF; 52 | WGPUDepthStencilState::depthBias = 0; 53 | WGPUDepthStencilState::depthBiasSlopeScale = 0; 54 | WGPUDepthStencilState::depthBiasClamp = 0; 55 | 56 | WGPUStencilFaceState::compare = "always"; 57 | WGPUStencilFaceState::failOp = "keep"; 58 | WGPUStencilFaceState::depthFailOp = "keep"; 59 | WGPUStencilFaceState::passOp = "keep"; 60 | 61 | WGPUVertexBufferLayout::stepMode = "vertex"; 62 | 63 | WGPUTexelCopyTextureInfo::mipLevel = 0; 64 | WGPUTexelCopyTextureInfo::aspect = "all"; 65 | 66 | WGPURenderPassMaxDrawCount::maxDrawCount = 50000000; 67 | 68 | WGPURenderPassDepthStencilAttachment::depthReadOnly = false; 69 | WGPURenderPassDepthStencilAttachment::stencilClearValue = 0; 70 | WGPURenderPassDepthStencilAttachment::stencilReadOnly = false; 71 | 72 | WGPURenderBundleEncoderDescriptor::sampleCount = 1; 73 | 74 | WGPURenderBundleEncoderDescriptor::depthReadOnly = false; 75 | WGPURenderBundleEncoderDescriptor::stencilReadOnly = false; 76 | 77 | WGPUOrigin3D::x = 0; 78 | WGPUOrigin3D::y = 0; 79 | WGPUOrigin3D::z = 0; 80 | 81 | WGPUExtent3D::height = 1; 82 | WGPUExtent3D::depthOrArrayLayers = 1; 83 | 84 | -------------------------------------------------------------------------------- /webgpu-raii.template.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a RAII wrapper for WebGPU native API. 3 | * 4 | * This file is part of the "Learn WebGPU for C++" book. 5 | * https://eliemichel.github.io/LearnWebGPU 6 | * 7 | * MIT License 8 | * Copyright (c) 2022-2025 Elie Michel 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to deal 12 | * in the Software without restriction, including without limitation the rights 13 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | * copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in all 18 | * copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | * SOFTWARE. 27 | */ 28 | 29 | #pragma once 30 | 31 | #include 32 | 33 | #include 34 | 35 | #ifdef __EMSCRIPTEN__ 36 | # define addRef reference 37 | #endif 38 | 39 | namespace wgpu { 40 | namespace raii { 41 | 42 | /** 43 | * RAII wrapper around a raw WebGPU type. 44 | * Use pointer-like dereferencing to access methods from the wrapped type. 45 | */ 46 | template 47 | class Wrapper { 48 | public: 49 | Wrapper() 50 | : m_raw(nullptr) 51 | {} 52 | 53 | Wrapper(Raw&& raw) 54 | : m_raw(raw) 55 | {} 56 | 57 | // We define a destructor... 58 | ~Wrapper() { 59 | Destruct(); 60 | } 61 | 62 | // Copy semantics 63 | Wrapper& operator=(const Wrapper& other) { 64 | Destruct(); 65 | assert(m_raw == nullptr); 66 | m_raw = other.m_raw; 67 | if (m_raw != nullptr) { 68 | m_raw.addRef(); 69 | } 70 | return *this; 71 | } 72 | 73 | Wrapper(const Wrapper& other) 74 | : m_raw(other.m_raw) 75 | { 76 | if (m_raw != nullptr) { 77 | m_raw.addRef(); 78 | } 79 | } 80 | 81 | // Move semantics 82 | Wrapper& operator=(Wrapper&& other) { 83 | Destruct(); 84 | assert(m_raw == nullptr); 85 | m_raw = other.m_raw; 86 | other.m_raw = nullptr; 87 | return *this; 88 | } 89 | 90 | Wrapper(Wrapper&& other) 91 | : m_raw(other.m_raw) 92 | { 93 | other.m_raw = nullptr; 94 | } 95 | 96 | operator bool() const { return m_raw; } 97 | const Raw& operator*() const { return m_raw; } 98 | Raw& operator*() { return m_raw; } 99 | const Raw* operator->() const { return &m_raw; } 100 | Raw* operator->() { return &m_raw; } 101 | 102 | private: 103 | void Destruct() { 104 | if (!m_raw) return; 105 | m_raw.release(); 106 | m_raw = nullptr; 107 | } 108 | 109 | private: 110 | // Raw resources that is wrapped by the RAII class 111 | Raw m_raw; 112 | }; 113 | 114 | #define HANDLE(Type) using Type = wgpu::raii::Wrapper 115 | 116 | {{handles_oneliner}} 117 | 118 | #undef HANDLE 119 | 120 | } // namespace raii 121 | } // namespace wgpu 122 | -------------------------------------------------------------------------------- /emdawn/webgpu-raii.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a RAII wrapper for WebGPU native API. 3 | * 4 | * This file is part of the "Learn WebGPU for C++" book. 5 | * https://eliemichel.github.io/LearnWebGPU 6 | * 7 | * MIT License 8 | * Copyright (c) 2022-2025 Elie Michel 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to deal 12 | * in the Software without restriction, including without limitation the rights 13 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | * copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in all 18 | * copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | * SOFTWARE. 27 | */ 28 | 29 | #pragma once 30 | 31 | #include 32 | 33 | #include 34 | 35 | #ifdef __EMSCRIPTEN__ 36 | # define addRef reference 37 | #endif 38 | 39 | namespace wgpu { 40 | namespace raii { 41 | 42 | /** 43 | * RAII wrapper around a raw WebGPU type. 44 | * Use pointer-like dereferencing to access methods from the wrapped type. 45 | */ 46 | template 47 | class Wrapper { 48 | public: 49 | Wrapper() 50 | : m_raw(nullptr) 51 | {} 52 | 53 | Wrapper(Raw&& raw) 54 | : m_raw(raw) 55 | {} 56 | 57 | // We define a destructor... 58 | ~Wrapper() { 59 | Destruct(); 60 | } 61 | 62 | // Copy semantics 63 | Wrapper& operator=(const Wrapper& other) { 64 | Destruct(); 65 | assert(m_raw == nullptr); 66 | m_raw = other.m_raw; 67 | if (m_raw != nullptr) { 68 | m_raw.addRef(); 69 | } 70 | return *this; 71 | } 72 | 73 | Wrapper(const Wrapper& other) 74 | : m_raw(other.m_raw) 75 | { 76 | if (m_raw != nullptr) { 77 | m_raw.addRef(); 78 | } 79 | } 80 | 81 | // Move semantics 82 | Wrapper& operator=(Wrapper&& other) { 83 | Destruct(); 84 | assert(m_raw == nullptr); 85 | m_raw = other.m_raw; 86 | other.m_raw = nullptr; 87 | return *this; 88 | } 89 | 90 | Wrapper(Wrapper&& other) 91 | : m_raw(other.m_raw) 92 | { 93 | other.m_raw = nullptr; 94 | } 95 | 96 | operator bool() const { return m_raw; } 97 | const Raw& operator*() const { return m_raw; } 98 | Raw& operator*() { return m_raw; } 99 | const Raw* operator->() const { return &m_raw; } 100 | Raw* operator->() { return &m_raw; } 101 | 102 | private: 103 | void Destruct() { 104 | if (!m_raw) return; 105 | m_raw.release(); 106 | m_raw = nullptr; 107 | } 108 | 109 | private: 110 | // Raw resources that is wrapped by the RAII class 111 | Raw m_raw; 112 | }; 113 | 114 | #define HANDLE(Type) using Type = wgpu::raii::Wrapper 115 | 116 | HANDLE(Adapter); 117 | HANDLE(BindGroup); 118 | HANDLE(BindGroupLayout); 119 | HANDLE(Buffer); 120 | HANDLE(CommandBuffer); 121 | HANDLE(CommandEncoder); 122 | HANDLE(ComputePassEncoder); 123 | HANDLE(ComputePipeline); 124 | HANDLE(Device); 125 | HANDLE(Instance); 126 | HANDLE(PipelineLayout); 127 | HANDLE(QuerySet); 128 | HANDLE(Queue); 129 | HANDLE(RenderBundle); 130 | HANDLE(RenderBundleEncoder); 131 | HANDLE(RenderPassEncoder); 132 | HANDLE(RenderPipeline); 133 | HANDLE(Sampler); 134 | HANDLE(ShaderModule); 135 | HANDLE(Surface); 136 | HANDLE(Texture); 137 | HANDLE(TextureView); 138 | 139 | #undef HANDLE 140 | 141 | } // namespace raii 142 | } // namespace wgpu 143 | -------------------------------------------------------------------------------- /wgpu-native/webgpu-raii.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a RAII wrapper for WebGPU native API. 3 | * 4 | * This file is part of the "Learn WebGPU for C++" book. 5 | * https://eliemichel.github.io/LearnWebGPU 6 | * 7 | * MIT License 8 | * Copyright (c) 2022-2025 Elie Michel 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to deal 12 | * in the Software without restriction, including without limitation the rights 13 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | * copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in all 18 | * copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | * SOFTWARE. 27 | */ 28 | 29 | #pragma once 30 | 31 | #include 32 | 33 | #include 34 | 35 | #ifdef __EMSCRIPTEN__ 36 | # define addRef reference 37 | #endif 38 | 39 | namespace wgpu { 40 | namespace raii { 41 | 42 | /** 43 | * RAII wrapper around a raw WebGPU type. 44 | * Use pointer-like dereferencing to access methods from the wrapped type. 45 | */ 46 | template 47 | class Wrapper { 48 | public: 49 | Wrapper() 50 | : m_raw(nullptr) 51 | {} 52 | 53 | Wrapper(Raw&& raw) 54 | : m_raw(raw) 55 | {} 56 | 57 | // We define a destructor... 58 | ~Wrapper() { 59 | Destruct(); 60 | } 61 | 62 | // Copy semantics 63 | Wrapper& operator=(const Wrapper& other) { 64 | Destruct(); 65 | assert(m_raw == nullptr); 66 | m_raw = other.m_raw; 67 | if (m_raw != nullptr) { 68 | m_raw.addRef(); 69 | } 70 | return *this; 71 | } 72 | 73 | Wrapper(const Wrapper& other) 74 | : m_raw(other.m_raw) 75 | { 76 | if (m_raw != nullptr) { 77 | m_raw.addRef(); 78 | } 79 | } 80 | 81 | // Move semantics 82 | Wrapper& operator=(Wrapper&& other) { 83 | Destruct(); 84 | assert(m_raw == nullptr); 85 | m_raw = other.m_raw; 86 | other.m_raw = nullptr; 87 | return *this; 88 | } 89 | 90 | Wrapper(Wrapper&& other) 91 | : m_raw(other.m_raw) 92 | { 93 | other.m_raw = nullptr; 94 | } 95 | 96 | operator bool() const { return m_raw; } 97 | const Raw& operator*() const { return m_raw; } 98 | Raw& operator*() { return m_raw; } 99 | const Raw* operator->() const { return &m_raw; } 100 | Raw* operator->() { return &m_raw; } 101 | 102 | private: 103 | void Destruct() { 104 | if (!m_raw) return; 105 | m_raw.release(); 106 | m_raw = nullptr; 107 | } 108 | 109 | private: 110 | // Raw resources that is wrapped by the RAII class 111 | Raw m_raw; 112 | }; 113 | 114 | #define HANDLE(Type) using Type = wgpu::raii::Wrapper 115 | 116 | HANDLE(Adapter); 117 | HANDLE(BindGroup); 118 | HANDLE(BindGroupLayout); 119 | HANDLE(Buffer); 120 | HANDLE(CommandBuffer); 121 | HANDLE(CommandEncoder); 122 | HANDLE(ComputePassEncoder); 123 | HANDLE(ComputePipeline); 124 | HANDLE(Device); 125 | HANDLE(Instance); 126 | HANDLE(PipelineLayout); 127 | HANDLE(QuerySet); 128 | HANDLE(Queue); 129 | HANDLE(RenderBundle); 130 | HANDLE(RenderBundleEncoder); 131 | HANDLE(RenderPassEncoder); 132 | HANDLE(RenderPipeline); 133 | HANDLE(Sampler); 134 | HANDLE(ShaderModule); 135 | HANDLE(Surface); 136 | HANDLE(Texture); 137 | HANDLE(TextureView); 138 | 139 | #undef HANDLE 140 | 141 | } // namespace raii 142 | } // namespace wgpu 143 | -------------------------------------------------------------------------------- /emscripten/webgpu-raii.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a RAII wrapper for WebGPU native API. 3 | * 4 | * This file is part of the "Learn WebGPU for C++" book. 5 | * https://eliemichel.github.io/LearnWebGPU 6 | * 7 | * MIT License 8 | * Copyright (c) 2022-2025 Elie Michel 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to deal 12 | * in the Software without restriction, including without limitation the rights 13 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | * copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in all 18 | * copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | * SOFTWARE. 27 | */ 28 | 29 | #pragma once 30 | 31 | #include 32 | 33 | #include 34 | 35 | #ifdef __EMSCRIPTEN__ 36 | # define addRef reference 37 | #endif 38 | 39 | namespace wgpu { 40 | namespace raii { 41 | 42 | /** 43 | * RAII wrapper around a raw WebGPU type. 44 | * Use pointer-like dereferencing to access methods from the wrapped type. 45 | */ 46 | template 47 | class Wrapper { 48 | public: 49 | Wrapper() 50 | : m_raw(nullptr) 51 | {} 52 | 53 | Wrapper(Raw&& raw) 54 | : m_raw(raw) 55 | {} 56 | 57 | // We define a destructor... 58 | ~Wrapper() { 59 | Destruct(); 60 | } 61 | 62 | // Copy semantics 63 | Wrapper& operator=(const Wrapper& other) { 64 | Destruct(); 65 | assert(m_raw == nullptr); 66 | m_raw = other.m_raw; 67 | if (m_raw != nullptr) { 68 | m_raw.addRef(); 69 | } 70 | return *this; 71 | } 72 | 73 | Wrapper(const Wrapper& other) 74 | : m_raw(other.m_raw) 75 | { 76 | if (m_raw != nullptr) { 77 | m_raw.addRef(); 78 | } 79 | } 80 | 81 | // Move semantics 82 | Wrapper& operator=(Wrapper&& other) { 83 | Destruct(); 84 | assert(m_raw == nullptr); 85 | m_raw = other.m_raw; 86 | other.m_raw = nullptr; 87 | return *this; 88 | } 89 | 90 | Wrapper(Wrapper&& other) 91 | : m_raw(other.m_raw) 92 | { 93 | other.m_raw = nullptr; 94 | } 95 | 96 | operator bool() const { return m_raw; } 97 | const Raw& operator*() const { return m_raw; } 98 | Raw& operator*() { return m_raw; } 99 | const Raw* operator->() const { return &m_raw; } 100 | Raw* operator->() { return &m_raw; } 101 | 102 | private: 103 | void Destruct() { 104 | if (!m_raw) return; 105 | m_raw.release(); 106 | m_raw = nullptr; 107 | } 108 | 109 | private: 110 | // Raw resources that is wrapped by the RAII class 111 | Raw m_raw; 112 | }; 113 | 114 | #define HANDLE(Type) using Type = wgpu::raii::Wrapper 115 | 116 | HANDLE(Adapter); 117 | HANDLE(BindGroup); 118 | HANDLE(BindGroupLayout); 119 | HANDLE(Buffer); 120 | HANDLE(CommandBuffer); 121 | HANDLE(CommandEncoder); 122 | HANDLE(ComputePassEncoder); 123 | HANDLE(ComputePipeline); 124 | HANDLE(Device); 125 | HANDLE(Instance); 126 | HANDLE(PipelineLayout); 127 | HANDLE(QuerySet); 128 | HANDLE(Queue); 129 | HANDLE(RenderBundle); 130 | HANDLE(RenderBundleEncoder); 131 | HANDLE(RenderPassEncoder); 132 | HANDLE(RenderPipeline); 133 | HANDLE(Sampler); 134 | HANDLE(ShaderModule); 135 | HANDLE(Surface); 136 | HANDLE(SwapChain); 137 | HANDLE(Texture); 138 | HANDLE(TextureView); 139 | 140 | #undef HANDLE 141 | 142 | } // namespace raii 143 | } // namespace wgpu 144 | -------------------------------------------------------------------------------- /dawn/webgpu-raii.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a RAII wrapper for WebGPU native API. 3 | * 4 | * This file is part of the "Learn WebGPU for C++" book. 5 | * https://eliemichel.github.io/LearnWebGPU 6 | * 7 | * MIT License 8 | * Copyright (c) 2022-2025 Elie Michel 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to deal 12 | * in the Software without restriction, including without limitation the rights 13 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | * copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in all 18 | * copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | * SOFTWARE. 27 | */ 28 | 29 | #pragma once 30 | 31 | #include 32 | 33 | #include 34 | 35 | #ifdef __EMSCRIPTEN__ 36 | # define addRef reference 37 | #endif 38 | 39 | namespace wgpu { 40 | namespace raii { 41 | 42 | /** 43 | * RAII wrapper around a raw WebGPU type. 44 | * Use pointer-like dereferencing to access methods from the wrapped type. 45 | */ 46 | template 47 | class Wrapper { 48 | public: 49 | Wrapper() 50 | : m_raw(nullptr) 51 | {} 52 | 53 | Wrapper(Raw&& raw) 54 | : m_raw(raw) 55 | {} 56 | 57 | // We define a destructor... 58 | ~Wrapper() { 59 | Destruct(); 60 | } 61 | 62 | // Copy semantics 63 | Wrapper& operator=(const Wrapper& other) { 64 | Destruct(); 65 | assert(m_raw == nullptr); 66 | m_raw = other.m_raw; 67 | if (m_raw != nullptr) { 68 | m_raw.addRef(); 69 | } 70 | return *this; 71 | } 72 | 73 | Wrapper(const Wrapper& other) 74 | : m_raw(other.m_raw) 75 | { 76 | if (m_raw != nullptr) { 77 | m_raw.addRef(); 78 | } 79 | } 80 | 81 | // Move semantics 82 | Wrapper& operator=(Wrapper&& other) { 83 | Destruct(); 84 | assert(m_raw == nullptr); 85 | m_raw = other.m_raw; 86 | other.m_raw = nullptr; 87 | return *this; 88 | } 89 | 90 | Wrapper(Wrapper&& other) 91 | : m_raw(other.m_raw) 92 | { 93 | other.m_raw = nullptr; 94 | } 95 | 96 | operator bool() const { return m_raw; } 97 | const Raw& operator*() const { return m_raw; } 98 | Raw& operator*() { return m_raw; } 99 | const Raw* operator->() const { return &m_raw; } 100 | Raw* operator->() { return &m_raw; } 101 | 102 | private: 103 | void Destruct() { 104 | if (!m_raw) return; 105 | m_raw.release(); 106 | m_raw = nullptr; 107 | } 108 | 109 | private: 110 | // Raw resources that is wrapped by the RAII class 111 | Raw m_raw; 112 | }; 113 | 114 | #define HANDLE(Type) using Type = wgpu::raii::Wrapper 115 | 116 | HANDLE(Adapter); 117 | HANDLE(BindGroup); 118 | HANDLE(BindGroupLayout); 119 | HANDLE(Buffer); 120 | HANDLE(CommandBuffer); 121 | HANDLE(CommandEncoder); 122 | HANDLE(ComputePassEncoder); 123 | HANDLE(ComputePipeline); 124 | HANDLE(Device); 125 | HANDLE(ExternalTexture); 126 | HANDLE(Instance); 127 | HANDLE(PipelineLayout); 128 | HANDLE(QuerySet); 129 | HANDLE(Queue); 130 | HANDLE(RenderBundle); 131 | HANDLE(RenderBundleEncoder); 132 | HANDLE(RenderPassEncoder); 133 | HANDLE(RenderPipeline); 134 | HANDLE(Sampler); 135 | HANDLE(ShaderModule); 136 | HANDLE(SharedBufferMemory); 137 | HANDLE(SharedFence); 138 | HANDLE(SharedTextureMemory); 139 | HANDLE(Surface); 140 | HANDLE(Texture); 141 | HANDLE(TextureView); 142 | 143 | #undef HANDLE 144 | 145 | } // namespace raii 146 | } // namespace wgpu 147 | -------------------------------------------------------------------------------- /emscripten/webgpu.template.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of the "Learn WebGPU for C++" book. 3 | * https://github.com/eliemichel/LearnWebGPU 4 | * 5 | * MIT License 6 | * Copyright (c) 2022-2024 Elie Michel 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in all 16 | * copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | * SOFTWARE. 25 | */ 26 | 27 | /** 28 | * Exactly one of your source files must #define WEBGPU_CPP_IMPLEMENTATION 29 | * before including this header. 30 | * 31 | * NB: This file has been generated by the webgpu-cpp generator 32 | * (see https://github.com/eliemichel/webgpu-cpp ) 33 | */ 34 | 35 | #pragma once 36 | 37 | {{webgpu_includes}} 38 | 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | 47 | #if __EMSCRIPTEN__ 48 | #include 49 | #endif 50 | 51 | #ifdef _MSVC_LANG 52 | # if _MSVC_LANG >= 202002L 53 | # define NO_DISCARD [[nodiscard("You should keep this handle alive for as long as the callback may get invoked.")]] 54 | # elif _MSVC_LANG >= 201703L 55 | # define NO_DISCARD [[nodiscard]] 56 | # else 57 | # define NO_DISCARD 58 | # endif 59 | #else 60 | # if __cplusplus >= 202002L 61 | # define NO_DISCARD [[nodiscard("You should keep this handle alive for as long as the callback may get invoked.")]] 62 | # elif __cplusplus >= 201703L 63 | # define NO_DISCARD [[nodiscard]] 64 | # else 65 | # define NO_DISCARD 66 | # endif 67 | #endif 68 | 69 | /** 70 | * A namespace providing a more C++ idiomatic API to WebGPU. 71 | */ 72 | namespace wgpu { 73 | 74 | struct DefaultFlag {}; 75 | constexpr DefaultFlag Default; 76 | 77 | #define HANDLE(Type) \ 78 | class Type { \ 79 | public: \ 80 | typedef Type S; /* S == Self */ \ 81 | typedef WGPU ## Type W; /* W == WGPU Type */ \ 82 | Type() : m_raw(nullptr) {} \ 83 | Type(const W& w) : m_raw(w) {} \ 84 | operator W&() { return m_raw; } \ 85 | operator const W&() const { return m_raw; } \ 86 | operator bool() const { return m_raw != nullptr; } \ 87 | bool operator==(const Type& other) const { return m_raw == other.m_raw; } \ 88 | bool operator!=(const Type& other) const { return m_raw != other.m_raw; } \ 89 | bool operator==(const W& other) const { return m_raw == other; } \ 90 | bool operator!=(const W& other) const { return m_raw != other; } \ 91 | friend auto operator<<(std::ostream &stream, const S& self) -> std::ostream & { \ 92 | return stream << ""; \ 93 | } \ 94 | private: \ 95 | W m_raw; \ 96 | public: 97 | 98 | #define DESCRIPTOR(Type) \ 99 | struct Type : public WGPU ## Type { \ 100 | public: \ 101 | typedef Type S; /* S == Self */ \ 102 | typedef WGPU ## Type W; /* W == WGPU Type */ \ 103 | Type() : W() { nextInChain = nullptr; } \ 104 | Type(const W &other) : W(other) { nextInChain = nullptr; } \ 105 | Type(const DefaultFlag &) : W() { setDefault(); } \ 106 | Type& operator=(const DefaultFlag &) { setDefault(); return *this; } \ 107 | friend auto operator<<(std::ostream &stream, const S&) -> std::ostream & { \ 108 | return stream << ""; \ 109 | } \ 110 | public: 111 | 112 | #define STRUCT_NO_OSTREAM(Type) \ 113 | struct Type : public WGPU ## Type { \ 114 | public: \ 115 | typedef Type S; /* S == Self */ \ 116 | typedef WGPU ## Type W; /* W == WGPU Type */ \ 117 | Type() : W() {} \ 118 | Type(const W &other) : W(other) {} \ 119 | Type(const DefaultFlag &) : W() { setDefault(); } \ 120 | Type& operator=(const DefaultFlag &) { setDefault(); return *this; } \ 121 | public: 122 | 123 | #define STRUCT(Type) \ 124 | STRUCT_NO_OSTREAM(Type) \ 125 | friend auto operator<<(std::ostream &stream, const S&) -> std::ostream & { \ 126 | return stream << ""; \ 127 | } \ 128 | public: 129 | 130 | #define ENUM(Type) \ 131 | class Type { \ 132 | public: \ 133 | typedef Type S; /* S == Self */ \ 134 | typedef WGPU ## Type W; /* W == WGPU Type */ \ 135 | constexpr Type() : m_raw(W{}) {} /* Using default value-initialization */ \ 136 | constexpr Type(const W& w) : m_raw(w) {} \ 137 | constexpr operator W() const { return m_raw; } \ 138 | W m_raw; /* Ideally, this would be private, but then types generated with this macro would not be structural. */ 139 | 140 | #define ENUM_ENTRY(Name, Value) \ 141 | static constexpr W Name = (W)(Value); 142 | 143 | #define END }; 144 | 145 | {{begin-inject}} 146 | HANDLE(Instance) 147 | Adapter requestAdapter(const RequestAdapterOptions& options); 148 | END 149 | HANDLE(Adapter) 150 | Device requestDevice(const DeviceDescriptor& descriptor); 151 | END 152 | STRUCT(Color) 153 | Color(double r, double g, double b, double a) : WGPUColor{ r, g, b, a } {} 154 | END 155 | STRUCT(Extent3D) 156 | Extent3D(uint32_t width, uint32_t height, uint32_t depthOrArrayLayers) : WGPUExtent3D{ width, height, depthOrArrayLayers } {} 157 | END 158 | STRUCT(Origin3D) 159 | Origin3D(uint32_t x, uint32_t y, uint32_t z) : WGPUOrigin3D{ x, y, z } {} 160 | END 161 | {{end-inject}} 162 | 163 | // Temporarily define this until emscripten adopts WGPUStringView 164 | struct StringView { 165 | public: 166 | typedef StringView S; 167 | typedef const char* W; 168 | StringView() : m_raw(nullptr) {} 169 | StringView(const W &other) : m_raw(other) {} 170 | StringView(const DefaultFlag &) : m_raw(nullptr) {} 171 | StringView(const std::string_view& cpp) : m_raw(cpp.data()) {} 172 | StringView& operator=(const DefaultFlag &) { m_raw = nullptr; return *this; } 173 | operator std::string_view() const; 174 | operator W() const { return m_raw; } 175 | friend auto operator<<(std::ostream& stream, const S& self) -> std::ostream& { 176 | return stream << std::string_view(self); 177 | } 178 | private: 179 | W m_raw; 180 | }; 181 | 182 | {{begin-blacklist}} 183 | wgpuDeviceGetLostFuture 184 | {{end-blacklist}} 185 | 186 | // Other type aliases 187 | {{type_aliases}} 188 | 189 | // Enumerations 190 | {{enums}} 191 | 192 | // Structs 193 | {{structs}} 194 | 195 | // Descriptors 196 | {{descriptors}} 197 | 198 | // Handles forward declarations 199 | {{handles_decl}} 200 | 201 | // Callback types 202 | {{callbacks}} 203 | 204 | // Handles detailed declarations 205 | {{handles}} 206 | 207 | // Non-member procedures 208 | {{procedures}} 209 | 210 | Instance createInstance(); 211 | Instance createInstance(const InstanceDescriptor& descriptor); 212 | 213 | #ifdef WEBGPU_CPP_IMPLEMENTATION 214 | 215 | Instance createInstance() { 216 | return wgpuCreateInstance(nullptr); 217 | } 218 | 219 | Instance createInstance(const InstanceDescriptor& descriptor) { 220 | return wgpuCreateInstance(&descriptor); 221 | } 222 | 223 | // Handles members implementation 224 | {{handles_impl}} 225 | 226 | // Extra implementations 227 | Adapter Instance::requestAdapter(const RequestAdapterOptions& options) { 228 | struct Context { 229 | Adapter adapter = nullptr; 230 | bool requestEnded = false; 231 | }; 232 | Context context; 233 | 234 | auto h = requestAdapter{{ext_suffix}}(options, [&context]( 235 | RequestAdapterStatus status, 236 | Adapter adapter, 237 | const char* message 238 | ) { 239 | if (status == RequestAdapterStatus::Success) { 240 | context.adapter = adapter; 241 | } 242 | else { 243 | std::cout << "Could not get WebGPU adapter: " << message << std::endl; 244 | } 245 | context.requestEnded = true; 246 | }); 247 | 248 | #if __EMSCRIPTEN__ 249 | while (!context.requestEnded) { 250 | emscripten_sleep(50); 251 | } 252 | #endif 253 | 254 | assert(context.requestEnded); 255 | return context.adapter; 256 | } 257 | 258 | Device Adapter::requestDevice(const DeviceDescriptor& descriptor) { 259 | struct Context { 260 | Device device = nullptr; 261 | bool requestEnded = false; 262 | }; 263 | Context context; 264 | 265 | auto h = requestDevice{{ext_suffix}}(descriptor, [&context]( 266 | RequestDeviceStatus status, 267 | Device device, 268 | const char* message 269 | ) { 270 | if (status == RequestDeviceStatus::Success) { 271 | context.device = device; 272 | } 273 | else { 274 | std::cout << "Could not get WebGPU device: " << message << std::endl; 275 | } 276 | context.requestEnded = true; 277 | }); 278 | 279 | #if __EMSCRIPTEN__ 280 | while (!context.requestEnded) { 281 | emscripten_sleep(50); 282 | } 283 | #endif 284 | 285 | assert(context.requestEnded); 286 | return context.device; 287 | } 288 | 289 | StringView::operator std::string_view() const { 290 | return std::string_view(m_raw); 291 | } 292 | 293 | #endif // WEBGPU_CPP_IMPLEMENTATION 294 | 295 | #undef HANDLE 296 | #undef DESCRIPTOR 297 | #undef ENUM 298 | #undef ENUM_ENTRY 299 | #undef END 300 | 301 | } // namespace wgpu 302 | -------------------------------------------------------------------------------- /webgpu.template.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of the "Learn WebGPU for C++" book. 3 | * https://github.com/eliemichel/LearnWebGPU 4 | * 5 | * MIT License 6 | * Copyright (c) 2022-2025 Elie Michel 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in all 16 | * copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | * SOFTWARE. 25 | */ 26 | 27 | /** 28 | * Exactly one of your source files must #define WEBGPU_CPP_IMPLEMENTATION 29 | * before including this header. 30 | * 31 | * NB: This file has been generated by the webgpu-cpp generator 32 | * (see https://github.com/eliemichel/webgpu-cpp ) 33 | */ 34 | 35 | #pragma once 36 | 37 | {{webgpu_includes}} 38 | 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | 47 | #if __EMSCRIPTEN__ 48 | #include 49 | #endif 50 | 51 | #ifdef _MSVC_LANG 52 | # if _MSVC_LANG >= 202002L 53 | # define NO_DISCARD [[nodiscard("You should keep this handle alive for as long as the callback may get invoked.")]] 54 | # elif _MSVC_LANG >= 201703L 55 | # define NO_DISCARD [[nodiscard]] 56 | # else 57 | # define NO_DISCARD 58 | # endif 59 | #else 60 | # if __cplusplus >= 202002L 61 | # define NO_DISCARD [[nodiscard("You should keep this handle alive for as long as the callback may get invoked.")]] 62 | # elif __cplusplus >= 201703L 63 | # define NO_DISCARD [[nodiscard]] 64 | # else 65 | # define NO_DISCARD 66 | # endif 67 | #endif 68 | 69 | /** 70 | * A namespace providing a more C++ idiomatic API to WebGPU. 71 | */ 72 | namespace wgpu { 73 | 74 | struct DefaultFlag {}; 75 | constexpr DefaultFlag Default; 76 | 77 | #define HANDLE(Type) \ 78 | class Type { \ 79 | public: \ 80 | typedef Type S; /* S == Self */ \ 81 | typedef WGPU ## Type W; /* W == WGPU Type */ \ 82 | Type() : m_raw(nullptr) {} \ 83 | Type(const W& w) : m_raw(w) {} \ 84 | operator W&() { return m_raw; } \ 85 | operator const W&() const { return m_raw; } \ 86 | operator bool() const { return m_raw != nullptr; } \ 87 | bool operator==(const Type& other) const { return m_raw == other.m_raw; } \ 88 | bool operator!=(const Type& other) const { return m_raw != other.m_raw; } \ 89 | bool operator==(const W& other) const { return m_raw == other; } \ 90 | bool operator!=(const W& other) const { return m_raw != other; } \ 91 | friend auto operator<<(std::ostream &stream, const S& self) -> std::ostream & { \ 92 | return stream << ""; \ 93 | } \ 94 | private: \ 95 | W m_raw; \ 96 | public: 97 | 98 | #define DESCRIPTOR(Type) \ 99 | struct Type : public WGPU ## Type { \ 100 | public: \ 101 | typedef Type S; /* S == Self */ \ 102 | typedef WGPU ## Type W; /* W == WGPU Type */ \ 103 | Type() : W() { nextInChain = nullptr; } \ 104 | Type(const W &other) : W(other) { nextInChain = nullptr; } \ 105 | Type(const DefaultFlag &) : W() { setDefault(); } \ 106 | Type& operator=(const DefaultFlag &) { setDefault(); return *this; } \ 107 | friend auto operator<<(std::ostream &stream, const S&) -> std::ostream & { \ 108 | return stream << ""; \ 109 | } \ 110 | public: 111 | 112 | #define STRUCT_NO_OSTREAM(Type) \ 113 | struct Type : public WGPU ## Type { \ 114 | public: \ 115 | typedef Type S; /* S == Self */ \ 116 | typedef WGPU ## Type W; /* W == WGPU Type */ \ 117 | Type() : W() {} \ 118 | Type(const W &other) : W(other) {} \ 119 | Type(const DefaultFlag &) : W() { setDefault(); } \ 120 | Type& operator=(const DefaultFlag &) { setDefault(); return *this; } \ 121 | public: 122 | 123 | #define STRUCT(Type) \ 124 | STRUCT_NO_OSTREAM(Type) \ 125 | friend auto operator<<(std::ostream &stream, const S&) -> std::ostream & { \ 126 | return stream << ""; \ 127 | } \ 128 | public: 129 | 130 | #define ENUM(Type) \ 131 | class Type { \ 132 | public: \ 133 | typedef Type S; /* S == Self */ \ 134 | typedef WGPU ## Type W; /* W == WGPU Type */ \ 135 | constexpr Type() : m_raw(W{}) {} /* Using default value-initialization */ \ 136 | constexpr Type(const W& w) : m_raw(w) {} \ 137 | constexpr operator W() const { return m_raw; } \ 138 | W m_raw; /* Ideally, this would be private, but then types generated with this macro would not be structural. */ 139 | 140 | #define ENUM_ENTRY(Name, Value) \ 141 | static constexpr W Name = (W)(Value); 142 | 143 | #define END }; 144 | 145 | {{begin-inject}} 146 | HANDLE(Instance) 147 | Adapter requestAdapter(const RequestAdapterOptions& options); 148 | END 149 | HANDLE(Adapter) 150 | Device requestDevice(const DeviceDescriptor& descriptor); 151 | END 152 | STRUCT(Color) 153 | Color(double r, double g, double b, double a) : WGPUColor{ r, g, b, a } {} 154 | END 155 | STRUCT(Extent3D) 156 | Extent3D(uint32_t width, uint32_t height, uint32_t depthOrArrayLayers) : WGPUExtent3D{ width, height, depthOrArrayLayers } {} 157 | END 158 | STRUCT(Origin3D) 159 | Origin3D(uint32_t x, uint32_t y, uint32_t z) : WGPUOrigin3D{ x, y, z } {} 160 | END 161 | STRUCT_NO_OSTREAM(StringView) 162 | StringView(const std::string_view& cpp) : WGPUStringView{ cpp.data(), cpp.length() } {} 163 | operator std::string_view() const; 164 | friend auto operator<<(std::ostream& stream, const S& self) -> std::ostream& { 165 | return stream << std::string_view(self); 166 | } 167 | END 168 | {{end-inject}} 169 | 170 | {{begin-blacklist}} 171 | wgpuDeviceGetLostFuture 172 | {{end-blacklist}} 173 | 174 | // Other type aliases 175 | {{type_aliases}} 176 | 177 | // Enumerations 178 | {{enums}} 179 | 180 | // Structs 181 | {{structs}} 182 | 183 | // Descriptors 184 | {{descriptors}} 185 | 186 | // Handles forward declarations 187 | {{handles_decl}} 188 | 189 | // Callback types 190 | {{callbacks}} 191 | 192 | // Handles detailed declarations 193 | {{handles}} 194 | 195 | // Non-member procedures 196 | {{procedures}} 197 | 198 | Instance createInstance(); 199 | Instance createInstance(const InstanceDescriptor& descriptor); 200 | 201 | #ifdef WEBGPU_CPP_IMPLEMENTATION 202 | 203 | Instance createInstance() { 204 | return wgpuCreateInstance(nullptr); 205 | } 206 | 207 | Instance createInstance(const InstanceDescriptor& descriptor) { 208 | return wgpuCreateInstance(&descriptor); 209 | } 210 | 211 | // Handles members implementation 212 | {{handles_impl}} 213 | 214 | // Extra implementations 215 | Adapter Instance::requestAdapter(const RequestAdapterOptions& options) { 216 | struct Context { 217 | Adapter adapter = nullptr; 218 | bool requestEnded = false; 219 | }; 220 | Context context; 221 | 222 | RequestAdapterCallbackInfo{{ext_suffix}} callbackInfo; 223 | callbackInfo.nextInChain = nullptr; 224 | callbackInfo.userdata1 = &context; 225 | callbackInfo.callback = []( 226 | WGPURequestAdapterStatus status, 227 | WGPUAdapter adapter, 228 | WGPUStringView message, 229 | void* userdata1, 230 | [[maybe_unused]] void* userdata2 231 | ) { 232 | Context& context = *reinterpret_cast(userdata1); 233 | if (status == RequestAdapterStatus::Success) { 234 | context.adapter = adapter; 235 | } 236 | else { 237 | std::cout << "Could not get WebGPU adapter: " << StringView(message) << std::endl; 238 | } 239 | context.requestEnded = true; 240 | }; 241 | callbackInfo.mode = CallbackMode::AllowSpontaneous; 242 | wgpuInstanceRequestAdapter(*this, &options, callbackInfo); 243 | 244 | #if __EMSCRIPTEN__ 245 | while (!context.requestEnded) { 246 | emscripten_sleep(50); 247 | } 248 | #endif 249 | 250 | assert(context.requestEnded); 251 | return context.adapter; 252 | } 253 | 254 | Device Adapter::requestDevice(const DeviceDescriptor& descriptor) { 255 | struct Context { 256 | Device device = nullptr; 257 | bool requestEnded = false; 258 | }; 259 | Context context; 260 | 261 | RequestDeviceCallbackInfo{{ext_suffix}} callbackInfo; 262 | callbackInfo.nextInChain = nullptr; 263 | callbackInfo.userdata1 = &context; 264 | callbackInfo.callback = []( 265 | WGPURequestDeviceStatus status, 266 | WGPUDevice device, 267 | WGPUStringView message, 268 | void* userdata1, 269 | [[maybe_unused]] void* userdata2 270 | ) { 271 | Context& context = *reinterpret_cast(userdata1); 272 | if (status == RequestDeviceStatus::Success) { 273 | context.device = device; 274 | } 275 | else { 276 | std::cout << "Could not get WebGPU device: " << StringView(message) << std::endl; 277 | } 278 | context.requestEnded = true; 279 | }; 280 | callbackInfo.mode = CallbackMode::AllowSpontaneous; 281 | wgpuAdapterRequestDevice(*this, &descriptor, callbackInfo); 282 | 283 | #if __EMSCRIPTEN__ 284 | while (!context.requestEnded) { 285 | emscripten_sleep(50); 286 | } 287 | #endif 288 | 289 | assert(context.requestEnded); 290 | return context.device; 291 | } 292 | 293 | StringView::operator std::string_view() const { 294 | return 295 | length == WGPU_STRLEN 296 | ? std::string_view(data) 297 | : std::string_view(data, length); 298 | } 299 | 300 | #endif // WEBGPU_CPP_IMPLEMENTATION 301 | 302 | #undef HANDLE 303 | #undef DESCRIPTOR 304 | #undef ENUM 305 | #undef ENUM_ENTRY 306 | #undef END 307 | 308 | } // namespace wgpu 309 | -------------------------------------------------------------------------------- /fetch_default_values.py: -------------------------------------------------------------------------------- 1 | """ 2 | Fetch from the official WebGPU specification the default values for 3 | descriptors. 4 | 5 | Copyright (c) 2022-2023 Élie Michel 6 | """ 7 | 8 | import argparse 9 | from os.path import dirname, isfile, join 10 | import dataclasses 11 | from dataclasses import dataclass, field 12 | import logging 13 | import re 14 | import json 15 | 16 | try: 17 | from lxml import html 18 | except ImportError: 19 | print("[ERROR] You need to install the lxml module! (Run `pip install lxml`)") 20 | exit(1) 21 | 22 | #------------------------------------------------ 23 | # Command line arguments 24 | 25 | parser = argparse.ArgumentParser(prog="fetch_default_values", description=__doc__) 26 | 27 | parser.add_argument("-u", "--url", type=str, default="https://www.w3.org/TR/webgpu", 28 | help="URL of the specification document") 29 | 30 | parser.add_argument('-v', '--log-level', type=str, default='INFO', 31 | help="level of verbosity: DEBUG, INFO, WARNING ou ERROR") 32 | 33 | parser.add_argument('-s', '--output-spec', type=str, default='spec.json', 34 | help="output JSON file containing the scraped API") 35 | 36 | parser.add_argument('-d', '--output-defaults', type=str, default='defaults.txt', 37 | help="output file containing the default values") 38 | 39 | #------------------------------------------------ 40 | # Main outline 41 | 42 | def main(args): 43 | configureLogging(args) 44 | spec = download(args.url, label="WebGPU specification") 45 | api = parseAllDefinitions(spec) 46 | exportApi(api, args.output_spec) 47 | exportDefaults(api, args.output_defaults) 48 | logging.info(f"Done.") 49 | 50 | #------------------------------------------------ 51 | # Structures 52 | 53 | @dataclass 54 | class FieldApi: 55 | name: str 56 | type: str 57 | default: str|None = None 58 | required: bool = False 59 | 60 | @dataclass 61 | class DictionaryApi: 62 | name: str 63 | parent: str|None 64 | fields: list[FieldApi] = field(default_factory=list) 65 | 66 | @dataclass 67 | class WebGpuApi: 68 | dictionaries: dict[str,DictionaryApi] = field(default_factory=dict) 69 | 70 | #------------------------------------------------ 71 | # Steps 72 | 73 | def configureLogging(args): 74 | logging.basicConfig(format='[%(levelname)s] %(message)s', level=args.log_level) 75 | 76 | def parseAllDefinitions(spec): 77 | root = html.fromstring(spec) 78 | defs = root.xpath("//pre[@class='idl highlight def']") 79 | 80 | api = WebGpuApi() 81 | 82 | for d in defs: 83 | parseDefinition(api, d) 84 | 85 | return api 86 | 87 | def exportApi(api, filename): 88 | logging.info(f"Exporting API specification to {filename}...") 89 | with open(filename, 'w', encoding="utf-8") as f: 90 | json.dump(api, f, indent=2, cls=EnhancedJSONEncoder) 91 | 92 | def exportDefaults(api, filename): 93 | logging.info(f"Exporting default values to {filename}...") 94 | 95 | # Some nested structures from the JavaScript APIs are unfolded in 96 | # the C header (or the other way around) so we remap the default 97 | # values to their parent structure. 98 | remap_to_parent = { 99 | # "JavaScript dictionary": "C Structure", 100 | "GPUBufferBinding": "WGPUBindGroupEntry", 101 | "GPURenderPassLayout": "WGPURenderBundleEncoderDescriptor", 102 | "GPUOrigin3DDict": "WGPUOrigin3D", 103 | "GPUExtent3DDict": "WGPUExtent3D", 104 | 105 | # ("JavaScript dictionary", "JavaScript property"): ("C Structure", "C field"), 106 | ("GPUPrimitiveState", "unclippedDepth"): ("WGPUPrimitiveDepthClipControl", "unclippedDepth"), 107 | } 108 | 109 | # Some structure are JavaScript-specific and have not been ported in 110 | # the native C header. 111 | ignored = [ 112 | "GPUExternalTextureDescriptor", 113 | "GPUImageDataLayout", 114 | "GPUImageCopyTextureTagged", 115 | "GPUImageCopyExternalImage", 116 | "GPUCanvasConfiguration", 117 | "GPUOrigin2DDict", 118 | ] 119 | 120 | with open(filename, 'w', encoding="utf-8") as f: 121 | for d in api.dictionaries.values(): 122 | if d.name in ignored: 123 | continue 124 | fixed_name = remap_to_parent.get(d.name, f"W{d.name}") 125 | wrote_any = False 126 | for field in d.fields: 127 | if field.default is not None: 128 | dict_name, field_name = remap_to_parent.get((d.name, field.name), (fixed_name, field.name)) 129 | f.write(f"{dict_name}::{field_name} = {field.default};\n") 130 | wrote_any = True 131 | if wrote_any: 132 | f.write(f"\n") 133 | 134 | #------------------------------------------------ 135 | # Parser 136 | 137 | def parseDefinition(api, body): 138 | it = iter(body.text_content().split("\n")) 139 | 140 | start_dict_re = re.compile(r"^dictionary (\w+)( : (\w+))? {$") 141 | split_start_dict_A_re = re.compile(r"^dictionary (\w+)$") 142 | split_start_dict_B_re = re.compile(r"^\s*: (\w+)? {$") 143 | start_enum_re = re.compile(r"^enum (\w+) {$") 144 | 145 | end_enum_re = re.compile(r"^};$") 146 | 147 | start_interface_re = re.compile(r"^interface (\w+)( : (\w+))? {$") 148 | split_start_interface_A_re = re.compile(r"^interface (\w+)$") 149 | split_start_interface_B_re = re.compile(r"^\s*: (\w+)? {$") 150 | start_partial_interface_re = re.compile(r"^partial interface (\w+) {$") 151 | start_interface_mixin_re = re.compile(r"^interface mixin (\w+) {$") 152 | end_interface_re = re.compile(r"^};$") 153 | 154 | start_namespace_re = re.compile(r"^namespace (\w+) {$") 155 | end_namespace_re = re.compile(r"^};$") 156 | 157 | interface_attribs_re = re.compile(r"^\[Exposed=") 158 | 159 | typedef_re = re.compile(r"^typedef( (\[\w+\]))?( (\w+))+;") 160 | typedef2_re = re.compile(r"^typedef \([\w<>]+( or [\w<>]+)+\) (\w+);") 161 | start_typedef_re = re.compile(r"^typedef \(\w+ or$") 162 | end_typedef_re = re.compile(r"^\s*\w+\) (\w+);") 163 | 164 | includes_re = re.compile(r"^(\w+) includes (\w+);") 165 | 166 | state = 'NONE' 167 | dict_name = None 168 | 169 | while (x := next(it, None)) is not None: 170 | if state == 'NONE': 171 | if (match := start_dict_re.search(x)): 172 | dict_name = match.group(1) 173 | parent_name = match.group(3) 174 | api.dictionaries[dict_name] = ( 175 | parseDictionary(DictionaryApi(dict_name, parent_name), it) 176 | ) 177 | elif (match := split_start_dict_A_re.search(x)): 178 | dict_name = match.group(1) 179 | state = 'SPLIT_START_DICT' 180 | elif (match := start_enum_re.search(x)): 181 | while (x := next(it, None)) is not None: 182 | if end_enum_re.search(x): 183 | break 184 | elif (match := start_partial_interface_re.search(x)): 185 | while (x := next(it, None)) is not None: 186 | if end_interface_re.search(x): 187 | break 188 | elif (match := start_interface_re.search(x)): 189 | while (x := next(it, None)) is not None: 190 | if end_interface_re.search(x): 191 | break 192 | elif (match := start_interface_mixin_re.search(x)): 193 | while (x := next(it, None)) is not None: 194 | if end_interface_re.search(x): 195 | break 196 | elif (match := split_start_interface_A_re.search(x)): 197 | dict_name = match.group(1) 198 | state = 'SPLIT_START_INTERFACE' 199 | elif (match := start_namespace_re.search(x)): 200 | while (x := next(it, None)) is not None: 201 | if end_namespace_re.search(x): 202 | break 203 | elif (match := start_typedef_re.search(x)): 204 | while (x := next(it, None)) is not None: 205 | if end_typedef_re.search(x): 206 | break 207 | elif (match := interface_attribs_re.search(x)): 208 | continue 209 | elif (match := typedef_re.search(x)): 210 | continue 211 | elif (match := typedef2_re.search(x)): 212 | continue 213 | elif (match := includes_re.search(x)): 214 | continue 215 | elif x.strip() == "": 216 | continue 217 | else: 218 | logging.warning(f"Unable to parse line: {x}") 219 | elif state == 'SPLIT_START_DICT': 220 | if (match := split_start_dict_B_re.search(x)): 221 | parent_name = match.group(1) 222 | api.dictionaries[dict_name] = ( 223 | parseDictionary(DictionaryApi(dict_name, parent_name), it) 224 | ) 225 | state = 'NONE' 226 | else: 227 | logging.warning(f"Unable to parse line: {x}") 228 | elif state == 'SPLIT_START_INTERFACE': 229 | if (match := split_start_interface_B_re.search(x)): 230 | parent_name = match.group(1) 231 | while (x := next(it, None)) is not None: 232 | if end_interface_re.search(x): 233 | break 234 | state = 'NONE' 235 | else: 236 | logging.warning(f"Unable to parse line: {x}") 237 | 238 | return api 239 | 240 | def parseDictionary(api, it): 241 | end_dict_re = re.compile(r"^};$") 242 | field_re = re.compile(r"""^\s*(\w+) (\w+) = ("?[\w-]+"?);$""") 243 | required_field_re = re.compile(r"^\s*required (\w+) (\w+);$") 244 | 245 | logging.debug(f"Starting dict {api.name}") 246 | 247 | while (x := next(it, None)) is not None: 248 | if (match := end_dict_re.search(x)): 249 | break 250 | elif (match := field_re.search(x)): 251 | field_type = match.group(1) 252 | field_name = match.group(2) 253 | field_default = match.group(3) 254 | api.fields.append(FieldApi(field_name, field_type, default=field_default)) 255 | elif (match := required_field_re.search(x)): 256 | field_type = match.group(1) 257 | field_name = match.group(2) 258 | api.fields.append(FieldApi(field_name, field_type, required=True)) 259 | 260 | logging.debug(f"Ending dict {api.name}") 261 | return api 262 | 263 | #------------------------------------------------ 264 | # Utils (should be shared with generator.py) 265 | 266 | def resolveFilepath(path): 267 | for p in [ join(dirname(__file__), path), path ]: 268 | if isfile(p): 269 | return p 270 | logging.error(f"Invalid template path: {path}") 271 | raise ValueError("Invalid template path") 272 | 273 | def download(url, label=None): 274 | """Get a file's content either from a remote URL or from a local file""" 275 | label = label + " " if label is not None else "" 276 | if url.startswith("https://") or url.startswith("http://"): 277 | logging.info(f"Downloading {label}from {url}...") 278 | import urllib.request 279 | response = urllib.request.urlopen(url) 280 | data = response.read() 281 | text = data.decode("utf-8") 282 | return text 283 | else: 284 | resolved = resolveFilepath(url) 285 | logging.info(f"Loading {label}from {resolved}...") 286 | with open(resolved, encoding="utf-8") as f: 287 | return f.read() 288 | 289 | class EnhancedJSONEncoder(json.JSONEncoder): 290 | """From https://stackoverflow.com/a/51286749""" 291 | def default(self, o): 292 | if dataclasses.is_dataclass(o): 293 | return dataclasses.asdict(o) 294 | return super().default(o) 295 | 296 | #------------------------------------------------ 297 | 298 | if __name__ == "__main__": 299 | main(parser.parse_args()) 300 | -------------------------------------------------------------------------------- /wgpu-native/wgpu.h: -------------------------------------------------------------------------------- 1 | #ifndef WGPU_H_ 2 | #define WGPU_H_ 3 | 4 | #include "webgpu.h" 5 | 6 | typedef enum WGPUNativeSType { 7 | // Start at 0003 since that's allocated range for wgpu-native 8 | WGPUSType_DeviceExtras = 0x00030001, 9 | WGPUSType_NativeLimits = 0x00030002, 10 | WGPUSType_PipelineLayoutExtras = 0x00030003, 11 | WGPUSType_ShaderModuleGLSLDescriptor = 0x00030004, 12 | WGPUSType_InstanceExtras = 0x00030006, 13 | WGPUSType_BindGroupEntryExtras = 0x00030007, 14 | WGPUSType_BindGroupLayoutEntryExtras = 0x00030008, 15 | WGPUSType_QuerySetDescriptorExtras = 0x00030009, 16 | WGPUSType_SurfaceConfigurationExtras = 0x0003000A, 17 | WGPUNativeSType_Force32 = 0x7FFFFFFF 18 | } WGPUNativeSType; 19 | 20 | typedef enum WGPUNativeFeature { 21 | WGPUNativeFeature_PushConstants = 0x00030001, 22 | WGPUNativeFeature_TextureAdapterSpecificFormatFeatures = 0x00030002, 23 | WGPUNativeFeature_MultiDrawIndirect = 0x00030003, 24 | WGPUNativeFeature_MultiDrawIndirectCount = 0x00030004, 25 | WGPUNativeFeature_VertexWritableStorage = 0x00030005, 26 | WGPUNativeFeature_TextureBindingArray = 0x00030006, 27 | WGPUNativeFeature_SampledTextureAndStorageBufferArrayNonUniformIndexing = 0x00030007, 28 | WGPUNativeFeature_PipelineStatisticsQuery = 0x00030008, 29 | WGPUNativeFeature_StorageResourceBindingArray = 0x00030009, 30 | WGPUNativeFeature_PartiallyBoundBindingArray = 0x0003000A, 31 | WGPUNativeFeature_TextureFormat16bitNorm = 0x0003000B, 32 | WGPUNativeFeature_TextureCompressionAstcHdr = 0x0003000C, 33 | WGPUNativeFeature_MappablePrimaryBuffers = 0x0003000E, 34 | WGPUNativeFeature_BufferBindingArray = 0x0003000F, 35 | WGPUNativeFeature_UniformBufferAndStorageTextureArrayNonUniformIndexing = 0x00030010, 36 | // TODO: requires wgpu.h api change 37 | // WGPUNativeFeature_AddressModeClampToZero = 0x00030011, 38 | // WGPUNativeFeature_AddressModeClampToBorder = 0x00030012, 39 | // WGPUNativeFeature_PolygonModeLine = 0x00030013, 40 | // WGPUNativeFeature_PolygonModePoint = 0x00030014, 41 | // WGPUNativeFeature_ConservativeRasterization = 0x00030015, 42 | // WGPUNativeFeature_ClearTexture = 0x00030016, 43 | WGPUNativeFeature_SpirvShaderPassthrough = 0x00030017, 44 | // WGPUNativeFeature_Multiview = 0x00030018, 45 | WGPUNativeFeature_VertexAttribute64bit = 0x00030019, 46 | WGPUNativeFeature_TextureFormatNv12 = 0x0003001A, 47 | WGPUNativeFeature_RayTracingAccelerationStructure = 0x0003001B, 48 | WGPUNativeFeature_RayQuery = 0x0003001C, 49 | WGPUNativeFeature_ShaderF64 = 0x0003001D, 50 | WGPUNativeFeature_ShaderI16 = 0x0003001E, 51 | WGPUNativeFeature_ShaderPrimitiveIndex = 0x0003001F, 52 | WGPUNativeFeature_ShaderEarlyDepthTest = 0x00030020, 53 | WGPUNativeFeature_Subgroup = 0x00030021, 54 | WGPUNativeFeature_SubgroupVertex = 0x00030022, 55 | WGPUNativeFeature_SubgroupBarrier = 0x00030023, 56 | WGPUNativeFeature_TimestampQueryInsideEncoders = 0x00030024, 57 | WGPUNativeFeature_TimestampQueryInsidePasses = 0x00030025, 58 | WGPUNativeFeature_Force32 = 0x7FFFFFFF 59 | } WGPUNativeFeature; 60 | 61 | typedef enum WGPULogLevel { 62 | WGPULogLevel_Off = 0x00000000, 63 | WGPULogLevel_Error = 0x00000001, 64 | WGPULogLevel_Warn = 0x00000002, 65 | WGPULogLevel_Info = 0x00000003, 66 | WGPULogLevel_Debug = 0x00000004, 67 | WGPULogLevel_Trace = 0x00000005, 68 | WGPULogLevel_Force32 = 0x7FFFFFFF 69 | } WGPULogLevel; 70 | 71 | typedef WGPUFlags WGPUInstanceBackend; 72 | static const WGPUInstanceBackend WGPUInstanceBackend_All = 0x00000000; 73 | static const WGPUInstanceBackend WGPUInstanceBackend_Vulkan = 1 << 0; 74 | static const WGPUInstanceBackend WGPUInstanceBackend_GL = 1 << 1; 75 | static const WGPUInstanceBackend WGPUInstanceBackend_Metal = 1 << 2; 76 | static const WGPUInstanceBackend WGPUInstanceBackend_DX12 = 1 << 3; 77 | static const WGPUInstanceBackend WGPUInstanceBackend_DX11 = 1 << 4; 78 | static const WGPUInstanceBackend WGPUInstanceBackend_BrowserWebGPU = 1 << 5; 79 | // Vulkan, Metal, DX12 and BrowserWebGPU 80 | static const WGPUInstanceBackend WGPUInstanceBackend_Primary = (1 << 0) | (1 << 2) | (1 << 3) | (1 << 5); 81 | // GL and DX11 82 | static const WGPUInstanceBackend WGPUInstanceBackend_Secondary = (1 << 1) | (1 << 4); 83 | static const WGPUInstanceBackend WGPUInstanceBackend_Force32 = 0x7FFFFFFF; 84 | 85 | typedef WGPUFlags WGPUInstanceFlag; 86 | static const WGPUInstanceFlag WGPUInstanceFlag_Default = 0x00000000; 87 | static const WGPUInstanceFlag WGPUInstanceFlag_Debug = 1 << 0; 88 | static const WGPUInstanceFlag WGPUInstanceFlag_Validation = 1 << 1; 89 | static const WGPUInstanceFlag WGPUInstanceFlag_DiscardHalLabels = 1 << 2; 90 | static const WGPUInstanceFlag WGPUInstanceFlag_Force32 = 0x7FFFFFFF; 91 | 92 | typedef enum WGPUDx12Compiler { 93 | WGPUDx12Compiler_Undefined = 0x00000000, 94 | WGPUDx12Compiler_Fxc = 0x00000001, 95 | WGPUDx12Compiler_Dxc = 0x00000002, 96 | WGPUDx12Compiler_Force32 = 0x7FFFFFFF 97 | } WGPUDx12Compiler; 98 | 99 | typedef enum WGPUGles3MinorVersion { 100 | WGPUGles3MinorVersion_Automatic = 0x00000000, 101 | WGPUGles3MinorVersion_Version0 = 0x00000001, 102 | WGPUGles3MinorVersion_Version1 = 0x00000002, 103 | WGPUGles3MinorVersion_Version2 = 0x00000003, 104 | WGPUGles3MinorVersion_Force32 = 0x7FFFFFFF 105 | } WGPUGles3MinorVersion; 106 | 107 | typedef enum WGPUPipelineStatisticName { 108 | WGPUPipelineStatisticName_VertexShaderInvocations = 0x00000000, 109 | WGPUPipelineStatisticName_ClipperInvocations = 0x00000001, 110 | WGPUPipelineStatisticName_ClipperPrimitivesOut = 0x00000002, 111 | WGPUPipelineStatisticName_FragmentShaderInvocations = 0x00000003, 112 | WGPUPipelineStatisticName_ComputeShaderInvocations = 0x00000004, 113 | WGPUPipelineStatisticName_Force32 = 0x7FFFFFFF 114 | } WGPUPipelineStatisticName WGPU_ENUM_ATTRIBUTE; 115 | 116 | typedef enum WGPUNativeQueryType { 117 | WGPUNativeQueryType_PipelineStatistics = 0x00030000, 118 | WGPUNativeQueryType_Force32 = 0x7FFFFFFF 119 | } WGPUNativeQueryType WGPU_ENUM_ATTRIBUTE; 120 | 121 | typedef struct WGPUInstanceExtras { 122 | WGPUChainedStruct chain; 123 | WGPUInstanceBackend backends; 124 | WGPUInstanceFlag flags; 125 | WGPUDx12Compiler dx12ShaderCompiler; 126 | WGPUGles3MinorVersion gles3MinorVersion; 127 | WGPUStringView dxilPath; 128 | WGPUStringView dxcPath; 129 | } WGPUInstanceExtras; 130 | 131 | typedef struct WGPUDeviceExtras { 132 | WGPUChainedStruct chain; 133 | WGPUStringView tracePath; 134 | } WGPUDeviceExtras; 135 | 136 | typedef struct WGPUNativeLimits { 137 | /** This struct chain is used as mutable in some places and immutable in others. */ 138 | WGPUChainedStructOut chain; 139 | uint32_t maxPushConstantSize; 140 | uint32_t maxNonSamplerBindings; 141 | } WGPUNativeLimits; 142 | 143 | typedef struct WGPUPushConstantRange { 144 | WGPUShaderStage stages; 145 | uint32_t start; 146 | uint32_t end; 147 | } WGPUPushConstantRange; 148 | 149 | typedef struct WGPUPipelineLayoutExtras { 150 | WGPUChainedStruct chain; 151 | size_t pushConstantRangeCount; 152 | WGPUPushConstantRange const * pushConstantRanges; 153 | } WGPUPipelineLayoutExtras; 154 | 155 | typedef uint64_t WGPUSubmissionIndex; 156 | 157 | typedef struct WGPUShaderDefine { 158 | WGPUStringView name; 159 | WGPUStringView value; 160 | } WGPUShaderDefine; 161 | 162 | typedef struct WGPUShaderModuleGLSLDescriptor { 163 | WGPUChainedStruct chain; 164 | WGPUShaderStage stage; 165 | WGPUStringView code; 166 | uint32_t defineCount; 167 | WGPUShaderDefine * defines; 168 | } WGPUShaderModuleGLSLDescriptor; 169 | 170 | typedef struct WGPUShaderModuleDescriptorSpirV { 171 | WGPUStringView label; 172 | uint32_t sourceSize; 173 | uint32_t const * source; 174 | } WGPUShaderModuleDescriptorSpirV; 175 | 176 | typedef struct WGPURegistryReport { 177 | size_t numAllocated; 178 | size_t numKeptFromUser; 179 | size_t numReleasedFromUser; 180 | size_t elementSize; 181 | } WGPURegistryReport; 182 | 183 | typedef struct WGPUHubReport { 184 | WGPURegistryReport adapters; 185 | WGPURegistryReport devices; 186 | WGPURegistryReport queues; 187 | WGPURegistryReport pipelineLayouts; 188 | WGPURegistryReport shaderModules; 189 | WGPURegistryReport bindGroupLayouts; 190 | WGPURegistryReport bindGroups; 191 | WGPURegistryReport commandBuffers; 192 | WGPURegistryReport renderBundles; 193 | WGPURegistryReport renderPipelines; 194 | WGPURegistryReport computePipelines; 195 | WGPURegistryReport pipelineCaches; 196 | WGPURegistryReport querySets; 197 | WGPURegistryReport buffers; 198 | WGPURegistryReport textures; 199 | WGPURegistryReport textureViews; 200 | WGPURegistryReport samplers; 201 | } WGPUHubReport; 202 | 203 | typedef struct WGPUGlobalReport { 204 | WGPURegistryReport surfaces; 205 | WGPUHubReport hub; 206 | } WGPUGlobalReport; 207 | 208 | typedef struct WGPUInstanceEnumerateAdapterOptions { 209 | WGPUChainedStruct const * nextInChain; 210 | WGPUInstanceBackend backends; 211 | } WGPUInstanceEnumerateAdapterOptions; 212 | 213 | typedef struct WGPUBindGroupEntryExtras { 214 | WGPUChainedStruct chain; 215 | WGPUBuffer const * buffers; 216 | size_t bufferCount; 217 | WGPUSampler const * samplers; 218 | size_t samplerCount; 219 | WGPUTextureView const * textureViews; 220 | size_t textureViewCount; 221 | } WGPUBindGroupEntryExtras; 222 | 223 | typedef struct WGPUBindGroupLayoutEntryExtras { 224 | WGPUChainedStruct chain; 225 | uint32_t count; 226 | } WGPUBindGroupLayoutEntryExtras; 227 | 228 | typedef struct WGPUQuerySetDescriptorExtras { 229 | WGPUChainedStruct chain; 230 | WGPUPipelineStatisticName const * pipelineStatistics; 231 | size_t pipelineStatisticCount; 232 | } WGPUQuerySetDescriptorExtras WGPU_STRUCTURE_ATTRIBUTE; 233 | 234 | typedef struct WGPUSurfaceConfigurationExtras { 235 | WGPUChainedStruct chain; 236 | uint32_t desiredMaximumFrameLatency; 237 | } WGPUSurfaceConfigurationExtras WGPU_STRUCTURE_ATTRIBUTE; 238 | 239 | typedef void (*WGPULogCallback)(WGPULogLevel level, WGPUStringView message, void * userdata); 240 | 241 | typedef enum WGPUNativeTextureFormat { 242 | // From Features::TEXTURE_FORMAT_16BIT_NORM 243 | WGPUNativeTextureFormat_R16Unorm = 0x00030001, 244 | WGPUNativeTextureFormat_R16Snorm = 0x00030002, 245 | WGPUNativeTextureFormat_Rg16Unorm = 0x00030003, 246 | WGPUNativeTextureFormat_Rg16Snorm = 0x00030004, 247 | WGPUNativeTextureFormat_Rgba16Unorm = 0x00030005, 248 | WGPUNativeTextureFormat_Rgba16Snorm = 0x00030006, 249 | // From Features::TEXTURE_FORMAT_NV12 250 | WGPUNativeTextureFormat_NV12 = 0x00030007, 251 | } WGPUNativeTextureFormat; 252 | 253 | #ifdef __cplusplus 254 | extern "C" { 255 | #endif 256 | 257 | void wgpuGenerateReport(WGPUInstance instance, WGPUGlobalReport * report); 258 | size_t wgpuInstanceEnumerateAdapters(WGPUInstance instance, WGPU_NULLABLE WGPUInstanceEnumerateAdapterOptions const * options, WGPUAdapter * adapters); 259 | 260 | WGPUSubmissionIndex wgpuQueueSubmitForIndex(WGPUQueue queue, size_t commandCount, WGPUCommandBuffer const * commands); 261 | 262 | // Returns true if the queue is empty, or false if there are more queue submissions still in flight. 263 | WGPUBool wgpuDevicePoll(WGPUDevice device, WGPUBool wait, WGPU_NULLABLE WGPUSubmissionIndex const * wrappedSubmissionIndex); 264 | WGPUShaderModule wgpuDeviceCreateShaderModuleSpirV(WGPUDevice device, WGPUShaderModuleDescriptorSpirV const * descriptor); 265 | 266 | void wgpuSetLogCallback(WGPULogCallback callback, void * userdata); 267 | 268 | void wgpuSetLogLevel(WGPULogLevel level); 269 | 270 | uint32_t wgpuGetVersion(void); 271 | 272 | void wgpuRenderPassEncoderSetPushConstants(WGPURenderPassEncoder encoder, WGPUShaderStage stages, uint32_t offset, uint32_t sizeBytes, void const * data); 273 | void wgpuComputePassEncoderSetPushConstants(WGPUComputePassEncoder encoder, uint32_t offset, uint32_t sizeBytes, void const * data); 274 | void wgpuRenderBundleEncoderSetPushConstants(WGPURenderBundleEncoder encoder, WGPUShaderStage stages, uint32_t offset, uint32_t sizeBytes, void const * data); 275 | 276 | void wgpuRenderPassEncoderMultiDrawIndirect(WGPURenderPassEncoder encoder, WGPUBuffer buffer, uint64_t offset, uint32_t count); 277 | void wgpuRenderPassEncoderMultiDrawIndexedIndirect(WGPURenderPassEncoder encoder, WGPUBuffer buffer, uint64_t offset, uint32_t count); 278 | 279 | void wgpuRenderPassEncoderMultiDrawIndirectCount(WGPURenderPassEncoder encoder, WGPUBuffer buffer, uint64_t offset, WGPUBuffer count_buffer, uint64_t count_buffer_offset, uint32_t max_count); 280 | void wgpuRenderPassEncoderMultiDrawIndexedIndirectCount(WGPURenderPassEncoder encoder, WGPUBuffer buffer, uint64_t offset, WGPUBuffer count_buffer, uint64_t count_buffer_offset, uint32_t max_count); 281 | 282 | void wgpuComputePassEncoderBeginPipelineStatisticsQuery(WGPUComputePassEncoder computePassEncoder, WGPUQuerySet querySet, uint32_t queryIndex); 283 | void wgpuComputePassEncoderEndPipelineStatisticsQuery(WGPUComputePassEncoder computePassEncoder); 284 | void wgpuRenderPassEncoderBeginPipelineStatisticsQuery(WGPURenderPassEncoder renderPassEncoder, WGPUQuerySet querySet, uint32_t queryIndex); 285 | void wgpuRenderPassEncoderEndPipelineStatisticsQuery(WGPURenderPassEncoder renderPassEncoder); 286 | 287 | void wgpuComputePassEncoderWriteTimestamp(WGPUComputePassEncoder computePassEncoder, WGPUQuerySet querySet, uint32_t queryIndex); 288 | void wgpuRenderPassEncoderWriteTimestamp(WGPURenderPassEncoder renderPassEncoder, WGPUQuerySet querySet, uint32_t queryIndex); 289 | 290 | #ifdef __cplusplus 291 | } // extern "C" 292 | #endif 293 | 294 | #endif 295 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | Learn WebGPU Logo 6 | 7 | 8 | LearnWebGPU  |  WebGPU-C++  |  WebGPU-distribution
9 | glfw3webgpu  |  sdl2webgpu  |  sdl3webgpu 10 | 11 | Discord | Join us! 12 |
13 | 14 | WebGPU-C++ 15 | ========== 16 | 17 | **Just what you need** to make the raw C API of WebGPU feel **a bit more confy** when programming in C++, but **zero runtime overhead**, this is mostly syntactic sugar! 🧁 18 | 19 | Table of Contents 20 | ----------------- 21 | 22 | - [What is this?](#what-is-this) 23 | * [Features at a glance](#features-at-a-glance) 24 | - [Quick Start](#quick-start) 25 | * [Setup](#setup) 26 | * [Usage](#usage) 27 | - [Going Further](#going-further) 28 | * [Custom generation](#custom-generation) 29 | * [Default values](#default-values) 30 | * [See also](#see-also) 31 | - [License](#license) 32 | 33 | What is this? 34 | ------------- 35 | 36 | This is a **single-file** shallow wrapper for using the WebGPU native API in a more **C++ idiomatic** way. It comes with **zero overhead** and is mostly syntactic sugar to make the original C API less verbose. All C++ types are naturally compatible with original C-style types, the memory layout remains untouched. 37 | 38 | The C++ wrapper is automatically generated from the official [webgpu.h](https://raw.githubusercontent.com/webgpu-native/webgpu-headers/main/webgpu.h), with the possibility to inject custom code and blacklist symbols that your implementation of `webgpu.h` does not handle yet (see [Going Further](#going-further)). 39 | 40 | ### Features at a glance 41 | 42 | - Namespace 43 | - Default descriptor values 44 | - Object notation 45 | - Capturing closures 46 | - Scoped enumerations 47 | 48 | There is also an extra `webgpu-raii.hpp` file generated alongside `webgpu.hpp`, which provides a [RAII](https://en.cppreference.com/w/cpp/language/raii) wrapper around WebGPU objects, so that lifetime is automatically managed. This is kept in a separate file, because the core `webgpu.hpp` is meant not to introduce any behavior compared to using the raw C API. More info in [WebGPU-raii](https://github.com/eliemichel/WebGPU-raii). 49 | 50 | Quick Start 51 | ----------- 52 | 53 | **NB** *To get started with WebGPU in general, see our [Learn WebGPU for C++](https://eliemichel.github.io/LearnWebGPU) documentation!* 54 | 55 | ### Setup 56 | 57 | 1. Copy the file [dawn/webgpu.hpp](dawn/webgpu.hpp), [wgpu-native/webgpu.hpp](wgpu-native/webgpu.hpp) or [emscripten/webgpu.hpp](emscripten/webgpu.hpp) (depending on your backend) to your C++17 project. 58 | 59 | **NB** You should also check the git tag that is in a file of this repo next to each `webgpu.hpp`. If your version of the backend is newer, see below how to generate the `webgpu.hpp` file yourself. 60 | 61 | 2. Replace `#include ` by `#include "webgpu/webgpu.hpp"` in your source files: 62 | 63 | ```C++ 64 | #include "webgpu/webgpu.hpp" 65 | ``` 66 | 67 | 3. In **exactly one** of your source files, add `#define WEBGPU_CPP_IMPLEMENTATION` before including webgpu.hpp: 68 | 69 | ```C++ 70 | #define WEBGPU_CPP_IMPLEMENTATION 71 | #include "webgpu/webgpu.hpp" 72 | ``` 73 | 74 | ### Usage 75 | 76 | #### Namespace 77 | 78 | Instead of prefixing every struct name with `WGPU` and every function name with `wgpu` (the C way), all symbols are put in a `wgpu` namespace (the C++ way): 79 | 80 | ```C 81 | // C style 82 | WGPUInstanceDescriptor desc = {}; 83 | desc.nextInChain = nullptr; 84 | WGPUInstance instance = wgpuCreateInstance(&desc); 85 | ``` 86 | 87 | becomes with namespaces: 88 | 89 | ```C++ 90 | // C++ style 91 | wgpu::InstanceDescriptor desc = {}; 92 | wgpu::Instance instance = wgpu::createInstance(desc); 93 | ``` 94 | 95 | Note that `wgpu::createInstance` accepts a reference to `desc`, where `wgpuCreateInstance` accepted a pointer. 96 | 97 | And of course you can start your source file with `using namespace wgpu;` to avoid spelling out `wgpu::` everywhere (although broad use of `using namespace` is not usually considered good practice). 98 | 99 | #### Default descriptor values 100 | 101 | Sometimes we just need to build a descriptor by default. More generally, we rarely need to have all the fields of the descriptor deviate from the default, so this wrapper provides an easy way to set all fields to the default values, which is automatically synchronized from [the official WebGPU specification](https://www.w3.org/TR/webgpu). 102 | 103 | Since it is very common, the `nextInChain` value is automatically set to `nullptr` by default. For other fields, the user must call the `setDefault()` method. This manual action prevents the wrapper from having hidden overhead: 104 | 105 | ```C++ 106 | BindGroupLayoutEntry bindGroupLayoutEntry; 107 | 108 | // This recursively set defaults for bindGroupLayoutEntry.buffer, 109 | // bindGroupLayoutEntry.sampler, etc. 110 | bindGroupLayoutEntry.setDefault(); 111 | 112 | // Even more compact: 113 | BindGroupLayoutEntry bindGroupLayoutEntry = Default; 114 | ``` 115 | 116 | #### Object notation 117 | 118 | Beyond namespace, most functions are also previewed by the name of their first argument, e.g.: 119 | 120 | ```C 121 | // C style 122 | WGPUBuffer wgpuDeviceCreateBuffer(WGPUDevice device, WGPUBufferDescriptor const * descriptor); 123 | ^^^^^^ ^^^^^^^^^^^^^^^^^ 124 | size_t wgpuAdapterEnumerateFeatures(WGPUAdapter adapter, WGPUFeatureName * features); 125 | ^^^^^^^ ^^^^^^^^^^^^^^^^^^^ 126 | void wgpuBufferDestroy(WGPUBuffer buffer); 127 | ^^^^^^ ^^^^^^^^^^^^^^^^^ 128 | ``` 129 | 130 | These functions are conceptually *methods* of the object constituted by their first argument, so in this wrapper they are exposed as such: 131 | 132 | ```C++ 133 | // C++ style 134 | namespace wgpu { 135 | struct Device { 136 | // [...] 137 | createBuffer(const BufferDescriptor& descriptor); 138 | }; 139 | 140 | struct Adapter { 141 | // [...] 142 | enumerateFeatures(FeatureName * features); 143 | }; 144 | 145 | struct Device { 146 | // [...] 147 | destroy(); 148 | }; 149 | } // namespace wgpu 150 | ``` 151 | 152 | Note also that descriptors are passed by reference rather than by pointer. 153 | 154 | #### Capturing closures 155 | 156 | Many asynchronous operations use callbacks. In order to provide some context to the callback's body, there is always a `void *userdata` argument passed around. This can be alleviated in C++ by using capturing closures. 157 | 158 | **NB** *This only alleviates the notations, but technically mechanism very similar to the user data pointer is automatically implemented when creating a capturing lambda.* 159 | 160 | ```C++ 161 | // C style 162 | struct Context { 163 | WGPUBuffer buffer; 164 | }; 165 | auto onBufferMapped = [](WGPUBufferMapAsyncStatus status, void* pUserData) { 166 | Context* context = reinterpret_cast(pUserData); 167 | std::cout << "Buffer mapped with status " << status << std::endl; 168 | unsigned char* bufferData = (unsigned char*)wgpuBufferGetMappedRange(context->buffer, 0, 16); 169 | std::cout << "bufferData[0] = " << (int)bufferData[0] << std::endl; 170 | wgpuBufferUnmap(context->buffer); 171 | }; 172 | Context context; 173 | wgpuBufferMapAsync(buffer, WGPUMapMode_Read, 0, 16, onBufferMapped, (void*)&context); 174 | ``` 175 | 176 | becomes 177 | 178 | ```C++ 179 | // C++ style 180 | buffer.mapAsync(buffer, [&context](wgpu::BufferMapAsyncStatus status) { 181 | std::cout << "Buffer mapped with status " << status << std::endl; 182 | unsigned char* bufferData = (unsigned char*)context.buffer.getMappedRange(0, 16); 183 | std::cout << "bufferData[0] = " << (int)bufferData[0] << std::endl; 184 | context.buffer.unmap(); 185 | }); 186 | ``` 187 | 188 | #### Scoped enumerations 189 | 190 | Because enums are *unscoped* by default, the WebGPU API is forced to prefix all values that an enum can take with the name of the enum, leading to quite long names: 191 | 192 | ```C 193 | typedef enum WGPURequestAdapterStatus { 194 | WGPURequestAdapterStatus_Success = 0x00000000, 195 | WGPURequestAdapterStatus_Unavailable = 0x00000001, 196 | WGPURequestAdapterStatus_Error = 0x00000002, 197 | WGPURequestAdapterStatus_Unknown = 0x00000003, 198 | WGPURequestAdapterStatus_Force32 = 0x7FFFFFFF 199 | } WGPURequestAdapterStatus; 200 | ``` 201 | 202 | It is possible in C++ to define *scoped* enums, which are strongly typed and can only be accessed through the name, for instance this scoped enum: 203 | 204 | ```C++ 205 | enum class RequestAdapterStatus { 206 | Success = 0x00000000, 207 | Unavailable = 0x00000001, 208 | Error = 0x00000002, 209 | Unknown = 0x00000003, 210 | Force32 = 0x7FFFFFFF 211 | }; 212 | ``` 213 | 214 | This can be used as follows: 215 | 216 | ```C++ 217 | wgpu::RequestAdapterStatus::Success; 218 | ``` 219 | 220 | **NB** *The actual implementation use a little trickery so that enum names are scoped, but implicitly converted to and from the original WebGPU enum values.* 221 | 222 | Going further 223 | ------------- 224 | 225 | ### Custom generation 226 | 227 | The easiest route is to use the Web service: [https://eliemichel.github.io/WebGPU-Cpp](https://eliemichel.github.io/WebGPU-Cpp) 228 | 229 | Otherwise, you can run the generation script locally. The file `webgpu.hpp` is generated by running the `generate.py` script (with at least Python 3.10). The best way to get an up to date doc is through it's help message: 230 | 231 | ``` 232 | $ python generate.py --help 233 | usage: generate.py [-h] [-t TEMPLATE] [-o OUTPUT] [-u HEADER_URL] [-d DEFAULTS] [--no-scoped-enums] 234 | [--no-fake-scoped-enums] [--use-non-member-procedures] [--no-const] 235 | 236 | Generate the webgpu-cpp binding from official webgpu-native headers. You should not have to change 237 | any of the default arguments for a regular use. This generates a webgpu.hpp file that you can 238 | include in your project. Exactly one of your source files must #define WEBGPU_CPP_IMPLEMENTATION 239 | before including this header. 240 | 241 | options: 242 | -h, --help show this help message and exit 243 | -t TEMPLATE, --template TEMPLATE 244 | Template used for generating the output binding file 245 | -o OUTPUT, --output OUTPUT 246 | Path where to output the generated webgpu.hpp 247 | -u HEADER_URL, --header-url HEADER_URL 248 | URL of the official webgpu.h from the webgpu-native project. If the URL 249 | does not start with http(s)://, it is considered as a local file. You can 250 | specify this option multiple times to agregate multiple headers (e.g., the 251 | standard webgpu.h plus backend-specific extensions wgpu.h). If no URL is 252 | specified, the official header from 253 | 'https://raw.githubusercontent.com/webgpu-native/webgpu-headers/main/webgpu.h' 254 | is used. 255 | -d DEFAULTS, --defaults DEFAULTS 256 | File listing default values for descriptor fields. This argument can be 257 | provided multiple times, the last ones override the previous values. 258 | --no-scoped-enums Do not replace WebGPU enums with C++ scoped enums 259 | --no-fake-scoped-enums 260 | Use scoped aliases to original enum values so that no casting is needed 261 | --use-non-member-procedures 262 | Include WebGPU methods that are not members of any WebGPU object 263 | --no-const By default, all methods of opaque handle types are const. This option makes 264 | them all non-const. 265 | ``` 266 | 267 | The default options download the last version of `webgpu.h` from the [webgpu-native/webgpu-headers repository](https://github.com/webgpu-native/webgpu-headers), but it is recommended to check that it matches your own version of this file (provided by your implementation, e.g., wgpu-native or Dawn). 268 | 269 | The template file uses a quite simple format: variables to be replaced by a snippet of code is put inside double brackets, e.g., `{{foo}}`. You can look at [webgpu.template.hpp](webgpu.template.hpp) for a list of available such variables. 270 | 271 | There are two special pairs of tags which are not variables: 272 | 273 | #### Blacklist 274 | 275 | Between `{{begin-blacklist}}` and `{{end-blacklist}}`, you can list WebGPU procedures that you would like the binding to ignore. This must be used when your version of `webgpu.h` declares some symbols that are not actually implemented by your binaries of WebGPU. 276 | 277 | #### Member injection 278 | 279 | Between `{{begin-inject}}` and `{{end-inject}}`, you can specify extra methods to inject in the auto-generated handle types. Within this scope, define a subscope with `HANDLE(Foo)` and `END` tags to add arbitrary members in type `Foo`. 280 | 281 | You must then declare the body of this method at the end of the file, between `#ifdef WEBGPU_CPP_IMPLEMENTATION` and `#endif // WEBGPU_CPP_IMPLEMENTATION` 282 | 283 | ### Default values 284 | 285 | Default values for descriptors and structs are not provided in the `webgpu.h` native header. They are however specified in [the official WebGPU specification](https://www.w3.org/TR/webgpu), so we provide a script that downloads and scraps this document to extract the default values. 286 | 287 | **NB** Running this script creates a `defaults.txt` file. If you want to add your own default values overrides, it is recommended to do so in the `extra-defaults.txt` file so that they do not get overwritten when running the scraper again. 288 | 289 | Enumeration for which there exists an enum value `Undefined` default to this value, unless they have been specified a different default value in one of the `default.txt` files. 290 | 291 | ``` 292 | $ python fetch_default_values.py --help 293 | usage: fetch_default_values [-h] [-u URL] [-v LOG_LEVEL] [-s OUTPUT_SPEC] [-d OUTPUT_DEFAULTS] 294 | 295 | Fetch from the official WebGPU specification the default values for descriptors. Copyright (c) 2022-2023 Élie Michel 296 | 297 | options: 298 | -h, --help show this help message and exit 299 | -u URL, --url URL URL of the specification document 300 | -v LOG_LEVEL, --log-level LOG_LEVEL 301 | level of verbosity: DEBUG, INFO, WARNING ou ERROR 302 | -s OUTPUT_SPEC, --output-spec OUTPUT_SPEC 303 | output JSON file containing the scraped API 304 | -d OUTPUT_DEFAULTS, --output-defaults OUTPUT_DEFAULTS 305 | output file containing the default values 306 | ``` 307 | 308 | ### Nullable descriptors 309 | 310 | Some procedures (e.g., `wgpuTextureCreateView`) have their last argument marked as "nullable". In this case, an overloaded version of the fonction is generated by the wrapper, where this last argument is removed, and internally `nullptr` is provided. 311 | 312 | ### Update all generated examples 313 | 314 | The examples provided in this repository were generated as follows: 315 | 316 | ``` 317 | # wgpu-native 318 | python generate.py -u wgpu-native/webgpu.h -u wgpu-native/wgpu.h -t webgpu.template.hpp -o wgpu-native/webgpu.hpp -d defaults.txt -d extra-defaults.txt 319 | 320 | # Dawn 321 | python generate.py -u dawn/webgpu.h -t webgpu.template.hpp -o dawn/webgpu.hpp --use-init-macros 322 | 323 | # emscripten 324 | python generate.py -u emscripten/webgpu.h -t emscripten/webgpu.template.hpp -o emscripten/webgpu.hpp -d defaults.txt -d extra-defaults.txt 325 | 326 | # emdawn 327 | python generate.py -u emdawn/webgpu.h -t webgpu.template.hpp -o emdawn/webgpu.hpp --use-init-macros 328 | ``` 329 | 330 | And the RAII wrappers were generated with the following commands: 331 | 332 | ``` 333 | # wgpu-native 334 | python generate.py -u wgpu-native/webgpu.h -u wgpu-native/wgpu.h -t webgpu-raii.template.hpp -o wgpu-native/webgpu-raii.hpp 335 | 336 | # Dawn 337 | python generate.py -u dawn/webgpu.h -t webgpu-raii.template.hpp -o dawn/webgpu-raii.hpp --use-init-macros 338 | 339 | # emscripten 340 | python generate.py -u emscripten/webgpu.h -t webgpu-raii.template.hpp -o emscripten/webgpu-raii.hpp 341 | 342 | # emdawn 343 | python generate.py -u emdawn/webgpu.h -t webgpu-raii.template.hpp -o emdawn/webgpu-raii.hpp --use-init-macros 344 | ``` 345 | 346 | ### See also 347 | 348 | To get started with WebGPU, see our [Learn WebGPU for C++](https://eliemichel.github.io/LearnWebGPU) documentation! 349 | 350 | This work was inspired by [PpluX' wgpu.hpp](https://github.com/pplux/wgpu.hpp). This repository has included a generator compatible with PpluX' one until [tag `pplux`](https://github.com/eliemichel/WebGPU-Cpp/releases/tag/pplux). 351 | 352 | License 353 | ------- 354 | 355 | See [LICENSE.txt](LICENSE.txt) 356 | -------------------------------------------------------------------------------- /spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "dictionaries": { 3 | "GPUObjectDescriptorBase": { 4 | "name": "GPUObjectDescriptorBase", 5 | "parent": null, 6 | "fields": [] 7 | }, 8 | "GPURequestAdapterOptions": { 9 | "name": "GPURequestAdapterOptions", 10 | "parent": null, 11 | "fields": [ 12 | { 13 | "name": "forceFallbackAdapter", 14 | "type": "boolean", 15 | "default": "false", 16 | "required": false 17 | } 18 | ] 19 | }, 20 | "GPUDeviceDescriptor": { 21 | "name": "GPUDeviceDescriptor", 22 | "parent": "GPUObjectDescriptorBase", 23 | "fields": [] 24 | }, 25 | "GPUBufferDescriptor": { 26 | "name": "GPUBufferDescriptor", 27 | "parent": "GPUObjectDescriptorBase", 28 | "fields": [ 29 | { 30 | "name": "size", 31 | "type": "GPUSize64", 32 | "default": null, 33 | "required": true 34 | }, 35 | { 36 | "name": "usage", 37 | "type": "GPUBufferUsageFlags", 38 | "default": null, 39 | "required": true 40 | }, 41 | { 42 | "name": "mappedAtCreation", 43 | "type": "boolean", 44 | "default": "false", 45 | "required": false 46 | } 47 | ] 48 | }, 49 | "GPUTextureDescriptor": { 50 | "name": "GPUTextureDescriptor", 51 | "parent": "GPUObjectDescriptorBase", 52 | "fields": [ 53 | { 54 | "name": "size", 55 | "type": "GPUExtent3D", 56 | "default": null, 57 | "required": true 58 | }, 59 | { 60 | "name": "mipLevelCount", 61 | "type": "GPUIntegerCoordinate", 62 | "default": "1", 63 | "required": false 64 | }, 65 | { 66 | "name": "sampleCount", 67 | "type": "GPUSize32", 68 | "default": "1", 69 | "required": false 70 | }, 71 | { 72 | "name": "dimension", 73 | "type": "GPUTextureDimension", 74 | "default": "\"2d\"", 75 | "required": false 76 | }, 77 | { 78 | "name": "format", 79 | "type": "GPUTextureFormat", 80 | "default": null, 81 | "required": true 82 | }, 83 | { 84 | "name": "usage", 85 | "type": "GPUTextureUsageFlags", 86 | "default": null, 87 | "required": true 88 | } 89 | ] 90 | }, 91 | "GPUTextureViewDescriptor": { 92 | "name": "GPUTextureViewDescriptor", 93 | "parent": "GPUObjectDescriptorBase", 94 | "fields": [ 95 | { 96 | "name": "aspect", 97 | "type": "GPUTextureAspect", 98 | "default": "\"all\"", 99 | "required": false 100 | }, 101 | { 102 | "name": "baseMipLevel", 103 | "type": "GPUIntegerCoordinate", 104 | "default": "0", 105 | "required": false 106 | }, 107 | { 108 | "name": "baseArrayLayer", 109 | "type": "GPUIntegerCoordinate", 110 | "default": "0", 111 | "required": false 112 | } 113 | ] 114 | }, 115 | "GPUExternalTextureDescriptor": { 116 | "name": "GPUExternalTextureDescriptor", 117 | "parent": "GPUObjectDescriptorBase", 118 | "fields": [ 119 | { 120 | "name": "colorSpace", 121 | "type": "PredefinedColorSpace", 122 | "default": "\"srgb\"", 123 | "required": false 124 | } 125 | ] 126 | }, 127 | "GPUSamplerDescriptor": { 128 | "name": "GPUSamplerDescriptor", 129 | "parent": "GPUObjectDescriptorBase", 130 | "fields": [ 131 | { 132 | "name": "addressModeU", 133 | "type": "GPUAddressMode", 134 | "default": "\"clamp-to-edge\"", 135 | "required": false 136 | }, 137 | { 138 | "name": "addressModeV", 139 | "type": "GPUAddressMode", 140 | "default": "\"clamp-to-edge\"", 141 | "required": false 142 | }, 143 | { 144 | "name": "addressModeW", 145 | "type": "GPUAddressMode", 146 | "default": "\"clamp-to-edge\"", 147 | "required": false 148 | }, 149 | { 150 | "name": "magFilter", 151 | "type": "GPUFilterMode", 152 | "default": "\"nearest\"", 153 | "required": false 154 | }, 155 | { 156 | "name": "minFilter", 157 | "type": "GPUFilterMode", 158 | "default": "\"nearest\"", 159 | "required": false 160 | }, 161 | { 162 | "name": "mipmapFilter", 163 | "type": "GPUMipmapFilterMode", 164 | "default": "\"nearest\"", 165 | "required": false 166 | }, 167 | { 168 | "name": "lodMinClamp", 169 | "type": "float", 170 | "default": "0", 171 | "required": false 172 | }, 173 | { 174 | "name": "lodMaxClamp", 175 | "type": "float", 176 | "default": "32", 177 | "required": false 178 | } 179 | ] 180 | }, 181 | "GPUBindGroupLayoutDescriptor": { 182 | "name": "GPUBindGroupLayoutDescriptor", 183 | "parent": "GPUObjectDescriptorBase", 184 | "fields": [] 185 | }, 186 | "GPUBindGroupLayoutEntry": { 187 | "name": "GPUBindGroupLayoutEntry", 188 | "parent": null, 189 | "fields": [ 190 | { 191 | "name": "binding", 192 | "type": "GPUIndex32", 193 | "default": null, 194 | "required": true 195 | }, 196 | { 197 | "name": "visibility", 198 | "type": "GPUShaderStageFlags", 199 | "default": null, 200 | "required": true 201 | } 202 | ] 203 | }, 204 | "GPUBufferBindingLayout": { 205 | "name": "GPUBufferBindingLayout", 206 | "parent": null, 207 | "fields": [ 208 | { 209 | "name": "type", 210 | "type": "GPUBufferBindingType", 211 | "default": "\"uniform\"", 212 | "required": false 213 | }, 214 | { 215 | "name": "hasDynamicOffset", 216 | "type": "boolean", 217 | "default": "false", 218 | "required": false 219 | }, 220 | { 221 | "name": "minBindingSize", 222 | "type": "GPUSize64", 223 | "default": "0", 224 | "required": false 225 | } 226 | ] 227 | }, 228 | "GPUSamplerBindingLayout": { 229 | "name": "GPUSamplerBindingLayout", 230 | "parent": null, 231 | "fields": [ 232 | { 233 | "name": "type", 234 | "type": "GPUSamplerBindingType", 235 | "default": "\"filtering\"", 236 | "required": false 237 | } 238 | ] 239 | }, 240 | "GPUTextureBindingLayout": { 241 | "name": "GPUTextureBindingLayout", 242 | "parent": null, 243 | "fields": [ 244 | { 245 | "name": "sampleType", 246 | "type": "GPUTextureSampleType", 247 | "default": "\"float\"", 248 | "required": false 249 | }, 250 | { 251 | "name": "viewDimension", 252 | "type": "GPUTextureViewDimension", 253 | "default": "\"2d\"", 254 | "required": false 255 | }, 256 | { 257 | "name": "multisampled", 258 | "type": "boolean", 259 | "default": "false", 260 | "required": false 261 | } 262 | ] 263 | }, 264 | "GPUStorageTextureBindingLayout": { 265 | "name": "GPUStorageTextureBindingLayout", 266 | "parent": null, 267 | "fields": [ 268 | { 269 | "name": "access", 270 | "type": "GPUStorageTextureAccess", 271 | "default": "\"write-only\"", 272 | "required": false 273 | }, 274 | { 275 | "name": "format", 276 | "type": "GPUTextureFormat", 277 | "default": null, 278 | "required": true 279 | }, 280 | { 281 | "name": "viewDimension", 282 | "type": "GPUTextureViewDimension", 283 | "default": "\"2d\"", 284 | "required": false 285 | } 286 | ] 287 | }, 288 | "GPUExternalTextureBindingLayout": { 289 | "name": "GPUExternalTextureBindingLayout", 290 | "parent": null, 291 | "fields": [] 292 | }, 293 | "GPUBindGroupDescriptor": { 294 | "name": "GPUBindGroupDescriptor", 295 | "parent": "GPUObjectDescriptorBase", 296 | "fields": [ 297 | { 298 | "name": "layout", 299 | "type": "GPUBindGroupLayout", 300 | "default": null, 301 | "required": true 302 | } 303 | ] 304 | }, 305 | "GPUBindGroupEntry": { 306 | "name": "GPUBindGroupEntry", 307 | "parent": null, 308 | "fields": [ 309 | { 310 | "name": "binding", 311 | "type": "GPUIndex32", 312 | "default": null, 313 | "required": true 314 | }, 315 | { 316 | "name": "resource", 317 | "type": "GPUBindingResource", 318 | "default": null, 319 | "required": true 320 | } 321 | ] 322 | }, 323 | "GPUBufferBinding": { 324 | "name": "GPUBufferBinding", 325 | "parent": null, 326 | "fields": [ 327 | { 328 | "name": "buffer", 329 | "type": "GPUBuffer", 330 | "default": null, 331 | "required": true 332 | }, 333 | { 334 | "name": "offset", 335 | "type": "GPUSize64", 336 | "default": "0", 337 | "required": false 338 | } 339 | ] 340 | }, 341 | "GPUPipelineLayoutDescriptor": { 342 | "name": "GPUPipelineLayoutDescriptor", 343 | "parent": "GPUObjectDescriptorBase", 344 | "fields": [] 345 | }, 346 | "GPUShaderModuleDescriptor": { 347 | "name": "GPUShaderModuleDescriptor", 348 | "parent": "GPUObjectDescriptorBase", 349 | "fields": [ 350 | { 351 | "name": "code", 352 | "type": "USVString", 353 | "default": null, 354 | "required": true 355 | } 356 | ] 357 | }, 358 | "GPUShaderModuleCompilationHint": { 359 | "name": "GPUShaderModuleCompilationHint", 360 | "parent": null, 361 | "fields": [ 362 | { 363 | "name": "entryPoint", 364 | "type": "USVString", 365 | "default": null, 366 | "required": true 367 | } 368 | ] 369 | }, 370 | "GPUPipelineErrorInit": { 371 | "name": "GPUPipelineErrorInit", 372 | "parent": null, 373 | "fields": [ 374 | { 375 | "name": "reason", 376 | "type": "GPUPipelineErrorReason", 377 | "default": null, 378 | "required": true 379 | } 380 | ] 381 | }, 382 | "GPUPipelineDescriptorBase": { 383 | "name": "GPUPipelineDescriptorBase", 384 | "parent": "GPUObjectDescriptorBase", 385 | "fields": [] 386 | }, 387 | "GPUProgrammableStage": { 388 | "name": "GPUProgrammableStage", 389 | "parent": null, 390 | "fields": [ 391 | { 392 | "name": "module", 393 | "type": "GPUShaderModule", 394 | "default": null, 395 | "required": true 396 | } 397 | ] 398 | }, 399 | "GPUComputePipelineDescriptor": { 400 | "name": "GPUComputePipelineDescriptor", 401 | "parent": "GPUPipelineDescriptorBase", 402 | "fields": [ 403 | { 404 | "name": "compute", 405 | "type": "GPUProgrammableStage", 406 | "default": null, 407 | "required": true 408 | } 409 | ] 410 | }, 411 | "GPURenderPipelineDescriptor": { 412 | "name": "GPURenderPipelineDescriptor", 413 | "parent": "GPUPipelineDescriptorBase", 414 | "fields": [ 415 | { 416 | "name": "vertex", 417 | "type": "GPUVertexState", 418 | "default": null, 419 | "required": true 420 | } 421 | ] 422 | }, 423 | "GPUPrimitiveState": { 424 | "name": "GPUPrimitiveState", 425 | "parent": null, 426 | "fields": [ 427 | { 428 | "name": "topology", 429 | "type": "GPUPrimitiveTopology", 430 | "default": "\"triangle-list\"", 431 | "required": false 432 | }, 433 | { 434 | "name": "frontFace", 435 | "type": "GPUFrontFace", 436 | "default": "\"ccw\"", 437 | "required": false 438 | }, 439 | { 440 | "name": "cullMode", 441 | "type": "GPUCullMode", 442 | "default": "\"none\"", 443 | "required": false 444 | }, 445 | { 446 | "name": "unclippedDepth", 447 | "type": "boolean", 448 | "default": "false", 449 | "required": false 450 | } 451 | ] 452 | }, 453 | "GPUMultisampleState": { 454 | "name": "GPUMultisampleState", 455 | "parent": null, 456 | "fields": [ 457 | { 458 | "name": "count", 459 | "type": "GPUSize32", 460 | "default": "1", 461 | "required": false 462 | }, 463 | { 464 | "name": "mask", 465 | "type": "GPUSampleMask", 466 | "default": "0xFFFFFFFF", 467 | "required": false 468 | }, 469 | { 470 | "name": "alphaToCoverageEnabled", 471 | "type": "boolean", 472 | "default": "false", 473 | "required": false 474 | } 475 | ] 476 | }, 477 | "GPUFragmentState": { 478 | "name": "GPUFragmentState", 479 | "parent": "GPUProgrammableStage", 480 | "fields": [] 481 | }, 482 | "GPUColorTargetState": { 483 | "name": "GPUColorTargetState", 484 | "parent": null, 485 | "fields": [ 486 | { 487 | "name": "format", 488 | "type": "GPUTextureFormat", 489 | "default": null, 490 | "required": true 491 | } 492 | ] 493 | }, 494 | "GPUBlendState": { 495 | "name": "GPUBlendState", 496 | "parent": null, 497 | "fields": [ 498 | { 499 | "name": "color", 500 | "type": "GPUBlendComponent", 501 | "default": null, 502 | "required": true 503 | }, 504 | { 505 | "name": "alpha", 506 | "type": "GPUBlendComponent", 507 | "default": null, 508 | "required": true 509 | } 510 | ] 511 | }, 512 | "GPUBlendComponent": { 513 | "name": "GPUBlendComponent", 514 | "parent": null, 515 | "fields": [ 516 | { 517 | "name": "operation", 518 | "type": "GPUBlendOperation", 519 | "default": "\"add\"", 520 | "required": false 521 | }, 522 | { 523 | "name": "srcFactor", 524 | "type": "GPUBlendFactor", 525 | "default": "\"one\"", 526 | "required": false 527 | }, 528 | { 529 | "name": "dstFactor", 530 | "type": "GPUBlendFactor", 531 | "default": "\"zero\"", 532 | "required": false 533 | } 534 | ] 535 | }, 536 | "GPUDepthStencilState": { 537 | "name": "GPUDepthStencilState", 538 | "parent": null, 539 | "fields": [ 540 | { 541 | "name": "format", 542 | "type": "GPUTextureFormat", 543 | "default": null, 544 | "required": true 545 | }, 546 | { 547 | "name": "stencilReadMask", 548 | "type": "GPUStencilValue", 549 | "default": "0xFFFFFFFF", 550 | "required": false 551 | }, 552 | { 553 | "name": "stencilWriteMask", 554 | "type": "GPUStencilValue", 555 | "default": "0xFFFFFFFF", 556 | "required": false 557 | }, 558 | { 559 | "name": "depthBias", 560 | "type": "GPUDepthBias", 561 | "default": "0", 562 | "required": false 563 | }, 564 | { 565 | "name": "depthBiasSlopeScale", 566 | "type": "float", 567 | "default": "0", 568 | "required": false 569 | }, 570 | { 571 | "name": "depthBiasClamp", 572 | "type": "float", 573 | "default": "0", 574 | "required": false 575 | } 576 | ] 577 | }, 578 | "GPUStencilFaceState": { 579 | "name": "GPUStencilFaceState", 580 | "parent": null, 581 | "fields": [ 582 | { 583 | "name": "compare", 584 | "type": "GPUCompareFunction", 585 | "default": "\"always\"", 586 | "required": false 587 | }, 588 | { 589 | "name": "failOp", 590 | "type": "GPUStencilOperation", 591 | "default": "\"keep\"", 592 | "required": false 593 | }, 594 | { 595 | "name": "depthFailOp", 596 | "type": "GPUStencilOperation", 597 | "default": "\"keep\"", 598 | "required": false 599 | }, 600 | { 601 | "name": "passOp", 602 | "type": "GPUStencilOperation", 603 | "default": "\"keep\"", 604 | "required": false 605 | } 606 | ] 607 | }, 608 | "GPUVertexState": { 609 | "name": "GPUVertexState", 610 | "parent": "GPUProgrammableStage", 611 | "fields": [] 612 | }, 613 | "GPUVertexBufferLayout": { 614 | "name": "GPUVertexBufferLayout", 615 | "parent": null, 616 | "fields": [ 617 | { 618 | "name": "arrayStride", 619 | "type": "GPUSize64", 620 | "default": null, 621 | "required": true 622 | }, 623 | { 624 | "name": "stepMode", 625 | "type": "GPUVertexStepMode", 626 | "default": "\"vertex\"", 627 | "required": false 628 | } 629 | ] 630 | }, 631 | "GPUVertexAttribute": { 632 | "name": "GPUVertexAttribute", 633 | "parent": null, 634 | "fields": [ 635 | { 636 | "name": "format", 637 | "type": "GPUVertexFormat", 638 | "default": null, 639 | "required": true 640 | }, 641 | { 642 | "name": "offset", 643 | "type": "GPUSize64", 644 | "default": null, 645 | "required": true 646 | }, 647 | { 648 | "name": "shaderLocation", 649 | "type": "GPUIndex32", 650 | "default": null, 651 | "required": true 652 | } 653 | ] 654 | }, 655 | "GPUImageDataLayout": { 656 | "name": "GPUImageDataLayout", 657 | "parent": null, 658 | "fields": [ 659 | { 660 | "name": "offset", 661 | "type": "GPUSize64", 662 | "default": "0", 663 | "required": false 664 | } 665 | ] 666 | }, 667 | "GPUImageCopyBuffer": { 668 | "name": "GPUImageCopyBuffer", 669 | "parent": "GPUImageDataLayout", 670 | "fields": [ 671 | { 672 | "name": "buffer", 673 | "type": "GPUBuffer", 674 | "default": null, 675 | "required": true 676 | } 677 | ] 678 | }, 679 | "GPUImageCopyTexture": { 680 | "name": "GPUImageCopyTexture", 681 | "parent": null, 682 | "fields": [ 683 | { 684 | "name": "texture", 685 | "type": "GPUTexture", 686 | "default": null, 687 | "required": true 688 | }, 689 | { 690 | "name": "mipLevel", 691 | "type": "GPUIntegerCoordinate", 692 | "default": "0", 693 | "required": false 694 | }, 695 | { 696 | "name": "aspect", 697 | "type": "GPUTextureAspect", 698 | "default": "\"all\"", 699 | "required": false 700 | } 701 | ] 702 | }, 703 | "GPUImageCopyTextureTagged": { 704 | "name": "GPUImageCopyTextureTagged", 705 | "parent": "GPUImageCopyTexture", 706 | "fields": [ 707 | { 708 | "name": "colorSpace", 709 | "type": "PredefinedColorSpace", 710 | "default": "\"srgb\"", 711 | "required": false 712 | }, 713 | { 714 | "name": "premultipliedAlpha", 715 | "type": "boolean", 716 | "default": "false", 717 | "required": false 718 | } 719 | ] 720 | }, 721 | "GPUImageCopyExternalImage": { 722 | "name": "GPUImageCopyExternalImage", 723 | "parent": null, 724 | "fields": [ 725 | { 726 | "name": "source", 727 | "type": "GPUImageCopyExternalImageSource", 728 | "default": null, 729 | "required": true 730 | }, 731 | { 732 | "name": "flipY", 733 | "type": "boolean", 734 | "default": "false", 735 | "required": false 736 | } 737 | ] 738 | }, 739 | "GPUCommandBufferDescriptor": { 740 | "name": "GPUCommandBufferDescriptor", 741 | "parent": "GPUObjectDescriptorBase", 742 | "fields": [] 743 | }, 744 | "GPUCommandEncoderDescriptor": { 745 | "name": "GPUCommandEncoderDescriptor", 746 | "parent": "GPUObjectDescriptorBase", 747 | "fields": [] 748 | }, 749 | "GPUComputePassTimestampWrites": { 750 | "name": "GPUComputePassTimestampWrites", 751 | "parent": null, 752 | "fields": [ 753 | { 754 | "name": "querySet", 755 | "type": "GPUQuerySet", 756 | "default": null, 757 | "required": true 758 | } 759 | ] 760 | }, 761 | "GPUComputePassDescriptor": { 762 | "name": "GPUComputePassDescriptor", 763 | "parent": "GPUObjectDescriptorBase", 764 | "fields": [] 765 | }, 766 | "GPURenderPassTimestampWrites": { 767 | "name": "GPURenderPassTimestampWrites", 768 | "parent": null, 769 | "fields": [ 770 | { 771 | "name": "querySet", 772 | "type": "GPUQuerySet", 773 | "default": null, 774 | "required": true 775 | } 776 | ] 777 | }, 778 | "GPURenderPassDescriptor": { 779 | "name": "GPURenderPassDescriptor", 780 | "parent": "GPUObjectDescriptorBase", 781 | "fields": [ 782 | { 783 | "name": "maxDrawCount", 784 | "type": "GPUSize64", 785 | "default": "50000000", 786 | "required": false 787 | } 788 | ] 789 | }, 790 | "GPURenderPassColorAttachment": { 791 | "name": "GPURenderPassColorAttachment", 792 | "parent": null, 793 | "fields": [ 794 | { 795 | "name": "view", 796 | "type": "GPUTextureView", 797 | "default": null, 798 | "required": true 799 | }, 800 | { 801 | "name": "loadOp", 802 | "type": "GPULoadOp", 803 | "default": null, 804 | "required": true 805 | }, 806 | { 807 | "name": "storeOp", 808 | "type": "GPUStoreOp", 809 | "default": null, 810 | "required": true 811 | } 812 | ] 813 | }, 814 | "GPURenderPassDepthStencilAttachment": { 815 | "name": "GPURenderPassDepthStencilAttachment", 816 | "parent": null, 817 | "fields": [ 818 | { 819 | "name": "view", 820 | "type": "GPUTextureView", 821 | "default": null, 822 | "required": true 823 | }, 824 | { 825 | "name": "depthReadOnly", 826 | "type": "boolean", 827 | "default": "false", 828 | "required": false 829 | }, 830 | { 831 | "name": "stencilClearValue", 832 | "type": "GPUStencilValue", 833 | "default": "0", 834 | "required": false 835 | }, 836 | { 837 | "name": "stencilReadOnly", 838 | "type": "boolean", 839 | "default": "false", 840 | "required": false 841 | } 842 | ] 843 | }, 844 | "GPURenderPassLayout": { 845 | "name": "GPURenderPassLayout", 846 | "parent": "GPUObjectDescriptorBase", 847 | "fields": [ 848 | { 849 | "name": "sampleCount", 850 | "type": "GPUSize32", 851 | "default": "1", 852 | "required": false 853 | } 854 | ] 855 | }, 856 | "GPURenderBundleDescriptor": { 857 | "name": "GPURenderBundleDescriptor", 858 | "parent": "GPUObjectDescriptorBase", 859 | "fields": [] 860 | }, 861 | "GPURenderBundleEncoderDescriptor": { 862 | "name": "GPURenderBundleEncoderDescriptor", 863 | "parent": "GPURenderPassLayout", 864 | "fields": [ 865 | { 866 | "name": "depthReadOnly", 867 | "type": "boolean", 868 | "default": "false", 869 | "required": false 870 | }, 871 | { 872 | "name": "stencilReadOnly", 873 | "type": "boolean", 874 | "default": "false", 875 | "required": false 876 | } 877 | ] 878 | }, 879 | "GPUQueueDescriptor": { 880 | "name": "GPUQueueDescriptor", 881 | "parent": "GPUObjectDescriptorBase", 882 | "fields": [] 883 | }, 884 | "GPUQuerySetDescriptor": { 885 | "name": "GPUQuerySetDescriptor", 886 | "parent": "GPUObjectDescriptorBase", 887 | "fields": [ 888 | { 889 | "name": "type", 890 | "type": "GPUQueryType", 891 | "default": null, 892 | "required": true 893 | }, 894 | { 895 | "name": "count", 896 | "type": "GPUSize32", 897 | "default": null, 898 | "required": true 899 | } 900 | ] 901 | }, 902 | "GPUCanvasConfiguration": { 903 | "name": "GPUCanvasConfiguration", 904 | "parent": null, 905 | "fields": [ 906 | { 907 | "name": "device", 908 | "type": "GPUDevice", 909 | "default": null, 910 | "required": true 911 | }, 912 | { 913 | "name": "format", 914 | "type": "GPUTextureFormat", 915 | "default": null, 916 | "required": true 917 | }, 918 | { 919 | "name": "colorSpace", 920 | "type": "PredefinedColorSpace", 921 | "default": "\"srgb\"", 922 | "required": false 923 | }, 924 | { 925 | "name": "alphaMode", 926 | "type": "GPUCanvasAlphaMode", 927 | "default": "\"opaque\"", 928 | "required": false 929 | } 930 | ] 931 | }, 932 | "GPUUncapturedErrorEventInit": { 933 | "name": "GPUUncapturedErrorEventInit", 934 | "parent": "EventInit", 935 | "fields": [ 936 | { 937 | "name": "error", 938 | "type": "GPUError", 939 | "default": null, 940 | "required": true 941 | } 942 | ] 943 | }, 944 | "GPUColorDict": { 945 | "name": "GPUColorDict", 946 | "parent": null, 947 | "fields": [ 948 | { 949 | "name": "r", 950 | "type": "double", 951 | "default": null, 952 | "required": true 953 | }, 954 | { 955 | "name": "g", 956 | "type": "double", 957 | "default": null, 958 | "required": true 959 | }, 960 | { 961 | "name": "b", 962 | "type": "double", 963 | "default": null, 964 | "required": true 965 | }, 966 | { 967 | "name": "a", 968 | "type": "double", 969 | "default": null, 970 | "required": true 971 | } 972 | ] 973 | }, 974 | "GPUOrigin2DDict": { 975 | "name": "GPUOrigin2DDict", 976 | "parent": null, 977 | "fields": [ 978 | { 979 | "name": "x", 980 | "type": "GPUIntegerCoordinate", 981 | "default": "0", 982 | "required": false 983 | }, 984 | { 985 | "name": "y", 986 | "type": "GPUIntegerCoordinate", 987 | "default": "0", 988 | "required": false 989 | } 990 | ] 991 | }, 992 | "GPUOrigin3DDict": { 993 | "name": "GPUOrigin3DDict", 994 | "parent": null, 995 | "fields": [ 996 | { 997 | "name": "x", 998 | "type": "GPUIntegerCoordinate", 999 | "default": "0", 1000 | "required": false 1001 | }, 1002 | { 1003 | "name": "y", 1004 | "type": "GPUIntegerCoordinate", 1005 | "default": "0", 1006 | "required": false 1007 | }, 1008 | { 1009 | "name": "z", 1010 | "type": "GPUIntegerCoordinate", 1011 | "default": "0", 1012 | "required": false 1013 | } 1014 | ] 1015 | }, 1016 | "GPUExtent3DDict": { 1017 | "name": "GPUExtent3DDict", 1018 | "parent": null, 1019 | "fields": [ 1020 | { 1021 | "name": "width", 1022 | "type": "GPUIntegerCoordinate", 1023 | "default": null, 1024 | "required": true 1025 | }, 1026 | { 1027 | "name": "height", 1028 | "type": "GPUIntegerCoordinate", 1029 | "default": "1", 1030 | "required": false 1031 | }, 1032 | { 1033 | "name": "depthOrArrayLayers", 1034 | "type": "GPUIntegerCoordinate", 1035 | "default": "1", 1036 | "required": false 1037 | } 1038 | ] 1039 | } 1040 | } 1041 | } -------------------------------------------------------------------------------- /generate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # This file is part of the "Learn WebGPU for C++" book. 4 | # https://github.com/eliemichel/LearnWebGPU 5 | # 6 | # MIT License 7 | # Copyright (c) 2022-2025 Elie Michel 8 | # 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy 10 | # of this software and associated documentation files (the "Software"), to deal 11 | # in the Software without restriction, including without limitation the rights 12 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | # copies of the Software, and to permit persons to whom the Software is 14 | # furnished to do so, subject to the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be included in all 17 | # copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | # SOFTWARE. 26 | 27 | # NB: The process is inpired by PpluX' wgpu.hpp generator 28 | # (see https://github.com/pplux/wgpu.hpp ) 29 | 30 | import re 31 | from dataclasses import dataclass, field 32 | from collections import defaultdict 33 | import os 34 | from os.path import dirname, isfile, join 35 | from typing import Dict, List 36 | import logging 37 | 38 | DEFAULT_HEADER_URL = "https://raw.githubusercontent.com/webgpu-native/webgpu-headers/main/webgpu.h" 39 | 40 | def makeArgParser(): 41 | import argparse 42 | 43 | parser = argparse.ArgumentParser(description=""" 44 | Generate the webgpu-cpp binding from official webgpu-native headers. 45 | You should not have to change any of the default arguments for a regular use. 46 | 47 | This generates a webgpu.hpp file that you can include in your project. 48 | Exactly one of your source files must #define WEBGPU_CPP_IMPLEMENTATION 49 | before including this header. 50 | """) 51 | 52 | parser.add_argument("-v", "--version", action='store_true', 53 | help="Display version information") 54 | 55 | parser.add_argument("-t", "--template", type=str, 56 | default="webgpu.template.hpp", 57 | help="Template used for generating the output binding file") 58 | 59 | parser.add_argument("-o", "--output", type=str, 60 | default="webgpu.hpp", 61 | help="Path where to output the generated webgpu.hpp") 62 | 63 | parser.add_argument("-u", "--header-url", action='append', 64 | default=[], 65 | help=f""" 66 | URL of the official webgpu.h from the webgpu-native project. If the URL 67 | does not start with http(s)://, it is considered as a local file. You can 68 | specify this option multiple times to agregate multiple headers (e.g., 69 | the standard webgpu.h plus backend-specific extensions wgpu.h). 70 | If no URL is specified, the official header from '{DEFAULT_HEADER_URL}' 71 | is used. 72 | """) 73 | 74 | parser.add_argument("-d", "--defaults", action='append', 75 | default=[], 76 | help="""This argument has been removed, use --use-init-macros instead.""") 77 | 78 | parser.add_argument("--ext-suffix", 79 | default="", 80 | help=""" 81 | Extension number needed for Dawn, which uses them to maintain backward 82 | compatibility of their API (might get removed once version 1 is out). 83 | Set to "2" when using dawn and leave to the empty default with wgpu-native. 84 | """) 85 | 86 | # Advanced options 87 | 88 | parser.add_argument("--no-scoped-enums", action='store_false', dest="use_scoped_enums", 89 | help="Do not replace WebGPU enums with C++ scoped enums") 90 | 91 | parser.add_argument("--no-fake-scoped-enums", action='store_false', dest="use_fake_scoped_enums", 92 | help="Use scoped aliases to original enum values so that no casting is needed") 93 | 94 | parser.add_argument("--use-non-member-procedures", action='store_true', 95 | help="Include WebGPU methods that are not members of any WebGPU object") 96 | 97 | parser.add_argument("--no-const", action='store_false', dest="use_const", 98 | help="By default, all methods of opaque handle types are const. This option makes them all non-const.") 99 | 100 | parser.add_argument("--use-init-macros", action='store_true', 101 | help="Use initialization macros provided by webgpu.h instead of writing custom setDefaults methods.") 102 | 103 | parser.add_argument("--use-inline", action='store_true', dest="use_inline", 104 | help="Make all methods inlined (seems to have an effect with clang, but MSVC fails at linking in that case).") 105 | 106 | return parser 107 | 108 | logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s") 109 | 110 | def main(args): 111 | applyDefaultArgs(args) 112 | 113 | if args.version: 114 | print("WebGPU-C++ generator v2.0.0") 115 | return 116 | 117 | template, meta = loadTemplate(args.template) 118 | api = WebGpuApi() 119 | for url in args.header_url: 120 | header = downloadHeader(url) 121 | parseHeader(api, header) 122 | 123 | binding = produceBinding(args, api, meta) 124 | 125 | generateOutput(args.output, template, binding) 126 | 127 | def applyDefaultArgs(args): 128 | if not args.use_init_macros or args.defaults != []: 129 | raise Exception( 130 | "The option --use-init-macros is now mandatory (because INIT macros are " + 131 | "now part of the standard API) and as a consequence the option --defaults " + 132 | "must no longer be used to provide default values. Use older versions of " + 133 | "this generator to run it on headers that do not provide such macros. Using " + 134 | "init macros will become the default in future version of this generator." 135 | ) 136 | if not args.header_url: 137 | args.header_url = [DEFAULT_HEADER_URL] 138 | if not args.defaults: 139 | args.defaults = ["defaults.txt", "extra-defaults.txt"] 140 | 141 | if hasattr(args, "virtual_fs"): 142 | VfsFile.virtual_fs = args.virtual_fs 143 | 144 | # ----------------------------------------------------------------------------- 145 | # Virtual File System, to enable using this script as a lib (in an environement 146 | # where there is no file system). 147 | 148 | class VfsFile(): 149 | virtual_fs = None 150 | def __init__(self, filename): 151 | self.filename = filename 152 | def __enter__(self): 153 | stream = VfsFile.virtual_fs[self.filename] 154 | stream.seek(0) 155 | return stream 156 | def __exit__(self, exc_type, exc_val, exc_tb): 157 | pass 158 | 159 | def openVfs(filename, mode='r', **kwargs): 160 | """ 161 | This is a wrapper around the standard 'open' that enables paths starting 162 | with "vfs://" to refer to files in a virtual file system. 163 | """ 164 | if filename.startswith("vfs://"): 165 | return VfsFile(filename[6:]) 166 | else: 167 | return open(filename, mode, **kwargs) 168 | 169 | def isfileVfs(filename): 170 | if filename.startswith("vfs://"): 171 | return filename[6:] in VfsFile.virtual_fs 172 | else: 173 | return isfile(filename) 174 | 175 | # ----------------------------------------------------------------------------- 176 | # Parser, for analyzing webgpu.h 177 | # The output of parsing is a WebGpuApi object 178 | 179 | @dataclass 180 | class PropertyApi: 181 | name: str 182 | type: str 183 | counter: str|None = None # list properties have an associated counter property 184 | 185 | @dataclass 186 | class HandleApi: 187 | """WebGPU objects manipulated through blind handles""" 188 | name: str 189 | properties: list[PropertyApi] = field(default_factory=list) 190 | 191 | @dataclass 192 | class ClassApi: 193 | """WebGPU structs whose fields are directly manipulated""" 194 | name: str 195 | parent: str|None = None 196 | properties: list[PropertyApi] = field(default_factory=list) 197 | is_descriptor: bool = False 198 | default_overrides: list[(str,str)] = field(default_factory=list) 199 | 200 | @dataclass 201 | class ProcedureArgumentApi: 202 | name: str 203 | type: str 204 | nullable: bool = False 205 | 206 | @dataclass 207 | class ProcedureApi: 208 | name: str 209 | return_type: str 210 | parent: str|None = None 211 | arguments: list[ProcedureArgumentApi] = field(default_factory=list) 212 | 213 | @dataclass 214 | class EnumerationEntryApi: 215 | key: str 216 | value: str 217 | 218 | @dataclass 219 | class EnumerationApi: 220 | name: str 221 | entries: list[EnumerationEntryApi] = field(default_factory=list) 222 | 223 | @dataclass 224 | class CallbackApi: 225 | name: str 226 | arguments: list[ProcedureArgumentApi] = field(default_factory=list) 227 | raw_arguments: str = "" 228 | 229 | @dataclass 230 | class TypeAliasApi: 231 | aliased_type: str 232 | wgpu_type: str 233 | 234 | @dataclass 235 | class WebGpuApi: 236 | handles: list[HandleApi] = field(default_factory=list) 237 | classes: list[ClassApi] = field(default_factory=list) 238 | procedures: list[ProcedureApi] = field(default_factory=list) 239 | enumerations: list[EnumerationApi] = field(default_factory=list) 240 | callbacks: list[CallbackApi] = field(default_factory=list) 241 | type_aliases: list[TypeAliasApi] = field(default_factory=list) 242 | stypes: dict[str,str] = field(default_factory=dict) # Name => SType::Name 243 | init_macros: list[str] = field(default_factory=list) 244 | 245 | def parseHeader(api, header): 246 | """ 247 | Add fields to api while reading a header file 248 | """ 249 | it = iter([ 250 | line 251 | .replace("WGPU_OBJECT_ATTRIBUTE", "") 252 | .replace("WGPU_ENUM_ATTRIBUTE", "") 253 | .replace("WGPU_STRUCTURE_ATTRIBUTE", "") 254 | .replace("WGPU_FUNCTION_ATTRIBUTE", "") 255 | #.replace("WGPU_NULLABLE", "") 256 | for line in header.split("\n") 257 | ]) 258 | 259 | struct_re = re.compile(r"struct *WGPU(\w+) *{") 260 | handle_re = re.compile(r"typedef struct .*WGPU([^_]\w+)\s*;") 261 | procedure_re = re.compile(r"(?:WGPU_EXPORT\s+)?([\w *]+) wgpu(\w+)\((.*)\)\s*;") 262 | enum_re = re.compile(r"typedef enum WGPU(\w+) {") 263 | flag_enum_re = re.compile(r"typedef WGPUFlags WGPU(\w+)Flags\s*;") 264 | new_flag_enum_re = re.compile(r"typedef WGPUFlags WGPU(\w+)\s*;") 265 | flag_value_re = re.compile(r"static const WGPU(\w+) WGPU(\w+)_(\w+) = (\w+)( /\*(.*)\*/)?;") 266 | typedef_re = re.compile(r"typedef (\w+) WGPU(\w+)\s*;") 267 | callback_re = re.compile(r"typedef void \(\*WGPU(\w+)Callback\)\((.*)\)\s*;") 268 | init_macro_re = re.compile(r"#define (WGPU_[A-Z0-9_]+_INIT)") 269 | 270 | while (x := next(it, None)) is not None: 271 | if (match := struct_re.search(x)): 272 | struct_name = match.group(1) 273 | api.classes.append(parseClass(struct_name, it)) 274 | continue 275 | 276 | if (match := handle_re.search(x)): 277 | struct_name = match.group(1) 278 | api.handles.append(HandleApi(name=struct_name)) 279 | continue 280 | 281 | if (match := procedure_re.search(x)): 282 | return_type = match.group(1) 283 | if return_type.startswith("WGPU_EXPORT"): 284 | return_type = return_type[len("WGPU_EXPORT"):] 285 | return_type = return_type.strip() 286 | api.procedures.append(ProcedureApi( 287 | name=match.group(2), 288 | return_type=return_type, 289 | arguments=parseProcArgs(match.group(3)), 290 | )) 291 | continue 292 | 293 | if (match := enum_re.search(x)): 294 | name = match.group(1) 295 | api.enumerations.append(parseEnum(name, it, api.stypes)) 296 | continue 297 | 298 | if (match := flag_enum_re.search(x)): 299 | api.type_aliases.append(TypeAliasApi( 300 | aliased_type="WGPUFlags", 301 | wgpu_type=match.group(1) + "Flags", 302 | )) 303 | continue 304 | 305 | if (match := new_flag_enum_re.search(x)): 306 | api.enumerations.append(EnumerationApi( 307 | name=match.group(1), 308 | )) 309 | continue 310 | 311 | if (match := flag_value_re.search(x)): 312 | enum_name = match.group(1) 313 | enum_name2 = match.group(2) 314 | entry = EnumerationEntryApi( 315 | key=match.group(3), 316 | value=match.group(4), 317 | ) 318 | assert(enum_name == enum_name2) 319 | found = False 320 | for enum in api.enumerations: 321 | if enum.name == enum_name: 322 | enum.entries.append(entry) 323 | found = True 324 | break 325 | if not found: 326 | api.enumerations.append(EnumerationApi( 327 | name=enum_name, 328 | entries=[ entry ] 329 | )) 330 | continue 331 | 332 | if (match := typedef_re.search(x)): 333 | api.type_aliases.append(TypeAliasApi( 334 | aliased_type=match.group(1), 335 | wgpu_type=match.group(2), 336 | )) 337 | continue 338 | 339 | if (match := callback_re.search(x)): 340 | api.callbacks.append(CallbackApi( 341 | name=match.group(1), 342 | arguments=parseProcArgs(match.group(2)), 343 | raw_arguments=match.group(2), 344 | )) 345 | continue 346 | 347 | if (match := init_macro_re.search(x)): 348 | api.init_macros.append(match.group(1)) 349 | continue 350 | 351 | # Post process: find parent of each method 352 | for proc in api.procedures: 353 | maxi = 0 354 | parent_names = ( 355 | [ handle.name for handle in api.handles ] + 356 | [ desc.name for desc in api.classes ] 357 | ) 358 | for parent in parent_names: 359 | if len(parent) > maxi and proc.name.startswith(parent) and len(parent) < len(proc.name): 360 | proc.parent = parent 361 | maxi = len(parent) 362 | if proc.parent is not None: 363 | proc.name = proc.name[maxi:] 364 | 365 | return api 366 | 367 | def parseEnum(name, it, stypes): 368 | entry_re = re.compile(r"^\s+WGPU([^_]+)_([\w_]+) = ([^,]+),?") 369 | end_re = re.compile(".*}") 370 | 371 | api = EnumerationApi(name=name) 372 | 373 | while (x := next(it, None)) is not None: 374 | if (match := entry_re.search(x)): 375 | prefix = match.group(1) 376 | key = match.group(2) 377 | #value = match.group(3) 378 | value = f"WGPU{prefix}_{key}" 379 | api.entries.append(EnumerationEntryApi(key, value)) 380 | 381 | if "WGPUSType_" in x: 382 | cast = "(WGPUSType)" if name != "SType" else "" 383 | stypes[key] = cast + name + "::" + key 384 | 385 | elif (match := end_re.search(x)): 386 | break 387 | 388 | return api 389 | 390 | def parseClass(name, it): 391 | api = ClassApi(name=name) 392 | end_of_struct_re = re.compile(r".*}") 393 | property_re = re.compile(r"^\s*(.+) (\w+);$") 394 | 395 | count_properties = [] 396 | x = next(it) 397 | while not end_of_struct_re.search(x): 398 | if (match := property_re.search(x)): 399 | prop = PropertyApi(name=match.group(2), type=match.group(1)) 400 | if prop.name == "nextInChain": 401 | api.is_descriptor = True 402 | if prop.name == "chain" and api.parent is not None: 403 | pass 404 | elif prop.name[-5:] == "Count": 405 | count_properties.append(prop) 406 | else: 407 | api.properties.append(prop) 408 | x = next(it) 409 | 410 | for counter in count_properties: 411 | # entri|ies -> entr|yCount 412 | # colorFormat|s -> colorFormat|sCount 413 | prefix = counter.name[:-6] 414 | found = False 415 | for r in api.properties: 416 | if r.name.startswith(prefix): 417 | r.counter = counter.name 418 | found = True 419 | break 420 | if not found: 421 | api.properties.append(counter) 422 | 423 | return api 424 | 425 | def parseProcArgs(line): 426 | args = [] 427 | for entry in line.split(","): 428 | entry = entry.strip() 429 | nullable = False 430 | if entry.endswith("/* nullable */"): 431 | nullable = True 432 | entry = entry[:-14].strip() 433 | if entry.startswith("WGPU_NULLABLE"): 434 | nullable = True 435 | entry = entry[13:].strip() 436 | tokens = entry.split() 437 | args.append(ProcedureArgumentApi( 438 | name=tokens[-1], 439 | type=" ".join(tokens[:-1]), 440 | nullable=nullable 441 | )) 442 | return args 443 | 444 | # ----------------------------------------------------------------------------- 445 | 446 | def produceBinding(args, api, meta): 447 | """Produce C++ binding""" 448 | binding = { 449 | "webgpu_includes": [], 450 | "descriptors": [], 451 | "structs": [], 452 | "class_impl": [], 453 | "class_oneliner": [], 454 | "handles_decl": [], 455 | "handles": [], 456 | "handles_impl": [], 457 | "handles_oneliner": [], 458 | "enums": [], 459 | "callbacks": [], 460 | "procedures": [], 461 | "type_aliases": [], 462 | "ext_suffix": args.ext_suffix, 463 | } 464 | 465 | for url in args.header_url: 466 | filename = os.path.split(url)[1] 467 | binding["webgpu_includes"].append(f"#include ") 468 | 469 | # Cached variables for format_arg 470 | handle_names = [ h.name for h in api.handles ] 471 | handle_cptr_names = [ f"{h.name} const *" for h in api.handles ] 472 | handle_ptr_names = [ f"{h.name} *" for h in api.handles ] 473 | class_cptr_names = [ f"{d.name} const *" for d in api.classes ] 474 | enum_names = [ e.name for e in api.enumerations ] 475 | enum_ptr_names = [ f"{e.name} *" for e in api.enumerations ] 476 | callbacks = { 477 | f"{cb.name}Callback": cb 478 | for cb in api.callbacks 479 | } 480 | def format_arg(arg): 481 | """ 482 | Given a function argument, return it (i) as an argument *received* from 483 | the C++ API and (ii) as an argument *passed* to the C API and (iii) as 484 | an argument passed to the C++ API. 485 | Also tells (iv) whether the next should be skipped (used only for the 486 | userdata pointer passed to callbacks). 487 | """ 488 | arg_type = arg.type 489 | arg_c = arg.name 490 | arg_cpp = arg.name 491 | skip_next = False 492 | 493 | if arg_type.startswith("struct "): 494 | arg_type = arg_type[len("struct "):] 495 | if arg_type.startswith("WGPU"): 496 | arg_type = arg_type[len("WGPU"):] 497 | 498 | if arg_type in class_cptr_names: 499 | base_type = arg_type[:-8] 500 | arg_type = f"const {base_type}&" 501 | arg_c = f"&{arg_c}" 502 | arg_cpp = f"*reinterpret_cast<{base_type} const *>({arg.name})" 503 | elif arg_type in callbacks: 504 | arg_type = f"{arg_type}&&" 505 | arg_c = "cCallback" 506 | skip_next = True 507 | elif arg_type in handle_cptr_names: 508 | arg_c = f"reinterpret_cast({arg_c})" 509 | elif arg_type in handle_ptr_names: 510 | arg_c = f"reinterpret_cast({arg_c})" 511 | 512 | if args.use_scoped_enums: 513 | if arg_type in enum_names: 514 | arg_c = f"static_cast({arg_c})" 515 | arg_cpp = f"static_cast<{arg_type}>({arg_cpp})" 516 | elif arg_type in enum_ptr_names: 517 | arg_c = f"reinterpret_cast({arg_c})" 518 | 519 | sig_cpp = f"{arg_type} {arg.name}" 520 | 521 | return sig_cpp, arg_c, arg_cpp, skip_next 522 | 523 | maybe_inline = "inline " if args.use_inline else "" 524 | class_names = [f"WGPU{c.name}" for c in api.classes] 525 | classes_and_handles = ( 526 | [ ('CLASS', cls_api) for cls_api in api.classes ] + 527 | [ ('HANDLE', handle_api) for handle_api in api.handles ] 528 | ) 529 | for entry_type, handle_or_class in classes_and_handles: 530 | entry_name = handle_or_class.name 531 | if entry_type == 'CLASS': 532 | macro = "DESCRIPTOR" if handle_or_class.is_descriptor else "STRUCT" 533 | namespace = "descriptors" if handle_or_class.is_descriptor else "structs" 534 | namespace_impl = "class_impl" 535 | namespace_oneliner = "class_oneliner" 536 | argument_self = "*this" 537 | use_const = False 538 | elif entry_type == 'HANDLE': 539 | binding["handles_decl"].append(f"class {entry_name};") 540 | macro = "HANDLE" 541 | namespace = "handles" 542 | namespace_impl = "handles_impl" 543 | namespace_oneliner = "handles_oneliner" 544 | argument_self = "m_raw" 545 | use_const = args.use_const 546 | 547 | if entry_name.startswith("INTERNAL__"): 548 | continue 549 | 550 | decls = [] 551 | implems = [] 552 | 553 | # Auto-generate setDefault 554 | if entry_type == 'CLASS': 555 | decls.append(f"\t{maybe_inline}void setDefault();\n") 556 | 557 | cls_api = handle_or_class 558 | prop_names = [f"{p.name}" for p in cls_api.properties] 559 | 560 | init_macro = f"WGPU_{toConstantCase(entry_name)}_INIT" 561 | if init_macro not in api.init_macros: 562 | logging.warning(f"Initialization macro '{init_macro}' was not found, falling back to empty initializer '{{}}'.") 563 | init_macro = "{}" 564 | prop_defaults = [ 565 | f"\t*this = WGPU{entry_name} {init_macro};\n", 566 | ] 567 | if "chain" in prop_names: 568 | if entry_name in api.stypes: 569 | prop_defaults.extend([ 570 | f"\tchain.sType = {api.stypes[entry_name]};\n", 571 | f"\tchain.next = nullptr;\n", 572 | ]) 573 | else: 574 | logging.warning(f"Type {entry_name} starts with a 'chain' field but has no apparent associated SType.") 575 | implems.append( 576 | f"{maybe_inline}void {entry_name}::setDefault() " + "{\n" 577 | + "".join(prop_defaults) 578 | + "}\n" 579 | ) 580 | 581 | for proc in api.procedures: 582 | if proc.parent != entry_name: 583 | continue 584 | if "wgpu" + entry_name + proc.name + "\n" in meta["blacklist"]: 585 | logging.debug(f"Skipping wgpu{entry_name}{proc.name} (blacklisted)...") 586 | continue 587 | method_name = proc.name[0].lower() + proc.name[1:] 588 | 589 | arguments, argument_names = [], [] 590 | skip_next = False 591 | for arg in proc.arguments[1:]: 592 | if skip_next: 593 | skip_next = False 594 | continue 595 | sig, name, _, skip_next = format_arg(arg) 596 | arguments.append(sig) 597 | argument_names.append(name) 598 | 599 | return_type = proc.return_type 600 | template_args = None 601 | 602 | # Wrap callback into std::function/Lambda 603 | if "userdata" == proc.arguments[-1].name: # OLD callback mechanism 604 | cb = callbacks[proc.arguments[-2].type[4:]] 605 | cb_name = proc.arguments[-2].name 606 | cb_arg_names = map(lambda a: format_arg(a)[2], cb.arguments[:-1]) 607 | body = ( 608 | f"\tauto handle = std::make_unique<{cb.name}Callback>({cb_name});\n" 609 | + f"\tstatic auto cCallback = []({cb.raw_arguments}) -> void {{\n" 610 | + f"\t\t{cb.name}Callback& callback = *reinterpret_cast<{cb.name}Callback*>(userdata);\n" 611 | + f"\t\tcallback({', '.join(cb_arg_names)});\n" 612 | + "\t};\n" 613 | + "\t{wrapped_call};\n" 614 | + "\treturn handle;\n" 615 | ) 616 | argument_names.append(f"reinterpret_cast(handle.get())") 617 | return_type = f"std::unique_ptr<{cb.name}Callback>" 618 | maybe_no_discard = "NO_DISCARD " 619 | elif proc.arguments[-1].type.endswith("CallbackInfo"): # NEW callback mechanism 620 | cb_type = proc.arguments[-1].type[4:-len("Info")] 621 | cb = callbacks[cb_type] 622 | cb_arg_names = [ format_arg(a)[2] for a in cb.arguments[:-2] ] 623 | cb_args = [ f"{a.type} {a.name}" for a in cb.arguments[:-2] ] 624 | 625 | # Remove callbackInfo arg and add CallbackMode and Lambda 626 | arguments.pop() 627 | arguments.extend([ "CallbackMode callbackMode", "const Lambda& callback" ]) 628 | 629 | template_args = [ "typename Lambda" ] 630 | 631 | body = "\n".join([ 632 | f"\tauto* lambda = new Lambda(callback);", 633 | f"\tauto cCallback = []({', '.join(cb_args)}, void* userdata1, void*) -> void {{", 634 | f"\t\tstd::unique_ptr lambda(reinterpret_cast(userdata1));", 635 | f"\t\t(*lambda)({', '.join(cb_arg_names)});", 636 | "\t};", 637 | f"\tWGPU{cb_type}Info callbackInfo = {{", 638 | "\t\t/* nextInChain = */ nullptr,", 639 | "\t\t/* mode = */ callbackMode,", 640 | "\t\t/* callback = */ cCallback,", 641 | "\t\t/* userdata1 = */ (void*)lambda,", 642 | "\t\t/* userdata2 = */ nullptr,", 643 | "\t};", 644 | "\treturn {wrapped_call};", 645 | "" 646 | ]) 647 | maybe_no_discard = "" 648 | else: 649 | if proc.arguments[-1].type.endswith("CallbackInfo"): 650 | print(f"- {entry_name}.{proc.name}: {proc.arguments[-1].name}") 651 | body = "\treturn {wrapped_call};\n" 652 | maybe_no_discard = "" 653 | 654 | argument_names_str = ', '.join([argument_self] + argument_names) 655 | 656 | begin_cast = "" 657 | end_cast = "" 658 | 659 | if return_type.startswith("WGPU"): 660 | return_type = return_type[4:] 661 | if args.use_scoped_enums: 662 | if return_type in enum_names: 663 | begin_cast = f"static_cast<{return_type}>(" 664 | end_cast = ")" 665 | 666 | wrapped_call = f"{begin_cast}wgpu{entry_name}{proc.name}({argument_names_str}){end_cast}" 667 | maybe_const = " const" if use_const else "" 668 | name_and_args = f"{method_name}({', '.join(arguments)}){maybe_const}" 669 | if template_args is None: 670 | decls.append(f"\t{maybe_inline}{maybe_no_discard}{return_type} {name_and_args};\n") 671 | implems.append( 672 | f"{maybe_inline}{return_type} {entry_name}::{name_and_args} {{\n" 673 | + body.replace("{wrapped_call}", wrapped_call) 674 | + "}\n" 675 | ) 676 | else: 677 | decls.append( 678 | "\ttemplate<" + ", ".join(template_args) + ">\n" 679 | + f"\t{maybe_inline}{return_type} {name_and_args} {{\n" 680 | + "\t" + body.replace("{wrapped_call}", wrapped_call).replace("\n", "\n\t") 681 | + "}\n" 682 | ) 683 | 684 | # Add utility overload for arguments of the form 'uint32_t xxCount, Xx const * xx' 685 | for i in range(len(proc.arguments) - 1): 686 | a, b = proc.arguments[i], proc.arguments[i + 1] 687 | if a.type in {"uint32_t","size_t"} and a.name.endswith("Count"): 688 | name = a.name[:-5] 689 | if b.type.endswith("const *") and b.name.startswith(name): 690 | vec_type = b.type[:-8] 691 | vec_name = name if name.endswith("s") else name + "s" 692 | 693 | alternatives = [ 694 | ( 695 | [f"const std::vector<{vec_type}>& {vec_name}"], 696 | [f"static_cast<{a.type}>({vec_name}.size())", f"{vec_name}.data()"] 697 | ), 698 | ( 699 | [f"const {vec_type}& {vec_name}"], 700 | [f"1", f"&{vec_name}"] 701 | ), 702 | ] 703 | 704 | for new_args, new_arg_names in alternatives: 705 | alt_arguments = arguments[:i-1] + new_args + arguments[i+2:] 706 | alt_argument_names = argument_names[:i-1] + new_arg_names + argument_names[i+2:] 707 | alt_argument_names_str = ', '.join([argument_self] + alt_argument_names) 708 | 709 | wrapped_call = f"wgpu{entry_name}{proc.name}({alt_argument_names_str})" 710 | 711 | maybe_const = " const" if use_const else "" 712 | 713 | name_and_args = f"{method_name}({', '.join(alt_arguments)}){maybe_const}" 714 | decls.append(f"\t{maybe_inline}{return_type} {name_and_args};\n") 715 | implems.append( 716 | f"{return_type} {entry_name}::{name_and_args} {{\n" 717 | + body.replace("{wrapped_call}", wrapped_call) 718 | + "}\n" 719 | ) 720 | 721 | # Add a variant when the last argument is a nullable descriptor 722 | if len(proc.arguments) > 1: 723 | arg = proc.arguments[-1] 724 | _, arg_c, __, ___ = format_arg(arg) 725 | if arg.nullable and arg_c.startswith("&"): 726 | alt_arguments = arguments[:-1] 727 | alt_argument_names = argument_names[:-1] 728 | alt_argument_names_str = ', '.join([argument_self] + alt_argument_names + ["nullptr"]) 729 | 730 | wrapped_call = f"{begin_cast}wgpu{entry_name}{proc.name}({alt_argument_names_str}){end_cast}" 731 | 732 | maybe_const = " const" if use_const else "" 733 | 734 | name_and_args = f"{method_name}({', '.join(alt_arguments)}){maybe_const}" 735 | decls.append(f"\t{maybe_inline}{return_type} {name_and_args};\n") 736 | implems.append( 737 | f"{maybe_inline}{return_type} {entry_name}::{name_and_args} {{\n" 738 | + body.replace("{wrapped_call}", wrapped_call) 739 | + "}\n" 740 | ) 741 | 742 | injected_decls = meta["injected-decls"].members.get(entry_name, []) 743 | macro = meta["injected-decls"].macro_override.get(entry_name, macro) 744 | 745 | binding[namespace].append( 746 | f"{macro}({entry_name})\n" 747 | + "".join(decls + injected_decls) 748 | + "END\n" 749 | ) 750 | 751 | binding[namespace_oneliner].append( 752 | f"{macro}({entry_name});" 753 | ) 754 | 755 | binding[namespace_impl].append( 756 | f"// Methods of {entry_name}\n" 757 | + "".join(implems) 758 | + "\n" 759 | ) 760 | 761 | # Only handles_impl is present in the tamplate 762 | binding["handles_impl"] = ( 763 | binding["class_impl"] + 764 | binding["handles_impl"] 765 | ) 766 | 767 | if args.use_non_member_procedures: 768 | for proc in api.procedures: 769 | if proc.parent is not None: 770 | continue 771 | arg_sig = map(lambda a: f"{a.type} {a.name}", proc.arguments) 772 | arg_names = map(lambda a: a.name, proc.arguments) 773 | proc_name = proc.name[0].lower() + proc.name[1:] 774 | binding["procedures"].append( 775 | f"{proc.return_type} {proc_name}({', '.join(arg_sig)}) {{\n" 776 | + f"\treturn wgpu{proc.name}({', '.join(arg_names)});\n" 777 | + "}" 778 | ) 779 | 780 | for enum in api.enumerations: 781 | if args.use_scoped_enums: 782 | if args.use_fake_scoped_enums: 783 | enum = ( 784 | f"ENUM({enum.name})\n" 785 | + "".join([ f"\tENUM_ENTRY({formatEnumValue(e.key)}, {e.value})\n" for e in enum.entries ]) 786 | + "END" 787 | ) 788 | else: 789 | enum = ( 790 | f"enum class {enum.name}: int {{\n" 791 | + "".join([ f"\t{formatEnumValue(e.key)} = {e.value},\n" for e in enum.entries ]) 792 | + "};" 793 | ) 794 | else: 795 | enum = f"typedef WGPU{enum.name} {enum.name};" 796 | binding["enums"].append(enum) 797 | 798 | # Use a dict to merge duplicates 799 | cb_dict = { 800 | cb.name: map(lambda a: format_arg(a)[0], cb.arguments[:-1]) 801 | for cb in api.callbacks 802 | } 803 | for cb_name, cb_args in cb_dict.items(): 804 | binding["callbacks"].append(f"using {cb_name}Callback = std::function;") 805 | 806 | for ta in api.type_aliases: 807 | binding["type_aliases"].append(f"using {ta.wgpu_type} = {ta.aliased_type};") 808 | 809 | for k, v in binding.items(): 810 | binding[k] = "\n".join(v) 811 | 812 | return binding 813 | 814 | # ----------------------------------------------------------------------------- 815 | # Utility functions 816 | 817 | def loadTemplate(path): 818 | resolved = resolveFilepath(path) 819 | logging.info(f"Loading template from {resolved}...") 820 | with openVfs(resolved, encoding="utf-8") as f: 821 | in_inject = False 822 | in_blacklist = False 823 | injected = "" 824 | blacklist = "" 825 | template = "" 826 | for line in f: 827 | if line.strip() == "{{begin-inject}}": 828 | in_inject = True 829 | continue 830 | if line.strip() == "{{end-inject}}": 831 | in_inject = False 832 | continue 833 | if line.strip() == "{{begin-blacklist}}": 834 | in_blacklist = True 835 | continue 836 | if line.strip() == "{{end-blacklist}}": 837 | in_blacklist = False 838 | continue 839 | if in_inject: 840 | injected += line 841 | elif in_blacklist: 842 | blacklist += line 843 | else: 844 | template += line 845 | template = ( 846 | template 847 | .replace('{', '{{') # escape brackets 848 | .replace('}', '}}') 849 | .replace('{{{{', '{') # transform double brackets into format string 850 | .replace('}}}}', '}') 851 | ) 852 | 853 | return template, { 854 | "injected-decls": parseTemplateInjection(injected), 855 | "blacklist": blacklist, 856 | } 857 | 858 | @dataclass 859 | class InjectedData(): 860 | # Extra members to insert, for each type 861 | members: Dict[str,List[str]] 862 | 863 | # Replacement for the HANDLE/STRUCT/etc macro used for this definition 864 | # (used to replace STRUCT by STRUCT_NO_OSTREAM) 865 | macro_override: Dict[str,str] 866 | 867 | def parseTemplateInjection(text): 868 | it = iter(text.split("\n")) 869 | 870 | injected_data = InjectedData( 871 | members = defaultdict(list), 872 | macro_override = {}, 873 | ) 874 | 875 | begin_re = re.compile(r"^(HANDLE|DESCRIPTOR|STRUCT|STRUCT_NO_OSTREAM)\((\w+)\)") 876 | end_re = re.compile(r"^END") 877 | current_category = None 878 | 879 | while (line := next(it, None)) is not None: 880 | if (match := begin_re.search(line)): 881 | current_category = match.group(2) 882 | injected_data.macro_override[current_category] = match.group(1) 883 | 884 | elif (match := end_re.search(line)): 885 | current_category = None 886 | 887 | elif current_category is not None: 888 | injected_data.members[current_category].append(line + "\n") 889 | 890 | return injected_data 891 | 892 | def downloadHeader(url): 893 | if url.startswith("https://") or url.startswith("http://"): 894 | logging.info(f"Downloading webgpu-native header from {url}...") 895 | import urllib.request 896 | response = urllib.request.urlopen(url) 897 | data = response.read() 898 | text = data.decode('utf-8') 899 | return text 900 | else: 901 | resolved = resolveFilepath(url) 902 | logging.info(f"Loading webgpu-native header from {resolved}...") 903 | with openVfs(resolved, encoding="utf-8") as f: 904 | return f.read() 905 | 906 | def generateOutput(path, template, fields): 907 | logging.info(f"Writing generated binding to {path}...") 908 | out = template.format(**fields) 909 | with openVfs(path, 'w', encoding="utf-8") as f: 910 | f.write(out) 911 | 912 | def resolveFilepath(path): 913 | for p in [ join(dirname(__file__), path), path ]: 914 | if isfileVfs(p): 915 | return p 916 | logging.error(f"Invalid template path: {path}") 917 | raise ValueError("Invalid template path") 918 | 919 | def formatEnumValue(value): 920 | if value[0] in '0123456789': 921 | return '_' + value 922 | else: 923 | return value 924 | 925 | def toConstantCase(caml_case): 926 | naive = ''.join(['_'+c if c.isupper() or c.isnumeric() else c for c in caml_case]).lstrip('_').upper() 927 | # We then regroup isolated characters together (because they correspond to acronyms): 928 | current_acronym = None 929 | tokens = [] 930 | for tok in naive.split("_"): 931 | if len(tok) == 1: 932 | if current_acronym is None: 933 | current_acronym = "" 934 | current_acronym += tok 935 | else: 936 | if current_acronym is not None: 937 | tokens.append(current_acronym) 938 | tokens.append(tok) 939 | current_acronym = None 940 | if current_acronym is not None: 941 | tokens.append(current_acronym) 942 | return "_".join(tokens) 943 | 944 | # ----------------------------------------------------------------------------- 945 | 946 | if __name__ == "__main__": 947 | args = makeArgParser().parse_args() 948 | main(args) 949 | 950 | --------------------------------------------------------------------------------