glslkit
GLSLソースを前処理・解析し、ビルド時に得たリフレクション情報(uniform / attribute
/ output / uniform block)をJSONとして残すためのRuby gem。外部gemへの依存はゼロ
(stdlibの json strscan set digest のみを使う)で、将来
ruby.wasm 上のWebGLバインディングに
そのまま組み込めることを狙っている。
Rails (Propshaft) との統合は別gem glslkit-rails が提供する。
インストール
gem "glslkit"
使い方
require "glslkit"
resolver = Glslkit::Resolvers::FileSystem.new(load_paths: ["app/shaders"])
source = Glslkit::Preprocessor.new(resolver: resolver).process("pbr.frag")
source.code # => 平坦化済みGLSL (String)。#include解決済み、#lineディレクティブ付き
source.reflection # => Glslkit::Reflection (attributes / uniforms / uniform_blocks / outputs)
source.source_map # => Glslkit::SourceMap (#lineのファイルインデックス → パス)
source.digest # => SHA256 hex (String)
#include の解決
#include "common/math.glsl" // 相対探索 → load_pathsの順に探索
#include <glslkit/pbr.glsl> // load_pathsのみを探索(相対探索はしない)
- 循環includeは
Glslkit::CircularIncludeError #pragma onceは2回目以降の展開をスキップ(デフォルトは毎回展開)#versionは複数ファイルに散在していても1行目に1回だけ出力し、値が食い違えばGlslkit::VersionConflictError#if/#ifdef/#else/#endifは評価せずそのまま出力する(内部の#includeは展開される。これはv0.1の既知の制限)
wasm側でファイルシステムの無い環境向けに Glslkit::Resolvers::Hash も同梱している:
resolver = Glslkit::Resolvers::Hash.new(
"pbr.frag" => "...",
"common/math.glsl" => "..."
)
リフレクション抽出
source.reflection.attributes # => [#<Attribute name="a_position" type="vec3" location=0 array_size=1>, ...]
source.reflection.uniforms # => [#<Uniform name="u_mvp" type="mat4" setter="uniformMatrix4fv" matrix=true sampler=false ...>, ...]
source.reflection.uniform_blocks # => [#<UniformBlock name="Camera" layout="std140" binding=0>, ...]
source.reflection.outputs # => [#<Output name="fragColor" type="vec4" location=0>, ...]
型からWebGL2のsetterへの対応表は Glslkit::Types が唯一の正:
Glslkit::Types.setter_for("mat4") # => "uniformMatrix4fv"
Glslkit::Types.matrix?("mat4") # => true
Glslkit::Types.sampler?("sampler2D") # => true
マニフェスト生成
vertex/fragmentの Source ペアから、spec/schema/reflection-v1.json
(Draft 2020-12 JSON Schema) に準拠したマニフェストを組み立てる:
program = Glslkit::Program.new(name: "pbr", sources: {vertex: vertex_source, fragment: fragment_source})
manifest = Glslkit::Manifest.build(
programs: [program],
urls: {"pbr" => {vertex: "/assets/pbr-a1b2.vert", fragment: "/assets/pbr-c3d4.frag"}}
)
manifest["generator"] # => "glslkit/<version>" (このManifestを実際に組み立てたのがglslkitであることの来歴。手書きマニフェストには付かない)
JSON.generate(manifest)
同名uniform/uniform_blockはステージ間で1エントリに統合され、型
(またはlayout/binding)が食い違えば Glslkit::StageMismatchError になる。
Glslkit::Manifest.new + #add_program という下位レベルの組み立て方も
存在するが、generator: を渡し忘れると来歴情報が欠けたマニフェストになる
(生成物なのに手書き扱いされる)。特別な理由が無ければ Manifest.build を使うこと。
Minify
コメント・空白の除去のみを行う(識別子リネームはしない)。改行は一切削除しない
ため、#line ディレクティブの行番号や Reflection の抽出結果を壊さない:
Glslkit::Minifier.minify(source.code)
エラークラス
Glslkit::IncludeNotFound, Glslkit::PathTraversalError,
Glslkit::CircularIncludeError, Glslkit::VersionConflictError,
Glslkit::StageMismatchError, Glslkit::UnknownTypeError