wgpu 1.0.0 → 1.2.0

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 (64) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +48 -0
  3. data/README.md +26 -3
  4. data/docs/README.md +22 -0
  5. data/docs/api_coverage.md +132 -0
  6. data/docs/async.md +31 -0
  7. data/docs/bind_groups.md +25 -0
  8. data/docs/buffer_data.md +37 -0
  9. data/docs/command_encoding.md +24 -0
  10. data/docs/errors.md +40 -0
  11. data/docs/getting_started_compute.md +61 -0
  12. data/docs/getting_started_rendering.md +62 -0
  13. data/docs/installation.md +94 -0
  14. data/docs/pipeline_descriptors.md +38 -0
  15. data/docs/releasing.md +22 -0
  16. data/docs/resource_lifetime.md +94 -0
  17. data/docs/shaders.md +32 -0
  18. data/docs/texture_readback.md +19 -0
  19. data/docs/troubleshooting.md +48 -0
  20. data/docs/upgrading_wgpu_native.md +26 -0
  21. data/ext/wgpu/extconf.rb +10 -142
  22. data/lib/wgpu/commands/command_buffer.rb +11 -0
  23. data/lib/wgpu/commands/command_encoder.rb +65 -8
  24. data/lib/wgpu/commands/compute_pass.rb +10 -0
  25. data/lib/wgpu/commands/render_bundle_encoder.rb +10 -3
  26. data/lib/wgpu/commands/render_pass.rb +34 -8
  27. data/lib/wgpu/core/adapter.rb +49 -20
  28. data/lib/wgpu/core/async_waiter.rb +92 -0
  29. data/lib/wgpu/core/canvas_context.rb +0 -2
  30. data/lib/wgpu/core/device.rb +160 -52
  31. data/lib/wgpu/core/instance.rb +9 -5
  32. data/lib/wgpu/core/queue.rb +81 -57
  33. data/lib/wgpu/core/surface.rb +16 -17
  34. data/lib/wgpu/data_types.rb +67 -0
  35. data/lib/wgpu/descriptor_helpers.rb +39 -0
  36. data/lib/wgpu/error.rb +55 -0
  37. data/lib/wgpu/logging.rb +63 -0
  38. data/lib/wgpu/native/abi_verifier.rb +109 -0
  39. data/lib/wgpu/native/callbacks.rb +9 -6
  40. data/lib/wgpu/native/capabilities.rb +31 -0
  41. data/lib/wgpu/native/distribution.rb +113 -0
  42. data/lib/wgpu/native/enum_helper.rb +63 -0
  43. data/lib/wgpu/native/enums.rb +82 -13
  44. data/lib/wgpu/native/functions.rb +17 -8
  45. data/lib/wgpu/native/installer.rb +192 -0
  46. data/lib/wgpu/native/loader.rb +40 -21
  47. data/lib/wgpu/native/structs.rb +19 -5
  48. data/lib/wgpu/native_resource.rb +171 -0
  49. data/lib/wgpu/pipeline/bind_group.rb +12 -0
  50. data/lib/wgpu/pipeline/bind_group_layout.rb +91 -37
  51. data/lib/wgpu/pipeline/compute_pipeline.rb +28 -26
  52. data/lib/wgpu/pipeline/render_pipeline.rb +180 -68
  53. data/lib/wgpu/pipeline/shader_module.rb +51 -12
  54. data/lib/wgpu/resources/buffer.rb +167 -84
  55. data/lib/wgpu/resources/query_set.rb +14 -12
  56. data/lib/wgpu/resources/sampler.rb +36 -20
  57. data/lib/wgpu/resources/texture.rb +44 -42
  58. data/lib/wgpu/resources/texture_view.rb +11 -3
  59. data/lib/wgpu/texture_format.rb +82 -0
  60. data/lib/wgpu/version.rb +1 -1
  61. data/lib/wgpu/window.rb +8 -1
  62. data/lib/wgpu.rb +33 -0
  63. data/sig/wgpu.rbs +381 -0
  64. metadata +33 -16
@@ -7,6 +7,7 @@ module WGPU
7
7
  def initialize(device, label: nil)
8
8
  @device = device
9
9
  @finished = false
10
+ @active_pass = nil
10
11
 
11
12
  desc = Native::CommandEncoderDescriptor.new
12
13
  desc[:next_in_chain] = nil
@@ -24,13 +25,26 @@ module WGPU
24
25
  end
25
26
 
26
27
  def begin_compute_pass(label: nil, timestamp_writes: nil)
27
- raise CommandError, "Encoder already finished" if @finished
28
- ComputePass.new(self, label: label, timestamp_writes: timestamp_writes)
28
+ ensure_can_begin_pass!
29
+ pass = ComputePass.new(self, label: label, timestamp_writes: timestamp_writes)
30
+ @active_pass = pass
31
+ return pass unless block_given?
32
+
33
+ begin
34
+ yield pass
35
+ ensure
36
+ begin
37
+ pass.end_pass unless pass.ended?
38
+ ensure
39
+ pass.release
40
+ pass_ended(pass)
41
+ end
42
+ end
29
43
  end
30
44
 
31
45
  def begin_render_pass(color_attachments:, depth_stencil_attachment: nil, occlusion_query_set: nil, timestamp_writes: nil, max_draw_count: nil, label: nil)
32
- raise CommandError, "Encoder already finished" if @finished
33
- RenderPass.new(self,
46
+ ensure_can_begin_pass!
47
+ pass = RenderPass.new(self,
34
48
  color_attachments: color_attachments,
35
49
  depth_stencil_attachment: depth_stencil_attachment,
36
50
  occlusion_query_set: occlusion_query_set,
@@ -38,6 +52,19 @@ module WGPU
38
52
  max_draw_count: max_draw_count,
39
53
  label: label
40
54
  )
55
+ @active_pass = pass
56
+ return pass unless block_given?
57
+
58
+ begin
59
+ yield pass
60
+ ensure
61
+ begin
62
+ pass.end_pass unless pass.ended?
63
+ ensure
64
+ pass.release
65
+ pass_ended(pass)
66
+ end
67
+ end
41
68
  end
42
69
 
43
70
  def copy_buffer_to_buffer(source:, source_offset: 0, destination:, destination_offset: 0, size:)
@@ -70,7 +97,11 @@ module WGPU
70
97
  dst[:origin][:x] = destination.dig(:origin, :x) || 0
71
98
  dst[:origin][:y] = destination.dig(:origin, :y) || 0
72
99
  dst[:origin][:z] = destination.dig(:origin, :z) || 0
73
- dst[:aspect] = destination[:aspect] || :all
100
+ dst[:aspect] = Native::EnumHelper.coerce(
101
+ Native::TextureAspect,
102
+ destination[:aspect] || :all,
103
+ name: "texture aspect"
104
+ )
74
105
 
75
106
  Native.wgpuCommandEncoderCopyBufferToTexture(@handle, src, dst, size)
76
107
  end
@@ -89,7 +120,11 @@ module WGPU
89
120
  src[:origin][:x] = source.dig(:origin, :x) || 0
90
121
  src[:origin][:y] = source.dig(:origin, :y) || 0
91
122
  src[:origin][:z] = source.dig(:origin, :z) || 0
92
- src[:aspect] = source[:aspect] || :all
123
+ src[:aspect] = Native::EnumHelper.coerce(
124
+ Native::TextureAspect,
125
+ source[:aspect] || :all,
126
+ name: "texture aspect"
127
+ )
93
128
 
94
129
  dst = Native::ImageCopyBuffer.new
95
130
  dst[:layout][:offset] = destination[:offset] || 0
@@ -109,7 +144,11 @@ module WGPU
109
144
  src[:origin][:x] = source.dig(:origin, :x) || 0
110
145
  src[:origin][:y] = source.dig(:origin, :y) || 0
111
146
  src[:origin][:z] = source.dig(:origin, :z) || 0
112
- src[:aspect] = source[:aspect] || :all
147
+ src[:aspect] = Native::EnumHelper.coerce(
148
+ Native::TextureAspect,
149
+ source[:aspect] || :all,
150
+ name: "source texture aspect"
151
+ )
113
152
 
114
153
  dst = Native::ImageCopyTexture.new
115
154
  dst[:texture] = destination[:texture].handle
@@ -117,7 +156,11 @@ module WGPU
117
156
  dst[:origin][:x] = destination.dig(:origin, :x) || 0
118
157
  dst[:origin][:y] = destination.dig(:origin, :y) || 0
119
158
  dst[:origin][:z] = destination.dig(:origin, :z) || 0
120
- dst[:aspect] = destination[:aspect] || :all
159
+ dst[:aspect] = Native::EnumHelper.coerce(
160
+ Native::TextureAspect,
161
+ destination[:aspect] || :all,
162
+ name: "destination texture aspect"
163
+ )
121
164
 
122
165
  size = Native::Extent3D.new
123
166
  size[:width] = copy_size[:width] || copy_size[0]
@@ -175,6 +218,9 @@ module WGPU
175
218
 
176
219
  def finish(label: nil)
177
220
  raise CommandError, "Encoder already finished" if @finished
221
+ if @active_pass && !@active_pass.ended?
222
+ raise CommandError, "Cannot finish command encoder while a pass is active"
223
+ end
178
224
  @finished = true
179
225
 
180
226
  desc = nil
@@ -197,5 +243,16 @@ module WGPU
197
243
  Native.wgpuCommandEncoderRelease(@handle)
198
244
  @handle = FFI::Pointer::NULL
199
245
  end
246
+
247
+ private
248
+
249
+ def ensure_can_begin_pass!
250
+ raise CommandError, "Encoder already finished" if @finished
251
+ raise CommandError, "A command pass is already active" if @active_pass && !@active_pass.ended?
252
+ end
253
+
254
+ def pass_ended(pass)
255
+ @active_pass = nil if @active_pass.equal?(pass)
256
+ end
200
257
  end
201
258
  end
@@ -5,6 +5,8 @@ module WGPU
5
5
  attr_reader :handle
6
6
 
7
7
  def initialize(encoder, label: nil, timestamp_writes: nil)
8
+ @encoder = encoder
9
+ @ended = false
8
10
  desc = Native::ComputePassDescriptor.new
9
11
  desc[:next_in_chain] = nil
10
12
  if label
@@ -73,13 +75,21 @@ module WGPU
73
75
  end
74
76
 
75
77
  def end_pass
78
+ return if @ended
79
+
76
80
  Native.wgpuComputePassEncoderEnd(@handle)
81
+ @ended = true
82
+ @encoder.send(:pass_ended, self)
77
83
  end
78
84
 
79
85
  def end
80
86
  end_pass
81
87
  end
82
88
 
89
+ def ended?
90
+ @ended
91
+ end
92
+
83
93
  def release
84
94
  return if @handle.null?
85
95
  Native.wgpuComputePassEncoderRelease(@handle)
@@ -21,13 +21,19 @@ module WGPU
21
21
  desc[:label][:length] = 0
22
22
  end
23
23
 
24
- formats = Array(color_formats).map { |f| Native::TextureFormat[f] }
24
+ formats = Array(color_formats).map do |format|
25
+ Native::EnumHelper.coerce(Native::TextureFormat, format, name: "color format")
26
+ end
25
27
  @formats_ptr = FFI::MemoryPointer.new(:uint32, formats.size)
26
28
  @formats_ptr.write_array_of_uint32(formats)
27
29
  desc[:color_format_count] = formats.size
28
30
  desc[:color_formats] = @formats_ptr
29
31
 
30
- desc[:depth_stencil_format] = depth_stencil_format || :undefined
32
+ desc[:depth_stencil_format] = Native::EnumHelper.coerce(
33
+ Native::TextureFormat,
34
+ depth_stencil_format || :undefined,
35
+ name: "depth stencil format"
36
+ )
31
37
  desc[:sample_count] = sample_count
32
38
  desc[:depth_read_only] = depth_read_only ? 1 : 0
33
39
  desc[:stencil_read_only] = stencil_read_only ? 1 : 0
@@ -65,7 +71,8 @@ module WGPU
65
71
  raise RenderBundleError, "Encoder already finished" if @finished
66
72
 
67
73
  size ||= buffer.size - offset
68
- Native.wgpuRenderBundleEncoderSetIndexBuffer(@handle, buffer.handle, format, offset, size)
74
+ format_value = Native::EnumHelper.coerce(Native::IndexFormat, format, name: "index format")
75
+ Native.wgpuRenderBundleEncoderSetIndexBuffer(@handle, buffer.handle, format_value, offset, size)
69
76
  end
70
77
 
71
78
  def draw(vertex_count, instance_count: 1, first_vertex: 0, first_instance: 0)
@@ -8,6 +8,7 @@ module WGPU
8
8
  @encoder = encoder
9
9
  @pointers = []
10
10
  @max_draw_count = max_draw_count
11
+ @ended = false
11
12
 
12
13
  desc = Native::RenderPassDescriptor.new
13
14
  desc[:next_in_chain] = nil
@@ -61,7 +62,8 @@ module WGPU
61
62
 
62
63
  def set_index_buffer(buffer, format, offset: 0, size: nil)
63
64
  size ||= buffer.size - offset
64
- Native.wgpuRenderPassEncoderSetIndexBuffer(@handle, buffer.handle, format, offset, size)
65
+ format_value = Native::EnumHelper.coerce(Native::IndexFormat, format, name: "index format")
66
+ Native.wgpuRenderPassEncoderSetIndexBuffer(@handle, buffer.handle, format_value, offset, size)
65
67
  end
66
68
 
67
69
  def draw(vertex_count, instance_count: 1, first_vertex: 0, first_instance: 0)
@@ -137,13 +139,21 @@ module WGPU
137
139
  end
138
140
 
139
141
  def end_pass
142
+ return if @ended
143
+
140
144
  Native.wgpuRenderPassEncoderEnd(@handle)
145
+ @ended = true
146
+ @encoder.send(:pass_ended, self)
141
147
  end
142
148
 
143
149
  def end
144
150
  end_pass
145
151
  end
146
152
 
153
+ def ended?
154
+ @ended
155
+ end
156
+
147
157
  def release
148
158
  return if @handle.null?
149
159
  Native.wgpuRenderPassEncoderRelease(@handle)
@@ -169,13 +179,13 @@ module WGPU
169
179
  @pointers << ptr
170
180
 
171
181
  attachments.each_with_index do |att, i|
172
- ca = Native::RenderPassColorAttachment.new(ptr + i * Native::RenderPassColorAttachment.size)
182
+ ca = Native::RenderPassColorAttachment.new(ptr + (i * Native::RenderPassColorAttachment.size))
173
183
  ca[:next_in_chain] = nil
174
184
  ca[:view] = att[:view].handle
175
185
  ca[:depth_slice] = att[:depth_slice] || 0xFFFFFFFF
176
186
  ca[:resolve_target] = att[:resolve_target]&.handle
177
- ca[:load_op] = att[:load_op] || :clear
178
- ca[:store_op] = att[:store_op] || :store
187
+ ca[:load_op] = Native::EnumHelper.coerce(Native::LoadOp, att[:load_op] || :clear, name: "load op")
188
+ ca[:store_op] = Native::EnumHelper.coerce(Native::StoreOp, att[:store_op] || :store, name: "store op")
179
189
 
180
190
  clear = att[:clear_value] || { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }
181
191
  ca[:clear_value][:r] = clear[:r] || 0.0
@@ -192,12 +202,28 @@ module WGPU
192
202
  @pointers << ds
193
203
 
194
204
  ds[:view] = att[:view].handle
195
- ds[:depth_load_op] = att[:depth_load_op] || :clear
196
- ds[:depth_store_op] = att[:depth_store_op] || :store
205
+ ds[:depth_load_op] = Native::EnumHelper.coerce(
206
+ Native::LoadOp,
207
+ att[:depth_load_op] || :clear,
208
+ name: "depth load op"
209
+ )
210
+ ds[:depth_store_op] = Native::EnumHelper.coerce(
211
+ Native::StoreOp,
212
+ att[:depth_store_op] || :store,
213
+ name: "depth store op"
214
+ )
197
215
  ds[:depth_clear_value] = att[:depth_clear_value] || 1.0
198
216
  ds[:depth_read_only] = att[:depth_read_only] ? 1 : 0
199
- ds[:stencil_load_op] = att[:stencil_load_op] || :clear
200
- ds[:stencil_store_op] = att[:stencil_store_op] || :store
217
+ ds[:stencil_load_op] = Native::EnumHelper.coerce(
218
+ Native::LoadOp,
219
+ att[:stencil_load_op] || :clear,
220
+ name: "stencil load op"
221
+ )
222
+ ds[:stencil_store_op] = Native::EnumHelper.coerce(
223
+ Native::StoreOp,
224
+ att[:stencil_store_op] || :store,
225
+ name: "stencil store op"
226
+ )
201
227
  ds[:stencil_clear_value] = att[:stencil_clear_value] || 0
202
228
  ds[:stencil_read_only] = att[:stencil_read_only] ? 1 : 0
203
229
 
@@ -2,45 +2,66 @@
2
2
 
3
3
  module WGPU
4
4
  class Adapter
5
- attr_reader :handle
5
+ attr_reader :handle, :instance
6
6
 
7
- CALLBACK_MODE_WAIT_ANY_ONLY = 1
8
-
9
- def self.from_handle(handle)
7
+ def self.from_handle(handle, instance: nil)
10
8
  adapter = allocate
11
9
  adapter.instance_variable_set(:@handle, handle)
10
+ adapter.instance_variable_set(:@instance, instance)
12
11
  adapter
13
12
  end
14
13
 
15
- def self.request(instance, power_preference: :high_performance, backend: nil, feature_level: :core, force_fallback_adapter: false, compatible_surface: nil)
14
+ def self.request(instance, power_preference: :high_performance, backend: nil, feature_level: :core,
15
+ force_fallback_adapter: false, compatible_surface: nil, timeout: nil)
16
16
  adapter_ptr = FFI::MemoryPointer.new(:pointer)
17
17
  status_holder = { value: nil, message: nil }
18
18
 
19
19
  callback = FFI::Function.new(
20
- :void, [:uint32, :pointer, Native::StringView.by_value, :pointer]
21
- ) do |status, adapter, message, _userdata|
20
+ :void, [:uint32, :pointer, Native::StringView.by_value, :pointer, :pointer]
21
+ ) do |status, adapter, message, _userdata1, _userdata2|
22
22
  status_holder[:value] = Native::RequestAdapterStatus[status]
23
23
  if message[:data] && !message[:data].null? && message[:length] > 0
24
24
  status_holder[:message] = message[:data].read_string(message[:length])
25
25
  end
26
26
  adapter_ptr.write_pointer(adapter)
27
+ status_holder[:done] = true
27
28
  end
28
29
 
29
30
  options = Native::RequestAdapterOptions.new
30
31
  options[:next_in_chain] = nil
31
- options[:feature_level] = feature_level
32
- options[:power_preference] = power_preference
32
+ options[:feature_level] = Native::EnumHelper.coerce(
33
+ Native::FeatureLevel,
34
+ feature_level,
35
+ name: "feature level"
36
+ )
37
+ options[:power_preference] = Native::EnumHelper.coerce(
38
+ Native::PowerPreference,
39
+ power_preference,
40
+ name: "power preference"
41
+ )
33
42
  options[:force_fallback_adapter] = force_fallback_adapter ? 1 : 0
34
- options[:backend_type] = backend || :undefined
43
+ options[:backend_type] = Native::EnumHelper.coerce(
44
+ Native::BackendType,
45
+ backend || :undefined,
46
+ name: "backend type"
47
+ )
35
48
  options[:compatible_surface] = compatible_surface&.handle
36
49
 
37
50
  callback_info = Native::RequestAdapterCallbackInfo.new
38
51
  callback_info[:next_in_chain] = nil
39
- callback_info[:mode] = CALLBACK_MODE_WAIT_ANY_ONLY
52
+ callback_info[:mode] = AsyncWaiter.callback_mode(instance: instance)
40
53
  callback_info[:callback] = callback
41
- callback_info[:userdata] = nil
42
-
43
- Native.wgpuInstanceRequestAdapter(instance.handle, options, callback_info)
54
+ callback_info[:userdata1] = nil
55
+ callback_info[:userdata2] = nil
56
+
57
+ callback_token = CallbackKeepalive.retain(instance, callback)
58
+ begin
59
+ status_holder[:done] = false
60
+ future = Native.wgpuInstanceRequestAdapter(instance.handle, options, callback_info)
61
+ AsyncWaiter.wait(status_holder: status_holder, instance: instance, future: future, timeout: timeout)
62
+ ensure
63
+ CallbackKeepalive.release(instance, callback_token)
64
+ end
44
65
 
45
66
  handle = adapter_ptr.read_pointer
46
67
  if handle.null? || status_holder[:value] != :success
@@ -48,23 +69,31 @@ module WGPU
48
69
  raise AdapterError, "Failed to request adapter: #{msg}"
49
70
  end
50
71
 
51
- new(handle)
72
+ new(handle, instance: instance)
52
73
  end
53
74
 
54
- def initialize(handle)
75
+ def initialize(handle, instance: nil)
55
76
  @handle = handle
77
+ @instance = instance
56
78
  end
57
79
 
58
- def request_device(label: nil, required_features: [], required_limits: nil)
59
- Device.request(self, label: label, required_features: required_features, required_limits: required_limits)
80
+ def request_device(label: nil, required_features: [], required_limits: nil, timeout: nil)
81
+ Device.request(
82
+ self,
83
+ label: label,
84
+ required_features: required_features,
85
+ required_limits: required_limits,
86
+ timeout: timeout
87
+ )
60
88
  end
61
89
 
62
- def request_device_async(label: nil, required_features: [], required_limits: nil)
90
+ def request_device_async(label: nil, required_features: [], required_limits: nil, timeout: nil)
63
91
  AsyncTask.new do
64
92
  request_device(
65
93
  label: label,
66
94
  required_features: required_features,
67
- required_limits: required_limits
95
+ required_limits: required_limits,
96
+ timeout: timeout
68
97
  )
69
98
  end
70
99
  end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WGPU
4
+ module AsyncWaiter
5
+ POLL_INTERVAL_SECONDS = 0.001
6
+ CALLBACK_MODE_FALLBACK = {
7
+ allow_process_events: 2,
8
+ allow_spontaneous: 3
9
+ }.freeze
10
+
11
+ module_function
12
+
13
+ def poll_interval
14
+ @poll_interval ||= POLL_INTERVAL_SECONDS
15
+ end
16
+
17
+ def poll_interval=(seconds)
18
+ value = Float(seconds)
19
+ raise ArgumentError, "poll interval must be positive" unless value.positive?
20
+
21
+ @poll_interval = value
22
+ end
23
+
24
+ def callback_mode(instance:)
25
+ if instance
26
+ callback_mode_value(:allow_process_events)
27
+ else
28
+ callback_mode_value(:allow_spontaneous)
29
+ end
30
+ end
31
+
32
+ def wait(status_holder:, instance: nil, device: nil, future: nil, timeout: nil)
33
+ timeout = Float(timeout) if timeout
34
+ raise ArgumentError, "timeout must be non-negative" if timeout&.negative?
35
+
36
+ deadline = monotonic_time + timeout if timeout
37
+ wait_info = build_wait_info(future) if instance && Native.future_api?
38
+
39
+ until status_holder[:done]
40
+ raise_timeout!(timeout) if deadline && monotonic_time >= deadline
41
+
42
+ waited = false
43
+ if instance && wait_info
44
+ waited = wait_with_wait_any(instance, wait_info)
45
+ elsif instance
46
+ instance.process_events
47
+ elsif device && Native.device_poll_available?
48
+ Native.wgpuDevicePoll(device.handle, 0, nil)
49
+ end
50
+ sleep(poll_interval) unless status_holder[:done] || waited
51
+ end
52
+ end
53
+
54
+ def callback_mode_value(name)
55
+ Native::CallbackMode[name] || CALLBACK_MODE_FALLBACK.fetch(name)
56
+ end
57
+ private_class_method :callback_mode_value
58
+
59
+ def build_wait_info(future)
60
+ return nil unless future.respond_to?(:[])
61
+
62
+ id = future[:id].to_i
63
+ return nil if id.zero?
64
+
65
+ wait_info = Native::FutureWaitInfo.new
66
+ wait_info[:future] = future
67
+ wait_info[:completed] = 0
68
+ wait_info
69
+ rescue StandardError
70
+ nil
71
+ end
72
+ private_class_method :build_wait_info
73
+
74
+ def wait_with_wait_any(instance, wait_info)
75
+ status = Native.wgpuInstanceWaitAny(instance.handle, 1, wait_info.to_ptr, 0)
76
+ return false if [:success, :timed_out].include?(status)
77
+
78
+ raise Error, "wgpuInstanceWaitAny failed: #{status.inspect}"
79
+ end
80
+ private_class_method :wait_with_wait_any
81
+
82
+ def monotonic_time
83
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
84
+ end
85
+ private_class_method :monotonic_time
86
+
87
+ def raise_timeout!(timeout)
88
+ raise TimeoutError, "GPU operation timed out after #{timeout} seconds"
89
+ end
90
+ private_class_method :raise_timeout!
91
+ end
92
+ end
@@ -29,8 +29,6 @@ module WGPU
29
29
 
30
30
  def configure(device:, format: nil, usage: :render_attachment, view_formats: [], color_space: "srgb", tone_mapping: nil, alpha_mode: :opaque, width: nil, height: nil, present_mode: :fifo)
31
31
  ensure_surface
32
- color_space # reserved for API parity
33
- tone_mapping # reserved for API parity
34
32
 
35
33
  width = width || @physical_size[0]
36
34
  height = height || @physical_size[1]