wgpu 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,184 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WGPU
4
+ class Buffer
5
+ attr_reader :handle, :size, :usage
6
+
7
+ def initialize(device, label: nil, size:, usage:, mapped_at_creation: false)
8
+ @device = device
9
+ @size = size
10
+ @usage = normalize_usage(usage)
11
+ @mapped = mapped_at_creation
12
+
13
+ desc = Native::BufferDescriptor.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[:usage] = @usage
24
+ desc[:size] = size
25
+ desc[:mapped_at_creation] = mapped_at_creation ? 1 : 0
26
+
27
+ device.push_error_scope(:validation)
28
+ @handle = Native.wgpuDeviceCreateBuffer(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 buffer"
33
+ raise BufferError, msg
34
+ end
35
+ end
36
+
37
+ def write(data, offset: 0)
38
+ ptr, byte_size = data_to_pointer(data)
39
+ Native.wgpuQueueWriteBuffer(@device.queue.handle, @handle, offset, ptr, byte_size)
40
+ end
41
+
42
+ def mapped_range(offset: 0, size: nil)
43
+ raise BufferError, "Buffer is not mapped" unless @mapped
44
+
45
+ size ||= @size - offset
46
+ ptr = Native.wgpuBufferGetMappedRange(@handle, offset, size)
47
+ raise BufferError, "Failed to get mapped range" if ptr.null?
48
+
49
+ BufferMappedRange.new(ptr, size)
50
+ end
51
+
52
+ def unmap
53
+ Native.wgpuBufferUnmap(@handle)
54
+ @mapped = false
55
+ end
56
+
57
+ def map_async(mode, offset: 0, size: nil)
58
+ size ||= @size - offset
59
+ mode_flag = case mode
60
+ when :read then Native::MapMode[:read]
61
+ when :write then Native::MapMode[:write]
62
+ when Integer then mode
63
+ else raise ArgumentError, "Invalid map mode: #{mode}"
64
+ end
65
+
66
+ status_holder = { done: false, status: nil }
67
+
68
+ callback = FFI::Function.new(:void, [:uint32, :pointer]) do |status, _userdata|
69
+ status_holder[:done] = true
70
+ status_holder[:status] = Native::MapAsyncStatus[status]
71
+ end
72
+
73
+ callback_info = Native::BufferMapCallbackInfo.new
74
+ callback_info[:next_in_chain] = nil
75
+ callback_info[:mode] = 1
76
+ callback_info[:callback] = callback
77
+ callback_info[:userdata] = nil
78
+
79
+ Native.wgpuBufferMapAsync(@handle, mode_flag, offset, size, callback_info)
80
+
81
+ until status_holder[:done]
82
+ Native.wgpuDevicePoll(@device.handle, 0, nil)
83
+ sleep(0.001)
84
+ end
85
+
86
+ if status_holder[:status] == :success
87
+ @mapped = true
88
+ true
89
+ else
90
+ raise BufferError, "Failed to map buffer: #{status_holder[:status]}"
91
+ end
92
+ end
93
+
94
+ def read_mapped_data(offset: 0, size: nil)
95
+ raise BufferError, "Buffer is not mapped" unless @mapped
96
+
97
+ size ||= @size - offset
98
+ ptr = Native.wgpuBufferGetConstMappedRange(@handle, offset, size)
99
+ raise BufferError, "Failed to get mapped range" if ptr.null?
100
+
101
+ ptr.read_bytes(size)
102
+ end
103
+
104
+ def read_mapped_floats(offset: 0, count: nil)
105
+ raise BufferError, "Buffer is not mapped" unless @mapped
106
+
107
+ size = count ? count * 4 : @size - offset
108
+ ptr = Native.wgpuBufferGetConstMappedRange(@handle, offset, size)
109
+ raise BufferError, "Failed to get mapped range" if ptr.null?
110
+
111
+ ptr.read_array_of_float(size / 4)
112
+ end
113
+
114
+ def map_state
115
+ @mapped ? :mapped : :unmapped
116
+ end
117
+
118
+ def destroy
119
+ Native.wgpuBufferDestroy(@handle)
120
+ end
121
+
122
+ def release
123
+ return if @handle.null?
124
+ Native.wgpuBufferRelease(@handle)
125
+ @handle = FFI::Pointer::NULL
126
+ end
127
+
128
+ private
129
+
130
+ def normalize_usage(usage)
131
+ case usage
132
+ when Integer
133
+ usage
134
+ when Symbol
135
+ Native::BufferUsage[usage]
136
+ when Array
137
+ usage.reduce(0) { |acc, u| acc | Native::BufferUsage[u] }
138
+ else
139
+ raise ArgumentError, "Invalid usage type: #{usage.class}"
140
+ end
141
+ end
142
+
143
+ def data_to_pointer(data)
144
+ case data
145
+ when String
146
+ ptr = FFI::MemoryPointer.new(:char, data.bytesize)
147
+ ptr.put_bytes(0, data)
148
+ [ptr, data.bytesize]
149
+ when Array
150
+ ptr = FFI::MemoryPointer.new(:float, data.size)
151
+ ptr.write_array_of_float(data)
152
+ [ptr, data.size * 4]
153
+ when FFI::Pointer
154
+ [data, data.size]
155
+ else
156
+ raise ArgumentError, "Unsupported data type: #{data.class}"
157
+ end
158
+ end
159
+ end
160
+
161
+ class BufferMappedRange
162
+ def initialize(pointer, size)
163
+ @pointer = pointer
164
+ @size = size
165
+ end
166
+
167
+ def read_floats(count = nil)
168
+ count ||= @size / 4
169
+ @pointer.read_array_of_float(count)
170
+ end
171
+
172
+ def write_floats(data)
173
+ @pointer.write_array_of_float(data)
174
+ end
175
+
176
+ def read_bytes
177
+ @pointer.read_bytes(@size)
178
+ end
179
+
180
+ def write_bytes(data)
181
+ @pointer.put_bytes(0, data)
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WGPU
4
+ class QuerySet
5
+ attr_reader :handle
6
+
7
+ def initialize(device, label: nil, type:, count:)
8
+ @device = device
9
+
10
+ desc = Native::QuerySetDescriptor.new
11
+ desc[:next_in_chain] = nil
12
+ if label
13
+ @label_ptr = FFI::MemoryPointer.from_string(label)
14
+ desc[:label][:data] = @label_ptr
15
+ desc[:label][:length] = label.bytesize
16
+ else
17
+ desc[:label][:data] = nil
18
+ desc[:label][:length] = 0
19
+ end
20
+ desc[:type] = type
21
+ desc[:count] = count
22
+
23
+ @handle = Native.wgpuDeviceCreateQuerySet(device.handle, desc)
24
+ raise ResourceError, "Failed to create query set" if @handle.null?
25
+ end
26
+
27
+ def count
28
+ Native.wgpuQuerySetGetCount(@handle)
29
+ end
30
+
31
+ def type
32
+ Native.wgpuQuerySetGetType(@handle)
33
+ end
34
+
35
+ def destroy
36
+ Native.wgpuQuerySetDestroy(@handle)
37
+ end
38
+
39
+ def release
40
+ return if @handle.null?
41
+ Native.wgpuQuerySetRelease(@handle)
42
+ @handle = FFI::Pointer::NULL
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WGPU
4
+ class Sampler
5
+ attr_reader :handle
6
+
7
+ def initialize(device, 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)
8
+ @device = device
9
+
10
+ desc = Native::SamplerDescriptor.new
11
+ desc[:next_in_chain] = nil
12
+ if label
13
+ @label_ptr = FFI::MemoryPointer.from_string(label)
14
+ desc[:label][:data] = @label_ptr
15
+ desc[:label][:length] = label.bytesize
16
+ else
17
+ desc[:label][:data] = nil
18
+ desc[:label][:length] = 0
19
+ end
20
+ desc[:address_mode_u] = address_mode_u
21
+ desc[:address_mode_v] = address_mode_v
22
+ desc[:address_mode_w] = address_mode_w
23
+ desc[:mag_filter] = mag_filter
24
+ desc[:min_filter] = min_filter
25
+ desc[:mipmap_filter] = mipmap_filter
26
+ desc[:lod_min_clamp] = lod_min_clamp
27
+ desc[:lod_max_clamp] = lod_max_clamp
28
+ desc[:compare] = compare || :undefined
29
+ desc[:max_anisotropy] = max_anisotropy
30
+
31
+ device.push_error_scope(:validation)
32
+ @handle = Native.wgpuDeviceCreateSampler(device.handle, desc)
33
+ error = device.pop_error_scope
34
+
35
+ if @handle.null? || (error[:type] && error[:type] != :no_error)
36
+ msg = error[:message] || "Failed to create sampler"
37
+ raise ResourceError, msg
38
+ end
39
+ end
40
+
41
+ def release
42
+ return if @handle.null?
43
+ Native.wgpuSamplerRelease(@handle)
44
+ @handle = FFI::Pointer::NULL
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WGPU
4
+ class Texture
5
+ attr_reader :handle
6
+
7
+ def initialize(device, label: nil, size:, format:, usage:, dimension: :d2, mip_level_count: 1, sample_count: 1, view_formats: [])
8
+ @device = device
9
+
10
+ desc = Native::TextureDescriptor.new
11
+ desc[:next_in_chain] = nil
12
+ if label
13
+ @label_ptr = FFI::MemoryPointer.from_string(label)
14
+ desc[:label][:data] = @label_ptr
15
+ desc[:label][:length] = label.bytesize
16
+ else
17
+ desc[:label][:data] = nil
18
+ desc[:label][:length] = 0
19
+ end
20
+ desc[:usage] = normalize_usage(usage)
21
+ desc[:dimension] = dimension
22
+ desc[:size][:width] = size[:width] || size[0]
23
+ desc[:size][:height] = size[:height] || size[1] || 1
24
+ desc[:size][:depth_or_array_layers] = size[:depth_or_array_layers] || size[2] || 1
25
+ desc[:format] = format
26
+ desc[:mip_level_count] = mip_level_count
27
+ desc[:sample_count] = sample_count
28
+ desc[:view_format_count] = view_formats.size
29
+ desc[:view_formats] = nil
30
+
31
+ device.push_error_scope(:validation)
32
+ @handle = Native.wgpuDeviceCreateTexture(device.handle, desc)
33
+ error = device.pop_error_scope
34
+
35
+ if @handle.null? || (error[:type] && error[:type] != :no_error)
36
+ msg = error[:message] || "Failed to create texture"
37
+ raise ResourceError, msg
38
+ end
39
+ end
40
+
41
+ def self.from_handle(handle)
42
+ texture = allocate
43
+ texture.instance_variable_set(:@handle, handle)
44
+ texture.instance_variable_set(:@device, nil)
45
+ texture
46
+ end
47
+
48
+ def create_view(label: nil, format: nil, dimension: nil, base_mip_level: 0, mip_level_count: nil, base_array_layer: 0, array_layer_count: nil, aspect: :all)
49
+ TextureView.new(self,
50
+ label: label,
51
+ format: format,
52
+ dimension: dimension,
53
+ base_mip_level: base_mip_level,
54
+ mip_level_count: mip_level_count,
55
+ base_array_layer: base_array_layer,
56
+ array_layer_count: array_layer_count,
57
+ aspect: aspect
58
+ )
59
+ end
60
+
61
+ def width
62
+ Native.wgpuTextureGetWidth(@handle)
63
+ end
64
+
65
+ def height
66
+ Native.wgpuTextureGetHeight(@handle)
67
+ end
68
+
69
+ def depth_or_array_layers
70
+ Native.wgpuTextureGetDepthOrArrayLayers(@handle)
71
+ end
72
+
73
+ def mip_level_count
74
+ Native.wgpuTextureGetMipLevelCount(@handle)
75
+ end
76
+
77
+ def sample_count
78
+ Native.wgpuTextureGetSampleCount(@handle)
79
+ end
80
+
81
+ def dimension
82
+ Native.wgpuTextureGetDimension(@handle)
83
+ end
84
+
85
+ def format
86
+ Native.wgpuTextureGetFormat(@handle)
87
+ end
88
+
89
+ def usage
90
+ Native.wgpuTextureGetUsage(@handle)
91
+ end
92
+
93
+ def destroy
94
+ Native.wgpuTextureDestroy(@handle)
95
+ end
96
+
97
+ def release
98
+ return if @handle.null?
99
+ Native.wgpuTextureRelease(@handle)
100
+ @handle = FFI::Pointer::NULL
101
+ end
102
+
103
+ private
104
+
105
+ def normalize_usage(usage)
106
+ case usage
107
+ when Integer
108
+ usage
109
+ when Symbol
110
+ Native::TextureUsage[usage]
111
+ when Array
112
+ usage.reduce(0) { |acc, u| acc | Native::TextureUsage[u] }
113
+ else
114
+ raise ArgumentError, "Invalid usage: #{usage}"
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WGPU
4
+ class TextureView
5
+ attr_reader :handle
6
+
7
+ def initialize(texture, label: nil, format: nil, dimension: nil, base_mip_level: 0, mip_level_count: nil, base_array_layer: 0, array_layer_count: nil, aspect: :all)
8
+ @texture = texture
9
+
10
+ desc = Native::TextureViewDescriptor.new
11
+ desc[:next_in_chain] = nil
12
+ if label
13
+ @label_ptr = FFI::MemoryPointer.from_string(label)
14
+ desc[:label][:data] = @label_ptr
15
+ desc[:label][:length] = label.bytesize
16
+ else
17
+ desc[:label][:data] = nil
18
+ desc[:label][:length] = 0
19
+ end
20
+ desc[:format] = format || :undefined
21
+ desc[:dimension] = dimension || :undefined
22
+ desc[:base_mip_level] = base_mip_level
23
+ desc[:mip_level_count] = mip_level_count || 0xFFFFFFFF
24
+ desc[:base_array_layer] = base_array_layer
25
+ desc[:array_layer_count] = array_layer_count || 0xFFFFFFFF
26
+ desc[:aspect] = aspect
27
+
28
+ @handle = Native.wgpuTextureCreateView(texture.handle, desc)
29
+ raise ResourceError, "Failed to create texture view" if @handle.null?
30
+ end
31
+
32
+ def self.from_handle(handle)
33
+ view = allocate
34
+ view.instance_variable_set(:@handle, handle)
35
+ view.instance_variable_set(:@texture, nil)
36
+ view
37
+ end
38
+
39
+ def release
40
+ return if @handle.null?
41
+ Native.wgpuTextureViewRelease(@handle)
42
+ @handle = FFI::Pointer::NULL
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WGPU
4
+ VERSION = "0.1.0"
5
+ end
data/lib/wgpu.rb ADDED
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "wgpu/version"
4
+ require_relative "wgpu/error"
5
+ require_relative "wgpu/native/loader"
6
+
7
+ require_relative "wgpu/resources/buffer"
8
+ require_relative "wgpu/resources/texture"
9
+ require_relative "wgpu/resources/texture_view"
10
+ require_relative "wgpu/resources/sampler"
11
+ require_relative "wgpu/resources/query_set"
12
+
13
+ require_relative "wgpu/pipeline/shader_module"
14
+ require_relative "wgpu/pipeline/bind_group_layout"
15
+ require_relative "wgpu/pipeline/bind_group"
16
+ require_relative "wgpu/pipeline/pipeline_layout"
17
+ require_relative "wgpu/pipeline/compute_pipeline"
18
+ require_relative "wgpu/pipeline/render_pipeline"
19
+
20
+ require_relative "wgpu/commands/command_buffer"
21
+ require_relative "wgpu/commands/compute_pass"
22
+ require_relative "wgpu/commands/render_pass"
23
+ require_relative "wgpu/commands/render_bundle"
24
+ require_relative "wgpu/commands/render_bundle_encoder"
25
+ require_relative "wgpu/commands/command_encoder"
26
+
27
+ require_relative "wgpu/core/queue"
28
+ require_relative "wgpu/core/device"
29
+ require_relative "wgpu/core/adapter"
30
+ require_relative "wgpu/core/instance"
31
+ require_relative "wgpu/core/surface"
32
+
33
+ module WGPU
34
+ end
metadata ADDED
@@ -0,0 +1,108 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: wgpu
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Yudai Takada
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: ffi
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.15'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '1.15'
26
+ - !ruby/object:Gem::Dependency
27
+ name: rubyzip
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '2.3'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '2.3'
40
+ description: Ruby bindings for the WebGPU API, providing GPU compute and graphics
41
+ capabilities through the wgpu-native library.
42
+ email:
43
+ - t.yudai92@gmail.com
44
+ executables: []
45
+ extensions:
46
+ - ext/wgpu/extconf.rb
47
+ extra_rdoc_files: []
48
+ files:
49
+ - LICENSE-APACHE
50
+ - LICENSE-MIT
51
+ - README.md
52
+ - ext/wgpu/Makefile
53
+ - ext/wgpu/extconf.rb
54
+ - lib/wgpu.rb
55
+ - lib/wgpu/commands/command_buffer.rb
56
+ - lib/wgpu/commands/command_encoder.rb
57
+ - lib/wgpu/commands/compute_pass.rb
58
+ - lib/wgpu/commands/render_bundle.rb
59
+ - lib/wgpu/commands/render_bundle_encoder.rb
60
+ - lib/wgpu/commands/render_pass.rb
61
+ - lib/wgpu/core/adapter.rb
62
+ - lib/wgpu/core/device.rb
63
+ - lib/wgpu/core/instance.rb
64
+ - lib/wgpu/core/queue.rb
65
+ - lib/wgpu/core/surface.rb
66
+ - lib/wgpu/error.rb
67
+ - lib/wgpu/native/callbacks.rb
68
+ - lib/wgpu/native/enums.rb
69
+ - lib/wgpu/native/functions.rb
70
+ - lib/wgpu/native/loader.rb
71
+ - lib/wgpu/native/structs.rb
72
+ - lib/wgpu/pipeline/bind_group.rb
73
+ - lib/wgpu/pipeline/bind_group_layout.rb
74
+ - lib/wgpu/pipeline/compute_pipeline.rb
75
+ - lib/wgpu/pipeline/pipeline_layout.rb
76
+ - lib/wgpu/pipeline/render_pipeline.rb
77
+ - lib/wgpu/pipeline/shader_module.rb
78
+ - lib/wgpu/resources/buffer.rb
79
+ - lib/wgpu/resources/query_set.rb
80
+ - lib/wgpu/resources/sampler.rb
81
+ - lib/wgpu/resources/texture.rb
82
+ - lib/wgpu/resources/texture_view.rb
83
+ - lib/wgpu/version.rb
84
+ homepage: https://github.com/ydah/wgpu-ruby
85
+ licenses:
86
+ - MIT
87
+ - Apache-2.0
88
+ metadata:
89
+ homepage_uri: https://github.com/ydah/wgpu-ruby
90
+ source_code_uri: https://github.com/ydah/wgpu-ruby
91
+ rdoc_options: []
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: 3.2.0
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ requirements: []
105
+ rubygems_version: 4.0.3
106
+ specification_version: 4
107
+ summary: Ruby bindings for WebGPU via wgpu-native
108
+ test_files: []