wgpu 1.0.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 +228 -0
- data/ext/wgpu/Makefile +7 -0
- data/ext/wgpu/extconf.rb +161 -0
- data/lib/wgpu/async_task.rb +55 -0
- data/lib/wgpu/commands/command_buffer.rb +17 -0
- data/lib/wgpu/commands/command_encoder.rb +201 -0
- data/lib/wgpu/commands/compute_pass.rb +89 -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 +207 -0
- data/lib/wgpu/core/adapter.rb +186 -0
- data/lib/wgpu/core/canvas_context.rb +104 -0
- data/lib/wgpu/core/device.rb +397 -0
- data/lib/wgpu/core/instance.rb +81 -0
- data/lib/wgpu/core/queue.rb +197 -0
- data/lib/wgpu/core/surface.rb +221 -0
- data/lib/wgpu/error.rb +16 -0
- data/lib/wgpu/native/callbacks.rb +26 -0
- data/lib/wgpu/native/enums.rb +529 -0
- data/lib/wgpu/native/functions.rb +419 -0
- data/lib/wgpu/native/loader.rb +61 -0
- data/lib/wgpu/native/structs.rb +646 -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 +88 -0
- data/lib/wgpu/pipeline/pipeline_layout.rb +43 -0
- data/lib/wgpu/pipeline/render_pipeline.rb +278 -0
- data/lib/wgpu/pipeline/shader_module.rb +202 -0
- data/lib/wgpu/resources/buffer.rb +228 -0
- data/lib/wgpu/resources/query_set.rb +45 -0
- data/lib/wgpu/resources/sampler.rb +47 -0
- data/lib/wgpu/resources/texture.rb +136 -0
- data/lib/wgpu/resources/texture_view.rb +49 -0
- data/lib/wgpu/version.rb +5 -0
- data/lib/wgpu/window.rb +177 -0
- data/lib/wgpu.rb +36 -0
- metadata +125 -0
|
@@ -0,0 +1,186 @@
|
|
|
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, feature_level: :core, force_fallback_adapter: false, compatible_surface: 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] = feature_level
|
|
32
|
+
options[:power_preference] = power_preference
|
|
33
|
+
options[:force_fallback_adapter] = force_fallback_adapter ? 1 : 0
|
|
34
|
+
options[:backend_type] = backend || :undefined
|
|
35
|
+
options[:compatible_surface] = compatible_surface&.handle
|
|
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 request_device_async(label: nil, required_features: [], required_limits: nil)
|
|
63
|
+
AsyncTask.new do
|
|
64
|
+
request_device(
|
|
65
|
+
label: label,
|
|
66
|
+
required_features: required_features,
|
|
67
|
+
required_limits: required_limits
|
|
68
|
+
)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def info
|
|
73
|
+
info_struct = Native::AdapterInfo.new
|
|
74
|
+
Native.wgpuAdapterGetInfo(@handle, info_struct)
|
|
75
|
+
|
|
76
|
+
result = {
|
|
77
|
+
vendor: string_view_to_string(info_struct[:vendor]),
|
|
78
|
+
architecture: string_view_to_string(info_struct[:architecture]),
|
|
79
|
+
device: string_view_to_string(info_struct[:device]),
|
|
80
|
+
description: string_view_to_string(info_struct[:description]),
|
|
81
|
+
backend_type: info_struct[:backend_type],
|
|
82
|
+
adapter_type: info_struct[:adapter_type],
|
|
83
|
+
vendor_id: info_struct[:vendor_id],
|
|
84
|
+
device_id: info_struct[:device_id]
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
Native.wgpuAdapterInfoFreeMembers(info_struct)
|
|
88
|
+
result
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def name
|
|
92
|
+
info[:device]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def vendor
|
|
96
|
+
info[:vendor]
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def backend_type
|
|
100
|
+
info[:backend_type]
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def adapter_type
|
|
104
|
+
info[:adapter_type]
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def features
|
|
108
|
+
supported = Native::SupportedFeatures.new
|
|
109
|
+
Native.wgpuAdapterGetFeatures(@handle, supported)
|
|
110
|
+
|
|
111
|
+
result = []
|
|
112
|
+
if supported[:feature_count] > 0 && !supported[:features].null?
|
|
113
|
+
supported[:features].read_array_of_uint32(supported[:feature_count]).each do |f|
|
|
114
|
+
result << Native::FeatureName[f]
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
result
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def has_feature?(feature)
|
|
121
|
+
features.include?(feature)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def limits
|
|
125
|
+
supported = Native::SupportedLimits.new
|
|
126
|
+
supported[:next_in_chain] = nil
|
|
127
|
+
Native.wgpuAdapterGetLimits(@handle, supported)
|
|
128
|
+
limits_to_hash(supported[:limits])
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def summary
|
|
132
|
+
info_hash = info
|
|
133
|
+
"#{info_hash[:device]} (#{info_hash[:adapter_type]}) via #{info_hash[:backend_type]}"
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def release
|
|
137
|
+
return if @handle.null?
|
|
138
|
+
Native.wgpuAdapterRelease(@handle)
|
|
139
|
+
@handle = FFI::Pointer::NULL
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
private
|
|
143
|
+
|
|
144
|
+
def string_view_to_string(string_view)
|
|
145
|
+
return "" if string_view[:data].null? || string_view[:length] == 0
|
|
146
|
+
string_view[:data].read_string(string_view[:length])
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def limits_to_hash(limits)
|
|
150
|
+
{
|
|
151
|
+
max_texture_dimension_1d: limits[:max_texture_dimension_1d],
|
|
152
|
+
max_texture_dimension_2d: limits[:max_texture_dimension_2d],
|
|
153
|
+
max_texture_dimension_3d: limits[:max_texture_dimension_3d],
|
|
154
|
+
max_texture_array_layers: limits[:max_texture_array_layers],
|
|
155
|
+
max_bind_groups: limits[:max_bind_groups],
|
|
156
|
+
max_bind_groups_plus_vertex_buffers: limits[:max_bind_groups_plus_vertex_buffers],
|
|
157
|
+
max_bindings_per_bind_group: limits[:max_bindings_per_bind_group],
|
|
158
|
+
max_dynamic_uniform_buffers_per_pipeline_layout: limits[:max_dynamic_uniform_buffers_per_pipeline_layout],
|
|
159
|
+
max_dynamic_storage_buffers_per_pipeline_layout: limits[:max_dynamic_storage_buffers_per_pipeline_layout],
|
|
160
|
+
max_sampled_textures_per_shader_stage: limits[:max_sampled_textures_per_shader_stage],
|
|
161
|
+
max_samplers_per_shader_stage: limits[:max_samplers_per_shader_stage],
|
|
162
|
+
max_storage_buffers_per_shader_stage: limits[:max_storage_buffers_per_shader_stage],
|
|
163
|
+
max_storage_textures_per_shader_stage: limits[:max_storage_textures_per_shader_stage],
|
|
164
|
+
max_uniform_buffers_per_shader_stage: limits[:max_uniform_buffers_per_shader_stage],
|
|
165
|
+
max_uniform_buffer_binding_size: limits[:max_uniform_buffer_binding_size],
|
|
166
|
+
max_storage_buffer_binding_size: limits[:max_storage_buffer_binding_size],
|
|
167
|
+
min_uniform_buffer_offset_alignment: limits[:min_uniform_buffer_offset_alignment],
|
|
168
|
+
min_storage_buffer_offset_alignment: limits[:min_storage_buffer_offset_alignment],
|
|
169
|
+
max_vertex_buffers: limits[:max_vertex_buffers],
|
|
170
|
+
max_buffer_size: limits[:max_buffer_size],
|
|
171
|
+
max_vertex_attributes: limits[:max_vertex_attributes],
|
|
172
|
+
max_vertex_buffer_array_stride: limits[:max_vertex_buffer_array_stride],
|
|
173
|
+
max_inter_stage_shader_variables: limits[:max_inter_stage_shader_variables],
|
|
174
|
+
max_color_attachments: limits[:max_color_attachments],
|
|
175
|
+
max_color_attachment_bytes_per_sample: limits[:max_color_attachment_bytes_per_sample],
|
|
176
|
+
max_compute_workgroup_storage_size: limits[:max_compute_workgroup_storage_size],
|
|
177
|
+
max_compute_invocations_per_workgroup: limits[:max_compute_invocations_per_workgroup],
|
|
178
|
+
max_compute_workgroup_size_x: limits[:max_compute_workgroup_size_x],
|
|
179
|
+
max_compute_workgroup_size_y: limits[:max_compute_workgroup_size_y],
|
|
180
|
+
max_compute_workgroup_size_z: limits[:max_compute_workgroup_size_z],
|
|
181
|
+
max_compute_workgroups_per_dimension: limits[:max_compute_workgroups_per_dimension],
|
|
182
|
+
max_subgroup_size: limits[:max_subgroup_size]
|
|
183
|
+
}
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WGPU
|
|
4
|
+
class CanvasContext
|
|
5
|
+
attr_reader :physical_size
|
|
6
|
+
|
|
7
|
+
def initialize(instance, present_info = {})
|
|
8
|
+
@instance = instance
|
|
9
|
+
@present_info = present_info || {}
|
|
10
|
+
@surface = @present_info[:surface]
|
|
11
|
+
@physical_size = [0, 0]
|
|
12
|
+
@config = nil
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def set_physical_size(width, height)
|
|
16
|
+
raise ArgumentError, "width and height must be non-negative" if width.to_i.negative? || height.to_i.negative?
|
|
17
|
+
|
|
18
|
+
@physical_size = [width.to_i, height.to_i]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def get_preferred_format(adapter)
|
|
22
|
+
ensure_surface
|
|
23
|
+
@surface.get_preferred_format(adapter)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def get_configuration
|
|
27
|
+
@config
|
|
28
|
+
end
|
|
29
|
+
|
|
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
|
+
ensure_surface
|
|
32
|
+
color_space # reserved for API parity
|
|
33
|
+
tone_mapping # reserved for API parity
|
|
34
|
+
|
|
35
|
+
width = width || @physical_size[0]
|
|
36
|
+
height = height || @physical_size[1]
|
|
37
|
+
raise SurfaceError, "Surface size must be positive before configure" if width.to_i <= 0 || height.to_i <= 0
|
|
38
|
+
|
|
39
|
+
resolved_format = format || get_preferred_format(device.adapter)
|
|
40
|
+
@surface.configure(
|
|
41
|
+
device: device,
|
|
42
|
+
format: resolved_format,
|
|
43
|
+
usage: usage,
|
|
44
|
+
width: width,
|
|
45
|
+
height: height,
|
|
46
|
+
present_mode: present_mode,
|
|
47
|
+
alpha_mode: alpha_mode,
|
|
48
|
+
view_formats: view_formats
|
|
49
|
+
)
|
|
50
|
+
@config = {
|
|
51
|
+
device: device,
|
|
52
|
+
format: resolved_format,
|
|
53
|
+
usage: usage,
|
|
54
|
+
view_formats: view_formats,
|
|
55
|
+
color_space: color_space,
|
|
56
|
+
tone_mapping: tone_mapping,
|
|
57
|
+
alpha_mode: alpha_mode,
|
|
58
|
+
width: width,
|
|
59
|
+
height: height,
|
|
60
|
+
present_mode: present_mode
|
|
61
|
+
}
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def unconfigure
|
|
65
|
+
@surface&.unconfigure
|
|
66
|
+
@config = nil
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def get_current_texture
|
|
70
|
+
raise SurfaceError, "Canvas context must be configured before get_current_texture" unless @config
|
|
71
|
+
|
|
72
|
+
@surface.current_texture
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def present
|
|
76
|
+
@surface&.present
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def release
|
|
80
|
+
@surface&.release
|
|
81
|
+
@surface = nil
|
|
82
|
+
@config = nil
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
def ensure_surface
|
|
88
|
+
return if @surface
|
|
89
|
+
|
|
90
|
+
@surface = case @present_info[:platform]&.to_sym
|
|
91
|
+
when :macos
|
|
92
|
+
Surface.from_metal_layer(@instance, @present_info.fetch(:layer))
|
|
93
|
+
when :windows
|
|
94
|
+
Surface.from_windows_hwnd(@instance, @present_info[:hinstance], @present_info.fetch(:hwnd))
|
|
95
|
+
when :x11, :linux_x11
|
|
96
|
+
Surface.from_xlib_window(@instance, @present_info.fetch(:display), @present_info.fetch(:window))
|
|
97
|
+
when :wayland, :linux_wayland
|
|
98
|
+
Surface.from_wayland_surface(@instance, @present_info.fetch(:display), @present_info.fetch(:surface))
|
|
99
|
+
else
|
|
100
|
+
raise SurfaceError, "Cannot build surface from present_info: #{@present_info.inspect}"
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WGPU
|
|
4
|
+
class Device
|
|
5
|
+
attr_reader :handle, :queue, :adapter
|
|
6
|
+
|
|
7
|
+
CALLBACK_MODE_WAIT_ANY_ONLY = 1
|
|
8
|
+
LIMIT_FIELDS = Native::Limits.members.freeze
|
|
9
|
+
|
|
10
|
+
def self.request(adapter, label: nil, required_features: [], required_limits: nil)
|
|
11
|
+
device_ptr = FFI::MemoryPointer.new(:pointer)
|
|
12
|
+
status_holder = { value: nil, message: nil }
|
|
13
|
+
|
|
14
|
+
callback = FFI::Function.new(
|
|
15
|
+
:void, [:uint32, :pointer, Native::StringView.by_value, :pointer]
|
|
16
|
+
) do |status, device, message, _userdata|
|
|
17
|
+
status_holder[:value] = Native::RequestDeviceStatus[status]
|
|
18
|
+
if message[:data] && !message[:data].null? && message[:length] > 0
|
|
19
|
+
status_holder[:message] = message[:data].read_string(message[:length])
|
|
20
|
+
end
|
|
21
|
+
device_ptr.write_pointer(device)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
queue_desc = Native::QueueDescriptor.new
|
|
25
|
+
queue_desc[:next_in_chain] = nil
|
|
26
|
+
queue_desc[:label][:data] = nil
|
|
27
|
+
queue_desc[:label][:length] = 0
|
|
28
|
+
|
|
29
|
+
device_lost_info = Native::DeviceLostCallbackInfo.new
|
|
30
|
+
device_lost_info[:next_in_chain] = nil
|
|
31
|
+
device_lost_info[:mode] = 0
|
|
32
|
+
device_lost_info[:callback] = nil
|
|
33
|
+
device_lost_info[:userdata] = nil
|
|
34
|
+
|
|
35
|
+
error_info = Native::UncapturedErrorCallbackInfo.new
|
|
36
|
+
error_info[:next_in_chain] = nil
|
|
37
|
+
error_info[:callback] = nil
|
|
38
|
+
error_info[:userdata] = nil
|
|
39
|
+
|
|
40
|
+
desc = Native::DeviceDescriptor.new
|
|
41
|
+
desc[:next_in_chain] = nil
|
|
42
|
+
if label
|
|
43
|
+
label_ptr = FFI::MemoryPointer.from_string(label)
|
|
44
|
+
desc[:label][:data] = label_ptr
|
|
45
|
+
desc[:label][:length] = label.bytesize
|
|
46
|
+
else
|
|
47
|
+
desc[:label][:data] = nil
|
|
48
|
+
desc[:label][:length] = 0
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
feature_values = normalize_required_features(required_features)
|
|
52
|
+
if feature_values.empty?
|
|
53
|
+
desc[:required_feature_count] = 0
|
|
54
|
+
desc[:required_features] = nil
|
|
55
|
+
else
|
|
56
|
+
features_ptr = FFI::MemoryPointer.new(:uint32, feature_values.size)
|
|
57
|
+
features_ptr.write_array_of_uint32(feature_values)
|
|
58
|
+
desc[:required_feature_count] = feature_values.size
|
|
59
|
+
desc[:required_features] = features_ptr
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
required_limits_struct = build_required_limits(adapter, required_limits)
|
|
63
|
+
desc[:required_limits] = required_limits_struct ? required_limits_struct.to_ptr : nil
|
|
64
|
+
desc[:default_queue] = queue_desc
|
|
65
|
+
desc[:device_lost_callback_info] = device_lost_info
|
|
66
|
+
desc[:uncaptured_error_callback_info] = error_info
|
|
67
|
+
|
|
68
|
+
callback_info = Native::RequestDeviceCallbackInfo.new
|
|
69
|
+
callback_info[:next_in_chain] = nil
|
|
70
|
+
callback_info[:mode] = CALLBACK_MODE_WAIT_ANY_ONLY
|
|
71
|
+
callback_info[:callback] = callback
|
|
72
|
+
callback_info[:userdata] = nil
|
|
73
|
+
|
|
74
|
+
Native.wgpuAdapterRequestDevice(adapter.handle, desc, callback_info)
|
|
75
|
+
|
|
76
|
+
handle = device_ptr.read_pointer
|
|
77
|
+
if handle.null? || status_holder[:value] != :success
|
|
78
|
+
msg = status_holder[:message] || "Unknown error"
|
|
79
|
+
raise DeviceError, "Failed to request device: #{msg}"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
new(handle, adapter: adapter)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def initialize(handle, adapter: nil)
|
|
86
|
+
@handle = handle
|
|
87
|
+
@adapter = adapter
|
|
88
|
+
@queue = Queue.new(Native.wgpuDeviceGetQueue(@handle), device: self)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def adapter_info
|
|
92
|
+
@adapter&.info
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def create_buffer(label: nil, size:, usage:, mapped_at_creation: false)
|
|
96
|
+
Buffer.new(self, label: label, size: size, usage: usage, mapped_at_creation: mapped_at_creation)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def create_shader_module(label: nil, code:, compilation_hints: [])
|
|
100
|
+
ShaderModule.new(self, label: label, code: code, compilation_hints: compilation_hints)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def create_command_encoder(label: nil)
|
|
104
|
+
CommandEncoder.new(self, label: label)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def create_bind_group_layout(label: nil, entries:)
|
|
108
|
+
BindGroupLayout.new(self, label: label, entries: entries)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def create_bind_group(label: nil, layout:, entries:)
|
|
112
|
+
BindGroup.new(self, label: label, layout: layout, entries: entries)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def create_pipeline_layout(label: nil, bind_group_layouts:)
|
|
116
|
+
PipelineLayout.new(self, label: label, bind_group_layouts: bind_group_layouts)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def create_compute_pipeline(label: nil, layout:, compute:)
|
|
120
|
+
ComputePipeline.new(self, label: label, layout: layout, compute: compute)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def create_compute_pipeline_async(label: nil, layout:, compute:)
|
|
124
|
+
AsyncTask.new do
|
|
125
|
+
create_compute_pipeline(label: label, layout: layout, compute: compute)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def create_render_pipeline(label: nil, layout:, vertex:, primitive: {}, depth_stencil: nil, multisample: {}, fragment: nil)
|
|
130
|
+
RenderPipeline.new(self,
|
|
131
|
+
label: label,
|
|
132
|
+
layout: layout,
|
|
133
|
+
vertex: vertex,
|
|
134
|
+
primitive: primitive,
|
|
135
|
+
depth_stencil: depth_stencil,
|
|
136
|
+
multisample: multisample,
|
|
137
|
+
fragment: fragment
|
|
138
|
+
)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def create_render_pipeline_async(label: nil, layout:, vertex:, primitive: {}, depth_stencil: nil, multisample: {}, fragment: nil)
|
|
142
|
+
AsyncTask.new do
|
|
143
|
+
create_render_pipeline(
|
|
144
|
+
label: label,
|
|
145
|
+
layout: layout,
|
|
146
|
+
vertex: vertex,
|
|
147
|
+
primitive: primitive,
|
|
148
|
+
depth_stencil: depth_stencil,
|
|
149
|
+
multisample: multisample,
|
|
150
|
+
fragment: fragment
|
|
151
|
+
)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def create_texture(label: nil, size:, format:, usage:, dimension: :d2, mip_level_count: 1, sample_count: 1, view_formats: [])
|
|
156
|
+
Texture.new(self,
|
|
157
|
+
label: label,
|
|
158
|
+
size: size,
|
|
159
|
+
format: format,
|
|
160
|
+
usage: usage,
|
|
161
|
+
dimension: dimension,
|
|
162
|
+
mip_level_count: mip_level_count,
|
|
163
|
+
sample_count: sample_count,
|
|
164
|
+
view_formats: view_formats
|
|
165
|
+
)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
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)
|
|
169
|
+
Sampler.new(self,
|
|
170
|
+
label: label,
|
|
171
|
+
address_mode_u: address_mode_u,
|
|
172
|
+
address_mode_v: address_mode_v,
|
|
173
|
+
address_mode_w: address_mode_w,
|
|
174
|
+
mag_filter: mag_filter,
|
|
175
|
+
min_filter: min_filter,
|
|
176
|
+
mipmap_filter: mipmap_filter,
|
|
177
|
+
lod_min_clamp: lod_min_clamp,
|
|
178
|
+
lod_max_clamp: lod_max_clamp,
|
|
179
|
+
compare: compare,
|
|
180
|
+
max_anisotropy: max_anisotropy
|
|
181
|
+
)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def create_buffer_with_data(label: nil, data:, usage:)
|
|
185
|
+
data_ptr, byte_size = data_to_pointer(data)
|
|
186
|
+
buffer = create_buffer(
|
|
187
|
+
label: label,
|
|
188
|
+
size: byte_size,
|
|
189
|
+
usage: usage,
|
|
190
|
+
mapped_at_creation: true
|
|
191
|
+
)
|
|
192
|
+
buffer.mapped_range.write_bytes(data_ptr.read_bytes(byte_size))
|
|
193
|
+
buffer.unmap
|
|
194
|
+
buffer
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def create_query_set(label: nil, type:, count:)
|
|
198
|
+
QuerySet.new(self, label: label, type: type, count: count)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def create_render_bundle_encoder(color_formats:, depth_stencil_format: nil, sample_count: 1,
|
|
202
|
+
depth_read_only: false, stencil_read_only: false, label: nil)
|
|
203
|
+
RenderBundleEncoder.new(self,
|
|
204
|
+
color_formats: color_formats,
|
|
205
|
+
depth_stencil_format: depth_stencil_format,
|
|
206
|
+
sample_count: sample_count,
|
|
207
|
+
depth_read_only: depth_read_only,
|
|
208
|
+
stencil_read_only: stencil_read_only,
|
|
209
|
+
label: label
|
|
210
|
+
)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def features
|
|
214
|
+
supported = Native::SupportedFeatures.new
|
|
215
|
+
Native.wgpuDeviceGetFeatures(@handle, supported)
|
|
216
|
+
|
|
217
|
+
result = []
|
|
218
|
+
if supported[:feature_count] > 0 && !supported[:features].null?
|
|
219
|
+
supported[:features].read_array_of_uint32(supported[:feature_count]).each do |f|
|
|
220
|
+
result << Native::FeatureName[f]
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
result
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def has_feature?(feature)
|
|
227
|
+
features.include?(feature)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def limits
|
|
231
|
+
supported = Native::SupportedLimits.new
|
|
232
|
+
supported[:next_in_chain] = nil
|
|
233
|
+
Native.wgpuDeviceGetLimits(@handle, supported)
|
|
234
|
+
limits_to_hash(supported[:limits])
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def poll(wait: false)
|
|
238
|
+
Native.wgpuDevicePoll(@handle, wait ? 1 : 0, nil)
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def push_error_scope(filter = :validation)
|
|
242
|
+
Native.wgpuDevicePushErrorScope(@handle, filter)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def pop_error_scope
|
|
246
|
+
error_holder = { type: nil, message: nil }
|
|
247
|
+
|
|
248
|
+
callback = FFI::Function.new(
|
|
249
|
+
:void, [:uint32, :uint32, Native::StringView.by_value, :pointer, :pointer]
|
|
250
|
+
) do |_status, error_type, message, _userdata1, _userdata2|
|
|
251
|
+
error_holder[:type] = Native::ErrorType[error_type]
|
|
252
|
+
if message[:data] && !message[:data].null? && message[:length] > 0
|
|
253
|
+
error_holder[:message] = message[:data].read_string(message[:length])
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
callback_info = Native::PopErrorScopeCallbackInfo.new
|
|
258
|
+
callback_info[:next_in_chain] = nil
|
|
259
|
+
callback_info[:mode] = 1
|
|
260
|
+
callback_info[:callback] = callback
|
|
261
|
+
callback_info[:userdata1] = nil
|
|
262
|
+
callback_info[:userdata2] = nil
|
|
263
|
+
|
|
264
|
+
Native.wgpuDevicePopErrorScope(@handle, callback_info)
|
|
265
|
+
|
|
266
|
+
error_holder
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def pop_error_scope_async
|
|
270
|
+
AsyncTask.new { pop_error_scope }
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def with_error_scope(filter = :validation)
|
|
274
|
+
push_error_scope(filter)
|
|
275
|
+
result = yield
|
|
276
|
+
error = pop_error_scope
|
|
277
|
+
if error[:type] && error[:type] != :no_error
|
|
278
|
+
raise Error, "GPU error (#{error[:type]}): #{error[:message]}"
|
|
279
|
+
end
|
|
280
|
+
result
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def destroy
|
|
284
|
+
return if @handle.null?
|
|
285
|
+
Native.wgpuDeviceDestroy(@handle)
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def release
|
|
289
|
+
@queue&.release
|
|
290
|
+
return if @handle.null?
|
|
291
|
+
Native.wgpuDeviceRelease(@handle)
|
|
292
|
+
@handle = FFI::Pointer::NULL
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
private
|
|
296
|
+
|
|
297
|
+
def self.normalize_required_features(required_features)
|
|
298
|
+
Array(required_features).map do |feature|
|
|
299
|
+
normalize_feature_name(feature)
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def self.normalize_feature_name(feature)
|
|
304
|
+
return feature if feature.is_a?(Integer)
|
|
305
|
+
|
|
306
|
+
key = feature.to_s.strip.tr("-", "_").to_sym
|
|
307
|
+
value = Native::FeatureName[key]
|
|
308
|
+
raise ArgumentError, "Unknown feature name: #{feature}" if value.nil?
|
|
309
|
+
|
|
310
|
+
value
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
def self.build_required_limits(adapter, required_limits)
|
|
314
|
+
return nil if required_limits.nil?
|
|
315
|
+
raise ArgumentError, "required_limits must be a Hash" unless required_limits.is_a?(Hash)
|
|
316
|
+
|
|
317
|
+
resolved_limits = adapter.limits.dup
|
|
318
|
+
required_limits.each do |name, value|
|
|
319
|
+
key = canonical_limit_key(name)
|
|
320
|
+
resolved_limits[key] = value unless value.nil?
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
required = Native::RequiredLimits.new
|
|
324
|
+
required[:next_in_chain] = nil
|
|
325
|
+
LIMIT_FIELDS.each do |field|
|
|
326
|
+
required[:limits][field] = resolved_limits[field] || 0
|
|
327
|
+
end
|
|
328
|
+
required
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
def self.canonical_limit_key(name)
|
|
332
|
+
normalized = name.to_s
|
|
333
|
+
.gsub(/([A-Z])/, "_\\1")
|
|
334
|
+
.downcase
|
|
335
|
+
.tr("-", "_")
|
|
336
|
+
.sub(/^_/, "")
|
|
337
|
+
.to_sym
|
|
338
|
+
return normalized if LIMIT_FIELDS.include?(normalized)
|
|
339
|
+
|
|
340
|
+
raise ArgumentError, "Unknown limit key: #{name}"
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
def data_to_pointer(data)
|
|
344
|
+
case data
|
|
345
|
+
when String
|
|
346
|
+
ptr = FFI::MemoryPointer.new(:char, data.bytesize)
|
|
347
|
+
ptr.put_bytes(0, data)
|
|
348
|
+
[ptr, data.bytesize]
|
|
349
|
+
when Array
|
|
350
|
+
ptr = FFI::MemoryPointer.new(:float, data.size)
|
|
351
|
+
ptr.write_array_of_float(data)
|
|
352
|
+
[ptr, data.size * 4]
|
|
353
|
+
when FFI::Pointer
|
|
354
|
+
[data, data.size]
|
|
355
|
+
else
|
|
356
|
+
raise ArgumentError, "Unsupported data type: #{data.class}"
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
def limits_to_hash(limits)
|
|
361
|
+
{
|
|
362
|
+
max_texture_dimension_1d: limits[:max_texture_dimension_1d],
|
|
363
|
+
max_texture_dimension_2d: limits[:max_texture_dimension_2d],
|
|
364
|
+
max_texture_dimension_3d: limits[:max_texture_dimension_3d],
|
|
365
|
+
max_texture_array_layers: limits[:max_texture_array_layers],
|
|
366
|
+
max_bind_groups: limits[:max_bind_groups],
|
|
367
|
+
max_bind_groups_plus_vertex_buffers: limits[:max_bind_groups_plus_vertex_buffers],
|
|
368
|
+
max_bindings_per_bind_group: limits[:max_bindings_per_bind_group],
|
|
369
|
+
max_dynamic_uniform_buffers_per_pipeline_layout: limits[:max_dynamic_uniform_buffers_per_pipeline_layout],
|
|
370
|
+
max_dynamic_storage_buffers_per_pipeline_layout: limits[:max_dynamic_storage_buffers_per_pipeline_layout],
|
|
371
|
+
max_sampled_textures_per_shader_stage: limits[:max_sampled_textures_per_shader_stage],
|
|
372
|
+
max_samplers_per_shader_stage: limits[:max_samplers_per_shader_stage],
|
|
373
|
+
max_storage_buffers_per_shader_stage: limits[:max_storage_buffers_per_shader_stage],
|
|
374
|
+
max_storage_textures_per_shader_stage: limits[:max_storage_textures_per_shader_stage],
|
|
375
|
+
max_uniform_buffers_per_shader_stage: limits[:max_uniform_buffers_per_shader_stage],
|
|
376
|
+
max_uniform_buffer_binding_size: limits[:max_uniform_buffer_binding_size],
|
|
377
|
+
max_storage_buffer_binding_size: limits[:max_storage_buffer_binding_size],
|
|
378
|
+
min_uniform_buffer_offset_alignment: limits[:min_uniform_buffer_offset_alignment],
|
|
379
|
+
min_storage_buffer_offset_alignment: limits[:min_storage_buffer_offset_alignment],
|
|
380
|
+
max_vertex_buffers: limits[:max_vertex_buffers],
|
|
381
|
+
max_buffer_size: limits[:max_buffer_size],
|
|
382
|
+
max_vertex_attributes: limits[:max_vertex_attributes],
|
|
383
|
+
max_vertex_buffer_array_stride: limits[:max_vertex_buffer_array_stride],
|
|
384
|
+
max_inter_stage_shader_variables: limits[:max_inter_stage_shader_variables],
|
|
385
|
+
max_color_attachments: limits[:max_color_attachments],
|
|
386
|
+
max_color_attachment_bytes_per_sample: limits[:max_color_attachment_bytes_per_sample],
|
|
387
|
+
max_compute_workgroup_storage_size: limits[:max_compute_workgroup_storage_size],
|
|
388
|
+
max_compute_invocations_per_workgroup: limits[:max_compute_invocations_per_workgroup],
|
|
389
|
+
max_compute_workgroup_size_x: limits[:max_compute_workgroup_size_x],
|
|
390
|
+
max_compute_workgroup_size_y: limits[:max_compute_workgroup_size_y],
|
|
391
|
+
max_compute_workgroup_size_z: limits[:max_compute_workgroup_size_z],
|
|
392
|
+
max_compute_workgroups_per_dimension: limits[:max_compute_workgroups_per_dimension],
|
|
393
|
+
max_subgroup_size: limits[:max_subgroup_size]
|
|
394
|
+
}
|
|
395
|
+
end
|
|
396
|
+
end
|
|
397
|
+
end
|