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.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +8 -0
  3. data/CHANGELOG.md +3 -1
  4. data/README.md +101 -28
  5. data/Rakefile +7 -0
  6. data/lib/rlsl/base_translator/call_parser.rb +13 -2
  7. data/lib/rlsl/base_translator/code_scanner.rb +39 -1
  8. data/lib/rlsl/base_translator.rb +24 -2
  9. data/lib/rlsl/code_generator/math_prelude.rb +103 -10
  10. data/lib/rlsl/code_generator/ruby_wrapper_generator.rb +52 -12
  11. data/lib/rlsl/code_generator/template_context.rb +6 -3
  12. data/lib/rlsl/code_generator/uniform_struct_generator.rb +1 -1
  13. data/lib/rlsl/code_generator.rb +7 -4
  14. data/lib/rlsl/compiled_shader.rb +1 -1
  15. data/lib/rlsl/errors.rb +29 -0
  16. data/lib/rlsl/glsl/translator.rb +19 -6
  17. data/lib/rlsl/msl/shader.rb +27 -9
  18. data/lib/rlsl/msl/translator.rb +25 -4
  19. data/lib/rlsl/msl/uniform_buffer_packer.rb +11 -7
  20. data/lib/rlsl/prism/ast_visitor/control_flow_visiting.rb +59 -18
  21. data/lib/rlsl/prism/ast_visitor/definition_visiting.rb +22 -9
  22. data/lib/rlsl/prism/ast_visitor/expression_visiting.rb +18 -14
  23. data/lib/rlsl/prism/ast_visitor/scope_context.rb +4 -0
  24. data/lib/rlsl/prism/ast_visitor.rb +78 -20
  25. data/lib/rlsl/prism/builtins/function_registry.rb +41 -24
  26. data/lib/rlsl/prism/builtins/operator_rules.rb +24 -0
  27. data/lib/rlsl/prism/builtins/swizzle_rules.rb +10 -1
  28. data/lib/rlsl/prism/builtins.rb +8 -0
  29. data/lib/rlsl/prism/emitters/base_emitter/control_flow_emission.rb +51 -8
  30. data/lib/rlsl/prism/emitters/base_emitter/definition_emission.rb +42 -12
  31. data/lib/rlsl/prism/emitters/base_emitter/expression_emission.rb +20 -4
  32. data/lib/rlsl/prism/emitters/base_emitter/statement_emission.rb +7 -1
  33. data/lib/rlsl/prism/emitters/base_emitter.rb +66 -5
  34. data/lib/rlsl/prism/emitters/c_emitter.rb +103 -7
  35. data/lib/rlsl/prism/emitters/glsl_emitter.rb +48 -0
  36. data/lib/rlsl/prism/emitters/msl_emitter.rb +43 -1
  37. data/lib/rlsl/prism/emitters/target_emitter.rb +56 -9
  38. data/lib/rlsl/prism/emitters/wgsl_emitter.rb +173 -18
  39. data/lib/rlsl/prism/errors.rb +9 -0
  40. data/lib/rlsl/prism/ir/control_flow.rb +6 -4
  41. data/lib/rlsl/prism/ir/definitions.rb +17 -2
  42. data/lib/rlsl/prism/ir/expressions.rb +5 -1
  43. data/lib/rlsl/prism/ir/node.rb +1 -1
  44. data/lib/rlsl/prism/ir/traversal.rb +6 -2
  45. data/lib/rlsl/prism/mutation_analyzer.rb +30 -0
  46. data/lib/rlsl/prism/node_traversal.rb +41 -0
  47. data/lib/rlsl/prism/parameter_list.rb +46 -0
  48. data/lib/rlsl/prism/return_flow_validator.rb +73 -0
  49. data/lib/rlsl/prism/source_extractor/block_locator.rb +23 -25
  50. data/lib/rlsl/prism/source_extractor.rb +7 -6
  51. data/lib/rlsl/prism/source_unit/parser.rb +42 -32
  52. data/lib/rlsl/prism/source_unit.rb +10 -10
  53. data/lib/rlsl/prism/target_capability_validator.rb +27 -2
  54. data/lib/rlsl/prism/transpiler.rb +40 -3
  55. data/lib/rlsl/prism/type_inference/call_type_resolver.rb +28 -4
  56. data/lib/rlsl/prism/type_inference/call_validator.rb +5 -1
  57. data/lib/rlsl/prism/type_inference/collection_type_resolver.rb +45 -19
  58. data/lib/rlsl/prism/type_inference/control_flow_inferer.rb +88 -7
  59. data/lib/rlsl/prism/type_inference/definition_inferer.rb +16 -1
  60. data/lib/rlsl/prism/type_inference/expression_inferer.rb +4 -0
  61. data/lib/rlsl/prism/type_inference/field_type_resolver.rb +23 -2
  62. data/lib/rlsl/prism/type_inference/scope_stack.rb +1 -1
  63. data/lib/rlsl/prism/type_inference/type_shapes.rb +3 -3
  64. data/lib/rlsl/prism/type_inference.rb +10 -5
  65. data/lib/rlsl/shader_builder/build_service.rb +34 -7
  66. data/lib/rlsl/shader_builder/native_extension_compiler.rb +50 -24
  67. data/lib/rlsl/shader_builder/shader_definition.rb +3 -3
  68. data/lib/rlsl/shader_builder/source_resolver.rb +19 -1
  69. data/lib/rlsl/shader_builder.rb +50 -10
  70. data/lib/rlsl/shader_name.rb +18 -0
  71. data/lib/rlsl/types/type_spec.rb +3 -3
  72. data/lib/rlsl/types/value_normalizer.rb +16 -11
  73. data/lib/rlsl/types.rb +0 -4
  74. data/lib/rlsl/uniform_context.rb +18 -1
  75. data/lib/rlsl/version.rb +1 -1
  76. data/lib/rlsl/wgsl/translator.rb +29 -6
  77. data/lib/rlsl/wgsl/uniform_layout.rb +25 -0
  78. data/lib/rlsl.rb +24 -3
  79. metadata +23 -11
@@ -8,9 +8,30 @@ module RLSL
8
8
  end
9
9
 
10
10
  def resolve(node)
11
- return :float if Builtins.single_component_field?(node.field)
11
+ if node.receiver.type == :uniforms
12
+ type = @uniforms[node.field.to_sym]
13
+ return type if type
12
14
 
13
- @uniforms[node.field.to_sym] || :float
15
+ raise SignatureError, "Unknown uniform field #{node.field.inspect}"
16
+ end
17
+
18
+ if Builtins.single_component_field?(node.field)
19
+ unless Builtins.valid_swizzle_for_type?(node.field, node.receiver.type)
20
+ raise SignatureError, "Invalid component #{node.field.inspect} for #{node.receiver.type || :unknown}"
21
+ end
22
+
23
+ return :float
24
+ end
25
+
26
+ if Builtins.swizzle?(node.field)
27
+ unless Builtins.valid_swizzle_for_type?(node.field, node.receiver.type)
28
+ raise SignatureError, "Invalid swizzle #{node.field.inspect} for #{node.receiver.type || :unknown}"
29
+ end
30
+
31
+ return Builtins.swizzle_type(node.field)
32
+ end
33
+
34
+ raise SignatureError, "Unknown field #{node.field.inspect} for #{node.receiver.type || :unknown}"
14
35
  end
15
36
  end
16
37
  end
@@ -12,7 +12,7 @@ module RLSL
12
12
  end
13
13
 
14
14
  def pop
15
- raise "Cannot pop the global scope" if @scopes.length == 1
15
+ raise RLSL::InternalError, "Cannot pop the global scope" if @scopes.length == 1
16
16
 
17
17
  @scopes.pop
18
18
  end
@@ -3,7 +3,7 @@
3
3
  module RLSL
4
4
  module Prism
5
5
  module TypeShapes
6
- ArrayType = Struct.new(:element_type) do
6
+ ArrayType = Struct.new(:element_type, :element_count) do
7
7
  def to_sym
8
8
  :"array_#{element_type}"
9
9
  end
@@ -15,8 +15,8 @@ module RLSL
15
15
 
16
16
  module_function
17
17
 
18
- def array(element_type)
19
- ArrayType.new(element_type)
18
+ def array(element_type, length = nil)
19
+ ArrayType.new(element_type, length)
20
20
  end
21
21
 
22
22
  def array?(type)
@@ -14,7 +14,7 @@ require_relative "type_inference/control_flow_inferer"
14
14
 
15
15
  module RLSL
16
16
  module Prism
17
- class SignatureError < StandardError; end
17
+ class SignatureError < RLSL::Error; end
18
18
 
19
19
  class TypeInference
20
20
  EXPRESSION_NODES = {
@@ -73,19 +73,21 @@ module RLSL
73
73
  )
74
74
  @definition_inferer = DefinitionInferer.new(
75
75
  infer: method(:infer),
76
+ lookup: method(:lookup),
76
77
  register: method(:register),
77
78
  collection_type_resolver: @collection_type_resolver
78
79
  )
79
80
  @control_flow_inferer = ControlFlowInferer.new(
80
81
  infer: method(:infer),
81
- infer_child_scope: method(:infer_child_scope),
82
- infer_in_scope: method(:infer_in_scope)
82
+ infer_in_scope: method(:infer_in_scope),
83
+ lookup: method(:lookup)
83
84
  )
84
85
  register_inferers
85
86
 
86
87
  uniforms.each do |name, type|
87
88
  register(name, type)
88
89
  end
90
+ register(:u, :uniforms)
89
91
 
90
92
  globals.each do |name, type|
91
93
  register(name, type)
@@ -100,8 +102,8 @@ module RLSL
100
102
  @types.register(name, type)
101
103
  end
102
104
 
103
- def register_function(name, returns:)
104
- @custom_functions[name.to_sym] = { returns: returns }
105
+ def register_function(name, returns:, params: {})
106
+ @custom_functions[name.to_sym] = { returns: returns, params: params }
105
107
  end
106
108
 
107
109
  def lookup(name)
@@ -111,6 +113,9 @@ module RLSL
111
113
  def infer(node, scoped: false)
112
114
  options = node.is_a?(IR::Block) ? { scoped: scoped } : {}
113
115
  @inferer_registry.infer(node, **options) || node
116
+ rescue RLSL::Error => error
117
+ error.with_source_location(node.location)
118
+ raise
114
119
  end
115
120
 
116
121
  private
@@ -11,26 +11,35 @@ module RLSL
11
11
  end
12
12
 
13
13
  def compile_and_load
14
- c_code = generate_c_code
15
- artifact = native_extension_compiler.build(c_code)
14
+ compiler = native_extension_compiler
15
+ base_code = generate_c_code
16
+ extension_name = compiler.extension_name_for(base_code)
17
+ c_code = generate_c_code(extension_name: extension_name)
18
+ artifact = compiler.build(c_code, ext_name: extension_name)
16
19
 
17
20
  require artifact.file
18
21
  CompiledShader.new(@name, artifact.ext_name, @definition.uniforms)
19
22
  end
20
23
 
21
24
  def build_metal_shader
22
- translator = MSL::Translator.new(@definition.uniforms, *translation_sources(:msl))
25
+ translator = MSL::Translator.new(@definition.uniforms, *translation_sources(:msl), name: @name)
23
26
  msl_source = translator.translate
24
27
 
25
28
  MSL::Shader.new(@name, @definition.uniforms, msl_source)
26
29
  end
27
30
 
28
31
  def build_wgsl_shader
29
- WGSL::Translator.new(@definition.uniforms, *translation_sources(:wgsl)).translate
32
+ validate_wgsl_module_names!
33
+ WGSL::Translator.new(@definition.uniforms, *translation_sources(:wgsl), name: @name).translate
30
34
  end
31
35
 
32
36
  def build_glsl_shader(version: "450")
33
- GLSL::Translator.new(@definition.uniforms, *translation_sources(:glsl), version: version).translate
37
+ GLSL::Translator.new(
38
+ @definition.uniforms,
39
+ *translation_sources(:glsl),
40
+ version: version,
41
+ name: @name
42
+ ).translate
34
43
  end
35
44
 
36
45
  def transpile_fragment(target)
@@ -47,9 +56,15 @@ module RLSL
47
56
 
48
57
  private
49
58
 
50
- def generate_c_code
59
+ def generate_c_code(extension_name: @name)
51
60
  helpers_code, fragment_code = resolved_sources(:c)
52
- codegen = CodeGenerator.new(@name, @definition.uniforms, -> { helpers_code }, -> { fragment_code })
61
+ codegen = CodeGenerator.new(
62
+ @name,
63
+ @definition.uniforms,
64
+ -> { helpers_code },
65
+ -> { fragment_code },
66
+ extension_name: extension_name
67
+ )
53
68
  codegen.generate
54
69
  end
55
70
 
@@ -72,6 +87,18 @@ module RLSL
72
87
  NativeExtensionCompiler.new(@name)
73
88
  end
74
89
  end
90
+
91
+ def validate_wgsl_module_names!
92
+ resources = @definition.uniforms.filter_map do |name, type|
93
+ [name, :"#{name}_sampler"] if type == :sampler2D
94
+ end.flatten
95
+ generated = %i[u output_texture rlsl_mod shader_fragment main]
96
+ functions = @definition.custom_functions.keys
97
+ conflicts = (resources & (generated + functions)) | (functions & generated)
98
+ return if conflicts.empty?
99
+
100
+ raise ArgumentError, "WGSL module name conflict: #{conflicts.join(', ')}"
101
+ end
75
102
  end
76
103
  end
77
104
  end
@@ -1,55 +1,75 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "digest"
4
+ require "open3"
5
+ require "shellwords"
4
6
 
5
7
  module RLSL
6
8
  class ShaderBuilder
7
9
  class NativeExtensionCompiler
8
10
  Artifact = Struct.new(:ext_name, :directory, :file, keyword_init: true)
9
11
 
10
- def initialize(shader_name, cache_dir: RLSL.cache_dir, ruby_bin: RbConfig.ruby, dylib_ext: RbConfig::CONFIG["DLEXT"])
11
- @shader_name = shader_name
12
+ def initialize(
13
+ shader_name,
14
+ cache_dir: RLSL.cache_dir,
15
+ ruby_bin: RbConfig.ruby,
16
+ dylib_ext: RbConfig::CONFIG["DLEXT"],
17
+ make_command: RbConfig::CONFIG["MAKE"] || "make",
18
+ fast_math: false
19
+ )
20
+ @shader_name = RLSL.validate_shader_name!(shader_name)
12
21
  @cache_dir = cache_dir
13
22
  @ruby_bin = ruby_bin
14
23
  @dylib_ext = dylib_ext
24
+ @make_command = Shellwords.split(make_command)
25
+ @fast_math = fast_math
15
26
  end
16
27
 
17
- def build(c_code)
18
- artifact = artifact_for(c_code)
19
- compile(artifact, c_code) unless File.exist?(artifact.file)
28
+ def build(c_code, ext_name: nil)
29
+ resolved_ext_name = ext_name || declared_extension_name(c_code) || extension_name_for(c_code)
30
+ artifact = artifact_for(c_code, ext_name: resolved_ext_name)
31
+ FileUtils.mkdir_p(artifact.directory)
32
+ File.open(File.join(artifact.directory, ".build.lock"), "w") do |lock|
33
+ lock.flock(File::LOCK_EX)
34
+ compile(artifact, c_code) unless File.exist?(artifact.file)
35
+ end
20
36
  artifact
21
37
  end
22
38
 
39
+ def extension_name_for(c_code)
40
+ code_hash = Digest::SHA256.hexdigest(c_code)[0, 16]
41
+ "#{@shader_name}_#{code_hash}"
42
+ end
43
+
23
44
  private
24
45
 
25
- def artifact_for(c_code)
26
- code_hash = Digest::MD5.hexdigest(c_code)[0..7]
27
- ext_name = "#{@shader_name}_#{code_hash}"
28
- directory = File.join(@cache_dir, ext_name)
46
+ def artifact_for(c_code, ext_name: extension_name_for(c_code))
47
+ validated_ext_name = RLSL.validate_identifier!(ext_name, context: "extension name")
48
+ directory = File.join(@cache_dir, extension_name_for(c_code))
29
49
 
30
50
  Artifact.new(
31
- ext_name: ext_name,
51
+ ext_name: validated_ext_name,
32
52
  directory: directory,
33
- file: File.join(directory, "#{@shader_name}.#{@dylib_ext}")
53
+ file: File.join(directory, "#{validated_ext_name}.#{@dylib_ext}")
34
54
  )
35
55
  end
36
56
 
37
57
  def compile(artifact, c_code)
38
- FileUtils.mkdir_p(artifact.directory)
58
+ File.write(File.join(artifact.directory, "#{artifact.ext_name}.c"), c_code)
59
+ File.write(File.join(artifact.directory, "extconf.rb"), extconf_source(artifact.ext_name))
39
60
 
40
- File.write(File.join(artifact.directory, "#{@shader_name}.c"), c_code)
41
- File.write(File.join(artifact.directory, "extconf.rb"), extconf_source(@shader_name))
61
+ run_command!(@ruby_bin, "extconf.rb", chdir: artifact.directory)
62
+ run_command!(*@make_command, chdir: artifact.directory)
63
+ end
42
64
 
43
- Dir.chdir(artifact.directory) do
44
- run_command(@ruby_bin, "extconf.rb") or raise "extconf failed for #{artifact.ext_name}"
45
- run_command("/usr/bin/make") or raise "make failed for #{artifact.ext_name}"
46
- end
65
+ def declared_extension_name(c_code)
66
+ c_code[/\bvoid\s+Init_([A-Za-z_][A-Za-z0-9_]*)\s*\(/, 1]
47
67
  end
48
68
 
49
69
  def extconf_source(ext_name)
50
70
  <<~RUBY
51
71
  require "mkmf"
52
- $CFLAGS << " -O3 -ffast-math"
72
+ $CFLAGS << " -O3#{@fast_math ? ' -ffast-math' : ''}"
53
73
  if RUBY_PLATFORM =~ /darwin/
54
74
  $CFLAGS << " -fblocks"
55
75
  end
@@ -57,13 +77,19 @@ module RLSL
57
77
  RUBY
58
78
  end
59
79
 
60
- def run_command(*args)
80
+ def run_command!(*args, chdir:)
81
+ runner = lambda do
82
+ stdout, stderr, status = Open3.capture3(*args, chdir: chdir)
83
+ return if status.success?
84
+
85
+ output = [stdout, stderr].reject(&:empty?).join("\n")
86
+ raise RLSL::CompilationError, "#{args.first} failed for #{@shader_name}:\n#{output}"
87
+ end
88
+
61
89
  if defined?(Bundler) && Bundler.respond_to?(:with_unbundled_env)
62
- Bundler.with_unbundled_env do
63
- system(*args, out: File::NULL, err: File::NULL)
64
- end
90
+ Bundler.with_unbundled_env { runner.call }
65
91
  else
66
- system(*args, out: File::NULL, err: File::NULL)
92
+ runner.call
67
93
  end
68
94
  end
69
95
  end
@@ -9,7 +9,7 @@ module RLSL
9
9
  uniforms: {},
10
10
  custom_functions: {},
11
11
  helpers_block: nil,
12
- helpers_mode: :c,
12
+ helpers_mode: :ruby,
13
13
  fragment_block: nil,
14
14
  fragment_mode: :c
15
15
  )
@@ -38,11 +38,11 @@ module RLSL
38
38
  end
39
39
 
40
40
  def ruby_fragment?
41
- @fragment_mode == :ruby
41
+ %i[ruby ruby_source].include?(@fragment_mode)
42
42
  end
43
43
 
44
44
  def ruby_helpers?
45
- @helpers_mode == :ruby
45
+ %i[ruby ruby_source].include?(@helpers_mode)
46
46
  end
47
47
 
48
48
  private
@@ -28,6 +28,13 @@ module RLSL
28
28
 
29
29
  def helpers_source(target)
30
30
  return source_snippet("") unless @definition.helpers_block
31
+ if @definition.helpers_mode == :ruby_source
32
+ source = @definition.helpers_block.call
33
+ return source_snippet(
34
+ helpers_transpiler.transpile_helpers_source(source, target, @definition.custom_functions),
35
+ format: :target
36
+ )
37
+ end
31
38
  return source_snippet(@definition.helpers_block.call) unless ruby_helpers?
32
39
 
33
40
  source_snippet(
@@ -38,6 +45,10 @@ module RLSL
38
45
 
39
46
  def fragment_source(target)
40
47
  return source_snippet("") unless @definition.fragment_block
48
+ if @definition.fragment_mode == :ruby_source
49
+ source = @definition.fragment_block.call
50
+ return source_snippet(fragment_transpiler.transpile_source(source, target), format: :target)
51
+ end
41
52
  return source_snippet(@definition.fragment_block.call) unless ruby_fragment?
42
53
 
43
54
  source_snippet(fragment_transpiler.transpile(@definition.fragment_block, target), format: :target)
@@ -63,7 +74,14 @@ module RLSL
63
74
  return {} unless ruby_helpers? && @definition.helpers_block
64
75
 
65
76
  @helper_globals ||= begin
66
- compilation = helpers_transpiler.compile_helpers(@definition.helpers_block, @definition.custom_functions)
77
+ compilation = if @definition.helpers_mode == :ruby_source
78
+ helpers_transpiler.compile_helpers_source(
79
+ @definition.helpers_block.call,
80
+ @definition.custom_functions
81
+ )
82
+ else
83
+ helpers_transpiler.compile_helpers(@definition.helpers_block, @definition.custom_functions)
84
+ end
67
85
  extract_global_types(compilation.ir)
68
86
  end
69
87
  end
@@ -10,7 +10,7 @@ module RLSL
10
10
  attr_reader :name
11
11
 
12
12
  def initialize(name, definition = ShaderDefinition.new)
13
- @name = name.to_s
13
+ @name = RLSL.validate_shader_name!(name)
14
14
  @definition = definition
15
15
  end
16
16
 
@@ -18,27 +18,62 @@ module RLSL
18
18
  if block_given?
19
19
  ctx = UniformContext.new
20
20
  ctx.instance_eval(&block)
21
- @definition = @definition.with_uniforms(ctx.uniforms)
21
+ update_definition(@definition.with_uniforms(ctx.uniforms))
22
22
  else
23
23
  @definition.uniforms
24
24
  end
25
25
  end
26
26
 
27
+ def uniform_types
28
+ @definition.uniforms
29
+ end
30
+
27
31
  def helpers(mode = :ruby, &block)
28
- @definition = @definition.with_helpers(mode: mode, block: block)
32
+ raise ArgumentError, "helpers requires a block" unless block
33
+
34
+ resolved_mode = mode.to_sym
35
+ raise ArgumentError, "helpers mode must be :c or :ruby" unless %i[c ruby].include?(resolved_mode)
36
+
37
+ if resolved_mode == :ruby
38
+ source_unit = Prism::SourceExtractor.new.extract_unit(block)
39
+ update_definition(@definition.with_helpers(mode: :ruby_source, block: -> { source_unit }))
40
+ else
41
+ update_definition(@definition.with_helpers(mode: :c, block: block))
42
+ end
43
+ end
44
+
45
+ def helpers_source(source)
46
+ update_definition(@definition.with_helpers(mode: :ruby_source, block: -> { source.to_s }))
29
47
  end
30
48
 
31
49
  def functions(&block)
32
50
  ctx = FunctionContext.new
33
51
  ctx.instance_eval(&block)
34
- @definition = @definition.with_custom_functions(ctx.functions)
52
+ update_definition(@definition.with_custom_functions(ctx.functions))
53
+ end
54
+
55
+ def fragment(mode = :auto, &block)
56
+ raise ArgumentError, "fragment requires a block" unless block
57
+
58
+ resolved_mode = if mode == :auto
59
+ block.parameters.empty? ? :c : :ruby
60
+ else
61
+ mode.to_sym
62
+ end
63
+ unless %i[c ruby].include?(resolved_mode)
64
+ raise ArgumentError, "fragment mode must be :c or :ruby"
65
+ end
66
+
67
+ if resolved_mode == :ruby
68
+ source_unit = Prism::SourceExtractor.new.extract_unit(block)
69
+ update_definition(@definition.with_fragment(mode: :ruby_source, block: -> { source_unit }))
70
+ else
71
+ update_definition(@definition.with_fragment(mode: :c, block: block))
72
+ end
35
73
  end
36
74
 
37
- def fragment(&block)
38
- @definition = @definition.with_fragment(
39
- mode: block.arity > 0 ? :ruby : :c,
40
- block: block
41
- )
75
+ def fragment_source(source)
76
+ update_definition(@definition.with_fragment(mode: :ruby_source, block: -> { source.to_s }))
42
77
  end
43
78
 
44
79
  def compile_and_load
@@ -68,7 +103,12 @@ module RLSL
68
103
  private
69
104
 
70
105
  def build_service
71
- BuildService.new(@name, @definition)
106
+ @build_service ||= BuildService.new(@name, @definition)
107
+ end
108
+
109
+ def update_definition(definition)
110
+ @definition = definition
111
+ @build_service = nil
72
112
  end
73
113
  end
74
114
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RLSL
4
+ IDENTIFIER_PATTERN = /\A[A-Za-z_][A-Za-z0-9_]*\z/
5
+ SHADER_NAME_PATTERN = IDENTIFIER_PATTERN
6
+
7
+ def self.validate_identifier!(name, context: "identifier")
8
+ normalized = name.to_s
9
+ return normalized if normalized.match?(IDENTIFIER_PATTERN)
10
+
11
+ raise ArgumentError,
12
+ "Invalid #{context} #{name.inspect}: use an ASCII identifier beginning with a letter or underscore"
13
+ end
14
+
15
+ def self.validate_shader_name!(name)
16
+ validate_identifier!(name, context: "shader name")
17
+ end
18
+ end
@@ -116,8 +116,8 @@ module RLSL
116
116
  bool: UniformTypeSpec.new(
117
117
  c_type: "int",
118
118
  glsl_type: "bool",
119
- wgsl_type: "bool",
120
- msl_type: "bool",
119
+ wgsl_type: "i32",
120
+ msl_type: "int",
121
121
  wrapper_kind: :bool,
122
122
  metal_alignment: 4,
123
123
  metal_size: 4,
@@ -156,7 +156,7 @@ module RLSL
156
156
  c_type: "sampler2D",
157
157
  glsl_type: "sampler2D",
158
158
  wgsl_type: "texture_2d<f32>",
159
- msl_type: nil,
159
+ msl_type: "texture2d<float>",
160
160
  compiled_supported: false,
161
161
  runtime_supported: false,
162
162
  function_shorthand: true
@@ -17,26 +17,24 @@ module RLSL
17
17
  return value if type.nil?
18
18
 
19
19
  spec = fetch(type)
20
- raise ArgumentError, unsupported_runtime_type_message(type, shader_name) unless spec.runtime_supported?
20
+ raise UniformValueError, unsupported_runtime_type_message(type, shader_name) unless spec.runtime_supported?
21
21
 
22
22
  case spec.wrapper_kind
23
23
  when :float
24
24
  Float(value)
25
25
  when :int
26
- Integer(value)
26
+ normalize_int(value, name: name, shader_name: shader_name)
27
27
  when :bool
28
28
  normalize_bool(value, name: name, shader_name: shader_name)
29
29
  when :vector
30
30
  normalize_vector(value, spec.vector_size, name: name, shader_name: shader_name)
31
31
  else
32
- raise ArgumentError, unsupported_runtime_type_message(type, shader_name)
32
+ raise UniformValueError, unsupported_runtime_type_message(type, shader_name)
33
33
  end
34
- rescue TypeError
35
- raise ArgumentError, invalid_uniform_message(name, type, value, shader_name)
36
- rescue ArgumentError => e
37
- raise e if e.message.start_with?("Invalid value for uniform", "Unsupported runtime uniform type")
38
-
39
- raise ArgumentError, invalid_uniform_message(name, type, value, shader_name)
34
+ rescue UniformValueError
35
+ raise
36
+ rescue TypeError, ArgumentError, RangeError
37
+ raise UniformValueError, invalid_uniform_message(name, type, value, shader_name)
40
38
  end
41
39
 
42
40
  def normalize_bool(value, name:, shader_name: nil)
@@ -44,12 +42,19 @@ module RLSL
44
42
  return false if value == 0
45
43
  return true if value == 1
46
44
 
47
- raise ArgumentError, invalid_uniform_message(name, :bool, value, shader_name)
45
+ raise UniformValueError, invalid_uniform_message(name, :bool, value, shader_name)
46
+ end
47
+
48
+ def normalize_int(value, name:, shader_name: nil)
49
+ integer = Integer(value)
50
+ return integer if (-2**31...2**31).cover?(integer)
51
+
52
+ raise UniformValueError, invalid_uniform_message(name, :int, value, shader_name)
48
53
  end
49
54
 
50
55
  def normalize_vector(value, vector_size, name:, shader_name: nil)
51
56
  unless value.is_a?(Array) && value.length == vector_size
52
- raise ArgumentError, invalid_vector_message(name, vector_size, value, shader_name)
57
+ raise UniformValueError, invalid_vector_message(name, vector_size, value, shader_name)
53
58
  end
54
59
 
55
60
  value.map { |component| Float(component) }
data/lib/rlsl/types.rb CHANGED
@@ -11,8 +11,4 @@ module RLSL
11
11
  extend TargetResolver
12
12
  extend ValueNormalizer
13
13
  end
14
-
15
- module TypeMapping
16
- C_UNIFORM_TYPES = UNIFORM_TYPE_SPECS.transform_values(&:c_type).freeze
17
- end
18
14
  end
@@ -3,6 +3,8 @@
3
3
  module RLSL
4
4
  # DSL context for defining uniform variables
5
5
  class UniformContext
6
+ RESERVED_NAMES = %i[resolution u frag_coord].freeze
7
+
6
8
  attr_reader :uniforms
7
9
 
8
10
  def initialize
@@ -10,7 +12,22 @@ module RLSL
10
12
  end
11
13
 
12
14
  def define_uniform(name, type)
13
- @uniforms[name] = type
15
+ normalized_name = RLSL.validate_identifier!(name, context: "uniform name").to_sym
16
+ if RESERVED_NAMES.include?(normalized_name)
17
+ raise ArgumentError, "Uniform name #{normalized_name.inspect} is reserved by RLSL"
18
+ end
19
+ if @uniforms.key?(normalized_name)
20
+ raise ArgumentError, "Uniform #{normalized_name.inspect} is already defined"
21
+ end
22
+ generated_sampler_names = @uniforms.filter_map do |uniform_name, uniform_type|
23
+ :"#{uniform_name}_sampler" if uniform_type == :sampler2D
24
+ end
25
+ if generated_sampler_names.include?(normalized_name) ||
26
+ (type == :sampler2D && @uniforms.key?(:"#{normalized_name}_sampler"))
27
+ raise ArgumentError, "Uniform #{normalized_name.inspect} conflicts with a generated sampler name"
28
+ end
29
+
30
+ @uniforms[normalized_name] = type
14
31
  end
15
32
 
16
33
  RLSL::UNIFORM_TYPES.each do |type|
data/lib/rlsl/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RLSL
4
- VERSION = "1.0.0"
4
+ VERSION = "1.0.1"
5
5
  end