rlsl 0.1.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -1
  3. data/README.md +2 -0
  4. data/lib/rlsl/base_translator/call_parser.rb +68 -0
  5. data/lib/rlsl/base_translator/code_rewriter.rb +107 -0
  6. data/lib/rlsl/base_translator/code_scanner.rb +102 -0
  7. data/lib/rlsl/base_translator.rb +159 -63
  8. data/lib/rlsl/code_generator/math_prelude.rb +79 -0
  9. data/lib/rlsl/code_generator/ruby_wrapper_generator.rb +97 -0
  10. data/lib/rlsl/code_generator/shader_function_generator.rb +19 -0
  11. data/lib/rlsl/code_generator/template_context.rb +41 -0
  12. data/lib/rlsl/code_generator/uniform_struct_generator.rb +29 -0
  13. data/lib/rlsl/code_generator.rb +26 -201
  14. data/lib/rlsl/compiled_shader.rb +4 -12
  15. data/lib/rlsl/function_context.rb +31 -14
  16. data/lib/rlsl/glsl/translator.rb +19 -38
  17. data/lib/rlsl/msl/shader.rb +8 -38
  18. data/lib/rlsl/msl/translator.rb +19 -36
  19. data/lib/rlsl/msl/uniform_buffer_packer.rb +68 -0
  20. data/lib/rlsl/prism/ast_visitor/control_flow_visiting.rb +96 -0
  21. data/lib/rlsl/prism/ast_visitor/definition_visiting.rb +79 -0
  22. data/lib/rlsl/prism/ast_visitor/expression_visiting.rb +168 -0
  23. data/lib/rlsl/prism/ast_visitor/scope_context.rb +44 -0
  24. data/lib/rlsl/prism/ast_visitor/visitor_registry.rb +21 -0
  25. data/lib/rlsl/prism/ast_visitor.rb +54 -298
  26. data/lib/rlsl/prism/builtins/function_registry.rb +114 -0
  27. data/lib/rlsl/prism/builtins/operator_rules.rb +99 -0
  28. data/lib/rlsl/prism/builtins/swizzle_rules.rb +38 -0
  29. data/lib/rlsl/prism/builtins.rb +31 -148
  30. data/lib/rlsl/prism/compilation_unit.rb +7 -0
  31. data/lib/rlsl/prism/emitters/base_emitter/control_flow_emission.rb +83 -0
  32. data/lib/rlsl/prism/emitters/base_emitter/definition_emission.rb +104 -0
  33. data/lib/rlsl/prism/emitters/base_emitter/expression_emission.rb +92 -0
  34. data/lib/rlsl/prism/emitters/base_emitter/statement_emission.rb +89 -0
  35. data/lib/rlsl/prism/emitters/base_emitter.rb +68 -423
  36. data/lib/rlsl/prism/emitters/c_emitter.rb +78 -121
  37. data/lib/rlsl/prism/emitters/glsl_emitter.rb +30 -65
  38. data/lib/rlsl/prism/emitters/msl_emitter.rb +35 -62
  39. data/lib/rlsl/prism/emitters/target_emitter.rb +77 -0
  40. data/lib/rlsl/prism/emitters/target_profile.rb +34 -0
  41. data/lib/rlsl/prism/emitters/wgsl_emitter.rb +65 -61
  42. data/lib/rlsl/prism/ir/control_flow.rb +83 -0
  43. data/lib/rlsl/prism/ir/definitions.rb +67 -0
  44. data/lib/rlsl/prism/ir/expressions.rb +197 -0
  45. data/lib/rlsl/prism/ir/node.rb +21 -0
  46. data/lib/rlsl/prism/ir/nodes.rb +4 -371
  47. data/lib/rlsl/prism/ir/traversal.rb +62 -0
  48. data/lib/rlsl/prism/source_extractor/block_locator.rb +52 -0
  49. data/lib/rlsl/prism/source_extractor.rb +14 -133
  50. data/lib/rlsl/prism/source_unit/parser.rb +78 -0
  51. data/lib/rlsl/prism/source_unit.rb +42 -0
  52. data/lib/rlsl/prism/target_capability_validator.rb +85 -0
  53. data/lib/rlsl/prism/transpiler.rb +63 -60
  54. data/lib/rlsl/prism/type_inference/call_type_resolver.rb +35 -0
  55. data/lib/rlsl/prism/type_inference/call_validator.rb +71 -0
  56. data/lib/rlsl/prism/type_inference/collection_type_resolver.rb +80 -0
  57. data/lib/rlsl/prism/type_inference/control_flow_inferer.rb +66 -0
  58. data/lib/rlsl/prism/type_inference/definition_inferer.rb +41 -0
  59. data/lib/rlsl/prism/type_inference/expression_inferer.rb +92 -0
  60. data/lib/rlsl/prism/type_inference/field_type_resolver.rb +17 -0
  61. data/lib/rlsl/prism/type_inference/inferer_registry.rb +38 -0
  62. data/lib/rlsl/prism/type_inference/scope_stack.rb +47 -0
  63. data/lib/rlsl/prism/type_inference/type_environment.rb +112 -0
  64. data/lib/rlsl/prism/type_inference/type_shapes.rb +33 -0
  65. data/lib/rlsl/prism/type_inference.rb +114 -248
  66. data/lib/rlsl/runtime_shader.rb +47 -0
  67. data/lib/rlsl/shader_builder/build_service.rb +77 -0
  68. data/lib/rlsl/shader_builder/native_extension_compiler.rb +71 -0
  69. data/lib/rlsl/shader_builder/shader_definition.rb +68 -0
  70. data/lib/rlsl/shader_builder/source_resolver.rb +91 -0
  71. data/lib/rlsl/shader_builder.rb +22 -113
  72. data/lib/rlsl/types/catalog.rb +47 -0
  73. data/lib/rlsl/types/target_resolver.rb +15 -0
  74. data/lib/rlsl/types/type_spec.rb +167 -0
  75. data/lib/rlsl/types/value_normalizer.rb +81 -0
  76. data/lib/rlsl/types.rb +11 -29
  77. data/lib/rlsl/uniform_context.rb +6 -12
  78. data/lib/rlsl/version.rb +1 -1
  79. data/lib/rlsl/wgsl/translator.rb +23 -39
  80. data/lib/rlsl.rb +14 -12
  81. metadata +55 -2
@@ -1,22 +1,103 @@
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 < StandardError; 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
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
60
+ )
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
+ register: method(:register),
77
+ collection_type_resolver: @collection_type_resolver
78
+ )
79
+ @control_flow_inferer = ControlFlowInferer.new(
80
+ infer: method(:infer),
81
+ infer_child_scope: method(:infer_child_scope),
82
+ infer_in_scope: method(:infer_in_scope)
83
+ )
84
+ register_inferers
12
85
 
13
86
  uniforms.each do |name, type|
14
- @symbol_table[name.to_sym] = type
87
+ register(name, type)
15
88
  end
89
+
90
+ globals.each do |name, type|
91
+ register(name, type)
92
+ end
93
+ end
94
+
95
+ def symbol_table
96
+ @types.to_h
16
97
  end
17
98
 
18
99
  def register(name, type)
19
- @symbol_table[name.to_sym] = type
100
+ @types.register(name, type)
20
101
  end
21
102
 
22
103
  def register_function(name, returns:)
@@ -24,265 +105,50 @@ module RLSL
24
105
  end
25
106
 
26
107
  def lookup(name)
27
- @symbol_table[name.to_sym]
108
+ @types.lookup(name)
28
109
  end
29
110
 
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
111
+ def infer(node, scoped: false)
112
+ options = node.is_a?(IR::Block) ? { scoped: scoped } : {}
113
+ @inferer_registry.infer(node, **options) || node
78
114
  end
79
115
 
80
116
  private
81
117
 
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
118
+ def register_inferers
119
+ @inferer_registry.register(IR::Block) { |node, scoped: false| infer_block(node, scoped: scoped) }
120
+ @inferer_registry.register_methods(@expression_inferer, EXPRESSION_NODES)
121
+ @inferer_registry.register_methods(@definition_inferer, DEFINITION_NODES)
122
+ @inferer_registry.register_methods(@control_flow_inferer, CONTROL_FLOW_NODES)
93
123
  end
94
124
 
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
112
- )
113
- node
114
- end
115
-
116
- def infer_unary_op(node)
117
- infer(node.operand)
118
-
119
- case node.operator.to_s
120
- when "-"
121
- node.type = node.operand.type
122
- when "!"
123
- node.type = :bool
124
- 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
131
-
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
140
- end
141
- node
142
- end
143
-
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
125
+ def infer_block(node, scoped: false)
126
+ infer_with_optional_scope(scoped) do
127
+ node.statements.each { |stmt| infer(stmt) }
128
+ node.type = node.statements.last&.type
129
+ node
151
130
  end
152
- node
153
131
  end
154
132
 
155
- def infer_swizzle(node)
156
- infer(node.receiver)
157
- node.type = Builtins.swizzle_type(node.components)
158
- node
133
+ def infer_child_scope(node)
134
+ infer_in_scope { infer(node) }
159
135
  end
160
136
 
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
168
- end
169
-
170
- def infer_return(node)
171
- infer(node.expression) if node.expression
172
- node.type = node.expression&.type
173
- node
174
- end
175
-
176
- def infer_assignment(node)
177
- infer(node.target)
178
- infer(node.value)
179
- node.type = node.value.type
180
- node
181
- end
182
-
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
137
+ def infer_in_scope(initial_scope = nil)
138
+ initial_scope ? @types.push(initial_scope) : @types.push
191
139
 
192
- def infer_while_loop(node)
193
- infer(node.condition)
194
- infer(node.body)
195
- node.type = nil
196
- node
140
+ yield
141
+ ensure
142
+ @types.pop
197
143
  end
198
144
 
199
- def infer_function_definition(node)
200
- node.param_types.each do |param_name, param_type|
201
- register(param_name, param_type)
202
- 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
- end
216
-
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
236
- end
237
-
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
251
-
252
- if node.element_type
253
- register("#{node.name}_element_type".to_sym, node.element_type)
254
- end
255
-
256
- node
257
- end
258
-
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
145
+ def infer_with_optional_scope(scoped)
146
+ return yield unless scoped
283
147
 
284
- node.type = nil
285
- node
148
+ @types.push
149
+ yield
150
+ ensure
151
+ @types.pop if scoped
286
152
  end
287
153
  end
288
154
  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,77 @@
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
+ c_code = generate_c_code
15
+ artifact = native_extension_compiler.build(c_code)
16
+
17
+ require artifact.file
18
+ CompiledShader.new(@name, artifact.ext_name, @definition.uniforms)
19
+ end
20
+
21
+ def build_metal_shader
22
+ translator = MSL::Translator.new(@definition.uniforms, *translation_sources(:msl))
23
+ msl_source = translator.translate
24
+
25
+ MSL::Shader.new(@name, @definition.uniforms, msl_source)
26
+ end
27
+
28
+ def build_wgsl_shader
29
+ WGSL::Translator.new(@definition.uniforms, *translation_sources(:wgsl)).translate
30
+ end
31
+
32
+ def build_glsl_shader(version: "450")
33
+ GLSL::Translator.new(@definition.uniforms, *translation_sources(:glsl), version: version).translate
34
+ end
35
+
36
+ def transpile_fragment(target)
37
+ return "" unless @definition.fragment_block
38
+
39
+ source_resolver.fragment_code(target)
40
+ end
41
+
42
+ def transpile_helpers(target)
43
+ return "" unless @definition.helpers_block
44
+
45
+ source_resolver.helpers_code(target)
46
+ end
47
+
48
+ private
49
+
50
+ def generate_c_code
51
+ helpers_code, fragment_code = resolved_sources(:c)
52
+ codegen = CodeGenerator.new(@name, @definition.uniforms, -> { helpers_code }, -> { fragment_code })
53
+ codegen.generate
54
+ end
55
+
56
+ def resolved_sources(target)
57
+ source_resolver.sources_for(target)
58
+ end
59
+
60
+ def translation_sources(target)
61
+ source_resolver.translation_sources_for(target)
62
+ end
63
+
64
+ def source_resolver
65
+ @source_resolver ||= @source_resolver_class.new(@definition)
66
+ end
67
+
68
+ def native_extension_compiler
69
+ @native_extension_compiler ||= if @compiler_factory
70
+ @compiler_factory.call(@name)
71
+ else
72
+ NativeExtensionCompiler.new(@name)
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+
5
+ module RLSL
6
+ class ShaderBuilder
7
+ class NativeExtensionCompiler
8
+ Artifact = Struct.new(:ext_name, :directory, :file, keyword_init: true)
9
+
10
+ def initialize(shader_name, cache_dir: RLSL.cache_dir, ruby_bin: RbConfig.ruby, dylib_ext: RbConfig::CONFIG["DLEXT"])
11
+ @shader_name = shader_name
12
+ @cache_dir = cache_dir
13
+ @ruby_bin = ruby_bin
14
+ @dylib_ext = dylib_ext
15
+ end
16
+
17
+ def build(c_code)
18
+ artifact = artifact_for(c_code)
19
+ compile(artifact, c_code) unless File.exist?(artifact.file)
20
+ artifact
21
+ end
22
+
23
+ private
24
+
25
+ def artifact_for(c_code)
26
+ code_hash = Digest::MD5.hexdigest(c_code)[0..7]
27
+ ext_name = "#{@shader_name}_#{code_hash}"
28
+ directory = File.join(@cache_dir, ext_name)
29
+
30
+ Artifact.new(
31
+ ext_name: ext_name,
32
+ directory: directory,
33
+ file: File.join(directory, "#{@shader_name}.#{@dylib_ext}")
34
+ )
35
+ end
36
+
37
+ def compile(artifact, c_code)
38
+ FileUtils.mkdir_p(artifact.directory)
39
+
40
+ File.write(File.join(artifact.directory, "#{@shader_name}.c"), c_code)
41
+ File.write(File.join(artifact.directory, "extconf.rb"), extconf_source(@shader_name))
42
+
43
+ Dir.chdir(artifact.directory) do
44
+ run_command(@ruby_bin, "extconf.rb") or raise "extconf failed for #{artifact.ext_name}"
45
+ run_command("/usr/bin/make") or raise "make failed for #{artifact.ext_name}"
46
+ end
47
+ end
48
+
49
+ def extconf_source(ext_name)
50
+ <<~RUBY
51
+ require "mkmf"
52
+ $CFLAGS << " -O3 -ffast-math"
53
+ if RUBY_PLATFORM =~ /darwin/
54
+ $CFLAGS << " -fblocks"
55
+ end
56
+ create_makefile("#{ext_name}")
57
+ RUBY
58
+ end
59
+
60
+ def run_command(*args)
61
+ if defined?(Bundler) && Bundler.respond_to?(:with_unbundled_env)
62
+ Bundler.with_unbundled_env do
63
+ system(*args, out: File::NULL, err: File::NULL)
64
+ end
65
+ else
66
+ system(*args, out: File::NULL, err: File::NULL)
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RLSL
4
+ class ShaderBuilder
5
+ class ShaderDefinition
6
+ attr_reader :uniforms, :custom_functions, :helpers_block, :helpers_mode, :fragment_block, :fragment_mode
7
+
8
+ def initialize(
9
+ uniforms: {},
10
+ custom_functions: {},
11
+ helpers_block: nil,
12
+ helpers_mode: :c,
13
+ fragment_block: nil,
14
+ fragment_mode: :c
15
+ )
16
+ @uniforms = normalize_hash(uniforms)
17
+ @custom_functions = normalize_hash(custom_functions)
18
+ @helpers_block = helpers_block
19
+ @helpers_mode = helpers_mode
20
+ @fragment_block = fragment_block
21
+ @fragment_mode = fragment_mode
22
+ end
23
+
24
+ def with_uniforms(uniforms)
25
+ copy(uniforms: uniforms)
26
+ end
27
+
28
+ def with_custom_functions(custom_functions)
29
+ copy(custom_functions: custom_functions)
30
+ end
31
+
32
+ def with_helpers(mode:, block:)
33
+ copy(helpers_mode: mode, helpers_block: block)
34
+ end
35
+
36
+ def with_fragment(mode:, block:)
37
+ copy(fragment_mode: mode, fragment_block: block)
38
+ end
39
+
40
+ def ruby_fragment?
41
+ @fragment_mode == :ruby
42
+ end
43
+
44
+ def ruby_helpers?
45
+ @helpers_mode == :ruby
46
+ end
47
+
48
+ private
49
+
50
+ def copy(**overrides)
51
+ self.class.new(
52
+ uniforms: overrides.fetch(:uniforms, @uniforms),
53
+ custom_functions: overrides.fetch(:custom_functions, @custom_functions),
54
+ helpers_block: overrides.fetch(:helpers_block, @helpers_block),
55
+ helpers_mode: overrides.fetch(:helpers_mode, @helpers_mode),
56
+ fragment_block: overrides.fetch(:fragment_block, @fragment_block),
57
+ fragment_mode: overrides.fetch(:fragment_mode, @fragment_mode)
58
+ )
59
+ end
60
+
61
+ def normalize_hash(hash)
62
+ hash.each_with_object({}) do |(name, value), normalized|
63
+ normalized[name.to_sym] = value
64
+ end.freeze
65
+ end
66
+ end
67
+ end
68
+ end