rlsl 0.1.1 → 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.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +8 -0
  3. data/CHANGELOG.md +13 -2
  4. data/README.md +101 -26
  5. data/Rakefile +7 -0
  6. data/lib/rlsl/base_translator/call_parser.rb +79 -0
  7. data/lib/rlsl/base_translator/code_rewriter.rb +107 -0
  8. data/lib/rlsl/base_translator/code_scanner.rb +140 -0
  9. data/lib/rlsl/base_translator.rb +182 -64
  10. data/lib/rlsl/code_generator/math_prelude.rb +172 -0
  11. data/lib/rlsl/code_generator/ruby_wrapper_generator.rb +137 -0
  12. data/lib/rlsl/code_generator/shader_function_generator.rb +19 -0
  13. data/lib/rlsl/code_generator/template_context.rb +44 -0
  14. data/lib/rlsl/code_generator/uniform_struct_generator.rb +29 -0
  15. data/lib/rlsl/code_generator.rb +30 -202
  16. data/lib/rlsl/compiled_shader.rb +5 -13
  17. data/lib/rlsl/errors.rb +29 -0
  18. data/lib/rlsl/function_context.rb +31 -14
  19. data/lib/rlsl/glsl/translator.rb +35 -41
  20. data/lib/rlsl/msl/shader.rb +34 -46
  21. data/lib/rlsl/msl/translator.rb +38 -34
  22. data/lib/rlsl/msl/uniform_buffer_packer.rb +72 -0
  23. data/lib/rlsl/prism/ast_visitor/control_flow_visiting.rb +137 -0
  24. data/lib/rlsl/prism/ast_visitor/definition_visiting.rb +92 -0
  25. data/lib/rlsl/prism/ast_visitor/expression_visiting.rb +172 -0
  26. data/lib/rlsl/prism/ast_visitor/scope_context.rb +48 -0
  27. data/lib/rlsl/prism/ast_visitor/visitor_registry.rb +21 -0
  28. data/lib/rlsl/prism/ast_visitor.rb +100 -286
  29. data/lib/rlsl/prism/builtins/function_registry.rb +131 -0
  30. data/lib/rlsl/prism/builtins/operator_rules.rb +123 -0
  31. data/lib/rlsl/prism/builtins/swizzle_rules.rb +47 -0
  32. data/lib/rlsl/prism/builtins.rb +39 -148
  33. data/lib/rlsl/prism/compilation_unit.rb +7 -0
  34. data/lib/rlsl/prism/emitters/base_emitter/control_flow_emission.rb +126 -0
  35. data/lib/rlsl/prism/emitters/base_emitter/definition_emission.rb +134 -0
  36. data/lib/rlsl/prism/emitters/base_emitter/expression_emission.rb +108 -0
  37. data/lib/rlsl/prism/emitters/base_emitter/statement_emission.rb +95 -0
  38. data/lib/rlsl/prism/emitters/base_emitter.rb +120 -414
  39. data/lib/rlsl/prism/emitters/c_emitter.rb +165 -112
  40. data/lib/rlsl/prism/emitters/glsl_emitter.rb +63 -50
  41. data/lib/rlsl/prism/emitters/msl_emitter.rb +67 -52
  42. data/lib/rlsl/prism/emitters/target_emitter.rb +124 -0
  43. data/lib/rlsl/prism/emitters/target_profile.rb +34 -0
  44. data/lib/rlsl/prism/emitters/wgsl_emitter.rb +217 -58
  45. data/lib/rlsl/prism/errors.rb +9 -0
  46. data/lib/rlsl/prism/ir/control_flow.rb +85 -0
  47. data/lib/rlsl/prism/ir/definitions.rb +82 -0
  48. data/lib/rlsl/prism/ir/expressions.rb +201 -0
  49. data/lib/rlsl/prism/ir/node.rb +21 -0
  50. data/lib/rlsl/prism/ir/nodes.rb +4 -371
  51. data/lib/rlsl/prism/ir/traversal.rb +66 -0
  52. data/lib/rlsl/prism/mutation_analyzer.rb +30 -0
  53. data/lib/rlsl/prism/node_traversal.rb +41 -0
  54. data/lib/rlsl/prism/parameter_list.rb +46 -0
  55. data/lib/rlsl/prism/return_flow_validator.rb +73 -0
  56. data/lib/rlsl/prism/source_extractor/block_locator.rb +50 -0
  57. data/lib/rlsl/prism/source_extractor.rb +19 -137
  58. data/lib/rlsl/prism/source_unit/parser.rb +88 -0
  59. data/lib/rlsl/prism/source_unit.rb +42 -0
  60. data/lib/rlsl/prism/target_capability_validator.rb +110 -0
  61. data/lib/rlsl/prism/transpiler.rb +99 -59
  62. data/lib/rlsl/prism/type_inference/call_type_resolver.rb +59 -0
  63. data/lib/rlsl/prism/type_inference/call_validator.rb +75 -0
  64. data/lib/rlsl/prism/type_inference/collection_type_resolver.rb +106 -0
  65. data/lib/rlsl/prism/type_inference/control_flow_inferer.rb +147 -0
  66. data/lib/rlsl/prism/type_inference/definition_inferer.rb +56 -0
  67. data/lib/rlsl/prism/type_inference/expression_inferer.rb +96 -0
  68. data/lib/rlsl/prism/type_inference/field_type_resolver.rb +38 -0
  69. data/lib/rlsl/prism/type_inference/inferer_registry.rb +38 -0
  70. data/lib/rlsl/prism/type_inference/scope_stack.rb +47 -0
  71. data/lib/rlsl/prism/type_inference/type_environment.rb +112 -0
  72. data/lib/rlsl/prism/type_inference/type_shapes.rb +33 -0
  73. data/lib/rlsl/prism/type_inference.rb +120 -249
  74. data/lib/rlsl/runtime_shader.rb +47 -0
  75. data/lib/rlsl/shader_builder/build_service.rb +104 -0
  76. data/lib/rlsl/shader_builder/native_extension_compiler.rb +97 -0
  77. data/lib/rlsl/shader_builder/shader_definition.rb +68 -0
  78. data/lib/rlsl/shader_builder/source_resolver.rb +109 -0
  79. data/lib/rlsl/shader_builder.rb +60 -111
  80. data/lib/rlsl/shader_name.rb +18 -0
  81. data/lib/rlsl/types/catalog.rb +47 -0
  82. data/lib/rlsl/types/target_resolver.rb +15 -0
  83. data/lib/rlsl/types/type_spec.rb +167 -0
  84. data/lib/rlsl/types/value_normalizer.rb +86 -0
  85. data/lib/rlsl/types.rb +9 -31
  86. data/lib/rlsl/uniform_context.rb +22 -11
  87. data/lib/rlsl/version.rb +1 -1
  88. data/lib/rlsl/wgsl/translator.rb +46 -39
  89. data/lib/rlsl/wgsl/uniform_layout.rb +25 -0
  90. data/lib/rlsl.rb +38 -15
  91. metadata +76 -11
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RLSL
4
+ class CodeGenerator
5
+ class UniformStructGenerator
6
+ def initialize(context)
7
+ @context = context
8
+ end
9
+
10
+ def generate
11
+ return "typedef struct { unsigned char _unused; } Uniforms;\n" if @context.uniform_entries.empty?
12
+
13
+ <<~C
14
+ typedef struct {
15
+ #{field_lines}
16
+ } Uniforms;
17
+ C
18
+ end
19
+
20
+ private
21
+
22
+ def field_lines
23
+ @context.uniform_entries.map do |uniform_name, spec|
24
+ " #{spec.c_type} #{uniform_name};"
25
+ end.join("\n")
26
+ end
27
+ end
28
+ end
29
+ end
@@ -1,229 +1,57 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "code_generator/template_context"
4
+ require_relative "code_generator/math_prelude"
5
+ require_relative "code_generator/uniform_struct_generator"
6
+ require_relative "code_generator/shader_function_generator"
7
+ require_relative "code_generator/ruby_wrapper_generator"
8
+
3
9
  module RLSL
4
10
  class CodeGenerator
5
- def initialize(name, uniforms, helpers_block, fragment_block)
6
- @name = name
7
- @uniforms = uniforms
8
- @helpers_block = helpers_block
9
- @fragment_block = fragment_block
11
+ def initialize(name, uniforms, helpers_block, fragment_block, extension_name: name)
12
+ @context = TemplateContext.new(
13
+ name: name,
14
+ uniforms: uniforms,
15
+ helpers_block: helpers_block,
16
+ fragment_block: fragment_block,
17
+ extension_name: extension_name
18
+ )
10
19
  end
11
20
 
12
21
  def generate
13
22
  <<~C
14
23
  #include <ruby.h>
24
+ #include <ruby/thread.h>
25
+ #include <limits.h>
15
26
  #include <math.h>
16
27
  #include <stdint.h>
17
28
  #ifdef __APPLE__
18
29
  #include <dispatch/dispatch.h>
19
30
  #endif
20
31
 
21
- #{generate_types}
22
- #{generate_math_helpers}
23
- #{generate_uniform_struct}
24
- #{generate_custom_helpers}
25
- #{generate_shader_function}
26
- #{generate_ruby_wrapper}
32
+ #{RLSL::C_TYPES}
33
+ #{MathPrelude.code}
34
+ #{UniformStructGenerator.new(@context).generate}
35
+ #{custom_helpers}
36
+ #{ShaderFunctionGenerator.new(@context).generate}
37
+ #{RubyWrapperGenerator.new(@context).generate}
27
38
 
28
- void Init_#{@name}(void) {
29
- VALUE mRLSL = rb_define_module("RLSL");
30
- VALUE mShaders = rb_define_module_under(mRLSL, "CompiledShaders");
31
- rb_define_module_function(mShaders, "#{@name}_render", shader_#{@name}_render, #{3 + @uniforms.size});
32
- }
39
+ #{init_function}
33
40
  C
34
41
  end
35
42
 
36
43
  private
37
44
 
38
- def generate_types
39
- <<~C
40
- typedef struct { float x, y; } vec2;
41
- typedef struct { float x, y, z; } vec3;
42
- typedef struct { float x, y, z, w; } vec4;
43
-
44
- #define PI 3.14159265f
45
- #define TAU 6.28318530f
46
- C
47
- end
48
-
49
- def generate_math_helpers
50
- <<~C
51
- static inline vec2 vec2_new(float x, float y) { return (vec2){x, y}; }
52
- static inline vec3 vec3_new(float x, float y, float z) { return (vec3){x, y, z}; }
53
- static inline vec4 vec4_new(float x, float y, float z, float w) { return (vec4){x, y, z, w}; }
54
-
55
- static inline vec2 vec2_add(vec2 a, vec2 b) { return (vec2){a.x + b.x, a.y + b.y}; }
56
- static inline vec3 vec3_add(vec3 a, vec3 b) { return (vec3){a.x + b.x, a.y + b.y, a.z + b.z}; }
57
-
58
- static inline vec2 vec2_sub(vec2 a, vec2 b) { return (vec2){a.x - b.x, a.y - b.y}; }
59
- static inline vec3 vec3_sub(vec3 a, vec3 b) { return (vec3){a.x - b.x, a.y - b.y, a.z - b.z}; }
60
-
61
- static inline vec2 vec2_mul(vec2 a, float s) { return (vec2){a.x * s, a.y * s}; }
62
- static inline vec3 vec3_mul(vec3 a, float s) { return (vec3){a.x * s, a.y * s, a.z * s}; }
63
-
64
- static inline vec2 vec2_div(vec2 a, float s) { return (vec2){a.x / s, a.y / s}; }
65
- static inline vec3 vec3_div(vec3 a, float s) { return (vec3){a.x / s, a.y / s, a.z / s}; }
66
-
67
- static inline float vec2_dot(vec2 a, vec2 b) { return a.x * b.x + a.y * b.y; }
68
- static inline float vec3_dot(vec3 a, vec3 b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
69
-
70
- static inline float vec2_length(vec2 v) { return sqrtf(v.x * v.x + v.y * v.y); }
71
- static inline float vec3_length(vec3 v) { return sqrtf(v.x * v.x + v.y * v.y + v.z * v.z); }
72
-
73
- static inline vec2 vec2_normalize(vec2 v) { float l = vec2_length(v); return l > 0 ? vec2_div(v, l) : v; }
74
- static inline vec3 vec3_normalize(vec3 v) { float l = vec3_length(v); return l > 0 ? vec3_div(v, l) : v; }
75
-
76
- static inline float fract(float x) { return x - floorf(x); }
77
- static inline float mix_f(float a, float b, float t) { return a + (b - a) * t; }
78
- static inline vec3 mix_v3(vec3 a, vec3 b, float t) {
79
- return (vec3){a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t};
80
- }
81
-
82
- static inline float clamp_f(float x, float lo, float hi) {
83
- return x < lo ? lo : (x > hi ? hi : x);
84
- }
85
-
86
- static inline float smoothstep(float edge0, float edge1, float x) {
87
- float t = clamp_f((x - edge0) / (edge1 - edge0), 0.0f, 1.0f);
88
- return t * t * (3.0f - 2.0f * t);
89
- }
90
-
91
- static inline float hash21(vec2 p) {
92
- float dot = p.x * 12.9898f + p.y * 78.233f;
93
- return fract(sinf(dot) * 43758.5453f);
94
- }
95
-
96
- static inline vec2 hash22(vec2 p) {
97
- float n = sinf(vec2_dot(p, vec2_new(127.1f, 311.7f)));
98
- return vec2_new(fract(n * 43758.5453f), fract(n * 12345.6789f));
99
- }
100
-
101
- // reflect: I - 2 * dot(N, I) * N
102
- static inline vec3 reflect(vec3 I, vec3 N) {
103
- float d = vec3_dot(N, I);
104
- return vec3_new(I.x - 2.0f * d * N.x, I.y - 2.0f * d * N.y, I.z - 2.0f * d * N.z);
105
- }
106
-
107
- // refract: Snell's law
108
- static inline vec3 refract(vec3 I, vec3 N, float eta) {
109
- float d = vec3_dot(N, I);
110
- float k = 1.0f - eta * eta * (1.0f - d * d);
111
- if (k < 0.0f) {
112
- return vec3_new(0.0f, 0.0f, 0.0f); // Total internal reflection
113
- }
114
- float s = eta * d + sqrtf(k);
115
- return vec3_new(eta * I.x - s * N.x, eta * I.y - s * N.y, eta * I.z - s * N.z);
116
- }
117
- C
118
- end
119
-
120
- def generate_uniform_struct
121
- if @uniforms.empty?
122
- "typedef struct {} Uniforms;\n"
123
- else
124
- fields = @uniforms.map do |name, type|
125
- case type
126
- when :float then " float #{name};"
127
- when :vec2 then " vec2 #{name};"
128
- when :vec3 then " vec3 #{name};"
129
- when :vec4 then " vec4 #{name};"
130
- end
131
- end.join("\n")
132
-
133
- "typedef struct {\n#{fields}\n} Uniforms;\n"
134
- end
135
- end
136
-
137
- def generate_custom_helpers
138
- return "" unless @helpers_block
139
-
140
- @helpers_block.call
45
+ def custom_helpers
46
+ @context.helpers_code
141
47
  end
142
48
 
143
- def generate_shader_function
144
- shader_body = @fragment_block.call
145
-
49
+ def init_function
146
50
  <<~C
147
- static vec3 shader_#{@name}(vec2 frag_coord, vec2 resolution, Uniforms u) {
148
- #{shader_body}
149
- }
150
- C
151
- end
152
-
153
- def generate_ruby_wrapper
154
- uniform_args = @uniforms.map { |name, _| "VALUE rb_#{name}" }.join(", ")
155
- uniform_args = ", " + uniform_args unless uniform_args.empty?
156
-
157
- uniform_parsing = @uniforms.map do |name, type|
158
- case type
159
- when :float
160
- " uniforms.#{name} = (float)NUM2DBL(rb_#{name});"
161
- when :vec2
162
- <<~C.strip
163
- Check_Type(rb_#{name}, T_ARRAY);
164
- uniforms.#{name} = vec2_new(
165
- (float)NUM2DBL(rb_ary_entry(rb_#{name}, 0)),
166
- (float)NUM2DBL(rb_ary_entry(rb_#{name}, 1))
167
- );
168
- C
169
- when :vec3
170
- <<~C.strip
171
- Check_Type(rb_#{name}, T_ARRAY);
172
- uniforms.#{name} = vec3_new(
173
- (float)NUM2DBL(rb_ary_entry(rb_#{name}, 0)),
174
- (float)NUM2DBL(rb_ary_entry(rb_#{name}, 1)),
175
- (float)NUM2DBL(rb_ary_entry(rb_#{name}, 2))
176
- );
177
- C
178
- end
179
- end.join("\n")
180
-
181
- <<~C
182
- static VALUE shader_#{@name}_render(VALUE self, VALUE rb_buffer, VALUE rb_width, VALUE rb_height#{uniform_args}) {
183
- int width = NUM2INT(rb_width);
184
- int height = NUM2INT(rb_height);
185
- vec2 resolution = vec2_new((float)width, (float)height);
186
-
187
- Uniforms uniforms;
188
- #{uniform_parsing}
189
-
190
- Check_Type(rb_buffer, T_STRING);
191
- rb_str_modify(rb_buffer);
192
- uint8_t *pixels = (uint8_t *)RSTRING_PTR(rb_buffer);
193
-
194
- #ifdef __APPLE__
195
- dispatch_apply(height, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t y) {
196
- int flipped_y = height - 1 - (int)y;
197
- for (int x = 0; x < width; x++) {
198
- vec2 frag_coord = vec2_new((float)x, (float)flipped_y);
199
- vec3 color = shader_#{@name}(frag_coord, resolution, uniforms);
200
-
201
- int idx = ((int)y * width + x) * 4;
202
- // Output as BGRA (macOS native format)
203
- pixels[idx] = (uint8_t)(clamp_f(color.z, 0.0f, 1.0f) * 255.0f);
204
- pixels[idx+1] = (uint8_t)(clamp_f(color.y, 0.0f, 1.0f) * 255.0f);
205
- pixels[idx+2] = (uint8_t)(clamp_f(color.x, 0.0f, 1.0f) * 255.0f);
206
- pixels[idx+3] = 255;
207
- }
208
- });
209
- #else
210
- for (int y = 0; y < height; y++) {
211
- int flipped_y = height - 1 - y;
212
- for (int x = 0; x < width; x++) {
213
- vec2 frag_coord = vec2_new((float)x, (float)flipped_y);
214
- vec3 color = shader_#{@name}(frag_coord, resolution, uniforms);
215
-
216
- int idx = (y * width + x) * 4;
217
- // Output as BGRA (macOS native format)
218
- pixels[idx] = (uint8_t)(clamp_f(color.z, 0.0f, 1.0f) * 255.0f);
219
- pixels[idx+1] = (uint8_t)(clamp_f(color.y, 0.0f, 1.0f) * 255.0f);
220
- pixels[idx+2] = (uint8_t)(clamp_f(color.x, 0.0f, 1.0f) * 255.0f);
221
- pixels[idx+3] = 255;
222
- }
223
- }
224
- #endif
225
-
226
- return Qnil;
51
+ void Init_#{@context.extension_name}(void) {
52
+ VALUE mRLSL = rb_define_module("RLSL");
53
+ VALUE mShaders = rb_define_module_under(mRLSL, "CompiledShaders");
54
+ rb_define_module_function(mShaders, "#{@context.extension_name}_render", shader_#{@context.name}_render, #{@context.render_arity});
227
55
  }
228
56
  C
229
57
  end
@@ -1,23 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RLSL
4
- class CompiledShader
5
- def initialize(name, ext_name, uniform_names)
6
- @name = name
4
+ class CompiledShader < RuntimeShader
5
+ def initialize(name, ext_name, uniforms)
6
+ super(name, uniforms)
7
7
  @ext_name = ext_name
8
- @uniform_names = uniform_names
9
- @render_method = RLSL::CompiledShaders.method("#{name}_render")
10
- end
11
-
12
- def metal?
13
- false
8
+ @render_method = RLSL::CompiledShaders.method("#{ext_name}_render")
14
9
  end
15
10
 
16
11
  def render(buffer, width, height, uniforms = {})
17
- args = [buffer, width, height]
18
- @uniform_names.each do |name|
19
- args << uniforms[name]
20
- end
12
+ args = [buffer, width, height] + ordered_uniform_values(uniforms)
21
13
  @render_method.call(*args)
22
14
  end
23
15
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RLSL
4
+ SourceLocation = Struct.new(:source_name, :line, :column, keyword_init: true) do
5
+ def to_s
6
+ "#{source_name || '(shader source)'}:#{line}:#{column}"
7
+ end
8
+ end
9
+
10
+ class Error < StandardError
11
+ attr_reader :source_location
12
+
13
+ def with_source_location(location)
14
+ @source_location ||= location
15
+ self
16
+ end
17
+
18
+ def message
19
+ return super unless source_location
20
+
21
+ "#{super} at #{source_location}"
22
+ end
23
+ end
24
+ class InternalError < Error; end
25
+ class ParseError < Error; end
26
+ class TranslationError < Error; end
27
+ class CompilationError < Error; end
28
+ class UniformValueError < ArgumentError; end
29
+ end
@@ -8,28 +8,45 @@ module RLSL
8
8
  @functions = {}
9
9
  end
10
10
 
11
- def float(*names)
12
- names.each { |name| @functions[name.to_sym] = { returns: :float } }
11
+ # Full form: specify return type and parameter types
12
+ # @example
13
+ # define :path_point, returns: :vec3, params: { z: :float }
14
+ # define :noise_a, returns: :float, params: { f: :float, h: :float, k: :float, p: :vec3 }
15
+ def define(name, returns:, params: {})
16
+ @functions[name.to_sym] = {
17
+ returns: validate_type!(returns),
18
+ params: normalize_params(params)
19
+ }
13
20
  end
14
21
 
15
- def vec2(*names)
16
- names.each { |name| @functions[name.to_sym] = { returns: :vec2 } }
22
+ UniformTypes.function_shorthand_types.each do |type|
23
+ define_method(type) do |*names|
24
+ register_shorthand_functions(type, *names)
25
+ end
17
26
  end
18
27
 
19
- def vec3(*names)
20
- names.each { |name| @functions[name.to_sym] = { returns: :vec3 } }
28
+ private
29
+
30
+ def register_shorthand_functions(type, *names)
31
+ validated_type = validate_type!(type)
32
+ names.each do |name|
33
+ @functions[name.to_sym] = { returns: validated_type }
34
+ end
21
35
  end
22
36
 
23
- def vec4(*names)
24
- names.each { |name| @functions[name.to_sym] = { returns: :vec4 } }
37
+ def normalize_params(params)
38
+ params.each_with_object({}) do |(name, type), normalized|
39
+ normalized[name.to_sym] = validate_type!(type)
40
+ end
25
41
  end
26
42
 
27
- # Full form: specify return type and parameter types
28
- # @example
29
- # define :path_point, returns: :vec3, params: { z: :float }
30
- # define :noise_a, returns: :float, params: { f: :float, h: :float, k: :float, p: :vec3 }
31
- def define(name, returns:, params: {})
32
- @functions[name.to_sym] = { returns: returns, params: params }
43
+ def validate_type!(type)
44
+ if type.is_a?(Array)
45
+ return type.map { |element_type| validate_type!(element_type) }
46
+ end
47
+
48
+ UniformTypes.fetch(type)
49
+ type
33
50
  end
34
51
  end
35
52
  end
@@ -3,37 +3,30 @@
3
3
  module RLSL
4
4
  module GLSL
5
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
6
+ PROFILE = BaseTranslator.build_profile(
7
+ uniform_target: :glsl,
8
+ identifier_replacements: {},
9
+ call_rewrites: BaseTranslator.common_call_rewrites(
10
+ target_vec2: "vec2",
11
+ target_vec3: "vec3",
12
+ target_vec4: "vec4"
13
+ ).merge(
14
+ "fmodf" => BaseTranslator.rename_call("mod"),
15
+ "atan2f" => BaseTranslator.rename_call("atan")
16
+ )
17
+ )
18
+
19
+ def initialize(uniforms, helpers_code, fragment_code, version: "450", name: nil)
20
+ super(uniforms, helpers_code, fragment_code, name: name)
21
+ @version = validate_version!(version)
21
22
  end
22
23
 
23
24
  protected
24
25
 
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
26
  def generate_shader(helpers, fragment)
35
27
  <<~GLSL
36
28
  #version #{@version}
29
+ #{generated_by_comment("GLSL")}
37
30
 
38
31
  // Uniforms
39
32
  #{generate_uniform_declarations}
@@ -44,8 +37,7 @@ module RLSL
44
37
  #{helpers}
45
38
 
46
39
  vec3 shader_fragment(vec2 frag_coord, vec2 resolution) {
47
- vec2 uv = frag_coord / resolution.y;
48
- #{fragment}
40
+ #{indent_source(fragment, 4)}
49
41
  }
50
42
 
51
43
  layout(local_size_x = 8, local_size_y = 8) in;
@@ -70,27 +62,29 @@ module RLSL
70
62
 
71
63
  private
72
64
 
65
+ def profile
66
+ PROFILE
67
+ end
68
+
73
69
  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
70
+ declarations = ["layout(std140, binding = 1) uniform ShaderUniforms {"]
71
+ declarations.concat(
72
+ uniform_lines(resolution_line: " #{target_vec2_type} resolution;") do |name, glsl_type|
73
+ " #{glsl_type} #{name};"
74
+ end
75
+ )
80
76
  declarations << "} u;"
77
+ texture_uniforms.each_with_index do |(name, _type), index|
78
+ declarations << "layout(binding = #{index + 2}) uniform sampler2D #{name};"
79
+ end
81
80
  declarations.join("\n")
82
81
  end
83
82
 
84
- def target_vec2_type
85
- "vec2"
86
- end
87
-
88
- def target_vec3_type
89
- "vec3"
90
- end
83
+ def validate_version!(version)
84
+ normalized = version.to_s
85
+ return normalized if normalized.match?(/\A[1-9]\d{2}(?: (?:core|compatibility|es))?\z/)
91
86
 
92
- def target_vec4_type
93
- "vec4"
87
+ raise ArgumentError, "Invalid GLSL version: #{version.inspect}"
94
88
  end
95
89
  end
96
90
  end
@@ -1,30 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- begin
4
- require "metaco"
5
- METACO_AVAILABLE = true
6
- rescue LoadError
7
- METACO_AVAILABLE = false
8
- end
9
-
10
3
  module RLSL
11
4
  module MSL
12
- class Shader
5
+ begin
6
+ require "metaco"
7
+ METACO_AVAILABLE = true
8
+ rescue LoadError
9
+ METACO_AVAILABLE = false
10
+ end
11
+
12
+ class Shader < RuntimeShader
13
+ COMPILED_HANDLE_CACHE_LIMIT = 64
13
14
  attr_reader :name, :msl_source
14
15
 
15
16
  def initialize(name, uniforms, msl_source)
16
- @name = name
17
- @uniform_types = uniforms
18
- @uniform_names = uniforms.keys
17
+ super(name, uniforms)
19
18
  @msl_source = msl_source
20
19
  @compiled_handles = {}
20
+ value_uniform_types = @uniform_types.reject { |_name, type| type == :sampler2D }
21
+ @uniform_buffer_packer = UniformBufferPacker.new(@name, value_uniform_types, value_uniform_types.keys)
21
22
  end
22
23
 
23
24
  def metal?
24
25
  true
25
26
  end
26
27
 
27
- def render_metal(handle, width, height, uniforms = {})
28
+ def render(handle, width, height, uniforms = {})
29
+ render_metal(handle, width, height, uniforms)
30
+ end
31
+
32
+ def prepare(handle)
28
33
  unless METACO_AVAILABLE
29
34
  raise LoadError, "metaco gem is required for Metal rendering. Install it with: gem install metaco"
30
35
  end
@@ -32,10 +37,26 @@ module RLSL
32
37
  unless @compiled_handles[handle]
33
38
  Metaco.compile_compute_shader(handle, @msl_source)
34
39
  @compiled_handles[handle] = true
40
+ @compiled_handles.shift while @compiled_handles.length > COMPILED_HANDLE_CACHE_LIMIT
35
41
  end
42
+ self
43
+ end
44
+
45
+ def render_metal(handle, width, height, uniforms = {}, textures: {})
46
+ prepare(handle)
36
47
 
37
48
  uniform_data = pack_uniforms(uniforms, width, height)
38
49
 
50
+ sampler_names = @uniform_types.filter_map { |name, type| name if type == :sampler2D }
51
+ sampler_names.each do |name|
52
+ texture = textures[name] || textures[name.to_s]
53
+ raise ArgumentError, "missing texture uniform: #{name}" unless texture
54
+ unless Metaco.respond_to?(:bind_compute_texture)
55
+ raise LoadError, "metaco texture support is required for sampler2D uniforms"
56
+ end
57
+ Metaco.bind_compute_texture(handle, sampler_names.index(name), texture)
58
+ end
59
+
39
60
  Metaco.dispatch_compute(handle, uniform_data)
40
61
  Metaco.present_compute(handle)
41
62
  end
@@ -43,40 +64,7 @@ module RLSL
43
64
  private
44
65
 
45
66
  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")
67
+ @uniform_buffer_packer.pack(width, height, uniforms)
80
68
  end
81
69
  end
82
70
  end