rlsl 0.1.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.
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RLSL
4
+ module GLSL
5
+ class Translator < BaseTranslator
6
+ TYPE_MAP = {
7
+ "vec2" => "vec2",
8
+ "vec3" => "vec3",
9
+ "vec4" => "vec4"
10
+ }.freeze
11
+
12
+ FUNC_REPLACEMENTS = BaseTranslator.common_func_replacements(
13
+ target_vec2: "vec2",
14
+ target_vec3: "vec3",
15
+ target_vec4: "vec4"
16
+ ).freeze
17
+
18
+ def initialize(uniforms, helpers_code, fragment_code, version: "450")
19
+ super(uniforms, helpers_code, fragment_code)
20
+ @version = version
21
+ end
22
+
23
+ protected
24
+
25
+ def translate_code(c_code)
26
+ result = super(c_code)
27
+ return result if result.empty?
28
+
29
+ result.gsub!(/\bstatic\s+/, "")
30
+ result.gsub!(/\binline\s+/, "")
31
+ result
32
+ end
33
+
34
+ def generate_shader(helpers, fragment)
35
+ <<~GLSL
36
+ #version #{@version}
37
+
38
+ // Uniforms
39
+ #{generate_uniform_declarations}
40
+
41
+ // Output
42
+ layout(rgba8, binding = 0) uniform writeonly image2D outputImage;
43
+
44
+ #{helpers}
45
+
46
+ vec3 shader_fragment(vec2 frag_coord, vec2 resolution) {
47
+ vec2 uv = frag_coord / resolution.y;
48
+ #{fragment}
49
+ }
50
+
51
+ layout(local_size_x = 8, local_size_y = 8) in;
52
+ void main() {
53
+ ivec2 texSize = imageSize(outputImage);
54
+ vec2 resolution = vec2(float(texSize.x), float(texSize.y));
55
+
56
+ if (gl_GlobalInvocationID.x >= uint(resolution.x) ||
57
+ gl_GlobalInvocationID.y >= uint(resolution.y)) {
58
+ return;
59
+ }
60
+
61
+ vec2 frag_coord = vec2(float(gl_GlobalInvocationID.x),
62
+ resolution.y - 1.0 - float(gl_GlobalInvocationID.y));
63
+ vec3 color = shader_fragment(frag_coord, resolution);
64
+
65
+ imageStore(outputImage, ivec2(gl_GlobalInvocationID.xy),
66
+ vec4(clamp(color, 0.0, 1.0), 1.0));
67
+ }
68
+ GLSL
69
+ end
70
+
71
+ private
72
+
73
+ def generate_uniform_declarations
74
+ declarations = ["layout(binding = 1) uniform ShaderUniforms {",
75
+ " vec2 resolution;"]
76
+ @uniforms.each do |name, type|
77
+ glsl_type = uniform_type_to_target(type)
78
+ declarations << " #{glsl_type} #{name};"
79
+ end
80
+ declarations << "} u;"
81
+ declarations.join("\n")
82
+ end
83
+
84
+ def target_vec2_type
85
+ "vec2"
86
+ end
87
+
88
+ def target_vec3_type
89
+ "vec3"
90
+ end
91
+
92
+ def target_vec4_type
93
+ "vec4"
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require "metaco"
5
+ METACO_AVAILABLE = true
6
+ rescue LoadError
7
+ METACO_AVAILABLE = false
8
+ end
9
+
10
+ module RLSL
11
+ module MSL
12
+ class Shader
13
+ attr_reader :name, :msl_source
14
+
15
+ def initialize(name, uniforms, msl_source)
16
+ @name = name
17
+ @uniform_types = uniforms
18
+ @uniform_names = uniforms.keys
19
+ @msl_source = msl_source
20
+ @compiled_handles = {}
21
+ end
22
+
23
+ def metal?
24
+ true
25
+ end
26
+
27
+ def render_metal(handle, width, height, uniforms = {})
28
+ unless METACO_AVAILABLE
29
+ raise LoadError, "metaco gem is required for Metal rendering. Install it with: gem install metaco"
30
+ end
31
+
32
+ unless @compiled_handles[handle]
33
+ Metaco.compile_compute_shader(handle, @msl_source)
34
+ @compiled_handles[handle] = true
35
+ end
36
+
37
+ uniform_data = pack_uniforms(uniforms, width, height)
38
+
39
+ Metaco.dispatch_compute(handle, uniform_data)
40
+ Metaco.present_compute(handle)
41
+ end
42
+
43
+ private
44
+
45
+ def pack_uniforms(uniforms, width, height)
46
+ data = [width.to_f, height.to_f].pack("ff")
47
+ current_offset = 8
48
+
49
+ @uniform_names.each do |name|
50
+ value = uniforms[name]
51
+ type = @uniform_types[name]
52
+
53
+ alignment = case type
54
+ when :float then 4
55
+ when :vec2 then 8
56
+ when :vec3, :vec4 then 16
57
+ end
58
+
59
+ padding_needed = (alignment - (current_offset % alignment)) % alignment
60
+ data += "\x00" * padding_needed
61
+ current_offset += padding_needed
62
+
63
+ case type
64
+ when :float
65
+ data += [value.to_f].pack("f")
66
+ current_offset += 4
67
+ when :vec2
68
+ data += value.pack("ff")
69
+ current_offset += 8
70
+ when :vec3
71
+ data += (value + [0.0]).pack("ffff")
72
+ current_offset += 16
73
+ when :vec4
74
+ data += value.pack("ffff")
75
+ current_offset += 16
76
+ end
77
+ end
78
+
79
+ data.ljust(256, "\x00")
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RLSL
4
+ module MSL
5
+ class Translator < BaseTranslator
6
+ TYPE_MAP = {
7
+ "vec2" => "float2",
8
+ "vec3" => "float3",
9
+ "vec4" => "float4"
10
+ }.freeze
11
+
12
+ FUNC_REPLACEMENTS = BaseTranslator.common_func_replacements(
13
+ target_vec2: "float2",
14
+ target_vec3: "float3",
15
+ target_vec4: "float4"
16
+ ).freeze
17
+
18
+ protected
19
+
20
+ def translate_code(c_code)
21
+ result = super(c_code)
22
+ return result if result.empty?
23
+
24
+ result.gsub!(/\bstatic\s+/, "")
25
+ result.gsub!(/\binline\s+/, "")
26
+ result
27
+ end
28
+
29
+ def generate_shader(helpers, fragment)
30
+ <<~MSL
31
+ #include <metal_stdlib>
32
+ using namespace metal;
33
+
34
+ // Uniform buffer structure
35
+ struct Uniforms {
36
+ #{generate_uniform_struct}
37
+ };
38
+
39
+ // Helper functions
40
+ #{helpers}
41
+
42
+ // Fragment shader function
43
+ float3 shader_fragment(float2 frag_coord, float2 resolution, constant Uniforms& u) {
44
+ float2 uv = frag_coord / resolution.y;
45
+ #{fragment}
46
+ }
47
+
48
+ // Compute kernel entry point
49
+ kernel void compute_shader(
50
+ texture2d<float, access::write> output [[texture(0)]],
51
+ constant Uniforms& u [[buffer(0)]],
52
+ uint2 gid [[thread_position_in_grid]]
53
+ ) {
54
+ float2 resolution = float2(output.get_width(), output.get_height());
55
+ if (gid.x >= uint(resolution.x) || gid.y >= uint(resolution.y)) return;
56
+
57
+ float2 frag_coord = float2(gid.x, resolution.y - 1.0 - float(gid.y));
58
+ float3 color = shader_fragment(frag_coord, resolution, u);
59
+
60
+ output.write(float4(clamp(color, 0.0, 1.0), 1.0), gid);
61
+ }
62
+ MSL
63
+ end
64
+
65
+ private
66
+
67
+ def generate_uniform_struct
68
+ fields = ["float2 resolution;"]
69
+ @uniforms.each do |name, type|
70
+ msl_type = uniform_type_to_target(type)
71
+ fields << "#{msl_type} #{name};"
72
+ end
73
+ fields.join("\n ")
74
+ end
75
+
76
+ def target_vec2_type
77
+ "float2"
78
+ end
79
+
80
+ def target_vec3_type
81
+ "float3"
82
+ end
83
+
84
+ def target_vec4_type
85
+ "float4"
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,371 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+ require "set"
5
+
6
+ module RLSL
7
+ module Prism
8
+ class ASTVisitor
9
+ BINARY_OPERATORS = %w[+ - * / % == != < > <= >= && ||].freeze
10
+ UNARY_OPERATORS = %w[- !].freeze
11
+
12
+ def initialize(context = {})
13
+ @context = context
14
+ @uniforms = context[:uniforms] || {}
15
+ @params = Set.new(context[:params] || [])
16
+ @declared_vars = Set.new
17
+ end
18
+
19
+ def parse(source)
20
+ result = ::Prism.parse(source)
21
+
22
+ unless result.success?
23
+ errors = result.errors.map(&:message).join(", ")
24
+ raise "Parse error: #{errors}"
25
+ end
26
+
27
+ program = result.value
28
+ visit(program)
29
+ end
30
+
31
+ def visit(node)
32
+ return nil if node.nil?
33
+
34
+ method_name = "visit_#{node_type(node)}"
35
+ if respond_to?(method_name, true)
36
+ send(method_name, node)
37
+ else
38
+ visit_default(node)
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def node_type(node)
45
+ node.class.name.split("::").last
46
+ .gsub(/Node$/, "")
47
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
48
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
49
+ .downcase
50
+ end
51
+
52
+ def visit_default(node)
53
+ children = []
54
+ node.child_nodes.compact.each do |child|
55
+ result = visit(child)
56
+ children << result if result
57
+ end
58
+ children.length == 1 ? children.first : children
59
+ end
60
+
61
+ def visit_program(node)
62
+ visit(node.statements)
63
+ end
64
+
65
+ def visit_statements(node)
66
+ statements = node.body.map { |stmt| visit(stmt) }.compact.flatten
67
+ IR::Block.new(statements)
68
+ end
69
+
70
+ def visit_block(node)
71
+ if node.parameters
72
+ node.parameters.parameters&.requireds&.each do |param|
73
+ @params.add(param.name.to_sym)
74
+ end
75
+ end
76
+
77
+ visit(node.body)
78
+ end
79
+
80
+ def visit_lambda(node)
81
+ visit_block(node)
82
+ end
83
+
84
+ def visit_local_variable_write(node)
85
+ name = node.name.to_sym
86
+ value = visit(node.value)
87
+
88
+ if @declared_vars.include?(name) || @params.include?(name)
89
+ IR::Assignment.new(IR::VarRef.new(name), value)
90
+ else
91
+ @declared_vars.add(name)
92
+ IR::VarDecl.new(name, value)
93
+ end
94
+ end
95
+
96
+ def visit_local_variable_read(node)
97
+ name = node.name.to_sym
98
+ type = infer_param_type(name)
99
+ IR::VarRef.new(name, type)
100
+ end
101
+
102
+ def visit_integer(node)
103
+ IR::Literal.new(node.value.to_f, :float)
104
+ end
105
+
106
+ def visit_float(node)
107
+ IR::Literal.new(node.value, :float)
108
+ end
109
+
110
+ def visit_rational(node)
111
+ IR::Literal.new(node.value.to_f, :float)
112
+ end
113
+
114
+ def visit_true(node)
115
+ IR::BoolLiteral.new(true)
116
+ end
117
+
118
+ def visit_false(node)
119
+ IR::BoolLiteral.new(false)
120
+ end
121
+
122
+ def visit_parentheses(node)
123
+ inner = visit(node.body)
124
+ if inner.is_a?(IR::Block) && inner.statements.length == 1
125
+ inner = inner.statements.first
126
+ end
127
+ IR::Parenthesized.new(inner)
128
+ end
129
+
130
+ def visit_call(node)
131
+ method_name = node.name.to_s
132
+ receiver = visit(node.receiver) if node.receiver
133
+ args = node.arguments&.arguments&.map { |arg| visit(arg) } || []
134
+
135
+ if !receiver && args.empty? && @params.include?(method_name.to_sym)
136
+ type = infer_param_type(method_name.to_sym)
137
+ return IR::VarRef.new(method_name.to_sym, type)
138
+ end
139
+
140
+ if receiver && args.empty? && !node.arguments
141
+ if Builtins.single_component_field?(method_name)
142
+ return IR::FieldAccess.new(receiver, method_name, :float)
143
+ elsif Builtins.swizzle?(method_name)
144
+ type = Builtins.swizzle_type(method_name)
145
+ return IR::Swizzle.new(receiver, method_name, type)
146
+ else
147
+ return IR::FieldAccess.new(receiver, method_name)
148
+ end
149
+ end
150
+
151
+ if BINARY_OPERATORS.include?(method_name) && receiver && args.length == 1
152
+ return IR::BinaryOp.new(method_name, receiver, args.first)
153
+ end
154
+
155
+ if method_name == "-@" && receiver
156
+ return IR::UnaryOp.new("-", receiver)
157
+ end
158
+ if method_name == "!" && args.length == 1
159
+ return IR::UnaryOp.new("!", args.first)
160
+ end
161
+
162
+ if method_name == "[]" && receiver && args.length == 1
163
+ return IR::ArrayIndex.new(receiver, args.first)
164
+ end
165
+
166
+ IR::FuncCall.new(method_name.to_sym, args, receiver)
167
+ end
168
+
169
+ def visit_if(node)
170
+ condition = visit(node.predicate)
171
+ then_branch = visit_with_scoped_vars(node.statements)
172
+ else_branch = node.subsequent ? visit_with_scoped_vars(node.subsequent) : nil
173
+
174
+ IR::IfStatement.new(condition, then_branch, else_branch)
175
+ end
176
+
177
+ def visit_with_scoped_vars(node)
178
+ saved_vars = @declared_vars.dup
179
+ result = visit(node)
180
+ @declared_vars = saved_vars
181
+ result
182
+ end
183
+
184
+ def visit_else(node)
185
+ visit(node.statements)
186
+ end
187
+
188
+ def visit_elsif(node)
189
+ visit_if(node)
190
+ end
191
+
192
+ def visit_if_node(node)
193
+ visit_if(node)
194
+ end
195
+
196
+ def visit_unless(node)
197
+ condition = IR::UnaryOp.new("!", visit(node.predicate))
198
+ then_branch = visit(node.statements)
199
+ else_branch = node.else_clause ? visit(node.else_clause) : nil
200
+
201
+ IR::IfStatement.new(condition, then_branch, else_branch)
202
+ end
203
+
204
+ def visit_return(node)
205
+ expr = node.arguments ? visit(node.arguments.arguments.first) : nil
206
+ IR::Return.new(expr)
207
+ end
208
+
209
+ def visit_range(node)
210
+ [visit(node.left), visit(node.right)]
211
+ end
212
+
213
+ def visit_for(node)
214
+ var_name = node.index.name.to_sym
215
+ range = visit(node.collection)
216
+ body = visit(node.statements)
217
+
218
+ IR::ForLoop.new(var_name, range[0], range[1], body)
219
+ end
220
+
221
+ def visit_call_with_block(node)
222
+ call_node = node
223
+ method_name = call_node.name.to_s
224
+
225
+ if method_name == "times" && call_node.receiver
226
+ count = visit(call_node.receiver)
227
+ block = visit(call_node.block)
228
+
229
+ var_name = :i
230
+ if call_node.block&.parameters&.parameters&.requireds&.any?
231
+ var_name = call_node.block.parameters.parameters.requireds.first.name.to_sym
232
+ end
233
+
234
+ IR::ForLoop.new(var_name, IR::Literal.new(0, :int), count, block)
235
+ else
236
+ visit_call(node)
237
+ end
238
+ end
239
+
240
+ def visit_and(node)
241
+ left = visit(node.left)
242
+ right = visit(node.right)
243
+ IR::BinaryOp.new("&&", left, right, :bool)
244
+ end
245
+
246
+ def visit_or(node)
247
+ left = visit(node.left)
248
+ right = visit(node.right)
249
+ IR::BinaryOp.new("||", left, right, :bool)
250
+ end
251
+
252
+ def visit_not(node)
253
+ operand = visit(node.expression)
254
+ IR::UnaryOp.new("!", operand, :bool)
255
+ end
256
+
257
+ def visit_while(node)
258
+ condition = visit(node.predicate)
259
+ body = visit(node.statements)
260
+ IR::WhileLoop.new(condition, body)
261
+ end
262
+
263
+ def visit_break(node)
264
+ IR::Break.new
265
+ end
266
+
267
+ def visit_constant_read(node)
268
+ name = node.name.to_s
269
+ if %w[PI TAU].include?(name)
270
+ IR::Constant.new(name.to_sym, :float)
271
+ else
272
+ IR::VarRef.new(name.to_sym)
273
+ end
274
+ end
275
+
276
+ def visit_def(node)
277
+ name = node.name.to_sym
278
+ params = []
279
+
280
+ if node.parameters
281
+ node.parameters.requireds&.each do |param|
282
+ params << param.name.to_sym
283
+ end
284
+ end
285
+
286
+ saved_params = @params.dup
287
+ saved_declared_vars = @declared_vars.dup
288
+ @declared_vars = Set.new
289
+ params.each { |p| @params.add(p) }
290
+
291
+ body = visit(node.body)
292
+
293
+ @params = saved_params
294
+ @declared_vars = saved_declared_vars
295
+
296
+ IR::FunctionDefinition.new(name, params, body)
297
+ end
298
+
299
+ def visit_array(node)
300
+ elements = node.elements.map { |elem| visit(elem) }
301
+ IR::ArrayLiteral.new(elements)
302
+ end
303
+
304
+ def visit_index(node)
305
+ array = visit(node.receiver)
306
+ index = visit(node.arguments.arguments.first)
307
+ IR::ArrayIndex.new(array, index)
308
+ end
309
+
310
+ def visit_constant_path(node)
311
+ path_parts = []
312
+ current = node
313
+ while current.is_a?(::Prism::ConstantPathNode)
314
+ path_parts.unshift(current.name.to_s)
315
+ current = current.parent
316
+ end
317
+ path_parts.unshift(current.name.to_s) if current.respond_to?(:name)
318
+
319
+ full_name = path_parts.join("_")
320
+ IR::VarRef.new(full_name.to_sym)
321
+ end
322
+
323
+ def visit_global_variable_read(node)
324
+ name = node.name.to_s.sub(/^\$/, "").to_sym
325
+ IR::VarRef.new(name)
326
+ end
327
+
328
+ def visit_global_variable_write(node)
329
+ name = node.name.to_s.sub(/^\$/, "").to_sym
330
+ value = visit(node.value)
331
+
332
+ IR::GlobalDecl.new(name, value, is_static: true)
333
+ end
334
+
335
+ def visit_constant_write(node)
336
+ name = node.name.to_sym
337
+ value = visit(node.value)
338
+
339
+ IR::GlobalDecl.new(name, value, is_const: true, is_static: true)
340
+ end
341
+
342
+ def visit_multi_write(node)
343
+ targets = node.lefts.map do |target|
344
+ name = target.name.to_sym
345
+ @declared_vars.add(name)
346
+ IR::VarRef.new(name)
347
+ end
348
+
349
+ value = visit(node.value)
350
+ IR::MultipleAssignment.new(targets, value)
351
+ end
352
+
353
+ def visit_local_variable_target(node)
354
+ name = node.name.to_sym
355
+ @declared_vars.add(name)
356
+ IR::VarRef.new(name)
357
+ end
358
+
359
+ def infer_param_type(name)
360
+ case name
361
+ when :frag_coord, :resolution
362
+ :vec2
363
+ when :u
364
+ :uniforms
365
+ else
366
+ nil
367
+ end
368
+ end
369
+ end
370
+ end
371
+ end