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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -1
- data/README.md +2 -0
- data/lib/rlsl/base_translator/call_parser.rb +68 -0
- data/lib/rlsl/base_translator/code_rewriter.rb +107 -0
- data/lib/rlsl/base_translator/code_scanner.rb +102 -0
- data/lib/rlsl/base_translator.rb +159 -63
- data/lib/rlsl/code_generator/math_prelude.rb +79 -0
- data/lib/rlsl/code_generator/ruby_wrapper_generator.rb +97 -0
- data/lib/rlsl/code_generator/shader_function_generator.rb +19 -0
- data/lib/rlsl/code_generator/template_context.rb +41 -0
- data/lib/rlsl/code_generator/uniform_struct_generator.rb +29 -0
- data/lib/rlsl/code_generator.rb +26 -201
- data/lib/rlsl/compiled_shader.rb +4 -12
- data/lib/rlsl/function_context.rb +31 -14
- data/lib/rlsl/glsl/translator.rb +19 -38
- data/lib/rlsl/msl/shader.rb +8 -38
- data/lib/rlsl/msl/translator.rb +19 -36
- data/lib/rlsl/msl/uniform_buffer_packer.rb +68 -0
- data/lib/rlsl/prism/ast_visitor/control_flow_visiting.rb +96 -0
- data/lib/rlsl/prism/ast_visitor/definition_visiting.rb +79 -0
- data/lib/rlsl/prism/ast_visitor/expression_visiting.rb +168 -0
- data/lib/rlsl/prism/ast_visitor/scope_context.rb +44 -0
- data/lib/rlsl/prism/ast_visitor/visitor_registry.rb +21 -0
- data/lib/rlsl/prism/ast_visitor.rb +54 -298
- data/lib/rlsl/prism/builtins/function_registry.rb +114 -0
- data/lib/rlsl/prism/builtins/operator_rules.rb +99 -0
- data/lib/rlsl/prism/builtins/swizzle_rules.rb +38 -0
- data/lib/rlsl/prism/builtins.rb +31 -148
- data/lib/rlsl/prism/compilation_unit.rb +7 -0
- data/lib/rlsl/prism/emitters/base_emitter/control_flow_emission.rb +83 -0
- data/lib/rlsl/prism/emitters/base_emitter/definition_emission.rb +104 -0
- data/lib/rlsl/prism/emitters/base_emitter/expression_emission.rb +92 -0
- data/lib/rlsl/prism/emitters/base_emitter/statement_emission.rb +89 -0
- data/lib/rlsl/prism/emitters/base_emitter.rb +68 -423
- data/lib/rlsl/prism/emitters/c_emitter.rb +78 -121
- data/lib/rlsl/prism/emitters/glsl_emitter.rb +30 -65
- data/lib/rlsl/prism/emitters/msl_emitter.rb +35 -62
- data/lib/rlsl/prism/emitters/target_emitter.rb +77 -0
- data/lib/rlsl/prism/emitters/target_profile.rb +34 -0
- data/lib/rlsl/prism/emitters/wgsl_emitter.rb +65 -61
- data/lib/rlsl/prism/ir/control_flow.rb +83 -0
- data/lib/rlsl/prism/ir/definitions.rb +67 -0
- data/lib/rlsl/prism/ir/expressions.rb +197 -0
- data/lib/rlsl/prism/ir/node.rb +21 -0
- data/lib/rlsl/prism/ir/nodes.rb +4 -371
- data/lib/rlsl/prism/ir/traversal.rb +62 -0
- data/lib/rlsl/prism/source_extractor/block_locator.rb +52 -0
- data/lib/rlsl/prism/source_extractor.rb +14 -133
- data/lib/rlsl/prism/source_unit/parser.rb +78 -0
- data/lib/rlsl/prism/source_unit.rb +42 -0
- data/lib/rlsl/prism/target_capability_validator.rb +85 -0
- data/lib/rlsl/prism/transpiler.rb +63 -60
- data/lib/rlsl/prism/type_inference/call_type_resolver.rb +35 -0
- data/lib/rlsl/prism/type_inference/call_validator.rb +71 -0
- data/lib/rlsl/prism/type_inference/collection_type_resolver.rb +80 -0
- data/lib/rlsl/prism/type_inference/control_flow_inferer.rb +66 -0
- data/lib/rlsl/prism/type_inference/definition_inferer.rb +41 -0
- data/lib/rlsl/prism/type_inference/expression_inferer.rb +92 -0
- data/lib/rlsl/prism/type_inference/field_type_resolver.rb +17 -0
- data/lib/rlsl/prism/type_inference/inferer_registry.rb +38 -0
- data/lib/rlsl/prism/type_inference/scope_stack.rb +47 -0
- data/lib/rlsl/prism/type_inference/type_environment.rb +112 -0
- data/lib/rlsl/prism/type_inference/type_shapes.rb +33 -0
- data/lib/rlsl/prism/type_inference.rb +114 -248
- data/lib/rlsl/runtime_shader.rb +47 -0
- data/lib/rlsl/shader_builder/build_service.rb +77 -0
- data/lib/rlsl/shader_builder/native_extension_compiler.rb +71 -0
- data/lib/rlsl/shader_builder/shader_definition.rb +68 -0
- data/lib/rlsl/shader_builder/source_resolver.rb +91 -0
- data/lib/rlsl/shader_builder.rb +22 -113
- data/lib/rlsl/types/catalog.rb +47 -0
- data/lib/rlsl/types/target_resolver.rb +15 -0
- data/lib/rlsl/types/type_spec.rb +167 -0
- data/lib/rlsl/types/value_normalizer.rb +81 -0
- data/lib/rlsl/types.rb +11 -29
- data/lib/rlsl/uniform_context.rb +6 -12
- data/lib/rlsl/version.rb +1 -1
- data/lib/rlsl/wgsl/translator.rb +23 -39
- data/lib/rlsl.rb +14 -12
- metadata +55 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 15d160bc13d36ac3efc480e1a99f20b374740d27d26a30a1eb73685883d16c67
|
|
4
|
+
data.tar.gz: 8f6a6dc93d8956138756c4e6356fb58b62b850998445b5dad8adcc02d22d4f18
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
data/lib/rlsl/base_translator.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
29
|
+
REMOVED_IDENTIFIERS = %w[static inline].freeze
|
|
8
30
|
|
|
9
31
|
def initialize(uniforms, helpers_code, fragment_code)
|
|
10
32
|
@uniforms = uniforms
|
|
11
|
-
@
|
|
12
|
-
@
|
|
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(@
|
|
17
|
-
fragment_translated = translate_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(
|
|
24
|
-
|
|
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
|
-
|
|
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
|
-
|
|
87
|
+
UniformTypes.target_type(:vec2, uniform_target)
|
|
99
88
|
end
|
|
100
89
|
|
|
101
90
|
def target_vec3_type
|
|
102
|
-
|
|
91
|
+
UniformTypes.target_type(:vec3, uniform_target)
|
|
103
92
|
end
|
|
104
93
|
|
|
105
94
|
def target_vec4_type
|
|
106
|
-
|
|
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
|