electra 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/CHANGELOG.md +5 -0
- data/LICENSE-SPIRV-Headers.txt +26 -0
- data/LICENSE.txt +21 -0
- data/README.md +141 -0
- data/examples/example_shaders.rb +55 -0
- data/lib/electra/binary.rb +475 -0
- data/lib/electra/data_compat.rb +25 -0
- data/lib/electra/function.rb +380 -0
- data/lib/electra/grammar.rb +968 -0
- data/lib/electra/module.rb +299 -0
- data/lib/electra/type.rb +11 -0
- data/lib/electra/value.rb +39 -0
- data/lib/electra/version.rb +6 -0
- data/lib/electra.rb +32 -0
- data/sig/electra.rbs +105 -0
- metadata +58 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Electra
|
|
4
|
+
# Ordered SPIR-V module builder. IDs belong to a module, never a global pool.
|
|
5
|
+
class Module
|
|
6
|
+
# SPIR-V logical module layout order.
|
|
7
|
+
SECTIONS = %i[capabilities extensions imports memory_model entry_points execution_modes debug annotations declarations functions].freeze
|
|
8
|
+
attr_reader :version
|
|
9
|
+
|
|
10
|
+
# @param version [String] SPIR-V version, 1.0 through 1.6
|
|
11
|
+
# @param generator [Integer] 32-bit generator word; zero means unregistered
|
|
12
|
+
def initialize(version: "1.0", generator: 0)
|
|
13
|
+
major, minor = version.to_s.split(".").map(&:to_i)
|
|
14
|
+
raise Error, "supported versions are 1.0 through 1.6" unless major == 1 && minor && minor.between?(0, 6) && version.to_s == "#{major}.#{minor}"
|
|
15
|
+
@version = (major << 16) | (minor << 8)
|
|
16
|
+
@generator = Binary.word(generator)
|
|
17
|
+
@sections = SECTIONS.to_h { |section| [section, []] }
|
|
18
|
+
@next_id = 1
|
|
19
|
+
@types = {}
|
|
20
|
+
@constants = {}
|
|
21
|
+
@names = {}
|
|
22
|
+
@definitions = {}
|
|
23
|
+
@capabilities = {}
|
|
24
|
+
@extensions = {}
|
|
25
|
+
@imports = {}
|
|
26
|
+
@decorations = {}
|
|
27
|
+
@entries = {}
|
|
28
|
+
@functions = []
|
|
29
|
+
capability(:Shader)
|
|
30
|
+
memory_model(:Logical, :GLSL450)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Allocate a module-local forward ID. A named reservation is idempotent.
|
|
34
|
+
# @param name [Object, nil] optional lookup key
|
|
35
|
+
# @return [Integer] SSA identifier, which must later be defined exactly once
|
|
36
|
+
def reserve_id(name = nil)
|
|
37
|
+
return @names[name] if name && @names.key?(name)
|
|
38
|
+
raise Error, "SPIR-V id limit exceeded" if @next_id >= Binary::MAX_BOUND
|
|
39
|
+
id = @next_id
|
|
40
|
+
@next_id += 1
|
|
41
|
+
@names[name] = id if name
|
|
42
|
+
id
|
|
43
|
+
end
|
|
44
|
+
alias ref reserve_id
|
|
45
|
+
|
|
46
|
+
# Low-level escape hatch. Operands follow the Khronos grammar order,
|
|
47
|
+
# including result type/result IDs. Reserve forward IDs with #ref first.
|
|
48
|
+
def emit(section, opcode, *operands)
|
|
49
|
+
buffer = @sections[section] or raise Error, "unknown section #{section}"
|
|
50
|
+
words = Binary.encode(opcode.to_s, operands)
|
|
51
|
+
schema = INSTRUCTIONS.fetch(opcode.to_s)[1]
|
|
52
|
+
index = schema.index { |kind, _| kind == "IdResult" }
|
|
53
|
+
if index
|
|
54
|
+
id = operands[index].to_i
|
|
55
|
+
raise Error, "id #{id} was not reserved in this module" unless id.between?(1, @next_id - 1)
|
|
56
|
+
raise Error, "id #{id} already defined" if @definitions[id]
|
|
57
|
+
@definitions[id] = true
|
|
58
|
+
end
|
|
59
|
+
buffer.concat(words)
|
|
60
|
+
self
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Add a capability once, using its Khronos spelling.
|
|
64
|
+
# @return [self]
|
|
65
|
+
def capability(name)
|
|
66
|
+
key = Binary.enum("Capability", name)[0]
|
|
67
|
+
emit(:capabilities, "OpCapability", name) unless @capabilities[key]
|
|
68
|
+
@capabilities[key] = true
|
|
69
|
+
self
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Add an OpExtension declaration once.
|
|
73
|
+
# @return [self]
|
|
74
|
+
def extension(name)
|
|
75
|
+
emit(:extensions, "OpExtension", name) unless @extensions[name]
|
|
76
|
+
@extensions[name] = true
|
|
77
|
+
self
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# @return [Integer] deduplicated extended-instruction set ID
|
|
81
|
+
def import(name)
|
|
82
|
+
@imports[name] ||= reserve_id.tap { |id| emit(:imports, "OpExtInstImport", id, name) }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Replace the module's single addressing/memory-model declaration.
|
|
86
|
+
# @return [self]
|
|
87
|
+
def memory_model(addressing = :Logical, model = :GLSL450)
|
|
88
|
+
@sections[:memory_model] = Binary.encode("OpMemoryModel", [addressing, model])
|
|
89
|
+
self
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Attach a debug name; does not change numeric ID allocation.
|
|
93
|
+
# @return [self]
|
|
94
|
+
def name(target, text)
|
|
95
|
+
emit(:debug, "OpName", identifier(target), text)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Add a decoration. Conflicting values for the same decoration are errors.
|
|
99
|
+
# @return [self]
|
|
100
|
+
def decorate(target, decoration, *values)
|
|
101
|
+
apply_decoration("OpDecorate", identifier(target), decoration, values)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Decorate a struct member, for example Offset or MatrixStride.
|
|
105
|
+
# @return [self]
|
|
106
|
+
def member_decorate(target, member, decoration, *values)
|
|
107
|
+
apply_decoration("OpMemberDecorate", identifier(target), decoration, values, member)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Resolve and structurally intern a type description.
|
|
111
|
+
# @param description [Symbol, String, Array, Type] scalar/composite type
|
|
112
|
+
# @return [Type] immutable type record owned by this module
|
|
113
|
+
def type(description)
|
|
114
|
+
if description.is_a?(Type)
|
|
115
|
+
raise Error, "type belongs to another module" unless description.owner.equal?(self)
|
|
116
|
+
return description
|
|
117
|
+
end
|
|
118
|
+
raise Error, "type must be a Symbol, String, Type or Array" unless description.is_a?(Array) || description.is_a?(Symbol) || description.is_a?(String)
|
|
119
|
+
key = description.is_a?(Array) ? description.dup.freeze : description.to_sym
|
|
120
|
+
return @types[key] if @types.key?(key)
|
|
121
|
+
if (alias_description = expand_type_alias(key))
|
|
122
|
+
return @types[key] = type(alias_description)
|
|
123
|
+
end
|
|
124
|
+
define_type(key, description)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# @return [Type] fixed-size array with optional explicit ArrayStride
|
|
128
|
+
def array(element, length, stride: nil)
|
|
129
|
+
value = type([:array, element, length, stride])
|
|
130
|
+
decorate(value, :ArrayStride, stride) if stride
|
|
131
|
+
value
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# @return [Type] struct with optional member offsets and Block decoration
|
|
135
|
+
def struct(*members, offsets: nil, block: false)
|
|
136
|
+
raise Error, "one offset per struct member is required" if offsets && offsets.length != members.length
|
|
137
|
+
value = type([:struct, members.freeze, nil, offsets&.freeze, block])
|
|
138
|
+
decorate(value, :Block) if block
|
|
139
|
+
offsets&.each_with_index { |offset, index| member_decorate(value, index, :Offset, offset) }
|
|
140
|
+
value
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Intern a scalar or nested composite constant by its exact bit pattern.
|
|
144
|
+
# @return [Value] module-scoped constant (not a mutable variable)
|
|
145
|
+
def constant(description, value)
|
|
146
|
+
value_type = type(description)
|
|
147
|
+
opcode, args = constant_encoding(value_type, value)
|
|
148
|
+
key = [value_type.id, opcode, *args]
|
|
149
|
+
@constants[key] ||= begin
|
|
150
|
+
id = reserve_id
|
|
151
|
+
emit(:declarations, opcode, value_type.id, id, *args)
|
|
152
|
+
Value.new(self, nil, value_type, id)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# @yieldparam function [Function] fragment shader builder
|
|
157
|
+
# @return [Function] completed entry point
|
|
158
|
+
def fragment_shader(name = "main", &block) = define_shader(:Fragment, name, &block)
|
|
159
|
+
# @yieldparam function [Function] vertex shader builder
|
|
160
|
+
# @return [Function] completed entry point
|
|
161
|
+
def vertex_shader(name = "main", &block) = define_shader(:Vertex, name, &block)
|
|
162
|
+
|
|
163
|
+
# Serialize sections in specification order and reject unresolved IDs.
|
|
164
|
+
# @return [String] little-endian SPIR-V bytes; retain for repeated GPU use
|
|
165
|
+
def to_binary
|
|
166
|
+
raise Error, "unfinished shader" unless @functions.all?(&:finished?)
|
|
167
|
+
missing = (1...@next_id).reject { |id| @definitions[id] }
|
|
168
|
+
raise Error, "unresolved ids: #{missing.join(', ')}" unless missing.empty?
|
|
169
|
+
binary = [Binary::MAGIC, @version, @generator, @next_id, 0, *SECTIONS.flat_map { |section| @sections.fetch(section) }].pack("V*")
|
|
170
|
+
Binary.disassemble(binary)
|
|
171
|
+
binary
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Extract an ID after checking module ownership.
|
|
175
|
+
# @return [Integer]
|
|
176
|
+
def identifier(value)
|
|
177
|
+
if value.respond_to?(:owner)
|
|
178
|
+
raise Error, "value belongs to another module" unless value.owner.equal?(self)
|
|
179
|
+
end
|
|
180
|
+
value.respond_to?(:id) ? value.id : value
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
private
|
|
184
|
+
|
|
185
|
+
def expand_type_alias(key)
|
|
186
|
+
return unless key.is_a?(Symbol)
|
|
187
|
+
if (match = key.to_s.match(/\A([biu]?)vec([2-4])\z/))
|
|
188
|
+
[:vector, {"" => :float, "b" => :bool, "i" => :int, "u" => :uint}.fetch(match[1]), match[2].to_i]
|
|
189
|
+
elsif (match = key.to_s.match(/\Amat([2-4])(?:x([2-4]))?\z/))
|
|
190
|
+
[:matrix, "vec#{match[2] || match[1]}".to_sym, match[1].to_i]
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def define_type(key, description)
|
|
195
|
+
element = nil
|
|
196
|
+
length = nil
|
|
197
|
+
members = []
|
|
198
|
+
case key
|
|
199
|
+
when :void then kind, opcode, args = :void, "OpTypeVoid", []
|
|
200
|
+
when :bool then kind, opcode, args = :bool, "OpTypeBool", []
|
|
201
|
+
when :float then kind, opcode, args = :float, "OpTypeFloat", [32]
|
|
202
|
+
when :int then kind, opcode, args = :int, "OpTypeInt", [32, 1]
|
|
203
|
+
when :uint then kind, opcode, args = :uint, "OpTypeInt", [32, 0]
|
|
204
|
+
else
|
|
205
|
+
raise Error, "unknown type #{description.inspect}" unless key.is_a?(Array)
|
|
206
|
+
kind = key[0]
|
|
207
|
+
case kind
|
|
208
|
+
when :vector, :matrix
|
|
209
|
+
element, length = type(key[1]), key[2]
|
|
210
|
+
raise Error, "vector/matrix dimensions must be 2..4" unless length.is_a?(Integer) && length.between?(2, 4)
|
|
211
|
+
raise Error, "invalid vector scalar" if kind == :vector && !%i[float int uint bool].include?(element.kind)
|
|
212
|
+
raise Error, "matrix needs float column vectors" if kind == :matrix && !(element.kind == :vector && element.element.kind == :float)
|
|
213
|
+
capability(:Matrix) if kind == :matrix
|
|
214
|
+
opcode, args = kind == :vector ? ["OpTypeVector", [element.id, length]] : ["OpTypeMatrix", [element.id, length]]
|
|
215
|
+
when :pointer
|
|
216
|
+
element = type(key[2])
|
|
217
|
+
length = key[1]
|
|
218
|
+
opcode, args = "OpTypePointer", [key[1], element.id]
|
|
219
|
+
when :function
|
|
220
|
+
members = key.drop(1).map { |item| type(item) }
|
|
221
|
+
opcode, args = "OpTypeFunction", members.map(&:id)
|
|
222
|
+
when :image2d
|
|
223
|
+
element = type(:float)
|
|
224
|
+
opcode, args = "OpTypeImage", [element.id, :"2D", 0, 0, 0, 1, :Unknown]
|
|
225
|
+
when :sampled_image
|
|
226
|
+
element = type([:image2d])
|
|
227
|
+
opcode, args = "OpTypeSampledImage", [element.id]
|
|
228
|
+
when :array
|
|
229
|
+
element, length = type(key[1]), key[2]
|
|
230
|
+
raise Error, "array length must be positive" unless length.is_a?(Integer) && length.between?(1, 0xffffffff)
|
|
231
|
+
opcode, args = "OpTypeArray", [element.id, constant(:uint, length).id]
|
|
232
|
+
when :struct
|
|
233
|
+
members = key[1].map { |item| type(item) }
|
|
234
|
+
opcode, args = "OpTypeStruct", members.map(&:id)
|
|
235
|
+
else
|
|
236
|
+
raise Error, "unknown type #{description.inspect}"
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
# Aliases, pointers and composites all converge on structural IDs.
|
|
240
|
+
canonical = [opcode, *args, *(key.is_a?(Array) && [:array, :struct].include?(kind) ? key.drop(3) : [])].freeze
|
|
241
|
+
return @types[key] = @types[canonical] if @types.key?(canonical)
|
|
242
|
+
id = reserve_id
|
|
243
|
+
emit(:declarations, opcode, id, *args)
|
|
244
|
+
value = Type.new(id:, kind:, element:, length:, members: members.freeze, owner: self)
|
|
245
|
+
@types[key] = @types[canonical] = value
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def constant_encoding(value_type, value)
|
|
249
|
+
case value_type.kind
|
|
250
|
+
when :bool
|
|
251
|
+
raise Error, "boolean constant must be true or false" unless value == true || value == false
|
|
252
|
+
opcode, args = value ? ["OpConstantTrue", []] : ["OpConstantFalse", []]
|
|
253
|
+
when :float
|
|
254
|
+
raise Error, "float constant must be Numeric" unless value.is_a?(Numeric)
|
|
255
|
+
opcode, args = "OpConstant", [Binary.float_bits(value)]
|
|
256
|
+
when :int, :uint
|
|
257
|
+
range = value_type.kind == :int ? (-0x80000000..0x7fffffff) : (0..0xffffffff)
|
|
258
|
+
raise Error, "integer constant out of range" unless value.is_a?(Integer) && range.cover?(value)
|
|
259
|
+
opcode, args = "OpConstant", [value & 0xffffffff]
|
|
260
|
+
when :vector, :matrix, :array, :struct
|
|
261
|
+
expected = value_type.kind == :struct ? value_type.members.length : value_type.length
|
|
262
|
+
raise Error, "expected #{expected} composite elements" unless value.is_a?(Array) && value.length == expected
|
|
263
|
+
args = value.each_with_index.map do |item, index|
|
|
264
|
+
component_type = value_type.kind == :struct ? value_type.members[index] : value_type.element
|
|
265
|
+
constant(component_type, item).id
|
|
266
|
+
end
|
|
267
|
+
opcode = "OpConstantComposite"
|
|
268
|
+
else
|
|
269
|
+
raise Error, "type cannot be a constant"
|
|
270
|
+
end
|
|
271
|
+
[opcode, args]
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def apply_decoration(opcode, id, decoration, values, member = nil)
|
|
275
|
+
number = Binary.enum("Decoration", decoration)[0]
|
|
276
|
+
key = [id, member, number]
|
|
277
|
+
raise Error, "conflicting #{decoration} decoration" if @decorations.key?(key) && @decorations[key] != values
|
|
278
|
+
unless @decorations.key?(key)
|
|
279
|
+
emit(:annotations, opcode, id, *[member].compact, decoration, *values)
|
|
280
|
+
@decorations[key] = values.freeze
|
|
281
|
+
end
|
|
282
|
+
self
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def define_shader(stage, entry_name)
|
|
286
|
+
raise Error, "shader block required" unless block_given?
|
|
287
|
+
raise Error, "duplicate entry point #{entry_name}" if @entries[[stage, entry_name]]
|
|
288
|
+
function = Function.new(self, stage)
|
|
289
|
+
@functions << function
|
|
290
|
+
yield function
|
|
291
|
+
function.finish
|
|
292
|
+
@entries[[stage, entry_name]] = function.id
|
|
293
|
+
emit(:entry_points, "OpEntryPoint", stage, function.id, entry_name, *function.interfaces)
|
|
294
|
+
emit(:execution_modes, "OpExecutionMode", function.id, :OriginUpperLeft) if stage == :Fragment
|
|
295
|
+
name(function.id, entry_name)
|
|
296
|
+
function
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
end
|
data/lib/electra/type.rb
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Electra
|
|
4
|
+
# Immutable structural type metadata; IDs are local to its owner module.
|
|
5
|
+
Type = Data.define(:id, :kind, :element, :length, :members, :owner) do
|
|
6
|
+
# @return [Integer] numeric SPIR-V type identifier
|
|
7
|
+
def to_i = id
|
|
8
|
+
# @return [Type] vector element type, or self for other types
|
|
9
|
+
def scalar = kind == :vector ? element : self
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Electra
|
|
4
|
+
# Immutable SSA value or typed variable pointer, tied to one module/function.
|
|
5
|
+
class Value
|
|
6
|
+
attr_reader :owner, :function, :type, :id, :storage
|
|
7
|
+
|
|
8
|
+
def initialize(owner, function, type, id, storage = nil)
|
|
9
|
+
@owner, @function, @type, @id, @storage = owner, function, type, id, storage
|
|
10
|
+
freeze
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# @return [Integer] numeric SPIR-V identifier
|
|
14
|
+
def to_i = id
|
|
15
|
+
# @return [Value] loaded SSA value, or self for an existing SSA value
|
|
16
|
+
def load
|
|
17
|
+
return self unless storage
|
|
18
|
+
function.result("OpLoad", type, id)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Store a type-compatible value into a writable variable.
|
|
22
|
+
# @return [self]
|
|
23
|
+
def store(value)
|
|
24
|
+
raise Error, "cannot store to #{storage || 'an SSA value'}" unless %i[Output Function Private].include?(storage)
|
|
25
|
+
item = function.value(value, type)
|
|
26
|
+
function.emit("OpStore", id, item.id)
|
|
27
|
+
self
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @param index [Integer, Symbol] vector index or rgba/xyzw component
|
|
31
|
+
# @return [Value] extracted scalar
|
|
32
|
+
def component(index)
|
|
33
|
+
raise Error, "component needs a function-local value" unless function
|
|
34
|
+
index = {r: 0, g: 1, b: 2, a: 3, x: 0, y: 1, z: 2, w: 3}.fetch(index, index)
|
|
35
|
+
raise Error, "component index out of bounds" unless type.kind == :vector && index.is_a?(Integer) && index.between?(0, type.length - 1)
|
|
36
|
+
function.result("OpCompositeExtract", type.element, load.id, index)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
data/lib/electra.rb
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "electra/version"
|
|
4
|
+
require_relative "electra/data_compat"
|
|
5
|
+
|
|
6
|
+
# Pure Ruby construction and inspection of UI vertex/fragment SPIR-V modules.
|
|
7
|
+
module Electra
|
|
8
|
+
# Invalid shader construction, malformed binary or unsupported assembly.
|
|
9
|
+
class Error < StandardError; end
|
|
10
|
+
|
|
11
|
+
# Decode a SPIR-V module using the generated Khronos operand grammar.
|
|
12
|
+
# @param binary [String] complete SPIR-V bytes, little or big endian
|
|
13
|
+
# @return [String] numeric-ID assembly, including round-trip header comments
|
|
14
|
+
# @raise [Error] on malformed header, framing, strings, operands or ID bounds
|
|
15
|
+
def self.disassemble(binary)
|
|
16
|
+
Binary.disassemble(binary)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Assemble numeric-ID SPIR-V text without invoking an external compiler.
|
|
20
|
+
# @param source [String] assembly in the format returned by {.disassemble}
|
|
21
|
+
# @return [String] binary bytes
|
|
22
|
+
# @raise [Error] on malformed or unsupported assembly
|
|
23
|
+
def self.assemble(source)
|
|
24
|
+
Binary.assemble(source)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
require_relative "electra/binary"
|
|
29
|
+
require_relative "electra/type"
|
|
30
|
+
require_relative "electra/module"
|
|
31
|
+
require_relative "electra/value"
|
|
32
|
+
require_relative "electra/function"
|
data/sig/electra.rbs
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
module Electra
|
|
2
|
+
VERSION: String
|
|
3
|
+
type type_description = Symbol | String | Type | Array[untyped]
|
|
4
|
+
type branch = ^(Function) -> untyped
|
|
5
|
+
|
|
6
|
+
class Error < StandardError
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def self.disassemble: (String binary) -> String
|
|
10
|
+
def self.assemble: (String source) -> String
|
|
11
|
+
|
|
12
|
+
class Type < Data
|
|
13
|
+
attr_reader id: Integer
|
|
14
|
+
attr_reader kind: Symbol
|
|
15
|
+
attr_reader element: Type?
|
|
16
|
+
attr_reader length: (Integer | Symbol)?
|
|
17
|
+
attr_reader members: Array[Type]
|
|
18
|
+
attr_reader owner: Module
|
|
19
|
+
def to_i: () -> Integer
|
|
20
|
+
def scalar: () -> Type
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class Module
|
|
24
|
+
attr_reader version: Integer
|
|
25
|
+
def initialize: (?version: String, ?generator: Integer) -> void
|
|
26
|
+
def reserve_id: (?untyped name) -> Integer
|
|
27
|
+
alias ref reserve_id
|
|
28
|
+
def emit: (Symbol section, String | Symbol opcode, *untyped operands) -> self
|
|
29
|
+
def capability: (String | Symbol | Integer name) -> self
|
|
30
|
+
def extension: (String name) -> self
|
|
31
|
+
def import: (String name) -> Integer
|
|
32
|
+
def memory_model: (?String | Symbol addressing, ?String | Symbol model) -> self
|
|
33
|
+
def name: (Integer | Type | Value target, String text) -> self
|
|
34
|
+
def decorate: (Integer | Type | Value target, String | Symbol decoration, *untyped values) -> self
|
|
35
|
+
def member_decorate: (Integer | Type | Value target, Integer member, String | Symbol decoration, *untyped values) -> self
|
|
36
|
+
def type: (type_description description) -> Type
|
|
37
|
+
def array: (type_description element, Integer length, ?stride: Integer?) -> Type
|
|
38
|
+
def struct: (*type_description members, ?offsets: Array[Integer]?, ?block: bool) -> Type
|
|
39
|
+
def constant: (type_description description, untyped value) -> Value
|
|
40
|
+
def fragment_shader: (?String name) { (Function) -> untyped } -> Function
|
|
41
|
+
def vertex_shader: (?String name) { (Function) -> untyped } -> Function
|
|
42
|
+
def to_binary: () -> String
|
|
43
|
+
def identifier: (Integer | Type | Value value) -> Integer
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class Value
|
|
47
|
+
attr_reader owner: Module
|
|
48
|
+
attr_reader function: Function?
|
|
49
|
+
attr_reader type: Type
|
|
50
|
+
attr_reader id: Integer
|
|
51
|
+
attr_reader storage: Symbol?
|
|
52
|
+
def initialize: (Module owner, Function? function, Type type, Integer id, ?Symbol? storage) -> void
|
|
53
|
+
def to_i: () -> Integer
|
|
54
|
+
def load: () -> Value
|
|
55
|
+
def store: (untyped value) -> self
|
|
56
|
+
def component: (Integer | Symbol index) -> Value
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
class Function
|
|
60
|
+
attr_reader owner: Module
|
|
61
|
+
attr_reader id: Integer
|
|
62
|
+
attr_reader interfaces: Array[Integer]
|
|
63
|
+
attr_reader stage: Symbol
|
|
64
|
+
def initialize: (Module owner, Symbol stage) -> void
|
|
65
|
+
def input: (type_description type, ?location: Integer?, ?builtin: Symbol?, ?flat: bool) -> Value
|
|
66
|
+
def output: (type_description type, ?location: Integer?, ?builtin: Symbol?) -> Value
|
|
67
|
+
def sampled_image_2d: (?set: Integer, ?binding: Integer) -> Value
|
|
68
|
+
def uniform_buffer: (*type_description types, ?set: Integer, ?binding: Integer, offsets: Array[Integer]) -> Value
|
|
69
|
+
def push_constant: (*type_description types, offsets: Array[Integer]) -> Value
|
|
70
|
+
def member: (Value buffer, Integer index) -> Value
|
|
71
|
+
def constant: (untyped item, ?type_description? type) -> Value
|
|
72
|
+
def value: (untyped item, ?type_description? expected) -> Value
|
|
73
|
+
def construct: (type_description description, *untyped items) -> Value
|
|
74
|
+
def splat: (untyped item, ?Integer count) -> Value
|
|
75
|
+
def sample: (Value texture, untyped uv, ?lod: untyped) -> Value
|
|
76
|
+
def add: (untyped left, untyped right) -> Value
|
|
77
|
+
def sub: (untyped left, untyped right) -> Value
|
|
78
|
+
def div: (untyped left, untyped right) -> Value
|
|
79
|
+
def mod: (untyped left, untyped right) -> Value
|
|
80
|
+
def mul: (untyped left, untyped right) -> Value
|
|
81
|
+
def negate: (untyped item) -> Value
|
|
82
|
+
def dot: (untyped left, untyped right) -> Value
|
|
83
|
+
def transpose: (untyped item) -> Value
|
|
84
|
+
def convert: (untyped item, type_description description) -> Value
|
|
85
|
+
def equal: (untyped left, untyped right) -> Value
|
|
86
|
+
def not_equal: (untyped left, untyped right) -> Value
|
|
87
|
+
def less_than: (untyped left, untyped right) -> Value
|
|
88
|
+
def less_equal: (untyped left, untyped right) -> Value
|
|
89
|
+
def greater_than: (untyped left, untyped right) -> Value
|
|
90
|
+
def greater_equal: (untyped left, untyped right) -> Value
|
|
91
|
+
def logical_and: (untyped left, untyped right) -> Value
|
|
92
|
+
def logical_or: (untyped left, untyped right) -> Value
|
|
93
|
+
def logical_not: (untyped item) -> Value
|
|
94
|
+
def select: (untyped condition, untyped yes, untyped no) -> Value
|
|
95
|
+
def if_else: (untyped condition, branch yes, ?branch? no) -> Value?
|
|
96
|
+
def discard: () -> nil
|
|
97
|
+
def discard_if: (untyped condition) -> nil
|
|
98
|
+
def return_void: () -> nil
|
|
99
|
+
def ext: (Symbol operation, *untyped items) -> Value
|
|
100
|
+
def emit: (String opcode, *untyped operands) -> self
|
|
101
|
+
def result: (String opcode, type_description type, *untyped operands) -> Value
|
|
102
|
+
def finish: () -> self
|
|
103
|
+
def finished?: () -> bool
|
|
104
|
+
end
|
|
105
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: electra
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Yudai Takada
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
email:
|
|
13
|
+
- t.yudai92@gmail.com
|
|
14
|
+
executables: []
|
|
15
|
+
extensions: []
|
|
16
|
+
extra_rdoc_files: []
|
|
17
|
+
files:
|
|
18
|
+
- CHANGELOG.md
|
|
19
|
+
- LICENSE-SPIRV-Headers.txt
|
|
20
|
+
- LICENSE.txt
|
|
21
|
+
- README.md
|
|
22
|
+
- examples/example_shaders.rb
|
|
23
|
+
- lib/electra.rb
|
|
24
|
+
- lib/electra/binary.rb
|
|
25
|
+
- lib/electra/data_compat.rb
|
|
26
|
+
- lib/electra/function.rb
|
|
27
|
+
- lib/electra/grammar.rb
|
|
28
|
+
- lib/electra/module.rb
|
|
29
|
+
- lib/electra/type.rb
|
|
30
|
+
- lib/electra/value.rb
|
|
31
|
+
- lib/electra/version.rb
|
|
32
|
+
- sig/electra.rbs
|
|
33
|
+
homepage: https://github.com/noxdea/electra
|
|
34
|
+
licenses:
|
|
35
|
+
- MIT
|
|
36
|
+
metadata:
|
|
37
|
+
source_code_uri: https://github.com/noxdea/electra
|
|
38
|
+
changelog_uri: https://github.com/noxdea/electra/blob/main/CHANGELOG.md
|
|
39
|
+
allowed_push_host: https://rubygems.org
|
|
40
|
+
rubygems_mfa_required: 'true'
|
|
41
|
+
rdoc_options: []
|
|
42
|
+
require_paths:
|
|
43
|
+
- lib
|
|
44
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
45
|
+
requirements:
|
|
46
|
+
- - ">="
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: '3.1'
|
|
49
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
54
|
+
requirements: []
|
|
55
|
+
rubygems_version: 4.0.19
|
|
56
|
+
specification_version: 4
|
|
57
|
+
summary: A pure Ruby SPIR-V shader emitter and disassembler
|
|
58
|
+
test_files: []
|