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.
- checksums.yaml +7 -0
- data/LICENSE-APACHE +190 -0
- data/LICENSE-MIT +21 -0
- data/README.md +213 -0
- data/ext/wgpu/Makefile +7 -0
- data/ext/wgpu/extconf.rb +161 -0
- data/lib/wgpu/commands/command_buffer.rb +17 -0
- data/lib/wgpu/commands/command_encoder.rb +189 -0
- data/lib/wgpu/commands/compute_pass.rb +76 -0
- data/lib/wgpu/commands/render_bundle.rb +18 -0
- data/lib/wgpu/commands/render_bundle_encoder.rb +148 -0
- data/lib/wgpu/commands/render_pass.rb +193 -0
- data/lib/wgpu/core/adapter.rb +170 -0
- data/lib/wgpu/core/device.rb +304 -0
- data/lib/wgpu/core/instance.rb +52 -0
- data/lib/wgpu/core/queue.rb +173 -0
- data/lib/wgpu/core/surface.rb +179 -0
- data/lib/wgpu/error.rb +16 -0
- data/lib/wgpu/native/callbacks.rb +26 -0
- data/lib/wgpu/native/enums.rb +524 -0
- data/lib/wgpu/native/functions.rb +413 -0
- data/lib/wgpu/native/loader.rb +61 -0
- data/lib/wgpu/native/structs.rb +609 -0
- data/lib/wgpu/pipeline/bind_group.rb +80 -0
- data/lib/wgpu/pipeline/bind_group_layout.rb +121 -0
- data/lib/wgpu/pipeline/compute_pipeline.rb +54 -0
- data/lib/wgpu/pipeline/pipeline_layout.rb +43 -0
- data/lib/wgpu/pipeline/render_pipeline.rb +248 -0
- data/lib/wgpu/pipeline/shader_module.rb +98 -0
- data/lib/wgpu/resources/buffer.rb +184 -0
- data/lib/wgpu/resources/query_set.rb +45 -0
- data/lib/wgpu/resources/sampler.rb +47 -0
- data/lib/wgpu/resources/texture.rb +118 -0
- data/lib/wgpu/resources/texture_view.rb +45 -0
- data/lib/wgpu/version.rb +5 -0
- data/lib/wgpu.rb +34 -0
- metadata +108 -0
|
@@ -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
|