wgpu 0.1.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.
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WGPU
4
+ class Adapter
5
+ attr_reader :handle
6
+
7
+ CALLBACK_MODE_WAIT_ANY_ONLY = 1
8
+
9
+ def self.from_handle(handle)
10
+ adapter = allocate
11
+ adapter.instance_variable_set(:@handle, handle)
12
+ adapter
13
+ end
14
+
15
+ def self.request(instance, power_preference: :high_performance, backend: nil)
16
+ adapter_ptr = FFI::MemoryPointer.new(:pointer)
17
+ status_holder = { value: nil, message: nil }
18
+
19
+ callback = FFI::Function.new(
20
+ :void, [:uint32, :pointer, Native::StringView.by_value, :pointer]
21
+ ) do |status, adapter, message, _userdata|
22
+ status_holder[:value] = Native::RequestAdapterStatus[status]
23
+ if message[:data] && !message[:data].null? && message[:length] > 0
24
+ status_holder[:message] = message[:data].read_string(message[:length])
25
+ end
26
+ adapter_ptr.write_pointer(adapter)
27
+ end
28
+
29
+ options = Native::RequestAdapterOptions.new
30
+ options[:next_in_chain] = nil
31
+ options[:feature_level] = :core
32
+ options[:power_preference] = power_preference
33
+ options[:force_fallback_adapter] = 0
34
+ options[:backend_type] = backend || :undefined
35
+ options[:compatible_surface] = nil
36
+
37
+ callback_info = Native::RequestAdapterCallbackInfo.new
38
+ callback_info[:next_in_chain] = nil
39
+ callback_info[:mode] = CALLBACK_MODE_WAIT_ANY_ONLY
40
+ callback_info[:callback] = callback
41
+ callback_info[:userdata] = nil
42
+
43
+ Native.wgpuInstanceRequestAdapter(instance.handle, options, callback_info)
44
+
45
+ handle = adapter_ptr.read_pointer
46
+ if handle.null? || status_holder[:value] != :success
47
+ msg = status_holder[:message] || "Unknown error"
48
+ raise AdapterError, "Failed to request adapter: #{msg}"
49
+ end
50
+
51
+ new(handle)
52
+ end
53
+
54
+ def initialize(handle)
55
+ @handle = handle
56
+ end
57
+
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)
60
+ end
61
+
62
+ def info
63
+ info_struct = Native::AdapterInfo.new
64
+ Native.wgpuAdapterGetInfo(@handle, info_struct)
65
+
66
+ result = {
67
+ vendor: string_view_to_string(info_struct[:vendor]),
68
+ architecture: string_view_to_string(info_struct[:architecture]),
69
+ device: string_view_to_string(info_struct[:device]),
70
+ description: string_view_to_string(info_struct[:description]),
71
+ backend_type: info_struct[:backend_type],
72
+ adapter_type: info_struct[:adapter_type],
73
+ vendor_id: info_struct[:vendor_id],
74
+ device_id: info_struct[:device_id]
75
+ }
76
+
77
+ Native.wgpuAdapterInfoFreeMembers(info_struct)
78
+ result
79
+ end
80
+
81
+ def name
82
+ info[:device]
83
+ end
84
+
85
+ def vendor
86
+ info[:vendor]
87
+ end
88
+
89
+ def backend_type
90
+ info[:backend_type]
91
+ end
92
+
93
+ def adapter_type
94
+ info[:adapter_type]
95
+ end
96
+
97
+ def features
98
+ supported = Native::SupportedFeatures.new
99
+ Native.wgpuAdapterGetFeatures(@handle, supported)
100
+
101
+ result = []
102
+ if supported[:feature_count] > 0 && !supported[:features].null?
103
+ supported[:features].read_array_of_uint32(supported[:feature_count]).each do |f|
104
+ result << Native::FeatureName[f]
105
+ end
106
+ end
107
+ result
108
+ end
109
+
110
+ def has_feature?(feature)
111
+ features.include?(feature)
112
+ end
113
+
114
+ def limits
115
+ supported = Native::SupportedLimits.new
116
+ supported[:next_in_chain] = nil
117
+ Native.wgpuAdapterGetLimits(@handle, supported)
118
+ limits_to_hash(supported[:limits])
119
+ end
120
+
121
+ def release
122
+ return if @handle.null?
123
+ Native.wgpuAdapterRelease(@handle)
124
+ @handle = FFI::Pointer::NULL
125
+ end
126
+
127
+ private
128
+
129
+ def string_view_to_string(string_view)
130
+ return "" if string_view[:data].null? || string_view[:length] == 0
131
+ string_view[:data].read_string(string_view[:length])
132
+ end
133
+
134
+ def limits_to_hash(limits)
135
+ {
136
+ max_texture_dimension_1d: limits[:max_texture_dimension_1d],
137
+ max_texture_dimension_2d: limits[:max_texture_dimension_2d],
138
+ max_texture_dimension_3d: limits[:max_texture_dimension_3d],
139
+ max_texture_array_layers: limits[:max_texture_array_layers],
140
+ max_bind_groups: limits[:max_bind_groups],
141
+ max_bind_groups_plus_vertex_buffers: limits[:max_bind_groups_plus_vertex_buffers],
142
+ max_bindings_per_bind_group: limits[:max_bindings_per_bind_group],
143
+ max_dynamic_uniform_buffers_per_pipeline_layout: limits[:max_dynamic_uniform_buffers_per_pipeline_layout],
144
+ max_dynamic_storage_buffers_per_pipeline_layout: limits[:max_dynamic_storage_buffers_per_pipeline_layout],
145
+ max_sampled_textures_per_shader_stage: limits[:max_sampled_textures_per_shader_stage],
146
+ max_samplers_per_shader_stage: limits[:max_samplers_per_shader_stage],
147
+ max_storage_buffers_per_shader_stage: limits[:max_storage_buffers_per_shader_stage],
148
+ max_storage_textures_per_shader_stage: limits[:max_storage_textures_per_shader_stage],
149
+ max_uniform_buffers_per_shader_stage: limits[:max_uniform_buffers_per_shader_stage],
150
+ max_uniform_buffer_binding_size: limits[:max_uniform_buffer_binding_size],
151
+ max_storage_buffer_binding_size: limits[:max_storage_buffer_binding_size],
152
+ min_uniform_buffer_offset_alignment: limits[:min_uniform_buffer_offset_alignment],
153
+ min_storage_buffer_offset_alignment: limits[:min_storage_buffer_offset_alignment],
154
+ max_vertex_buffers: limits[:max_vertex_buffers],
155
+ max_buffer_size: limits[:max_buffer_size],
156
+ max_vertex_attributes: limits[:max_vertex_attributes],
157
+ max_vertex_buffer_array_stride: limits[:max_vertex_buffer_array_stride],
158
+ max_inter_stage_shader_variables: limits[:max_inter_stage_shader_variables],
159
+ max_color_attachments: limits[:max_color_attachments],
160
+ max_color_attachment_bytes_per_sample: limits[:max_color_attachment_bytes_per_sample],
161
+ max_compute_workgroup_storage_size: limits[:max_compute_workgroup_storage_size],
162
+ max_compute_invocations_per_workgroup: limits[:max_compute_invocations_per_workgroup],
163
+ max_compute_workgroup_size_x: limits[:max_compute_workgroup_size_x],
164
+ max_compute_workgroup_size_y: limits[:max_compute_workgroup_size_y],
165
+ max_compute_workgroup_size_z: limits[:max_compute_workgroup_size_z],
166
+ max_compute_workgroups_per_dimension: limits[:max_compute_workgroups_per_dimension]
167
+ }
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,304 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WGPU
4
+ class Device
5
+ attr_reader :handle, :queue
6
+
7
+ CALLBACK_MODE_WAIT_ANY_ONLY = 1
8
+
9
+ def self.request(adapter, label: nil, required_features: [], required_limits: nil)
10
+ device_ptr = FFI::MemoryPointer.new(:pointer)
11
+ status_holder = { value: nil, message: nil }
12
+
13
+ callback = FFI::Function.new(
14
+ :void, [:uint32, :pointer, Native::StringView.by_value, :pointer]
15
+ ) do |status, device, message, _userdata|
16
+ status_holder[:value] = Native::RequestDeviceStatus[status]
17
+ if message[:data] && !message[:data].null? && message[:length] > 0
18
+ status_holder[:message] = message[:data].read_string(message[:length])
19
+ end
20
+ device_ptr.write_pointer(device)
21
+ end
22
+
23
+ queue_desc = Native::QueueDescriptor.new
24
+ queue_desc[:next_in_chain] = nil
25
+ queue_desc[:label][:data] = nil
26
+ queue_desc[:label][:length] = 0
27
+
28
+ device_lost_info = Native::DeviceLostCallbackInfo.new
29
+ device_lost_info[:next_in_chain] = nil
30
+ device_lost_info[:mode] = 0
31
+ device_lost_info[:callback] = nil
32
+ device_lost_info[:userdata] = nil
33
+
34
+ error_info = Native::UncapturedErrorCallbackInfo.new
35
+ error_info[:next_in_chain] = nil
36
+ error_info[:callback] = nil
37
+ error_info[:userdata] = nil
38
+
39
+ desc = Native::DeviceDescriptor.new
40
+ desc[:next_in_chain] = nil
41
+ if label
42
+ label_ptr = FFI::MemoryPointer.from_string(label)
43
+ desc[:label][:data] = label_ptr
44
+ desc[:label][:length] = label.bytesize
45
+ else
46
+ desc[:label][:data] = nil
47
+ desc[:label][:length] = 0
48
+ end
49
+ desc[:required_feature_count] = 0
50
+ desc[:required_features] = nil
51
+ desc[:required_limits] = nil
52
+ desc[:default_queue] = queue_desc
53
+ desc[:device_lost_callback_info] = device_lost_info
54
+ desc[:uncaptured_error_callback_info] = error_info
55
+
56
+ callback_info = Native::RequestDeviceCallbackInfo.new
57
+ callback_info[:next_in_chain] = nil
58
+ callback_info[:mode] = CALLBACK_MODE_WAIT_ANY_ONLY
59
+ callback_info[:callback] = callback
60
+ callback_info[:userdata] = nil
61
+
62
+ Native.wgpuAdapterRequestDevice(adapter.handle, desc, callback_info)
63
+
64
+ handle = device_ptr.read_pointer
65
+ if handle.null? || status_holder[:value] != :success
66
+ msg = status_holder[:message] || "Unknown error"
67
+ raise DeviceError, "Failed to request device: #{msg}"
68
+ end
69
+
70
+ new(handle)
71
+ end
72
+
73
+ def initialize(handle)
74
+ @handle = handle
75
+ @queue = Queue.new(Native.wgpuDeviceGetQueue(@handle))
76
+ end
77
+
78
+ def create_buffer(label: nil, size:, usage:, mapped_at_creation: false)
79
+ Buffer.new(self, label: label, size: size, usage: usage, mapped_at_creation: mapped_at_creation)
80
+ end
81
+
82
+ def create_shader_module(label: nil, code:)
83
+ ShaderModule.new(self, label: label, code: code)
84
+ end
85
+
86
+ def create_command_encoder(label: nil)
87
+ CommandEncoder.new(self, label: label)
88
+ end
89
+
90
+ def create_bind_group_layout(label: nil, entries:)
91
+ BindGroupLayout.new(self, label: label, entries: entries)
92
+ end
93
+
94
+ def create_bind_group(label: nil, layout:, entries:)
95
+ BindGroup.new(self, label: label, layout: layout, entries: entries)
96
+ end
97
+
98
+ def create_pipeline_layout(label: nil, bind_group_layouts:)
99
+ PipelineLayout.new(self, label: label, bind_group_layouts: bind_group_layouts)
100
+ end
101
+
102
+ def create_compute_pipeline(label: nil, layout:, compute:)
103
+ ComputePipeline.new(self, label: label, layout: layout, compute: compute)
104
+ end
105
+
106
+ def create_render_pipeline(label: nil, layout:, vertex:, primitive: {}, depth_stencil: nil, multisample: {}, fragment: nil)
107
+ RenderPipeline.new(self,
108
+ label: label,
109
+ layout: layout,
110
+ vertex: vertex,
111
+ primitive: primitive,
112
+ depth_stencil: depth_stencil,
113
+ multisample: multisample,
114
+ fragment: fragment
115
+ )
116
+ end
117
+
118
+ def create_texture(label: nil, size:, format:, usage:, dimension: :d2, mip_level_count: 1, sample_count: 1, view_formats: [])
119
+ Texture.new(self,
120
+ label: label,
121
+ size: size,
122
+ format: format,
123
+ usage: usage,
124
+ dimension: dimension,
125
+ mip_level_count: mip_level_count,
126
+ sample_count: sample_count,
127
+ view_formats: view_formats
128
+ )
129
+ end
130
+
131
+ def create_sampler(label: nil, address_mode_u: :clamp_to_edge, address_mode_v: :clamp_to_edge, address_mode_w: :clamp_to_edge, mag_filter: :nearest, min_filter: :nearest, mipmap_filter: :nearest, lod_min_clamp: 0.0, lod_max_clamp: 32.0, compare: nil, max_anisotropy: 1)
132
+ Sampler.new(self,
133
+ label: label,
134
+ address_mode_u: address_mode_u,
135
+ address_mode_v: address_mode_v,
136
+ address_mode_w: address_mode_w,
137
+ mag_filter: mag_filter,
138
+ min_filter: min_filter,
139
+ mipmap_filter: mipmap_filter,
140
+ lod_min_clamp: lod_min_clamp,
141
+ lod_max_clamp: lod_max_clamp,
142
+ compare: compare,
143
+ max_anisotropy: max_anisotropy
144
+ )
145
+ end
146
+
147
+ def create_buffer_with_data(label: nil, data:, usage:)
148
+ data_ptr, byte_size = data_to_pointer(data)
149
+ buffer = create_buffer(
150
+ label: label,
151
+ size: byte_size,
152
+ usage: usage,
153
+ mapped_at_creation: true
154
+ )
155
+ buffer.mapped_range.write_bytes(data_ptr.read_bytes(byte_size))
156
+ buffer.unmap
157
+ buffer
158
+ end
159
+
160
+ def create_query_set(label: nil, type:, count:)
161
+ QuerySet.new(self, label: label, type: type, count: count)
162
+ end
163
+
164
+ def create_render_bundle_encoder(color_formats:, depth_stencil_format: nil, sample_count: 1,
165
+ depth_read_only: false, stencil_read_only: false, label: nil)
166
+ RenderBundleEncoder.new(self,
167
+ color_formats: color_formats,
168
+ depth_stencil_format: depth_stencil_format,
169
+ sample_count: sample_count,
170
+ depth_read_only: depth_read_only,
171
+ stencil_read_only: stencil_read_only,
172
+ label: label
173
+ )
174
+ end
175
+
176
+ def features
177
+ supported = Native::SupportedFeatures.new
178
+ Native.wgpuDeviceGetFeatures(@handle, supported)
179
+
180
+ result = []
181
+ if supported[:feature_count] > 0 && !supported[:features].null?
182
+ supported[:features].read_array_of_uint32(supported[:feature_count]).each do |f|
183
+ result << Native::FeatureName[f]
184
+ end
185
+ end
186
+ result
187
+ end
188
+
189
+ def has_feature?(feature)
190
+ features.include?(feature)
191
+ end
192
+
193
+ def limits
194
+ supported = Native::SupportedLimits.new
195
+ supported[:next_in_chain] = nil
196
+ Native.wgpuDeviceGetLimits(@handle, supported)
197
+ limits_to_hash(supported[:limits])
198
+ end
199
+
200
+ def poll(wait: false)
201
+ Native.wgpuDevicePoll(@handle, wait ? 1 : 0, nil)
202
+ end
203
+
204
+ def push_error_scope(filter = :validation)
205
+ Native.wgpuDevicePushErrorScope(@handle, filter)
206
+ end
207
+
208
+ def pop_error_scope
209
+ error_holder = { type: nil, message: nil }
210
+
211
+ callback = FFI::Function.new(
212
+ :void, [:uint32, :uint32, Native::StringView.by_value, :pointer, :pointer]
213
+ ) do |_status, error_type, message, _userdata1, _userdata2|
214
+ error_holder[:type] = Native::ErrorType[error_type]
215
+ if message[:data] && !message[:data].null? && message[:length] > 0
216
+ error_holder[:message] = message[:data].read_string(message[:length])
217
+ end
218
+ end
219
+
220
+ callback_info = Native::PopErrorScopeCallbackInfo.new
221
+ callback_info[:next_in_chain] = nil
222
+ callback_info[:mode] = 1
223
+ callback_info[:callback] = callback
224
+ callback_info[:userdata1] = nil
225
+ callback_info[:userdata2] = nil
226
+
227
+ Native.wgpuDevicePopErrorScope(@handle, callback_info)
228
+
229
+ error_holder
230
+ end
231
+
232
+ def with_error_scope(filter = :validation)
233
+ push_error_scope(filter)
234
+ result = yield
235
+ error = pop_error_scope
236
+ if error[:type] && error[:type] != :no_error
237
+ raise Error, "GPU error (#{error[:type]}): #{error[:message]}"
238
+ end
239
+ result
240
+ end
241
+
242
+ def release
243
+ @queue&.release
244
+ return if @handle.null?
245
+ Native.wgpuDeviceRelease(@handle)
246
+ @handle = FFI::Pointer::NULL
247
+ end
248
+
249
+ private
250
+
251
+ def data_to_pointer(data)
252
+ case data
253
+ when String
254
+ ptr = FFI::MemoryPointer.new(:char, data.bytesize)
255
+ ptr.put_bytes(0, data)
256
+ [ptr, data.bytesize]
257
+ when Array
258
+ ptr = FFI::MemoryPointer.new(:float, data.size)
259
+ ptr.write_array_of_float(data)
260
+ [ptr, data.size * 4]
261
+ when FFI::Pointer
262
+ [data, data.size]
263
+ else
264
+ raise ArgumentError, "Unsupported data type: #{data.class}"
265
+ end
266
+ end
267
+
268
+ def limits_to_hash(limits)
269
+ {
270
+ max_texture_dimension_1d: limits[:max_texture_dimension_1d],
271
+ max_texture_dimension_2d: limits[:max_texture_dimension_2d],
272
+ max_texture_dimension_3d: limits[:max_texture_dimension_3d],
273
+ max_texture_array_layers: limits[:max_texture_array_layers],
274
+ max_bind_groups: limits[:max_bind_groups],
275
+ max_bind_groups_plus_vertex_buffers: limits[:max_bind_groups_plus_vertex_buffers],
276
+ max_bindings_per_bind_group: limits[:max_bindings_per_bind_group],
277
+ max_dynamic_uniform_buffers_per_pipeline_layout: limits[:max_dynamic_uniform_buffers_per_pipeline_layout],
278
+ max_dynamic_storage_buffers_per_pipeline_layout: limits[:max_dynamic_storage_buffers_per_pipeline_layout],
279
+ max_sampled_textures_per_shader_stage: limits[:max_sampled_textures_per_shader_stage],
280
+ max_samplers_per_shader_stage: limits[:max_samplers_per_shader_stage],
281
+ max_storage_buffers_per_shader_stage: limits[:max_storage_buffers_per_shader_stage],
282
+ max_storage_textures_per_shader_stage: limits[:max_storage_textures_per_shader_stage],
283
+ max_uniform_buffers_per_shader_stage: limits[:max_uniform_buffers_per_shader_stage],
284
+ max_uniform_buffer_binding_size: limits[:max_uniform_buffer_binding_size],
285
+ max_storage_buffer_binding_size: limits[:max_storage_buffer_binding_size],
286
+ min_uniform_buffer_offset_alignment: limits[:min_uniform_buffer_offset_alignment],
287
+ min_storage_buffer_offset_alignment: limits[:min_storage_buffer_offset_alignment],
288
+ max_vertex_buffers: limits[:max_vertex_buffers],
289
+ max_buffer_size: limits[:max_buffer_size],
290
+ max_vertex_attributes: limits[:max_vertex_attributes],
291
+ max_vertex_buffer_array_stride: limits[:max_vertex_buffer_array_stride],
292
+ max_inter_stage_shader_variables: limits[:max_inter_stage_shader_variables],
293
+ max_color_attachments: limits[:max_color_attachments],
294
+ max_color_attachment_bytes_per_sample: limits[:max_color_attachment_bytes_per_sample],
295
+ max_compute_workgroup_storage_size: limits[:max_compute_workgroup_storage_size],
296
+ max_compute_invocations_per_workgroup: limits[:max_compute_invocations_per_workgroup],
297
+ max_compute_workgroup_size_x: limits[:max_compute_workgroup_size_x],
298
+ max_compute_workgroup_size_y: limits[:max_compute_workgroup_size_y],
299
+ max_compute_workgroup_size_z: limits[:max_compute_workgroup_size_z],
300
+ max_compute_workgroups_per_dimension: limits[:max_compute_workgroups_per_dimension]
301
+ }
302
+ end
303
+ end
304
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WGPU
4
+ class Instance
5
+ attr_reader :handle
6
+
7
+ def initialize
8
+ desc = Native::InstanceDescriptor.new
9
+ desc[:next_in_chain] = nil
10
+ desc[:features][:next_in_chain] = nil
11
+ desc[:features][:timed_wait_any_enable] = 0
12
+ desc[:features][:timed_wait_any_max_count] = 0
13
+
14
+ @handle = Native.wgpuCreateInstance(desc)
15
+ raise InitializationError, "Failed to create WebGPU instance" if @handle.null?
16
+ end
17
+
18
+ def request_adapter(power_preference: :high_performance, backend: nil)
19
+ Adapter.request(self, power_preference: power_preference, backend: backend)
20
+ end
21
+
22
+ def enumerate_adapters(backends: nil)
23
+ options = nil
24
+ if backends
25
+ options = Native::InstanceEnumerateAdapterOptions.new
26
+ options[:next_in_chain] = nil
27
+ options[:backends] = backends
28
+ end
29
+
30
+ count = Native.wgpuInstanceEnumerateAdapters(@handle, options, nil)
31
+ return [] if count == 0
32
+
33
+ adapters_ptr = FFI::MemoryPointer.new(:pointer, count)
34
+ Native.wgpuInstanceEnumerateAdapters(@handle, options, adapters_ptr)
35
+
36
+ adapters_ptr.read_array_of_pointer(count).map do |ptr|
37
+ Adapter.from_handle(ptr)
38
+ end
39
+ end
40
+
41
+ def process_events
42
+ Native.wgpuInstanceProcessEvents(@handle)
43
+ end
44
+
45
+ def release
46
+ return if @handle.null?
47
+ Native.wgpuInstanceRelease(@handle)
48
+ @handle = FFI::Pointer::NULL
49
+ end
50
+
51
+ end
52
+ end
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WGPU
4
+ class Queue
5
+ attr_reader :handle
6
+
7
+ def initialize(handle)
8
+ @handle = handle
9
+ end
10
+
11
+ def submit(command_buffers)
12
+ buffers = Array(command_buffers)
13
+ return if buffers.empty?
14
+
15
+ handles = buffers.map(&:handle)
16
+ ptr = FFI::MemoryPointer.new(:pointer, handles.size)
17
+ ptr.write_array_of_pointer(handles)
18
+
19
+ Native.wgpuQueueSubmit(@handle, handles.size, ptr)
20
+ end
21
+
22
+ def write_buffer(buffer, offset, data)
23
+ data_ptr, byte_size = data_to_pointer(data)
24
+ Native.wgpuQueueWriteBuffer(@handle, buffer.handle, offset, data_ptr, byte_size)
25
+ end
26
+
27
+ def write_texture(destination:, data:, data_layout:, size:)
28
+ data_ptr, byte_size = data_to_pointer(data)
29
+
30
+ dst = Native::ImageCopyTexture.new
31
+ dst[:texture] = destination[:texture].handle
32
+ dst[:mip_level] = destination[:mip_level] || 0
33
+ dst[:origin][:x] = destination.dig(:origin, :x) || 0
34
+ dst[:origin][:y] = destination.dig(:origin, :y) || 0
35
+ dst[:origin][:z] = destination.dig(:origin, :z) || 0
36
+ dst[:aspect] = destination[:aspect] || :all
37
+
38
+ extent = Native::Extent3D.new
39
+ if size.is_a?(Array)
40
+ extent[:width] = size[0]
41
+ extent[:height] = size[1] || 1
42
+ extent[:depth_or_array_layers] = size[2] || 1
43
+ else
44
+ extent[:width] = size[:width]
45
+ extent[:height] = size[:height] || 1
46
+ extent[:depth_or_array_layers] = size[:depth_or_array_layers] || 1
47
+ end
48
+
49
+ layout = Native::TextureDataLayout.new
50
+ layout[:offset] = data_layout[:offset] || 0
51
+ layout[:bytes_per_row] = data_layout[:bytes_per_row]
52
+ layout[:rows_per_image] = data_layout[:rows_per_image] || extent[:height]
53
+
54
+ Native.wgpuQueueWriteTexture(@handle, dst, data_ptr, byte_size, layout, extent)
55
+ end
56
+
57
+ def read_buffer(buffer, offset: 0, size: nil, device:)
58
+ size ||= buffer.size - offset
59
+
60
+ staging = Buffer.new(device,
61
+ size: size,
62
+ usage: [:map_read, :copy_dst]
63
+ )
64
+
65
+ encoder = CommandEncoder.new(device)
66
+ encoder.copy_buffer_to_buffer(
67
+ source: buffer,
68
+ source_offset: offset,
69
+ destination: staging,
70
+ destination_offset: 0,
71
+ size: size
72
+ )
73
+ command_buffer = encoder.finish
74
+ submit([command_buffer])
75
+
76
+ staging.map_async(:read)
77
+ data = staging.read_mapped_data
78
+ staging.unmap
79
+ staging.release
80
+
81
+ data
82
+ end
83
+
84
+ def read_texture(source:, data_layout:, size:, device:)
85
+ width = size[:width] || size[0]
86
+ height = size[:height] || size[1] || 1
87
+ depth = size[:depth_or_array_layers] || size[2] || 1
88
+ bytes_per_row = data_layout[:bytes_per_row]
89
+ rows_per_image = data_layout[:rows_per_image] || height
90
+ buffer_size = bytes_per_row * rows_per_image * depth
91
+
92
+ staging = Buffer.new(device,
93
+ size: buffer_size,
94
+ usage: [:map_read, :copy_dst]
95
+ )
96
+
97
+ encoder = CommandEncoder.new(device)
98
+ encoder.copy_texture_to_buffer(
99
+ source: source,
100
+ destination: {
101
+ buffer: staging,
102
+ offset: 0,
103
+ bytes_per_row: bytes_per_row,
104
+ rows_per_image: rows_per_image
105
+ },
106
+ copy_size: size
107
+ )
108
+ command_buffer = encoder.finish
109
+ submit([command_buffer])
110
+
111
+ staging.map_async(:read)
112
+ data = staging.read_mapped_data
113
+ staging.unmap
114
+ staging.release
115
+
116
+ data
117
+ end
118
+
119
+ def on_submitted_work_done(device: nil)
120
+ status_holder = { done: false, status: nil }
121
+
122
+ callback = FFI::Function.new(
123
+ :void, [:uint32, :pointer, :pointer]
124
+ ) do |status, _userdata1, _userdata2|
125
+ status_holder[:done] = true
126
+ status_holder[:status] = Native::QueueWorkDoneStatus[status]
127
+ end
128
+
129
+ callback_info = Native::QueueWorkDoneCallbackInfo.new
130
+ callback_info[:next_in_chain] = nil
131
+ callback_info[:mode] = 1
132
+ callback_info[:callback] = callback
133
+ callback_info[:userdata1] = nil
134
+ callback_info[:userdata2] = nil
135
+
136
+ Native.wgpuQueueOnSubmittedWorkDone(@handle, callback_info)
137
+
138
+ if device
139
+ until status_holder[:done]
140
+ Native.wgpuDevicePoll(device.handle, 0, nil)
141
+ sleep(0.001)
142
+ end
143
+ end
144
+
145
+ status_holder[:status]
146
+ end
147
+
148
+ def release
149
+ return if @handle.null?
150
+ Native.wgpuQueueRelease(@handle)
151
+ @handle = FFI::Pointer::NULL
152
+ end
153
+
154
+ private
155
+
156
+ def data_to_pointer(data)
157
+ case data
158
+ when String
159
+ ptr = FFI::MemoryPointer.new(:char, data.bytesize)
160
+ ptr.put_bytes(0, data)
161
+ [ptr, data.bytesize]
162
+ when Array
163
+ ptr = FFI::MemoryPointer.new(:float, data.size)
164
+ ptr.write_array_of_float(data)
165
+ [ptr, data.size * 4]
166
+ when FFI::Pointer
167
+ [data, data.size]
168
+ else
169
+ raise ArgumentError, "Unsupported data type: #{data.class}"
170
+ end
171
+ end
172
+ end
173
+ end