wgpu 1.1.0 → 1.2.1
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 +88 -0
- data/README.md +44 -5
- data/docs/README.md +22 -0
- data/docs/api_coverage.md +140 -0
- data/docs/async.md +41 -0
- data/docs/bind_groups.md +25 -0
- data/docs/buffer_data.md +37 -0
- data/docs/command_encoding.md +49 -0
- data/docs/errors.md +40 -0
- data/docs/getting_started_compute.md +62 -0
- data/docs/getting_started_rendering.md +62 -0
- data/docs/installation.md +94 -0
- data/docs/pipeline_descriptors.md +43 -0
- data/docs/releasing.md +32 -0
- data/docs/resource_lifetime.md +108 -0
- data/docs/shaders.md +32 -0
- data/docs/texture_readback.md +46 -0
- data/docs/troubleshooting.md +53 -0
- data/docs/upgrading_wgpu_native.md +37 -0
- data/ext/wgpu/extconf.rb +10 -142
- data/lib/wgpu/async_task.rb +19 -0
- data/lib/wgpu/commands/command_buffer.rb +25 -1
- data/lib/wgpu/commands/command_encoder.rb +123 -9
- data/lib/wgpu/commands/compute_pass.rb +53 -0
- data/lib/wgpu/commands/render_bundle.rb +9 -1
- data/lib/wgpu/commands/render_bundle_encoder.rb +65 -4
- data/lib/wgpu/commands/render_pass.rb +136 -8
- data/lib/wgpu/core/adapter.rb +123 -19
- data/lib/wgpu/core/async_waiter.rb +47 -4
- data/lib/wgpu/core/canvas_context.rb +32 -2
- data/lib/wgpu/core/device.rb +399 -53
- data/lib/wgpu/core/instance.rb +28 -4
- data/lib/wgpu/core/queue.rb +197 -51
- data/lib/wgpu/core/surface.rb +64 -17
- data/lib/wgpu/data_types.rb +83 -0
- data/lib/wgpu/descriptor_helpers.rb +104 -0
- data/lib/wgpu/error.rb +70 -0
- data/lib/wgpu/logging.rb +63 -0
- data/lib/wgpu/native/abi_verifier.rb +143 -0
- data/lib/wgpu/native/callbacks.rb +10 -1
- data/lib/wgpu/native/capabilities.rb +32 -2
- data/lib/wgpu/native/distribution.rb +176 -0
- data/lib/wgpu/native/enum_helper.rb +80 -0
- data/lib/wgpu/native/enums.rb +68 -8
- data/lib/wgpu/native/fixtures/webgpu-v27.0.4.0-enums.h +848 -0
- data/lib/wgpu/native/functions.rb +21 -10
- data/lib/wgpu/native/installer.rb +223 -0
- data/lib/wgpu/native/loader.rb +61 -21
- data/lib/wgpu/native/structs.rb +18 -1
- data/lib/wgpu/native_resource.rb +342 -0
- data/lib/wgpu/pipeline/bind_group.rb +21 -0
- data/lib/wgpu/pipeline/bind_group_layout.rb +108 -41
- data/lib/wgpu/pipeline/compute_pipeline.rb +40 -50
- data/lib/wgpu/pipeline/pipeline_layout.rb +8 -0
- data/lib/wgpu/pipeline/render_pipeline.rb +207 -110
- data/lib/wgpu/pipeline/shader_module.rb +95 -28
- data/lib/wgpu/resources/buffer.rb +387 -85
- data/lib/wgpu/resources/query_set.rb +26 -12
- data/lib/wgpu/resources/sampler.rb +43 -20
- data/lib/wgpu/resources/texture.rb +89 -48
- data/lib/wgpu/resources/texture_view.rb +35 -7
- data/lib/wgpu/texture_format.rb +96 -0
- data/lib/wgpu/version.rb +1 -1
- data/lib/wgpu/window.rb +34 -1
- data/lib/wgpu.rb +32 -0
- data/sig/wgpu.rbs +460 -0
- metadata +33 -16
|
@@ -4,6 +4,10 @@ module WGPU
|
|
|
4
4
|
class PipelineLayout
|
|
5
5
|
attr_reader :handle
|
|
6
6
|
|
|
7
|
+
# Creates a pipeline layout from ordered bind group layouts.
|
|
8
|
+
# @param device [Device] owning device
|
|
9
|
+
# @param bind_group_layouts [Array<BindGroupLayout>] layouts by group index
|
|
10
|
+
# @raise [PipelineError] if native validation or creation fails
|
|
7
11
|
def initialize(device, label: nil, bind_group_layouts:)
|
|
8
12
|
@device = device
|
|
9
13
|
|
|
@@ -34,6 +38,10 @@ module WGPU
|
|
|
34
38
|
end
|
|
35
39
|
end
|
|
36
40
|
|
|
41
|
+
# Releases the native pipeline layout handle.
|
|
42
|
+
#
|
|
43
|
+
# Calling this method more than once has no effect.
|
|
44
|
+
# @return [void]
|
|
37
45
|
def release
|
|
38
46
|
return if @handle.null?
|
|
39
47
|
Native.wgpuPipelineLayoutRelease(@handle)
|
|
@@ -4,32 +4,23 @@ module WGPU
|
|
|
4
4
|
class RenderPipeline
|
|
5
5
|
attr_reader :handle
|
|
6
6
|
|
|
7
|
+
# Creates a render pipeline.
|
|
8
|
+
# @param device [Device] owning device
|
|
9
|
+
# @param layout [PipelineLayout, :auto, nil] pipeline layout
|
|
10
|
+
# @param vertex [Hash] programmable vertex stage descriptor
|
|
11
|
+
# @param fragment [Hash, nil] optional fragment stage descriptor
|
|
12
|
+
# @raise [PipelineError] if native validation or creation fails
|
|
7
13
|
def initialize(device, label: nil, layout:, vertex:, primitive: {}, depth_stencil: nil, multisample: {}, fragment: nil)
|
|
8
14
|
@device = device
|
|
9
|
-
@pointers =
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
setup_multisample_state(desc[:multisample], multisample)
|
|
19
|
-
|
|
20
|
-
if depth_stencil
|
|
21
|
-
ds_ptr = setup_depth_stencil_state(depth_stencil)
|
|
22
|
-
desc[:depth_stencil] = ds_ptr
|
|
23
|
-
else
|
|
24
|
-
desc[:depth_stencil] = nil
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
if fragment
|
|
28
|
-
frag_ptr = setup_fragment_state(fragment)
|
|
29
|
-
desc[:fragment] = frag_ptr
|
|
30
|
-
else
|
|
31
|
-
desc[:fragment] = nil
|
|
32
|
-
end
|
|
15
|
+
desc, @pointers = build_descriptor(
|
|
16
|
+
label:,
|
|
17
|
+
layout:,
|
|
18
|
+
vertex:,
|
|
19
|
+
primitive:,
|
|
20
|
+
depth_stencil:,
|
|
21
|
+
multisample:,
|
|
22
|
+
fragment:
|
|
23
|
+
)
|
|
33
24
|
|
|
34
25
|
device.push_error_scope(:validation)
|
|
35
26
|
@handle = Native.wgpuDeviceCreateRenderPipeline(device.handle, desc)
|
|
@@ -41,12 +32,20 @@ module WGPU
|
|
|
41
32
|
end
|
|
42
33
|
end
|
|
43
34
|
|
|
35
|
+
# Returns the bind group layout inferred or assigned at an index.
|
|
36
|
+
# @param index [Integer] bind group index
|
|
37
|
+
# @return [BindGroupLayout]
|
|
38
|
+
# @raise [PipelineError] if no native layout is returned
|
|
44
39
|
def get_bind_group_layout(index)
|
|
45
40
|
handle = Native.wgpuRenderPipelineGetBindGroupLayout(@handle, index)
|
|
46
41
|
raise PipelineError, "Failed to get bind group layout at index #{index}" if handle.null?
|
|
47
|
-
BindGroupLayout.from_handle(handle)
|
|
42
|
+
BindGroupLayout.from_handle(handle, device: @device)
|
|
48
43
|
end
|
|
49
44
|
|
|
45
|
+
# Releases the native render pipeline handle.
|
|
46
|
+
#
|
|
47
|
+
# Calling this method more than once has no effect.
|
|
48
|
+
# @return [void]
|
|
50
49
|
def release
|
|
51
50
|
return if @handle.null?
|
|
52
51
|
Native.wgpuRenderPipelineRelease(@handle)
|
|
@@ -55,31 +54,38 @@ module WGPU
|
|
|
55
54
|
|
|
56
55
|
private
|
|
57
56
|
|
|
58
|
-
def
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
57
|
+
def build_descriptor(label:, layout:, vertex:, primitive:, depth_stencil:, multisample:, fragment:)
|
|
58
|
+
@pointers = []
|
|
59
|
+
desc = Native::RenderPipelineDescriptor.new
|
|
60
|
+
desc[:next_in_chain] = nil
|
|
61
|
+
DescriptorHelpers.set_label(desc, label, keepalive: @pointers)
|
|
62
|
+
desc[:layout] = normalize_layout(layout)
|
|
63
|
+
|
|
64
|
+
setup_vertex_state(desc[:vertex], vertex)
|
|
65
|
+
setup_primitive_state(desc[:primitive], primitive)
|
|
66
|
+
setup_multisample_state(desc[:multisample], multisample)
|
|
67
|
+
desc[:depth_stencil] = depth_stencil ? setup_depth_stencil_state(depth_stencil) : nil
|
|
68
|
+
desc[:fragment] = fragment ? setup_fragment_state(fragment) : nil
|
|
69
|
+
|
|
70
|
+
[desc, @pointers]
|
|
68
71
|
end
|
|
69
72
|
|
|
70
73
|
def setup_vertex_state(vertex_state, vertex)
|
|
74
|
+
DescriptorHelpers.validate_keys!(
|
|
75
|
+
vertex,
|
|
76
|
+
allowed: %i[module entry_point constants buffers],
|
|
77
|
+
required: [:module],
|
|
78
|
+
context: "render pipeline vertex descriptor"
|
|
79
|
+
)
|
|
71
80
|
vertex_state[:next_in_chain] = nil
|
|
72
81
|
vertex_state[:module] = vertex[:module].handle
|
|
73
82
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
vertex_state[:constant_count] = 0
|
|
81
|
-
vertex_state[:constants] = nil
|
|
82
|
-
setup_constants(vertex_state, vertex[:constants])
|
|
83
|
+
DescriptorHelpers.set_nullable_string_view(
|
|
84
|
+
vertex_state[:entry_point],
|
|
85
|
+
vertex[:entry_point],
|
|
86
|
+
keepalive: @pointers
|
|
87
|
+
)
|
|
88
|
+
DescriptorHelpers.set_constants(vertex_state, vertex[:constants], keepalive: @pointers)
|
|
83
89
|
|
|
84
90
|
buffers = vertex[:buffers] || []
|
|
85
91
|
if buffers.empty?
|
|
@@ -97,8 +103,18 @@ module WGPU
|
|
|
97
103
|
@pointers << layouts_ptr
|
|
98
104
|
|
|
99
105
|
buffers.each_with_index do |buf, i|
|
|
100
|
-
|
|
101
|
-
|
|
106
|
+
DescriptorHelpers.validate_keys!(
|
|
107
|
+
buf,
|
|
108
|
+
allowed: %i[array_stride step_mode attributes],
|
|
109
|
+
required: [:array_stride],
|
|
110
|
+
context: "vertex buffer layout"
|
|
111
|
+
)
|
|
112
|
+
layout = Native::VertexBufferLayout.new(layouts_ptr + (i * Native::VertexBufferLayout.size))
|
|
113
|
+
layout[:step_mode] = Native::EnumHelper.coerce(
|
|
114
|
+
Native::VertexStepMode,
|
|
115
|
+
buf[:step_mode] || :vertex,
|
|
116
|
+
name: "vertex step mode"
|
|
117
|
+
)
|
|
102
118
|
layout[:array_stride] = buf[:array_stride]
|
|
103
119
|
|
|
104
120
|
attrs = buf[:attributes] || []
|
|
@@ -109,8 +125,18 @@ module WGPU
|
|
|
109
125
|
attrs_ptr = FFI::MemoryPointer.new(Native::VertexAttribute, attrs.size)
|
|
110
126
|
@pointers << attrs_ptr
|
|
111
127
|
attrs.each_with_index do |attr, j|
|
|
112
|
-
|
|
113
|
-
|
|
128
|
+
DescriptorHelpers.validate_keys!(
|
|
129
|
+
attr,
|
|
130
|
+
allowed: %i[format offset shader_location],
|
|
131
|
+
required: %i[format offset shader_location],
|
|
132
|
+
context: "vertex attribute"
|
|
133
|
+
)
|
|
134
|
+
a = Native::VertexAttribute.new(attrs_ptr + (j * Native::VertexAttribute.size))
|
|
135
|
+
a[:format] = Native::EnumHelper.coerce(
|
|
136
|
+
Native::VertexFormat,
|
|
137
|
+
attr[:format],
|
|
138
|
+
name: "vertex format"
|
|
139
|
+
)
|
|
114
140
|
a[:offset] = attr[:offset]
|
|
115
141
|
a[:shader_location] = attr[:shader_location]
|
|
116
142
|
end
|
|
@@ -123,15 +149,41 @@ module WGPU
|
|
|
123
149
|
end
|
|
124
150
|
|
|
125
151
|
def setup_primitive_state(primitive_state, primitive)
|
|
152
|
+
DescriptorHelpers.validate_keys!(
|
|
153
|
+
primitive,
|
|
154
|
+
allowed: %i[topology strip_index_format front_face cull_mode unclipped_depth],
|
|
155
|
+
context: "primitive state"
|
|
156
|
+
)
|
|
126
157
|
primitive_state[:next_in_chain] = nil
|
|
127
|
-
primitive_state[:topology] =
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
158
|
+
primitive_state[:topology] = Native::EnumHelper.coerce(
|
|
159
|
+
Native::PrimitiveTopology,
|
|
160
|
+
primitive[:topology] || :triangle_list,
|
|
161
|
+
name: "primitive topology"
|
|
162
|
+
)
|
|
163
|
+
primitive_state[:strip_index_format] = Native::EnumHelper.coerce(
|
|
164
|
+
Native::IndexFormat,
|
|
165
|
+
primitive[:strip_index_format] || :undefined,
|
|
166
|
+
name: "strip index format"
|
|
167
|
+
)
|
|
168
|
+
primitive_state[:front_face] = Native::EnumHelper.coerce(
|
|
169
|
+
Native::FrontFace,
|
|
170
|
+
primitive[:front_face] || :ccw,
|
|
171
|
+
name: "front face"
|
|
172
|
+
)
|
|
173
|
+
primitive_state[:cull_mode] = Native::EnumHelper.coerce(
|
|
174
|
+
Native::CullMode,
|
|
175
|
+
primitive[:cull_mode] || :none,
|
|
176
|
+
name: "cull mode"
|
|
177
|
+
)
|
|
131
178
|
primitive_state[:unclipped_depth] = primitive[:unclipped_depth] ? 1 : 0
|
|
132
179
|
end
|
|
133
180
|
|
|
134
181
|
def setup_multisample_state(multisample_state, multisample)
|
|
182
|
+
DescriptorHelpers.validate_keys!(
|
|
183
|
+
multisample,
|
|
184
|
+
allowed: %i[count mask alpha_to_coverage_enabled],
|
|
185
|
+
context: "multisample state"
|
|
186
|
+
)
|
|
135
187
|
multisample_state[:next_in_chain] = nil
|
|
136
188
|
multisample_state[:count] = multisample[:count] || 1
|
|
137
189
|
multisample_state[:mask] = multisample[:mask] || 0xFFFFFFFF
|
|
@@ -139,12 +191,29 @@ module WGPU
|
|
|
139
191
|
end
|
|
140
192
|
|
|
141
193
|
def setup_depth_stencil_state(depth_stencil)
|
|
194
|
+
DescriptorHelpers.validate_keys!(
|
|
195
|
+
depth_stencil,
|
|
196
|
+
allowed: %i[
|
|
197
|
+
format depth_write_enabled depth_compare stencil_front stencil_back
|
|
198
|
+
stencil_read_mask stencil_write_mask depth_bias depth_bias_slope_scale depth_bias_clamp
|
|
199
|
+
],
|
|
200
|
+
required: [:format],
|
|
201
|
+
context: "depth stencil state"
|
|
202
|
+
)
|
|
142
203
|
ds = Native::DepthStencilState.new
|
|
143
204
|
@pointers << ds
|
|
144
205
|
ds[:next_in_chain] = nil
|
|
145
|
-
ds[:format] =
|
|
206
|
+
ds[:format] = Native::EnumHelper.coerce(
|
|
207
|
+
Native::TextureFormat,
|
|
208
|
+
depth_stencil[:format],
|
|
209
|
+
name: "depth stencil format"
|
|
210
|
+
)
|
|
146
211
|
ds[:depth_write_enabled] = depth_stencil[:depth_write_enabled] ? 1 : 0
|
|
147
|
-
ds[:depth_compare] =
|
|
212
|
+
ds[:depth_compare] = Native::EnumHelper.coerce(
|
|
213
|
+
Native::CompareFunction,
|
|
214
|
+
depth_stencil[:depth_compare] || :always,
|
|
215
|
+
name: "depth compare function"
|
|
216
|
+
)
|
|
148
217
|
|
|
149
218
|
setup_stencil_face(ds[:stencil_front], depth_stencil[:stencil_front] || {})
|
|
150
219
|
setup_stencil_face(ds[:stencil_back], depth_stencil[:stencil_back] || {})
|
|
@@ -159,27 +228,51 @@ module WGPU
|
|
|
159
228
|
end
|
|
160
229
|
|
|
161
230
|
def setup_stencil_face(face, config)
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
231
|
+
DescriptorHelpers.validate_keys!(
|
|
232
|
+
config,
|
|
233
|
+
allowed: %i[compare fail_op depth_fail_op pass_op],
|
|
234
|
+
context: "stencil face state"
|
|
235
|
+
)
|
|
236
|
+
face[:compare] = Native::EnumHelper.coerce(
|
|
237
|
+
Native::CompareFunction,
|
|
238
|
+
config[:compare] || :always,
|
|
239
|
+
name: "stencil compare function"
|
|
240
|
+
)
|
|
241
|
+
face[:fail_op] = Native::EnumHelper.coerce(
|
|
242
|
+
Native::StencilOperation,
|
|
243
|
+
config[:fail_op] || :keep,
|
|
244
|
+
name: "stencil fail operation"
|
|
245
|
+
)
|
|
246
|
+
face[:depth_fail_op] = Native::EnumHelper.coerce(
|
|
247
|
+
Native::StencilOperation,
|
|
248
|
+
config[:depth_fail_op] || :keep,
|
|
249
|
+
name: "stencil depth fail operation"
|
|
250
|
+
)
|
|
251
|
+
face[:pass_op] = Native::EnumHelper.coerce(
|
|
252
|
+
Native::StencilOperation,
|
|
253
|
+
config[:pass_op] || :keep,
|
|
254
|
+
name: "stencil pass operation"
|
|
255
|
+
)
|
|
166
256
|
end
|
|
167
257
|
|
|
168
258
|
def setup_fragment_state(fragment)
|
|
259
|
+
DescriptorHelpers.validate_keys!(
|
|
260
|
+
fragment,
|
|
261
|
+
allowed: %i[module entry_point constants targets],
|
|
262
|
+
required: [:module],
|
|
263
|
+
context: "render pipeline fragment descriptor"
|
|
264
|
+
)
|
|
169
265
|
frag = Native::FragmentState.new
|
|
170
266
|
@pointers << frag
|
|
171
267
|
frag[:next_in_chain] = nil
|
|
172
268
|
frag[:module] = fragment[:module].handle
|
|
173
269
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
frag[:constant_count] = 0
|
|
181
|
-
frag[:constants] = nil
|
|
182
|
-
setup_constants(frag, fragment[:constants])
|
|
270
|
+
DescriptorHelpers.set_nullable_string_view(
|
|
271
|
+
frag[:entry_point],
|
|
272
|
+
fragment[:entry_point],
|
|
273
|
+
keepalive: @pointers
|
|
274
|
+
)
|
|
275
|
+
DescriptorHelpers.set_constants(frag, fragment[:constants], keepalive: @pointers)
|
|
183
276
|
|
|
184
277
|
targets = fragment[:targets] || []
|
|
185
278
|
if targets.empty?
|
|
@@ -199,9 +292,19 @@ module WGPU
|
|
|
199
292
|
@pointers << targets_ptr
|
|
200
293
|
|
|
201
294
|
targets.each_with_index do |target, i|
|
|
202
|
-
|
|
295
|
+
DescriptorHelpers.validate_keys!(
|
|
296
|
+
target,
|
|
297
|
+
allowed: %i[format blend write_mask],
|
|
298
|
+
required: [:format],
|
|
299
|
+
context: "color target state"
|
|
300
|
+
)
|
|
301
|
+
ct = Native::ColorTargetState.new(targets_ptr + (i * Native::ColorTargetState.size))
|
|
203
302
|
ct[:next_in_chain] = nil
|
|
204
|
-
ct[:format] =
|
|
303
|
+
ct[:format] = Native::EnumHelper.coerce(
|
|
304
|
+
Native::TextureFormat,
|
|
305
|
+
target[:format],
|
|
306
|
+
name: "color target format"
|
|
307
|
+
)
|
|
205
308
|
ct[:write_mask] = normalize_write_mask(target[:write_mask])
|
|
206
309
|
|
|
207
310
|
if target[:blend]
|
|
@@ -216,63 +319,57 @@ module WGPU
|
|
|
216
319
|
end
|
|
217
320
|
|
|
218
321
|
def setup_blend_state(blend)
|
|
322
|
+
DescriptorHelpers.validate_keys!(
|
|
323
|
+
blend,
|
|
324
|
+
allowed: %i[color alpha],
|
|
325
|
+
context: "blend state"
|
|
326
|
+
)
|
|
219
327
|
bs = Native::BlendState.new
|
|
220
328
|
@pointers << bs
|
|
221
329
|
|
|
222
330
|
color = blend[:color] || {}
|
|
223
|
-
bs[:color]
|
|
224
|
-
bs[:color][:src_factor] = color[:src_factor] || :one
|
|
225
|
-
bs[:color][:dst_factor] = color[:dst_factor] || :zero
|
|
331
|
+
setup_blend_component(bs[:color], color, "color")
|
|
226
332
|
|
|
227
333
|
alpha = blend[:alpha] || {}
|
|
228
|
-
bs[:alpha]
|
|
229
|
-
bs[:alpha][:src_factor] = alpha[:src_factor] || :one
|
|
230
|
-
bs[:alpha][:dst_factor] = alpha[:dst_factor] || :zero
|
|
334
|
+
setup_blend_component(bs[:alpha], alpha, "alpha")
|
|
231
335
|
|
|
232
336
|
bs.to_ptr
|
|
233
337
|
end
|
|
234
338
|
|
|
339
|
+
def setup_blend_component(component, config, name)
|
|
340
|
+
DescriptorHelpers.validate_keys!(
|
|
341
|
+
config,
|
|
342
|
+
allowed: %i[operation src_factor dst_factor],
|
|
343
|
+
context: "#{name} blend component"
|
|
344
|
+
)
|
|
345
|
+
component[:operation] = Native::EnumHelper.coerce(
|
|
346
|
+
Native::BlendOperation,
|
|
347
|
+
config[:operation] || :add,
|
|
348
|
+
name: "#{name} blend operation"
|
|
349
|
+
)
|
|
350
|
+
component[:src_factor] = Native::EnumHelper.coerce(
|
|
351
|
+
Native::BlendFactor,
|
|
352
|
+
config[:src_factor] || :one,
|
|
353
|
+
name: "#{name} source blend factor"
|
|
354
|
+
)
|
|
355
|
+
component[:dst_factor] = Native::EnumHelper.coerce(
|
|
356
|
+
Native::BlendFactor,
|
|
357
|
+
config[:dst_factor] || :zero,
|
|
358
|
+
name: "#{name} destination blend factor"
|
|
359
|
+
)
|
|
360
|
+
end
|
|
361
|
+
|
|
235
362
|
def normalize_write_mask(mask)
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
when Symbol
|
|
242
|
-
Native::ColorWriteMask[mask]
|
|
243
|
-
when Array
|
|
244
|
-
mask.reduce(0) { |acc, m| acc | Native::ColorWriteMask[m] }
|
|
245
|
-
else
|
|
246
|
-
raise ArgumentError, "Invalid write_mask: #{mask}"
|
|
247
|
-
end
|
|
363
|
+
Native::EnumHelper.coerce_flags(
|
|
364
|
+
Native::ColorWriteMask,
|
|
365
|
+
mask || :all,
|
|
366
|
+
name: "color write mask"
|
|
367
|
+
)
|
|
248
368
|
end
|
|
249
369
|
|
|
250
370
|
def normalize_layout(layout)
|
|
251
371
|
return nil if layout.nil? || layout == :auto || layout == "auto"
|
|
252
372
|
layout.handle
|
|
253
373
|
end
|
|
254
|
-
|
|
255
|
-
def setup_constants(stage_state, constants)
|
|
256
|
-
return if constants.nil? || constants.empty?
|
|
257
|
-
|
|
258
|
-
constants_ptr = FFI::MemoryPointer.new(Native::ConstantEntry, constants.size)
|
|
259
|
-
@pointers << constants_ptr
|
|
260
|
-
|
|
261
|
-
constants.each_with_index do |(key, value), i|
|
|
262
|
-
entry_ptr = constants_ptr + (i * Native::ConstantEntry.size)
|
|
263
|
-
entry = Native::ConstantEntry.new(entry_ptr)
|
|
264
|
-
entry[:next_in_chain] = nil
|
|
265
|
-
|
|
266
|
-
key_str = key.to_s
|
|
267
|
-
key_ptr = FFI::MemoryPointer.from_string(key_str)
|
|
268
|
-
@pointers << key_ptr
|
|
269
|
-
entry[:key][:data] = key_ptr
|
|
270
|
-
entry[:key][:length] = key_str.bytesize
|
|
271
|
-
entry[:value] = value.to_f
|
|
272
|
-
end
|
|
273
|
-
|
|
274
|
-
stage_state[:constant_count] = constants.size
|
|
275
|
-
stage_state[:constants] = constants_ptr
|
|
276
|
-
end
|
|
277
374
|
end
|
|
278
375
|
end
|
|
@@ -4,12 +4,19 @@ module WGPU
|
|
|
4
4
|
class ShaderModule
|
|
5
5
|
attr_reader :handle
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
# Creates a shader module from WGSL, GLSL, or SPIR-V input.
|
|
8
|
+
# @param device [Device] owning device
|
|
9
|
+
# @param code [String, Array<Integer>, nil] shader source or binary
|
|
10
|
+
# @param spirv [String, Array<Integer>, nil] explicit SPIR-V input
|
|
11
|
+
# @param validate [Boolean] whether to fetch and raise compilation errors
|
|
12
|
+
# @raise [ShaderError] if native validation, creation, or compilation fails
|
|
13
|
+
def initialize(device, label: nil, code: nil, spirv: nil, compilation_hints: [], validate: false)
|
|
8
14
|
@device = device
|
|
9
15
|
@pointers = []
|
|
10
|
-
compilation_hints
|
|
16
|
+
@compilation_hints = compilation_hints
|
|
17
|
+
source = select_source(code, spirv)
|
|
11
18
|
|
|
12
|
-
source_ptr = build_shader_source(
|
|
19
|
+
source_ptr = build_shader_source(source, label: label)
|
|
13
20
|
|
|
14
21
|
desc = Native::ShaderModuleDescriptor.new
|
|
15
22
|
desc[:next_in_chain] = source_ptr
|
|
@@ -29,42 +36,66 @@ module WGPU
|
|
|
29
36
|
|
|
30
37
|
if @handle.null? || (error[:type] && error[:type] != :no_error)
|
|
31
38
|
msg = error[:message] || "Failed to create shader module"
|
|
32
|
-
raise ShaderError, msg
|
|
39
|
+
raise ShaderError, shader_error_message(msg, label)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
return unless validate
|
|
43
|
+
|
|
44
|
+
begin
|
|
45
|
+
validate_compilation!(label)
|
|
46
|
+
rescue StandardError
|
|
47
|
+
release
|
|
48
|
+
raise
|
|
33
49
|
end
|
|
34
50
|
end
|
|
35
51
|
|
|
52
|
+
# Fetches compiler diagnostics for the shader.
|
|
53
|
+
# @return [Hash] request status and compilation messages
|
|
54
|
+
# @raise [ShaderError] if the pinned native library cannot provide diagnostics
|
|
36
55
|
def get_compilation_info
|
|
56
|
+
unless Native.compilation_info_available?
|
|
57
|
+
raise ShaderError,
|
|
58
|
+
"Shader compilation info is not implemented by wgpu-native #{Native::Distribution::VERSION}"
|
|
59
|
+
end
|
|
60
|
+
|
|
37
61
|
result_holder = { done: false, status: nil, messages: [] }
|
|
38
62
|
instance = @device.adapter&.instance
|
|
39
63
|
|
|
64
|
+
callback_lifetime_release = device_callback_lifetime_lease
|
|
65
|
+
callback_token = nil
|
|
40
66
|
callback = FFI::Function.new(
|
|
41
67
|
:void, [:uint32, :pointer, :pointer, :pointer]
|
|
42
68
|
) do |status, compilation_info_ptr, _userdata1, _userdata2|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
69
|
+
begin
|
|
70
|
+
result_holder[:status] = Native::CompilationInfoRequestStatus[status]
|
|
71
|
+
|
|
72
|
+
unless compilation_info_ptr.null?
|
|
73
|
+
info = Native::CompilationInfo.new(compilation_info_ptr)
|
|
74
|
+
count = info[:message_count]
|
|
75
|
+
if count > 0 && !info[:messages].null?
|
|
76
|
+
count.times do |i|
|
|
77
|
+
msg_ptr = info[:messages] + (i * Native::CompilationMessage.size)
|
|
78
|
+
msg = Native::CompilationMessage.new(msg_ptr)
|
|
79
|
+
message_text = if msg[:message][:data] && !msg[:message][:data].null? && msg[:message][:length] > 0
|
|
80
|
+
msg[:message][:data].read_string(msg[:message][:length])
|
|
81
|
+
else
|
|
82
|
+
""
|
|
83
|
+
end
|
|
84
|
+
result_holder[:messages] << CompilationMessage.new(
|
|
85
|
+
type: msg[:type],
|
|
86
|
+
message: message_text,
|
|
87
|
+
line_num: msg[:line_num],
|
|
88
|
+
line_pos: msg[:line_pos],
|
|
89
|
+
offset: msg[:offset],
|
|
90
|
+
length: msg[:length]
|
|
91
|
+
)
|
|
92
|
+
end
|
|
66
93
|
end
|
|
67
94
|
end
|
|
95
|
+
result_holder[:done] = true
|
|
96
|
+
ensure
|
|
97
|
+
CallbackKeepalive.release(self, callback_token)
|
|
98
|
+
callback_lifetime_release.call
|
|
68
99
|
end
|
|
69
100
|
end
|
|
70
101
|
|
|
@@ -75,7 +106,15 @@ module WGPU
|
|
|
75
106
|
callback_info[:userdata1] = nil
|
|
76
107
|
callback_info[:userdata2] = nil
|
|
77
108
|
|
|
78
|
-
|
|
109
|
+
callback_token = CallbackKeepalive.retain(self, callback)
|
|
110
|
+
future =
|
|
111
|
+
begin
|
|
112
|
+
Native.wgpuShaderModuleGetCompilationInfo(@handle, callback_info)
|
|
113
|
+
rescue StandardError
|
|
114
|
+
CallbackKeepalive.release(self, callback_token)
|
|
115
|
+
callback_lifetime_release.call
|
|
116
|
+
raise
|
|
117
|
+
end
|
|
79
118
|
AsyncWaiter.wait(status_holder: result_holder, instance: instance, device: @device, future: future)
|
|
80
119
|
|
|
81
120
|
{
|
|
@@ -84,10 +123,16 @@ module WGPU
|
|
|
84
123
|
}
|
|
85
124
|
end
|
|
86
125
|
|
|
126
|
+
# Fetches compiler diagnostics on a background task.
|
|
127
|
+
# @return [AsyncTask] task yielding compilation information
|
|
87
128
|
def get_compilation_info_async
|
|
88
129
|
AsyncTask.new { get_compilation_info }
|
|
89
130
|
end
|
|
90
131
|
|
|
132
|
+
# Releases the native shader module handle.
|
|
133
|
+
#
|
|
134
|
+
# Calling this method more than once has no effect.
|
|
135
|
+
# @return [void]
|
|
91
136
|
def release
|
|
92
137
|
return if @handle.null?
|
|
93
138
|
Native.wgpuShaderModuleRelease(@handle)
|
|
@@ -96,6 +141,28 @@ module WGPU
|
|
|
96
141
|
|
|
97
142
|
private
|
|
98
143
|
|
|
144
|
+
def select_source(code, spirv)
|
|
145
|
+
sources = [code, spirv].reject(&:nil?)
|
|
146
|
+
raise ArgumentError, "provide exactly one of code: or spirv:" unless sources.one?
|
|
147
|
+
|
|
148
|
+
sources.first
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def validate_compilation!(label)
|
|
152
|
+
info = get_compilation_info
|
|
153
|
+
errors = info[:messages].select { |message| message.type == :error }
|
|
154
|
+
return if errors.empty?
|
|
155
|
+
|
|
156
|
+
details = errors.map(&:to_s).join("\n")
|
|
157
|
+
release
|
|
158
|
+
raise ShaderError, shader_error_message(details, label)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def shader_error_message(message, label)
|
|
162
|
+
context = label ? " for #{label.inspect}" : ""
|
|
163
|
+
"Shader compilation failed#{context}: #{message}"
|
|
164
|
+
end
|
|
165
|
+
|
|
99
166
|
def build_shader_source(code, label:)
|
|
100
167
|
if code.is_a?(String)
|
|
101
168
|
if spirv_binary?(code)
|