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
@@ -1,288 +1,159 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "type_inference/scope_stack"
4
+ require_relative "type_inference/type_shapes"
5
+ require_relative "type_inference/inferer_registry"
6
+ require_relative "type_inference/call_validator"
7
+ require_relative "type_inference/type_environment"
8
+ require_relative "type_inference/call_type_resolver"
9
+ require_relative "type_inference/field_type_resolver"
10
+ require_relative "type_inference/collection_type_resolver"
11
+ require_relative "type_inference/expression_inferer"
12
+ require_relative "type_inference/definition_inferer"
13
+ require_relative "type_inference/control_flow_inferer"
14
+
3
15
  module RLSL
4
16
  module Prism
5
- class TypeInference
6
- attr_reader :symbol_table
17
+ class SignatureError < RLSL::Error; end
7
18
 
8
- def initialize(uniforms = {}, custom_functions = {})
9
- @symbol_table = {}
19
+ class TypeInference
20
+ EXPRESSION_NODES = {
21
+ IR::VarRef => :infer_var_ref,
22
+ IR::Literal => :infer_literal,
23
+ IR::BoolLiteral => :infer_bool_literal,
24
+ IR::BinaryOp => :infer_binary_op,
25
+ IR::UnaryOp => :infer_unary_op,
26
+ IR::FuncCall => :infer_func_call,
27
+ IR::FieldAccess => :infer_field_access,
28
+ IR::Swizzle => :infer_swizzle,
29
+ IR::Parenthesized => :infer_parenthesized,
30
+ IR::ArrayLiteral => :infer_array_literal,
31
+ IR::ArrayIndex => :infer_array_index
32
+ }.freeze
33
+
34
+ DEFINITION_NODES = {
35
+ IR::VarDecl => :infer_var_decl,
36
+ IR::Assignment => :infer_assignment,
37
+ IR::GlobalDecl => :infer_global_decl,
38
+ IR::MultipleAssignment => :infer_multiple_assignment
39
+ }.freeze
40
+
41
+ CONTROL_FLOW_NODES = {
42
+ IR::IfStatement => :infer_if_statement,
43
+ IR::Ternary => :infer_ternary,
44
+ IR::Return => :infer_return,
45
+ IR::ForLoop => :infer_for_loop,
46
+ IR::WhileLoop => :infer_while_loop,
47
+ IR::FunctionDefinition => :infer_function_definition
48
+ }.freeze
49
+
50
+ def initialize(uniforms = {}, custom_functions = {}, globals: {})
51
+ @types = TypeEnvironment.new
10
52
  @uniforms = uniforms
11
53
  @custom_functions = custom_functions
12
-
13
- uniforms.each do |name, type|
14
- @symbol_table[name.to_sym] = type
15
- end
16
- end
17
-
18
- def register(name, type)
19
- @symbol_table[name.to_sym] = type
20
- end
21
-
22
- def register_function(name, returns:)
23
- @custom_functions[name.to_sym] = { returns: returns }
24
- end
25
-
26
- def lookup(name)
27
- @symbol_table[name.to_sym]
28
- end
29
-
30
- def infer(node)
31
- case node
32
- when IR::Block
33
- infer_block(node)
34
- when IR::VarDecl
35
- infer_var_decl(node)
36
- when IR::VarRef
37
- infer_var_ref(node)
38
- when IR::Literal
39
- infer_literal(node)
40
- when IR::BoolLiteral
41
- node.type = :bool
42
- node
43
- when IR::BinaryOp
44
- infer_binary_op(node)
45
- when IR::UnaryOp
46
- infer_unary_op(node)
47
- when IR::FuncCall
48
- infer_func_call(node)
49
- when IR::FieldAccess
50
- infer_field_access(node)
51
- when IR::Swizzle
52
- infer_swizzle(node)
53
- when IR::IfStatement
54
- infer_if_statement(node)
55
- when IR::Return
56
- infer_return(node)
57
- when IR::Assignment
58
- infer_assignment(node)
59
- when IR::ForLoop
60
- infer_for_loop(node)
61
- when IR::WhileLoop
62
- infer_while_loop(node)
63
- when IR::Parenthesized
64
- infer_parenthesized(node)
65
- when IR::FunctionDefinition
66
- infer_function_definition(node)
67
- when IR::ArrayLiteral
68
- infer_array_literal(node)
69
- when IR::ArrayIndex
70
- infer_array_index(node)
71
- when IR::GlobalDecl
72
- infer_global_decl(node)
73
- when IR::MultipleAssignment
74
- infer_multiple_assignment(node)
75
- else
76
- node
77
- end
78
- end
79
-
80
- private
81
-
82
- def infer_block(node)
83
- node.statements.each { |stmt| infer(stmt) }
84
- node.type = node.statements.last&.type
85
- node
86
- end
87
-
88
- def infer_var_decl(node)
89
- infer(node.initializer) if node.initializer
90
- node.type ||= node.initializer&.type
91
- register(node.name, node.type) if node.type
92
- node
93
- end
94
-
95
- def infer_var_ref(node)
96
- node.type ||= lookup(node.name)
97
- node
98
- end
99
-
100
- def infer_literal(node)
101
- node
102
- end
103
-
104
- def infer_binary_op(node)
105
- infer(node.left)
106
- infer(node.right)
107
-
108
- node.type = Builtins.binary_op_result_type(
109
- node.operator,
110
- node.left.type,
111
- node.right.type
54
+ @globals = globals
55
+ @inferer_registry = InfererRegistry.new
56
+ @call_validator = CallValidator.new
57
+ @call_type_resolver = CallTypeResolver.new(
58
+ custom_functions: @custom_functions,
59
+ call_validator: @call_validator
112
60
  )
113
- node
114
- end
115
-
116
- def infer_unary_op(node)
117
- infer(node.operand)
61
+ @field_type_resolver = FieldTypeResolver.new(uniforms: @uniforms)
62
+ @collection_type_resolver = CollectionTypeResolver.new(
63
+ type_environment: @types,
64
+ custom_functions: @custom_functions,
65
+ register: method(:register)
66
+ )
67
+ @expression_inferer = ExpressionInferer.new(
68
+ infer: method(:infer),
69
+ lookup: method(:lookup),
70
+ call_type_resolver: @call_type_resolver,
71
+ field_type_resolver: @field_type_resolver,
72
+ collection_type_resolver: @collection_type_resolver
73
+ )
74
+ @definition_inferer = DefinitionInferer.new(
75
+ infer: method(:infer),
76
+ lookup: method(:lookup),
77
+ register: method(:register),
78
+ collection_type_resolver: @collection_type_resolver
79
+ )
80
+ @control_flow_inferer = ControlFlowInferer.new(
81
+ infer: method(:infer),
82
+ infer_in_scope: method(:infer_in_scope),
83
+ lookup: method(:lookup)
84
+ )
85
+ register_inferers
118
86
 
119
- case node.operator.to_s
120
- when "-"
121
- node.type = node.operand.type
122
- when "!"
123
- node.type = :bool
87
+ uniforms.each do |name, type|
88
+ register(name, type)
124
89
  end
125
- node
126
- end
127
-
128
- def infer_func_call(node)
129
- node.args.each { |arg| infer(arg) }
130
- infer(node.receiver) if node.receiver
90
+ register(:u, :uniforms)
131
91
 
132
- sig = Builtins.function_signature(node.name)
133
- if sig
134
- arg_types = node.args.map(&:type)
135
- node.type = Builtins.resolve_return_type(sig[:returns], arg_types)
136
- elsif @custom_functions.key?(node.name.to_sym)
137
- node.type = @custom_functions[node.name.to_sym][:returns]
138
- else
139
- node.type = node.receiver&.type
92
+ globals.each do |name, type|
93
+ register(name, type)
140
94
  end
141
- node
142
95
  end
143
96
 
144
- def infer_field_access(node)
145
- infer(node.receiver)
146
-
147
- if Builtins.single_component_field?(node.field)
148
- node.type = :float
149
- else
150
- node.type = @uniforms[node.field.to_sym] || :float
151
- end
152
- node
97
+ def symbol_table
98
+ @types.to_h
153
99
  end
154
100
 
155
- def infer_swizzle(node)
156
- infer(node.receiver)
157
- node.type = Builtins.swizzle_type(node.components)
158
- node
101
+ def register(name, type)
102
+ @types.register(name, type)
159
103
  end
160
104
 
161
- def infer_if_statement(node)
162
- infer(node.condition)
163
- infer(node.then_branch)
164
- infer(node.else_branch) if node.else_branch
165
-
166
- node.type = node.then_branch.type
167
- node
105
+ def register_function(name, returns:, params: {})
106
+ @custom_functions[name.to_sym] = { returns: returns, params: params }
168
107
  end
169
108
 
170
- def infer_return(node)
171
- infer(node.expression) if node.expression
172
- node.type = node.expression&.type
173
- node
109
+ def lookup(name)
110
+ @types.lookup(name)
174
111
  end
175
112
 
176
- def infer_assignment(node)
177
- infer(node.target)
178
- infer(node.value)
179
- node.type = node.value.type
180
- node
113
+ def infer(node, scoped: false)
114
+ options = node.is_a?(IR::Block) ? { scoped: scoped } : {}
115
+ @inferer_registry.infer(node, **options) || node
116
+ rescue RLSL::Error => error
117
+ error.with_source_location(node.location)
118
+ raise
181
119
  end
182
120
 
183
- def infer_for_loop(node)
184
- register(node.variable, :int)
185
- infer(node.range_start)
186
- infer(node.range_end)
187
- infer(node.body)
188
- node.type = nil
189
- node
190
- end
121
+ private
191
122
 
192
- def infer_while_loop(node)
193
- infer(node.condition)
194
- infer(node.body)
195
- node.type = nil
196
- node
123
+ def register_inferers
124
+ @inferer_registry.register(IR::Block) { |node, scoped: false| infer_block(node, scoped: scoped) }
125
+ @inferer_registry.register_methods(@expression_inferer, EXPRESSION_NODES)
126
+ @inferer_registry.register_methods(@definition_inferer, DEFINITION_NODES)
127
+ @inferer_registry.register_methods(@control_flow_inferer, CONTROL_FLOW_NODES)
197
128
  end
198
129
 
199
- def infer_function_definition(node)
200
- node.param_types.each do |param_name, param_type|
201
- register(param_name, param_type)
130
+ def infer_block(node, scoped: false)
131
+ infer_with_optional_scope(scoped) do
132
+ node.statements.each { |stmt| infer(stmt) }
133
+ node.type = node.statements.last&.type
134
+ node
202
135
  end
203
-
204
- infer(node.body)
205
-
206
- node.return_type ||= node.body&.type
207
- node.type = node.return_type
208
- node
209
- end
210
-
211
- def infer_parenthesized(node)
212
- infer(node.expression)
213
- node.type = node.expression.type
214
- node
215
136
  end
216
137
 
217
- def infer_array_literal(node)
218
- node.elements.each { |elem| infer(elem) }
219
-
220
- element_type = node.elements.first&.type || :float
221
- node.type = :"array_#{element_type}"
222
- node
223
- end
224
-
225
- def infer_array_index(node)
226
- infer(node.array)
227
- infer(node.index)
228
-
229
- array_type = node.array.type
230
- if array_type.to_s.start_with?("array_")
231
- node.type = array_type.to_s.sub("array_", "").to_sym
232
- else
233
- node.type = @symbol_table["#{node.array.name}_element_type".to_sym] || :float
234
- end
235
- node
138
+ def infer_child_scope(node)
139
+ infer_in_scope { infer(node) }
236
140
  end
237
141
 
238
- def infer_global_decl(node)
239
- infer(node.initializer) if node.initializer
240
-
241
- if node.initializer.is_a?(IR::ArrayLiteral)
242
- node.array_size ||= node.initializer.elements.length
243
- first_elem = node.initializer.elements.first
244
- node.element_type ||= first_elem&.type || :float
245
- node.type = :"array_#{node.element_type}"
246
- else
247
- node.type ||= node.initializer&.type
248
- end
249
-
250
- register(node.name, node.type) if node.type
142
+ def infer_in_scope(initial_scope = nil)
143
+ initial_scope ? @types.push(initial_scope) : @types.push
251
144
 
252
- if node.element_type
253
- register("#{node.name}_element_type".to_sym, node.element_type)
254
- end
255
-
256
- node
145
+ yield
146
+ ensure
147
+ @types.pop
257
148
  end
258
149
 
259
- def infer_multiple_assignment(node)
260
- infer(node.value)
261
-
262
- value_type = node.value.type
263
- if value_type.is_a?(IR::TupleType)
264
- node.targets.each_with_index do |target, i|
265
- target.type = value_type.types[i]
266
- register(target.name, target.type)
267
- end
268
- elsif value_type.to_s.start_with?("array_")
269
- elem_type = value_type.to_s.sub("array_", "").to_sym
270
- node.targets.each do |target|
271
- target.type = elem_type
272
- register(target.name, target.type)
273
- end
274
- elsif @custom_functions.key?(node.value.name) && node.value.is_a?(IR::FuncCall)
275
- func_info = @custom_functions[node.value.name]
276
- if func_info[:returns].is_a?(Array)
277
- node.targets.each_with_index do |target, i|
278
- target.type = func_info[:returns][i]
279
- register(target.name, target.type)
280
- end
281
- end
282
- end
150
+ def infer_with_optional_scope(scoped)
151
+ return yield unless scoped
283
152
 
284
- node.type = nil
285
- node
153
+ @types.push
154
+ yield
155
+ ensure
156
+ @types.pop if scoped
286
157
  end
287
158
  end
288
159
  end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RLSL
4
+ class RuntimeShader
5
+ attr_reader :name, :uniform_types, :uniform_names
6
+
7
+ def initialize(name, uniforms)
8
+ @name = name
9
+ @uniform_types = normalize_uniform_types(uniforms)
10
+ @uniform_names = @uniform_types.keys
11
+ end
12
+
13
+ def metal?
14
+ false
15
+ end
16
+
17
+ def render(*)
18
+ raise NotImplementedError, "Subclasses must implement render"
19
+ end
20
+
21
+ protected
22
+
23
+ def normalized_uniforms(uniforms)
24
+ UniformTypes.normalize_values(@uniform_types, uniforms, shader_name: @name)
25
+ end
26
+
27
+ def ordered_uniform_values(uniforms)
28
+ normalized = normalized_uniforms(uniforms)
29
+ @uniform_names.map { |name| normalized[name] }
30
+ end
31
+
32
+ private
33
+
34
+ def normalize_uniform_types(uniforms)
35
+ case uniforms
36
+ when Hash
37
+ uniforms.each_with_object({}) do |(name, type), normalized|
38
+ normalized[name.to_sym] = type
39
+ end
40
+ else
41
+ Array(uniforms).each_with_object({}) do |name, normalized|
42
+ normalized[name.to_sym] = nil
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RLSL
4
+ class ShaderBuilder
5
+ class BuildService
6
+ def initialize(name, definition, source_resolver_class: SourceResolver, compiler_factory: nil)
7
+ @name = name
8
+ @definition = definition
9
+ @source_resolver_class = source_resolver_class
10
+ @compiler_factory = compiler_factory
11
+ end
12
+
13
+ def compile_and_load
14
+ compiler = native_extension_compiler
15
+ base_code = generate_c_code
16
+ extension_name = compiler.extension_name_for(base_code)
17
+ c_code = generate_c_code(extension_name: extension_name)
18
+ artifact = compiler.build(c_code, ext_name: extension_name)
19
+
20
+ require artifact.file
21
+ CompiledShader.new(@name, artifact.ext_name, @definition.uniforms)
22
+ end
23
+
24
+ def build_metal_shader
25
+ translator = MSL::Translator.new(@definition.uniforms, *translation_sources(:msl), name: @name)
26
+ msl_source = translator.translate
27
+
28
+ MSL::Shader.new(@name, @definition.uniforms, msl_source)
29
+ end
30
+
31
+ def build_wgsl_shader
32
+ validate_wgsl_module_names!
33
+ WGSL::Translator.new(@definition.uniforms, *translation_sources(:wgsl), name: @name).translate
34
+ end
35
+
36
+ def build_glsl_shader(version: "450")
37
+ GLSL::Translator.new(
38
+ @definition.uniforms,
39
+ *translation_sources(:glsl),
40
+ version: version,
41
+ name: @name
42
+ ).translate
43
+ end
44
+
45
+ def transpile_fragment(target)
46
+ return "" unless @definition.fragment_block
47
+
48
+ source_resolver.fragment_code(target)
49
+ end
50
+
51
+ def transpile_helpers(target)
52
+ return "" unless @definition.helpers_block
53
+
54
+ source_resolver.helpers_code(target)
55
+ end
56
+
57
+ private
58
+
59
+ def generate_c_code(extension_name: @name)
60
+ helpers_code, fragment_code = resolved_sources(:c)
61
+ codegen = CodeGenerator.new(
62
+ @name,
63
+ @definition.uniforms,
64
+ -> { helpers_code },
65
+ -> { fragment_code },
66
+ extension_name: extension_name
67
+ )
68
+ codegen.generate
69
+ end
70
+
71
+ def resolved_sources(target)
72
+ source_resolver.sources_for(target)
73
+ end
74
+
75
+ def translation_sources(target)
76
+ source_resolver.translation_sources_for(target)
77
+ end
78
+
79
+ def source_resolver
80
+ @source_resolver ||= @source_resolver_class.new(@definition)
81
+ end
82
+
83
+ def native_extension_compiler
84
+ @native_extension_compiler ||= if @compiler_factory
85
+ @compiler_factory.call(@name)
86
+ else
87
+ NativeExtensionCompiler.new(@name)
88
+ end
89
+ end
90
+
91
+ def validate_wgsl_module_names!
92
+ resources = @definition.uniforms.filter_map do |name, type|
93
+ [name, :"#{name}_sampler"] if type == :sampler2D
94
+ end.flatten
95
+ generated = %i[u output_texture rlsl_mod shader_fragment main]
96
+ functions = @definition.custom_functions.keys
97
+ conflicts = (resources & (generated + functions)) | (functions & generated)
98
+ return if conflicts.empty?
99
+
100
+ raise ArgumentError, "WGSL module name conflict: #{conflicts.join(', ')}"
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+ require "open3"
5
+ require "shellwords"
6
+
7
+ module RLSL
8
+ class ShaderBuilder
9
+ class NativeExtensionCompiler
10
+ Artifact = Struct.new(:ext_name, :directory, :file, keyword_init: true)
11
+
12
+ def initialize(
13
+ shader_name,
14
+ cache_dir: RLSL.cache_dir,
15
+ ruby_bin: RbConfig.ruby,
16
+ dylib_ext: RbConfig::CONFIG["DLEXT"],
17
+ make_command: RbConfig::CONFIG["MAKE"] || "make",
18
+ fast_math: false
19
+ )
20
+ @shader_name = RLSL.validate_shader_name!(shader_name)
21
+ @cache_dir = cache_dir
22
+ @ruby_bin = ruby_bin
23
+ @dylib_ext = dylib_ext
24
+ @make_command = Shellwords.split(make_command)
25
+ @fast_math = fast_math
26
+ end
27
+
28
+ def build(c_code, ext_name: nil)
29
+ resolved_ext_name = ext_name || declared_extension_name(c_code) || extension_name_for(c_code)
30
+ artifact = artifact_for(c_code, ext_name: resolved_ext_name)
31
+ FileUtils.mkdir_p(artifact.directory)
32
+ File.open(File.join(artifact.directory, ".build.lock"), "w") do |lock|
33
+ lock.flock(File::LOCK_EX)
34
+ compile(artifact, c_code) unless File.exist?(artifact.file)
35
+ end
36
+ artifact
37
+ end
38
+
39
+ def extension_name_for(c_code)
40
+ code_hash = Digest::SHA256.hexdigest(c_code)[0, 16]
41
+ "#{@shader_name}_#{code_hash}"
42
+ end
43
+
44
+ private
45
+
46
+ def artifact_for(c_code, ext_name: extension_name_for(c_code))
47
+ validated_ext_name = RLSL.validate_identifier!(ext_name, context: "extension name")
48
+ directory = File.join(@cache_dir, extension_name_for(c_code))
49
+
50
+ Artifact.new(
51
+ ext_name: validated_ext_name,
52
+ directory: directory,
53
+ file: File.join(directory, "#{validated_ext_name}.#{@dylib_ext}")
54
+ )
55
+ end
56
+
57
+ def compile(artifact, c_code)
58
+ File.write(File.join(artifact.directory, "#{artifact.ext_name}.c"), c_code)
59
+ File.write(File.join(artifact.directory, "extconf.rb"), extconf_source(artifact.ext_name))
60
+
61
+ run_command!(@ruby_bin, "extconf.rb", chdir: artifact.directory)
62
+ run_command!(*@make_command, chdir: artifact.directory)
63
+ end
64
+
65
+ def declared_extension_name(c_code)
66
+ c_code[/\bvoid\s+Init_([A-Za-z_][A-Za-z0-9_]*)\s*\(/, 1]
67
+ end
68
+
69
+ def extconf_source(ext_name)
70
+ <<~RUBY
71
+ require "mkmf"
72
+ $CFLAGS << " -O3#{@fast_math ? ' -ffast-math' : ''}"
73
+ if RUBY_PLATFORM =~ /darwin/
74
+ $CFLAGS << " -fblocks"
75
+ end
76
+ create_makefile("#{ext_name}")
77
+ RUBY
78
+ end
79
+
80
+ def run_command!(*args, chdir:)
81
+ runner = lambda do
82
+ stdout, stderr, status = Open3.capture3(*args, chdir: chdir)
83
+ return if status.success?
84
+
85
+ output = [stdout, stderr].reject(&:empty?).join("\n")
86
+ raise RLSL::CompilationError, "#{args.first} failed for #{@shader_name}:\n#{output}"
87
+ end
88
+
89
+ if defined?(Bundler) && Bundler.respond_to?(:with_unbundled_env)
90
+ Bundler.with_unbundled_env { runner.call }
91
+ else
92
+ runner.call
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end