rlsl 1.0.0 → 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.
- checksums.yaml +4 -4
- data/.rubocop.yml +8 -0
- data/CHANGELOG.md +3 -1
- data/README.md +101 -28
- data/Rakefile +7 -0
- data/lib/rlsl/base_translator/call_parser.rb +13 -2
- data/lib/rlsl/base_translator/code_scanner.rb +39 -1
- data/lib/rlsl/base_translator.rb +24 -2
- data/lib/rlsl/code_generator/math_prelude.rb +103 -10
- data/lib/rlsl/code_generator/ruby_wrapper_generator.rb +52 -12
- data/lib/rlsl/code_generator/template_context.rb +6 -3
- data/lib/rlsl/code_generator/uniform_struct_generator.rb +1 -1
- data/lib/rlsl/code_generator.rb +7 -4
- data/lib/rlsl/compiled_shader.rb +1 -1
- data/lib/rlsl/errors.rb +29 -0
- data/lib/rlsl/glsl/translator.rb +19 -6
- data/lib/rlsl/msl/shader.rb +27 -9
- data/lib/rlsl/msl/translator.rb +25 -4
- data/lib/rlsl/msl/uniform_buffer_packer.rb +11 -7
- data/lib/rlsl/prism/ast_visitor/control_flow_visiting.rb +59 -18
- data/lib/rlsl/prism/ast_visitor/definition_visiting.rb +22 -9
- data/lib/rlsl/prism/ast_visitor/expression_visiting.rb +18 -14
- data/lib/rlsl/prism/ast_visitor/scope_context.rb +4 -0
- data/lib/rlsl/prism/ast_visitor.rb +78 -20
- data/lib/rlsl/prism/builtins/function_registry.rb +41 -24
- data/lib/rlsl/prism/builtins/operator_rules.rb +24 -0
- data/lib/rlsl/prism/builtins/swizzle_rules.rb +10 -1
- data/lib/rlsl/prism/builtins.rb +8 -0
- data/lib/rlsl/prism/emitters/base_emitter/control_flow_emission.rb +51 -8
- data/lib/rlsl/prism/emitters/base_emitter/definition_emission.rb +42 -12
- data/lib/rlsl/prism/emitters/base_emitter/expression_emission.rb +20 -4
- data/lib/rlsl/prism/emitters/base_emitter/statement_emission.rb +7 -1
- data/lib/rlsl/prism/emitters/base_emitter.rb +66 -5
- data/lib/rlsl/prism/emitters/c_emitter.rb +103 -7
- data/lib/rlsl/prism/emitters/glsl_emitter.rb +48 -0
- data/lib/rlsl/prism/emitters/msl_emitter.rb +43 -1
- data/lib/rlsl/prism/emitters/target_emitter.rb +56 -9
- data/lib/rlsl/prism/emitters/wgsl_emitter.rb +173 -18
- data/lib/rlsl/prism/errors.rb +9 -0
- data/lib/rlsl/prism/ir/control_flow.rb +6 -4
- data/lib/rlsl/prism/ir/definitions.rb +17 -2
- data/lib/rlsl/prism/ir/expressions.rb +5 -1
- data/lib/rlsl/prism/ir/node.rb +1 -1
- data/lib/rlsl/prism/ir/traversal.rb +6 -2
- data/lib/rlsl/prism/mutation_analyzer.rb +30 -0
- data/lib/rlsl/prism/node_traversal.rb +41 -0
- data/lib/rlsl/prism/parameter_list.rb +46 -0
- data/lib/rlsl/prism/return_flow_validator.rb +73 -0
- data/lib/rlsl/prism/source_extractor/block_locator.rb +23 -25
- data/lib/rlsl/prism/source_extractor.rb +7 -6
- data/lib/rlsl/prism/source_unit/parser.rb +42 -32
- data/lib/rlsl/prism/source_unit.rb +10 -10
- data/lib/rlsl/prism/target_capability_validator.rb +27 -2
- data/lib/rlsl/prism/transpiler.rb +40 -3
- data/lib/rlsl/prism/type_inference/call_type_resolver.rb +28 -4
- data/lib/rlsl/prism/type_inference/call_validator.rb +5 -1
- data/lib/rlsl/prism/type_inference/collection_type_resolver.rb +45 -19
- data/lib/rlsl/prism/type_inference/control_flow_inferer.rb +88 -7
- data/lib/rlsl/prism/type_inference/definition_inferer.rb +16 -1
- data/lib/rlsl/prism/type_inference/expression_inferer.rb +4 -0
- data/lib/rlsl/prism/type_inference/field_type_resolver.rb +23 -2
- data/lib/rlsl/prism/type_inference/scope_stack.rb +1 -1
- data/lib/rlsl/prism/type_inference/type_shapes.rb +3 -3
- data/lib/rlsl/prism/type_inference.rb +10 -5
- data/lib/rlsl/shader_builder/build_service.rb +34 -7
- data/lib/rlsl/shader_builder/native_extension_compiler.rb +50 -24
- data/lib/rlsl/shader_builder/shader_definition.rb +3 -3
- data/lib/rlsl/shader_builder/source_resolver.rb +19 -1
- data/lib/rlsl/shader_builder.rb +50 -10
- data/lib/rlsl/shader_name.rb +18 -0
- data/lib/rlsl/types/type_spec.rb +3 -3
- data/lib/rlsl/types/value_normalizer.rb +16 -11
- data/lib/rlsl/types.rb +0 -4
- data/lib/rlsl/uniform_context.rb +18 -1
- data/lib/rlsl/version.rb +1 -1
- data/lib/rlsl/wgsl/translator.rb +29 -6
- data/lib/rlsl/wgsl/uniform_layout.rb +25 -0
- data/lib/rlsl.rb +24 -3
- metadata +23 -11
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f67d6c410b44bdc05706df3bb9bd923d89484a779ff379a8015ff7a4003333a4
|
|
4
|
+
data.tar.gz: 9231a24a3a255afb8e6512af7cd6e9fa5201e5b5bdf2fad914370f0b843c80b2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e6fb3852aa2f2e8910cba85f5904e8fe1de204d07544f0289bd9fba0c68c185b848b163616e5c37f53184fedb4cb62dd456a61fe36793ce450ef40de04fd7116
|
|
7
|
+
data.tar.gz: c52f07944ea36ca72e95ee5ea6425d5e2b410cfee054dd9ccfd23053b9b42851248f7eb93f49bfa8f8899acc953590ba7240911dd4e9dd961752a86a30d73522
|
data/.rubocop.yml
ADDED
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
|
-
|
|
1
|
+
<h1 align="center">RLSL</h1>
|
|
2
2
|
|
|
3
|
-
Ruby Like Shading Language
|
|
3
|
+
<p align="center">Ruby Like Shading Language — write shaders in Ruby and transpile them to GPU and CPU targets.</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<a href="https://rubygems.org/gems/rlsl"><img src="https://badge.fury.io/rb/rlsl.svg" alt="Gem Version"></a>
|
|
7
|
+
<a href="https://rubygems.org/gems/rlsl"><img src="https://img.shields.io/gem/dt/rlsl?label=downloads" alt="Downloads"></a>
|
|
8
|
+
<a href="https://www.ruby-lang.org/"><img src="https://img.shields.io/badge/ruby-%3E%3D3.1-CC342D?logo=ruby&logoColor=white" alt="Ruby Version"></a>
|
|
9
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-750014.svg" alt="License"></a>
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
[Features](#features) · [Installation](#installation) · [Usage](#usage) · [Texture Resources](#texture-resources) · [Supported Types](#supported-types)
|
|
13
|
+
|
|
14
|
+
***
|
|
15
|
+
|
|
16
|
+
RLSL is a Ruby DSL for shader authoring with type inference and target-specific code generation. Its C target supports CPU rendering.
|
|
4
17
|
|
|
5
18
|
## Features
|
|
6
19
|
|
|
@@ -33,6 +46,30 @@ Or install it yourself as:
|
|
|
33
46
|
$ gem install rlsl
|
|
34
47
|
```
|
|
35
48
|
|
|
49
|
+
### Requirements
|
|
50
|
+
|
|
51
|
+
- Ruby >= 3.1.0
|
|
52
|
+
- [Prism](https://github.com/ruby/prism) >= 1.0.0 (for Ruby parsing)
|
|
53
|
+
- A working Ruby C-extension toolchain for `RLSL.define` (`make` is selected from Ruby's `RbConfig`)
|
|
54
|
+
|
|
55
|
+
The test matrix covers supported Ruby releases on Ubuntu, macOS, and Windows, and runs a dedicated compatibility job against the declared Prism 1.0.0 lower bound. Metal runtime execution is macOS-only. GLSL, WGSL, and MSL source generation is platform-independent.
|
|
56
|
+
|
|
57
|
+
### Optional: Metal Shader Execution (macOS only)
|
|
58
|
+
|
|
59
|
+
To run Metal shaders natively, install the [metaco](https://github.com/rbgfx/metaco) gem separately:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
$ gem install metaco
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Or add to your Gemfile:
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
gem "metaco", platforms: :ruby, install_if: -> { RUBY_PLATFORM.include?("darwin") }
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
MSL code generation (`RLSL.to_msl` or `RLSL.define_metal`) works without metaco. The gem is only required when calling `render_metal` at runtime.
|
|
72
|
+
|
|
36
73
|
## Usage
|
|
37
74
|
|
|
38
75
|
### Basic Example
|
|
@@ -82,7 +119,7 @@ end
|
|
|
82
119
|
### Generate MSL (Metal)
|
|
83
120
|
|
|
84
121
|
```ruby
|
|
85
|
-
|
|
122
|
+
msl_code = RLSL.to_msl(:my_shader) do
|
|
86
123
|
uniforms do
|
|
87
124
|
float :time
|
|
88
125
|
end
|
|
@@ -93,6 +130,8 @@ metal_shader = RLSL.define_metal(:my_shader) do
|
|
|
93
130
|
end
|
|
94
131
|
```
|
|
95
132
|
|
|
133
|
+
`RLSL.to_glsl`, `RLSL.to_wgsl`, and `RLSL.to_msl` return source strings. `RLSL.define` compiles the C target for CPU rendering, while `RLSL.define_metal` returns an `RLSL::MSL::Shader` for optional Metal execution.
|
|
134
|
+
|
|
96
135
|
### Using Helper Functions
|
|
97
136
|
|
|
98
137
|
```ruby
|
|
@@ -102,8 +141,8 @@ RLSL.to_glsl(:complex_shader) do
|
|
|
102
141
|
end
|
|
103
142
|
|
|
104
143
|
functions do
|
|
105
|
-
float :
|
|
106
|
-
vec3 :
|
|
144
|
+
define :noise, returns: :float, params: { p: :vec2 }
|
|
145
|
+
define :get_color, returns: :vec3, params: { uv: :vec2, t: :float }
|
|
107
146
|
end
|
|
108
147
|
|
|
109
148
|
helpers(:ruby) do
|
|
@@ -123,6 +162,53 @@ RLSL.to_glsl(:complex_shader) do
|
|
|
123
162
|
end
|
|
124
163
|
```
|
|
125
164
|
|
|
165
|
+
Every Ruby helper function needs a complete declaration in `functions`; parameter names and order must match the Ruby method definition. This prevents unknown parameters from silently becoming floats.
|
|
166
|
+
|
|
167
|
+
### Fragment Parameters and Explicit Source
|
|
168
|
+
|
|
169
|
+
Fragment parameters are positional. The first parameter is the fragment coordinate (`vec2`), the second is the resolution (`vec2`), and the third is the uniform object. Their Ruby names may be changed; generated target code uses the canonical names `frag_coord`, `resolution`, and `u`.
|
|
170
|
+
|
|
171
|
+
Ruby-mode blocks are captured from their source file when `fragment` or `helpers` is declared. They must come from a readable file, and multiple shader blocks must not start on the same source line. Code created by `eval`, `ruby -e`, or some REPLs has no readable source file. Once declared, later edits to the file do not change the captured shader source.
|
|
172
|
+
|
|
173
|
+
For generated code and REPL use, provide the Ruby source explicitly with `fragment_source` or `helpers_source`:
|
|
174
|
+
|
|
175
|
+
```ruby
|
|
176
|
+
builder = RLSL::ShaderBuilder.new(:generated_shader)
|
|
177
|
+
builder.fragment_source <<~RUBY
|
|
178
|
+
|coordinate, size, uniforms|
|
|
179
|
+
uv = coordinate / size
|
|
180
|
+
vec3(uv.x, uv.y, uniforms.time)
|
|
181
|
+
RUBY
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
A block with parameters is automatically treated as Ruby shader code. A no-argument block defaults to legacy C-source mode; use `fragment(:ruby) { vec3(1.0, 0.0, 0.0) }` to select Ruby mode explicitly. Raw helper or fragment source can be selected with `helpers(:c)` and `fragment(:c)`.
|
|
185
|
+
|
|
186
|
+
WGSL generation supports Ruby shader source only. Legacy C snippets can still be translated to C, GLSL, and MSL, but `to_wgsl` rejects them because C declarations and function syntax cannot be converted into valid WGSL by identifier rewriting alone.
|
|
187
|
+
|
|
188
|
+
Use `builder.uniform_types` to inspect declared uniform types. The block form of `uniforms` remains the declaration API.
|
|
189
|
+
|
|
190
|
+
Uniform names must be ASCII identifiers and may not use the RLSL-reserved names `resolution`, `frag_coord`, or `u`. Duplicate uniform declarations are rejected. The generated GLSL uniform block uses `std140` at binding 1.
|
|
191
|
+
|
|
192
|
+
### Texture Resources
|
|
193
|
+
|
|
194
|
+
`sampler2D` uniforms are source-generation resources for GLSL, WGSL, and MSL. Use `texture`, `texture2D`, or `textureLod` in Ruby shader code. They are not fields in the uniform buffer:
|
|
195
|
+
|
|
196
|
+
- GLSL reserves binding 0 for output and binding 1 for the uniform block; textures begin at binding 2.
|
|
197
|
+
- WGSL uses group 0 bindings 0 and 1 for the uniform buffer and output. Each texture/sampler pair then uses bindings 2/3, 4/5, and so on.
|
|
198
|
+
- MSL uses texture 0 for output, texture 1 onward for sampled textures, and buffer 0 for uniforms. The generated shader uses an internal linear sampler.
|
|
199
|
+
|
|
200
|
+
The C renderer does not bind `sampler2D` resources; attempts to use texture
|
|
201
|
+
functions on C are rejected with a target-capability error. Metal runtime
|
|
202
|
+
rendering accepts a `textures:` hash keyed by sampler name and binds each
|
|
203
|
+
texture in declaration order:
|
|
204
|
+
|
|
205
|
+
```ruby
|
|
206
|
+
shader.render_metal(handle, width, height, uniforms, textures: { albedo: texture })
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Texture-enabled source can also be emitted with `to_glsl`, `to_wgsl`, or
|
|
210
|
+
`to_msl` and bound by the host application.
|
|
211
|
+
|
|
126
212
|
## Supported Types
|
|
127
213
|
|
|
128
214
|
- `bool` - Boolean (conditional logic)
|
|
@@ -136,6 +222,8 @@ end
|
|
|
136
222
|
- `mat2` - 2x2 matrix (2D texture coordinate transformations)
|
|
137
223
|
- `sampler2D` - 2D texture sampler
|
|
138
224
|
|
|
225
|
+
Scalar and vector uniforms (`bool`, `int`, `float`, and `vec2` through `vec4`) are supported by the CPU and Metal runtime packers. Matrix and sampler uniforms are source-generation only.
|
|
226
|
+
|
|
139
227
|
## Built-in Functions
|
|
140
228
|
|
|
141
229
|
RLSL supports common shader functions:
|
|
@@ -150,37 +238,22 @@ RLSL supports common shader functions:
|
|
|
150
238
|
- `PI` - 3.14159265358979323846
|
|
151
239
|
- `TAU` - 6.28318530717958647692
|
|
152
240
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
- Ruby >= 3.1.0
|
|
156
|
-
- [Prism](https://github.com/ruby/prism) gem (for Ruby parsing)
|
|
157
|
-
|
|
158
|
-
### Optional: Metal Shader Execution (macOS only)
|
|
159
|
-
|
|
160
|
-
To run Metal shaders natively, install the [metaco](https://github.com/ydah/metaco) gem separately:
|
|
161
|
-
|
|
162
|
-
```bash
|
|
163
|
-
$ gem install metaco
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
Or add to your Gemfile:
|
|
167
|
-
|
|
168
|
-
```ruby
|
|
169
|
-
gem "metaco", platforms: :ruby, install_if: -> { RUBY_PLATFORM.include?("darwin") }
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
MSL code generation (`RLSL.define_metal`) works without metaco. The gem is only required when calling `render_metal` at runtime.
|
|
241
|
+
Ruby-style `Math::PI` and `Math::TAU` are accepted as aliases in shader source.
|
|
173
242
|
|
|
174
243
|
## Development
|
|
175
244
|
|
|
176
|
-
After checking out the repo, run `bundle install` to install dependencies. Then
|
|
245
|
+
After checking out the repo, run `bundle install` to install dependencies. Then run `rake verify` for lint and tests.
|
|
177
246
|
|
|
178
247
|
```bash
|
|
179
248
|
$ bundle install
|
|
180
|
-
$ rake
|
|
249
|
+
$ rake verify
|
|
181
250
|
```
|
|
182
251
|
|
|
183
|
-
For a quick local coverage summary, run `COVERAGE=1 rake test`.
|
|
252
|
+
For a quick local coverage summary, run `COVERAGE=1 rake test`. Set `COVERAGE_MIN=85` to enforce a minimum percentage. Target compiler integration tests use `glslangValidator`, Naga's `naga` CLI, and `xcrun metal` when those tools are installed; CI requires the GLSL and WGSL validators.
|
|
253
|
+
|
|
254
|
+
## Contributing
|
|
255
|
+
|
|
256
|
+
Bug reports and pull requests are welcome at [rbgfx/rlsl](https://github.com/rbgfx/rlsl).
|
|
184
257
|
|
|
185
258
|
## License
|
|
186
259
|
|
data/Rakefile
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "bundler/gem_tasks"
|
|
4
4
|
require "rake/testtask"
|
|
5
|
+
require "rubocop/rake_task"
|
|
5
6
|
|
|
6
7
|
Rake::TestTask.new(:test) do |t|
|
|
7
8
|
t.libs << "test"
|
|
@@ -10,3 +11,9 @@ Rake::TestTask.new(:test) do |t|
|
|
|
10
11
|
end
|
|
11
12
|
|
|
12
13
|
task default: :test
|
|
14
|
+
|
|
15
|
+
RuboCop::RakeTask.new(:lint) do |task|
|
|
16
|
+
task.options = ["--cache", "false", "--only", "Lint,Security", "lib"]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
task verify: %i[lint test]
|
|
@@ -12,7 +12,7 @@ module RLSL
|
|
|
12
12
|
char = segment[index]
|
|
13
13
|
|
|
14
14
|
if quote
|
|
15
|
-
quote = nil if char == quote && segment
|
|
15
|
+
quote = nil if char == quote && !escaped?(segment, index)
|
|
16
16
|
else
|
|
17
17
|
case char
|
|
18
18
|
when '"', "'"
|
|
@@ -39,7 +39,7 @@ module RLSL
|
|
|
39
39
|
|
|
40
40
|
arguments_source.each_char.with_index do |char, index|
|
|
41
41
|
if quote
|
|
42
|
-
quote = nil if char == quote && arguments_source
|
|
42
|
+
quote = nil if char == quote && !escaped?(arguments_source, index)
|
|
43
43
|
next
|
|
44
44
|
end
|
|
45
45
|
|
|
@@ -63,6 +63,17 @@ module RLSL
|
|
|
63
63
|
|
|
64
64
|
arguments << tail
|
|
65
65
|
end
|
|
66
|
+
|
|
67
|
+
def self.escaped?(source, index)
|
|
68
|
+
backslashes = 0
|
|
69
|
+
cursor = index - 1
|
|
70
|
+
while cursor >= 0 && source[cursor] == "\\"
|
|
71
|
+
backslashes += 1
|
|
72
|
+
cursor -= 1
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
backslashes.odd?
|
|
76
|
+
end
|
|
66
77
|
end
|
|
67
78
|
end
|
|
68
79
|
end
|
|
@@ -39,6 +39,11 @@ module RLSL
|
|
|
39
39
|
private
|
|
40
40
|
|
|
41
41
|
def read_preserved_segment(index)
|
|
42
|
+
if preprocessor_start?(index)
|
|
43
|
+
directive, next_index = read_preprocessor(index)
|
|
44
|
+
return [{ type: :preprocessor, text: directive }, next_index]
|
|
45
|
+
end
|
|
46
|
+
|
|
42
47
|
if line_comment_start?(index)
|
|
43
48
|
comment, next_index = read_line_comment(index)
|
|
44
49
|
return [{ type: :line_comment, text: comment }, next_index]
|
|
@@ -57,6 +62,28 @@ module RLSL
|
|
|
57
62
|
nil
|
|
58
63
|
end
|
|
59
64
|
|
|
65
|
+
def preprocessor_start?(index)
|
|
66
|
+
return false unless @code[index] == "#"
|
|
67
|
+
|
|
68
|
+
line_start = @code.rindex("\n", index - 1)
|
|
69
|
+
prefix = @code[(line_start ? line_start + 1 : 0)...index]
|
|
70
|
+
prefix.match?(/\A[ \t]*\z/)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def read_preprocessor(index)
|
|
74
|
+
cursor = index
|
|
75
|
+
|
|
76
|
+
loop do
|
|
77
|
+
newline_index = @code.index("\n", cursor)
|
|
78
|
+
return [@code[index..], @code.length] unless newline_index
|
|
79
|
+
|
|
80
|
+
line = @code[cursor...newline_index]
|
|
81
|
+
return [@code[index...newline_index], newline_index] unless line.rstrip.end_with?("\\")
|
|
82
|
+
|
|
83
|
+
cursor = newline_index + 1
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
60
87
|
def line_comment_start?(index)
|
|
61
88
|
@code[index, 2] == "//"
|
|
62
89
|
end
|
|
@@ -88,7 +115,7 @@ module RLSL
|
|
|
88
115
|
cursor = index + 1
|
|
89
116
|
|
|
90
117
|
while cursor < @code.length
|
|
91
|
-
if @code[cursor] == quote &&
|
|
118
|
+
if @code[cursor] == quote && !escaped?(cursor)
|
|
92
119
|
return [@code[index..cursor], cursor + 1]
|
|
93
120
|
end
|
|
94
121
|
|
|
@@ -97,6 +124,17 @@ module RLSL
|
|
|
97
124
|
|
|
98
125
|
raise ArgumentError, "Unterminated string literal in translation source"
|
|
99
126
|
end
|
|
127
|
+
|
|
128
|
+
def escaped?(index)
|
|
129
|
+
backslashes = 0
|
|
130
|
+
cursor = index - 1
|
|
131
|
+
while cursor >= 0 && @code[cursor] == "\\"
|
|
132
|
+
backslashes += 1
|
|
133
|
+
cursor -= 1
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
backslashes.odd?
|
|
137
|
+
end
|
|
100
138
|
end
|
|
101
139
|
end
|
|
102
140
|
end
|
data/lib/rlsl/base_translator.rb
CHANGED
|
@@ -28,8 +28,9 @@ module RLSL
|
|
|
28
28
|
|
|
29
29
|
REMOVED_IDENTIFIERS = %w[static inline].freeze
|
|
30
30
|
|
|
31
|
-
def initialize(uniforms, helpers_code, fragment_code)
|
|
31
|
+
def initialize(uniforms, helpers_code, fragment_code, name: nil)
|
|
32
32
|
@uniforms = uniforms
|
|
33
|
+
@shader_name = name&.to_s
|
|
33
34
|
@helpers_source = normalize_source(helpers_code)
|
|
34
35
|
@fragment_source = normalize_source(fragment_code)
|
|
35
36
|
end
|
|
@@ -45,11 +46,14 @@ module RLSL
|
|
|
45
46
|
def translate_code(source)
|
|
46
47
|
snippet = normalize_source(source)
|
|
47
48
|
return "" if snippet.code.empty?
|
|
49
|
+
validate_source_format!(snippet)
|
|
48
50
|
return snippet.code if snippet.target_code?
|
|
49
51
|
|
|
50
52
|
profile.translate(snippet.code)
|
|
51
53
|
end
|
|
52
54
|
|
|
55
|
+
def validate_source_format!(_source); end
|
|
56
|
+
|
|
53
57
|
def generate_shader(_helpers, _fragment)
|
|
54
58
|
raise NotImplementedError, "Subclasses must implement generate_shader"
|
|
55
59
|
end
|
|
@@ -117,12 +121,30 @@ module RLSL
|
|
|
117
121
|
|
|
118
122
|
def uniform_lines(resolution_line:, &block)
|
|
119
123
|
lines = [resolution_line]
|
|
120
|
-
|
|
124
|
+
value_uniforms.each do |name, type|
|
|
121
125
|
lines << block.call(name, uniform_type_to_target(type))
|
|
122
126
|
end
|
|
123
127
|
lines
|
|
124
128
|
end
|
|
125
129
|
|
|
130
|
+
def value_uniforms
|
|
131
|
+
@uniforms.reject { |_name, type| type.to_sym == :sampler2D }
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def texture_uniforms
|
|
135
|
+
@uniforms.select { |_name, type| type.to_sym == :sampler2D }
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def indent_source(source, spaces)
|
|
139
|
+
prefix = " " * spaces
|
|
140
|
+
source.to_s.lines.map { |line| line.strip.empty? ? line : "#{prefix}#{line}" }.join.rstrip
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def generated_by_comment(language)
|
|
144
|
+
suffix = @shader_name ? ": #{@shader_name}" : ""
|
|
145
|
+
"// #{language} shader generated by RLSL#{suffix}"
|
|
146
|
+
end
|
|
147
|
+
|
|
126
148
|
def profile
|
|
127
149
|
return self.class::PROFILE if self.class.const_defined?(:PROFILE, false)
|
|
128
150
|
|
|
@@ -10,33 +10,125 @@ module RLSL
|
|
|
10
10
|
static inline vec2 vec2_new(float x, float y) { return (vec2){x, y}; }
|
|
11
11
|
static inline vec3 vec3_new(float x, float y, float z) { return (vec3){x, y, z}; }
|
|
12
12
|
static inline vec4 vec4_new(float x, float y, float z, float w) { return (vec4){x, y, z, w}; }
|
|
13
|
+
static inline vec2 vec2_splat(float x) { return vec2_new(x, x); }
|
|
14
|
+
static inline vec3 vec3_splat(float x) { return vec3_new(x, x, x); }
|
|
15
|
+
static inline vec4 vec4_splat(float x) { return vec4_new(x, x, x, x); }
|
|
16
|
+
|
|
17
|
+
static inline vec2 vec2_from_vec2(vec2 v) { return v; }
|
|
18
|
+
static inline vec3 vec3_from_vec3(vec3 v) { return v; }
|
|
19
|
+
static inline vec4 vec4_from_vec4(vec4 v) { return v; }
|
|
20
|
+
static inline vec3 vec3_from_vec2_float(vec2 v, float z) { return vec3_new(v.x, v.y, z); }
|
|
21
|
+
static inline vec3 vec3_from_float_vec2(float x, vec2 v) { return vec3_new(x, v.x, v.y); }
|
|
22
|
+
static inline vec4 vec4_from_vec3_float(vec3 v, float w) { return vec4_new(v.x, v.y, v.z, w); }
|
|
23
|
+
static inline vec4 vec4_from_float_vec3(float x, vec3 v) { return vec4_new(x, v.x, v.y, v.z); }
|
|
24
|
+
static inline vec4 vec4_from_vec2_vec2(vec2 a, vec2 b) { return vec4_new(a.x, a.y, b.x, b.y); }
|
|
25
|
+
static inline vec4 vec4_from_vec2_float_float(vec2 v, float z, float w) { return vec4_new(v.x, v.y, z, w); }
|
|
26
|
+
static inline vec4 vec4_from_float_vec2_float(float x, vec2 v, float w) { return vec4_new(x, v.x, v.y, w); }
|
|
27
|
+
static inline vec4 vec4_from_float_float_vec2(float x, float y, vec2 v) { return vec4_new(x, y, v.x, v.y); }
|
|
28
|
+
|
|
29
|
+
static inline float vec2_component(vec2 v, int i) { return i == 0 ? v.x : v.y; }
|
|
30
|
+
static inline float vec3_component(vec3 v, int i) { return i == 0 ? v.x : (i == 1 ? v.y : v.z); }
|
|
31
|
+
static inline float vec4_component(vec4 v, int i) {
|
|
32
|
+
return i == 0 ? v.x : (i == 1 ? v.y : (i == 2 ? v.z : v.w));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
#define RLSL_DEFINE_SWIZZLES(type) \
|
|
36
|
+
static inline vec2 type##_swizzle2(type v, int a, int b) { \
|
|
37
|
+
return vec2_new(type##_component(v, a), type##_component(v, b)); \
|
|
38
|
+
} \
|
|
39
|
+
static inline vec3 type##_swizzle3(type v, int a, int b, int c) { \
|
|
40
|
+
return vec3_new(type##_component(v, a), type##_component(v, b), type##_component(v, c)); \
|
|
41
|
+
} \
|
|
42
|
+
static inline vec4 type##_swizzle4(type v, int a, int b, int c, int d) { \
|
|
43
|
+
return vec4_new(type##_component(v, a), type##_component(v, b), type##_component(v, c), type##_component(v, d)); \
|
|
44
|
+
}
|
|
45
|
+
RLSL_DEFINE_SWIZZLES(vec2)
|
|
46
|
+
RLSL_DEFINE_SWIZZLES(vec3)
|
|
47
|
+
RLSL_DEFINE_SWIZZLES(vec4)
|
|
48
|
+
#undef RLSL_DEFINE_SWIZZLES
|
|
13
49
|
|
|
14
50
|
static inline vec2 vec2_add(vec2 a, vec2 b) { return (vec2){a.x + b.x, a.y + b.y}; }
|
|
15
51
|
static inline vec3 vec3_add(vec3 a, vec3 b) { return (vec3){a.x + b.x, a.y + b.y, a.z + b.z}; }
|
|
52
|
+
static inline vec4 vec4_add(vec4 a, vec4 b) { return (vec4){a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w}; }
|
|
16
53
|
|
|
17
54
|
static inline vec2 vec2_sub(vec2 a, vec2 b) { return (vec2){a.x - b.x, a.y - b.y}; }
|
|
18
55
|
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
|
-
|
|
21
|
-
static inline
|
|
22
|
-
|
|
23
|
-
static inline
|
|
24
|
-
static inline
|
|
56
|
+
static inline vec4 vec4_sub(vec4 a, vec4 b) { return (vec4){a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w}; }
|
|
57
|
+
|
|
58
|
+
static inline vec2 vec2_add_scalar(vec2 a, float s) { return (vec2){a.x + s, a.y + s}; }
|
|
59
|
+
static inline vec3 vec3_add_scalar(vec3 a, float s) { return (vec3){a.x + s, a.y + s, a.z + s}; }
|
|
60
|
+
static inline vec4 vec4_add_scalar(vec4 a, float s) { return (vec4){a.x + s, a.y + s, a.z + s, a.w + s}; }
|
|
61
|
+
static inline vec2 vec2_scalar_add(float s, vec2 a) { return vec2_add_scalar(a, s); }
|
|
62
|
+
static inline vec3 vec3_scalar_add(float s, vec3 a) { return vec3_add_scalar(a, s); }
|
|
63
|
+
static inline vec4 vec4_scalar_add(float s, vec4 a) { return vec4_add_scalar(a, s); }
|
|
64
|
+
|
|
65
|
+
static inline vec2 vec2_sub_scalar(vec2 a, float s) { return (vec2){a.x - s, a.y - s}; }
|
|
66
|
+
static inline vec3 vec3_sub_scalar(vec3 a, float s) { return (vec3){a.x - s, a.y - s, a.z - s}; }
|
|
67
|
+
static inline vec4 vec4_sub_scalar(vec4 a, float s) { return (vec4){a.x - s, a.y - s, a.z - s, a.w - s}; }
|
|
68
|
+
static inline vec2 vec2_scalar_sub(float s, vec2 a) { return (vec2){s - a.x, s - a.y}; }
|
|
69
|
+
static inline vec3 vec3_scalar_sub(float s, vec3 a) { return (vec3){s - a.x, s - a.y, s - a.z}; }
|
|
70
|
+
static inline vec4 vec4_scalar_sub(float s, vec4 a) { return (vec4){s - a.x, s - a.y, s - a.z, s - a.w}; }
|
|
71
|
+
|
|
72
|
+
static inline vec2 vec2_mul_scalar(vec2 a, float s) { return (vec2){a.x * s, a.y * s}; }
|
|
73
|
+
static inline vec3 vec3_mul_scalar(vec3 a, float s) { return (vec3){a.x * s, a.y * s, a.z * s}; }
|
|
74
|
+
static inline vec4 vec4_mul_scalar(vec4 a, float s) { return (vec4){a.x * s, a.y * s, a.z * s, a.w * s}; }
|
|
75
|
+
static inline vec2 vec2_scalar_mul(float s, vec2 a) { return vec2_mul_scalar(a, s); }
|
|
76
|
+
static inline vec3 vec3_scalar_mul(float s, vec3 a) { return vec3_mul_scalar(a, s); }
|
|
77
|
+
static inline vec4 vec4_scalar_mul(float s, vec4 a) { return vec4_mul_scalar(a, s); }
|
|
78
|
+
static inline vec2 vec2_mul_components(vec2 a, vec2 b) { return (vec2){a.x * b.x, a.y * b.y}; }
|
|
79
|
+
static inline vec3 vec3_mul_components(vec3 a, vec3 b) { return (vec3){a.x * b.x, a.y * b.y, a.z * b.z}; }
|
|
80
|
+
static inline vec4 vec4_mul_components(vec4 a, vec4 b) { return (vec4){a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w}; }
|
|
81
|
+
|
|
82
|
+
static inline vec2 vec2_div_scalar(vec2 a, float s) { return (vec2){a.x / s, a.y / s}; }
|
|
83
|
+
static inline vec3 vec3_div_scalar(vec3 a, float s) { return (vec3){a.x / s, a.y / s, a.z / s}; }
|
|
84
|
+
static inline vec4 vec4_div_scalar(vec4 a, float s) { return (vec4){a.x / s, a.y / s, a.z / s, a.w / s}; }
|
|
85
|
+
static inline vec2 vec2_scalar_div(float s, vec2 a) { return (vec2){s / a.x, s / a.y}; }
|
|
86
|
+
static inline vec3 vec3_scalar_div(float s, vec3 a) { return (vec3){s / a.x, s / a.y, s / a.z}; }
|
|
87
|
+
static inline vec4 vec4_scalar_div(float s, vec4 a) { return (vec4){s / a.x, s / a.y, s / a.z, s / a.w}; }
|
|
88
|
+
static inline vec2 vec2_div_components(vec2 a, vec2 b) { return (vec2){a.x / b.x, a.y / b.y}; }
|
|
89
|
+
static inline vec3 vec3_div_components(vec3 a, vec3 b) { return (vec3){a.x / b.x, a.y / b.y, a.z / b.z}; }
|
|
90
|
+
static inline vec4 vec4_div_components(vec4 a, vec4 b) { return (vec4){a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w}; }
|
|
91
|
+
|
|
92
|
+
/* Legacy names used by raw C fragments. */
|
|
93
|
+
static inline vec2 vec2_mul(vec2 a, float s) { return vec2_mul_scalar(a, s); }
|
|
94
|
+
static inline vec3 vec3_mul(vec3 a, float s) { return vec3_mul_scalar(a, s); }
|
|
95
|
+
static inline vec2 vec2_div(vec2 a, float s) { return vec2_div_scalar(a, s); }
|
|
96
|
+
static inline vec3 vec3_div(vec3 a, float s) { return vec3_div_scalar(a, s); }
|
|
25
97
|
|
|
26
98
|
static inline float vec2_dot(vec2 a, vec2 b) { return a.x * b.x + a.y * b.y; }
|
|
27
99
|
static inline float vec3_dot(vec3 a, vec3 b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
|
|
100
|
+
static inline float vec4_dot(vec4 a, vec4 b) { return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; }
|
|
101
|
+
|
|
102
|
+
static inline vec3 vec3_cross(vec3 a, vec3 b) {
|
|
103
|
+
return vec3_new(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x);
|
|
104
|
+
}
|
|
28
105
|
|
|
29
106
|
static inline float vec2_length(vec2 v) { return sqrtf(v.x * v.x + v.y * v.y); }
|
|
30
107
|
static inline float vec3_length(vec3 v) { return sqrtf(v.x * v.x + v.y * v.y + v.z * v.z); }
|
|
108
|
+
static inline float vec4_length(vec4 v) { return sqrtf(vec4_dot(v, v)); }
|
|
109
|
+
|
|
110
|
+
static inline float vec2_distance(vec2 a, vec2 b) { return vec2_length(vec2_sub(a, b)); }
|
|
111
|
+
static inline float vec3_distance(vec3 a, vec3 b) { return vec3_length(vec3_sub(a, b)); }
|
|
112
|
+
static inline float vec4_distance(vec4 a, vec4 b) { return vec4_length(vec4_sub(a, b)); }
|
|
31
113
|
|
|
32
|
-
static inline vec2 vec2_normalize(vec2 v) { float l = vec2_length(v); return l > 0 ?
|
|
33
|
-
static inline vec3 vec3_normalize(vec3 v) { float l = vec3_length(v); return l > 0 ?
|
|
114
|
+
static inline vec2 vec2_normalize(vec2 v) { float l = vec2_length(v); return l > 0 ? vec2_div_scalar(v, l) : v; }
|
|
115
|
+
static inline vec3 vec3_normalize(vec3 v) { float l = vec3_length(v); return l > 0 ? vec3_div_scalar(v, l) : v; }
|
|
116
|
+
static inline vec4 vec4_normalize(vec4 v) { float l = vec4_length(v); return l > 0 ? vec4_div_scalar(v, l) : v; }
|
|
34
117
|
|
|
35
118
|
static inline float fract(float x) { return x - floorf(x); }
|
|
119
|
+
static inline float rlsl_mod(float x, float y) { return x - y * floorf(x / y); }
|
|
120
|
+
static inline float sign_f(float x) { return (x > 0.0f) - (x < 0.0f); }
|
|
121
|
+
static inline float step_f(float edge, float x) { return x < edge ? 0.0f : 1.0f; }
|
|
36
122
|
static inline float mix_f(float a, float b, float t) { return a + (b - a) * t; }
|
|
123
|
+
static inline vec2 mix_v2(vec2 a, vec2 b, float t) {
|
|
124
|
+
return (vec2){a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t};
|
|
125
|
+
}
|
|
37
126
|
static inline vec3 mix_v3(vec3 a, vec3 b, float t) {
|
|
38
127
|
return (vec3){a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t};
|
|
39
128
|
}
|
|
129
|
+
static inline vec4 mix_v4(vec4 a, vec4 b, float t) {
|
|
130
|
+
return (vec4){a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t, a.w + (b.w - a.w) * t};
|
|
131
|
+
}
|
|
40
132
|
|
|
41
133
|
static inline float clamp_f(float x, float lo, float hi) {
|
|
42
134
|
return x < lo ? lo : (x > hi ? hi : x);
|
|
@@ -53,8 +145,9 @@ module RLSL
|
|
|
53
145
|
}
|
|
54
146
|
|
|
55
147
|
static inline vec2 hash22(vec2 p) {
|
|
56
|
-
float
|
|
57
|
-
|
|
148
|
+
float x = sinf(vec2_dot(p, vec2_new(127.1f, 311.7f)));
|
|
149
|
+
float y = sinf(vec2_dot(p, vec2_new(269.5f, 183.3f)));
|
|
150
|
+
return vec2_new(fract(x * 43758.5453f), fract(y * 43758.5453f));
|
|
58
151
|
}
|
|
59
152
|
|
|
60
153
|
static inline vec3 reflect(vec3 I, vec3 N) {
|
|
@@ -20,7 +20,7 @@ module RLSL
|
|
|
20
20
|
for (int x = 0; x < width; x++) {
|
|
21
21
|
vec2 frag_coord = vec2_new((float)x, (float)flipped_y);
|
|
22
22
|
vec3 color = shader_#{@context.name}(frag_coord, resolution, uniforms);
|
|
23
|
-
|
|
23
|
+
size_t idx = ((size_t)y * (size_t)width + (size_t)x) * 4;
|
|
24
24
|
// Output as BGRA (macOS native format)
|
|
25
25
|
pixels[idx] = (uint8_t)(clamp_f(color.z, 0.0f, 1.0f) * 255.0f);
|
|
26
26
|
pixels[idx+1] = (uint8_t)(clamp_f(color.y, 0.0f, 1.0f) * 255.0f);
|
|
@@ -29,9 +29,49 @@ module RLSL
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
typedef struct {
|
|
33
|
+
uint8_t *pixels;
|
|
34
|
+
int width;
|
|
35
|
+
int height;
|
|
36
|
+
vec2 resolution;
|
|
37
|
+
Uniforms uniforms;
|
|
38
|
+
} render_arguments;
|
|
39
|
+
|
|
40
|
+
static void *render_without_gvl(void *opaque) {
|
|
41
|
+
render_arguments *arguments = (render_arguments *)opaque;
|
|
42
|
+
|
|
43
|
+
#ifdef __APPLE__
|
|
44
|
+
dispatch_apply(arguments->height, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t y) {
|
|
45
|
+
render_scanline(arguments->pixels, arguments->width, arguments->height, (int)y,
|
|
46
|
+
arguments->resolution, arguments->uniforms);
|
|
47
|
+
});
|
|
48
|
+
#else
|
|
49
|
+
for (int y = 0; y < arguments->height; y++) {
|
|
50
|
+
render_scanline(arguments->pixels, arguments->width, arguments->height, y,
|
|
51
|
+
arguments->resolution, arguments->uniforms);
|
|
52
|
+
}
|
|
53
|
+
#endif
|
|
54
|
+
|
|
55
|
+
return NULL;
|
|
56
|
+
}
|
|
57
|
+
|
|
32
58
|
static VALUE shader_#{@context.name}_render(VALUE self, VALUE rb_buffer, VALUE rb_width, VALUE rb_height#{@context.uniform_argument_list}) {
|
|
33
|
-
|
|
34
|
-
|
|
59
|
+
long long requested_width = NUM2LL(rb_width);
|
|
60
|
+
long long requested_height = NUM2LL(rb_height);
|
|
61
|
+
if (requested_width <= 0 || requested_width > INT_MAX ||
|
|
62
|
+
requested_height <= 0 || requested_height > INT_MAX) {
|
|
63
|
+
rb_raise(rb_eArgError, "width and height must be positive integers no greater than INT_MAX");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
size_t width_size = (size_t)requested_width;
|
|
67
|
+
size_t height_size = (size_t)requested_height;
|
|
68
|
+
if (height_size > SIZE_MAX / width_size || width_size * height_size > SIZE_MAX / 4) {
|
|
69
|
+
rb_raise(rb_eRangeError, "render dimensions overflow the output buffer size");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
size_t required_bytes = width_size * height_size * 4;
|
|
73
|
+
int width = (int)requested_width;
|
|
74
|
+
int height = (int)requested_height;
|
|
35
75
|
vec2 resolution = vec2_new((float)width, (float)height);
|
|
36
76
|
|
|
37
77
|
Uniforms uniforms;
|
|
@@ -39,17 +79,14 @@ module RLSL
|
|
|
39
79
|
|
|
40
80
|
Check_Type(rb_buffer, T_STRING);
|
|
41
81
|
rb_str_modify(rb_buffer);
|
|
82
|
+
if ((size_t)RSTRING_LEN(rb_buffer) < required_bytes) {
|
|
83
|
+
rb_raise(rb_eArgError, "pixel buffer is too small: need %zu bytes, got %ld",
|
|
84
|
+
required_bytes, RSTRING_LEN(rb_buffer));
|
|
85
|
+
}
|
|
42
86
|
uint8_t *pixels = (uint8_t *)RSTRING_PTR(rb_buffer);
|
|
43
87
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
render_scanline(pixels, width, height, (int)y, resolution, uniforms);
|
|
47
|
-
});
|
|
48
|
-
#else
|
|
49
|
-
for (int y = 0; y < height; y++) {
|
|
50
|
-
render_scanline(pixels, width, height, y, resolution, uniforms);
|
|
51
|
-
}
|
|
52
|
-
#endif
|
|
88
|
+
render_arguments arguments = {pixels, width, height, resolution, uniforms};
|
|
89
|
+
rb_thread_call_without_gvl(render_without_gvl, &arguments, RUBY_UBF_IO, NULL);
|
|
53
90
|
|
|
54
91
|
return Qnil;
|
|
55
92
|
}
|
|
@@ -87,6 +124,9 @@ module RLSL
|
|
|
87
124
|
|
|
88
125
|
<<~C.strip
|
|
89
126
|
Check_Type(rb_#{uniform_name}, T_ARRAY);
|
|
127
|
+
if (RARRAY_LEN(rb_#{uniform_name}) != #{vector_size}) {
|
|
128
|
+
rb_raise(rb_eArgError, "uniform #{uniform_name} must contain exactly #{vector_size} components");
|
|
129
|
+
}
|
|
90
130
|
uniforms.#{uniform_name} = #{constructor}(
|
|
91
131
|
#{components.join(",\n")}
|
|
92
132
|
);
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
module RLSL
|
|
4
4
|
class CodeGenerator
|
|
5
5
|
class TemplateContext
|
|
6
|
-
attr_reader :name
|
|
6
|
+
attr_reader :name, :extension_name
|
|
7
7
|
|
|
8
|
-
def initialize(name:, uniforms:, helpers_block:, fragment_block:)
|
|
9
|
-
@name = name
|
|
8
|
+
def initialize(name:, uniforms:, helpers_block:, fragment_block:, extension_name: name)
|
|
9
|
+
@name = RLSL.validate_shader_name!(name)
|
|
10
|
+
@extension_name = RLSL.validate_identifier!(extension_name, context: "extension name")
|
|
10
11
|
@uniforms = uniforms
|
|
11
12
|
@helpers_block = helpers_block
|
|
12
13
|
@fragment_block = fragment_block
|
|
@@ -23,6 +24,8 @@ module RLSL
|
|
|
23
24
|
end
|
|
24
25
|
|
|
25
26
|
def fragment_code
|
|
27
|
+
return "" unless @fragment_block
|
|
28
|
+
|
|
26
29
|
@fragment_block.call
|
|
27
30
|
end
|
|
28
31
|
|