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.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE-APACHE +190 -0
  3. data/LICENSE-MIT +21 -0
  4. data/README.md +228 -0
  5. data/ext/wgpu/Makefile +7 -0
  6. data/ext/wgpu/extconf.rb +161 -0
  7. data/lib/wgpu/async_task.rb +55 -0
  8. data/lib/wgpu/commands/command_buffer.rb +17 -0
  9. data/lib/wgpu/commands/command_encoder.rb +201 -0
  10. data/lib/wgpu/commands/compute_pass.rb +89 -0
  11. data/lib/wgpu/commands/render_bundle.rb +18 -0
  12. data/lib/wgpu/commands/render_bundle_encoder.rb +148 -0
  13. data/lib/wgpu/commands/render_pass.rb +207 -0
  14. data/lib/wgpu/core/adapter.rb +186 -0
  15. data/lib/wgpu/core/canvas_context.rb +104 -0
  16. data/lib/wgpu/core/device.rb +397 -0
  17. data/lib/wgpu/core/instance.rb +81 -0
  18. data/lib/wgpu/core/queue.rb +197 -0
  19. data/lib/wgpu/core/surface.rb +221 -0
  20. data/lib/wgpu/error.rb +16 -0
  21. data/lib/wgpu/native/callbacks.rb +26 -0
  22. data/lib/wgpu/native/enums.rb +529 -0
  23. data/lib/wgpu/native/functions.rb +419 -0
  24. data/lib/wgpu/native/loader.rb +61 -0
  25. data/lib/wgpu/native/structs.rb +646 -0
  26. data/lib/wgpu/pipeline/bind_group.rb +80 -0
  27. data/lib/wgpu/pipeline/bind_group_layout.rb +121 -0
  28. data/lib/wgpu/pipeline/compute_pipeline.rb +88 -0
  29. data/lib/wgpu/pipeline/pipeline_layout.rb +43 -0
  30. data/lib/wgpu/pipeline/render_pipeline.rb +278 -0
  31. data/lib/wgpu/pipeline/shader_module.rb +202 -0
  32. data/lib/wgpu/resources/buffer.rb +228 -0
  33. data/lib/wgpu/resources/query_set.rb +45 -0
  34. data/lib/wgpu/resources/sampler.rb +47 -0
  35. data/lib/wgpu/resources/texture.rb +136 -0
  36. data/lib/wgpu/resources/texture_view.rb +49 -0
  37. data/lib/wgpu/version.rb +5 -0
  38. data/lib/wgpu/window.rb +177 -0
  39. data/lib/wgpu.rb +36 -0
  40. metadata +125 -0
@@ -0,0 +1,202 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WGPU
4
+ class ShaderModule
5
+ attr_reader :handle
6
+
7
+ def initialize(device, label: nil, code:, compilation_hints: [])
8
+ @device = device
9
+ @pointers = []
10
+ compilation_hints # currently unused by wgpu-native API
11
+
12
+ source_ptr = build_shader_source(code, label: label)
13
+
14
+ desc = Native::ShaderModuleDescriptor.new
15
+ desc[:next_in_chain] = source_ptr
16
+ if label
17
+ label_ptr = FFI::MemoryPointer.from_string(label)
18
+ @pointers << label_ptr
19
+ desc[:label][:data] = label_ptr
20
+ desc[:label][:length] = label.bytesize
21
+ else
22
+ desc[:label][:data] = nil
23
+ desc[:label][:length] = 0
24
+ end
25
+
26
+ device.push_error_scope(:validation)
27
+ @handle = Native.wgpuDeviceCreateShaderModule(device.handle, desc)
28
+ error = device.pop_error_scope
29
+
30
+ if @handle.null? || (error[:type] && error[:type] != :no_error)
31
+ msg = error[:message] || "Failed to create shader module"
32
+ raise ShaderError, msg
33
+ end
34
+ end
35
+
36
+ def get_compilation_info
37
+ result_holder = { done: false, status: nil, messages: [] }
38
+
39
+ callback = FFI::Function.new(
40
+ :void, [:uint32, :pointer, :pointer, :pointer]
41
+ ) do |status, compilation_info_ptr, _userdata1, _userdata2|
42
+ result_holder[:done] = true
43
+ result_holder[:status] = Native::CompilationInfoRequestStatus[status]
44
+
45
+ unless compilation_info_ptr.null?
46
+ info = Native::CompilationInfo.new(compilation_info_ptr)
47
+ count = info[:message_count]
48
+ if count > 0 && !info[:messages].null?
49
+ count.times do |i|
50
+ msg_ptr = info[:messages] + (i * Native::CompilationMessage.size)
51
+ msg = Native::CompilationMessage.new(msg_ptr)
52
+ message_text = if msg[:message][:data] && !msg[:message][:data].null? && msg[:message][:length] > 0
53
+ msg[:message][:data].read_string(msg[:message][:length])
54
+ else
55
+ ""
56
+ end
57
+ result_holder[:messages] << {
58
+ type: msg[:type],
59
+ message: message_text,
60
+ line_num: msg[:line_num],
61
+ line_pos: msg[:line_pos],
62
+ offset: msg[:offset],
63
+ length: msg[:length]
64
+ }
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ callback_info = Native::CompilationInfoCallbackInfo.new
71
+ callback_info[:next_in_chain] = nil
72
+ callback_info[:mode] = 1
73
+ callback_info[:callback] = callback
74
+ callback_info[:userdata1] = nil
75
+ callback_info[:userdata2] = nil
76
+
77
+ Native.wgpuShaderModuleGetCompilationInfo(@handle, callback_info)
78
+
79
+ until result_holder[:done]
80
+ Native.wgpuDevicePoll(@device.handle, 0, nil)
81
+ sleep(0.001)
82
+ end
83
+
84
+ {
85
+ status: result_holder[:status],
86
+ messages: result_holder[:messages]
87
+ }
88
+ end
89
+
90
+ def get_compilation_info_async
91
+ AsyncTask.new { get_compilation_info }
92
+ end
93
+
94
+ def release
95
+ return if @handle.null?
96
+ Native.wgpuShaderModuleRelease(@handle)
97
+ @handle = FFI::Pointer::NULL
98
+ end
99
+
100
+ private
101
+
102
+ def build_shader_source(code, label:)
103
+ if code.is_a?(String)
104
+ if spirv_binary?(code)
105
+ build_spirv_source(code.b)
106
+ elsif glsl_source?(code)
107
+ build_glsl_source(code, label: label)
108
+ else
109
+ build_wgsl_source(code)
110
+ end
111
+ elsif code.is_a?(Array)
112
+ build_spirv_source(code.pack("L<*"))
113
+ elsif code.respond_to?(:to_str)
114
+ build_shader_source(code.to_str, label: label)
115
+ else
116
+ # Assume binary SPIR-V for byte-like objects.
117
+ build_spirv_source(code)
118
+ end
119
+ end
120
+
121
+ def glsl_source?(source)
122
+ source.lstrip.start_with?("#version")
123
+ end
124
+
125
+ def spirv_binary?(source)
126
+ source.bytesize >= 4 && source.byteslice(0, 4).bytes == [0x03, 0x02, 0x23, 0x07]
127
+ end
128
+
129
+ def build_wgsl_source(source)
130
+ code_ptr = FFI::MemoryPointer.from_string(source)
131
+ @pointers << code_ptr
132
+
133
+ wgsl = Native::ShaderSourceWGSL.new
134
+ wgsl[:chain][:next] = nil
135
+ wgsl[:chain][:s_type] = :shader_source_wgsl
136
+ wgsl[:code][:data] = code_ptr
137
+ wgsl[:code][:length] = source.bytesize
138
+
139
+ @pointers << wgsl
140
+ wgsl.to_ptr
141
+ end
142
+
143
+ def build_spirv_source(binary)
144
+ bytes =
145
+ if binary.is_a?(String)
146
+ binary
147
+ elsif binary.respond_to?(:to_str)
148
+ binary.to_str
149
+ elsif binary.respond_to?(:to_a)
150
+ binary.to_a.pack("C*")
151
+ else
152
+ raise ArgumentError, "Unsupported SPIR-V data type: #{binary.class}"
153
+ end
154
+
155
+ raise ArgumentError, "SPIR-V bytecode size must be a multiple of 4" if (bytes.bytesize % 4) != 0
156
+
157
+ byte_ptr = FFI::MemoryPointer.new(:char, bytes.bytesize)
158
+ byte_ptr.put_bytes(0, bytes)
159
+ @pointers << byte_ptr
160
+
161
+ spirv = Native::ShaderSourceSPIRV.new
162
+ spirv[:chain][:next] = nil
163
+ spirv[:chain][:s_type] = :shader_source_spirv
164
+ spirv[:code_size] = bytes.bytesize / 4
165
+ spirv[:code] = byte_ptr
166
+
167
+ @pointers << spirv
168
+ spirv.to_ptr
169
+ end
170
+
171
+ def build_glsl_source(source, label:)
172
+ stage = shader_stage_for_glsl(label)
173
+ raise ArgumentError, "GLSL shader requires label containing comp/vert/frag" if stage.nil?
174
+
175
+ code_ptr = FFI::MemoryPointer.from_string(source)
176
+ @pointers << code_ptr
177
+
178
+ glsl = Native::ShaderSourceGLSL.new
179
+ glsl[:chain][:next] = nil
180
+ glsl[:chain][:s_type] = :shader_source_glsl
181
+ glsl[:stage] = stage
182
+ glsl[:code][:data] = code_ptr
183
+ glsl[:code][:length] = source.bytesize
184
+ glsl[:define_count] = 0
185
+ glsl[:defines] = nil
186
+
187
+ @pointers << glsl
188
+ glsl.to_ptr
189
+ end
190
+
191
+ def shader_stage_for_glsl(label)
192
+ return nil unless label
193
+
194
+ down = label.downcase
195
+ return Native::ShaderStage[:compute] if down.include?("comp")
196
+ return Native::ShaderStage[:vertex] if down.include?("vert")
197
+ return Native::ShaderStage[:fragment] if down.include?("frag")
198
+
199
+ nil
200
+ end
201
+ end
202
+ end
@@ -0,0 +1,228 @@
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
+ @map_callbacks = []
13
+
14
+ desc = Native::BufferDescriptor.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[:usage] = @usage
25
+ desc[:size] = size
26
+ desc[:mapped_at_creation] = mapped_at_creation ? 1 : 0
27
+
28
+ device.push_error_scope(:validation)
29
+ @handle = Native.wgpuDeviceCreateBuffer(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 buffer"
34
+ raise BufferError, msg
35
+ end
36
+ end
37
+
38
+ def write(data, offset: 0)
39
+ ptr, byte_size = data_to_pointer(data)
40
+ Native.wgpuQueueWriteBuffer(@device.queue.handle, @handle, offset, ptr, byte_size)
41
+ end
42
+
43
+ def mapped_range(offset: 0, size: nil)
44
+ raise BufferError, "Buffer is not mapped" unless @mapped
45
+
46
+ size ||= @size - offset
47
+ ptr = Native.wgpuBufferGetMappedRange(@handle, offset, size)
48
+ raise BufferError, "Failed to get mapped range" if ptr.null?
49
+
50
+ BufferMappedRange.new(ptr, size)
51
+ end
52
+
53
+ def get_mapped_range(offset: 0, size: nil)
54
+ mapped_range(offset: offset, size: size)
55
+ end
56
+
57
+ def unmap
58
+ Native.wgpuBufferUnmap(@handle)
59
+ @mapped = false
60
+ end
61
+
62
+ def map_sync(mode, offset: 0, size: nil)
63
+ status_holder, callback = begin_map_request(mode, offset: offset, size: size)
64
+ wait_for_map(status_holder)
65
+ finalize_map(status_holder)
66
+ ensure
67
+ @map_callbacks.delete(callback) if callback
68
+ end
69
+
70
+ def map_async(mode, offset: 0, size: nil)
71
+ status_holder, callback = begin_map_request(mode, offset: offset, size: size)
72
+ AsyncTask.new do
73
+ wait_for_map(status_holder)
74
+ finalize_map(status_holder)
75
+ ensure
76
+ @map_callbacks.delete(callback)
77
+ end
78
+ end
79
+
80
+ def read_mapped_data(offset: 0, size: nil)
81
+ raise BufferError, "Buffer is not mapped" unless @mapped
82
+
83
+ size ||= @size - offset
84
+ ptr = Native.wgpuBufferGetConstMappedRange(@handle, offset, size)
85
+ raise BufferError, "Failed to get mapped range" if ptr.null?
86
+
87
+ ptr.read_bytes(size)
88
+ end
89
+
90
+ def read_mapped(offset: 0, size: nil)
91
+ read_mapped_data(offset: offset, size: size)
92
+ end
93
+
94
+ def write_mapped(data, offset: 0)
95
+ raise BufferError, "Buffer is not mapped" unless @mapped
96
+
97
+ ptr, byte_size = data_to_pointer(data)
98
+ target = Native.wgpuBufferGetMappedRange(@handle, offset, byte_size)
99
+ raise BufferError, "Failed to get mapped range" if target.null?
100
+
101
+ target.put_bytes(0, ptr.read_bytes(byte_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
+ state = Native.wgpuBufferGetMapState(@handle)
116
+ state || (@mapped ? :mapped : :unmapped)
117
+ end
118
+
119
+ def destroy
120
+ Native.wgpuBufferDestroy(@handle)
121
+ end
122
+
123
+ def release
124
+ return if @handle.null?
125
+ Native.wgpuBufferRelease(@handle)
126
+ @handle = FFI::Pointer::NULL
127
+ end
128
+
129
+ private
130
+
131
+ def begin_map_request(mode, offset:, size:)
132
+ size ||= @size - offset
133
+ mode_flag = case mode
134
+ when :read then Native::MapMode[:read]
135
+ when :write then Native::MapMode[:write]
136
+ when Integer then mode
137
+ else raise ArgumentError, "Invalid map mode: #{mode}"
138
+ end
139
+
140
+ status_holder = { done: false, status: nil }
141
+ callback = FFI::Function.new(:void, [:uint32, :pointer]) do |status, _userdata|
142
+ status_holder[:done] = true
143
+ status_holder[:status] = Native::MapAsyncStatus[status]
144
+ end
145
+ @map_callbacks << callback
146
+
147
+ callback_info = Native::BufferMapCallbackInfo.new
148
+ callback_info[:next_in_chain] = nil
149
+ callback_info[:mode] = 1
150
+ callback_info[:callback] = callback
151
+ callback_info[:userdata] = nil
152
+
153
+ Native.wgpuBufferMapAsync(@handle, mode_flag, offset, size, callback_info)
154
+
155
+ [status_holder, callback]
156
+ end
157
+
158
+ def wait_for_map(status_holder)
159
+ until status_holder[:done]
160
+ Native.wgpuDevicePoll(@device.handle, 0, nil)
161
+ sleep(0.001)
162
+ end
163
+ end
164
+
165
+ def finalize_map(status_holder)
166
+ if status_holder[:status] == :success
167
+ @mapped = true
168
+ true
169
+ else
170
+ raise BufferError, "Failed to map buffer: #{status_holder[:status]}"
171
+ end
172
+ end
173
+
174
+ def normalize_usage(usage)
175
+ case usage
176
+ when Integer
177
+ usage
178
+ when Symbol
179
+ Native::BufferUsage[usage]
180
+ when Array
181
+ usage.reduce(0) { |acc, u| acc | Native::BufferUsage[u] }
182
+ else
183
+ raise ArgumentError, "Invalid usage type: #{usage.class}"
184
+ end
185
+ end
186
+
187
+ def data_to_pointer(data)
188
+ case data
189
+ when String
190
+ ptr = FFI::MemoryPointer.new(:char, data.bytesize)
191
+ ptr.put_bytes(0, data)
192
+ [ptr, data.bytesize]
193
+ when Array
194
+ ptr = FFI::MemoryPointer.new(:float, data.size)
195
+ ptr.write_array_of_float(data)
196
+ [ptr, data.size * 4]
197
+ when FFI::Pointer
198
+ [data, data.size]
199
+ else
200
+ raise ArgumentError, "Unsupported data type: #{data.class}"
201
+ end
202
+ end
203
+ end
204
+
205
+ class BufferMappedRange
206
+ def initialize(pointer, size)
207
+ @pointer = pointer
208
+ @size = size
209
+ end
210
+
211
+ def read_floats(count = nil)
212
+ count ||= @size / 4
213
+ @pointer.read_array_of_float(count)
214
+ end
215
+
216
+ def write_floats(data)
217
+ @pointer.write_array_of_float(data)
218
+ end
219
+
220
+ def read_bytes
221
+ @pointer.read_bytes(@size)
222
+ end
223
+
224
+ def write_bytes(data)
225
+ @pointer.put_bytes(0, data)
226
+ end
227
+ end
228
+ 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,136 @@
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
+ if view_formats.empty?
30
+ @view_formats_ptr = nil
31
+ desc[:view_formats] = nil
32
+ else
33
+ format_values = view_formats.map do |vf|
34
+ vf.is_a?(Integer) ? vf : Native::TextureFormat[vf]
35
+ end
36
+ @view_formats_ptr = FFI::MemoryPointer.new(:uint32, format_values.size)
37
+ @view_formats_ptr.write_array_of_uint32(format_values)
38
+ desc[:view_formats] = @view_formats_ptr
39
+ end
40
+
41
+ device.push_error_scope(:validation)
42
+ @handle = Native.wgpuDeviceCreateTexture(device.handle, desc)
43
+ error = device.pop_error_scope
44
+
45
+ if @handle.null? || (error[:type] && error[:type] != :no_error)
46
+ msg = error[:message] || "Failed to create texture"
47
+ raise ResourceError, msg
48
+ end
49
+ end
50
+
51
+ def self.from_handle(handle)
52
+ texture = allocate
53
+ texture.instance_variable_set(:@handle, handle)
54
+ texture.instance_variable_set(:@device, nil)
55
+ texture
56
+ end
57
+
58
+ 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)
59
+ TextureView.new(self,
60
+ label: label,
61
+ format: format,
62
+ dimension: dimension,
63
+ base_mip_level: base_mip_level,
64
+ mip_level_count: mip_level_count,
65
+ base_array_layer: base_array_layer,
66
+ array_layer_count: array_layer_count,
67
+ aspect: aspect
68
+ )
69
+ end
70
+
71
+ def width
72
+ Native.wgpuTextureGetWidth(@handle)
73
+ end
74
+
75
+ def size
76
+ {
77
+ width: width,
78
+ height: height,
79
+ depth_or_array_layers: depth_or_array_layers
80
+ }
81
+ end
82
+
83
+ def height
84
+ Native.wgpuTextureGetHeight(@handle)
85
+ end
86
+
87
+ def depth_or_array_layers
88
+ Native.wgpuTextureGetDepthOrArrayLayers(@handle)
89
+ end
90
+
91
+ def mip_level_count
92
+ Native.wgpuTextureGetMipLevelCount(@handle)
93
+ end
94
+
95
+ def sample_count
96
+ Native.wgpuTextureGetSampleCount(@handle)
97
+ end
98
+
99
+ def dimension
100
+ Native.wgpuTextureGetDimension(@handle)
101
+ end
102
+
103
+ def format
104
+ Native.wgpuTextureGetFormat(@handle)
105
+ end
106
+
107
+ def usage
108
+ Native.wgpuTextureGetUsage(@handle)
109
+ end
110
+
111
+ def destroy
112
+ Native.wgpuTextureDestroy(@handle)
113
+ end
114
+
115
+ def release
116
+ return if @handle.null?
117
+ Native.wgpuTextureRelease(@handle)
118
+ @handle = FFI::Pointer::NULL
119
+ end
120
+
121
+ private
122
+
123
+ def normalize_usage(usage)
124
+ case usage
125
+ when Integer
126
+ usage
127
+ when Symbol
128
+ Native::TextureUsage[usage]
129
+ when Array
130
+ usage.reduce(0) { |acc, u| acc | Native::TextureUsage[u] }
131
+ else
132
+ raise ArgumentError, "Invalid usage: #{usage}"
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WGPU
4
+ class TextureView
5
+ attr_reader :handle, :texture
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 size
40
+ @texture&.size
41
+ end
42
+
43
+ def release
44
+ return if @handle.null?
45
+ Native.wgpuTextureViewRelease(@handle)
46
+ @handle = FFI::Pointer::NULL
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WGPU
4
+ VERSION = "1.0.0"
5
+ end