├── .github └── workflows │ └── release.yml ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── dawn.patch ├── examples ├── .gitignore ├── 00-compute │ ├── compute.wgsl │ ├── index.js │ └── package.json ├── 01-render │ ├── fragment.wgsl │ ├── index.js │ ├── package.json │ └── vertex.wgsl ├── 02-window │ ├── fragment.wgsl │ ├── index.js │ ├── package.json │ └── vertex.wgsl ├── 03-texture-loading │ ├── assets │ │ └── image.png │ ├── index.js │ ├── package.json │ └── shaders.wgsl └── README.md ├── package-lock.json ├── package.json ├── scripts ├── build.mjs ├── clean.mjs ├── configure.mjs ├── download-dawn.mjs ├── download-depot-tools.mjs ├── download-release.mjs ├── install-deps-mac.sh ├── install-deps-ubuntu.sh ├── install-deps-windows.cmd ├── install.mjs ├── make.mjs ├── release.mjs ├── upload-release.mjs └── util │ ├── common.js │ └── fetch.js └── src ├── index.d.ts └── index.js /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Build and upload 2 | 3 | on: workflow_dispatch 4 | 5 | permissions: 6 | contents: write 7 | 8 | jobs: 9 | build: 10 | name: ${{ matrix.platform.name }} 11 | 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | platform: 16 | - { name: 'Linux (x64)' ,os: ubuntu-22.04 } 17 | - { name: 'Linux (arm64)' ,os: ubuntu-22.04-arm } 18 | - { name: 'Windows (x64)' ,os: windows-2022 } 19 | - { name: 'Mac (x64)' ,os: macos-14 ,arch: x64 } 20 | - { name: 'Mac (arm64)' ,os: macos-14 ,arch: arm64 } 21 | 22 | runs-on: ${{ matrix.platform.os }} 23 | 24 | steps: 25 | - uses: actions/checkout@v4 26 | 27 | - if: ${{ startsWith(matrix.platform.os, 'ubuntu-') }} 28 | run: sudo apt-get update && sudo ./scripts/install-deps-ubuntu.sh 29 | 30 | - if: ${{ startsWith(matrix.platform.os, 'windows-') }} 31 | shell: bash 32 | run: | 33 | MSVC_DIR='C:/Program Files/Microsoft Visual Studio/2022/Enterprise/VC/Tools/MSVC' 34 | MSVC_VERSION=$(ls "${MSVC_DIR}" | sort -Vr | head -n1) 35 | MSVC="${MSVC_DIR}/${MSVC_VERSION}" 36 | WINSDK_DIR='C:/Program Files (x86)/Windows Kits/10' 37 | WINSDK_VERSION=$(ls "${WINSDK_DIR}/bin" | grep -E '^[0-9]' | sort -Vr | head -n1) 38 | echo "${MSVC}/bin/Hostx64/x64" >> $GITHUB_PATH 39 | echo "${WINSDK_DIR}/bin/${WINSDK_VERSION}/x64" >> $GITHUB_PATH 40 | LIB="${WINSDK_DIR}/Lib/${WINSDK_VERSION}/um/x64" 41 | LIB="${WINSDK_DIR}/Lib/${WINSDK_VERSION}/ucrt/x64;$LIB" 42 | LIB="${MSVC}/lib/x64;$LIB" 43 | echo "LIB=$LIB" >> $GITHUB_ENV 44 | INCLUDE="${MSVC}/include" 45 | INCLUDE="${WINSDK_DIR}/Include/${WINSDK_VERSION}/ucrt;$INCLUDE" 46 | INCLUDE="${WINSDK_DIR}/Include/${WINSDK_VERSION}/um;$INCLUDE" 47 | INCLUDE="${WINSDK_DIR}/Include/${WINSDK_VERSION}/shared;$INCLUDE" 48 | INCLUDE="${WINSDK_DIR}/Include/${WINSDK_VERSION}/winrt;$INCLUDE" 49 | echo "INCLUDE=$INCLUDE" >> $GITHUB_ENV 50 | scripts/install-deps-windows.cmd 51 | 52 | - if: ${{ startsWith(matrix.platform.os, 'macos-') }} 53 | run: ./scripts/install-deps-mac.sh 54 | 55 | - env: 56 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 57 | CROSS_COMPILE_ARCH: ${{ matrix.platform.arch }} 58 | run: npm run release 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | depot_tools 3 | dawn 4 | build 5 | dist 6 | publish 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /.github/ 2 | /examples/ 3 | /depot_tools/ 4 | /dawn/ 5 | /build/ 6 | /dist/ 7 | /publish/ 8 | /scripts/clean.mjs 9 | /scripts/install-deps-mac.sh 10 | /scripts/install-deps-ubuntu.sh 11 | /scripts/install-deps-windows.cmd 12 | /scripts/release.mjs 13 | /scripts/upload-release.mjs 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022 Konstantin M 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @kmamal/gpu 2 | 3 | [![Package](https://img.shields.io/npm/v/%2540kmamal%252Fgpu)](https://www.npmjs.com/package/@kmamal/gpu) 4 | [![Dependencies](https://img.shields.io/librariesio/release/npm/@kmamal/gpu)](https://libraries.io/npm/@kmamal%2Fgpu) 5 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 6 | 7 | WebGPU for Node.js via [Google Dawn](https://dawn.googlesource.com/dawn/+/refs/heads/main/src/dawn/node/). 8 | Allows you to use WebGPU without a browser. 9 | 10 | It should work on Linux, Mac, and Windows. 11 | Prebuilt binaries are available for x64 architectures, and arm-based Macs. 12 | 13 | 14 | ## Instructions 15 | 16 | Check the [examples](https://github.com/kmamal/gpu/tree/master/examples) for how to use this package. 17 | You can use both [compute](https://github.com/kmamal/gpu/tree/master/examples/00-compute) and [render](https://github.com/kmamal/gpu/tree/master/examples/01-render) pipelines. 18 | For render pipelines, you can either render the result to a buffer and save it as an image, or you can use [@kmamal/sdl](https://github.com/kmamal/node-sdl#readme) to render directly to a window as in [this example](https://github.com/kmamal/gpu/tree/master/examples/02-window). 19 | 20 | 21 | # API Reference 22 | 23 | ## Contents 24 | 25 | * [Globals](#globals) 26 | * [gpu.create(flags)](#gpucreateflags) 27 | * [gpu.destroy(instance)](#gpudestroyinstance) 28 | * [gpu.renderGPUDeviceToWindow(options)](#gpurendergpudevicetowindowoptions) 29 | * [class Renderer](#class-renderer) 30 | * [renderer.getCurrentTexture()](#renderergetcurrenttexture) 31 | * [renderer.getCurrentTextureView()](#renderergetcurrenttextureview) 32 | * [renderer.swap()](#rendererswap) 33 | * [renderer.resize()](#rendererresize) 34 | 35 | 36 | ### Globals 37 | 38 | ### gpu.create(flags) 39 | 40 | * `flags: []` An array of flags to pass to dawn_node. 41 | 42 | Creates a WebGPU instance object. 43 | The returned object is equivalent to the browser's [`GPU`](https://developer.mozilla.org/en-US/docs/Web/API/GPU) object. 44 | 45 | Any flags passed to the `create()` function must be in the form of `'key=value'`. 46 | It is usually a good idea to pass at least the `'verbose=1'` flag to help with debugging. 47 | 48 | ### gpu.destroy(instance) 49 | 50 | Instances created with [`gpu.create()`](#gpucreateflags) need to be cleaned up before the program exits. 51 | Usually you will call `gpu.destroy(instance)` right after calling `device.destroy()`. 52 | 53 | ### gpu.renderGPUDeviceToWindow(options) 54 | 55 | * `options: ` 56 | * `device: `[``](http://developer.mozilla.org/en-US/docs/Web/API/GPUDevice) The device to render from. 57 | * `window: `[``](https://github.com/kmamal/node-sdl?tab=readme-ov-file#class-window) The window to render to. 58 | * `presentMode: ` The swapchain mode. Default: `'fifo'` 59 | 60 | Crates a Renderer object that is used to connect a device to a window so that the device output renders directly to the window. 61 | 62 | Possible options for `presentMode` are `'fifo'`, `'fifoRelaxed'`, `'immediate'`, and `'mailbox'`. 63 | 64 | ### class Renderer 65 | 66 | This class is not directly exposed by the API so you can't use it with the new operator. 67 | Instead, objects returned by [`gpu.renderGPUDeviceToWindow()`](gpurendergpudevicetowindowoptions) are of this type. 68 | 69 | ### renderer.getCurrentTexture() 70 | 71 | Return an object of type [`GPUTexture`](https://developer.mozilla.org/en-US/docs/Web/API/GPUTexture). 72 | Things drawn to the texture will appear on the window when [`renderer.swap()`](#rendererswap) is called. 73 | 74 | ### renderer.getCurrentTextureView() 75 | 76 | Return an object of type [`GPUTextureView`](https://developer.mozilla.org/en-US/docs/Web/API/GPUTextureView). 77 | Things drawn to the texture view will appear on the window when [`renderer.swap()`](#rendererswap) is called. 78 | 79 | ### renderer.swap() 80 | 81 | Call this function after your render pass to display the results on the window. 82 | 83 | ### renderer.resize() 84 | 85 | Must be called after the window has been resized. 86 | -------------------------------------------------------------------------------- /dawn.patch: -------------------------------------------------------------------------------- 1 | diff --git a/DEPS b/DEPS 2 | index 018c84ffc..dea1ab010 100644 3 | --- a/DEPS 4 | +++ b/DEPS 5 | @@ -50,10 +50,10 @@ vars = { 6 | # RBE project to download rewrapper config files for. Only needed if 7 | # different from the project used in 'rbe_instance' 8 | 'rewrapper_cfg_project': '', 9 | - # reclient CIPD package 10 | - 'reclient_package': 'infra/rbe/client/', 11 | - # reclient CIPD package version 12 | - 'reclient_version': 're_client_version:0.143.0.518e369-gomaip', 13 | + # # reclient CIPD package 14 | + # 'reclient_package': 'infra/rbe/client/', 15 | + # # reclient CIPD package version 16 | + # 'reclient_version': 're_client_version:0.143.0.518e369-gomaip', 17 | # siso CIPD package version. 18 | 'siso_version': 'git_revision:0b6159c39573013a79a47f8544200df07a6a74fc', 19 | 20 | @@ -419,17 +419,17 @@ deps = { 21 | 'dep_type': 'cipd', 22 | }, 23 | 24 | - # RBE dependencies 25 | - 'buildtools/reclient': { 26 | - 'packages': [ 27 | - { 28 | - 'package': Var('reclient_package') + '${{platform}}', 29 | - 'version': Var('reclient_version'), 30 | - } 31 | - ], 32 | - 'dep_type': 'cipd', 33 | - 'condition': 'dawn_standalone', 34 | - }, 35 | + # # RBE dependencies 36 | + # 'buildtools/reclient': { 37 | + # 'packages': [ 38 | + # { 39 | + # 'package': Var('reclient_package') + '${{platform}}', 40 | + # 'version': Var('reclient_version'), 41 | + # } 42 | + # ], 43 | + # 'dep_type': 'cipd', 44 | + # 'condition': 'dawn_standalone', 45 | + # }, 46 | 47 | # Misc dependencies inherited from Tint 48 | 'third_party/protobuf': { 49 | @@ -676,22 +676,22 @@ hooks = [ 50 | ], 51 | }, 52 | 53 | - # Download remote exec cfg files 54 | - { 55 | - 'name': 'fetch_reclient_cfgs', 56 | - 'pattern': '.', 57 | - 'condition': 'download_remoteexec_cfg and dawn_standalone', 58 | - 'action': ['python3', 59 | - 'buildtools/reclient_cfgs/fetch_reclient_cfgs.py', 60 | - '--rbe_instance', 61 | - Var('rbe_instance'), 62 | - '--reproxy_cfg_template', 63 | - 'reproxy.cfg.template', 64 | - '--rewrapper_cfg_project', 65 | - Var('rewrapper_cfg_project'), 66 | - '--quiet', 67 | - ], 68 | - }, 69 | + # # Download remote exec cfg files 70 | + # { 71 | + # 'name': 'fetch_reclient_cfgs', 72 | + # 'pattern': '.', 73 | + # 'condition': 'download_remoteexec_cfg and dawn_standalone', 74 | + # 'action': ['python3', 75 | + # 'buildtools/reclient_cfgs/fetch_reclient_cfgs.py', 76 | + # '--rbe_instance', 77 | + # Var('rbe_instance'), 78 | + # '--reproxy_cfg_template', 79 | + # 'reproxy.cfg.template', 80 | + # '--rewrapper_cfg_project', 81 | + # Var('rewrapper_cfg_project'), 82 | + # '--quiet', 83 | + # ], 84 | + # }, 85 | # Configure Siso for developer builds. 86 | { 87 | 'name': 'configure_siso', 88 | diff --git a/include/webgpu/webgpu_sdl.h b/include/webgpu/webgpu_sdl.h 89 | new file mode 100644 90 | index 000000000..98ae22ee3 91 | --- /dev/null 92 | +++ b/include/webgpu/webgpu_sdl.h 93 | @@ -0,0 +1,60 @@ 94 | +// Copyright 2022 The Dawn & Tint Authors 95 | +// 96 | +// Redistribution and use in source and binary forms, with or without 97 | +// modification, are permitted provided that the following conditions are met: 98 | +// 99 | +// 1. Redistributions of source code must retain the above copyright notice, this 100 | +// list of conditions and the following disclaimer. 101 | +// 102 | +// 2. Redistributions in binary form must reproduce the above copyright notice, 103 | +// this list of conditions and the following disclaimer in the documentation 104 | +// and/or other materials provided with the distribution. 105 | +// 106 | +// 3. Neither the name of the copyright holder nor the names of its 107 | +// contributors may be used to endorse or promote products derived from 108 | +// this software without specific prior written permission. 109 | +// 110 | +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 111 | +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 112 | +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 113 | +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 114 | +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 115 | +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 116 | +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 117 | +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 118 | +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 119 | +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 120 | + 121 | +#ifndef INCLUDE_WEBGPU_WEBGPU_SDL_H_ 122 | +#define INCLUDE_WEBGPU_WEBGPU_SDL_H_ 123 | + 124 | +#include 125 | + 126 | +#include "dawn/dawn_proc.h" 127 | +#include "webgpu/webgpu_cpp.h" 128 | + 129 | +#if defined(WGPU_SDL_SHARED_LIBRARY) 130 | +#if defined(_WIN32) 131 | +#if defined(WGPU_SDL_IMPLEMENTATION) 132 | +#define WGPU_SDL_EXPORT __declspec(dllexport) 133 | +#else 134 | +#define WGPU_SDL_EXPORT __declspec(dllimport) 135 | +#endif 136 | +#else // defined(_WIN32) 137 | +#if defined(WGPU_SDL_IMPLEMENTATION) 138 | +#define WGPU_SDL_EXPORT __attribute__((visibility("default"))) 139 | +#else 140 | +#define WGPU_SDL_EXPORT 141 | +#endif 142 | +#endif // defined(_WIN32) 143 | +#else // defined(WGPU_SDL_SHARED_LIBRARY) 144 | +#define WGPU_SDL_EXPORT 145 | +#endif // defined(WGPU_SDL_SHARED_LIBRARY) 146 | + 147 | +namespace wgpu::sdl { 148 | + 149 | +WGPU_SDL_EXPORT WGPUSurface CreateSurfaceForWindow(const DawnProcTable *procs, const WGPUInstance& instance, void* nativeData); 150 | + 151 | +} // namespace wgpu::sdl 152 | + 153 | +#endif // INCLUDE_WEBGPU_WEBGPU_SDL_H_ 154 | diff --git a/src/dawn/CMakeLists.txt b/src/dawn/CMakeLists.txt 155 | index 38ec75e00..1513430f3 100644 156 | --- a/src/dawn/CMakeLists.txt 157 | +++ b/src/dawn/CMakeLists.txt 158 | @@ -107,6 +107,8 @@ if (DAWN_BUILD_NODE_BINDINGS) 159 | add_subdirectory(node) 160 | endif() 161 | 162 | +add_subdirectory(sdl) 163 | + 164 | ############################################################################### 165 | # libdawn_proc 166 | # Only built when not building for Emscripten 167 | diff --git a/src/dawn/node/CMakeLists.txt b/src/dawn/node/CMakeLists.txt 168 | index d8860f618..cd4716fda 100644 169 | --- a/src/dawn/node/CMakeLists.txt 170 | +++ b/src/dawn/node/CMakeLists.txt 171 | @@ -103,7 +103,7 @@ set_target_properties(dawn_node PROPERTIES 172 | CXX_STANDARD 17 173 | ) 174 | target_link_libraries(dawn_node dawn_node_binding dawn_node_interop dawn_native dawncpp dawn_proc 175 | - libtint) 176 | + libtint dawn_sdl) 177 | target_include_directories(dawn_node PRIVATE 178 | "${PROJECT_SOURCE_DIR}" 179 | "${NODE_ADDON_API_DIR}" 180 | diff --git a/src/dawn/node/Module.cpp b/src/dawn/node/Module.cpp 181 | index 0ac36d0e3..220e420eb 100644 182 | --- a/src/dawn/node/Module.cpp 183 | +++ b/src/dawn/node/Module.cpp 184 | @@ -30,11 +30,17 @@ 185 | #include 186 | #include 187 | #include 188 | +#include 189 | 190 | #include "dawn/dawn_proc.h" 191 | #include "src/dawn/node/binding/Flags.h" 192 | #include "src/dawn/node/binding/GPU.h" 193 | -#include "tint/tint.h" 194 | +#include "src/dawn/node/binding/GPUDevice.h" 195 | +#include "src/dawn/node/binding/GPUTexture.h" 196 | +#include "src/dawn/node/binding/GPUTextureView.h" 197 | +#include "src/dawn/node/binding/Converter.h" 198 | + 199 | +#include "webgpu/webgpu_sdl.h" 200 | 201 | #ifdef DAWN_EMIT_COVERAGE 202 | extern "C" { 203 | @@ -44,10 +50,21 @@ int __llvm_profile_write_file(void); 204 | } 205 | #endif // DAWN_EMIT_COVERAGE 206 | 207 | +const DawnProcTable *procs; 208 | + 209 | +Napi::FunctionReference constructor; 210 | + 211 | +std::map wgpuPresentModes = { 212 | + { "fifo", WGPUPresentMode_Fifo }, 213 | + { "fifoRelaxed", WGPUPresentMode_FifoRelaxed }, 214 | + { "immediate", WGPUPresentMode_Immediate }, 215 | + { "mailbox", WGPUPresentMode_Mailbox } 216 | +}; 217 | + 218 | namespace { 219 | 220 | Napi::Value CreateGPU(const Napi::CallbackInfo& info) { 221 | - const auto& env = info.Env(); 222 | + const Napi::Env& env = info.Env(); 223 | 224 | std::tuple> args; 225 | if (auto res = wgpu::interop::FromJS(info, args); !res) { 226 | @@ -69,7 +86,174 @@ Napi::Value CreateGPU(const Napi::CallbackInfo& info) { 227 | } 228 | 229 | // Construct a wgpu::interop::GPU interface, implemented by wgpu::bindings::GPU. 230 | - return wgpu::interop::GPU::Create(env, std::move(flags)); 231 | + auto gpu = wgpu::interop::GPU::Create(env, std::move(flags)); 232 | + Napi::Object jsGpu = gpu; 233 | + 234 | + return jsGpu; 235 | +} 236 | + 237 | +class Renderer : public Napi::ObjectWrap { 238 | + 239 | +public: 240 | + 241 | + static void Init(const Napi::Env &env) { 242 | + Napi::Function func = DefineClass(env, "Renderer", { 243 | + InstanceMethod("getPreferredFormat", &Renderer::getPreferredFormat), 244 | + InstanceMethod("getCurrentTexture", &Renderer::getCurrentTexture), 245 | + InstanceMethod("getCurrentTextureView", &Renderer::getCurrentTextureView), 246 | + InstanceMethod("swap", &Renderer::swap), 247 | + InstanceMethod("resize", &Renderer::resize), 248 | + }); 249 | + 250 | + constructor = Napi::Persistent(func); 251 | + constructor.SuppressDestruct(); 252 | + } 253 | + 254 | + Renderer(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) { 255 | + const Napi::Env& env = info.Env(); 256 | + 257 | + Napi::Object jsDevice = info[0].As(); 258 | + _device = Napi::Persistent(jsDevice); 259 | + 260 | + Napi::Object jsWindow = info[1].As(); 261 | + _window = Napi::Persistent(jsWindow); 262 | + 263 | + Napi::String jsPresentMode = info[2].As(); 264 | + _wgpuPresentMode = wgpuPresentModes[jsPresentMode.Utf8Value()]; 265 | + 266 | + _wgpuDevice = reinterpret_cast(wgpu::interop::GPUDevice::Unwrap(_device.Value()))->device_.Get(); 267 | + _wgpuAdapter = procs->deviceGetAdapter(_wgpuDevice); 268 | + _wgpuInstance = procs->adapterGetInstance(_wgpuAdapter); 269 | + 270 | + _createSurface(env); 271 | + }; 272 | + 273 | + static Napi::Object NewInstance(Napi::Env env, Napi::Value device, Napi::Value window, Napi::Value presentMode) { 274 | + return constructor.New({ device, window, presentMode }); 275 | + } 276 | + 277 | + Napi::Value getPreferredFormat(const Napi::CallbackInfo& info) { 278 | + const Napi::Env& env = info.Env(); 279 | + 280 | + wgpu::interop::GPUTextureFormat format; 281 | + wgpu::binding::Converter convert(env); 282 | + bool success = convert(format, static_cast(_wgpuPreferredFormat)); 283 | + return success ? wgpu::interop::ToJS(env, format) : env.Null(); 284 | + } 285 | + 286 | + Napi::Value getCurrentTexture(const Napi::CallbackInfo& info) { 287 | + const Napi::Env& env = info.Env(); 288 | + 289 | + WGPUSurfaceTexture wgpuSurfaceTexture = {}; 290 | + procs->surfaceGetCurrentTexture(_wgpuSurface, &wgpuSurfaceTexture); 291 | + 292 | + return wgpu::interop::GPUTexture::Create(env, 293 | + wgpu::Device::Acquire(_wgpuDevice), 294 | + wgpu::TextureDescriptor(), 295 | + wgpu::Texture::Acquire(wgpuSurfaceTexture.texture) 296 | + ); 297 | + } 298 | + 299 | + Napi::Value getCurrentTextureView(const Napi::CallbackInfo& info) { 300 | + const Napi::Env& env = info.Env(); 301 | + 302 | + WGPUSurfaceTexture wgpuSurfaceTexture = {}; 303 | + procs->surfaceGetCurrentTexture(_wgpuSurface, &wgpuSurfaceTexture); 304 | + 305 | + WGPUTextureView wgpuTextureView = procs->textureCreateView(wgpuSurfaceTexture.texture, nullptr); 306 | + 307 | + return wgpu::interop::GPUTextureView::Create(env, 308 | + wgpu::TextureViewDescriptor(), 309 | + wgpu::TextureView::Acquire(wgpuTextureView) 310 | + ); 311 | + } 312 | + 313 | + Napi::Value swap(const Napi::CallbackInfo& info) { 314 | + const Napi::Env& env = info.Env(); 315 | + procs->surfacePresent(_wgpuSurface); 316 | + return env.Undefined(); 317 | + } 318 | + 319 | + Napi::Value resize(const Napi::CallbackInfo& info) { 320 | + const Napi::Env& env = info.Env(); 321 | + _createSurface(env); 322 | + return env.Undefined(); 323 | + } 324 | + 325 | +private: 326 | + 327 | + Napi::ObjectReference _device; 328 | + Napi::ObjectReference _window; 329 | + WGPUPresentMode _wgpuPresentMode; 330 | + WGPUDevice _wgpuDevice; 331 | + WGPUAdapter _wgpuAdapter; 332 | + WGPUInstance _wgpuInstance; 333 | + WGPUSurface _wgpuSurface; 334 | + WGPUTextureFormat _wgpuPreferredFormat; 335 | + 336 | + void _createSurface(const Napi::Env &env) { 337 | + Napi::Buffer buffer = { env, _window.Get("_native").As().Get("gpu") }; 338 | + _wgpuSurface = wgpu::sdl::CreateSurfaceForWindow(procs, _wgpuInstance, buffer.Data()); 339 | + 340 | + WGPUSurfaceCapabilities wgpuCapabilities = {}; 341 | + procs->surfaceGetCapabilities(_wgpuSurface, _wgpuAdapter, &wgpuCapabilities); 342 | + 343 | + _wgpuPreferredFormat = wgpuCapabilities.formats[0]; 344 | + 345 | + WGPUSurfaceConfiguration wgpuConfig = {}; 346 | + wgpuConfig.device = _wgpuDevice; 347 | + wgpuConfig.format = _wgpuPreferredFormat; 348 | + wgpuConfig.usage = WGPUTextureUsage_RenderAttachment; 349 | + wgpuConfig.alphaMode = WGPUCompositeAlphaMode_Opaque; 350 | + wgpuConfig.width = _window.Get("_pixelWidth").ToNumber().Uint32Value(); 351 | + wgpuConfig.height = _window.Get("_pixelHeight").ToNumber().Uint32Value(); 352 | + wgpuConfig.presentMode = _wgpuPresentMode; 353 | + procs->surfaceConfigure(_wgpuSurface, &wgpuConfig); 354 | + } 355 | +}; 356 | + 357 | +Napi::Value RenderGPUDeviceToWindow(const Napi::CallbackInfo& info) { 358 | + const Napi::Env& env = info.Env(); 359 | + 360 | + if (info.Length() != 1) { 361 | + Napi::Error::New(env, "renderGPUDeviceToWindow expects exactly 1 argument").ThrowAsJavaScriptException(); 362 | + return env.Undefined(); 363 | + } 364 | + Napi::Value jsArg = info[0]; 365 | + 366 | + if (!jsArg.IsObject()) { 367 | + Napi::Error::New(env, "argument to renderGPUDeviceToWindow must be an object").ThrowAsJavaScriptException(); 368 | + return env.Undefined(); 369 | + } 370 | + Napi::Object jsObj = jsArg.ToObject(); 371 | + 372 | + Napi::Value jsDevice = jsObj.Get("device"); 373 | + if (!jsDevice.IsObject()) { 374 | + Napi::Error::New(env, "device argument to renderGPUDeviceToWindow must be an object").ThrowAsJavaScriptException(); 375 | + return env.Undefined(); 376 | + } 377 | + 378 | + Napi::Value jsWindow = jsObj.Get("window"); 379 | + if (!jsWindow.IsObject()) { 380 | + Napi::Error::New(env, "window argument to renderGPUDeviceToWindow must be an object").ThrowAsJavaScriptException(); 381 | + return env.Undefined(); 382 | + } 383 | + 384 | + Napi::Value jsPresentMode = jsObj.Get("presentMode"); 385 | + if (jsPresentMode.IsUndefined()) { 386 | + jsPresentMode = Napi::String::New(env, "fifo"); 387 | + } else if (!jsPresentMode.IsString()) { 388 | + Napi::Error::New(env, "presentMode argument to renderGPUDeviceToWindow must be a string").ThrowAsJavaScriptException(); 389 | + return env.Undefined(); 390 | + } else { 391 | + std::string presentModeStr = jsPresentMode.As().Utf8Value(); 392 | + if (wgpuPresentModes.find(presentModeStr) == wgpuPresentModes.end()) { 393 | + Napi::Error::New(env, "presentMode argument to renderGPUDeviceToWindow has invalid value").ThrowAsJavaScriptException(); 394 | + return env.Undefined(); 395 | + } 396 | + } 397 | + 398 | + return Renderer::NewInstance(env, jsDevice, jsWindow, jsPresentMode); 399 | } 400 | 401 | #ifdef DAWN_EMIT_COVERAGE 402 | @@ -105,13 +289,18 @@ struct Coverage { 403 | // object. 404 | NAPI_MODULE_EXPORT Napi::Object Initialize(Napi::Env env, Napi::Object exports) { 405 | // Set all the Dawn procedure function pointers. 406 | - dawnProcSetProcs(&dawn::native::GetProcs()); 407 | + procs = &dawn::native::GetProcs(); 408 | + dawnProcSetProcs(procs); 409 | 410 | // Register all the interop types 411 | exports.Set(Napi::String::New(env, "globals"), wgpu::interop::Initialize(env)); 412 | 413 | // Export function that creates and returns the wgpu::interop::GPU interface 414 | - exports.Set(Napi::String::New(env, "create"), Napi::Function::New(env)); 415 | + exports.Set(Napi::String::New(env, "_create"), Napi::Function::New(env)); 416 | + 417 | + Renderer::Init(env); 418 | + 419 | + exports.Set(Napi::String::New(env, "renderGPUDeviceToWindow"), Napi::Function::New(env)); 420 | 421 | #ifdef DAWN_EMIT_COVERAGE 422 | Coverage* coverage = new Coverage(); 423 | diff --git a/src/dawn/node/binding/AsyncRunner.cpp b/src/dawn/node/binding/AsyncRunner.cpp 424 | index 403883397..4e878803e 100644 425 | --- a/src/dawn/node/binding/AsyncRunner.cpp 426 | +++ b/src/dawn/node/binding/AsyncRunner.cpp 427 | @@ -64,7 +64,7 @@ void AsyncRunner::ScheduleProcessEvents(Napi::Env env) { 428 | 429 | auto weak_self = weak_this_; 430 | env.Global() 431 | - .Get("setImmediate") 432 | + .Get("setTimeout") 433 | .As() 434 | .Call({ 435 | // TODO(crbug.com/dawn/1127): Create once, reuse. 436 | @@ -76,9 +76,15 @@ void AsyncRunner::ScheduleProcessEvents(Napi::Env env) { 437 | } 438 | 439 | self->process_events_queued_ = false; 440 | + 441 | + if (self->tasks_waiting_ == 0) { 442 | + return ; 443 | + } 444 | + 445 | wgpuInstanceProcessEvents(self->instance_->Get()); 446 | self->ScheduleProcessEvents(env); 447 | }), 448 | + Napi::Number::New(env, 100) 449 | }); 450 | } 451 | 452 | diff --git a/src/dawn/node/binding/GPU.h b/src/dawn/node/binding/GPU.h 453 | index 64c2a35ff..2631e7492 100644 454 | --- a/src/dawn/node/binding/GPU.h 455 | +++ b/src/dawn/node/binding/GPU.h 456 | @@ -51,9 +51,10 @@ class GPU final : public interop::GPU { 457 | interop::GPUTextureFormat getPreferredCanvasFormat(Napi::Env) override; 458 | interop::Interface getWgslLanguageFeatures(Napi::Env) override; 459 | 460 | + std::unique_ptr instance_; 461 | + 462 | private: 463 | const Flags flags_; 464 | - std::unique_ptr instance_; 465 | std::shared_ptr async_; 466 | }; 467 | 468 | diff --git a/src/dawn/node/binding/GPUDevice.h b/src/dawn/node/binding/GPUDevice.h 469 | index a3a2531e8..596820f53 100644 470 | --- a/src/dawn/node/binding/GPUDevice.h 471 | +++ b/src/dawn/node/binding/GPUDevice.h 472 | @@ -135,9 +135,10 @@ class GPUDevice final : public interop::GPUDevice { 473 | std::optional> options) override; 474 | bool dispatchEvent(Napi::Env, interop::Interface event) override; 475 | 476 | + wgpu::Device device_; 477 | + 478 | private: 479 | Napi::Env env_; 480 | - wgpu::Device device_; 481 | std::shared_ptr async_; 482 | 483 | // This promise's JS object lives as long as the device because it is stored in .lost 484 | diff --git a/src/dawn/sdl/BUILD.gn b/src/dawn/sdl/BUILD.gn 485 | new file mode 100644 486 | index 000000000..e21b8881b 487 | --- /dev/null 488 | +++ b/src/dawn/sdl/BUILD.gn 489 | @@ -0,0 +1,50 @@ 490 | +# Copyright 2022 The Dawn & Tint Authors 491 | +# 492 | +# Redistribution and use in source and binary forms, with or without 493 | +# modification, are permitted provided that the following conditions are met: 494 | +# 495 | +# 1. Redistributions of source code must retain the above copyright notice, this 496 | +# list of conditions and the following disclaimer. 497 | +# 498 | +# 2. Redistributions in binary form must reproduce the above copyright notice, 499 | +# this list of conditions and the following disclaimer in the documentation 500 | +# and/or other materials provided with the distribution. 501 | +# 502 | +# 3. Neither the name of the copyright holder nor the names of its 503 | +# contributors may be used to endorse or promote products derived from 504 | +# this software without specific prior written permission. 505 | +# 506 | +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 507 | +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 508 | +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 509 | +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 510 | +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 511 | +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 512 | +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 513 | +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 514 | +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 515 | +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 516 | + 517 | +import("../../../scripts/dawn_overrides_with_defaults.gni") 518 | + 519 | +import("${dawn_root}/scripts/dawn_component.gni") 520 | +import("${dawn_root}/scripts/dawn_features.gni") 521 | + 522 | +############################################################################### 523 | +# SDL utils 524 | +############################################################################### 525 | + 526 | +static_library("sdl") { 527 | + defines = [ "WGPU_SDL_IMPLEMENTATION" ] 528 | + 529 | + sources = [ "utils.cpp" ] 530 | + deps = [ 531 | + "${dawn_root}/src/dawn:cpp", 532 | + "${dawn_root}/src/dawn:proc", 533 | + "${dawn_root}/src/dawn/common", 534 | + ] 535 | + 536 | + public_deps = [ 537 | + "${dawn_root}/include/dawn:cpp_headers", 538 | + ] 539 | +} 540 | diff --git a/src/dawn/sdl/CMakeLists.txt b/src/dawn/sdl/CMakeLists.txt 541 | new file mode 100644 542 | index 000000000..f8f8a79cf 543 | --- /dev/null 544 | +++ b/src/dawn/sdl/CMakeLists.txt 545 | @@ -0,0 +1,49 @@ 546 | +# Copyright 2022 The Dawn & Tint Authors 547 | +# 548 | +# Redistribution and use in source and binary forms, with or without 549 | +# modification, are permitted provided that the following conditions are met: 550 | +# 551 | +# 1. Redistributions of source code must retain the above copyright notice, this 552 | +# list of conditions and the following disclaimer. 553 | +# 554 | +# 2. Redistributions in binary form must reproduce the above copyright notice, 555 | +# this list of conditions and the following disclaimer in the documentation 556 | +# and/or other materials provided with the distribution. 557 | +# 558 | +# 3. Neither the name of the copyright holder nor the names of its 559 | +# contributors may be used to endorse or promote products derived from 560 | +# this software without specific prior written permission. 561 | +# 562 | +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 563 | +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 564 | +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 565 | +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 566 | +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 567 | +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 568 | +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 569 | +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 570 | +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 571 | +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 572 | + 573 | +add_library(dawn_sdl STATIC ${DAWN_PLACEHOLDER_FILE}) 574 | +common_compile_options(dawn_sdl) 575 | +target_sources(dawn_sdl 576 | + INTERFACE 577 | + "${DAWN_INCLUDE_DIR}/webgpu/webgpu_sdl.h" 578 | + PRIVATE 579 | + "utils.cpp" 580 | +) 581 | +target_link_libraries(dawn_sdl 582 | + PUBLIC 583 | + dawncpp_headers 584 | + dawn_common 585 | + PRIVATE 586 | + dawn_internal_config 587 | +) 588 | + 589 | +target_compile_definitions(dawn_sdl PRIVATE "WGPU_IMPLEMENTATION") 590 | +if(BUILD_SHARED_LIBS) 591 | + target_compile_definitions(dawn_sdl PRIVATE "WGPU_SHARED_LIBRARY") 592 | +endif() 593 | + 594 | +add_library(webgpu_sdl ALIAS dawn_sdl) 595 | diff --git a/src/dawn/sdl/utils.cpp b/src/dawn/sdl/utils.cpp 596 | new file mode 100644 597 | index 000000000..a774d8cae 598 | --- /dev/null 599 | +++ b/src/dawn/sdl/utils.cpp 600 | @@ -0,0 +1,107 @@ 601 | +// Copyright 2020 The Dawn & Tint Authors 602 | +// 603 | +// Redistribution and use in source and binary forms, with or without 604 | +// modification, are permitted provided that the following conditions are met: 605 | +// 606 | +// 1. Redistributions of source code must retain the above copyright notice, this 607 | +// list of conditions and the following disclaimer. 608 | +// 609 | +// 2. Redistributions in binary form must reproduce the above copyright notice, 610 | +// this list of conditions and the following disclaimer in the documentation 611 | +// and/or other materials provided with the distribution. 612 | +// 613 | +// 3. Neither the name of the copyright holder nor the names of its 614 | +// contributors may be used to endorse or promote products derived from 615 | +// this software without specific prior written permission. 616 | +// 617 | +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 618 | +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 619 | +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 620 | +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 621 | +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 622 | +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 623 | +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 624 | +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 625 | +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 626 | +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 627 | + 628 | +#include 629 | +#include 630 | +#include 631 | + 632 | +#include "dawn/common/Platform.h" 633 | +#include "webgpu/webgpu_sdl.h" 634 | + 635 | +namespace wgpu::sdl { 636 | + 637 | +#if DAWN_PLATFORM_IS(WINDOWS) 638 | + #include 639 | + struct NativeData { 640 | + HWND hwnd; 641 | + HINSTANCE hinstance; 642 | + }; 643 | +#elif defined(DAWN_ENABLE_BACKEND_METAL) 644 | + class CALayer; 645 | + struct NativeData { 646 | + CALayer *layer; 647 | + }; 648 | +#elif defined(DAWN_USE_X11) 649 | + #include 650 | + struct NativeData { 651 | + Display* display; 652 | + Window window; 653 | + }; 654 | +#endif 655 | + 656 | +std::unique_ptr GetSurfaceDescriptor(NativeData* native) { 657 | +#if DAWN_PLATFORM_IS(WINDOWS) 658 | + WGPUSurfaceSourceWindowsHWND* desc = new WGPUSurfaceSourceWindowsHWND(); 659 | + desc->chain.next = nullptr; 660 | + desc->chain.sType = WGPUSType_SurfaceSourceWindowsHWND; 661 | + desc->hwnd = native->hwnd; 662 | + desc->hinstance = native->hinstance; 663 | + return { 664 | + reinterpret_cast(desc), 665 | + [](WGPUChainedStruct* desc) { 666 | + delete reinterpret_cast(desc); 667 | + } 668 | + }; 669 | +#elif defined(DAWN_ENABLE_BACKEND_METAL) 670 | + WGPUSurfaceSourceMetalLayer* desc = new WGPUSurfaceSourceMetalLayer(); 671 | + desc->chain.next = nullptr; 672 | + desc->chain.sType = WGPUSType_SurfaceSourceMetalLayer; 673 | + desc->layer = native->layer; 674 | + return { 675 | + reinterpret_cast(desc), 676 | + [](WGPUChainedStruct* desc) { 677 | + delete reinterpret_cast(desc); 678 | + } 679 | + }; 680 | +#elif defined(DAWN_USE_X11) 681 | + WGPUSurfaceSourceXlibWindow* desc = new WGPUSurfaceSourceXlibWindow(); 682 | + desc->chain.next = nullptr; 683 | + desc->chain.sType = WGPUSType_SurfaceSourceXlibWindow; 684 | + desc->display = native->display; 685 | + desc->window = native->window; 686 | + return { 687 | + reinterpret_cast(desc), 688 | + [](WGPUChainedStruct* desc) { 689 | + delete reinterpret_cast(desc); 690 | + } 691 | + }; 692 | +#else 693 | + return { nullptr, [](WGPUChainedStruct*) {} }; 694 | +#endif 695 | +} 696 | + 697 | +WGPUSurface CreateSurfaceForWindow(const DawnProcTable *procs, const WGPUInstance& instance, void* window) { 698 | + std::unique_ptr chainedDescriptor = GetSurfaceDescriptor((NativeData*) window); 699 | + 700 | + WGPUSurfaceDescriptor surfaceDesc = {}; 701 | + surfaceDesc.nextInChain = chainedDescriptor.get(); 702 | + WGPUSurface surface = procs->instanceCreateSurface(instance, &surfaceDesc); 703 | + 704 | + return surface; 705 | +} 706 | + 707 | +} // namespace wgpu::sdl 708 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | **/package-lock.json 2 | -------------------------------------------------------------------------------- /examples/00-compute/compute.wgsl: -------------------------------------------------------------------------------- 1 | struct Matrix { 2 | size : vec2, 3 | numbers: array, 4 | } 5 | 6 | @group(0) @binding(0) var firstMatrix : Matrix; 7 | @group(0) @binding(1) var secondMatrix : Matrix; 8 | @group(0) @binding(2) var resultMatrix : Matrix; 9 | 10 | @compute @workgroup_size(8, 8) 11 | fn main(@builtin(global_invocation_id) global_id : vec3) { 12 | // Guard against out-of-bounds work group sizes 13 | if (global_id.x >= u32(firstMatrix.size.x) || global_id.y >= u32(secondMatrix.size.y)) { 14 | return; 15 | } 16 | 17 | resultMatrix.size = vec2(firstMatrix.size.x, secondMatrix.size.y); 18 | 19 | let resultCell = vec2(global_id.x, global_id.y); 20 | var result = 0.0; 21 | for (var i = 0u; i < u32(firstMatrix.size.y); i = i + 1u) { 22 | let a = i + resultCell.x * u32(firstMatrix.size.y); 23 | let b = resultCell.y + i * u32(secondMatrix.size.y); 24 | result = result + firstMatrix.numbers[a] * secondMatrix.numbers[b]; 25 | } 26 | 27 | let index = resultCell.y + resultCell.x * u32(secondMatrix.size.y); 28 | resultMatrix.numbers[index] = result; 29 | } 30 | -------------------------------------------------------------------------------- /examples/00-compute/index.js: -------------------------------------------------------------------------------- 1 | // 2 | // Based on [this article](https://web.dev/gpu-compute/) written by [François Beaufort](https://github.com/beaufortfrancois) 3 | // 4 | 5 | import gpu from '@kmamal/gpu' 6 | import fs from 'node:fs' 7 | import path from 'node:path' 8 | import { fileURLToPath } from 'node:url' 9 | const __dirname = fileURLToPath(new URL('.', import.meta.url)) 10 | 11 | const instance = gpu.create([ 'verbose=1' ]) 12 | const adapter = await instance.requestAdapter() 13 | const device = await adapter.requestDevice() 14 | 15 | const matrix1 = new Float32Array([ 16 | 2, // Rows 17 | 4, // Columns 18 | ...[ 1, 2, 3, 4 ], 19 | ...[ 5, 6, 7, 8 ], 20 | ]) 21 | 22 | const gpuBufferMatrix1 = device.createBuffer({ 23 | mappedAtCreation: true, 24 | size: matrix1.byteLength, 25 | usage: gpu.GPUBufferUsage.STORAGE, 26 | }) 27 | new Float32Array(gpuBufferMatrix1.getMappedRange()).set(matrix1) 28 | gpuBufferMatrix1.unmap() 29 | 30 | const matrix2 = new Float32Array([ 31 | 4, // Rows 32 | 2, // Columns 33 | ...[ 1, 2 ], 34 | ...[ 3, 4 ], 35 | ...[ 5, 6 ], 36 | ...[ 7, 8 ], 37 | ]) 38 | 39 | const gpuBufferMatrix2 = device.createBuffer({ 40 | mappedAtCreation: true, 41 | size: matrix2.byteLength, 42 | usage: gpu.GPUBufferUsage.STORAGE, 43 | }) 44 | new Float32Array(gpuBufferMatrix2.getMappedRange()).set(matrix2) 45 | gpuBufferMatrix2.unmap() 46 | 47 | const resultSize = Float32Array.BYTES_PER_ELEMENT * (2 + matrix1[0] * matrix2[1]) 48 | const resultBuffer = device.createBuffer({ 49 | size: resultSize, 50 | usage: gpu.GPUBufferUsage.STORAGE | gpu.GPUBufferUsage.COPY_SRC, 51 | }) 52 | 53 | const gpuReadBuffer = device.createBuffer({ 54 | size: resultSize, 55 | usage: gpu.GPUBufferUsage.COPY_DST | gpu.GPUBufferUsage.MAP_READ, 56 | }) 57 | 58 | const computeShaderFile = path.join(__dirname, 'compute.wgsl') 59 | const computeShaderCode = await fs.promises.readFile(computeShaderFile, 'utf8') 60 | 61 | const computePipeline = device.createComputePipeline({ 62 | layout: 'auto', 63 | compute: { 64 | module: device.createShaderModule({ code: computeShaderCode }), 65 | entryPoint: "main", 66 | }, 67 | }) 68 | 69 | const bindGroup = device.createBindGroup({ 70 | layout: computePipeline.getBindGroupLayout(0), 71 | entries: [ 72 | { 73 | binding: 0, 74 | resource: { buffer: gpuBufferMatrix1 }, 75 | }, 76 | { 77 | binding: 1, 78 | resource: { buffer: gpuBufferMatrix2 }, 79 | }, 80 | { 81 | binding: 2, 82 | resource: { buffer: resultBuffer }, 83 | }, 84 | ], 85 | }) 86 | 87 | const commandEncoder = device.createCommandEncoder() 88 | const passEncoder = commandEncoder.beginComputePass() 89 | passEncoder.setPipeline(computePipeline) 90 | passEncoder.setBindGroup(0, bindGroup) 91 | passEncoder.dispatchWorkgroups( 92 | Math.ceil(matrix1[0] / 8), 93 | Math.ceil(matrix2[1] / 8), 94 | ) 95 | passEncoder.end() 96 | commandEncoder.copyBufferToBuffer(resultBuffer, 0, gpuReadBuffer, 0, resultSize) 97 | device.queue.submit([ commandEncoder.finish() ]) 98 | 99 | await gpuReadBuffer.mapAsync(gpu.GPUMapMode.READ) 100 | console.log(new Float32Array(gpuReadBuffer.getMappedRange())) 101 | 102 | device.destroy() 103 | gpu.destroy(instance) 104 | -------------------------------------------------------------------------------- /examples/00-compute/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.0", 3 | "name": "gpu-example-compute", 4 | "main": "index.js", 5 | "type": "module", 6 | "dependencies": { 7 | "@kmamal/gpu": "*" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/01-render/fragment.wgsl: -------------------------------------------------------------------------------- 1 | @fragment 2 | fn main(@location(0) inColor: vec3) -> @location(0) vec4 { 3 | return vec4(inColor, 1.0); 4 | } 5 | -------------------------------------------------------------------------------- /examples/01-render/index.js: -------------------------------------------------------------------------------- 1 | // 2 | // Based on [this article](https://alain.xyz/blog/raw-webgpu) written by [Alain Galvan](https://github.com/alaingalvan) 3 | // 4 | 5 | import gpu from '@kmamal/gpu' 6 | import sdl from '@kmamal/sdl' 7 | import fs from 'node:fs' 8 | import path from 'node:path' 9 | import { fileURLToPath } from 'node:url' 10 | const __dirname = fileURLToPath(new URL('.', import.meta.url)) 11 | 12 | const window = sdl.video.createWindow({ accelerated: false }) 13 | const { pixelWidth: width, pixelHeight: height } = window 14 | 15 | const instance = gpu.create([ 'verbose=1' ]) 16 | const adapter = await instance.requestAdapter() 17 | const device = await adapter.requestDevice() 18 | 19 | const colorTexture = device.createTexture({ 20 | size: [ width, height, 1 ], 21 | dimension: '2d', 22 | format: 'rgba8unorm', 23 | usage: gpu.GPUTextureUsage.RENDER_ATTACHMENT | gpu.GPUTextureUsage.COPY_SRC, 24 | }) 25 | const colorTextureView = colorTexture.createView() 26 | 27 | const bufferSize = width * height * 4 28 | const readBuffer = device.createBuffer({ 29 | size: bufferSize, 30 | usage: gpu.GPUBufferUsage.COPY_DST | gpu.GPUBufferUsage.MAP_READ, 31 | }) 32 | 33 | const positions = new Float32Array([ 34 | ...[ 1.0, -1.0, 0.0 ], 35 | ...[ -1.0, -1.0, 0.0 ], 36 | ...[ 0.0, 1.0, 0.0 ], 37 | ]) 38 | 39 | const colors = new Float32Array([ 40 | ...[ 1.0, 0.0, 0.0 ], 41 | ...[ 0.0, 1.0, 0.0 ], 42 | ...[ 0.0, 0.0, 1.0 ], 43 | ]) 44 | 45 | const indices = new Uint16Array([ 0, 1, 2 ]) 46 | 47 | const createBuffer = (arr, usage) => { 48 | const buffer = device.createBuffer({ 49 | size: (arr.byteLength + 3) & ~3, 50 | usage, 51 | mappedAtCreation: true, 52 | }) 53 | 54 | const writeArray = arr instanceof Uint16Array 55 | ? new Uint16Array(buffer.getMappedRange()) 56 | : new Float32Array(buffer.getMappedRange()) 57 | writeArray.set(arr) 58 | buffer.unmap() 59 | return buffer 60 | } 61 | 62 | const positionBuffer = createBuffer(positions, gpu.GPUBufferUsage.VERTEX) 63 | const colorBuffer = createBuffer(colors, gpu.GPUBufferUsage.VERTEX) 64 | const indexBuffer = createBuffer(indices, gpu.GPUBufferUsage.INDEX) 65 | 66 | const vertexShaderFile = path.join(__dirname, 'vertex.wgsl') 67 | const vertexShaderCode = await fs.promises.readFile(vertexShaderFile, 'utf8') 68 | 69 | const fragmentShaderFile = path.join(__dirname, 'fragment.wgsl') 70 | const fragmentShaderCode = await fs.promises.readFile(fragmentShaderFile, 'utf8') 71 | 72 | const pipeline = device.createRenderPipeline({ 73 | layout: 'auto', 74 | vertex: { 75 | module: device.createShaderModule({ code: vertexShaderCode }), 76 | entryPoint: 'main', 77 | buffers: [ 78 | { 79 | attributes: [ 80 | { 81 | shaderLocation: 0, 82 | offset: 0, 83 | format: 'float32x3', 84 | }, 85 | ], 86 | arrayStride: 3 * Float32Array.BYTES_PER_ELEMENT, 87 | stepMode: 'vertex', 88 | }, 89 | { 90 | attributes: [ 91 | { 92 | shaderLocation: 1, 93 | offset: 0, 94 | format: 'float32x3', 95 | }, 96 | ], 97 | arrayStride: 3 * Float32Array.BYTES_PER_ELEMENT, 98 | stepMode: 'vertex', 99 | }, 100 | ], 101 | }, 102 | fragment: { 103 | module: device.createShaderModule({ code: fragmentShaderCode }), 104 | entryPoint: 'main', 105 | targets: [ { format: 'rgba8unorm' } ], 106 | }, 107 | primitive: { 108 | topology: 'triangle-list', 109 | }, 110 | }) 111 | 112 | const commandEncoder = device.createCommandEncoder() 113 | 114 | const renderPass = commandEncoder.beginRenderPass({ 115 | colorAttachments: [ 116 | { 117 | view: colorTextureView, 118 | clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }, 119 | loadOp: 'clear', 120 | storeOp: 'store', 121 | }, 122 | ], 123 | }) 124 | renderPass.setPipeline(pipeline) 125 | renderPass.setViewport(0, 0, width, height, 0, 1) 126 | renderPass.setScissorRect(0, 0, width, height) 127 | renderPass.setVertexBuffer(0, positionBuffer) 128 | renderPass.setVertexBuffer(1, colorBuffer) 129 | renderPass.setIndexBuffer(indexBuffer, 'uint16') 130 | renderPass.drawIndexed(3) 131 | renderPass.end() 132 | 133 | commandEncoder.copyTextureToBuffer( 134 | { texture: colorTexture }, 135 | { buffer: readBuffer, bytesPerRow: width * 4 }, 136 | { width, height }, 137 | ) 138 | 139 | device.queue.submit([ commandEncoder.finish() ]) 140 | 141 | await readBuffer.mapAsync(gpu.GPUMapMode.READ) 142 | const resultBuffer = new Uint8Array(readBuffer.getMappedRange()) 143 | window.render(width, height, width * 4, 'rgba32', Buffer.from(resultBuffer)) 144 | 145 | window.on('close', () => { 146 | device.destroy() 147 | gpu.destroy(instance) 148 | }) 149 | -------------------------------------------------------------------------------- /examples/01-render/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.0", 3 | "name": "gpu-example-render", 4 | "main": "index.js", 5 | "type": "module", 6 | "dependencies": { 7 | "@kmamal/gpu": "*", 8 | "@kmamal/sdl": "*" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/01-render/vertex.wgsl: -------------------------------------------------------------------------------- 1 | struct VSOut { 2 | @builtin(position) Position: vec4, 3 | @location(0) color: vec3, 4 | }; 5 | 6 | @vertex 7 | fn main(@location(0) inPos: vec3, @location(1) inColor: vec3) -> VSOut { 8 | var vsOut: VSOut; 9 | vsOut.Position = vec4(inPos, 1.0); 10 | vsOut.color = inColor; 11 | return vsOut; 12 | } 13 | -------------------------------------------------------------------------------- /examples/02-window/fragment.wgsl: -------------------------------------------------------------------------------- 1 | @fragment 2 | fn main(@location(0) inColor: vec3) -> @location(0) vec4 { 3 | return vec4(inColor, 1.0); 4 | } 5 | -------------------------------------------------------------------------------- /examples/02-window/index.js: -------------------------------------------------------------------------------- 1 | // 2 | // Based on [this article](https://alain.xyz/blog/raw-webgpu) written by [Alain Galvan](https://github.com/alaingalvan) 3 | // 4 | 5 | import sdl from '@kmamal/sdl' 6 | import gpu from '@kmamal/gpu' 7 | 8 | import fs from 'node:fs' 9 | import path from 'node:path' 10 | import { fileURLToPath } from 'node:url' 11 | const __dirname = fileURLToPath(new URL('.', import.meta.url)) 12 | 13 | const window = sdl.video.createWindow({ webgpu: true }) 14 | const { pixelWidth: width, pixelHeight: height } = window 15 | 16 | const instance = gpu.create([ 'verbose=1' ]) 17 | const adapter = await instance.requestAdapter() 18 | const device = await adapter.requestDevice() 19 | const renderer = gpu.renderGPUDeviceToWindow({ device, window }) 20 | 21 | const positions = new Float32Array([ 22 | ...[ 1.0, -1.0, 0.0 ], 23 | ...[ -1.0, -1.0, 0.0 ], 24 | ...[ 0.0, 1.0, 0.0 ], 25 | ]) 26 | 27 | const colors = new Float32Array([ 28 | ...[ 1.0, 0.0, 0.0 ], 29 | ...[ 0.0, 1.0, 0.0 ], 30 | ...[ 0.0, 0.0, 1.0 ], 31 | ]) 32 | 33 | const indices = new Uint16Array([ 0, 1, 2 ]) 34 | 35 | const createBuffer = (arr, usage) => { 36 | const buffer = device.createBuffer({ 37 | size: (arr.byteLength + 3) & ~3, 38 | usage, 39 | mappedAtCreation: true, 40 | }) 41 | 42 | const writeArray = arr instanceof Uint16Array 43 | ? new Uint16Array(buffer.getMappedRange()) 44 | : new Float32Array(buffer.getMappedRange()) 45 | writeArray.set(arr) 46 | buffer.unmap() 47 | return buffer 48 | } 49 | 50 | const positionBuffer = createBuffer(positions, gpu.GPUBufferUsage.VERTEX) 51 | const colorBuffer = createBuffer(colors, gpu.GPUBufferUsage.VERTEX) 52 | const indexBuffer = createBuffer(indices, gpu.GPUBufferUsage.INDEX) 53 | 54 | const vertexShaderFile = path.join(__dirname, 'vertex.wgsl') 55 | const vertexShaderCode = await fs.promises.readFile(vertexShaderFile, 'utf8') 56 | 57 | const fragmentShaderFile = path.join(__dirname, 'fragment.wgsl') 58 | const fragmentShaderCode = await fs.promises.readFile(fragmentShaderFile, 'utf8') 59 | 60 | const pipeline = device.createRenderPipeline({ 61 | layout: 'auto', 62 | vertex: { 63 | module: device.createShaderModule({ code: vertexShaderCode }), 64 | entryPoint: 'main', 65 | buffers: [ 66 | { 67 | attributes: [ 68 | { 69 | shaderLocation: 0, 70 | offset: 0, 71 | format: 'float32x3', 72 | }, 73 | ], 74 | arrayStride: 3 * Float32Array.BYTES_PER_ELEMENT, 75 | stepMode: 'vertex', 76 | }, 77 | { 78 | attributes: [ 79 | { 80 | shaderLocation: 1, 81 | offset: 0, 82 | format: 'float32x3', 83 | }, 84 | ], 85 | arrayStride: 3 * Float32Array.BYTES_PER_ELEMENT, 86 | stepMode: 'vertex', 87 | }, 88 | ], 89 | }, 90 | fragment: { 91 | module: device.createShaderModule({ code: fragmentShaderCode }), 92 | entryPoint: 'main', 93 | targets: [ { format: renderer.getPreferredFormat() } ], 94 | }, 95 | primitive: { 96 | topology: 'triangle-list', 97 | }, 98 | }) 99 | 100 | const render = () => { 101 | if (window.destroyed) { return } 102 | 103 | const commandEncoder = device.createCommandEncoder() 104 | 105 | const renderPass = commandEncoder.beginRenderPass({ 106 | colorAttachments: [ 107 | { 108 | view: renderer.getCurrentTextureView(), 109 | clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }, 110 | loadOp: 'clear', 111 | storeOp: 'store', 112 | }, 113 | ], 114 | }) 115 | renderPass.setPipeline(pipeline) 116 | renderPass.setViewport(0, 0, width, height, 0, 1) 117 | renderPass.setScissorRect(0, 0, width, height) 118 | renderPass.setVertexBuffer(0, positionBuffer) 119 | renderPass.setVertexBuffer(1, colorBuffer) 120 | renderPass.setIndexBuffer(indexBuffer, 'uint16') 121 | renderPass.drawIndexed(3) 122 | renderPass.end() 123 | 124 | device.queue.submit([ commandEncoder.finish() ]) 125 | 126 | renderer.swap() 127 | 128 | setTimeout(render, 0) 129 | } 130 | 131 | render() 132 | 133 | window.on('close', () => { 134 | device.destroy() 135 | gpu.destroy(instance) 136 | }) 137 | -------------------------------------------------------------------------------- /examples/02-window/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.0", 3 | "name": "gpu-example-window", 4 | "main": "index.js", 5 | "type": "module", 6 | "dependencies": { 7 | "@kmamal/gpu": "*", 8 | "@kmamal/sdl": "*" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/02-window/vertex.wgsl: -------------------------------------------------------------------------------- 1 | struct VSOut { 2 | @builtin(position) Position: vec4, 3 | @location(0) color: vec3, 4 | }; 5 | 6 | @vertex 7 | fn main(@location(0) inPos: vec3, @location(1) inColor: vec3 ) -> VSOut { 8 | var vsOut: VSOut; 9 | vsOut.Position = vec4(inPos, 1.0); 10 | vsOut.color = inColor; 11 | return vsOut; 12 | } 13 | -------------------------------------------------------------------------------- /examples/03-texture-loading/assets/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmamal/gpu/9a9db59ca221808579c38309e881cf97b4fcffdb/examples/03-texture-loading/assets/image.png -------------------------------------------------------------------------------- /examples/03-texture-loading/index.js: -------------------------------------------------------------------------------- 1 | import sdl from '@kmamal/sdl' 2 | import gpu from '@kmamal/gpu' 3 | import { PNG } from 'pngjs' 4 | 5 | import fs from 'node:fs' 6 | import path from 'node:path' 7 | import { fileURLToPath } from 'node:url' 8 | const __dirname = fileURLToPath(new URL('.', import.meta.url)) 9 | 10 | const window = sdl.video.createWindow({ webgpu: true }) 11 | const { pixelWidth: width, pixelHeight: height } = window 12 | 13 | const instance = gpu.create([ "verbose=1" ]) 14 | const adapter = await instance.requestAdapter() 15 | const device = await adapter.requestDevice() 16 | const renderer = gpu.renderGPUDeviceToWindow({ device, window }) 17 | 18 | 19 | const shaderFile = path.join(__dirname, 'shaders.wgsl') 20 | const shaderCode = await fs.promises.readFile(shaderFile, 'utf8') 21 | const shaderModule = device.createShaderModule({ code: shaderCode }) 22 | 23 | 24 | const createBuffer = (arr, usage) => { 25 | const buffer = device.createBuffer({ 26 | size: (arr.byteLength + 3) & ~3, 27 | usage, 28 | mappedAtCreation: true, 29 | }) 30 | 31 | const writeArray = arr instanceof Uint16Array 32 | ? new Uint16Array(buffer.getMappedRange()) 33 | : new Float32Array(buffer.getMappedRange()) 34 | writeArray.set(arr) 35 | buffer.unmap() 36 | return buffer 37 | } 38 | 39 | const vertices = new Float32Array([ 40 | /* eslint-disable @stylistic/array-element-newline */ 41 | -1.0, -1.0, 42 | +1.0, -1.0, 43 | +1.0, +1.0, 44 | -1.0, +1.0, 45 | /* eslint-enable @stylistic/array-element-newline */ 46 | ]) 47 | const vertexBuffer = createBuffer(vertices, gpu.GPUBufferUsage.VERTEX) 48 | 49 | const indices = new Uint16Array([ 50 | /* eslint-disable @stylistic/array-element-newline */ 51 | 0, 1, 2, 52 | 2, 3, 0, 53 | /* eslint-enable @stylistic/array-element-newline */ 54 | ]) 55 | const indexBuffer = createBuffer(indices, gpu.GPUBufferUsage.INDEX) 56 | 57 | 58 | const pngFile = path.join(__dirname, 'assets/image.png') 59 | const pngData = await fs.promises.readFile(pngFile) 60 | const image = PNG.sync.read(pngData) 61 | 62 | const texture = device.createTexture({ 63 | size: { width: image.width, height: image.height }, 64 | format: 'rgba8unorm', 65 | usage: gpu.GPUTextureUsage.TEXTURE_BINDING | gpu.GPUTextureUsage.COPY_DST, 66 | }) 67 | 68 | device.queue.writeTexture( 69 | { texture }, 70 | image.data, 71 | { bytesPerRow: 4 * image.width }, 72 | { width: image.width, height: image.height }, 73 | ) 74 | 75 | const sampler = device.createSampler({ 76 | addressModeU: 'repeat', 77 | addressModeV: 'repeat', 78 | magFilter: 'linear', 79 | minFilter: 'linear', 80 | }) 81 | 82 | 83 | const bindGroupLayout = device.createBindGroupLayout({ 84 | entries: [ 85 | { 86 | binding: 0, 87 | visibility: gpu.GPUShaderStage.FRAGMENT, 88 | texture: {}, 89 | }, 90 | { 91 | binding: 1, 92 | visibility: gpu.GPUShaderStage.FRAGMENT, 93 | sampler: {}, 94 | }, 95 | ], 96 | }) 97 | 98 | const pipeline = device.createRenderPipeline({ 99 | layout: device.createPipelineLayout({ 100 | bindGroupLayouts: [ bindGroupLayout ], 101 | }), 102 | vertex: { 103 | module: shaderModule, 104 | entryPoint: 'vert_main', 105 | buffers: [ 106 | { 107 | attributes: [ 108 | { 109 | shaderLocation: 0, 110 | offset: 0, 111 | format: 'float32x2', 112 | }, 113 | ], 114 | arrayStride: 2 * Float32Array.BYTES_PER_ELEMENT, 115 | stepMode: 'vertex', 116 | }, 117 | ], 118 | }, 119 | fragment: { 120 | module: shaderModule, 121 | entryPoint: 'frag_main', 122 | targets: [ { format: renderer.getPreferredFormat() } ], 123 | }, 124 | primitive: { 125 | topology: 'triangle-list', 126 | }, 127 | }) 128 | 129 | const bindGroup = device.createBindGroup({ 130 | layout: bindGroupLayout, 131 | entries: [ 132 | { binding: 0, resource: texture.createView() }, 133 | { binding: 1, resource: sampler }, 134 | ], 135 | }) 136 | 137 | 138 | const render = () => { 139 | if (window.destroyed) { return } 140 | 141 | const colorTextureView = renderer.getCurrentTextureView() 142 | 143 | const commandEncoder = device.createCommandEncoder() 144 | 145 | const renderPass = commandEncoder.beginRenderPass({ 146 | colorAttachments: [ 147 | { 148 | view: colorTextureView, 149 | clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }, 150 | loadOp: 'clear', 151 | storeOp: 'store', 152 | }, 153 | ], 154 | }) 155 | renderPass.setPipeline(pipeline) 156 | renderPass.setBindGroup(0, bindGroup) 157 | renderPass.setViewport(0, 0, width, height, 0, 1) 158 | renderPass.setScissorRect(0, 0, width, height) 159 | renderPass.setVertexBuffer(0, vertexBuffer) 160 | renderPass.setIndexBuffer(indexBuffer, 'uint16') 161 | renderPass.drawIndexed(indices.length) 162 | renderPass.end() 163 | 164 | device.queue.submit([ commandEncoder.finish() ]) 165 | 166 | renderer.swap() 167 | 168 | setTimeout(render, 0) 169 | } 170 | 171 | render() 172 | 173 | window.on('close', () => { 174 | device.destroy() 175 | gpu.destroy(instance) 176 | }) 177 | -------------------------------------------------------------------------------- /examples/03-texture-loading/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.0", 3 | "name": "gpu-example-texture-loading", 4 | "main": "index.js", 5 | "type": "module", 6 | "dependencies": { 7 | "@kmamal/gpu": "*", 8 | "@kmamal/sdl": "*", 9 | "pngjs": "*" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/03-texture-loading/shaders.wgsl: -------------------------------------------------------------------------------- 1 | 2 | @group(0) @binding(0) var texture: texture_2d; 3 | @group(0) @binding(1) var texture_sampler: sampler; 4 | 5 | struct VSIn { 6 | @location(0) pos: vec2f, 7 | }; 8 | 9 | struct VSOut { 10 | @builtin(position) pos: vec4f, 11 | @location(0) uv: vec2f, 12 | }; 13 | 14 | @vertex 15 | fn vert_main(in: VSIn) -> VSOut { 16 | var out: VSOut; 17 | out.pos = vec4f(in.pos.xy, 0.0, 1.0); 18 | out.uv = in.pos.xy * 0.5 + 0.5; 19 | out.uv.y *= -1; 20 | return out; 21 | } 22 | 23 | @fragment 24 | fn frag_main(in: VSOut) -> @location(0) vec4f 25 | { 26 | var color = textureSample(texture, texture_sampler, in.uv).rgb; 27 | return vec4f(color, 1.0); 28 | } 29 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | ## [0. Compute Pipeline](https://github.com/kmamal/gpu/tree/master/examples/00-compute) 4 | 5 | Multiplies two matrices together and prints the result. 6 | 7 | ## [1. Render Pipeline](https://github.com/kmamal/gpu/tree/master/examples/01-render) 8 | 9 | Renders a triagle and displays it in a window. 10 | 11 | ## [2. Render directly to window](https://github.com/kmamal/gpu/tree/master/examples/02-window) 12 | 13 | As above, but renders directly to a window surface. 14 | 15 | ## [3. Creating a texture from an image](https://github.com/kmamal/gpu/tree/master/examples/03-texture-loading) 16 | 17 | On the browser textures can be created very easily using `createImageBitmap()` and `device.queue.copyExternalImageToTexture()`. 18 | In the dawn Node.js bindings the `device.queue.copyExternalImageToTexture()` function is unimplemented since there are no standardized image objects. 19 | This forces us to use `device.queue.writeTexture()` to create a texture from data that we have previously decoded. 20 | This example presents one of many possible ways to get a buffer of pixels from a file on disk. 21 | Here we are using the `pngjs` package to decode a `.png` file and show it on the screen. 22 | 23 | 24 | // TODO: more 25 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@kmamal/gpu", 3 | "version": "0.2.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "@kmamal/gpu", 9 | "version": "0.2.0", 10 | "hasInstallScript": true, 11 | "license": "MIT", 12 | "dependencies": { 13 | "node-addon-api": "^8.3.1", 14 | "node-gyp": "^11.1.0", 15 | "tar": "^7.4.3" 16 | }, 17 | "devDependencies": { 18 | "@types/node": "^22.13.5" 19 | } 20 | }, 21 | "node_modules/@isaacs/cliui": { 22 | "version": "8.0.2", 23 | "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", 24 | "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", 25 | "license": "ISC", 26 | "dependencies": { 27 | "string-width": "^5.1.2", 28 | "string-width-cjs": "npm:string-width@^4.2.0", 29 | "strip-ansi": "^7.0.1", 30 | "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", 31 | "wrap-ansi": "^8.1.0", 32 | "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" 33 | }, 34 | "engines": { 35 | "node": ">=12" 36 | } 37 | }, 38 | "node_modules/@isaacs/fs-minipass": { 39 | "version": "4.0.1", 40 | "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", 41 | "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", 42 | "license": "ISC", 43 | "dependencies": { 44 | "minipass": "^7.0.4" 45 | }, 46 | "engines": { 47 | "node": ">=18.0.0" 48 | } 49 | }, 50 | "node_modules/@npmcli/agent": { 51 | "version": "3.0.0", 52 | "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-3.0.0.tgz", 53 | "integrity": "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q==", 54 | "license": "ISC", 55 | "dependencies": { 56 | "agent-base": "^7.1.0", 57 | "http-proxy-agent": "^7.0.0", 58 | "https-proxy-agent": "^7.0.1", 59 | "lru-cache": "^10.0.1", 60 | "socks-proxy-agent": "^8.0.3" 61 | }, 62 | "engines": { 63 | "node": "^18.17.0 || >=20.5.0" 64 | } 65 | }, 66 | "node_modules/@npmcli/fs": { 67 | "version": "4.0.0", 68 | "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz", 69 | "integrity": "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==", 70 | "license": "ISC", 71 | "dependencies": { 72 | "semver": "^7.3.5" 73 | }, 74 | "engines": { 75 | "node": "^18.17.0 || >=20.5.0" 76 | } 77 | }, 78 | "node_modules/@pkgjs/parseargs": { 79 | "version": "0.11.0", 80 | "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", 81 | "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", 82 | "license": "MIT", 83 | "optional": true, 84 | "engines": { 85 | "node": ">=14" 86 | } 87 | }, 88 | "node_modules/@types/node": { 89 | "version": "22.13.5", 90 | "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.5.tgz", 91 | "integrity": "sha512-+lTU0PxZXn0Dr1NBtC7Y8cR21AJr87dLLU953CWA6pMxxv/UDc7jYAY90upcrie1nRcD6XNG5HOYEDtgW5TxAg==", 92 | "dev": true, 93 | "license": "MIT", 94 | "dependencies": { 95 | "undici-types": "~6.20.0" 96 | } 97 | }, 98 | "node_modules/abbrev": { 99 | "version": "3.0.0", 100 | "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.0.tgz", 101 | "integrity": "sha512-+/kfrslGQ7TNV2ecmQwMJj/B65g5KVq1/L3SGVZ3tCYGqlzFuFCGBZJtMP99wH3NpEUyAjn0zPdPUg0D+DwrOA==", 102 | "license": "ISC", 103 | "engines": { 104 | "node": "^18.17.0 || >=20.5.0" 105 | } 106 | }, 107 | "node_modules/agent-base": { 108 | "version": "7.1.3", 109 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", 110 | "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", 111 | "license": "MIT", 112 | "engines": { 113 | "node": ">= 14" 114 | } 115 | }, 116 | "node_modules/ansi-regex": { 117 | "version": "6.1.0", 118 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", 119 | "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", 120 | "license": "MIT", 121 | "engines": { 122 | "node": ">=12" 123 | }, 124 | "funding": { 125 | "url": "https://github.com/chalk/ansi-regex?sponsor=1" 126 | } 127 | }, 128 | "node_modules/ansi-styles": { 129 | "version": "6.2.1", 130 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", 131 | "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", 132 | "license": "MIT", 133 | "engines": { 134 | "node": ">=12" 135 | }, 136 | "funding": { 137 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 138 | } 139 | }, 140 | "node_modules/balanced-match": { 141 | "version": "1.0.2", 142 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 143 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 144 | "license": "MIT" 145 | }, 146 | "node_modules/brace-expansion": { 147 | "version": "2.0.1", 148 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", 149 | "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", 150 | "license": "MIT", 151 | "dependencies": { 152 | "balanced-match": "^1.0.0" 153 | } 154 | }, 155 | "node_modules/cacache": { 156 | "version": "19.0.1", 157 | "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz", 158 | "integrity": "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==", 159 | "license": "ISC", 160 | "dependencies": { 161 | "@npmcli/fs": "^4.0.0", 162 | "fs-minipass": "^3.0.0", 163 | "glob": "^10.2.2", 164 | "lru-cache": "^10.0.1", 165 | "minipass": "^7.0.3", 166 | "minipass-collect": "^2.0.1", 167 | "minipass-flush": "^1.0.5", 168 | "minipass-pipeline": "^1.2.4", 169 | "p-map": "^7.0.2", 170 | "ssri": "^12.0.0", 171 | "tar": "^7.4.3", 172 | "unique-filename": "^4.0.0" 173 | }, 174 | "engines": { 175 | "node": "^18.17.0 || >=20.5.0" 176 | } 177 | }, 178 | "node_modules/chownr": { 179 | "version": "3.0.0", 180 | "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", 181 | "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", 182 | "license": "BlueOak-1.0.0", 183 | "engines": { 184 | "node": ">=18" 185 | } 186 | }, 187 | "node_modules/color-convert": { 188 | "version": "2.0.1", 189 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 190 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 191 | "license": "MIT", 192 | "dependencies": { 193 | "color-name": "~1.1.4" 194 | }, 195 | "engines": { 196 | "node": ">=7.0.0" 197 | } 198 | }, 199 | "node_modules/color-name": { 200 | "version": "1.1.4", 201 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 202 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 203 | "license": "MIT" 204 | }, 205 | "node_modules/cross-spawn": { 206 | "version": "7.0.6", 207 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", 208 | "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", 209 | "license": "MIT", 210 | "dependencies": { 211 | "path-key": "^3.1.0", 212 | "shebang-command": "^2.0.0", 213 | "which": "^2.0.1" 214 | }, 215 | "engines": { 216 | "node": ">= 8" 217 | } 218 | }, 219 | "node_modules/debug": { 220 | "version": "4.4.0", 221 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", 222 | "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", 223 | "license": "MIT", 224 | "dependencies": { 225 | "ms": "^2.1.3" 226 | }, 227 | "engines": { 228 | "node": ">=6.0" 229 | }, 230 | "peerDependenciesMeta": { 231 | "supports-color": { 232 | "optional": true 233 | } 234 | } 235 | }, 236 | "node_modules/eastasianwidth": { 237 | "version": "0.2.0", 238 | "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", 239 | "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", 240 | "license": "MIT" 241 | }, 242 | "node_modules/emoji-regex": { 243 | "version": "9.2.2", 244 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", 245 | "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", 246 | "license": "MIT" 247 | }, 248 | "node_modules/encoding": { 249 | "version": "0.1.13", 250 | "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", 251 | "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", 252 | "license": "MIT", 253 | "optional": true, 254 | "dependencies": { 255 | "iconv-lite": "^0.6.2" 256 | } 257 | }, 258 | "node_modules/env-paths": { 259 | "version": "2.2.1", 260 | "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", 261 | "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", 262 | "license": "MIT", 263 | "engines": { 264 | "node": ">=6" 265 | } 266 | }, 267 | "node_modules/err-code": { 268 | "version": "2.0.3", 269 | "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", 270 | "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", 271 | "license": "MIT" 272 | }, 273 | "node_modules/exponential-backoff": { 274 | "version": "3.1.2", 275 | "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.2.tgz", 276 | "integrity": "sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==", 277 | "license": "Apache-2.0" 278 | }, 279 | "node_modules/foreground-child": { 280 | "version": "3.3.0", 281 | "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", 282 | "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", 283 | "license": "ISC", 284 | "dependencies": { 285 | "cross-spawn": "^7.0.0", 286 | "signal-exit": "^4.0.1" 287 | }, 288 | "engines": { 289 | "node": ">=14" 290 | }, 291 | "funding": { 292 | "url": "https://github.com/sponsors/isaacs" 293 | } 294 | }, 295 | "node_modules/fs-minipass": { 296 | "version": "3.0.3", 297 | "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", 298 | "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", 299 | "license": "ISC", 300 | "dependencies": { 301 | "minipass": "^7.0.3" 302 | }, 303 | "engines": { 304 | "node": "^14.17.0 || ^16.13.0 || >=18.0.0" 305 | } 306 | }, 307 | "node_modules/glob": { 308 | "version": "10.4.5", 309 | "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", 310 | "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", 311 | "license": "ISC", 312 | "dependencies": { 313 | "foreground-child": "^3.1.0", 314 | "jackspeak": "^3.1.2", 315 | "minimatch": "^9.0.4", 316 | "minipass": "^7.1.2", 317 | "package-json-from-dist": "^1.0.0", 318 | "path-scurry": "^1.11.1" 319 | }, 320 | "bin": { 321 | "glob": "dist/esm/bin.mjs" 322 | }, 323 | "funding": { 324 | "url": "https://github.com/sponsors/isaacs" 325 | } 326 | }, 327 | "node_modules/graceful-fs": { 328 | "version": "4.2.11", 329 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", 330 | "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", 331 | "license": "ISC" 332 | }, 333 | "node_modules/http-cache-semantics": { 334 | "version": "4.1.1", 335 | "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", 336 | "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", 337 | "license": "BSD-2-Clause" 338 | }, 339 | "node_modules/http-proxy-agent": { 340 | "version": "7.0.2", 341 | "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", 342 | "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", 343 | "license": "MIT", 344 | "dependencies": { 345 | "agent-base": "^7.1.0", 346 | "debug": "^4.3.4" 347 | }, 348 | "engines": { 349 | "node": ">= 14" 350 | } 351 | }, 352 | "node_modules/https-proxy-agent": { 353 | "version": "7.0.6", 354 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", 355 | "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", 356 | "license": "MIT", 357 | "dependencies": { 358 | "agent-base": "^7.1.2", 359 | "debug": "4" 360 | }, 361 | "engines": { 362 | "node": ">= 14" 363 | } 364 | }, 365 | "node_modules/iconv-lite": { 366 | "version": "0.6.3", 367 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", 368 | "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", 369 | "license": "MIT", 370 | "optional": true, 371 | "dependencies": { 372 | "safer-buffer": ">= 2.1.2 < 3.0.0" 373 | }, 374 | "engines": { 375 | "node": ">=0.10.0" 376 | } 377 | }, 378 | "node_modules/imurmurhash": { 379 | "version": "0.1.4", 380 | "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", 381 | "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", 382 | "license": "MIT", 383 | "engines": { 384 | "node": ">=0.8.19" 385 | } 386 | }, 387 | "node_modules/ip-address": { 388 | "version": "9.0.5", 389 | "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", 390 | "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", 391 | "license": "MIT", 392 | "dependencies": { 393 | "jsbn": "1.1.0", 394 | "sprintf-js": "^1.1.3" 395 | }, 396 | "engines": { 397 | "node": ">= 12" 398 | } 399 | }, 400 | "node_modules/is-fullwidth-code-point": { 401 | "version": "3.0.0", 402 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 403 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 404 | "license": "MIT", 405 | "engines": { 406 | "node": ">=8" 407 | } 408 | }, 409 | "node_modules/isexe": { 410 | "version": "2.0.0", 411 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 412 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 413 | "license": "ISC" 414 | }, 415 | "node_modules/jackspeak": { 416 | "version": "3.4.3", 417 | "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", 418 | "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", 419 | "license": "BlueOak-1.0.0", 420 | "dependencies": { 421 | "@isaacs/cliui": "^8.0.2" 422 | }, 423 | "funding": { 424 | "url": "https://github.com/sponsors/isaacs" 425 | }, 426 | "optionalDependencies": { 427 | "@pkgjs/parseargs": "^0.11.0" 428 | } 429 | }, 430 | "node_modules/jsbn": { 431 | "version": "1.1.0", 432 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", 433 | "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", 434 | "license": "MIT" 435 | }, 436 | "node_modules/lru-cache": { 437 | "version": "10.4.3", 438 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", 439 | "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", 440 | "license": "ISC" 441 | }, 442 | "node_modules/make-fetch-happen": { 443 | "version": "14.0.3", 444 | "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz", 445 | "integrity": "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==", 446 | "license": "ISC", 447 | "dependencies": { 448 | "@npmcli/agent": "^3.0.0", 449 | "cacache": "^19.0.1", 450 | "http-cache-semantics": "^4.1.1", 451 | "minipass": "^7.0.2", 452 | "minipass-fetch": "^4.0.0", 453 | "minipass-flush": "^1.0.5", 454 | "minipass-pipeline": "^1.2.4", 455 | "negotiator": "^1.0.0", 456 | "proc-log": "^5.0.0", 457 | "promise-retry": "^2.0.1", 458 | "ssri": "^12.0.0" 459 | }, 460 | "engines": { 461 | "node": "^18.17.0 || >=20.5.0" 462 | } 463 | }, 464 | "node_modules/minimatch": { 465 | "version": "9.0.5", 466 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", 467 | "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", 468 | "license": "ISC", 469 | "dependencies": { 470 | "brace-expansion": "^2.0.1" 471 | }, 472 | "engines": { 473 | "node": ">=16 || 14 >=14.17" 474 | }, 475 | "funding": { 476 | "url": "https://github.com/sponsors/isaacs" 477 | } 478 | }, 479 | "node_modules/minipass": { 480 | "version": "7.1.2", 481 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", 482 | "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", 483 | "license": "ISC", 484 | "engines": { 485 | "node": ">=16 || 14 >=14.17" 486 | } 487 | }, 488 | "node_modules/minipass-collect": { 489 | "version": "2.0.1", 490 | "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", 491 | "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", 492 | "license": "ISC", 493 | "dependencies": { 494 | "minipass": "^7.0.3" 495 | }, 496 | "engines": { 497 | "node": ">=16 || 14 >=14.17" 498 | } 499 | }, 500 | "node_modules/minipass-fetch": { 501 | "version": "4.0.0", 502 | "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-4.0.0.tgz", 503 | "integrity": "sha512-2v6aXUXwLP1Epd/gc32HAMIWoczx+fZwEPRHm/VwtrJzRGwR1qGZXEYV3Zp8ZjjbwaZhMrM6uHV4KVkk+XCc2w==", 504 | "license": "MIT", 505 | "dependencies": { 506 | "minipass": "^7.0.3", 507 | "minipass-sized": "^1.0.3", 508 | "minizlib": "^3.0.1" 509 | }, 510 | "engines": { 511 | "node": "^18.17.0 || >=20.5.0" 512 | }, 513 | "optionalDependencies": { 514 | "encoding": "^0.1.13" 515 | } 516 | }, 517 | "node_modules/minipass-flush": { 518 | "version": "1.0.5", 519 | "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", 520 | "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", 521 | "license": "ISC", 522 | "dependencies": { 523 | "minipass": "^3.0.0" 524 | }, 525 | "engines": { 526 | "node": ">= 8" 527 | } 528 | }, 529 | "node_modules/minipass-flush/node_modules/minipass": { 530 | "version": "3.3.6", 531 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", 532 | "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", 533 | "license": "ISC", 534 | "dependencies": { 535 | "yallist": "^4.0.0" 536 | }, 537 | "engines": { 538 | "node": ">=8" 539 | } 540 | }, 541 | "node_modules/minipass-flush/node_modules/yallist": { 542 | "version": "4.0.0", 543 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 544 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", 545 | "license": "ISC" 546 | }, 547 | "node_modules/minipass-pipeline": { 548 | "version": "1.2.4", 549 | "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", 550 | "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", 551 | "license": "ISC", 552 | "dependencies": { 553 | "minipass": "^3.0.0" 554 | }, 555 | "engines": { 556 | "node": ">=8" 557 | } 558 | }, 559 | "node_modules/minipass-pipeline/node_modules/minipass": { 560 | "version": "3.3.6", 561 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", 562 | "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", 563 | "license": "ISC", 564 | "dependencies": { 565 | "yallist": "^4.0.0" 566 | }, 567 | "engines": { 568 | "node": ">=8" 569 | } 570 | }, 571 | "node_modules/minipass-pipeline/node_modules/yallist": { 572 | "version": "4.0.0", 573 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 574 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", 575 | "license": "ISC" 576 | }, 577 | "node_modules/minipass-sized": { 578 | "version": "1.0.3", 579 | "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", 580 | "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", 581 | "license": "ISC", 582 | "dependencies": { 583 | "minipass": "^3.0.0" 584 | }, 585 | "engines": { 586 | "node": ">=8" 587 | } 588 | }, 589 | "node_modules/minipass-sized/node_modules/minipass": { 590 | "version": "3.3.6", 591 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", 592 | "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", 593 | "license": "ISC", 594 | "dependencies": { 595 | "yallist": "^4.0.0" 596 | }, 597 | "engines": { 598 | "node": ">=8" 599 | } 600 | }, 601 | "node_modules/minipass-sized/node_modules/yallist": { 602 | "version": "4.0.0", 603 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 604 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", 605 | "license": "ISC" 606 | }, 607 | "node_modules/minizlib": { 608 | "version": "3.0.1", 609 | "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.1.tgz", 610 | "integrity": "sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==", 611 | "license": "MIT", 612 | "dependencies": { 613 | "minipass": "^7.0.4", 614 | "rimraf": "^5.0.5" 615 | }, 616 | "engines": { 617 | "node": ">= 18" 618 | } 619 | }, 620 | "node_modules/mkdirp": { 621 | "version": "3.0.1", 622 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", 623 | "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", 624 | "license": "MIT", 625 | "bin": { 626 | "mkdirp": "dist/cjs/src/bin.js" 627 | }, 628 | "engines": { 629 | "node": ">=10" 630 | }, 631 | "funding": { 632 | "url": "https://github.com/sponsors/isaacs" 633 | } 634 | }, 635 | "node_modules/ms": { 636 | "version": "2.1.3", 637 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 638 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 639 | "license": "MIT" 640 | }, 641 | "node_modules/negotiator": { 642 | "version": "1.0.0", 643 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", 644 | "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", 645 | "license": "MIT", 646 | "engines": { 647 | "node": ">= 0.6" 648 | } 649 | }, 650 | "node_modules/node-addon-api": { 651 | "version": "8.3.1", 652 | "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.3.1.tgz", 653 | "integrity": "sha512-lytcDEdxKjGJPTLEfW4mYMigRezMlyJY8W4wxJK8zE533Jlb8L8dRuObJFWg2P+AuOIxoCgKF+2Oq4d4Zd0OUA==", 654 | "license": "MIT", 655 | "engines": { 656 | "node": "^18 || ^20 || >= 21" 657 | } 658 | }, 659 | "node_modules/node-gyp": { 660 | "version": "11.1.0", 661 | "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-11.1.0.tgz", 662 | "integrity": "sha512-/+7TuHKnBpnMvUQnsYEb0JOozDZqarQbfNuSGLXIjhStMT0fbw7IdSqWgopOP5xhRZE+lsbIvAHcekddruPZgQ==", 663 | "license": "MIT", 664 | "dependencies": { 665 | "env-paths": "^2.2.0", 666 | "exponential-backoff": "^3.1.1", 667 | "glob": "^10.3.10", 668 | "graceful-fs": "^4.2.6", 669 | "make-fetch-happen": "^14.0.3", 670 | "nopt": "^8.0.0", 671 | "proc-log": "^5.0.0", 672 | "semver": "^7.3.5", 673 | "tar": "^7.4.3", 674 | "which": "^5.0.0" 675 | }, 676 | "bin": { 677 | "node-gyp": "bin/node-gyp.js" 678 | }, 679 | "engines": { 680 | "node": "^18.17.0 || >=20.5.0" 681 | } 682 | }, 683 | "node_modules/node-gyp/node_modules/isexe": { 684 | "version": "3.1.1", 685 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", 686 | "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", 687 | "license": "ISC", 688 | "engines": { 689 | "node": ">=16" 690 | } 691 | }, 692 | "node_modules/node-gyp/node_modules/which": { 693 | "version": "5.0.0", 694 | "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", 695 | "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", 696 | "license": "ISC", 697 | "dependencies": { 698 | "isexe": "^3.1.1" 699 | }, 700 | "bin": { 701 | "node-which": "bin/which.js" 702 | }, 703 | "engines": { 704 | "node": "^18.17.0 || >=20.5.0" 705 | } 706 | }, 707 | "node_modules/nopt": { 708 | "version": "8.1.0", 709 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", 710 | "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", 711 | "license": "ISC", 712 | "dependencies": { 713 | "abbrev": "^3.0.0" 714 | }, 715 | "bin": { 716 | "nopt": "bin/nopt.js" 717 | }, 718 | "engines": { 719 | "node": "^18.17.0 || >=20.5.0" 720 | } 721 | }, 722 | "node_modules/p-map": { 723 | "version": "7.0.3", 724 | "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz", 725 | "integrity": "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==", 726 | "license": "MIT", 727 | "engines": { 728 | "node": ">=18" 729 | }, 730 | "funding": { 731 | "url": "https://github.com/sponsors/sindresorhus" 732 | } 733 | }, 734 | "node_modules/package-json-from-dist": { 735 | "version": "1.0.1", 736 | "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", 737 | "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", 738 | "license": "BlueOak-1.0.0" 739 | }, 740 | "node_modules/path-key": { 741 | "version": "3.1.1", 742 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 743 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 744 | "license": "MIT", 745 | "engines": { 746 | "node": ">=8" 747 | } 748 | }, 749 | "node_modules/path-scurry": { 750 | "version": "1.11.1", 751 | "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", 752 | "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", 753 | "license": "BlueOak-1.0.0", 754 | "dependencies": { 755 | "lru-cache": "^10.2.0", 756 | "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" 757 | }, 758 | "engines": { 759 | "node": ">=16 || 14 >=14.18" 760 | }, 761 | "funding": { 762 | "url": "https://github.com/sponsors/isaacs" 763 | } 764 | }, 765 | "node_modules/proc-log": { 766 | "version": "5.0.0", 767 | "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", 768 | "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", 769 | "license": "ISC", 770 | "engines": { 771 | "node": "^18.17.0 || >=20.5.0" 772 | } 773 | }, 774 | "node_modules/promise-retry": { 775 | "version": "2.0.1", 776 | "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", 777 | "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", 778 | "license": "MIT", 779 | "dependencies": { 780 | "err-code": "^2.0.2", 781 | "retry": "^0.12.0" 782 | }, 783 | "engines": { 784 | "node": ">=10" 785 | } 786 | }, 787 | "node_modules/retry": { 788 | "version": "0.12.0", 789 | "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", 790 | "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", 791 | "license": "MIT", 792 | "engines": { 793 | "node": ">= 4" 794 | } 795 | }, 796 | "node_modules/rimraf": { 797 | "version": "5.0.10", 798 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", 799 | "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", 800 | "license": "ISC", 801 | "dependencies": { 802 | "glob": "^10.3.7" 803 | }, 804 | "bin": { 805 | "rimraf": "dist/esm/bin.mjs" 806 | }, 807 | "funding": { 808 | "url": "https://github.com/sponsors/isaacs" 809 | } 810 | }, 811 | "node_modules/safer-buffer": { 812 | "version": "2.1.2", 813 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 814 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 815 | "license": "MIT", 816 | "optional": true 817 | }, 818 | "node_modules/semver": { 819 | "version": "7.7.1", 820 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", 821 | "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", 822 | "license": "ISC", 823 | "bin": { 824 | "semver": "bin/semver.js" 825 | }, 826 | "engines": { 827 | "node": ">=10" 828 | } 829 | }, 830 | "node_modules/shebang-command": { 831 | "version": "2.0.0", 832 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 833 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 834 | "license": "MIT", 835 | "dependencies": { 836 | "shebang-regex": "^3.0.0" 837 | }, 838 | "engines": { 839 | "node": ">=8" 840 | } 841 | }, 842 | "node_modules/shebang-regex": { 843 | "version": "3.0.0", 844 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 845 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 846 | "license": "MIT", 847 | "engines": { 848 | "node": ">=8" 849 | } 850 | }, 851 | "node_modules/signal-exit": { 852 | "version": "4.1.0", 853 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", 854 | "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", 855 | "license": "ISC", 856 | "engines": { 857 | "node": ">=14" 858 | }, 859 | "funding": { 860 | "url": "https://github.com/sponsors/isaacs" 861 | } 862 | }, 863 | "node_modules/smart-buffer": { 864 | "version": "4.2.0", 865 | "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", 866 | "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", 867 | "license": "MIT", 868 | "engines": { 869 | "node": ">= 6.0.0", 870 | "npm": ">= 3.0.0" 871 | } 872 | }, 873 | "node_modules/socks": { 874 | "version": "2.8.4", 875 | "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz", 876 | "integrity": "sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==", 877 | "license": "MIT", 878 | "dependencies": { 879 | "ip-address": "^9.0.5", 880 | "smart-buffer": "^4.2.0" 881 | }, 882 | "engines": { 883 | "node": ">= 10.0.0", 884 | "npm": ">= 3.0.0" 885 | } 886 | }, 887 | "node_modules/socks-proxy-agent": { 888 | "version": "8.0.5", 889 | "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", 890 | "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", 891 | "license": "MIT", 892 | "dependencies": { 893 | "agent-base": "^7.1.2", 894 | "debug": "^4.3.4", 895 | "socks": "^2.8.3" 896 | }, 897 | "engines": { 898 | "node": ">= 14" 899 | } 900 | }, 901 | "node_modules/sprintf-js": { 902 | "version": "1.1.3", 903 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", 904 | "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", 905 | "license": "BSD-3-Clause" 906 | }, 907 | "node_modules/ssri": { 908 | "version": "12.0.0", 909 | "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz", 910 | "integrity": "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==", 911 | "license": "ISC", 912 | "dependencies": { 913 | "minipass": "^7.0.3" 914 | }, 915 | "engines": { 916 | "node": "^18.17.0 || >=20.5.0" 917 | } 918 | }, 919 | "node_modules/string-width": { 920 | "version": "5.1.2", 921 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", 922 | "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", 923 | "license": "MIT", 924 | "dependencies": { 925 | "eastasianwidth": "^0.2.0", 926 | "emoji-regex": "^9.2.2", 927 | "strip-ansi": "^7.0.1" 928 | }, 929 | "engines": { 930 | "node": ">=12" 931 | }, 932 | "funding": { 933 | "url": "https://github.com/sponsors/sindresorhus" 934 | } 935 | }, 936 | "node_modules/string-width-cjs": { 937 | "name": "string-width", 938 | "version": "4.2.3", 939 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 940 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 941 | "license": "MIT", 942 | "dependencies": { 943 | "emoji-regex": "^8.0.0", 944 | "is-fullwidth-code-point": "^3.0.0", 945 | "strip-ansi": "^6.0.1" 946 | }, 947 | "engines": { 948 | "node": ">=8" 949 | } 950 | }, 951 | "node_modules/string-width-cjs/node_modules/ansi-regex": { 952 | "version": "5.0.1", 953 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 954 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 955 | "license": "MIT", 956 | "engines": { 957 | "node": ">=8" 958 | } 959 | }, 960 | "node_modules/string-width-cjs/node_modules/emoji-regex": { 961 | "version": "8.0.0", 962 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 963 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 964 | "license": "MIT" 965 | }, 966 | "node_modules/string-width-cjs/node_modules/strip-ansi": { 967 | "version": "6.0.1", 968 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 969 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 970 | "license": "MIT", 971 | "dependencies": { 972 | "ansi-regex": "^5.0.1" 973 | }, 974 | "engines": { 975 | "node": ">=8" 976 | } 977 | }, 978 | "node_modules/strip-ansi": { 979 | "version": "7.1.0", 980 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", 981 | "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", 982 | "license": "MIT", 983 | "dependencies": { 984 | "ansi-regex": "^6.0.1" 985 | }, 986 | "engines": { 987 | "node": ">=12" 988 | }, 989 | "funding": { 990 | "url": "https://github.com/chalk/strip-ansi?sponsor=1" 991 | } 992 | }, 993 | "node_modules/strip-ansi-cjs": { 994 | "name": "strip-ansi", 995 | "version": "6.0.1", 996 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 997 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 998 | "license": "MIT", 999 | "dependencies": { 1000 | "ansi-regex": "^5.0.1" 1001 | }, 1002 | "engines": { 1003 | "node": ">=8" 1004 | } 1005 | }, 1006 | "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { 1007 | "version": "5.0.1", 1008 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 1009 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 1010 | "license": "MIT", 1011 | "engines": { 1012 | "node": ">=8" 1013 | } 1014 | }, 1015 | "node_modules/tar": { 1016 | "version": "7.4.3", 1017 | "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", 1018 | "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", 1019 | "license": "ISC", 1020 | "dependencies": { 1021 | "@isaacs/fs-minipass": "^4.0.0", 1022 | "chownr": "^3.0.0", 1023 | "minipass": "^7.1.2", 1024 | "minizlib": "^3.0.1", 1025 | "mkdirp": "^3.0.1", 1026 | "yallist": "^5.0.0" 1027 | }, 1028 | "engines": { 1029 | "node": ">=18" 1030 | } 1031 | }, 1032 | "node_modules/undici-types": { 1033 | "version": "6.20.0", 1034 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", 1035 | "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", 1036 | "dev": true, 1037 | "license": "MIT" 1038 | }, 1039 | "node_modules/unique-filename": { 1040 | "version": "4.0.0", 1041 | "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz", 1042 | "integrity": "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==", 1043 | "license": "ISC", 1044 | "dependencies": { 1045 | "unique-slug": "^5.0.0" 1046 | }, 1047 | "engines": { 1048 | "node": "^18.17.0 || >=20.5.0" 1049 | } 1050 | }, 1051 | "node_modules/unique-slug": { 1052 | "version": "5.0.0", 1053 | "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz", 1054 | "integrity": "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==", 1055 | "license": "ISC", 1056 | "dependencies": { 1057 | "imurmurhash": "^0.1.4" 1058 | }, 1059 | "engines": { 1060 | "node": "^18.17.0 || >=20.5.0" 1061 | } 1062 | }, 1063 | "node_modules/which": { 1064 | "version": "2.0.2", 1065 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 1066 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 1067 | "license": "ISC", 1068 | "dependencies": { 1069 | "isexe": "^2.0.0" 1070 | }, 1071 | "bin": { 1072 | "node-which": "bin/node-which" 1073 | }, 1074 | "engines": { 1075 | "node": ">= 8" 1076 | } 1077 | }, 1078 | "node_modules/wrap-ansi": { 1079 | "version": "8.1.0", 1080 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", 1081 | "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", 1082 | "license": "MIT", 1083 | "dependencies": { 1084 | "ansi-styles": "^6.1.0", 1085 | "string-width": "^5.0.1", 1086 | "strip-ansi": "^7.0.1" 1087 | }, 1088 | "engines": { 1089 | "node": ">=12" 1090 | }, 1091 | "funding": { 1092 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 1093 | } 1094 | }, 1095 | "node_modules/wrap-ansi-cjs": { 1096 | "name": "wrap-ansi", 1097 | "version": "7.0.0", 1098 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 1099 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 1100 | "license": "MIT", 1101 | "dependencies": { 1102 | "ansi-styles": "^4.0.0", 1103 | "string-width": "^4.1.0", 1104 | "strip-ansi": "^6.0.0" 1105 | }, 1106 | "engines": { 1107 | "node": ">=10" 1108 | }, 1109 | "funding": { 1110 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 1111 | } 1112 | }, 1113 | "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { 1114 | "version": "5.0.1", 1115 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 1116 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 1117 | "license": "MIT", 1118 | "engines": { 1119 | "node": ">=8" 1120 | } 1121 | }, 1122 | "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { 1123 | "version": "4.3.0", 1124 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 1125 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 1126 | "license": "MIT", 1127 | "dependencies": { 1128 | "color-convert": "^2.0.1" 1129 | }, 1130 | "engines": { 1131 | "node": ">=8" 1132 | }, 1133 | "funding": { 1134 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 1135 | } 1136 | }, 1137 | "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { 1138 | "version": "8.0.0", 1139 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 1140 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 1141 | "license": "MIT" 1142 | }, 1143 | "node_modules/wrap-ansi-cjs/node_modules/string-width": { 1144 | "version": "4.2.3", 1145 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 1146 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 1147 | "license": "MIT", 1148 | "dependencies": { 1149 | "emoji-regex": "^8.0.0", 1150 | "is-fullwidth-code-point": "^3.0.0", 1151 | "strip-ansi": "^6.0.1" 1152 | }, 1153 | "engines": { 1154 | "node": ">=8" 1155 | } 1156 | }, 1157 | "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { 1158 | "version": "6.0.1", 1159 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 1160 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 1161 | "license": "MIT", 1162 | "dependencies": { 1163 | "ansi-regex": "^5.0.1" 1164 | }, 1165 | "engines": { 1166 | "node": ">=8" 1167 | } 1168 | }, 1169 | "node_modules/yallist": { 1170 | "version": "5.0.0", 1171 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", 1172 | "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", 1173 | "license": "BlueOak-1.0.0", 1174 | "engines": { 1175 | "node": ">=18" 1176 | } 1177 | } 1178 | } 1179 | } 1180 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "name": "@kmamal/gpu", 4 | "description": "WebGPU for Node.js via Google Dawn", 5 | "keywords": [ 6 | "webgpu", 7 | "gpu", 8 | "shader", 9 | "compute", 10 | "dawn" 11 | ], 12 | "license": "MIT", 13 | "repository": { 14 | "type": "git", 15 | "url": "git+ssh://git@github.com:kmamal/gpu.git" 16 | }, 17 | "main": "./src/index.js", 18 | "types": "./src/index.d.ts", 19 | "exports": "./src/index.js", 20 | "scripts": { 21 | "install": "cd scripts && node install.mjs", 22 | "download-release": "cd scripts && node download-release.mjs", 23 | "build": "cd scripts && node build.mjs", 24 | "download-depot-tools": "cd scripts && node download-depot-tools.mjs", 25 | "download-dawn": "cd scripts && node download-dawn.mjs", 26 | "configure": "cd scripts && node configure.mjs", 27 | "make": "cd scripts && node make.mjs", 28 | "upload-release": "cd scripts && node upload-release.mjs", 29 | "release": "cd scripts && node release.mjs", 30 | "clean": "cd scripts && node clean.mjs" 31 | }, 32 | "dependencies": { 33 | "node-addon-api": "^8.3.1", 34 | "node-gyp": "^11.1.0", 35 | "tar": "^7.4.3" 36 | }, 37 | "depotTools": { 38 | "url": "https://chromium.googlesource.com/chromium/tools/depot_tools.git", 39 | "commit": "cf5b6bd0cbc9a4a149818d0306a73723ddce5903" 40 | }, 41 | "dawn": { 42 | "url": "https://dawn.googlesource.com/dawn", 43 | "commit": "ddf744c3326de9291039d88f207981f205c7704a" 44 | }, 45 | "devDependencies": { 46 | "@types/node": "^22.13.5" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /scripts/build.mjs: -------------------------------------------------------------------------------- 1 | import Fs from 'fs' 2 | import C from './util/common.js' 3 | 4 | await Promise.all([ 5 | C.dir.depotTools, 6 | C.dir.dawn, 7 | C.dir.build, 8 | C.dir.dist, 9 | C.dir.publish, 10 | ].map(async (dir) => { 11 | await Fs.promises.rm(dir, { recursive: true }).catch(() => {}) 12 | })) 13 | 14 | await import('./download-depot-tools.mjs') 15 | await import('./download-dawn.mjs') 16 | await import('./configure.mjs') 17 | await import('./make.mjs') 18 | -------------------------------------------------------------------------------- /scripts/clean.mjs: -------------------------------------------------------------------------------- 1 | import Fs from 'fs' 2 | import Path from 'path' 3 | import C from './util/common.js' 4 | 5 | const dirs = [ 6 | Path.join(C.dir.root, 'node_modules'), 7 | C.dir.depotTools, 8 | C.dir.dawn, 9 | C.dir.build, 10 | C.dir.dist, 11 | C.dir.publish, 12 | ] 13 | 14 | console.log("delete") 15 | await Promise.all(dirs.map(async (dir) => { 16 | console.log(" ", dir) 17 | await Fs.promises.rm(dir, { recursive: true }).catch(() => {}) 18 | })) 19 | -------------------------------------------------------------------------------- /scripts/configure.mjs: -------------------------------------------------------------------------------- 1 | import Fs from 'fs' 2 | import { execSync } from 'child_process' 3 | import C from './util/common.js' 4 | 5 | process.chdir(C.dir.dawn) 6 | await Fs.promises.copyFile('scripts/standalone-with-node.gclient', '.gclient') 7 | 8 | console.log("run gclient sync") 9 | execSync('gclient sync --no-history -j8 -vvv', { 10 | stdio: 'inherit', 11 | env: { 12 | ...process.env, 13 | ...C.depotTools.env, 14 | DEPOT_TOOLS_UPDATE: '0', 15 | }, 16 | }) 17 | 18 | console.log("configure build in", C.dir.build) 19 | 20 | await Fs.promises.rm(C.dir.build, { recursive: true }).catch(() => {}) 21 | await Fs.promises.mkdir(C.dir.build, { recursive: true }) 22 | 23 | let CFLAGS 24 | let LDFLAGS 25 | let crossCompileFlag 26 | let backendFlags = [] 27 | if (C.platform === 'darwin') { 28 | let arch = process.env.CROSS_COMPILE_ARCH ?? C.arch 29 | if (arch === 'x64') { arch = 'x86_64' } 30 | 31 | crossCompileFlag = `-DCMAKE_OSX_ARCHITECTURES="${arch}"` 32 | 33 | if (C.targetArch === 'arm64') { 34 | CFLAGS = '-mmacosx-version-min=11.0' 35 | LDFLAGS = '-mmacosx-version-min=11.0' 36 | } else { 37 | CFLAGS = [ 38 | '-mmacosx-version-min=10.9', 39 | '-DMAC_OS_X_VERSION_MIN_REQUIRED=1070', 40 | ].join(' ') 41 | LDFLAGS = '-mmacosx-version-min=10.9' 42 | } 43 | } else if (C.platform === 'linux') { 44 | backendFlags = [ 45 | '-DDAWN_USE_X11=ON', 46 | '-DDAWN_USE_WAYLAND=OFF', 47 | ] 48 | } 49 | 50 | execSync(`cmake ${[ 51 | '-S', 52 | `"${C.dir.dawn}"`, 53 | '-B', 54 | `"${C.dir.build}"`, 55 | '-GNinja', 56 | '-DCMAKE_BUILD_TYPE=Release', 57 | '-DDAWN_BUILD_NODE_BINDINGS=ON', 58 | '-DDAWN_BUILD_SAMPLES=OFF', 59 | '-DTINT_BUILD_TESTS=OFF', 60 | '-DTINT_BUILD_CMD_TOOLS=OFF', 61 | '-DDAWN_USE_GLFW=OFF', 62 | '-DDAWN_SUPPORTS_GLFW_FOR_WINDOWING=OFF', 63 | '-DDAWN_ENABLE_PIC=ON', 64 | '-DDAWN_ENABLE_SPIRV_VALIDATION=ON', 65 | '-DDAWN_ALWAYS_ASSERT=ON', 66 | crossCompileFlag, 67 | ...backendFlags, 68 | ].filter(Boolean).join(' ')}`, { 69 | stdio: 'inherit', 70 | env: { 71 | ...process.env, 72 | ...C.depotTools.env, 73 | CFLAGS, 74 | LDFLAGS, 75 | }, 76 | }) 77 | -------------------------------------------------------------------------------- /scripts/download-dawn.mjs: -------------------------------------------------------------------------------- 1 | import Fs from 'fs' 2 | import Path from 'path' 3 | import { execSync } from 'child_process' 4 | import C from './util/common.js' 5 | 6 | console.log("clone", C.dawn.url) 7 | await Fs.promises.rm(C.dir.dawn, { recursive: true }).catch(() => {}) 8 | execSync([ 9 | `mkdir ${C.dir.dawn}`, 10 | `cd ${C.dir.dawn}`, 11 | 'git init', 12 | `git remote add origin ${C.dawn.url}`, 13 | `git fetch --depth 1 origin ${C.dawn.commit}`, 14 | 'git checkout FETCH_HEAD', 15 | ].join(' && '), { 16 | stdio: 'inherit', 17 | cwd: C.dir.root, 18 | }) 19 | 20 | console.log("applying patch") 21 | process.chdir(C.dir.dawn) 22 | execSync(`git apply --ignore-space-change --ignore-whitespace ${Path.join(C.dir.root, 'dawn.patch')}`, { 23 | stdio: 'inherit', 24 | }) 25 | -------------------------------------------------------------------------------- /scripts/download-depot-tools.mjs: -------------------------------------------------------------------------------- 1 | import Fs from 'fs' 2 | import { execSync } from 'child_process' 3 | import C from './util/common.js' 4 | 5 | console.log("clone", C.depotTools.url) 6 | await Fs.promises.rm(C.dir.depotTools, { recursive: true }).catch(() => {}) 7 | execSync([ 8 | `mkdir ${C.dir.depotTools}`, 9 | `cd ${C.dir.depotTools}`, 10 | 'git init', 11 | `git remote add origin ${C.depotTools.url}`, 12 | `git fetch --depth 1 origin ${C.depotTools.commit}`, 13 | 'git checkout FETCH_HEAD', 14 | ].join(' && '), { 15 | stdio: 'inherit', 16 | cwd: C.dir.root, 17 | }) 18 | 19 | if (C.platform === 'win32') { 20 | execSync('gclient', { 21 | stdio: 'inherit', 22 | env: { 23 | ...process.env, 24 | ...C.depotTools.env, 25 | }, 26 | }) 27 | 28 | await Fs.promises.rm(`${C.dir.depotTools}/ninja`) 29 | } 30 | -------------------------------------------------------------------------------- /scripts/download-release.mjs: -------------------------------------------------------------------------------- 1 | import Fs from 'fs' 2 | import { once } from 'events' 3 | import C from './util/common.js' 4 | import { fetch } from './util/fetch.js' 5 | import * as Tar from 'tar' 6 | 7 | const url = `https://github.com/${C.owner}/${C.repo}/releases/download/v${C.version}/${C.assetName}` 8 | 9 | console.log("fetch", url) 10 | const response = await fetch(url) 11 | 12 | console.log("unpack to", C.dir.dist) 13 | await Fs.promises.rm(C.dir.dist, { recursive: true }).catch(() => {}) 14 | await Fs.promises.mkdir(C.dir.dist, { recursive: true }) 15 | const tar = Tar.extract({ gzip: true, C: C.dir.dist }) 16 | response.stream().pipe(tar) 17 | await once(tar, 'finish') 18 | -------------------------------------------------------------------------------- /scripts/install-deps-mac.sh: -------------------------------------------------------------------------------- 1 | 2 | brew install ninja go 3 | -------------------------------------------------------------------------------- /scripts/install-deps-ubuntu.sh: -------------------------------------------------------------------------------- 1 | 2 | apt-get install -y \ 3 | ninja-build \ 4 | libx11-xcb-dev \ 5 | libxrandr-dev \ 6 | libxinerama-dev \ 7 | libxcursor-dev \ 8 | libxi-dev 9 | -------------------------------------------------------------------------------- /scripts/install-deps-windows.cmd: -------------------------------------------------------------------------------- 1 | 2 | choco install ninja 3 | -------------------------------------------------------------------------------- /scripts/install.mjs: -------------------------------------------------------------------------------- 1 | 2 | if (!process.env.BUILD_DAWN_FROM_SOURCE) { 3 | try { 4 | await import('./download-release.mjs') 5 | process.exit(0) 6 | } catch (_) { 7 | console.log("failed to download release") 8 | } 9 | } else { 10 | console.log("skip download and build from source") 11 | } 12 | 13 | await import('./build.mjs') 14 | -------------------------------------------------------------------------------- /scripts/make.mjs: -------------------------------------------------------------------------------- 1 | import Fs from 'fs' 2 | import Path from 'path' 3 | import { execSync } from 'child_process' 4 | import C from './util/common.js' 5 | 6 | console.log("build in", C.dir.build) 7 | execSync(`ninja -v -C ${C.dir.build} dawn.node`, { 8 | stdio: 'inherit', 9 | env: { 10 | ...process.env, 11 | ...C.depotTools.env, 12 | }, 13 | }) 14 | 15 | console.log("copy to", C.dir.dist) 16 | await Fs.promises.rm(C.dir.dist, { recursive: true }).catch(() => {}) 17 | await Fs.promises.mkdir(C.dir.dist, { recursive: true }) 18 | await Fs.promises.copyFile( 19 | Path.join(C.dir.build, 'dawn.node'), 20 | Path.join(C.dir.dist, 'dawn.node'), 21 | ) 22 | -------------------------------------------------------------------------------- /scripts/release.mjs: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process' 2 | 3 | await import('./clean.mjs') 4 | 5 | execSync('npm install', { 6 | stdio: 'inherit', 7 | env: { 8 | ...process.env, 9 | BUILD_DAWN_FROM_SOURCE: 1, 10 | }, 11 | }) 12 | 13 | await import('./upload-release.mjs') 14 | -------------------------------------------------------------------------------- /scripts/upload-release.mjs: -------------------------------------------------------------------------------- 1 | import Fs from 'fs' 2 | import Path from 'path' 3 | import C from './util/common.js' 4 | import { fetch } from './util/fetch.js' 5 | import * as Tar from 'tar' 6 | 7 | const commonHeaders = { 8 | "Accept": 'application/vnd.github+json', 9 | "Authorization": `Bearer ${process.env.GITHUB_TOKEN}`, 10 | 'User-Agent': `@${C.owner}/${C.repo}@${C.version}`, 11 | } 12 | 13 | let response 14 | 15 | getRelease: { 16 | console.log("get release", C.version) 17 | 18 | try { 19 | response = await fetch( 20 | `https://api.github.com/repos/${C.owner}/${C.repo}/releases/tags/v${C.version}`, 21 | { headers: commonHeaders }, 22 | ) 23 | console.log("release exists", C.version) 24 | break getRelease 25 | } catch (error) { 26 | console.log(error.message) 27 | } 28 | 29 | console.log("create release", C.version) 30 | 31 | response = await fetch( 32 | `https://api.github.com/repos/${C.owner}/${C.repo}/releases`, 33 | { 34 | method: 'POST', 35 | headers: commonHeaders, 36 | body: JSON.stringify({ 37 | tag_name: `v${C.version}`, // eslint-disable-line camelcase 38 | name: `v${C.version}`, 39 | prerelease: C.isPrerelease, 40 | make_latest: `${!C.isPrerelease}`, // eslint-disable-line camelcase 41 | }), 42 | }, 43 | ) 44 | } 45 | const releaseId = (await response.json()).id 46 | 47 | console.log("create archive", C.assetName) 48 | await Fs.promises.rm(C.dir.publish, { recursive: true }).catch(() => {}) 49 | await Fs.promises.mkdir(C.dir.publish, { recursive: true }) 50 | const assetPath = Path.join(C.dir.publish, C.assetName) 51 | 52 | process.chdir(C.dir.dist) 53 | await Tar.create( 54 | { gzip: true, file: assetPath }, 55 | await Fs.promises.readdir('.'), 56 | ) 57 | const buffer = await Fs.promises.readFile(assetPath) 58 | 59 | response = await fetch( 60 | `https://api.github.com/repos/${C.owner}/${C.repo}/releases/${releaseId}/assets`, 61 | { headers: commonHeaders }, 62 | ) 63 | 64 | const list = await response.json() 65 | const asset = list.find((x) => x.name === C.assetName) 66 | if (asset) { 67 | console.log("delete asset", C.assetName) 68 | await fetch( 69 | `https://api.github.com/repos/${C.owner}/${C.repo}/releases/assets/${asset.id}`, 70 | { 71 | method: 'DELETE', 72 | headers: commonHeaders, 73 | }, 74 | ) 75 | } 76 | 77 | console.log("upload", C.assetName) 78 | await fetch( 79 | `https://uploads.github.com/repos/${C.owner}/${C.repo}/releases/${releaseId}/assets?name=${C.assetName}`, 80 | { 81 | method: 'POST', 82 | headers: { 83 | ...commonHeaders, 84 | 'Content-Type': 'application/gzip', 85 | }, 86 | body: buffer, 87 | }, 88 | ) 89 | -------------------------------------------------------------------------------- /scripts/util/common.js: -------------------------------------------------------------------------------- 1 | const Fs = require('fs') 2 | const Path = require('path') 3 | 4 | const dir = {} 5 | dir.root = Path.resolve(__dirname, '../..') 6 | dir.depotTools = Path.join(dir.root, 'depot_tools') 7 | dir.dawn = Path.join(dir.root, 'dawn') 8 | dir.build = Path.join(dir.root, 'build') 9 | dir.dist = Path.join(dir.root, 'dist') 10 | dir.publish = Path.join(dir.root, 'publish') 11 | 12 | const pkgPath = Path.join(dir.root, 'package.json') 13 | const pkg = JSON.parse(Fs.readFileSync(pkgPath).toString()) 14 | const version = pkg.version 15 | const isPrerelease = version.includes('-') 16 | const [ , owner, repo ] = pkg.repository.url.match(/([^/:]+)\/([^/]+).git$/u) 17 | 18 | const { platform, arch } = process 19 | const targetArch = process.env.CROSS_COMPILE_ARCH || arch 20 | const assetName = `dawn-v${version}-${platform}-${targetArch}.tar.gz` 21 | 22 | const { depotTools, dawn } = pkg 23 | depotTools.env = platform === 'win32' ? { 24 | DEPOT_TOOLS_WIN_TOOLCHAIN: '0', 25 | PATH: `${dir.depotTools};${process.env.PATH}`, 26 | } : { 27 | PATH: `${dir.depotTools}:${process.env.PATH}`, 28 | } 29 | 30 | module.exports = { 31 | dir, 32 | version, 33 | isPrerelease, 34 | owner, 35 | repo, 36 | platform, 37 | arch, 38 | targetArch, 39 | assetName, 40 | depotTools, 41 | dawn, 42 | } 43 | -------------------------------------------------------------------------------- /scripts/util/fetch.js: -------------------------------------------------------------------------------- 1 | 2 | const _consume = async (stream) => { 3 | const chunks = [] 4 | for await (const chunk of stream) { 5 | chunks.push(chunk) 6 | } 7 | return Buffer.concat(chunks) 8 | } 9 | 10 | const fetch = async (_url, options = {}) => { 11 | let url = _url 12 | let { 13 | maxRedirect = 5, 14 | body = null, 15 | } = options 16 | 17 | const { protocol } = new URL(url) 18 | const lib = require(protocol.slice(0, -1)) 19 | 20 | const evalPromise = (resolve, reject) => { 21 | const request = lib.request(url, options) 22 | .on('error', reject) 23 | .on('response', resolve) 24 | 25 | if (!body) { 26 | request.end() 27 | return 28 | } 29 | 30 | if (body?.length) { 31 | if (!Buffer.isBuffer(body)) { body = Buffer.from(body) } 32 | body = Promise.resolve(body) 33 | } else { 34 | body = _consume(body) 35 | } 36 | 37 | body.then((buffer) => { 38 | request.setHeader('Content-Length', buffer.length) 39 | request.end(buffer) 40 | }) 41 | } 42 | 43 | for (;;) { 44 | const response = await new Promise(evalPromise) 45 | 46 | const { statusCode } = response 47 | 48 | if (300 <= statusCode && statusCode < 400) { 49 | if (maxRedirect-- > 0) { 50 | url = response.headers.location 51 | continue 52 | } 53 | } 54 | 55 | if (!(200 <= statusCode && statusCode < 300)) { 56 | let responseBody 57 | try { responseBody = (await _consume(response)).toString() } catch (_) {} 58 | try { responseBody = JSON.parse(responseBody) } catch (_) {} 59 | throw Object.assign(new Error(`bad status code ${statusCode}`), { 60 | statusCode, 61 | responseBody, 62 | }) 63 | } 64 | 65 | return { 66 | stream () { return response }, 67 | async buffer () { 68 | return await _consume(response) 69 | }, 70 | async text () { 71 | const buffer = await this.buffer() 72 | return buffer.toString() 73 | }, 74 | async json () { 75 | const text = await this.text() 76 | return JSON.parse(text) 77 | }, 78 | } 79 | } 80 | } 81 | 82 | module.exports = { fetch } 83 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const { 2 | _create, 3 | renderGPUDeviceToWindow, 4 | globals, 5 | } = require('../dist/dawn.node') 6 | 7 | const instances = new Set() 8 | 9 | const fn = () => { instances.delete(null) } 10 | let interval = null 11 | 12 | const create = (...args) => { 13 | const instance = _create(...args) 14 | 15 | if (instances.size === 0) { 16 | interval = setInterval(fn, 60e3) 17 | } 18 | instances.add(instance) 19 | 20 | return instance 21 | } 22 | 23 | const destroy = (instance) => { 24 | instances.delete(instance) 25 | if (instances.size === 0) { 26 | clearInterval(interval) 27 | interval = null 28 | } 29 | } 30 | 31 | module.exports = { 32 | create, 33 | destroy, 34 | renderGPUDeviceToWindow, 35 | ...globals, 36 | } 37 | --------------------------------------------------------------------------------