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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f0753b46ec16e2df8308ecab2b9e197cf24ab1d02f5028240d8fa6f9a886529b
4
- data.tar.gz: dcaf85ad34c2e2f4aa9479dbe3f405fd063d9d46c47966a1d4e137a8c4c513cd
3
+ metadata.gz: 15d160bc13d36ac3efc480e1a99f20b374740d27d26a30a1eb73685883d16c67
4
+ data.tar.gz: 8f6a6dc93d8956138756c4e6356fb58b62b850998445b5dad8adcc02d22d4f18
5
5
  SHA512:
6
- metadata.gz: 13bd3732c03a7b6f0c0244ee63cc4d49ebba3264fa1dc6e8a78237b8e1d0c69ed15129787174871556e6afb8452d8cba6fb6c8bbac3f31a5488765a9866a1977
7
- data.tar.gz: e970cf8b44c96da77f52c0b6b68f578c896b710f2e5e41e5089726d652e762b51f1d6e3523393f4d31c355c9f3c845f7ca115dba2d1bba0e0e14d0e013dc06fc
6
+ metadata.gz: 644a31110884bacb33734231caee01dad98086a71f5d82738e343fe14e4316938a5432b0e3c3f8c47b40f8b940f534b786d46c9a3dc56ecaad2fd8414a857be4
7
+ data.tar.gz: fdd2eb0cb01d5dfbf5d485e5da40bbaa61dde2c92bd6d2dda9f3ebb1f3ff0538483c6ddaa0fe8beed09ef17207e24d846ced805a0f9f473473405c6d3c737db1
data/CHANGELOG.md CHANGED
@@ -2,10 +2,19 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 1.0.0 - 2026-04-05
6
+
7
+ - Breaking: custom translator and emitter integrations now require explicit target `PROFILE` definitions; the old stateful Prism transpiler API and compatibility aliases have been removed.
8
+ - Added: the Ruby shader DSL now supports ternary expressions, compound local assignments, and fragment transpilation that can reference globals/constants defined in Ruby helpers.
9
+ - Added: `functions` now supports validated signatures via `define`, plus shorthand declarations for `int`, `bool`, `mat2`, `mat3`, `mat4`, and `sampler2D`.
10
+ - Changed: compiled shaders and Metal shaders now validate and normalize uniform values at runtime, with clearer errors for missing uniforms and invalid scalar/vector/bool inputs.
11
+ - Fixed: integer type inference for custom function calls is preserved, and block source extraction is more reliable for assigned `if` expressions and modifier forms.
12
+ - Fixed: native extension builds and target capability errors are more robust, reducing backend-specific build and translation failures.
13
+
5
14
  ## 0.1.1 - 2026-01-04
6
15
 
7
16
  - Make metaco an optional runtime dependency and document its installation.
8
17
 
9
18
  ## 0.1.0 - 2026-01-04
10
19
 
11
- - Initial release.
20
+ - Initial release.
data/README.md CHANGED
@@ -180,6 +180,8 @@ $ bundle install
180
180
  $ rake test
181
181
  ```
182
182
 
183
+ For a quick local coverage summary, run `COVERAGE=1 rake test`. It prints the overall `lib/` coverage and the lowest-covered files without requiring extra gems.
184
+
183
185
  ## License
184
186
 
185
187
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RLSL
4
+ class BaseTranslator
5
+ class CallParser
6
+ def self.extract_group(segment, opening_index)
7
+ depth = 0
8
+ index = opening_index
9
+ quote = nil
10
+
11
+ while index < segment.length
12
+ char = segment[index]
13
+
14
+ if quote
15
+ quote = nil if char == quote && segment[index - 1] != "\\"
16
+ else
17
+ case char
18
+ when '"', "'"
19
+ quote = char
20
+ when "(", "[", "{"
21
+ depth += 1
22
+ when ")", "]", "}"
23
+ depth -= 1
24
+ return [segment[(opening_index + 1)...index], index] if depth.zero?
25
+ end
26
+ end
27
+
28
+ index += 1
29
+ end
30
+
31
+ raise ArgumentError, "Unbalanced delimiter in translation source"
32
+ end
33
+
34
+ def self.split_arguments(arguments_source)
35
+ arguments = []
36
+ depth = 0
37
+ quote = nil
38
+ start_index = 0
39
+
40
+ arguments_source.each_char.with_index do |char, index|
41
+ if quote
42
+ quote = nil if char == quote && arguments_source[index - 1] != "\\"
43
+ next
44
+ end
45
+
46
+ case char
47
+ when '"', "'"
48
+ quote = char
49
+ when "(", "[", "{"
50
+ depth += 1
51
+ when ")", "]", "}"
52
+ depth -= 1
53
+ when ","
54
+ next unless depth.zero?
55
+
56
+ arguments << arguments_source[start_index...index]
57
+ start_index = index + 1
58
+ end
59
+ end
60
+
61
+ tail = arguments_source[start_index..]
62
+ return [] if arguments.empty? && tail.to_s.strip.empty?
63
+
64
+ arguments << tail
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "call_parser"
4
+ require_relative "code_scanner"
5
+
6
+ module RLSL
7
+ class BaseTranslator
8
+ class CodeRewriter
9
+ IDENTIFIER_PATTERN = /[A-Za-z_][A-Za-z0-9_]*/
10
+
11
+ def initialize(code)
12
+ @code = code.to_s
13
+ end
14
+
15
+ def rewrite(identifier_replacements:, call_rewrites:, removed_identifiers:)
16
+ rewrite_segment(
17
+ @code,
18
+ identifier_replacements: stringify_keys(identifier_replacements),
19
+ call_rewrites: stringify_keys(call_rewrites),
20
+ removed_identifiers: Array(removed_identifiers).map(&:to_s)
21
+ )
22
+ end
23
+
24
+ private
25
+
26
+ def rewrite_segment(segment, identifier_replacements:, call_rewrites:, removed_identifiers:)
27
+ CodeScanner.new(segment).each_segment.map do |code_segment|
28
+ code_segment.code? ? rewrite_code_segment(
29
+ code_segment.text,
30
+ identifier_replacements: identifier_replacements,
31
+ call_rewrites: call_rewrites,
32
+ removed_identifiers: removed_identifiers
33
+ ) : code_segment.text
34
+ end.join
35
+ end
36
+
37
+ def rewrite_code_segment(segment, identifier_replacements:, call_rewrites:, removed_identifiers:)
38
+ output = +""
39
+ index = 0
40
+
41
+ while index < segment.length
42
+ if identifier_start?(segment, index)
43
+ identifier, identifier_end = read_identifier(segment, index)
44
+ call_index = skip_whitespace(segment, identifier_end)
45
+
46
+ if call_rewrites.key?(identifier) && segment[call_index] == "("
47
+ arguments_source, closing_index = CallParser.extract_group(segment, call_index)
48
+ rewritten_args = CallParser.split_arguments(arguments_source).map do |argument|
49
+ rewrite_segment(
50
+ argument,
51
+ identifier_replacements: identifier_replacements,
52
+ call_rewrites: call_rewrites,
53
+ removed_identifiers: removed_identifiers
54
+ ).strip
55
+ end
56
+ output << render_call(call_rewrites.fetch(identifier), rewritten_args)
57
+ index = closing_index + 1
58
+ next
59
+ end
60
+
61
+ unless removed_identifiers.include?(identifier)
62
+ output << (identifier_replacements[identifier] || identifier)
63
+ end
64
+ index = identifier_end
65
+ next
66
+ end
67
+
68
+ output << segment[index]
69
+ index += 1
70
+ end
71
+
72
+ output
73
+ end
74
+
75
+ def render_call(rewriter, arguments)
76
+ return rewriter.call(arguments) if rewriter.respond_to?(:call)
77
+
78
+ "#{rewriter}(#{arguments.join(', ')})"
79
+ end
80
+
81
+ def stringify_keys(hash)
82
+ hash.each_with_object({}) do |(key, value), memo|
83
+ memo[key.to_s] = value
84
+ end
85
+ end
86
+
87
+ def identifier_start?(segment, index)
88
+ segment[index] =~ /[A-Za-z_]/
89
+ end
90
+
91
+ def read_identifier(segment, index)
92
+ match = IDENTIFIER_PATTERN.match(segment, index)
93
+ [match[0], match.end(0)]
94
+ end
95
+
96
+ def skip_whitespace(segment, index)
97
+ current_index = index
98
+ current_index += 1 while current_index < segment.length && whitespace?(segment[current_index])
99
+ current_index
100
+ end
101
+
102
+ def whitespace?(char)
103
+ char =~ /\s/
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RLSL
4
+ class BaseTranslator
5
+ class CodeScanner
6
+ Segment = Struct.new(:type, :text, keyword_init: true) do
7
+ def code?
8
+ type == :code
9
+ end
10
+ end
11
+
12
+ def initialize(code)
13
+ @code = code.to_s
14
+ end
15
+
16
+ def each_segment
17
+ return enum_for(:each_segment) unless block_given?
18
+
19
+ buffer = +""
20
+ index = 0
21
+
22
+ while index < @code.length
23
+ preserved, next_index = read_preserved_segment(index)
24
+ unless preserved
25
+ buffer << @code[index]
26
+ index += 1
27
+ next
28
+ end
29
+
30
+ yield Segment.new(type: :code, text: buffer) unless buffer.empty?
31
+ buffer = +""
32
+ yield Segment.new(type: preserved[:type], text: preserved[:text])
33
+ index = next_index
34
+ end
35
+
36
+ yield Segment.new(type: :code, text: buffer) unless buffer.empty?
37
+ end
38
+
39
+ private
40
+
41
+ def read_preserved_segment(index)
42
+ if line_comment_start?(index)
43
+ comment, next_index = read_line_comment(index)
44
+ return [{ type: :line_comment, text: comment }, next_index]
45
+ end
46
+
47
+ if block_comment_start?(index)
48
+ comment, next_index = read_block_comment(index)
49
+ return [{ type: :block_comment, text: comment }, next_index]
50
+ end
51
+
52
+ if string_delimiter?(@code[index])
53
+ literal, next_index = read_quoted(index)
54
+ return [{ type: :string, text: literal }, next_index]
55
+ end
56
+
57
+ nil
58
+ end
59
+
60
+ def line_comment_start?(index)
61
+ @code[index, 2] == "//"
62
+ end
63
+
64
+ def block_comment_start?(index)
65
+ @code[index, 2] == "/*"
66
+ end
67
+
68
+ def string_delimiter?(char)
69
+ char == '"' || char == "'"
70
+ end
71
+
72
+ def read_line_comment(index)
73
+ newline_index = @code.index("\n", index)
74
+ end_index = newline_index ? newline_index : @code.length
75
+ [@code[index...end_index], end_index]
76
+ end
77
+
78
+ def read_block_comment(index)
79
+ closing_index = @code.index("*/", index + 2)
80
+ raise ArgumentError, "Unterminated block comment in translation source" unless closing_index
81
+
82
+ end_index = closing_index + 2
83
+ [@code[index...end_index], end_index]
84
+ end
85
+
86
+ def read_quoted(index)
87
+ quote = @code[index]
88
+ cursor = index + 1
89
+
90
+ while cursor < @code.length
91
+ if @code[cursor] == quote && @code[cursor - 1] != "\\"
92
+ return [@code[index..cursor], cursor + 1]
93
+ end
94
+
95
+ cursor += 1
96
+ end
97
+
98
+ raise ArgumentError, "Unterminated string literal in translation source"
99
+ end
100
+ end
101
+ end
102
+ end
@@ -1,92 +1,73 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "base_translator/code_rewriter"
4
+
3
5
  module RLSL
4
6
  class BaseTranslator
5
- FUNC_REPLACEMENTS = [].freeze
7
+ SourceSnippet = Struct.new(:code, :format, keyword_init: true) do
8
+ def target_code?
9
+ format == :target
10
+ end
11
+ end
12
+
13
+ TargetProfile = Struct.new(
14
+ :uniform_target,
15
+ :identifier_replacements,
16
+ :call_rewrites,
17
+ :removed_identifiers,
18
+ keyword_init: true
19
+ ) do
20
+ def translate(code)
21
+ CodeRewriter.new(code).rewrite(
22
+ identifier_replacements: identifier_replacements,
23
+ call_rewrites: call_rewrites,
24
+ removed_identifiers: removed_identifiers
25
+ )
26
+ end
27
+ end
6
28
 
7
- TYPE_MAP = {}.freeze
29
+ REMOVED_IDENTIFIERS = %w[static inline].freeze
8
30
 
9
31
  def initialize(uniforms, helpers_code, fragment_code)
10
32
  @uniforms = uniforms
11
- @helpers_code = helpers_code || ""
12
- @fragment_code = fragment_code || ""
33
+ @helpers_source = normalize_source(helpers_code)
34
+ @fragment_source = normalize_source(fragment_code)
13
35
  end
14
36
 
15
37
  def translate
16
- helpers_translated = translate_code(@helpers_code)
17
- fragment_translated = translate_code(@fragment_code)
38
+ helpers_translated = translate_code(@helpers_source)
39
+ fragment_translated = translate_code(@fragment_source)
18
40
  generate_shader(helpers_translated, fragment_translated)
19
41
  end
20
42
 
21
43
  protected
22
44
 
23
- def translate_code(c_code)
24
- return "" if c_code.nil? || c_code.empty?
45
+ def translate_code(source)
46
+ snippet = normalize_source(source)
47
+ return "" if snippet.code.empty?
48
+ return snippet.code if snippet.target_code?
25
49
 
26
- result = c_code.dup
27
-
28
- self.class::TYPE_MAP.each do |c_type, target_type|
29
- result.gsub!(/\b#{c_type}\b/, target_type)
30
- end
31
-
32
- self.class::FUNC_REPLACEMENTS.each do |pattern, replacement|
33
- result.gsub!(pattern, replacement)
34
- end
35
-
36
- result
50
+ profile.translate(snippet.code)
37
51
  end
38
52
 
39
53
  def generate_shader(_helpers, _fragment)
40
54
  raise NotImplementedError, "Subclasses must implement generate_shader"
41
55
  end
42
56
 
43
- def self.common_func_replacements(target_vec2:, target_vec3:, target_vec4:)
44
- [
45
- [/vec2_new\(([^,]+),\s*([^)]+)\)/, "#{target_vec2}(\\1, \\2)"],
46
- [/vec3_new\(([^,]+),\s*([^,]+),\s*([^)]+)\)/, "#{target_vec3}(\\1, \\2, \\3)"],
47
- [/vec4_new\(([^,]+),\s*([^,]+),\s*([^,]+),\s*([^)]+)\)/, "#{target_vec4}(\\1, \\2, \\3, \\4)"],
48
- [/vec2_add\(([^,]+),\s*([^)]+)\)/, '(\1 + \2)'],
49
- [/vec3_add\(([^,]+),\s*([^)]+)\)/, '(\1 + \2)'],
50
- [/vec2_sub\(([^,]+),\s*([^)]+)\)/, '(\1 - \2)'],
51
- [/vec3_sub\(([^,]+),\s*([^)]+)\)/, '(\1 - \2)'],
52
- [/vec2_mul\(([^,]+),\s*([^)]+)\)/, '(\1 * \2)'],
53
- [/vec3_mul\(([^,]+),\s*([^)]+)\)/, '(\1 * \2)'],
54
- [/vec2_div\(([^,]+),\s*([^)]+)\)/, '(\1 / \2)'],
55
- [/vec3_div\(([^,]+),\s*([^)]+)\)/, '(\1 / \2)'],
56
- [/vec2_dot\(([^,]+),\s*([^)]+)\)/, 'dot(\1, \2)'],
57
- [/vec3_dot\(([^,]+),\s*([^)]+)\)/, 'dot(\1, \2)'],
58
- [/vec2_length\(([^)]+)\)/, 'length(\1)'],
59
- [/vec3_length\(([^)]+)\)/, 'length(\1)'],
60
- [/vec2_normalize\(([^)]+)\)/, 'normalize(\1)'],
61
- [/vec3_normalize\(([^)]+)\)/, 'normalize(\1)'],
62
- [/sqrtf\(/, "sqrt("],
63
- [/sinf\(/, "sin("],
64
- [/cosf\(/, "cos("],
65
- [/tanf\(/, "tan("],
66
- [/fabsf\(/, "abs("],
67
- [/fminf\(/, "min("],
68
- [/fmaxf\(/, "max("],
69
- [/floorf\(/, "floor("],
70
- [/ceilf\(/, "ceil("],
71
- [/powf\(/, "pow("],
72
- [/expf\(/, "exp("],
73
- [/logf\(/, "log("],
74
- [/atan2f\(/, "atan2("],
75
- [/fmodf\(/, "fmod("],
76
- [/mix_f\(/, "mix("],
77
- [/mix_v3\(/, "mix("],
78
- [/clamp_f\(/, "clamp("],
79
- [/smoothstep\(/, "smoothstep("],
80
- [/fract\(/, "fract("]
81
- ]
82
- end
83
-
84
57
  def uniform_type_to_target(type)
85
58
  case type
86
59
  when :float then target_float_type
60
+ when :int then target_int_type
61
+ when :bool then target_bool_type
87
62
  when :vec2 then target_vec2_type
88
63
  when :vec3 then target_vec3_type
89
64
  when :vec4 then target_vec4_type
65
+ when :mat2 then target_mat2_type
66
+ when :mat3 then target_mat3_type
67
+ when :mat4 then target_mat4_type
68
+ when :sampler2D then target_sampler2d_type
69
+ else
70
+ raise ArgumentError, "Unsupported uniform type: #{type.inspect}"
90
71
  end
91
72
  end
92
73
 
@@ -94,16 +75,131 @@ module RLSL
94
75
  "float"
95
76
  end
96
77
 
78
+ def target_int_type
79
+ UniformTypes.target_type(:int, uniform_target)
80
+ end
81
+
82
+ def target_bool_type
83
+ UniformTypes.target_type(:bool, uniform_target)
84
+ end
85
+
97
86
  def target_vec2_type
98
- raise NotImplementedError
87
+ UniformTypes.target_type(:vec2, uniform_target)
99
88
  end
100
89
 
101
90
  def target_vec3_type
102
- raise NotImplementedError
91
+ UniformTypes.target_type(:vec3, uniform_target)
103
92
  end
104
93
 
105
94
  def target_vec4_type
106
- raise NotImplementedError
95
+ UniformTypes.target_type(:vec4, uniform_target)
96
+ end
97
+
98
+ def target_mat2_type
99
+ UniformTypes.target_type(:mat2, uniform_target)
100
+ end
101
+
102
+ def target_mat3_type
103
+ UniformTypes.target_type(:mat3, uniform_target)
104
+ end
105
+
106
+ def target_mat4_type
107
+ UniformTypes.target_type(:mat4, uniform_target)
108
+ end
109
+
110
+ def target_sampler2d_type
111
+ UniformTypes.target_type(:sampler2D, uniform_target)
112
+ end
113
+
114
+ def uniform_target
115
+ profile.uniform_target
116
+ end
117
+
118
+ def uniform_lines(resolution_line:, &block)
119
+ lines = [resolution_line]
120
+ @uniforms.each do |name, type|
121
+ lines << block.call(name, uniform_type_to_target(type))
122
+ end
123
+ lines
124
+ end
125
+
126
+ def profile
127
+ return self.class::PROFILE if self.class.const_defined?(:PROFILE, false)
128
+
129
+ raise NotImplementedError, "Subclasses must define PROFILE"
130
+ end
131
+
132
+ def self.build_profile(uniform_target:, identifier_replacements:, call_rewrites:, removed_identifiers: REMOVED_IDENTIFIERS)
133
+ TargetProfile.new(
134
+ uniform_target: uniform_target,
135
+ identifier_replacements: identifier_replacements.freeze,
136
+ call_rewrites: call_rewrites.freeze,
137
+ removed_identifiers: removed_identifiers.freeze
138
+ ).freeze
139
+ end
140
+
141
+ def self.common_call_rewrites(target_vec2:, target_vec3:, target_vec4:)
142
+ {
143
+ "vec2_new" => rename_call(target_vec2),
144
+ "vec3_new" => rename_call(target_vec3),
145
+ "vec4_new" => rename_call(target_vec4),
146
+ "vec2_add" => infix_call("+"),
147
+ "vec3_add" => infix_call("+"),
148
+ "vec2_sub" => infix_call("-"),
149
+ "vec3_sub" => infix_call("-"),
150
+ "vec2_mul" => infix_call("*"),
151
+ "vec3_mul" => infix_call("*"),
152
+ "vec2_div" => infix_call("/"),
153
+ "vec3_div" => infix_call("/"),
154
+ "vec2_dot" => rename_call("dot"),
155
+ "vec3_dot" => rename_call("dot"),
156
+ "vec2_length" => rename_call("length"),
157
+ "vec3_length" => rename_call("length"),
158
+ "vec2_normalize" => rename_call("normalize"),
159
+ "vec3_normalize" => rename_call("normalize"),
160
+ "sqrtf" => rename_call("sqrt"),
161
+ "sinf" => rename_call("sin"),
162
+ "cosf" => rename_call("cos"),
163
+ "tanf" => rename_call("tan"),
164
+ "fabsf" => rename_call("abs"),
165
+ "fminf" => rename_call("min"),
166
+ "fmaxf" => rename_call("max"),
167
+ "floorf" => rename_call("floor"),
168
+ "ceilf" => rename_call("ceil"),
169
+ "powf" => rename_call("pow"),
170
+ "expf" => rename_call("exp"),
171
+ "logf" => rename_call("log"),
172
+ "atan2f" => rename_call("atan2"),
173
+ "fmodf" => rename_call("fmod"),
174
+ "mix_f" => rename_call("mix"),
175
+ "mix_v3" => rename_call("mix"),
176
+ "clamp_f" => rename_call("clamp"),
177
+ "smoothstep" => rename_call("smoothstep"),
178
+ "fract" => rename_call("fract")
179
+ }.freeze
180
+ end
181
+
182
+ def self.rename_call(name)
183
+ lambda do |arguments|
184
+ "#{name}(#{arguments.join(', ')})"
185
+ end
186
+ end
187
+
188
+ def self.infix_call(operator)
189
+ lambda do |arguments|
190
+ raise ArgumentError, "Expected 2 arguments for #{operator} rewrite, got #{arguments.length}" unless arguments.length == 2
191
+
192
+ "(#{arguments[0]} #{operator} #{arguments[1]})"
193
+ end
194
+ end
195
+
196
+ private
197
+
198
+ def normalize_source(source)
199
+ return SourceSnippet.new(code: "", format: :legacy) if source.nil?
200
+ return source if source.is_a?(SourceSnippet)
201
+
202
+ SourceSnippet.new(code: source.to_s, format: :legacy)
107
203
  end
108
204
  end
109
205
  end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RLSL
4
+ class CodeGenerator
5
+ module MathPrelude
6
+ module_function
7
+
8
+ def code
9
+ <<~C
10
+ static inline vec2 vec2_new(float x, float y) { return (vec2){x, y}; }
11
+ static inline vec3 vec3_new(float x, float y, float z) { return (vec3){x, y, z}; }
12
+ static inline vec4 vec4_new(float x, float y, float z, float w) { return (vec4){x, y, z, w}; }
13
+
14
+ static inline vec2 vec2_add(vec2 a, vec2 b) { return (vec2){a.x + b.x, a.y + b.y}; }
15
+ static inline vec3 vec3_add(vec3 a, vec3 b) { return (vec3){a.x + b.x, a.y + b.y, a.z + b.z}; }
16
+
17
+ static inline vec2 vec2_sub(vec2 a, vec2 b) { return (vec2){a.x - b.x, a.y - b.y}; }
18
+ static inline vec3 vec3_sub(vec3 a, vec3 b) { return (vec3){a.x - b.x, a.y - b.y, a.z - b.z}; }
19
+
20
+ static inline vec2 vec2_mul(vec2 a, float s) { return (vec2){a.x * s, a.y * s}; }
21
+ static inline vec3 vec3_mul(vec3 a, float s) { return (vec3){a.x * s, a.y * s, a.z * s}; }
22
+
23
+ static inline vec2 vec2_div(vec2 a, float s) { return (vec2){a.x / s, a.y / s}; }
24
+ static inline vec3 vec3_div(vec3 a, float s) { return (vec3){a.x / s, a.y / s, a.z / s}; }
25
+
26
+ static inline float vec2_dot(vec2 a, vec2 b) { return a.x * b.x + a.y * b.y; }
27
+ static inline float vec3_dot(vec3 a, vec3 b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
28
+
29
+ static inline float vec2_length(vec2 v) { return sqrtf(v.x * v.x + v.y * v.y); }
30
+ static inline float vec3_length(vec3 v) { return sqrtf(v.x * v.x + v.y * v.y + v.z * v.z); }
31
+
32
+ static inline vec2 vec2_normalize(vec2 v) { float l = vec2_length(v); return l > 0 ? vec2_div(v, l) : v; }
33
+ static inline vec3 vec3_normalize(vec3 v) { float l = vec3_length(v); return l > 0 ? vec3_div(v, l) : v; }
34
+
35
+ static inline float fract(float x) { return x - floorf(x); }
36
+ static inline float mix_f(float a, float b, float t) { return a + (b - a) * t; }
37
+ static inline vec3 mix_v3(vec3 a, vec3 b, float t) {
38
+ return (vec3){a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t};
39
+ }
40
+
41
+ static inline float clamp_f(float x, float lo, float hi) {
42
+ return x < lo ? lo : (x > hi ? hi : x);
43
+ }
44
+
45
+ static inline float smoothstep(float edge0, float edge1, float x) {
46
+ float t = clamp_f((x - edge0) / (edge1 - edge0), 0.0f, 1.0f);
47
+ return t * t * (3.0f - 2.0f * t);
48
+ }
49
+
50
+ static inline float hash21(vec2 p) {
51
+ float dot = p.x * 12.9898f + p.y * 78.233f;
52
+ return fract(sinf(dot) * 43758.5453f);
53
+ }
54
+
55
+ static inline vec2 hash22(vec2 p) {
56
+ float n = sinf(vec2_dot(p, vec2_new(127.1f, 311.7f)));
57
+ return vec2_new(fract(n * 43758.5453f), fract(n * 12345.6789f));
58
+ }
59
+
60
+ static inline vec3 reflect(vec3 I, vec3 N) {
61
+ float d = vec3_dot(N, I);
62
+ return vec3_new(I.x - 2.0f * d * N.x, I.y - 2.0f * d * N.y, I.z - 2.0f * d * N.z);
63
+ }
64
+
65
+ static inline vec3 refract(vec3 I, vec3 N, float eta) {
66
+ float d = vec3_dot(N, I);
67
+ float k = 1.0f - eta * eta * (1.0f - d * d);
68
+ if (k < 0.0f) {
69
+ return vec3_new(0.0f, 0.0f, 0.0f);
70
+ }
71
+
72
+ float s = eta * d + sqrtf(k);
73
+ return vec3_new(eta * I.x - s * N.x, eta * I.y - s * N.y, eta * I.z - s * N.z);
74
+ }
75
+ C
76
+ end
77
+ end
78
+ end
79
+ end