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,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
|