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.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +88 -0
  3. data/README.md +44 -5
  4. data/docs/README.md +22 -0
  5. data/docs/api_coverage.md +140 -0
  6. data/docs/async.md +41 -0
  7. data/docs/bind_groups.md +25 -0
  8. data/docs/buffer_data.md +37 -0
  9. data/docs/command_encoding.md +49 -0
  10. data/docs/errors.md +40 -0
  11. data/docs/getting_started_compute.md +62 -0
  12. data/docs/getting_started_rendering.md +62 -0
  13. data/docs/installation.md +94 -0
  14. data/docs/pipeline_descriptors.md +43 -0
  15. data/docs/releasing.md +32 -0
  16. data/docs/resource_lifetime.md +108 -0
  17. data/docs/shaders.md +32 -0
  18. data/docs/texture_readback.md +46 -0
  19. data/docs/troubleshooting.md +53 -0
  20. data/docs/upgrading_wgpu_native.md +37 -0
  21. data/ext/wgpu/extconf.rb +10 -142
  22. data/lib/wgpu/async_task.rb +19 -0
  23. data/lib/wgpu/commands/command_buffer.rb +25 -1
  24. data/lib/wgpu/commands/command_encoder.rb +123 -9
  25. data/lib/wgpu/commands/compute_pass.rb +53 -0
  26. data/lib/wgpu/commands/render_bundle.rb +9 -1
  27. data/lib/wgpu/commands/render_bundle_encoder.rb +65 -4
  28. data/lib/wgpu/commands/render_pass.rb +136 -8
  29. data/lib/wgpu/core/adapter.rb +123 -19
  30. data/lib/wgpu/core/async_waiter.rb +47 -4
  31. data/lib/wgpu/core/canvas_context.rb +32 -2
  32. data/lib/wgpu/core/device.rb +399 -53
  33. data/lib/wgpu/core/instance.rb +28 -4
  34. data/lib/wgpu/core/queue.rb +197 -51
  35. data/lib/wgpu/core/surface.rb +64 -17
  36. data/lib/wgpu/data_types.rb +83 -0
  37. data/lib/wgpu/descriptor_helpers.rb +104 -0
  38. data/lib/wgpu/error.rb +70 -0
  39. data/lib/wgpu/logging.rb +63 -0
  40. data/lib/wgpu/native/abi_verifier.rb +143 -0
  41. data/lib/wgpu/native/callbacks.rb +10 -1
  42. data/lib/wgpu/native/capabilities.rb +32 -2
  43. data/lib/wgpu/native/distribution.rb +176 -0
  44. data/lib/wgpu/native/enum_helper.rb +80 -0
  45. data/lib/wgpu/native/enums.rb +68 -8
  46. data/lib/wgpu/native/fixtures/webgpu-v27.0.4.0-enums.h +848 -0
  47. data/lib/wgpu/native/functions.rb +21 -10
  48. data/lib/wgpu/native/installer.rb +223 -0
  49. data/lib/wgpu/native/loader.rb +61 -21
  50. data/lib/wgpu/native/structs.rb +18 -1
  51. data/lib/wgpu/native_resource.rb +342 -0
  52. data/lib/wgpu/pipeline/bind_group.rb +21 -0
  53. data/lib/wgpu/pipeline/bind_group_layout.rb +108 -41
  54. data/lib/wgpu/pipeline/compute_pipeline.rb +40 -50
  55. data/lib/wgpu/pipeline/pipeline_layout.rb +8 -0
  56. data/lib/wgpu/pipeline/render_pipeline.rb +207 -110
  57. data/lib/wgpu/pipeline/shader_module.rb +95 -28
  58. data/lib/wgpu/resources/buffer.rb +387 -85
  59. data/lib/wgpu/resources/query_set.rb +26 -12
  60. data/lib/wgpu/resources/sampler.rb +43 -20
  61. data/lib/wgpu/resources/texture.rb +89 -48
  62. data/lib/wgpu/resources/texture_view.rb +35 -7
  63. data/lib/wgpu/texture_format.rb +96 -0
  64. data/lib/wgpu/version.rb +1 -1
  65. data/lib/wgpu/window.rb +34 -1
  66. data/lib/wgpu.rb +32 -0
  67. data/sig/wgpu.rbs +460 -0
  68. 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
- require "fileutils"
4
- require "net/http"
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
- WGPU_VERSION = "v27.0.4.0"
8
- GITHUB_RELEASE_URL = "https://github.com/gfx-rs/wgpu-native/releases/download/#{WGPU_VERSION}"
6
+ require "wgpu/native/installer"
9
7
 
10
- PLATFORM_MAP = {
11
- /x86_64-linux/ => { file: "wgpu-linux-x86_64-release.zip", lib: "libwgpu_native.so" },
12
- /aarch64-linux/ => { file: "wgpu-linux-aarch64-release.zip", lib: "libwgpu_native.so" },
13
- /x86_64-darwin/ => { file: "wgpu-macos-x86_64-release.zip", lib: "libwgpu_native.dylib" },
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
- system("which curl >/dev/null 2>&1")
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
- FileUtils.mkdir_p(cache_dir)
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)