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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +48 -0
- data/README.md +26 -3
- data/docs/README.md +22 -0
- data/docs/api_coverage.md +132 -0
- data/docs/async.md +31 -0
- data/docs/bind_groups.md +25 -0
- data/docs/buffer_data.md +37 -0
- data/docs/command_encoding.md +24 -0
- data/docs/errors.md +40 -0
- data/docs/getting_started_compute.md +61 -0
- data/docs/getting_started_rendering.md +62 -0
- data/docs/installation.md +94 -0
- data/docs/pipeline_descriptors.md +38 -0
- data/docs/releasing.md +22 -0
- data/docs/resource_lifetime.md +94 -0
- data/docs/shaders.md +32 -0
- data/docs/texture_readback.md +19 -0
- data/docs/troubleshooting.md +48 -0
- data/docs/upgrading_wgpu_native.md +26 -0
- data/ext/wgpu/extconf.rb +10 -142
- data/lib/wgpu/commands/command_buffer.rb +11 -0
- data/lib/wgpu/commands/command_encoder.rb +65 -8
- data/lib/wgpu/commands/compute_pass.rb +10 -0
- data/lib/wgpu/commands/render_bundle_encoder.rb +10 -3
- data/lib/wgpu/commands/render_pass.rb +34 -8
- data/lib/wgpu/core/adapter.rb +49 -20
- data/lib/wgpu/core/async_waiter.rb +92 -0
- data/lib/wgpu/core/canvas_context.rb +0 -2
- data/lib/wgpu/core/device.rb +160 -52
- data/lib/wgpu/core/instance.rb +9 -5
- data/lib/wgpu/core/queue.rb +81 -57
- data/lib/wgpu/core/surface.rb +16 -17
- data/lib/wgpu/data_types.rb +67 -0
- data/lib/wgpu/descriptor_helpers.rb +39 -0
- data/lib/wgpu/error.rb +55 -0
- data/lib/wgpu/logging.rb +63 -0
- data/lib/wgpu/native/abi_verifier.rb +109 -0
- data/lib/wgpu/native/callbacks.rb +9 -6
- data/lib/wgpu/native/capabilities.rb +31 -0
- data/lib/wgpu/native/distribution.rb +113 -0
- data/lib/wgpu/native/enum_helper.rb +63 -0
- data/lib/wgpu/native/enums.rb +82 -13
- data/lib/wgpu/native/functions.rb +17 -8
- data/lib/wgpu/native/installer.rb +192 -0
- data/lib/wgpu/native/loader.rb +40 -21
- data/lib/wgpu/native/structs.rb +19 -5
- data/lib/wgpu/native_resource.rb +171 -0
- data/lib/wgpu/pipeline/bind_group.rb +12 -0
- data/lib/wgpu/pipeline/bind_group_layout.rb +91 -37
- data/lib/wgpu/pipeline/compute_pipeline.rb +28 -26
- data/lib/wgpu/pipeline/render_pipeline.rb +180 -68
- data/lib/wgpu/pipeline/shader_module.rb +51 -12
- data/lib/wgpu/resources/buffer.rb +167 -84
- data/lib/wgpu/resources/query_set.rb +14 -12
- data/lib/wgpu/resources/sampler.rb +36 -20
- data/lib/wgpu/resources/texture.rb +44 -42
- data/lib/wgpu/resources/texture_view.rb +11 -3
- data/lib/wgpu/texture_format.rb +82 -0
- data/lib/wgpu/version.rb +1 -1
- data/lib/wgpu/window.rb +8 -1
- data/lib/wgpu.rb +33 -0
- data/sig/wgpu.rbs +381 -0
- 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
|
-
|
|
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
|
-
|
|
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] =
|
|
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] =
|
|
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] =
|
|
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] =
|
|
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
|
|
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] =
|
|
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.
|
|
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.
|
|
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] =
|
|
196
|
-
|
|
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] =
|
|
200
|
-
|
|
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
|
|
data/lib/wgpu/core/adapter.rb
CHANGED
|
@@ -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
|
-
|
|
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,
|
|
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,
|
|
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] =
|
|
32
|
-
|
|
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] =
|
|
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] =
|
|
52
|
+
callback_info[:mode] = AsyncWaiter.callback_mode(instance: instance)
|
|
40
53
|
callback_info[:callback] = callback
|
|
41
|
-
callback_info[:
|
|
42
|
-
|
|
43
|
-
|
|
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(
|
|
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]
|