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,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WGPU
|
|
4
|
+
class BindGroupLayout
|
|
5
|
+
attr_reader :handle
|
|
6
|
+
|
|
7
|
+
def self.from_handle(handle)
|
|
8
|
+
layout = allocate
|
|
9
|
+
layout.instance_variable_set(:@handle, handle)
|
|
10
|
+
layout.instance_variable_set(:@device, nil)
|
|
11
|
+
layout
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def initialize(device, label: nil, entries:)
|
|
15
|
+
@device = device
|
|
16
|
+
|
|
17
|
+
entries_array = entries.map { |e| create_entry(e) }
|
|
18
|
+
entries_ptr = FFI::MemoryPointer.new(Native::BindGroupLayoutEntry, entries_array.size)
|
|
19
|
+
entries_array.each_with_index do |entry, i|
|
|
20
|
+
offset = i * Native::BindGroupLayoutEntry.size
|
|
21
|
+
(entries_ptr + offset).put_bytes(0, entry.pointer.read_bytes(Native::BindGroupLayoutEntry.size))
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
desc = Native::BindGroupLayoutDescriptor.new
|
|
25
|
+
desc[:next_in_chain] = nil
|
|
26
|
+
if label
|
|
27
|
+
label_ptr = FFI::MemoryPointer.from_string(label)
|
|
28
|
+
desc[:label][:data] = label_ptr
|
|
29
|
+
desc[:label][:length] = label.bytesize
|
|
30
|
+
else
|
|
31
|
+
desc[:label][:data] = nil
|
|
32
|
+
desc[:label][:length] = 0
|
|
33
|
+
end
|
|
34
|
+
desc[:entry_count] = entries_array.size
|
|
35
|
+
desc[:entries] = entries_ptr
|
|
36
|
+
|
|
37
|
+
device.push_error_scope(:validation)
|
|
38
|
+
@handle = Native.wgpuDeviceCreateBindGroupLayout(device.handle, desc)
|
|
39
|
+
error = device.pop_error_scope
|
|
40
|
+
|
|
41
|
+
if @handle.null? || (error[:type] && error[:type] != :no_error)
|
|
42
|
+
msg = error[:message] || "Failed to create bind group layout"
|
|
43
|
+
raise PipelineError, msg
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def release
|
|
48
|
+
return if @handle.null?
|
|
49
|
+
Native.wgpuBindGroupLayoutRelease(@handle)
|
|
50
|
+
@handle = FFI::Pointer::NULL
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def create_entry(entry_hash)
|
|
56
|
+
entry = Native::BindGroupLayoutEntry.new
|
|
57
|
+
entry[:next_in_chain] = nil
|
|
58
|
+
entry[:binding] = entry_hash[:binding]
|
|
59
|
+
entry[:visibility] = normalize_visibility(entry_hash[:visibility])
|
|
60
|
+
|
|
61
|
+
entry[:buffer][:next_in_chain] = nil
|
|
62
|
+
entry[:buffer][:type] = :binding_not_used
|
|
63
|
+
entry[:buffer][:has_dynamic_offset] = 0
|
|
64
|
+
entry[:buffer][:min_binding_size] = 0
|
|
65
|
+
|
|
66
|
+
entry[:sampler][:next_in_chain] = nil
|
|
67
|
+
entry[:sampler][:type] = :binding_not_used
|
|
68
|
+
|
|
69
|
+
entry[:texture][:next_in_chain] = nil
|
|
70
|
+
entry[:texture][:sample_type] = :binding_not_used
|
|
71
|
+
entry[:texture][:view_dimension] = :undefined
|
|
72
|
+
entry[:texture][:multisampled] = 0
|
|
73
|
+
|
|
74
|
+
entry[:storage_texture][:next_in_chain] = nil
|
|
75
|
+
entry[:storage_texture][:access] = :binding_not_used
|
|
76
|
+
entry[:storage_texture][:format] = :undefined
|
|
77
|
+
entry[:storage_texture][:view_dimension] = :undefined
|
|
78
|
+
|
|
79
|
+
if entry_hash[:buffer]
|
|
80
|
+
buffer_info = entry_hash[:buffer]
|
|
81
|
+
entry[:buffer][:type] = buffer_info[:type] || :storage
|
|
82
|
+
entry[:buffer][:has_dynamic_offset] = buffer_info[:has_dynamic_offset] ? 1 : 0
|
|
83
|
+
entry[:buffer][:min_binding_size] = buffer_info[:min_binding_size] || 0
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
if entry_hash[:sampler]
|
|
87
|
+
sampler_info = entry_hash[:sampler]
|
|
88
|
+
entry[:sampler][:type] = sampler_info[:type] || :filtering
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
if entry_hash[:texture]
|
|
92
|
+
texture_info = entry_hash[:texture]
|
|
93
|
+
entry[:texture][:sample_type] = texture_info[:sample_type] || :float
|
|
94
|
+
entry[:texture][:view_dimension] = texture_info[:view_dimension] || :d2
|
|
95
|
+
entry[:texture][:multisampled] = texture_info[:multisampled] ? 1 : 0
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
if entry_hash[:storage_texture]
|
|
99
|
+
st_info = entry_hash[:storage_texture]
|
|
100
|
+
entry[:storage_texture][:access] = st_info[:access] || :write_only
|
|
101
|
+
entry[:storage_texture][:format] = st_info[:format]
|
|
102
|
+
entry[:storage_texture][:view_dimension] = st_info[:view_dimension] || :d2
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
entry
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def normalize_visibility(visibility)
|
|
109
|
+
case visibility
|
|
110
|
+
when Integer
|
|
111
|
+
visibility
|
|
112
|
+
when Symbol
|
|
113
|
+
Native::ShaderStage[visibility]
|
|
114
|
+
when Array
|
|
115
|
+
visibility.reduce(0) { |acc, v| acc | Native::ShaderStage[v] }
|
|
116
|
+
else
|
|
117
|
+
raise ArgumentError, "Invalid visibility: #{visibility}"
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WGPU
|
|
4
|
+
class ComputePipeline
|
|
5
|
+
attr_reader :handle
|
|
6
|
+
|
|
7
|
+
def initialize(device, label: nil, layout:, compute:)
|
|
8
|
+
@device = device
|
|
9
|
+
|
|
10
|
+
entry_point = compute[:entry_point] || "main"
|
|
11
|
+
entry_point_ptr = FFI::MemoryPointer.from_string(entry_point)
|
|
12
|
+
|
|
13
|
+
desc = Native::ComputePipelineDescriptor.new
|
|
14
|
+
desc[:next_in_chain] = nil
|
|
15
|
+
if label
|
|
16
|
+
label_ptr = FFI::MemoryPointer.from_string(label)
|
|
17
|
+
desc[:label][:data] = label_ptr
|
|
18
|
+
desc[:label][:length] = label.bytesize
|
|
19
|
+
else
|
|
20
|
+
desc[:label][:data] = nil
|
|
21
|
+
desc[:label][:length] = 0
|
|
22
|
+
end
|
|
23
|
+
desc[:layout] = layout.handle
|
|
24
|
+
|
|
25
|
+
desc[:compute][:next_in_chain] = nil
|
|
26
|
+
desc[:compute][:module] = compute[:module].handle
|
|
27
|
+
desc[:compute][:entry_point][:data] = entry_point_ptr
|
|
28
|
+
desc[:compute][:entry_point][:length] = entry_point.bytesize
|
|
29
|
+
desc[:compute][:constant_count] = 0
|
|
30
|
+
desc[:compute][:constants] = nil
|
|
31
|
+
|
|
32
|
+
device.push_error_scope(:validation)
|
|
33
|
+
@handle = Native.wgpuDeviceCreateComputePipeline(device.handle, desc)
|
|
34
|
+
error = device.pop_error_scope
|
|
35
|
+
|
|
36
|
+
if @handle.null? || (error[:type] && error[:type] != :no_error)
|
|
37
|
+
msg = error[:message] || "Failed to create compute pipeline"
|
|
38
|
+
raise PipelineError, msg
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def get_bind_group_layout(index)
|
|
43
|
+
handle = Native.wgpuComputePipelineGetBindGroupLayout(@handle, index)
|
|
44
|
+
raise PipelineError, "Failed to get bind group layout at index #{index}" if handle.null?
|
|
45
|
+
BindGroupLayout.from_handle(handle)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def release
|
|
49
|
+
return if @handle.null?
|
|
50
|
+
Native.wgpuComputePipelineRelease(@handle)
|
|
51
|
+
@handle = FFI::Pointer::NULL
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WGPU
|
|
4
|
+
class PipelineLayout
|
|
5
|
+
attr_reader :handle
|
|
6
|
+
|
|
7
|
+
def initialize(device, label: nil, bind_group_layouts:)
|
|
8
|
+
@device = device
|
|
9
|
+
|
|
10
|
+
layouts = Array(bind_group_layouts)
|
|
11
|
+
layouts_ptr = FFI::MemoryPointer.new(:pointer, layouts.size)
|
|
12
|
+
layouts_ptr.write_array_of_pointer(layouts.map(&:handle))
|
|
13
|
+
|
|
14
|
+
desc = Native::PipelineLayoutDescriptor.new
|
|
15
|
+
desc[:next_in_chain] = nil
|
|
16
|
+
if label
|
|
17
|
+
label_ptr = FFI::MemoryPointer.from_string(label)
|
|
18
|
+
desc[:label][:data] = label_ptr
|
|
19
|
+
desc[:label][:length] = label.bytesize
|
|
20
|
+
else
|
|
21
|
+
desc[:label][:data] = nil
|
|
22
|
+
desc[:label][:length] = 0
|
|
23
|
+
end
|
|
24
|
+
desc[:bind_group_layout_count] = layouts.size
|
|
25
|
+
desc[:bind_group_layouts] = layouts_ptr
|
|
26
|
+
|
|
27
|
+
device.push_error_scope(:validation)
|
|
28
|
+
@handle = Native.wgpuDeviceCreatePipelineLayout(device.handle, desc)
|
|
29
|
+
error = device.pop_error_scope
|
|
30
|
+
|
|
31
|
+
if @handle.null? || (error[:type] && error[:type] != :no_error)
|
|
32
|
+
msg = error[:message] || "Failed to create pipeline layout"
|
|
33
|
+
raise PipelineError, msg
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def release
|
|
38
|
+
return if @handle.null?
|
|
39
|
+
Native.wgpuPipelineLayoutRelease(@handle)
|
|
40
|
+
@handle = FFI::Pointer::NULL
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WGPU
|
|
4
|
+
class RenderPipeline
|
|
5
|
+
attr_reader :handle
|
|
6
|
+
|
|
7
|
+
def initialize(device, label: nil, layout:, vertex:, primitive: {}, depth_stencil: nil, multisample: {}, fragment: nil)
|
|
8
|
+
@device = device
|
|
9
|
+
@pointers = []
|
|
10
|
+
|
|
11
|
+
desc = Native::RenderPipelineDescriptor.new
|
|
12
|
+
desc[:next_in_chain] = nil
|
|
13
|
+
setup_label(desc, label)
|
|
14
|
+
desc[:layout] = layout.handle
|
|
15
|
+
|
|
16
|
+
setup_vertex_state(desc[:vertex], vertex)
|
|
17
|
+
setup_primitive_state(desc[:primitive], primitive)
|
|
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
|
|
33
|
+
|
|
34
|
+
device.push_error_scope(:validation)
|
|
35
|
+
@handle = Native.wgpuDeviceCreateRenderPipeline(device.handle, desc)
|
|
36
|
+
error = device.pop_error_scope
|
|
37
|
+
|
|
38
|
+
if @handle.null? || (error[:type] && error[:type] != :no_error)
|
|
39
|
+
msg = error[:message] || "Failed to create render pipeline"
|
|
40
|
+
raise PipelineError, msg
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def get_bind_group_layout(index)
|
|
45
|
+
handle = Native.wgpuRenderPipelineGetBindGroupLayout(@handle, index)
|
|
46
|
+
raise PipelineError, "Failed to get bind group layout at index #{index}" if handle.null?
|
|
47
|
+
BindGroupLayout.from_handle(handle)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def release
|
|
51
|
+
return if @handle.null?
|
|
52
|
+
Native.wgpuRenderPipelineRelease(@handle)
|
|
53
|
+
@handle = FFI::Pointer::NULL
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def setup_label(desc, label)
|
|
59
|
+
if label
|
|
60
|
+
ptr = FFI::MemoryPointer.from_string(label)
|
|
61
|
+
@pointers << ptr
|
|
62
|
+
desc[:label][:data] = ptr
|
|
63
|
+
desc[:label][:length] = label.bytesize
|
|
64
|
+
else
|
|
65
|
+
desc[:label][:data] = nil
|
|
66
|
+
desc[:label][:length] = 0
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def setup_vertex_state(vertex_state, vertex)
|
|
71
|
+
vertex_state[:next_in_chain] = nil
|
|
72
|
+
vertex_state[:module] = vertex[:module].handle
|
|
73
|
+
|
|
74
|
+
entry_point = vertex[:entry_point] || "main"
|
|
75
|
+
entry_ptr = FFI::MemoryPointer.from_string(entry_point)
|
|
76
|
+
@pointers << entry_ptr
|
|
77
|
+
vertex_state[:entry_point][:data] = entry_ptr
|
|
78
|
+
vertex_state[:entry_point][:length] = entry_point.bytesize
|
|
79
|
+
|
|
80
|
+
vertex_state[:constant_count] = 0
|
|
81
|
+
vertex_state[:constants] = nil
|
|
82
|
+
|
|
83
|
+
buffers = vertex[:buffers] || []
|
|
84
|
+
if buffers.empty?
|
|
85
|
+
vertex_state[:buffer_count] = 0
|
|
86
|
+
vertex_state[:buffers] = nil
|
|
87
|
+
else
|
|
88
|
+
buffer_layouts = setup_vertex_buffer_layouts(buffers)
|
|
89
|
+
vertex_state[:buffer_count] = buffers.size
|
|
90
|
+
vertex_state[:buffers] = buffer_layouts
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def setup_vertex_buffer_layouts(buffers)
|
|
95
|
+
layouts_ptr = FFI::MemoryPointer.new(Native::VertexBufferLayout, buffers.size)
|
|
96
|
+
@pointers << layouts_ptr
|
|
97
|
+
|
|
98
|
+
buffers.each_with_index do |buf, i|
|
|
99
|
+
layout = Native::VertexBufferLayout.new(layouts_ptr + i * Native::VertexBufferLayout.size)
|
|
100
|
+
layout[:array_stride] = buf[:array_stride]
|
|
101
|
+
layout[:step_mode] = buf[:step_mode] || :vertex
|
|
102
|
+
|
|
103
|
+
attrs = buf[:attributes] || []
|
|
104
|
+
if attrs.empty?
|
|
105
|
+
layout[:attribute_count] = 0
|
|
106
|
+
layout[:attributes] = nil
|
|
107
|
+
else
|
|
108
|
+
attrs_ptr = FFI::MemoryPointer.new(Native::VertexAttribute, attrs.size)
|
|
109
|
+
@pointers << attrs_ptr
|
|
110
|
+
attrs.each_with_index do |attr, j|
|
|
111
|
+
a = Native::VertexAttribute.new(attrs_ptr + j * Native::VertexAttribute.size)
|
|
112
|
+
a[:format] = attr[:format]
|
|
113
|
+
a[:offset] = attr[:offset]
|
|
114
|
+
a[:shader_location] = attr[:shader_location]
|
|
115
|
+
end
|
|
116
|
+
layout[:attribute_count] = attrs.size
|
|
117
|
+
layout[:attributes] = attrs_ptr
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
layouts_ptr
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def setup_primitive_state(primitive_state, primitive)
|
|
125
|
+
primitive_state[:next_in_chain] = nil
|
|
126
|
+
primitive_state[:topology] = primitive[:topology] || :triangle_list
|
|
127
|
+
primitive_state[:strip_index_format] = primitive[:strip_index_format] || :undefined
|
|
128
|
+
primitive_state[:front_face] = primitive[:front_face] || :ccw
|
|
129
|
+
primitive_state[:cull_mode] = primitive[:cull_mode] || :none
|
|
130
|
+
primitive_state[:unclipped_depth] = primitive[:unclipped_depth] ? 1 : 0
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def setup_multisample_state(multisample_state, multisample)
|
|
134
|
+
multisample_state[:next_in_chain] = nil
|
|
135
|
+
multisample_state[:count] = multisample[:count] || 1
|
|
136
|
+
multisample_state[:mask] = multisample[:mask] || 0xFFFFFFFF
|
|
137
|
+
multisample_state[:alpha_to_coverage_enabled] = multisample[:alpha_to_coverage_enabled] ? 1 : 0
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def setup_depth_stencil_state(depth_stencil)
|
|
141
|
+
ds = Native::DepthStencilState.new
|
|
142
|
+
@pointers << ds
|
|
143
|
+
ds[:next_in_chain] = nil
|
|
144
|
+
ds[:format] = depth_stencil[:format]
|
|
145
|
+
ds[:depth_write_enabled] = depth_stencil[:depth_write_enabled] ? 1 : 0
|
|
146
|
+
ds[:depth_compare] = depth_stencil[:depth_compare] || :always
|
|
147
|
+
|
|
148
|
+
setup_stencil_face(ds[:stencil_front], depth_stencil[:stencil_front] || {})
|
|
149
|
+
setup_stencil_face(ds[:stencil_back], depth_stencil[:stencil_back] || {})
|
|
150
|
+
|
|
151
|
+
ds[:stencil_read_mask] = depth_stencil[:stencil_read_mask] || 0xFFFFFFFF
|
|
152
|
+
ds[:stencil_write_mask] = depth_stencil[:stencil_write_mask] || 0xFFFFFFFF
|
|
153
|
+
ds[:depth_bias] = depth_stencil[:depth_bias] || 0
|
|
154
|
+
ds[:depth_bias_slope_scale] = depth_stencil[:depth_bias_slope_scale] || 0.0
|
|
155
|
+
ds[:depth_bias_clamp] = depth_stencil[:depth_bias_clamp] || 0.0
|
|
156
|
+
|
|
157
|
+
ds.to_ptr
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def setup_stencil_face(face, config)
|
|
161
|
+
face[:compare] = config[:compare] || :always
|
|
162
|
+
face[:fail_op] = config[:fail_op] || :keep
|
|
163
|
+
face[:depth_fail_op] = config[:depth_fail_op] || :keep
|
|
164
|
+
face[:pass_op] = config[:pass_op] || :keep
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def setup_fragment_state(fragment)
|
|
168
|
+
frag = Native::FragmentState.new
|
|
169
|
+
@pointers << frag
|
|
170
|
+
frag[:next_in_chain] = nil
|
|
171
|
+
frag[:module] = fragment[:module].handle
|
|
172
|
+
|
|
173
|
+
entry_point = fragment[:entry_point] || "main"
|
|
174
|
+
entry_ptr = FFI::MemoryPointer.from_string(entry_point)
|
|
175
|
+
@pointers << entry_ptr
|
|
176
|
+
frag[:entry_point][:data] = entry_ptr
|
|
177
|
+
frag[:entry_point][:length] = entry_point.bytesize
|
|
178
|
+
|
|
179
|
+
frag[:constant_count] = 0
|
|
180
|
+
frag[:constants] = nil
|
|
181
|
+
|
|
182
|
+
targets = fragment[:targets] || []
|
|
183
|
+
if targets.empty?
|
|
184
|
+
frag[:target_count] = 0
|
|
185
|
+
frag[:targets] = nil
|
|
186
|
+
else
|
|
187
|
+
targets_ptr = setup_color_targets(targets)
|
|
188
|
+
frag[:target_count] = targets.size
|
|
189
|
+
frag[:targets] = targets_ptr
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
frag.to_ptr
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def setup_color_targets(targets)
|
|
196
|
+
targets_ptr = FFI::MemoryPointer.new(Native::ColorTargetState, targets.size)
|
|
197
|
+
@pointers << targets_ptr
|
|
198
|
+
|
|
199
|
+
targets.each_with_index do |target, i|
|
|
200
|
+
ct = Native::ColorTargetState.new(targets_ptr + i * Native::ColorTargetState.size)
|
|
201
|
+
ct[:next_in_chain] = nil
|
|
202
|
+
ct[:format] = target[:format]
|
|
203
|
+
ct[:write_mask] = normalize_write_mask(target[:write_mask])
|
|
204
|
+
|
|
205
|
+
if target[:blend]
|
|
206
|
+
blend_ptr = setup_blend_state(target[:blend])
|
|
207
|
+
ct[:blend] = blend_ptr
|
|
208
|
+
else
|
|
209
|
+
ct[:blend] = nil
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
targets_ptr
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def setup_blend_state(blend)
|
|
217
|
+
bs = Native::BlendState.new
|
|
218
|
+
@pointers << bs
|
|
219
|
+
|
|
220
|
+
color = blend[:color] || {}
|
|
221
|
+
bs[:color][:operation] = color[:operation] || :add
|
|
222
|
+
bs[:color][:src_factor] = color[:src_factor] || :one
|
|
223
|
+
bs[:color][:dst_factor] = color[:dst_factor] || :zero
|
|
224
|
+
|
|
225
|
+
alpha = blend[:alpha] || {}
|
|
226
|
+
bs[:alpha][:operation] = alpha[:operation] || :add
|
|
227
|
+
bs[:alpha][:src_factor] = alpha[:src_factor] || :one
|
|
228
|
+
bs[:alpha][:dst_factor] = alpha[:dst_factor] || :zero
|
|
229
|
+
|
|
230
|
+
bs.to_ptr
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def normalize_write_mask(mask)
|
|
234
|
+
case mask
|
|
235
|
+
when nil
|
|
236
|
+
Native::ColorWriteMask[:all]
|
|
237
|
+
when Integer
|
|
238
|
+
mask
|
|
239
|
+
when Symbol
|
|
240
|
+
Native::ColorWriteMask[mask]
|
|
241
|
+
when Array
|
|
242
|
+
mask.reduce(0) { |acc, m| acc | Native::ColorWriteMask[m] }
|
|
243
|
+
else
|
|
244
|
+
raise ArgumentError, "Invalid write_mask: #{mask}"
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WGPU
|
|
4
|
+
class ShaderModule
|
|
5
|
+
attr_reader :handle
|
|
6
|
+
|
|
7
|
+
def initialize(device, label: nil, code:)
|
|
8
|
+
@device = device
|
|
9
|
+
@code_ptr = FFI::MemoryPointer.from_string(code)
|
|
10
|
+
|
|
11
|
+
wgsl_desc = Native::ShaderSourceWGSL.new
|
|
12
|
+
wgsl_desc[:chain][:next] = nil
|
|
13
|
+
wgsl_desc[:chain][:s_type] = :shader_source_wgsl
|
|
14
|
+
wgsl_desc[:code][:data] = @code_ptr
|
|
15
|
+
wgsl_desc[:code][:length] = code.bytesize
|
|
16
|
+
|
|
17
|
+
desc = Native::ShaderModuleDescriptor.new
|
|
18
|
+
desc[:next_in_chain] = wgsl_desc.to_ptr
|
|
19
|
+
if label
|
|
20
|
+
label_ptr = FFI::MemoryPointer.from_string(label)
|
|
21
|
+
desc[:label][:data] = label_ptr
|
|
22
|
+
desc[:label][:length] = label.bytesize
|
|
23
|
+
else
|
|
24
|
+
desc[:label][:data] = nil
|
|
25
|
+
desc[:label][:length] = 0
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
device.push_error_scope(:validation)
|
|
29
|
+
@handle = Native.wgpuDeviceCreateShaderModule(device.handle, desc)
|
|
30
|
+
error = device.pop_error_scope
|
|
31
|
+
|
|
32
|
+
if @handle.null? || (error[:type] && error[:type] != :no_error)
|
|
33
|
+
msg = error[:message] || "Failed to create shader module"
|
|
34
|
+
raise ShaderError, msg
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def get_compilation_info
|
|
39
|
+
result_holder = { done: false, status: nil, messages: [] }
|
|
40
|
+
|
|
41
|
+
callback = FFI::Function.new(
|
|
42
|
+
:void, [:uint32, :pointer, :pointer, :pointer]
|
|
43
|
+
) do |status, compilation_info_ptr, _userdata1, _userdata2|
|
|
44
|
+
result_holder[:done] = true
|
|
45
|
+
result_holder[:status] = Native::CompilationInfoRequestStatus[status]
|
|
46
|
+
|
|
47
|
+
unless compilation_info_ptr.null?
|
|
48
|
+
info = Native::CompilationInfo.new(compilation_info_ptr)
|
|
49
|
+
count = info[:message_count]
|
|
50
|
+
if count > 0 && !info[:messages].null?
|
|
51
|
+
count.times do |i|
|
|
52
|
+
msg_ptr = info[:messages] + (i * Native::CompilationMessage.size)
|
|
53
|
+
msg = Native::CompilationMessage.new(msg_ptr)
|
|
54
|
+
message_text = if msg[:message][:data] && !msg[:message][:data].null? && msg[:message][:length] > 0
|
|
55
|
+
msg[:message][:data].read_string(msg[:message][:length])
|
|
56
|
+
else
|
|
57
|
+
""
|
|
58
|
+
end
|
|
59
|
+
result_holder[:messages] << {
|
|
60
|
+
type: msg[:type],
|
|
61
|
+
message: message_text,
|
|
62
|
+
line_num: msg[:line_num],
|
|
63
|
+
line_pos: msg[:line_pos],
|
|
64
|
+
offset: msg[:offset],
|
|
65
|
+
length: msg[:length]
|
|
66
|
+
}
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
callback_info = Native::CompilationInfoCallbackInfo.new
|
|
73
|
+
callback_info[:next_in_chain] = nil
|
|
74
|
+
callback_info[:mode] = 1
|
|
75
|
+
callback_info[:callback] = callback
|
|
76
|
+
callback_info[:userdata1] = nil
|
|
77
|
+
callback_info[:userdata2] = nil
|
|
78
|
+
|
|
79
|
+
Native.wgpuShaderModuleGetCompilationInfo(@handle, callback_info)
|
|
80
|
+
|
|
81
|
+
until result_holder[:done]
|
|
82
|
+
Native.wgpuDevicePoll(@device.handle, 0, nil)
|
|
83
|
+
sleep(0.001)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
{
|
|
87
|
+
status: result_holder[:status],
|
|
88
|
+
messages: result_holder[:messages]
|
|
89
|
+
}
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def release
|
|
93
|
+
return if @handle.null?
|
|
94
|
+
Native.wgpuShaderModuleRelease(@handle)
|
|
95
|
+
@handle = FFI::Pointer::NULL
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|