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.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +88 -0
  3. data/README.md +44 -5
  4. data/docs/README.md +22 -0
  5. data/docs/api_coverage.md +140 -0
  6. data/docs/async.md +41 -0
  7. data/docs/bind_groups.md +25 -0
  8. data/docs/buffer_data.md +37 -0
  9. data/docs/command_encoding.md +49 -0
  10. data/docs/errors.md +40 -0
  11. data/docs/getting_started_compute.md +62 -0
  12. data/docs/getting_started_rendering.md +62 -0
  13. data/docs/installation.md +94 -0
  14. data/docs/pipeline_descriptors.md +43 -0
  15. data/docs/releasing.md +32 -0
  16. data/docs/resource_lifetime.md +108 -0
  17. data/docs/shaders.md +32 -0
  18. data/docs/texture_readback.md +46 -0
  19. data/docs/troubleshooting.md +53 -0
  20. data/docs/upgrading_wgpu_native.md +37 -0
  21. data/ext/wgpu/extconf.rb +10 -142
  22. data/lib/wgpu/async_task.rb +19 -0
  23. data/lib/wgpu/commands/command_buffer.rb +25 -1
  24. data/lib/wgpu/commands/command_encoder.rb +123 -9
  25. data/lib/wgpu/commands/compute_pass.rb +53 -0
  26. data/lib/wgpu/commands/render_bundle.rb +9 -1
  27. data/lib/wgpu/commands/render_bundle_encoder.rb +65 -4
  28. data/lib/wgpu/commands/render_pass.rb +136 -8
  29. data/lib/wgpu/core/adapter.rb +123 -19
  30. data/lib/wgpu/core/async_waiter.rb +47 -4
  31. data/lib/wgpu/core/canvas_context.rb +32 -2
  32. data/lib/wgpu/core/device.rb +399 -53
  33. data/lib/wgpu/core/instance.rb +28 -4
  34. data/lib/wgpu/core/queue.rb +197 -51
  35. data/lib/wgpu/core/surface.rb +64 -17
  36. data/lib/wgpu/data_types.rb +83 -0
  37. data/lib/wgpu/descriptor_helpers.rb +104 -0
  38. data/lib/wgpu/error.rb +70 -0
  39. data/lib/wgpu/logging.rb +63 -0
  40. data/lib/wgpu/native/abi_verifier.rb +143 -0
  41. data/lib/wgpu/native/callbacks.rb +10 -1
  42. data/lib/wgpu/native/capabilities.rb +32 -2
  43. data/lib/wgpu/native/distribution.rb +176 -0
  44. data/lib/wgpu/native/enum_helper.rb +80 -0
  45. data/lib/wgpu/native/enums.rb +68 -8
  46. data/lib/wgpu/native/fixtures/webgpu-v27.0.4.0-enums.h +848 -0
  47. data/lib/wgpu/native/functions.rb +21 -10
  48. data/lib/wgpu/native/installer.rb +223 -0
  49. data/lib/wgpu/native/loader.rb +61 -21
  50. data/lib/wgpu/native/structs.rb +18 -1
  51. data/lib/wgpu/native_resource.rb +342 -0
  52. data/lib/wgpu/pipeline/bind_group.rb +21 -0
  53. data/lib/wgpu/pipeline/bind_group_layout.rb +108 -41
  54. data/lib/wgpu/pipeline/compute_pipeline.rb +40 -50
  55. data/lib/wgpu/pipeline/pipeline_layout.rb +8 -0
  56. data/lib/wgpu/pipeline/render_pipeline.rb +207 -110
  57. data/lib/wgpu/pipeline/shader_module.rb +95 -28
  58. data/lib/wgpu/resources/buffer.rb +387 -85
  59. data/lib/wgpu/resources/query_set.rb +26 -12
  60. data/lib/wgpu/resources/sampler.rb +43 -20
  61. data/lib/wgpu/resources/texture.rb +89 -48
  62. data/lib/wgpu/resources/texture_view.rb +35 -7
  63. data/lib/wgpu/texture_format.rb +96 -0
  64. data/lib/wgpu/version.rb +1 -1
  65. data/lib/wgpu/window.rb +34 -1
  66. data/lib/wgpu.rb +32 -0
  67. data/sig/wgpu.rbs +460 -0
  68. metadata +33 -16
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WGPU
4
+ module DataTypes
5
+ FORMATS = {
6
+ f32: ["e*", 4],
7
+ f64: ["E*", 8],
8
+ u32: ["L<*", 4],
9
+ i32: ["l<*", 4],
10
+ u16: ["S<*", 2],
11
+ u8: ["C*", 1]
12
+ }.freeze
13
+
14
+ module_function
15
+
16
+ # Encodes Ruby values in little-endian native transfer format.
17
+ # @param values [Array] values to encode
18
+ # @param type [Symbol] element type
19
+ # @return [String] encoded bytes
20
+ def pack(values, type: :f32)
21
+ format, = format_for(type)
22
+ Array(values).pack(format)
23
+ rescue RangeError => e
24
+ raise ArgumentError, "value is out of range for #{type}: #{e.message}"
25
+ end
26
+
27
+ # Decodes little-endian transfer bytes into Ruby values.
28
+ # @param bytes [String] encoded bytes
29
+ # @param type [Symbol] element type
30
+ # @return [Array]
31
+ def unpack(bytes, type: :f32)
32
+ format, byte_size = format_for(type)
33
+ unless (bytes.bytesize % byte_size).zero?
34
+ raise ArgumentError, "#{type} data size must be a multiple of #{byte_size} bytes"
35
+ end
36
+
37
+ bytes.unpack(format)
38
+ end
39
+
40
+ # Returns the byte width of one typed value.
41
+ # @param type [Symbol] element type
42
+ # @return [Integer]
43
+ def byte_size(type)
44
+ format_for(type).last
45
+ end
46
+
47
+ # Converts data to an FFI pointer and reports its byte length.
48
+ # @return [Array(FFI::Pointer, Integer)] pointer and byte length
49
+ def to_pointer(data, type: :f32)
50
+ return [data, pointer_size(data)] if data.is_a?(FFI::Pointer)
51
+
52
+ bytes = data.is_a?(String) ? data : pack(data, type:)
53
+ pointer = FFI::MemoryPointer.new(:char, bytes.bytesize)
54
+ pointer.put_bytes(0, bytes)
55
+ [pointer, bytes.bytesize]
56
+ end
57
+
58
+ # Validates a non-negative aligned byte value.
59
+ # @return [Integer] normalized value
60
+ # @raise [ArgumentError] if negative or misaligned
61
+ def validate_alignment!(value, alignment, name:)
62
+ integer = Integer(value)
63
+ raise ArgumentError, "#{name} must be non-negative" if integer.negative?
64
+ return integer if (integer % alignment).zero?
65
+
66
+ raise ArgumentError, "#{name} must be aligned to #{alignment} bytes (got #{integer})"
67
+ end
68
+
69
+ def format_for(type)
70
+ FORMATS.fetch(type.to_sym)
71
+ rescue NoMethodError, KeyError
72
+ raise ArgumentError, "Unknown data type #{type.inspect}. Valid values: #{FORMATS.keys.map(&:inspect).join(", ")}"
73
+ end
74
+ private_class_method :format_for
75
+
76
+ def pointer_size(pointer)
77
+ pointer.size
78
+ rescue NoMethodError
79
+ raise ArgumentError, "FFI pointer must have a known size; pass size explicitly"
80
+ end
81
+ private_class_method :pointer_size
82
+ end
83
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WGPU
4
+ module DescriptorHelpers
5
+ SIZE_MAX = (1 << (FFI.type_size(:size_t) * 8)) - 1
6
+
7
+ module_function
8
+
9
+ # Writes an optional Ruby label into a native descriptor.
10
+ #
11
+ # @param descriptor [FFI::Struct] descriptor with a +label+ member
12
+ # @param label [String, nil] label text
13
+ # @param keepalive [Array] receives allocated pointers that must remain alive
14
+ # @return [void]
15
+ def set_label(descriptor, label, keepalive:)
16
+ if label
17
+ pointer = FFI::MemoryPointer.from_string(label)
18
+ keepalive << pointer
19
+ descriptor[:label][:data] = pointer
20
+ descriptor[:label][:length] = label.bytesize
21
+ else
22
+ descriptor[:label][:data] = nil
23
+ descriptor[:label][:length] = 0
24
+ end
25
+ end
26
+
27
+ # Allocates and fills a native uint32 array.
28
+ #
29
+ # @param values [Array<Integer>] values to copy
30
+ # @param keepalive [Array] receives the allocated pointer
31
+ # @return [FFI::MemoryPointer, nil] pointer, or +nil+ for an empty array
32
+ def uint32_array(values, keepalive:)
33
+ return nil if values.empty?
34
+
35
+ pointer = FFI::MemoryPointer.new(:uint32, values.length)
36
+ pointer.write_array_of_uint32(values)
37
+ keepalive << pointer
38
+ pointer
39
+ end
40
+
41
+ # Writes a nullable string into a native string view.
42
+ #
43
+ # @param string_view [Native::StringView] destination view
44
+ # @param value [String, nil] string value; +nil+ uses the native null sentinel
45
+ # @param keepalive [Array] receives allocated pointers that must remain alive
46
+ # @return [void]
47
+ def set_nullable_string_view(string_view, value, keepalive:)
48
+ if value.nil?
49
+ string_view[:data] = nil
50
+ string_view[:length] = SIZE_MAX
51
+ return
52
+ end
53
+
54
+ string = value
55
+ pointer = FFI::MemoryPointer.from_string(string)
56
+ keepalive << pointer
57
+ string_view[:data] = pointer
58
+ string_view[:length] = string.bytesize
59
+ end
60
+
61
+ # Writes pipeline-overridable constants into a stage descriptor.
62
+ #
63
+ # @param stage_descriptor [FFI::Struct] programmable stage descriptor
64
+ # @param constants [Hash{#to_s => Numeric}, nil] constant names and values
65
+ # @param keepalive [Array] receives allocated pointers that must remain alive
66
+ # @return [void]
67
+ def set_constants(stage_descriptor, constants, keepalive:)
68
+ stage_descriptor[:constant_count] = 0
69
+ stage_descriptor[:constants] = nil
70
+ return if constants.nil? || constants.empty?
71
+
72
+ constants_pointer = FFI::MemoryPointer.new(Native::ConstantEntry, constants.size)
73
+ keepalive << constants_pointer
74
+
75
+ constants.each_with_index do |(key, value), index|
76
+ entry_pointer = constants_pointer + (index * Native::ConstantEntry.size)
77
+ entry = Native::ConstantEntry.new(entry_pointer)
78
+ entry[:next_in_chain] = nil
79
+ set_nullable_string_view(entry[:key], key.to_s, keepalive:)
80
+ entry[:value] = value.to_f
81
+ end
82
+
83
+ stage_descriptor[:constant_count] = constants.size
84
+ stage_descriptor[:constants] = constants_pointer
85
+ end
86
+
87
+ # Checks required keys and warns about unsupported descriptor keys.
88
+ # @param options [Hash] descriptor options
89
+ # @param allowed [Array<Symbol>] supported keys
90
+ # @param required [Array<Symbol>] mandatory keys
91
+ # @return [Hash] original options
92
+ # @raise [ArgumentError] when required keys are missing
93
+ def validate_keys!(options, allowed:, required: [], context: "descriptor")
94
+ return options unless options.is_a?(Hash)
95
+
96
+ missing = required - options.keys
97
+ raise ArgumentError, "#{context} is missing required keys: #{missing.map(&:inspect).join(", ")}" unless missing.empty?
98
+
99
+ unknown = options.keys - allowed
100
+ warn "Unknown #{context} keys: #{unknown.map(&:inspect).join(", ")}" unless unknown.empty?
101
+ options
102
+ end
103
+ end
104
+ end
data/lib/wgpu/error.rb CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  module WGPU
4
4
  class Error < StandardError; end
5
+ class ValidationError < Error; end
6
+ class OutOfMemoryError < Error; end
7
+ class InternalError < Error; end
8
+ class DeviceLostError < Error; end
9
+ class TimeoutError < Error; end
5
10
  class InitializationError < Error; end
6
11
  class AdapterError < Error; end
7
12
  class DeviceError < Error; end
@@ -12,5 +17,70 @@ module WGPU
12
17
  class PipelineError < Error; end
13
18
  class CommandError < Error; end
14
19
  class SurfaceError < Error; end
20
+ class SurfaceAcquisitionError < SurfaceError
21
+ attr_reader :status
22
+
23
+ # Creates an acquisition error for a native surface status.
24
+ # @param status [Symbol] native acquisition status
25
+ # @param message [String, nil] optional override message
26
+ def initialize(status, message = nil)
27
+ @status = status
28
+ super(message || "Failed to get current surface texture: #{status}")
29
+ end
30
+ end
15
31
  class RenderBundleError < Error; end
32
+
33
+ GPUError = Data.define(:type, :message) do
34
+ # Converts a native error-scope result into a typed error.
35
+ # @param error [Hash, nil] native error result
36
+ # @return [GPUError, nil]
37
+ def self.from_hash(error)
38
+ return if error.nil? || error[:type].nil? || error[:type] == :no_error
39
+
40
+ new(type: error[:type], message: error[:message].to_s)
41
+ end
42
+
43
+ # Returns the Ruby exception class matching this GPU error type.
44
+ # @return [Class<Error>]
45
+ def exception_class
46
+ {
47
+ validation: ValidationError,
48
+ out_of_memory: OutOfMemoryError,
49
+ internal: InternalError,
50
+ device_lost: DeviceLostError
51
+ }.fetch(type, Error)
52
+ end
53
+
54
+ # Raises this error as its matching Ruby exception.
55
+ # @return [void]
56
+ # @raise [Error]
57
+ def raise!
58
+ raise exception_class, "GPU error (#{type}): #{message}"
59
+ end
60
+
61
+ # Returns a serializable representation of the error.
62
+ # @return [Hash]
63
+ def to_h
64
+ { type:, message: }
65
+ end
66
+ end
67
+
68
+ CompilationMessage = Data.define(
69
+ :type,
70
+ :message,
71
+ :line_num,
72
+ :line_pos,
73
+ :offset,
74
+ :length
75
+ ) do
76
+ alias line line_num
77
+ alias column line_pos
78
+
79
+ # Formats the diagnostic with source location, severity, and message.
80
+ # @return [String]
81
+ def to_s
82
+ location = line_num&.positive? ? "#{line_num}:#{line_pos}" : "unknown location"
83
+ "#{location}: #{type}: #{message}"
84
+ end
85
+ end
16
86
  end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WGPU
4
+ class << self
5
+ # Returns the configured wgpu-native log level.
6
+ attr_reader :log_level
7
+
8
+ # Sets the wgpu-native log level.
9
+ #
10
+ # @param level [Symbol, Integer] one of `:off`, `:error`, `:warn`, `:info`,
11
+ # `:debug`, or `:trace`
12
+ # @return [Symbol, Integer] the supplied level
13
+ def log_level=(level)
14
+ ensure_native_logging!
15
+ value = Native::EnumHelper.coerce(Native::LogLevel, level, name: "log level")
16
+ Native.wgpuSetLogLevel(value)
17
+ @log_level = Native::LogLevel[value]
18
+ end
19
+
20
+ # Registers a process-wide wgpu-native log handler.
21
+ #
22
+ # The native callback is retained by the WGPU module for the rest of the
23
+ # process, or until this method replaces it.
24
+ #
25
+ # @yieldparam level [Symbol] native log severity
26
+ # @yieldparam message [String] UTF-8 log message
27
+ # @return [WGPU]
28
+ def on_log(&handler)
29
+ raise ArgumentError, "block is required" unless handler
30
+
31
+ ensure_native_logging!
32
+ @log_handler = handler
33
+ @native_log_callback = FFI::Function.new(
34
+ :void,
35
+ [:uint32, Native::StringView.by_value, :pointer]
36
+ ) do |level, message, _userdata|
37
+ text =
38
+ if message[:data] && !message[:data].null? && message[:length].positive?
39
+ message[:data].read_string(message[:length])
40
+ else
41
+ ""
42
+ end
43
+ @log_handler.call(Native::LogLevel[level] || level, text)
44
+ rescue StandardError => e
45
+ warn "WGPU log handler failed: #{e.class}: #{e.message}"
46
+ end
47
+ Native.wgpuSetLogCallback(@native_log_callback, nil)
48
+ self
49
+ end
50
+
51
+ private
52
+
53
+ def ensure_native_logging!
54
+ return if Native.logging_available?
55
+
56
+ raise Error,
57
+ "wgpu-native logging is unavailable in the loaded library " \
58
+ "(expected #{Native::Distribution::VERSION})"
59
+ end
60
+ end
61
+
62
+ @log_level = :warn
63
+ end
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WGPU
4
+ module Native
5
+ class AbiVerifier
6
+ FORCE32_KEY = "force32"
7
+ EXTENSION_ENTRIES = {
8
+ "SType" => %w[invalid shadersourceglsl],
9
+ "FeatureName" => %w[
10
+ pushconstants textureadapterspecificformatfeatures multidrawindirectcount
11
+ vertexwritablestorage texturebindingarray
12
+ sampledtextureandstoragebufferarraynonuniformindexing
13
+ pipelinestatisticsquery storageresourcebindingarray
14
+ partiallyboundbindingarray textureformat16bitnorm
15
+ texturecompressionastchdr mappableprimarybuffers bufferbindingarray
16
+ uniformbufferandstoragetexturearraynonuniformindexing polygonmodeline
17
+ polygonmodepoint conservativerasterization spirvshaderpassthrough
18
+ vertexattribute64bit textureformatnv12 rayquery shaderf64 shaderi16
19
+ shaderprimitiveindex shaderearlydepthtest subgroup subgroupvertex
20
+ subgroupbarrier timestampqueryinsideencoders timestampqueryinsidepasses
21
+ shaderint64
22
+ ]
23
+ }.freeze
24
+
25
+ # Creates a verifier for a header and native binding.
26
+ # @param header_path [String, nil] header fixture to compare
27
+ # @param native [Module] native binding module
28
+ def initialize(header_path: nil, native: Native)
29
+ @header_path = header_path || self.class.default_header_path
30
+ @native = native
31
+ end
32
+
33
+ # Verifies the runtime version and Ruby enum ABI against the header.
34
+ # @return [true]
35
+ # @raise [WGPU::Error] when differences are detected
36
+ def verify!
37
+ differences = enum_differences
38
+ version_difference = native_version_difference
39
+ differences.unshift(version_difference) if version_difference
40
+ return true if differences.empty?
41
+
42
+ raise WGPU::Error, "wgpu-native ABI differences:\n#{differences.join("\n")}"
43
+ end
44
+
45
+ # Compares Ruby enum values with the pinned native header.
46
+ #
47
+ # @return [Array<String>] human-readable differences
48
+ def enum_differences
49
+ header_enums = parse_header
50
+ ruby_enums.filter_map do |name, mapping|
51
+ header_mapping = header_enums[name]
52
+ next unless header_mapping
53
+
54
+ compare_enum(name, ruby_mapping(mapping), header_mapping)
55
+ end
56
+ end
57
+
58
+ # Locates the pinned header used for ABI verification.
59
+ # @return [String] absolute header path
60
+ # @raise [WGPU::Error] if no header is available
61
+ def self.default_header_path
62
+ override = ENV["WGPU_HEADER_PATH"]
63
+ return File.expand_path(override) if override && !override.empty?
64
+
65
+ fixture = fixture_header_path
66
+ return fixture if File.file?(fixture)
67
+
68
+ candidates = Distribution.cache_directories.map do |cache_dir|
69
+ File.join(cache_dir, "include", "webgpu", "webgpu.h")
70
+ end
71
+ path = candidates.find { |candidate| File.file?(candidate) }
72
+ return path if path
73
+
74
+ raise WGPU::Error, <<~MSG.chomp
75
+ Pinned #{Distribution::VERSION} webgpu.h was not found.
76
+ Restore the repository ABI fixture, run `bundle exec rake wgpu:install`,
77
+ or set WGPU_HEADER_PATH.
78
+ Searched: #{([fixture] + candidates).join(", ")}
79
+ MSG
80
+ end
81
+
82
+ # Returns the repository's pinned enum fixture path.
83
+ #
84
+ # @return [String] absolute header fixture path
85
+ def self.fixture_header_path
86
+ File.expand_path("fixtures/webgpu-#{Distribution::VERSION}-enums.h", __dir__)
87
+ end
88
+
89
+ private
90
+
91
+ def native_version_difference
92
+ actual = @native.wgpuGetVersion
93
+ expected = Distribution::ENCODED_VERSION
94
+ return if actual == expected
95
+
96
+ "version: runtime #{Distribution.version_string(actual)} (0x#{format("%08x", actual)}) " \
97
+ "does not match pinned #{Distribution::VERSION} (0x#{format("%08x", expected)})"
98
+ end
99
+
100
+ def parse_header
101
+ source = File.read(@header_path)
102
+ source.scan(
103
+ /typedef enum WGPU(\w+)\s*\{(.*?)\}\s*WGPU\1(?:\s+WGPU_ENUM_ATTRIBUTE)?;/m
104
+ ).to_h do |name, body|
105
+ entries = body.scan(
106
+ /WGPU#{Regexp.escape(name)}_([A-Za-z0-9_]+)\s*=\s*(0x[0-9A-Fa-f]+|\d+)/
107
+ ).to_h { |entry, value| [normalize(entry), Integer(value)] }
108
+ entries.delete(FORCE32_KEY)
109
+ [name, entries]
110
+ end
111
+ end
112
+
113
+ def ruby_enums
114
+ @native.constants(false).sort.filter_map do |constant_name|
115
+ value = @native.const_get(constant_name)
116
+ [constant_name.to_s, value.to_h] if value.is_a?(FFI::Enum)
117
+ end
118
+ end
119
+
120
+ def ruby_mapping(mapping)
121
+ mapping.to_h { |key, value| [normalize(key), value] }
122
+ end
123
+
124
+ def normalize(name)
125
+ normalized = name.to_s.delete("_").downcase
126
+ normalized.sub(/\A([123])d/, 'd\1')
127
+ end
128
+
129
+ def compare_enum(name, ruby_mapping, header_mapping)
130
+ missing = header_mapping.keys - ruby_mapping.keys
131
+ extra = ruby_mapping.keys - header_mapping.keys - EXTENSION_ENTRIES.fetch(name, [])
132
+ wrong = (header_mapping.keys & ruby_mapping.keys).filter_map do |key|
133
+ next if header_mapping[key] == ruby_mapping[key]
134
+
135
+ "#{key}=#{ruby_mapping[key]} (header #{header_mapping[key]})"
136
+ end
137
+ return if missing.empty? && extra.empty? && wrong.empty?
138
+
139
+ "#{name}: missing=#{missing.inspect}, extra=#{extra.inspect}, mismatched=#{wrong.inspect}"
140
+ end
141
+ end
142
+ end
143
+ end
@@ -8,6 +8,12 @@ module WGPU
8
8
  callback :request_device_callback,
9
9
  [RequestDeviceStatus, :pointer, StringView.by_value, :pointer, :pointer], :void
10
10
 
11
+ callback :create_compute_pipeline_async_callback,
12
+ [CreatePipelineAsyncStatus, :pointer, StringView.by_value, :pointer, :pointer], :void
13
+
14
+ callback :create_render_pipeline_async_callback,
15
+ [CreatePipelineAsyncStatus, :pointer, StringView.by_value, :pointer, :pointer], :void
16
+
11
17
  callback :buffer_map_callback,
12
18
  [MapAsyncStatus, StringView.by_value, :pointer, :pointer], :void
13
19
 
@@ -15,12 +21,15 @@ module WGPU
15
21
  [:pointer, ErrorType, StringView.by_value, :pointer, :pointer], :void
16
22
 
17
23
  callback :device_lost_callback,
18
- [:pointer, :uint32, StringView.by_value, :pointer, :pointer], :void
24
+ [:pointer, DeviceLostReason, StringView.by_value, :pointer, :pointer], :void
19
25
 
20
26
  callback :pop_error_scope_callback,
21
27
  [PopErrorScopeStatus, ErrorType, StringView.by_value, :pointer, :pointer], :void
22
28
 
23
29
  callback :queue_work_done_callback,
24
30
  [QueueWorkDoneStatus, :pointer, :pointer], :void
31
+
32
+ callback :log_callback,
33
+ [LogLevel, StringView.by_value, :pointer], :void
25
34
  end
26
35
  end
@@ -3,12 +3,42 @@
3
3
  module WGPU
4
4
  module Native
5
5
  class << self
6
+ # Reports whether future-based callback waiting is available.
7
+ # @return [Boolean]
6
8
  def future_api?
7
- respond_to?(:wgpuInstanceWaitAny)
9
+ optional_function_available?(:wgpuInstanceWaitAny)
8
10
  end
9
11
 
12
+ # Reports whether explicit device polling is available.
13
+ # @return [Boolean]
10
14
  def device_poll_available?
11
- respond_to?(:wgpuDevicePoll)
15
+ optional_function_available?(:wgpuDevicePoll)
16
+ end
17
+
18
+ # wgpu-native v27 exports wgpuShaderModuleGetCompilationInfo, but the
19
+ # implementation is a Rust panic stub. Calling it aborts the process, so
20
+ # symbol presence alone cannot be used as a capability check.
21
+ def compilation_info_available?
22
+ Distribution.capability_implemented?(:compilation_info)
23
+ end
24
+
25
+ # Reports whether querying buffer map state is safe in the pinned runtime.
26
+ # @return [Boolean]
27
+ def buffer_map_state_available?
28
+ Distribution.capability_implemented?(:buffer_map_state)
29
+ end
30
+
31
+ # Reports whether native asynchronous pipeline creation is implemented.
32
+ # @return [Boolean]
33
+ def pipeline_async_available?
34
+ Distribution.capability_implemented?(:pipeline_async)
35
+ end
36
+
37
+ # Reports whether native logging callbacks and levels are available.
38
+ # @return [Boolean]
39
+ def logging_available?
40
+ optional_function_available?(:wgpuSetLogCallback) &&
41
+ optional_function_available?(:wgpuSetLogLevel)
12
42
  end
13
43
  end
14
44
  end