glslkit 0.1.0.pre
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 +7 -0
- data/CHANGELOG.md +32 -0
- data/LICENSE.txt +21 -0
- data/README.md +104 -0
- data/glslkit.gemspec +25 -0
- data/lib/glslkit/bundle.rb +118 -0
- data/lib/glslkit/diagnostic.rb +27 -0
- data/lib/glslkit/digest.rb +18 -0
- data/lib/glslkit/errors.rb +14 -0
- data/lib/glslkit/manifest.rb +198 -0
- data/lib/glslkit/minifier.rb +33 -0
- data/lib/glslkit/preprocessor.rb +131 -0
- data/lib/glslkit/program.rb +30 -0
- data/lib/glslkit/reflection.rb +178 -0
- data/lib/glslkit/resolver.rb +22 -0
- data/lib/glslkit/resolvers/file_system.rb +62 -0
- data/lib/glslkit/resolvers/hash.rb +24 -0
- data/lib/glslkit/runtime.rb +20 -0
- data/lib/glslkit/source.rb +5 -0
- data/lib/glslkit/source_map.rb +88 -0
- data/lib/glslkit/types.rb +97 -0
- data/lib/glslkit/validator.rb +228 -0
- data/lib/glslkit/version.rb +5 -0
- data/lib/glslkit.rb +21 -0
- metadata +67 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "diagnostic"
|
|
4
|
+
|
|
5
|
+
module Glslkit
|
|
6
|
+
# Glslkit::Program(§8.3)を1つ受け取り、単一ステージで完結する検証ルールを
|
|
7
|
+
# 実行する。マージ前のReflectionを直接見るため、Manifestが統合してしまう
|
|
8
|
+
# 情報(同一ステージ内の重複など)も検出できる。例外は投げず、診断の配列を
|
|
9
|
+
# 常に返す。
|
|
10
|
+
class Validator
|
|
11
|
+
class Result
|
|
12
|
+
def initialize(diagnostics)
|
|
13
|
+
@diagnostics = diagnostics
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
attr_reader :diagnostics
|
|
17
|
+
|
|
18
|
+
def errors
|
|
19
|
+
diagnostics.select(&:error?)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def warnings
|
|
23
|
+
diagnostics.select(&:warning?)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def ok?
|
|
27
|
+
errors.empty?
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def initialize(disabled: [])
|
|
32
|
+
@disabled = disabled.map(&:to_s)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def validate(program)
|
|
36
|
+
diagnostics = []
|
|
37
|
+
diagnostics.concat(check_out_in_correspondence(program))
|
|
38
|
+
diagnostics.concat(check_cross_stage_uniform_mismatch(program)) if enabled?("E003")
|
|
39
|
+
diagnostics.concat(check_attribute_location_collisions(program)) if enabled?("E004")
|
|
40
|
+
diagnostics.concat(check_output_location_collisions(program)) if enabled?("E005")
|
|
41
|
+
diagnostics.concat(check_duplicate_names_within_a_stage(program)) if enabled?("E006")
|
|
42
|
+
diagnostics.concat(check_reserved_gl_prefix(program)) if enabled?("E007")
|
|
43
|
+
Result.new(diagnostics)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
# E001/E002/W001はvertexのoutとfragmentのin(Reflection上はどちらも
|
|
49
|
+
# `attributes`/`outputs`という同じ構造体で表現される)を名前で突き合わせる。
|
|
50
|
+
# 3つとも両ステージが揃っていないと意味を持たないため、片方でも欠けていれば
|
|
51
|
+
# 何も検出しない(§8.3 E002のスキップ条件と同じ扱いをW001/E001にも広げる)。
|
|
52
|
+
#
|
|
53
|
+
# アンカーはSPEC.md §8.3の規則通り: fragment側に該当宣言があればfragment、
|
|
54
|
+
# 無ければvertexを指す。E001は必ず両側に宣言があるのでfragment固定、
|
|
55
|
+
# E002はfragment側にのみ、W001はvertex側にのみ宣言がある。
|
|
56
|
+
def check_out_in_correspondence(program)
|
|
57
|
+
vertex = program.sources[:vertex]
|
|
58
|
+
fragment = program.sources[:fragment]
|
|
59
|
+
return [] unless vertex && fragment
|
|
60
|
+
|
|
61
|
+
vertex_out = index_by_name(vertex.reflection.outputs)
|
|
62
|
+
fragment_in = index_by_name(fragment.reflection.attributes)
|
|
63
|
+
|
|
64
|
+
diagnostics = []
|
|
65
|
+
diagnostics.concat(check_type_mismatch(program, fragment, vertex_out, fragment_in)) if enabled?("E001")
|
|
66
|
+
diagnostics.concat(check_missing_vertex_out(program, fragment, vertex_out, fragment_in)) if enabled?("E002")
|
|
67
|
+
diagnostics.concat(check_missing_fragment_in(program, vertex, vertex_out, fragment_in)) if enabled?("W001")
|
|
68
|
+
diagnostics
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def check_type_mismatch(program, fragment, vertex_out, fragment_in)
|
|
72
|
+
(vertex_out.keys & fragment_in.keys).filter_map do |name|
|
|
73
|
+
v, f = vertex_out[name], fragment_in[name]
|
|
74
|
+
next if v.type == f.type
|
|
75
|
+
|
|
76
|
+
message = "\"#{name}\": vertex out is #{v.type}, fragment in is #{f.type}"
|
|
77
|
+
build_diagnostic(program, :fragment, fragment, "E001", name, f.output_line, message)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def check_missing_vertex_out(program, fragment, vertex_out, fragment_in)
|
|
82
|
+
(fragment_in.keys - vertex_out.keys).map do |name|
|
|
83
|
+
f = fragment_in[name]
|
|
84
|
+
message = "\"#{name}\": fragment in has no matching vertex out"
|
|
85
|
+
build_diagnostic(program, :fragment, fragment, "E002", name, f.output_line, message, severity: :warning)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def check_missing_fragment_in(program, vertex, vertex_out, fragment_in)
|
|
90
|
+
(vertex_out.keys - fragment_in.keys).map do |name|
|
|
91
|
+
v = vertex_out[name]
|
|
92
|
+
message = "\"#{name}\": vertex out has no matching fragment in"
|
|
93
|
+
build_diagnostic(program, :vertex, vertex, "W001", name, v.output_line, message, severity: :warning)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# 既存のManifest#merge_uniforms/#merge_uniform_blocksがStageMismatchErrorを
|
|
98
|
+
# 投げる条件を、マージ前に同じロジックで検出する(§8.4「E003の扱い」)。
|
|
99
|
+
# 名前が両ステージに存在する場合のみ比較する(片方だけならエラーにならない、
|
|
100
|
+
# という既存のManifestの挙動に合わせる)。
|
|
101
|
+
def check_cross_stage_uniform_mismatch(program)
|
|
102
|
+
vertex = program.sources[:vertex]
|
|
103
|
+
fragment = program.sources[:fragment]
|
|
104
|
+
return [] unless vertex && fragment
|
|
105
|
+
|
|
106
|
+
check_uniform_type_mismatch(program, vertex, fragment) +
|
|
107
|
+
check_uniform_block_mismatch(program, vertex, fragment)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def check_uniform_type_mismatch(program, vertex, fragment)
|
|
111
|
+
vertex_uniforms = index_by_name(vertex.reflection.uniforms)
|
|
112
|
+
fragment_uniforms = index_by_name(fragment.reflection.uniforms)
|
|
113
|
+
|
|
114
|
+
(vertex_uniforms.keys & fragment_uniforms.keys).filter_map do |name|
|
|
115
|
+
v, f = vertex_uniforms[name], fragment_uniforms[name]
|
|
116
|
+
next if v.type == f.type
|
|
117
|
+
|
|
118
|
+
message = "uniform \"#{name}\" is #{v.type} in vertex but #{f.type} in fragment"
|
|
119
|
+
build_diagnostic(program, :fragment, fragment, "E003", name, f.output_line, message)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def check_uniform_block_mismatch(program, vertex, fragment)
|
|
124
|
+
vertex_blocks = index_by_name(vertex.reflection.uniform_blocks)
|
|
125
|
+
fragment_blocks = index_by_name(fragment.reflection.uniform_blocks)
|
|
126
|
+
|
|
127
|
+
(vertex_blocks.keys & fragment_blocks.keys).filter_map do |name|
|
|
128
|
+
v, f = vertex_blocks[name], fragment_blocks[name]
|
|
129
|
+
next if v.layout == f.layout && v.binding == f.binding
|
|
130
|
+
|
|
131
|
+
message = "uniform block \"#{name}\" differs between vertex and fragment " \
|
|
132
|
+
"(layout: #{v.layout} vs #{f.layout}, binding: #{v.binding.inspect} vs #{f.binding.inspect})"
|
|
133
|
+
build_diagnostic(program, :fragment, fragment, "E003", name, f.output_line, message)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def index_by_name(entries)
|
|
138
|
+
entries.each_with_object({}) { |entry, hash| hash[entry.name] = entry }
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def enabled?(code)
|
|
142
|
+
!@disabled.include?(code)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# attributeはvertexステージにしか存在しない(SPEC.md §4のManifest決定)。
|
|
146
|
+
def check_attribute_location_collisions(program)
|
|
147
|
+
stage = :vertex
|
|
148
|
+
source = program.sources[stage]
|
|
149
|
+
return [] unless source
|
|
150
|
+
|
|
151
|
+
duplicate_groups_by(source.reflection.attributes, &:location).flat_map do |location, entries|
|
|
152
|
+
message = "attribute location #{location} is used by #{entries.map(&:name).join(", ")}"
|
|
153
|
+
[group_diagnostic(program, stage, source, "E004", entries, message)]
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# outputはfragmentステージにしか存在しない(同上)。
|
|
158
|
+
def check_output_location_collisions(program)
|
|
159
|
+
stage = :fragment
|
|
160
|
+
source = program.sources[stage]
|
|
161
|
+
return [] unless source
|
|
162
|
+
|
|
163
|
+
duplicate_groups_by(source.reflection.outputs, &:location).flat_map do |location, entries|
|
|
164
|
+
message = "output location #{location} is used by #{entries.map(&:name).join(", ")}"
|
|
165
|
+
[group_diagnostic(program, stage, source, "E005", entries, message)]
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# GLSLの識別子はattribute/uniform/uniform block/outputを問わず1つの
|
|
170
|
+
# 名前空間を共有するため、種類をまたいで同名かどうかをチェックする。
|
|
171
|
+
def check_duplicate_names_within_a_stage(program)
|
|
172
|
+
Program::STAGES.flat_map do |stage|
|
|
173
|
+
source = program.sources[stage]
|
|
174
|
+
next [] unless source
|
|
175
|
+
|
|
176
|
+
duplicate_groups_by(all_declarations(source.reflection), &:name).flat_map do |name, entries|
|
|
177
|
+
[group_diagnostic(program, stage, source, "E006", entries, "\"#{name}\" is declared more than once")]
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def check_reserved_gl_prefix(program)
|
|
183
|
+
Program::STAGES.flat_map do |stage|
|
|
184
|
+
source = program.sources[stage]
|
|
185
|
+
next [] unless source
|
|
186
|
+
|
|
187
|
+
all_declarations(source.reflection).select { |entry| entry.name.start_with?("gl_") }.map do |entry|
|
|
188
|
+
message = "\"#{entry.name}\" starts with the reserved prefix \"gl_\""
|
|
189
|
+
build_diagnostic(program, stage, source, "E007", entry.name, entry.output_line, message)
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def all_declarations(reflection)
|
|
195
|
+
reflection.attributes + reflection.uniforms + reflection.uniform_blocks + reflection.outputs
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# yieldした値がnilのエントリは無視する(未指定location同士を衝突扱いに
|
|
199
|
+
# しないため)。出現順を保ったまま、2件以上あるグループだけを返す。
|
|
200
|
+
def duplicate_groups_by(entries)
|
|
201
|
+
groups = Hash.new { |h, k| h[k] = [] }
|
|
202
|
+
entries.each do |entry|
|
|
203
|
+
key = yield entry
|
|
204
|
+
groups[key] << entry unless key.nil?
|
|
205
|
+
end
|
|
206
|
+
groups.select { |_key, group| group.size > 1 }
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def group_diagnostic(program, stage, source, code, entries, message)
|
|
210
|
+
anchor = entries.min_by(&:output_line)
|
|
211
|
+
build_diagnostic(program, stage, source, code, nil, anchor.output_line, message)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def build_diagnostic(program, stage, source, code, name, output_line, message, severity: :error)
|
|
215
|
+
file, line = source.source_map&.resolve(output_line) || [nil, nil]
|
|
216
|
+
Diagnostic.new(
|
|
217
|
+
severity: severity,
|
|
218
|
+
code: code,
|
|
219
|
+
message: message,
|
|
220
|
+
program: program.name,
|
|
221
|
+
stage: stage,
|
|
222
|
+
name: name,
|
|
223
|
+
file: file,
|
|
224
|
+
line: line
|
|
225
|
+
)
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|
data/lib/glslkit.rb
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "glslkit/version"
|
|
4
|
+
require_relative "glslkit/errors"
|
|
5
|
+
require_relative "glslkit/types"
|
|
6
|
+
require_relative "glslkit/resolver"
|
|
7
|
+
require_relative "glslkit/resolvers/file_system"
|
|
8
|
+
require_relative "glslkit/resolvers/hash"
|
|
9
|
+
require_relative "glslkit/source_map"
|
|
10
|
+
require_relative "glslkit/source"
|
|
11
|
+
require_relative "glslkit/reflection"
|
|
12
|
+
require_relative "glslkit/preprocessor"
|
|
13
|
+
require_relative "glslkit/manifest"
|
|
14
|
+
require_relative "glslkit/minifier"
|
|
15
|
+
require_relative "glslkit/program"
|
|
16
|
+
require_relative "glslkit/diagnostic"
|
|
17
|
+
require_relative "glslkit/validator"
|
|
18
|
+
require_relative "glslkit/bundle"
|
|
19
|
+
|
|
20
|
+
module Glslkit
|
|
21
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: glslkit
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0.pre
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- kyubey1228
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 2026-08-24 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: 'Flattens #include directives in GLSL shaders and extracts uniform/attribute/output
|
|
13
|
+
reflection data as JSON, with zero runtime dependencies so it can run under ruby.wasm.'
|
|
14
|
+
email:
|
|
15
|
+
- kyuuka1228@gmail.com
|
|
16
|
+
executables: []
|
|
17
|
+
extensions: []
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- CHANGELOG.md
|
|
21
|
+
- LICENSE.txt
|
|
22
|
+
- README.md
|
|
23
|
+
- glslkit.gemspec
|
|
24
|
+
- lib/glslkit.rb
|
|
25
|
+
- lib/glslkit/bundle.rb
|
|
26
|
+
- lib/glslkit/diagnostic.rb
|
|
27
|
+
- lib/glslkit/digest.rb
|
|
28
|
+
- lib/glslkit/errors.rb
|
|
29
|
+
- lib/glslkit/manifest.rb
|
|
30
|
+
- lib/glslkit/minifier.rb
|
|
31
|
+
- lib/glslkit/preprocessor.rb
|
|
32
|
+
- lib/glslkit/program.rb
|
|
33
|
+
- lib/glslkit/reflection.rb
|
|
34
|
+
- lib/glslkit/resolver.rb
|
|
35
|
+
- lib/glslkit/resolvers/file_system.rb
|
|
36
|
+
- lib/glslkit/resolvers/hash.rb
|
|
37
|
+
- lib/glslkit/runtime.rb
|
|
38
|
+
- lib/glslkit/source.rb
|
|
39
|
+
- lib/glslkit/source_map.rb
|
|
40
|
+
- lib/glslkit/types.rb
|
|
41
|
+
- lib/glslkit/validator.rb
|
|
42
|
+
- lib/glslkit/version.rb
|
|
43
|
+
homepage: https://github.com/kyubey1228/glslkit
|
|
44
|
+
licenses:
|
|
45
|
+
- MIT
|
|
46
|
+
metadata:
|
|
47
|
+
homepage_uri: https://github.com/kyubey1228/glslkit
|
|
48
|
+
source_code_uri: https://github.com/kyubey1228/glslkit/tree/main/core
|
|
49
|
+
changelog_uri: https://github.com/kyubey1228/glslkit/blob/main/core/CHANGELOG.md
|
|
50
|
+
rdoc_options: []
|
|
51
|
+
require_paths:
|
|
52
|
+
- lib
|
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
54
|
+
requirements:
|
|
55
|
+
- - ">="
|
|
56
|
+
- !ruby/object:Gem::Version
|
|
57
|
+
version: '3.1'
|
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
59
|
+
requirements:
|
|
60
|
+
- - ">="
|
|
61
|
+
- !ruby/object:Gem::Version
|
|
62
|
+
version: '0'
|
|
63
|
+
requirements: []
|
|
64
|
+
rubygems_version: 3.6.2
|
|
65
|
+
specification_version: 4
|
|
66
|
+
summary: GLSL preprocessing and reflection toolkit
|
|
67
|
+
test_files: []
|