wgpu 1.1.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +88 -0
- data/README.md +44 -5
- data/docs/README.md +22 -0
- data/docs/api_coverage.md +140 -0
- data/docs/async.md +41 -0
- data/docs/bind_groups.md +25 -0
- data/docs/buffer_data.md +37 -0
- data/docs/command_encoding.md +49 -0
- data/docs/errors.md +40 -0
- data/docs/getting_started_compute.md +62 -0
- data/docs/getting_started_rendering.md +62 -0
- data/docs/installation.md +94 -0
- data/docs/pipeline_descriptors.md +43 -0
- data/docs/releasing.md +32 -0
- data/docs/resource_lifetime.md +108 -0
- data/docs/shaders.md +32 -0
- data/docs/texture_readback.md +46 -0
- data/docs/troubleshooting.md +53 -0
- data/docs/upgrading_wgpu_native.md +37 -0
- data/ext/wgpu/extconf.rb +10 -142
- data/lib/wgpu/async_task.rb +19 -0
- data/lib/wgpu/commands/command_buffer.rb +25 -1
- data/lib/wgpu/commands/command_encoder.rb +123 -9
- data/lib/wgpu/commands/compute_pass.rb +53 -0
- data/lib/wgpu/commands/render_bundle.rb +9 -1
- data/lib/wgpu/commands/render_bundle_encoder.rb +65 -4
- data/lib/wgpu/commands/render_pass.rb +136 -8
- data/lib/wgpu/core/adapter.rb +123 -19
- data/lib/wgpu/core/async_waiter.rb +47 -4
- data/lib/wgpu/core/canvas_context.rb +32 -2
- data/lib/wgpu/core/device.rb +399 -53
- data/lib/wgpu/core/instance.rb +28 -4
- data/lib/wgpu/core/queue.rb +197 -51
- data/lib/wgpu/core/surface.rb +64 -17
- data/lib/wgpu/data_types.rb +83 -0
- data/lib/wgpu/descriptor_helpers.rb +104 -0
- data/lib/wgpu/error.rb +70 -0
- data/lib/wgpu/logging.rb +63 -0
- data/lib/wgpu/native/abi_verifier.rb +143 -0
- data/lib/wgpu/native/callbacks.rb +10 -1
- data/lib/wgpu/native/capabilities.rb +32 -2
- data/lib/wgpu/native/distribution.rb +176 -0
- data/lib/wgpu/native/enum_helper.rb +80 -0
- data/lib/wgpu/native/enums.rb +68 -8
- data/lib/wgpu/native/fixtures/webgpu-v27.0.4.0-enums.h +848 -0
- data/lib/wgpu/native/functions.rb +21 -10
- data/lib/wgpu/native/installer.rb +223 -0
- data/lib/wgpu/native/loader.rb +61 -21
- data/lib/wgpu/native/structs.rb +18 -1
- data/lib/wgpu/native_resource.rb +342 -0
- data/lib/wgpu/pipeline/bind_group.rb +21 -0
- data/lib/wgpu/pipeline/bind_group_layout.rb +108 -41
- data/lib/wgpu/pipeline/compute_pipeline.rb +40 -50
- data/lib/wgpu/pipeline/pipeline_layout.rb +8 -0
- data/lib/wgpu/pipeline/render_pipeline.rb +207 -110
- data/lib/wgpu/pipeline/shader_module.rb +95 -28
- data/lib/wgpu/resources/buffer.rb +387 -85
- data/lib/wgpu/resources/query_set.rb +26 -12
- data/lib/wgpu/resources/sampler.rb +43 -20
- data/lib/wgpu/resources/texture.rb +89 -48
- data/lib/wgpu/resources/texture_view.rb +35 -7
- data/lib/wgpu/texture_format.rb +96 -0
- data/lib/wgpu/version.rb +1 -1
- data/lib/wgpu/window.rb +34 -1
- data/lib/wgpu.rb +32 -0
- data/sig/wgpu.rbs +460 -0
- metadata +33 -16
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Getting started: rendering
|
|
2
|
+
|
|
3
|
+
Rendering adds a window-system integration. The bundled helper uses SDL3, but
|
|
4
|
+
surfaces themselves are independent of SDL and may be created from native
|
|
5
|
+
Metal, Win32, Xlib, or Wayland handles.
|
|
6
|
+
|
|
7
|
+
Add the optional dependency:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem "wgpu"
|
|
11
|
+
gem "sdl3", "~> 1.0"
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Install the SDL3 system library, then load the helper explicitly:
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
require "wgpu"
|
|
18
|
+
require "wgpu/window"
|
|
19
|
+
|
|
20
|
+
window = WGPU::Window::SDLWindow.new(
|
|
21
|
+
title: "WebGPU",
|
|
22
|
+
width: 800,
|
|
23
|
+
height: 600
|
|
24
|
+
)
|
|
25
|
+
instance = WGPU::Instance.new
|
|
26
|
+
surface = window.create_surface(instance)
|
|
27
|
+
adapter = instance.request_adapter(compatible_surface: surface)
|
|
28
|
+
device = adapter.request_device
|
|
29
|
+
|
|
30
|
+
format = surface.capabilities(adapter).fetch(:formats).first
|
|
31
|
+
width, height = window.drawable_size
|
|
32
|
+
surface.configure(
|
|
33
|
+
device: device,
|
|
34
|
+
format: format,
|
|
35
|
+
width: width,
|
|
36
|
+
height: height,
|
|
37
|
+
present_mode: :fifo
|
|
38
|
+
)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
For every frame, acquire `surface.current_texture`, create a view, encode and
|
|
42
|
+
submit a render pass, call `surface.present`, and release frame-local objects.
|
|
43
|
+
The returned texture exposes `surface_status` as `:success_optimal` or
|
|
44
|
+
`:success_suboptimal`. Acquisition failures raise
|
|
45
|
+
`WGPU::SurfaceAcquisitionError`; inspect its `status` reader for `:timeout`,
|
|
46
|
+
`:outdated`, `:lost`, `:out_of_memory`, or `:device_lost`. Reconfigure on
|
|
47
|
+
`:outdated`/`:lost` after re-reading the drawable size.
|
|
48
|
+
When `drawable_size` changes, configure the surface again with positive pixel
|
|
49
|
+
dimensions. Do not render while a minimized window reports zero dimensions.
|
|
50
|
+
|
|
51
|
+
`Surface` is the low-level native-window API. `CanvasContext` owns and
|
|
52
|
+
delegates to a surface while retaining the familiar configure/acquire/present
|
|
53
|
+
shape. Integrations other than SDL3 can pass an `FFI::Pointer` obtained from
|
|
54
|
+
their window toolkit to `Surface.from_metal_layer`,
|
|
55
|
+
`Surface.from_windows_hwnd`, `Surface.from_xlib_window`, or
|
|
56
|
+
`Surface.from_wayland_surface`. The application must keep the corresponding
|
|
57
|
+
native window/display/layer alive longer than the surface; wgpu-ruby does not
|
|
58
|
+
take ownership of those platform objects.
|
|
59
|
+
|
|
60
|
+
[`examples/09_clear_color.rb`](../examples/09_clear_color.rb) is the smallest
|
|
61
|
+
resizable render loop. The other rendering examples cover vertex/index
|
|
62
|
+
buffers, textures, bind groups, depth testing, and uniform updates.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Installation and native artifacts
|
|
2
|
+
|
|
3
|
+
wgpu-ruby is a Ruby-FFI binding. Gem installation downloads a prebuilt
|
|
4
|
+
wgpu-native library; it does not compile a C extension.
|
|
5
|
+
|
|
6
|
+
## Supported runtime
|
|
7
|
+
|
|
8
|
+
- Ruby 3.2 or newer
|
|
9
|
+
- 64-bit macOS x86_64/arm64
|
|
10
|
+
- 64-bit Linux glibc x86_64/aarch64
|
|
11
|
+
- 64-bit Windows x86_64 with the MSVC wgpu-native artifact
|
|
12
|
+
|
|
13
|
+
Linux musl/Alpine and 32-bit runtimes do not have prebuilt artifacts. They may
|
|
14
|
+
still work with a compatible custom library supplied through `WGPU_LIB_PATH`.
|
|
15
|
+
|
|
16
|
+
## Pinned release and artifacts
|
|
17
|
+
|
|
18
|
+
The binding targets wgpu-native `v27.0.4.0` from:
|
|
19
|
+
|
|
20
|
+
`https://github.com/gfx-rs/wgpu-native/releases/download/v27.0.4.0/`
|
|
21
|
+
|
|
22
|
+
| Ruby platform | Archive | Library |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| `x86_64-linux` | `wgpu-linux-x86_64-release.zip` | `libwgpu_native.so` |
|
|
25
|
+
| `aarch64-linux` | `wgpu-linux-aarch64-release.zip` | `libwgpu_native.so` |
|
|
26
|
+
| `x86_64-darwin` | `wgpu-macos-x86_64-release.zip` | `libwgpu_native.dylib` |
|
|
27
|
+
| `arm64-darwin` | `wgpu-macos-aarch64-release.zip` | `libwgpu_native.dylib` |
|
|
28
|
+
| Windows `mingw`/`mswin` | `wgpu-windows-x86_64-msvc-release.zip` | `wgpu_native.dll` |
|
|
29
|
+
|
|
30
|
+
## Resolution order
|
|
31
|
+
|
|
32
|
+
1. If `WGPU_LIB_PATH` is set, installation skips the download and runtime
|
|
33
|
+
loading uses that exact file.
|
|
34
|
+
2. Otherwise the runtime uses the first configured cache location:
|
|
35
|
+
`WGPU_CACHE_DIR`, `XDG_CACHE_HOME`, then the operating-system default.
|
|
36
|
+
3. The legacy `~/.cache/wgpu-ruby/v27.0.4.0/lib/` location is also searched so
|
|
37
|
+
an existing installation does not require another download.
|
|
38
|
+
4. If the cached library is absent, reinstall the gem or run
|
|
39
|
+
`bundle exec ruby ext/wgpu/extconf.rb` from a source checkout.
|
|
40
|
+
|
|
41
|
+
Download uses `curl` when available, then Ruby `Net::HTTP`. Extraction uses
|
|
42
|
+
`rubyzip`, then PowerShell `Expand-Archive` on Windows, then `unzip`.
|
|
43
|
+
|
|
44
|
+
## Manual installation
|
|
45
|
+
|
|
46
|
+
1. Download the archive for the target above.
|
|
47
|
+
2. Extract it and locate the library in the archive's `lib` directory.
|
|
48
|
+
3. Set `WGPU_LIB_PATH` to the absolute library path before starting Ruby:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
export WGPU_LIB_PATH=/opt/wgpu-native/lib/libwgpu_native.so
|
|
52
|
+
bundle exec ruby -Ilib -e 'require "wgpu"; puts WGPU::Native.library_path'
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
PowerShell:
|
|
56
|
+
|
|
57
|
+
```powershell
|
|
58
|
+
$env:WGPU_LIB_PATH = "C:\wgpu-native\lib\wgpu_native.dll"
|
|
59
|
+
bundle exec ruby -Ilib -e 'require "wgpu"; puts WGPU::Native.library_path'
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The library must match the process architecture and the v27 ABI expected by
|
|
63
|
+
this gem.
|
|
64
|
+
|
|
65
|
+
## Environment variables
|
|
66
|
+
|
|
67
|
+
| Variable | Meaning |
|
|
68
|
+
|---|---|
|
|
69
|
+
| `WGPU_LIB_PATH` | Absolute path to a custom wgpu-native shared library; highest priority. |
|
|
70
|
+
| `WGPU_CACHE_DIR` | wgpu-ruby cache root override. The version directory is created directly beneath it. |
|
|
71
|
+
| `XDG_CACHE_HOME` | Cache base on Unix when `WGPU_CACHE_DIR` is unset. |
|
|
72
|
+
| `WGPU_SKIP_GPU_TESTS=1` | Skip tests that require an adapter. This is a test setting, not a runtime backend selector. |
|
|
73
|
+
|
|
74
|
+
Default cache paths are:
|
|
75
|
+
|
|
76
|
+
| Platform | Versioned cache directory |
|
|
77
|
+
|---|---|
|
|
78
|
+
| macOS | `~/Library/Caches/wgpu-ruby/v27.0.4.0/` |
|
|
79
|
+
| Linux/other Unix | `~/.cache/wgpu-ruby/v27.0.4.0/` |
|
|
80
|
+
| Windows | `%LOCALAPPDATA%\wgpu-ruby\v27.0.4.0\` |
|
|
81
|
+
|
|
82
|
+
Every downloaded archive is checked against the SHA-256 digest recorded in
|
|
83
|
+
`lib/wgpu/native/distribution.rb` before extraction. A mismatch deletes the
|
|
84
|
+
archive and stops installation.
|
|
85
|
+
|
|
86
|
+
## Troubleshooting
|
|
87
|
+
|
|
88
|
+
- **Library not found:** rerun the install hook or set `WGPU_LIB_PATH`.
|
|
89
|
+
- **Unsupported platform:** build wgpu-native for the host and use
|
|
90
|
+
`WGPU_LIB_PATH`.
|
|
91
|
+
- **Linux cannot create an adapter:** install a Vulkan driver. Mesa lavapipe
|
|
92
|
+
(`mesa-vulkan-drivers`) is sufficient for software-based tests.
|
|
93
|
+
- **Rendering examples fail to load SDL3:** install the SDL3 system library and
|
|
94
|
+
the `sdl3` Ruby gem. Compute APIs do not load `wgpu/window`.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Pipeline descriptors
|
|
2
|
+
|
|
3
|
+
`WGPU::ComputePipeline` and `WGPU::RenderPipeline` accept Ruby Hashes that mirror
|
|
4
|
+
WebGPU pipeline descriptors. Passing `layout: :auto`, `"auto"`, or `nil` requests
|
|
5
|
+
automatic layout inference. A layout object continues to select an explicit
|
|
6
|
+
pipeline layout.
|
|
7
|
+
|
|
8
|
+
## Default pipeline state
|
|
9
|
+
|
|
10
|
+
The wrapper applies the following WebGPU defaults when a field is omitted:
|
|
11
|
+
|
|
12
|
+
| State | Field | Default |
|
|
13
|
+
|---|---|---|
|
|
14
|
+
| Vertex/fragment/compute | `entry_point` | omitted (`nil`) |
|
|
15
|
+
| Vertex buffer | `step_mode` | `:vertex` |
|
|
16
|
+
| Primitive | `topology` | `:triangle_list` |
|
|
17
|
+
| Primitive | `strip_index_format` | `:undefined` |
|
|
18
|
+
| Primitive | `front_face` | `:ccw` |
|
|
19
|
+
| Primitive | `cull_mode` | `:none` |
|
|
20
|
+
| Primitive | `unclipped_depth` | `false` |
|
|
21
|
+
| Multisample | `count` | `1` |
|
|
22
|
+
| Multisample | `mask` | `0xFFFFFFFF` |
|
|
23
|
+
| Multisample | `alpha_to_coverage_enabled` | `false` |
|
|
24
|
+
| Depth/stencil | `depth_compare` | `:always` |
|
|
25
|
+
| Stencil face | operations | `:keep` |
|
|
26
|
+
| Color target | `write_mask` | `:all` |
|
|
27
|
+
| Blend component | `operation` | `:add` |
|
|
28
|
+
| Blend component | `src_factor` | `:one` |
|
|
29
|
+
| Blend component | `dst_factor` | `:zero` |
|
|
30
|
+
|
|
31
|
+
`vertex[:module]`, each vertex attribute's `format`, `offset`, and
|
|
32
|
+
`shader_location`, and each color target's `format` are required. A
|
|
33
|
+
depth/stencil state requires `format`. Unknown descriptor keys produce a warning
|
|
34
|
+
so misspellings are visible while preserving v1.x compatibility.
|
|
35
|
+
|
|
36
|
+
When `entry_point` is omitted, the wrapper passes WebGPU's nullable string
|
|
37
|
+
sentinel to wgpu-native. Native pipeline creation then selects the sole matching
|
|
38
|
+
entry point in that shader stage. Specify `entry_point:` when a stage contains
|
|
39
|
+
multiple matching entry points.
|
|
40
|
+
|
|
41
|
+
Both vertex/fragment and compute stages accept `constants:`, a Hash mapping WGSL
|
|
42
|
+
override names to numeric values. The descriptor keeps all FFI strings and
|
|
43
|
+
arrays alive until native pipeline creation completes.
|
data/docs/releasing.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Releasing
|
|
2
|
+
|
|
3
|
+
The release workflow uses RubyGems Trusted Publishing and runs for `v*` tags.
|
|
4
|
+
Before the first automated release, configure a trusted publisher for the
|
|
5
|
+
`ydah/wgpu-ruby` repository, workflow `release.yml`, and GitHub environment
|
|
6
|
+
`release` on RubyGems.org. The trusted publisher and protected environment are
|
|
7
|
+
repository/RubyGems.org settings, so they cannot be configured or verified from
|
|
8
|
+
this repository alone.
|
|
9
|
+
|
|
10
|
+
## Dry run
|
|
11
|
+
|
|
12
|
+
Run the **Push gem** workflow manually from the GitHub Actions page before
|
|
13
|
+
tagging a release. A `workflow_dispatch` run executes the same native build,
|
|
14
|
+
ABI verification, CPU and GPU specs, examples, RuboCop, RBS, YARD, coverage,
|
|
15
|
+
and gem build checks as a tag run. It skips both the tag/version comparison and
|
|
16
|
+
the RubyGems publishing action, so it cannot publish a gem.
|
|
17
|
+
|
|
18
|
+
Release checklist:
|
|
19
|
+
|
|
20
|
+
1. Complete the [wgpu-native upgrade checklist](upgrading_wgpu_native.md) when
|
|
21
|
+
the native version changes.
|
|
22
|
+
2. Update `WGPU::VERSION` and move Unreleased changelog entries under the new
|
|
23
|
+
version/date heading.
|
|
24
|
+
3. Run the complete local verification documented in the project README.
|
|
25
|
+
4. Build and inspect the gem: `gem build wgpu.gemspec`.
|
|
26
|
+
5. Merge the release commit, create an annotated `vX.Y.Z` tag, and push it.
|
|
27
|
+
6. Confirm CI and the protected `release` environment. The workflow rejects a
|
|
28
|
+
tag that does not equal `v#{WGPU::VERSION}`.
|
|
29
|
+
7. Verify the published gem and generated documentation on RubyGems.org.
|
|
30
|
+
|
|
31
|
+
The workflow requests only `contents: write` and `id-token: write`; it stores no
|
|
32
|
+
long-lived RubyGems API key.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Resource lifetime
|
|
2
|
+
|
|
3
|
+
wgpu-ruby follows wgpu-native's explicit reference-counted handle model.
|
|
4
|
+
Callers own every wrapper returned to them and must call `release` when it is no
|
|
5
|
+
longer needed. The library does not automatically release native handles from
|
|
6
|
+
Ruby finalizers.
|
|
7
|
+
|
|
8
|
+
## `release` and `destroy`
|
|
9
|
+
|
|
10
|
+
- `release` gives up the wrapper's native reference. It sets the stored handle
|
|
11
|
+
to `FFI::Pointer::NULL`; calling `release` again is harmless.
|
|
12
|
+
- `destroy` immediately invalidates the underlying GPU resource contents but
|
|
13
|
+
does not give up the wrapper's reference. Call `release` after `destroy`.
|
|
14
|
+
- Calling another public native operation after `release` raises
|
|
15
|
+
`WGPU::ResourceError` before FFI is entered. `handle`, `released?`, `label`,
|
|
16
|
+
`inspect`, and repeated `release` remain available.
|
|
17
|
+
- Every native wrapper supports `use { |resource| ... }`. It returns the block
|
|
18
|
+
result and releases the wrapper in `ensure`, including when the block raises.
|
|
19
|
+
This is Ruby convenience API rather than a WebGPU operation.
|
|
20
|
+
- Device callbacks share a lifetime across the device and every derived native
|
|
21
|
+
wrapper. Releasing a device before a child does not free callback closures
|
|
22
|
+
that wgpu-native may still reach; they are freed after the final child
|
|
23
|
+
wrapper and pending callback operation complete.
|
|
24
|
+
|
|
25
|
+
For short-lived resources:
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
bytes = device.create_buffer(
|
|
29
|
+
size: 256,
|
|
30
|
+
usage: %i[map_read copy_dst]
|
|
31
|
+
).use do |buffer|
|
|
32
|
+
# Work with buffer; it is released on every exit path.
|
|
33
|
+
end
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Wrapper matrix
|
|
37
|
+
|
|
38
|
+
The table covers all 23 native wrapper classes. `BufferMappedRange`,
|
|
39
|
+
`AsyncTask`, and `Window::SDLWindow` are not independent wgpu-native
|
|
40
|
+
reference-counted handles.
|
|
41
|
+
|
|
42
|
+
| Wrapper | `release` | `destroy` | Ownership note |
|
|
43
|
+
|---|:---:|:---:|---|
|
|
44
|
+
| `Instance` | ✅ | — | Release after all adapters/surfaces derived from it. |
|
|
45
|
+
| `Adapter` | ✅ | — | Returned by instance discovery/request. |
|
|
46
|
+
| `Device` | ✅ | ✅ | `release` also releases the cached queue wrapper first. |
|
|
47
|
+
| `Queue` | ✅ | — | Normally owned through `Device#queue`; device release cascades to it. |
|
|
48
|
+
| `Surface` | ✅ | — | Release after unconfiguring and after current texture work completes. |
|
|
49
|
+
| `CanvasContext` | ✅ | — | Releases its internally created surface. |
|
|
50
|
+
| `Buffer` | ✅ | ✅ | Destroy contents when early reclamation is desired, then release. |
|
|
51
|
+
| `Texture` | ✅ | ✅ | Texture views should be released before their texture. |
|
|
52
|
+
| `TextureView` | ✅ | — | `Texture#from_handle`/view wrappers are caller-owned. |
|
|
53
|
+
| `Sampler` | ✅ | — | |
|
|
54
|
+
| `QuerySet` | ✅ | ✅ | Destroy query storage, then release. The wrapper suppresses v27's aborting native double-removal path. |
|
|
55
|
+
| `ShaderModule` | ✅ | — | |
|
|
56
|
+
| `BindGroupLayout` | ✅ | — | Layouts returned by `get_bind_group_layout` are caller-owned. |
|
|
57
|
+
| `BindGroup` | ✅ | — | |
|
|
58
|
+
| `PipelineLayout` | ✅ | — | |
|
|
59
|
+
| `ComputePipeline` | ✅ | — | |
|
|
60
|
+
| `RenderPipeline` | ✅ | — | |
|
|
61
|
+
| `CommandEncoder` | ✅ | — | Finish before release. |
|
|
62
|
+
| `CommandBuffer` | ✅ | — | Release after submission is no longer needed. |
|
|
63
|
+
| `ComputePass` | ✅ | — | End the pass before finishing its encoder. |
|
|
64
|
+
| `RenderPass` | ✅ | — | End the pass before finishing its encoder. |
|
|
65
|
+
| `RenderBundleEncoder` | ✅ | — | Finish before release. |
|
|
66
|
+
| `RenderBundle` | ✅ | — | |
|
|
67
|
+
|
|
68
|
+
## Recommended release order
|
|
69
|
+
|
|
70
|
+
Release in the reverse order of creation:
|
|
71
|
+
|
|
72
|
+
1. End active compute/render passes.
|
|
73
|
+
2. Finish encoders and submit command buffers.
|
|
74
|
+
3. Release pass encoders, command buffers, and command encoders.
|
|
75
|
+
4. Release bind groups, pipelines/layouts, shader modules, samplers, texture
|
|
76
|
+
views, buffers, textures, and query sets.
|
|
77
|
+
5. Unconfigure and release surfaces/canvas contexts.
|
|
78
|
+
6. Release the device (which releases its queue), then adapter and instance.
|
|
79
|
+
|
|
80
|
+
Explicitly synchronize work when the application needs resources to remain
|
|
81
|
+
alive until completion. A `CommandBuffer` is consumed by its first successful
|
|
82
|
+
`Queue#submit`; submitting it again raises `WGPU::CommandError`. Submission
|
|
83
|
+
does not itself release the command buffer or its referenced Ruby wrappers.
|
|
84
|
+
|
|
85
|
+
## Internal staging resources
|
|
86
|
+
|
|
87
|
+
`Queue#read_buffer` and `Queue#read_texture` create a temporary staging buffer,
|
|
88
|
+
submit a copy, map and read it, then unmap and release it. Cleanup independently
|
|
89
|
+
attempts to unmap and release the command buffer, encoder, and internal staging
|
|
90
|
+
buffer on every exit path, even when an earlier cleanup operation raises.
|
|
91
|
+
Applications should not attempt to retain or release this internal buffer.
|
|
92
|
+
|
|
93
|
+
For repeated readbacks, pass a caller-owned `staging:` buffer created by the
|
|
94
|
+
same device. It must be unmapped, have `:map_read` and `:copy_dst` usage, and be
|
|
95
|
+
large enough for the requested byte range (`read_buffer`) or padded copy
|
|
96
|
+
footprint (`read_texture`). The queue unmaps it after each read but never
|
|
97
|
+
releases it, so it can be reused and the caller remains responsible for its
|
|
98
|
+
eventual release. A device-owned queue uses its owning device automatically;
|
|
99
|
+
only queues constructed without an owner require the explicit `device:`
|
|
100
|
+
keyword.
|
|
101
|
+
|
|
102
|
+
## Leak diagnostics
|
|
103
|
+
|
|
104
|
+
Set `WGPU_DEBUG_LEAKS=1` before requiring the gem, or set
|
|
105
|
+
`WGPU.debug_leaks = true` before creating resources, to enable warnings.
|
|
106
|
+
Unreleased resources are reported with their wrapper class and label during GC
|
|
107
|
+
or process exit. The detector never releases a native handle; it is diagnostic
|
|
108
|
+
only and is disabled by default.
|
data/docs/shaders.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Shader modules and diagnostics
|
|
2
|
+
|
|
3
|
+
WGSL remains the default input:
|
|
4
|
+
|
|
5
|
+
```ruby
|
|
6
|
+
shader = device.create_shader_module(code: wgsl, label: "simulation")
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Pass `validate: true` to request compilation information immediately when the
|
|
10
|
+
loaded wgpu-native version implements that API. Error messages are raised as
|
|
11
|
+
`WGPU::ShaderError` with the shader label and line/column diagnostics. Without
|
|
12
|
+
this option, creation follows the original lazy validation behavior.
|
|
13
|
+
|
|
14
|
+
`ShaderModule#get_compilation_info` returns a Hash with `status:` and
|
|
15
|
+
`messages:`. Each message is a `WGPU::CompilationMessage` with `type`,
|
|
16
|
+
`message`, `line_num`/`line`, `line_pos`/`column`, `offset`, and `length`.
|
|
17
|
+
|
|
18
|
+
wgpu-native v27.0.4.0 exports the compilation-info function as an
|
|
19
|
+
unimplemented panic stub. wgpu-ruby detects that version and raises
|
|
20
|
+
`WGPU::ShaderError` before crossing the native boundary. This guard avoids a
|
|
21
|
+
process abort; diagnostics can be enabled when a future pinned wgpu-native
|
|
22
|
+
release implements the function.
|
|
23
|
+
|
|
24
|
+
wgpu-native's SPIR-V extension is available explicitly:
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
shader = device.create_shader_module(spirv: binary_bytes, label: "compute")
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
SPIR-V input must contain a whole number of 32-bit words. It is a
|
|
31
|
+
wgpu-native extension rather than portable WebGPU API and should be avoided
|
|
32
|
+
when browser portability matters.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Texture readback
|
|
2
|
+
|
|
3
|
+
`WGPU::TextureFormat` exposes WebGPU texel-block calculations:
|
|
4
|
+
|
|
5
|
+
```ruby
|
|
6
|
+
tight = WGPU::TextureFormat.bytes_per_row(width, :rgba8_unorm)
|
|
7
|
+
stride = WGPU::TextureFormat.aligned_bytes_per_row(width, :rgba8_unorm)
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
`block_size` returns the bytes in one texel block, while `block_dimensions`
|
|
11
|
+
returns its width and height. Compressed BC, ETC2/EAC, and ASTC formats use
|
|
12
|
+
their real block dimensions. Formats whose depth representation is
|
|
13
|
+
implementation-defined do not claim a portable copy footprint. Combined
|
|
14
|
+
depth/stencil copies require an explicit `aspect:`.
|
|
15
|
+
|
|
16
|
+
`Queue#read_texture` validates that `bytes_per_row` is a multiple of WebGPU's
|
|
17
|
+
256-byte copy alignment and large enough for the requested width and format.
|
|
18
|
+
Use `aligned_bytes_per_row` when the tight row is not already aligned. Automatic
|
|
19
|
+
padding and mipmap generation are intentionally outside this low-level binding.
|
|
20
|
+
|
|
21
|
+
For example, an `rgba8_unorm` row that is 65 pixels wide contains 260 bytes,
|
|
22
|
+
but its smallest valid readback stride is 512 bytes:
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
width = 65
|
|
26
|
+
height = 2
|
|
27
|
+
bytes_per_row = WGPU::TextureFormat.aligned_bytes_per_row(width, :rgba8_unorm)
|
|
28
|
+
# => 512
|
|
29
|
+
|
|
30
|
+
pixels = queue.read_texture(
|
|
31
|
+
source: { texture: texture },
|
|
32
|
+
data_layout: { bytes_per_row: bytes_per_row, rows_per_image: height },
|
|
33
|
+
size: { width: width, height: height }
|
|
34
|
+
)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The returned string includes row padding and has
|
|
38
|
+
`bytes_per_row * rows_per_image * depth_or_array_layers` bytes. Read only the
|
|
39
|
+
tight bytes of each row when processing pixels.
|
|
40
|
+
|
|
41
|
+
Both `Queue#read_texture` and `Queue#read_buffer` accept an optional `staging:`
|
|
42
|
+
buffer for repeated readbacks. It must be unmapped, belong to the same device,
|
|
43
|
+
be large enough for the full returned string, and include both `:map_read` and
|
|
44
|
+
`:copy_dst` usage. The helper unmaps caller-provided staging after each read but
|
|
45
|
+
does not release it, so the caller can reuse it and remains responsible for
|
|
46
|
+
releasing it. The queue uses its owning device when `device:` is omitted.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Troubleshooting
|
|
2
|
+
|
|
3
|
+
## Native library is not found
|
|
4
|
+
|
|
5
|
+
Run `bundle exec rake wgpu:install`. The error lists every searched cache path.
|
|
6
|
+
For a custom build, set `WGPU_LIB_PATH` to the shared library file, not its
|
|
7
|
+
directory. See [installation](installation.md).
|
|
8
|
+
|
|
9
|
+
## Download or checksum fails
|
|
10
|
+
|
|
11
|
+
The installer deletes an archive whose SHA-256 differs from the pinned release
|
|
12
|
+
metadata. Retry on a trusted network. Do not bypass the checksum; download the
|
|
13
|
+
matching artifact manually and set `WGPU_LIB_PATH` if GitHub Releases is
|
|
14
|
+
unavailable.
|
|
15
|
+
|
|
16
|
+
## No adapter is available
|
|
17
|
+
|
|
18
|
+
Update the platform GPU driver. On headless Linux, install Vulkan loader and
|
|
19
|
+
Mesa lavapipe, set `VK_ICD_FILENAMES` to the lavapipe ICD JSON, and inspect
|
|
20
|
+
`vulkaninfo --summary`. `WGPU_BACKEND=vulkan` can make backend selection
|
|
21
|
+
explicit.
|
|
22
|
+
|
|
23
|
+
## A synchronous operation hangs
|
|
24
|
+
|
|
25
|
+
Pass `timeout:` to adapter/device requests, `Buffer#map_sync`,
|
|
26
|
+
`Device#pop_error_scope`, or `Queue#on_submitted_work_done`. A timeout raises
|
|
27
|
+
`WGPU::TimeoutError`. In an event loop, regularly call
|
|
28
|
+
`Instance#process_events` or `Device#poll`.
|
|
29
|
+
|
|
30
|
+
For a native adapter wrapped with `Adapter.from_handle`, provide its
|
|
31
|
+
`instance:` before using `Device.request(timeout: ...)`. Timeout is rejected
|
|
32
|
+
without that instance because a late native completion cannot be cleaned up
|
|
33
|
+
safely.
|
|
34
|
+
|
|
35
|
+
## Texture readback rejects bytes_per_row
|
|
36
|
+
|
|
37
|
+
Buffer/texture copy rows must be 256-byte aligned. Calculate the value with
|
|
38
|
+
`WGPU::TextureFormat.aligned_bytes_per_row(width, format)` and account for
|
|
39
|
+
padding when decoding each row.
|
|
40
|
+
|
|
41
|
+
## SDL3 cannot be loaded
|
|
42
|
+
|
|
43
|
+
Compute code should only `require "wgpu"`. Rendering code must separately add
|
|
44
|
+
the `sdl3` Ruby gem, install the SDL3 system library, and then require
|
|
45
|
+
`wgpu/window`.
|
|
46
|
+
|
|
47
|
+
## Diagnosing validation and shader errors
|
|
48
|
+
|
|
49
|
+
Use `device.on_uncaptured_error`, scoped errors, and labels. Compilation-info
|
|
50
|
+
validation is guarded on pinned wgpu-native v27 because its exported function
|
|
51
|
+
is a panic stub; see [Shaders and diagnostics](shaders.md). Set
|
|
52
|
+
`WGPU_DEBUG_LEAKS=1` in development to warn about wrappers that reach GC or
|
|
53
|
+
process exit without `release`.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Upgrading wgpu-native
|
|
2
|
+
|
|
3
|
+
Use this checklist for each pinned wgpu-native update.
|
|
4
|
+
|
|
5
|
+
1. Update only `WGPU::Native::Distribution::VERSION`.
|
|
6
|
+
2. Obtain the five release artifact names and SHA-256 digests from the official
|
|
7
|
+
wgpu-native GitHub release. Update `ARTIFACTS`.
|
|
8
|
+
3. Run `rg` for the old version and confirm no second version definition
|
|
9
|
+
remains.
|
|
10
|
+
4. Run `bundle exec rake wgpu:clean wgpu:install` on a disposable cache, then
|
|
11
|
+
test an existing legacy cache separately.
|
|
12
|
+
5. Regenerate the repository enum fixture from the installed release header:
|
|
13
|
+
|
|
14
|
+
```console
|
|
15
|
+
bundle exec ruby script/update_abi_fixture.rb \
|
|
16
|
+
"$WGPU_CACHE_DIR/<version>/include/webgpu/webgpu.h"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The generated comment records the source SHA-256. For the current
|
|
20
|
+
`v27.0.4.0` release, `include/webgpu/webgpu.h` is
|
|
21
|
+
`a6fccf7f9f2fa674d1adfe4f6ea89784a876395b2307bfc2b06f2e77cf6cf356`.
|
|
22
|
+
Review every enum addition, removal, and value change, then run
|
|
23
|
+
`bundle exec rake wgpu:verify_abi`.
|
|
24
|
+
6. Compare structs, field offsets, callback signatures, and exported functions.
|
|
25
|
+
Add ABI specs for changed layouts. Check `include/webgpu/wgpu.h` for native
|
|
26
|
+
extensions as well. `wgpuGetVersion` encodes `vMAJOR.MINOR.PATCH.BUILD` in
|
|
27
|
+
four bytes; the verifier compares it with `Distribution::VERSION`. Mark
|
|
28
|
+
functions optional only when the Ruby API has a valid capability fallback.
|
|
29
|
+
7. Run `bundle exec rspec --tag '~gpu'`, RuboCop, RBS validation, and YARD.
|
|
30
|
+
8. Run every `:gpu` spec and `rake examples:ci` with lavapipe. Run rendering
|
|
31
|
+
examples on at least one real surface backend.
|
|
32
|
+
9. Build the gem and install it into a clean environment without SDL3.
|
|
33
|
+
10. Record ABI/API changes, platform artifacts, and migration notes in
|
|
34
|
+
`CHANGELOG.md`.
|
|
35
|
+
|
|
36
|
+
For a dry run, use a temporary `WGPU_CACHE_DIR` and restore the version/artifact
|
|
37
|
+
edits afterward. Never replace the shared user cache as part of verification.
|
data/ext/wgpu/extconf.rb
CHANGED
|
@@ -1,151 +1,19 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
require "uri"
|
|
3
|
+
lib_dir = File.expand_path("../../lib", __dir__)
|
|
4
|
+
$LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
GITHUB_RELEASE_URL = "https://github.com/gfx-rs/wgpu-native/releases/download/#{WGPU_VERSION}"
|
|
6
|
+
require "wgpu/native/installer"
|
|
9
7
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
/arm64-darwin/ => { file: "wgpu-macos-aarch64-release.zip", lib: "libwgpu_native.dylib" },
|
|
15
|
-
/mingw|mswin/ => { file: "wgpu-windows-x86_64-msvc-release.zip", lib: "wgpu_native.dll" }
|
|
16
|
-
}.freeze
|
|
17
|
-
|
|
18
|
-
def detect_platform
|
|
19
|
-
PLATFORM_MAP.each do |pattern, info|
|
|
20
|
-
return info if RUBY_PLATFORM =~ pattern
|
|
21
|
-
end
|
|
22
|
-
abort "Unsupported platform: #{RUBY_PLATFORM}\nSupported: #{PLATFORM_MAP.keys.map(&:inspect).join(", ")}"
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def cache_dir
|
|
26
|
-
File.join(Dir.home, ".cache", "wgpu-ruby", WGPU_VERSION)
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def windows?
|
|
30
|
-
RUBY_PLATFORM =~ /mingw|mswin/
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def curl_available?
|
|
34
|
-
if windows?
|
|
35
|
-
system("where curl >nul 2>&1")
|
|
8
|
+
begin
|
|
9
|
+
installer = WGPU::Native::Installer.new
|
|
10
|
+
if ENV["WGPU_LIB_PATH"]
|
|
11
|
+
puts "Using custom wgpu-native from WGPU_LIB_PATH: #{installer.install}"
|
|
36
12
|
else
|
|
37
|
-
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def download_with_curl(url, dest)
|
|
42
|
-
system("curl", "-fsSL", "-o", dest, url)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def download_with_ruby(url, dest, redirects = 5)
|
|
46
|
-
raise "Too many redirects" if redirects == 0
|
|
47
|
-
|
|
48
|
-
uri = URI.parse(url)
|
|
49
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
|
50
|
-
http.use_ssl = (uri.scheme == "https")
|
|
51
|
-
http.open_timeout = 10
|
|
52
|
-
http.read_timeout = 120
|
|
53
|
-
|
|
54
|
-
request = Net::HTTP::Get.new(uri.request_uri)
|
|
55
|
-
|
|
56
|
-
http.request(request) do |response|
|
|
57
|
-
case response
|
|
58
|
-
when Net::HTTPRedirection
|
|
59
|
-
download_with_ruby(response["location"], dest, redirects - 1)
|
|
60
|
-
when Net::HTTPSuccess
|
|
61
|
-
File.open(dest, "wb") do |file|
|
|
62
|
-
response.read_body { |chunk| file.write(chunk) }
|
|
63
|
-
end
|
|
64
|
-
else
|
|
65
|
-
return false
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
true
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
def download_file(url, dest)
|
|
72
|
-
return if curl_available? && download_with_curl(url, dest)
|
|
73
|
-
return if download_with_ruby(url, dest)
|
|
74
|
-
|
|
75
|
-
abort "Download failed: #{url}"
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def extract_with_rubyzip(zip_path, dest_dir)
|
|
79
|
-
require "zip"
|
|
80
|
-
Zip::File.open(zip_path) do |zip_file|
|
|
81
|
-
zip_file.each do |entry|
|
|
82
|
-
dest_path = File.join(dest_dir, entry.name)
|
|
83
|
-
FileUtils.mkdir_p(File.dirname(dest_path))
|
|
84
|
-
entry.extract(dest_path) { true }
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
true
|
|
88
|
-
rescue LoadError
|
|
89
|
-
false
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
def extract_with_powershell(zip_path, dest_dir)
|
|
93
|
-
return false unless windows?
|
|
94
|
-
|
|
95
|
-
system("powershell", "-Command", "Expand-Archive -Force -Path '#{zip_path}' -DestinationPath '#{dest_dir}'")
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
def extract_with_unzip(zip_path, dest_dir)
|
|
99
|
-
system("unzip", "-o", "-q", zip_path, "-d", dest_dir)
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def extract_zip(zip_path, dest_dir)
|
|
103
|
-
return if extract_with_rubyzip(zip_path, dest_dir)
|
|
104
|
-
return if windows? && extract_with_powershell(zip_path, dest_dir)
|
|
105
|
-
return if extract_with_unzip(zip_path, dest_dir)
|
|
106
|
-
|
|
107
|
-
abort "Failed to extract zip. Please install 'rubyzip' gem."
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def lib_dir
|
|
111
|
-
File.join(cache_dir, "lib")
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
def download_wgpu_native
|
|
115
|
-
platform = detect_platform
|
|
116
|
-
lib_path = File.join(lib_dir, platform[:lib])
|
|
117
|
-
|
|
118
|
-
if File.exist?(lib_path)
|
|
119
|
-
puts "wgpu-native already cached at #{lib_path}"
|
|
120
|
-
return lib_path
|
|
13
|
+
installer.install
|
|
121
14
|
end
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
url = "#{GITHUB_RELEASE_URL}/#{platform[:file]}"
|
|
126
|
-
zip_path = File.join(cache_dir, platform[:file])
|
|
127
|
-
|
|
128
|
-
puts "Downloading wgpu-native #{WGPU_VERSION} from GitHub..."
|
|
129
|
-
puts " URL: #{url}"
|
|
130
|
-
download_file(url, zip_path)
|
|
131
|
-
|
|
132
|
-
puts "Extracting to #{cache_dir}..."
|
|
133
|
-
extract_zip(zip_path, cache_dir)
|
|
134
|
-
|
|
135
|
-
FileUtils.rm_f(zip_path)
|
|
136
|
-
|
|
137
|
-
unless File.exist?(lib_path)
|
|
138
|
-
abort "Failed to find #{platform[:lib]} after extraction"
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
puts "wgpu-native installed successfully!"
|
|
142
|
-
lib_path
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
if ENV["WGPU_LIB_PATH"]
|
|
146
|
-
puts "Using custom wgpu-native from WGPU_LIB_PATH: #{ENV["WGPU_LIB_PATH"]}"
|
|
147
|
-
else
|
|
148
|
-
download_wgpu_native
|
|
15
|
+
rescue WGPU::Native::InstallError => error
|
|
16
|
+
abort error.message
|
|
149
17
|
end
|
|
150
18
|
|
|
151
19
|
File.write("Makefile", <<~MAKEFILE)
|