rlsl 0.1.1 → 1.0.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 (91) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +8 -0
  3. data/CHANGELOG.md +13 -2
  4. data/README.md +101 -26
  5. data/Rakefile +7 -0
  6. data/lib/rlsl/base_translator/call_parser.rb +79 -0
  7. data/lib/rlsl/base_translator/code_rewriter.rb +107 -0
  8. data/lib/rlsl/base_translator/code_scanner.rb +140 -0
  9. data/lib/rlsl/base_translator.rb +182 -64
  10. data/lib/rlsl/code_generator/math_prelude.rb +172 -0
  11. data/lib/rlsl/code_generator/ruby_wrapper_generator.rb +137 -0
  12. data/lib/rlsl/code_generator/shader_function_generator.rb +19 -0
  13. data/lib/rlsl/code_generator/template_context.rb +44 -0
  14. data/lib/rlsl/code_generator/uniform_struct_generator.rb +29 -0
  15. data/lib/rlsl/code_generator.rb +30 -202
  16. data/lib/rlsl/compiled_shader.rb +5 -13
  17. data/lib/rlsl/errors.rb +29 -0
  18. data/lib/rlsl/function_context.rb +31 -14
  19. data/lib/rlsl/glsl/translator.rb +35 -41
  20. data/lib/rlsl/msl/shader.rb +34 -46
  21. data/lib/rlsl/msl/translator.rb +38 -34
  22. data/lib/rlsl/msl/uniform_buffer_packer.rb +72 -0
  23. data/lib/rlsl/prism/ast_visitor/control_flow_visiting.rb +137 -0
  24. data/lib/rlsl/prism/ast_visitor/definition_visiting.rb +92 -0
  25. data/lib/rlsl/prism/ast_visitor/expression_visiting.rb +172 -0
  26. data/lib/rlsl/prism/ast_visitor/scope_context.rb +48 -0
  27. data/lib/rlsl/prism/ast_visitor/visitor_registry.rb +21 -0
  28. data/lib/rlsl/prism/ast_visitor.rb +100 -286
  29. data/lib/rlsl/prism/builtins/function_registry.rb +131 -0
  30. data/lib/rlsl/prism/builtins/operator_rules.rb +123 -0
  31. data/lib/rlsl/prism/builtins/swizzle_rules.rb +47 -0
  32. data/lib/rlsl/prism/builtins.rb +39 -148
  33. data/lib/rlsl/prism/compilation_unit.rb +7 -0
  34. data/lib/rlsl/prism/emitters/base_emitter/control_flow_emission.rb +126 -0
  35. data/lib/rlsl/prism/emitters/base_emitter/definition_emission.rb +134 -0
  36. data/lib/rlsl/prism/emitters/base_emitter/expression_emission.rb +108 -0
  37. data/lib/rlsl/prism/emitters/base_emitter/statement_emission.rb +95 -0
  38. data/lib/rlsl/prism/emitters/base_emitter.rb +120 -414
  39. data/lib/rlsl/prism/emitters/c_emitter.rb +165 -112
  40. data/lib/rlsl/prism/emitters/glsl_emitter.rb +63 -50
  41. data/lib/rlsl/prism/emitters/msl_emitter.rb +67 -52
  42. data/lib/rlsl/prism/emitters/target_emitter.rb +124 -0
  43. data/lib/rlsl/prism/emitters/target_profile.rb +34 -0
  44. data/lib/rlsl/prism/emitters/wgsl_emitter.rb +217 -58
  45. data/lib/rlsl/prism/errors.rb +9 -0
  46. data/lib/rlsl/prism/ir/control_flow.rb +85 -0
  47. data/lib/rlsl/prism/ir/definitions.rb +82 -0
  48. data/lib/rlsl/prism/ir/expressions.rb +201 -0
  49. data/lib/rlsl/prism/ir/node.rb +21 -0
  50. data/lib/rlsl/prism/ir/nodes.rb +4 -371
  51. data/lib/rlsl/prism/ir/traversal.rb +66 -0
  52. data/lib/rlsl/prism/mutation_analyzer.rb +30 -0
  53. data/lib/rlsl/prism/node_traversal.rb +41 -0
  54. data/lib/rlsl/prism/parameter_list.rb +46 -0
  55. data/lib/rlsl/prism/return_flow_validator.rb +73 -0
  56. data/lib/rlsl/prism/source_extractor/block_locator.rb +50 -0
  57. data/lib/rlsl/prism/source_extractor.rb +19 -137
  58. data/lib/rlsl/prism/source_unit/parser.rb +88 -0
  59. data/lib/rlsl/prism/source_unit.rb +42 -0
  60. data/lib/rlsl/prism/target_capability_validator.rb +110 -0
  61. data/lib/rlsl/prism/transpiler.rb +99 -59
  62. data/lib/rlsl/prism/type_inference/call_type_resolver.rb +59 -0
  63. data/lib/rlsl/prism/type_inference/call_validator.rb +75 -0
  64. data/lib/rlsl/prism/type_inference/collection_type_resolver.rb +106 -0
  65. data/lib/rlsl/prism/type_inference/control_flow_inferer.rb +147 -0
  66. data/lib/rlsl/prism/type_inference/definition_inferer.rb +56 -0
  67. data/lib/rlsl/prism/type_inference/expression_inferer.rb +96 -0
  68. data/lib/rlsl/prism/type_inference/field_type_resolver.rb +38 -0
  69. data/lib/rlsl/prism/type_inference/inferer_registry.rb +38 -0
  70. data/lib/rlsl/prism/type_inference/scope_stack.rb +47 -0
  71. data/lib/rlsl/prism/type_inference/type_environment.rb +112 -0
  72. data/lib/rlsl/prism/type_inference/type_shapes.rb +33 -0
  73. data/lib/rlsl/prism/type_inference.rb +120 -249
  74. data/lib/rlsl/runtime_shader.rb +47 -0
  75. data/lib/rlsl/shader_builder/build_service.rb +104 -0
  76. data/lib/rlsl/shader_builder/native_extension_compiler.rb +97 -0
  77. data/lib/rlsl/shader_builder/shader_definition.rb +68 -0
  78. data/lib/rlsl/shader_builder/source_resolver.rb +109 -0
  79. data/lib/rlsl/shader_builder.rb +60 -111
  80. data/lib/rlsl/shader_name.rb +18 -0
  81. data/lib/rlsl/types/catalog.rb +47 -0
  82. data/lib/rlsl/types/target_resolver.rb +15 -0
  83. data/lib/rlsl/types/type_spec.rb +167 -0
  84. data/lib/rlsl/types/value_normalizer.rb +86 -0
  85. data/lib/rlsl/types.rb +9 -31
  86. data/lib/rlsl/uniform_context.rb +22 -11
  87. data/lib/rlsl/version.rb +1 -1
  88. data/lib/rlsl/wgsl/translator.rb +46 -39
  89. data/lib/rlsl/wgsl/uniform_layout.rb +25 -0
  90. data/lib/rlsl.rb +38 -15
  91. metadata +76 -11
@@ -3,17 +3,53 @@
3
3
  require "prism"
4
4
  require "set"
5
5
 
6
+ require_relative "node_traversal"
7
+ require_relative "errors"
8
+ require_relative "parameter_list"
9
+ require_relative "ast_visitor/visitor_registry"
10
+ require_relative "ast_visitor/scope_context"
11
+ require_relative "ast_visitor/expression_visiting"
12
+ require_relative "ast_visitor/control_flow_visiting"
13
+ require_relative "ast_visitor/definition_visiting"
14
+
6
15
  module RLSL
7
16
  module Prism
8
17
  class ASTVisitor
18
+ MAX_AST_DEPTH = 512
9
19
  BINARY_OPERATORS = %w[+ - * / % == != < > <= >= && ||].freeze
10
20
  UNARY_OPERATORS = %w[- !].freeze
21
+ TRANSPARENT_NODES = VisitorRegistry::TRANSPARENT_NODES
22
+ NODE_VISITORS = VisitorRegistry.build(
23
+ {}.tap do |visitors|
24
+ visitors[::Prism::ProgramNode] = :visit_program if defined?(::Prism::ProgramNode)
25
+ visitors[::Prism::StatementsNode] = :visit_statements if defined?(::Prism::StatementsNode)
26
+ end,
27
+ ExpressionVisiting::VISITORS,
28
+ ControlFlowVisiting::VISITORS,
29
+ DefinitionVisiting::VISITORS
30
+ )
31
+
32
+ include ExpressionVisiting
33
+ include ControlFlowVisiting
34
+ include DefinitionVisiting
11
35
 
12
36
  def initialize(context = {})
13
37
  @context = context
14
38
  @uniforms = context[:uniforms] || {}
15
- @params = Set.new(context[:params] || [])
16
- @declared_vars = Set.new
39
+ @source_name = context[:source_name] || "(shader source)"
40
+ @line_offset = context[:line_offset].to_i
41
+ params = context[:params] || []
42
+ @scope_context = ScopeContext.new(params: params)
43
+ positional_types = params.each_with_index.to_h do |name, index|
44
+ [name.to_sym, %i[vec2 vec2 uniforms][index]]
45
+ end
46
+ @parameter_types = { frag_coord: :vec2, resolution: :vec2, u: :uniforms }.merge(positional_types)
47
+ @parameter_bindings = { frag_coord: :frag_coord, resolution: :resolution, u: :u }
48
+ @implicit_loop_index = 0
49
+ @source_identifiers = Set.new
50
+ params.each_with_index do |name, index|
51
+ @parameter_bindings[name.to_sym] = %i[frag_coord resolution u][index]
52
+ end
17
53
  end
18
54
 
19
55
  def parse(source)
@@ -21,34 +57,39 @@ module RLSL
21
57
 
22
58
  unless result.success?
23
59
  errors = result.errors.map(&:message).join(", ")
24
- raise "Parse error: #{errors}"
60
+ raise RLSL::ParseError, "Parse error: #{errors}"
25
61
  end
26
62
 
27
63
  program = result.value
64
+ if NodeTraversal.depth_exceeds?(program, MAX_AST_DEPTH)
65
+ raise UnsupportedSyntaxError, "Shader syntax nesting exceeds #{MAX_AST_DEPTH} nodes"
66
+ end
67
+ @source_identifiers = NodeTraversal.each(program).filter_map do |node|
68
+ node.name.to_sym if node.respond_to?(:name)
69
+ end.to_set
70
+
28
71
  visit(program)
29
72
  end
30
73
 
31
74
  def visit(node)
32
75
  return nil if node.nil?
33
76
 
34
- method_name = "visit_#{node_type(node)}"
35
- if respond_to?(method_name, true)
36
- send(method_name, node)
37
- else
38
- visit_default(node)
39
- end
77
+ method_name = NODE_VISITORS[node.class]
78
+ result = if method_name
79
+ send(method_name, node)
80
+ else
81
+ raise UnsupportedSyntaxError, "Unsupported Prism node: #{node.class}" unless transparent_node?(node)
82
+
83
+ visit_default(node)
84
+ end
85
+ attach_source_location(result, node.location)
86
+ rescue RLSL::Error => error
87
+ error.with_source_location(source_location(node.location))
88
+ raise
40
89
  end
41
90
 
42
91
  private
43
92
 
44
- def node_type(node)
45
- node.class.name.split("::").last
46
- .gsub(/Node$/, "")
47
- .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
48
- .gsub(/([a-z\d])([A-Z])/, '\1_\2')
49
- .downcase
50
- end
51
-
52
93
  def visit_default(node)
53
94
  children = []
54
95
  node.child_nodes.compact.each do |child|
@@ -58,6 +99,10 @@ module RLSL
58
99
  children.length == 1 ? children.first : children
59
100
  end
60
101
 
102
+ def transparent_node?(node)
103
+ TRANSPARENT_NODES.include?(node.class)
104
+ end
105
+
61
106
  def visit_program(node)
62
107
  visit(node.statements)
63
108
  end
@@ -67,304 +112,73 @@ module RLSL
67
112
  IR::Block.new(statements)
68
113
  end
69
114
 
70
- def visit_block(node)
71
- if node.parameters
72
- node.parameters.parameters&.requireds&.each do |param|
73
- @params.add(param.name.to_sym)
74
- end
75
- end
76
-
77
- visit(node.body)
78
- end
79
-
80
- def visit_lambda(node)
81
- visit_block(node)
82
- end
83
-
84
- def visit_local_variable_write(node)
85
- name = node.name.to_sym
86
- value = visit(node.value)
87
-
88
- if @declared_vars.include?(name) || @params.include?(name)
89
- IR::Assignment.new(IR::VarRef.new(name), value)
90
- else
91
- @declared_vars.add(name)
92
- IR::VarDecl.new(name, value)
93
- end
94
- end
95
-
96
- def visit_local_variable_read(node)
97
- name = node.name.to_sym
98
- type = infer_param_type(name)
99
- IR::VarRef.new(name, type)
100
- end
101
-
102
- def visit_integer(node)
103
- IR::Literal.new(node.value.to_f, :float)
104
- end
105
-
106
- def visit_float(node)
107
- IR::Literal.new(node.value, :float)
115
+ def visit_with_scoped_vars(node, params: [])
116
+ @scope_context.with_scope(params: params) { visit(node) }
108
117
  end
109
118
 
110
- def visit_rational(node)
111
- IR::Literal.new(node.value.to_f, :float)
112
- end
113
-
114
- def visit_true(node)
115
- IR::BoolLiteral.new(true)
116
- end
117
-
118
- def visit_false(node)
119
- IR::BoolLiteral.new(false)
120
- end
121
-
122
- def visit_parentheses(node)
123
- inner = visit(node.body)
124
- if inner.is_a?(IR::Block) && inner.statements.length == 1
125
- inner = inner.statements.first
126
- end
127
- IR::Parenthesized.new(inner)
128
- end
129
-
130
- def visit_call(node)
131
- method_name = node.name.to_s
132
- receiver = visit(node.receiver) if node.receiver
133
- args = node.arguments&.arguments&.map { |arg| visit(arg) } || []
134
-
135
- if !receiver && args.empty? && @params.include?(method_name.to_sym)
136
- type = infer_param_type(method_name.to_sym)
137
- return IR::VarRef.new(method_name.to_sym, type)
138
- end
139
-
140
- if receiver && args.empty? && !node.arguments
141
- if Builtins.single_component_field?(method_name)
142
- return IR::FieldAccess.new(receiver, method_name, :float)
143
- elsif Builtins.swizzle?(method_name)
144
- type = Builtins.swizzle_type(method_name)
145
- return IR::Swizzle.new(receiver, method_name, type)
146
- else
147
- return IR::FieldAccess.new(receiver, method_name)
148
- end
149
- end
150
-
151
- if BINARY_OPERATORS.include?(method_name) && receiver && args.length == 1
152
- return IR::BinaryOp.new(method_name, receiver, args.first)
153
- end
154
-
155
- if method_name == "-@" && receiver
156
- return IR::UnaryOp.new("-", receiver)
157
- end
158
- if method_name == "!" && args.length == 1
159
- return IR::UnaryOp.new("!", args.first)
160
- end
161
-
162
- if method_name == "[]" && receiver && args.length == 1
163
- return IR::ArrayIndex.new(receiver, args.first)
164
- end
165
-
166
- IR::FuncCall.new(method_name.to_sym, args, receiver)
167
- end
168
-
169
- def visit_if(node)
170
- condition = visit(node.predicate)
171
- then_branch = visit_with_scoped_vars(node.statements)
172
- else_branch = node.subsequent ? visit_with_scoped_vars(node.subsequent) : nil
173
-
174
- IR::IfStatement.new(condition, then_branch, else_branch)
175
- end
176
-
177
- def visit_with_scoped_vars(node)
178
- saved_vars = @declared_vars.dup
179
- result = visit(node)
180
- @declared_vars = saved_vars
181
- result
182
- end
183
-
184
- def visit_else(node)
185
- visit(node.statements)
186
- end
187
-
188
- def visit_elsif(node)
189
- visit_if(node)
190
- end
191
-
192
- def visit_if_node(node)
193
- visit_if(node)
194
- end
195
-
196
- def visit_unless(node)
197
- condition = IR::UnaryOp.new("!", visit(node.predicate))
198
- then_branch = visit(node.statements)
199
- else_branch = node.else_clause ? visit(node.else_clause) : nil
200
-
201
- IR::IfStatement.new(condition, then_branch, else_branch)
202
- end
203
-
204
- def visit_return(node)
205
- expr = node.arguments ? visit(node.arguments.arguments.first) : nil
206
- IR::Return.new(expr)
207
- end
208
-
209
- def visit_range(node)
210
- [visit(node.left), visit(node.right)]
211
- end
212
-
213
- def visit_for(node)
214
- var_name = node.index.name.to_sym
215
- range = visit(node.collection)
216
- body = visit(node.statements)
217
-
218
- IR::ForLoop.new(var_name, range[0], range[1], body)
219
- end
220
-
221
- def visit_call_with_block(node)
222
- call_node = node
223
- method_name = call_node.name.to_s
224
-
225
- if method_name == "times" && call_node.receiver
226
- count = visit(call_node.receiver)
227
- block = visit(call_node.block)
228
-
229
- var_name = :i
230
- if call_node.block&.parameters&.parameters&.requireds&.any?
231
- var_name = call_node.block.parameters.parameters.requireds.first.name.to_sym
232
- end
233
-
234
- IR::ForLoop.new(var_name, IR::Literal.new(0, :int), count, block)
235
- else
236
- visit_call(node)
237
- end
119
+ def infer_param_type(name)
120
+ @parameter_types[name.to_sym]
238
121
  end
239
122
 
240
- def visit_and(node)
241
- left = visit(node.left)
242
- right = visit(node.right)
243
- IR::BinaryOp.new("&&", left, right, :bool)
123
+ def emitted_parameter_name(name)
124
+ @parameter_bindings[name.to_sym] || name.to_sym
244
125
  end
245
126
 
246
- def visit_or(node)
247
- left = visit(node.left)
248
- right = visit(node.right)
249
- IR::BinaryOp.new("||", left, right, :bool)
127
+ def extract_required_params(node)
128
+ ParameterList.required_names(node).map(&:to_sym)
250
129
  end
251
130
 
252
- def visit_not(node)
253
- operand = visit(node.expression)
254
- IR::UnaryOp.new("!", operand, :bool)
255
- end
131
+ def extract_block_params(node)
132
+ return [] unless node&.parameters
256
133
 
257
- def visit_while(node)
258
- condition = visit(node.predicate)
259
- body = visit(node.statements)
260
- IR::WhileLoop.new(condition, body)
134
+ extract_required_params(node.parameters.parameters)
261
135
  end
262
136
 
263
- def visit_break(node)
264
- IR::Break.new
137
+ def parameter_reference?(name)
138
+ @scope_context.parameter?(name)
265
139
  end
266
140
 
267
- def visit_constant_read(node)
268
- name = node.name.to_s
269
- if %w[PI TAU].include?(name)
270
- IR::Constant.new(name.to_sym, :float)
271
- else
272
- IR::VarRef.new(name.to_sym)
273
- end
141
+ def fragment_parameter_reference?(name)
142
+ @scope_context.root_parameter?(name)
274
143
  end
275
144
 
276
- def visit_def(node)
277
- name = node.name.to_sym
278
- params = []
279
-
280
- if node.parameters
281
- node.parameters.requireds&.each do |param|
282
- params << param.name.to_sym
283
- end
145
+ def emitted_assignment_name(name)
146
+ if fragment_parameter_reference?(name) && infer_param_type(name) == :uniforms
147
+ raise UnsupportedSyntaxError, "The fragment uniform parameter cannot be reassigned"
284
148
  end
285
149
 
286
- saved_params = @params.dup
287
- saved_declared_vars = @declared_vars.dup
288
- @declared_vars = Set.new
289
- params.each { |p| @params.add(p) }
290
-
291
- body = visit(node.body)
292
-
293
- @params = saved_params
294
- @declared_vars = saved_declared_vars
295
-
296
- IR::FunctionDefinition.new(name, params, body)
150
+ fragment_parameter_reference?(name) ? emitted_parameter_name(name) : name
297
151
  end
298
152
 
299
- def visit_array(node)
300
- elements = node.elements.map { |elem| visit(elem) }
301
- IR::ArrayLiteral.new(elements)
153
+ def known_variable?(name)
154
+ @scope_context.known_variable?(name)
302
155
  end
303
156
 
304
- def visit_index(node)
305
- array = visit(node.receiver)
306
- index = visit(node.arguments.arguments.first)
307
- IR::ArrayIndex.new(array, index)
157
+ def declare_variable(name)
158
+ @scope_context.declare(name)
308
159
  end
309
160
 
310
- def visit_constant_path(node)
311
- path_parts = []
312
- current = node
313
- while current.is_a?(::Prism::ConstantPathNode)
314
- path_parts.unshift(current.name.to_s)
315
- current = current.parent
161
+ def next_implicit_loop_variable
162
+ loop do
163
+ name = :"_rlsl_i#{@implicit_loop_index}"
164
+ @implicit_loop_index += 1
165
+ return name unless @source_identifiers.include?(name)
316
166
  end
317
- path_parts.unshift(current.name.to_s) if current.respond_to?(:name)
318
-
319
- full_name = path_parts.join("_")
320
- IR::VarRef.new(full_name.to_sym)
321
- end
322
-
323
- def visit_global_variable_read(node)
324
- name = node.name.to_s.sub(/^\$/, "").to_sym
325
- IR::VarRef.new(name)
326
167
  end
327
168
 
328
- def visit_global_variable_write(node)
329
- name = node.name.to_s.sub(/^\$/, "").to_sym
330
- value = visit(node.value)
169
+ def attach_source_location(result, prism_location)
170
+ return result unless result.is_a?(IR::Node)
331
171
 
332
- IR::GlobalDecl.new(name, value, is_static: true)
333
- end
334
-
335
- def visit_constant_write(node)
336
- name = node.name.to_sym
337
- value = visit(node.value)
338
-
339
- IR::GlobalDecl.new(name, value, is_const: true, is_static: true)
340
- end
341
-
342
- def visit_multi_write(node)
343
- targets = node.lefts.map do |target|
344
- name = target.name.to_sym
345
- @declared_vars.add(name)
346
- IR::VarRef.new(name)
347
- end
348
-
349
- value = visit(node.value)
350
- IR::MultipleAssignment.new(targets, value)
351
- end
352
-
353
- def visit_local_variable_target(node)
354
- name = node.name.to_sym
355
- @declared_vars.add(name)
356
- IR::VarRef.new(name)
172
+ result.location ||= source_location(prism_location)
173
+ result
357
174
  end
358
175
 
359
- def infer_param_type(name)
360
- case name
361
- when :frag_coord, :resolution
362
- :vec2
363
- when :u
364
- :uniforms
365
- else
366
- nil
367
- end
176
+ def source_location(prism_location)
177
+ RLSL::SourceLocation.new(
178
+ source_name: @source_name,
179
+ line: @line_offset + prism_location.start_line,
180
+ column: prism_location.start_column + 1
181
+ )
368
182
  end
369
183
  end
370
184
  end
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RLSL
4
+ module Prism
5
+ module Builtins
6
+ module FunctionRegistry
7
+ ALL_TARGETS = %i[c glsl wgsl msl].freeze
8
+ META_TYPES = %i[any same first second third common floating interpolated].freeze
9
+
10
+ FUNCTIONS = {
11
+ vec2: { args: %i[any any], returns: :vec2, variadic: true, min_args: 1 },
12
+ vec3: { args: %i[any any any], returns: :vec3, variadic: true, min_args: 1 },
13
+ vec4: { args: %i[any any any any], returns: :vec4, variadic: true, min_args: 1 },
14
+
15
+ mat2: { args: %i[any any any any], returns: :mat2, variadic: true, min_args: 1, targets: %i[glsl wgsl msl] },
16
+ mat3: { args: %i[any any any any any any any any any], returns: :mat3, variadic: true, min_args: 1, targets: %i[glsl wgsl msl] },
17
+ mat4: { args: %i[any any any any any any any any any any any any any any any any], returns: :mat4, variadic: true, min_args: 1, targets: %i[glsl wgsl msl] },
18
+
19
+ sin: { args: [:float], returns: :float },
20
+ cos: { args: [:float], returns: :float },
21
+ tan: { args: [:float], returns: :float },
22
+ asin: { args: [:float], returns: :float },
23
+ acos: { args: [:float], returns: :float },
24
+ atan: { args: %i[float float], returns: :float, variadic: true, min_args: 1 },
25
+ atan2: { args: %i[float float], returns: :float },
26
+
27
+ pow: { args: %i[float float], returns: :float },
28
+ exp: { args: [:float], returns: :float },
29
+ log: { args: [:float], returns: :float },
30
+ sqrt: { args: [:any], returns: :floating },
31
+
32
+ abs: { args: [:any], returns: :same },
33
+ sign: { args: [:any], returns: :same },
34
+ floor: { args: [:any], returns: :floating },
35
+ ceil: { args: [:any], returns: :floating },
36
+ fract: { args: [:any], returns: :floating },
37
+ mod: { args: %i[float float], returns: :float },
38
+ min: { args: %i[any any], returns: :common },
39
+ max: { args: %i[any any], returns: :common },
40
+ clamp: { args: %i[any any any], returns: :common },
41
+ mix: { args: %i[any any float], returns: :interpolated },
42
+ step: { args: %i[float any], returns: :second },
43
+ smoothstep: { args: %i[float float any], returns: :third },
44
+
45
+ length: { args: [:any], returns: :float },
46
+ distance: { args: %i[any any], returns: :float },
47
+ dot: { args: %i[any any], returns: :float },
48
+ cross: { args: %i[vec3 vec3], returns: :vec3 },
49
+ normalize: { args: [:any], returns: :same },
50
+ reflect: { args: %i[any any], returns: :first },
51
+ refract: { args: %i[any any float], returns: :first },
52
+
53
+ hash21: { args: [:vec2], returns: :float, targets: [:c] },
54
+ hash22: { args: [:vec2], returns: :vec2, targets: [:c] },
55
+
56
+ lessThan: { args: %i[any any], returns: :bool, targets: [] },
57
+ lessThanEqual: { args: %i[any any], returns: :bool, targets: [] },
58
+ greaterThan: { args: %i[any any], returns: :bool, targets: [] },
59
+ greaterThanEqual: { args: %i[any any], returns: :bool, targets: [] },
60
+ equal: { args: %i[any any], returns: :bool, targets: [] },
61
+ notEqual: { args: %i[any any], returns: :bool, targets: [] },
62
+
63
+ inverse: { args: [:any], returns: :same, targets: %i[glsl wgsl msl] },
64
+ transpose: { args: [:any], returns: :same, targets: %i[glsl wgsl msl] },
65
+ determinant: { args: [:any], returns: :float, targets: %i[glsl wgsl msl] },
66
+
67
+ texture2D: { args: %i[sampler2D vec2], returns: :vec4, targets: %i[glsl wgsl msl] },
68
+ texture: { args: %i[sampler2D vec2], returns: :vec4, targets: %i[glsl wgsl msl] },
69
+ textureLod: { args: %i[sampler2D vec2 float], returns: :vec4, targets: %i[glsl wgsl msl] }
70
+ }.freeze
71
+
72
+ module_function
73
+
74
+ def function?(name)
75
+ FUNCTIONS.key?(name.to_sym)
76
+ end
77
+
78
+ def function_signature(name)
79
+ FUNCTIONS[name.to_sym]
80
+ end
81
+
82
+ def supported_on_target?(name, target)
83
+ signature = function_signature(name)
84
+ return false unless signature
85
+
86
+ Array(signature[:targets] || ALL_TARGETS).include?(target.to_sym)
87
+ end
88
+
89
+ def explicit_types(name)
90
+ signature = function_signature(name)
91
+ return [] unless signature
92
+
93
+ ([signature[:returns]] + Array(signature[:args])).filter_map do |type|
94
+ type if explicit_type?(type)
95
+ end.uniq
96
+ end
97
+
98
+ def resolve_return_type(rule, arg_types)
99
+ case rule
100
+ when :same then arg_types.first
101
+ when :first then arg_types.first
102
+ when :second then arg_types[1]
103
+ when :third then arg_types[2]
104
+ when :common
105
+ type = OperatorRules.common_type(arg_types)
106
+ type if numeric_type?(type)
107
+ when :floating
108
+ type = arg_types.first
109
+ return :float if type == :int
110
+
111
+ type if type == :float || OperatorRules.vector_type?(type)
112
+ when :interpolated
113
+ type = OperatorRules.common_type(arg_types.first(2))
114
+ return :float if type == :int
115
+
116
+ type if numeric_type?(type)
117
+ when Symbol then rule
118
+ end
119
+ end
120
+
121
+ def explicit_type?(type)
122
+ type.is_a?(Symbol) && !META_TYPES.include?(type)
123
+ end
124
+
125
+ def numeric_type?(type)
126
+ OperatorRules.scalar_type?(type) || OperatorRules.vector_type?(type)
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end