マクロでやりたい放題 ~Nim のマクロについて~
Nim ってなんぞ
そもそも Nim ってなんぞ?という人がほとんどでしょうから、軽く説明します。
Nim は下記のようなキメラな静的型付きの言語です。
スコープが正気で式ベースの Python のようなオフサイドルールの構文を持つ
Pascal の風味もちょっとある(proc, result)
Lisp の影響を受けたマクロが使える(今回の話題)
Rust などの影響を受けたスコープベースのメモリ管理手法をデフォルトで採用している
コンパイルオプションで GC も選択可能
自分でメモリ管理することも可能
中間言語とそのコンパイラを選択可能で、実行ファイルを生成する
デフォルトでは C 言語を中間言語とし、GCC でコンパイルする
実行ファイルのみならず JavaScript も生成できる AltJS でもある(なんで?)
またこのような特徴もあります。
様々なプラットフォーム向けにコンパイルできる
他方で現状、サードパーティーライブラリは少なめ。一部の熱狂的なユーザーがめちゃくちゃ頑張ってる
C 言語のライブラリを容易に利用できる FFI
C++ にも対応している
JavaScript にも対応している(なんで?)
作ったもの
Nim だけでいわゆるシェーダー芸をやってみたかったので、Nim の proc(プロシージャ。副作用が許される関数のようなもの) を GLSL にトランスパイルする macro powered な 変態 ライブラリ shady を使い、ライブラリ morchella を実装してみました。 Windows 11 で morchella/examples/abstract_pattern_0.nim を Nim 2.0.0 でコンパイルして実行した様子。実行ファイルのサイズは 640KB 程。
https://scrapbox.io/files/65817773f8f8a70024fff2b0.png
Nim の AST とマクロ
Nim には Lisp から影響を受けた強力なマクロがあります。NimNode の階層構造で表される Nim の AST を、マクロを使ってコンパイル時に読んだり書いたりできます。proc をパースして GLSL にトランスパイルするところは、shady が toGLSL マクロでやってくれます。対象のコードがどういう階層構造になってるか知りたい場合は、std/macros にある dumpTree を使って調べることができます。dumpTree もマクロです。 例えばこんな感じに dumpTree 以下に proc を書くと、その AST がコンパイル時に表示されます。
code:dump_tree_fragment_shader_proc.nim
import std/macros
import morchella
import morchella/math
dumpTree:
proc fragmentShaderAbstractPattern0Proc(
gl_FragCoord: Vec4,
pixelColor: var Vec4
) =
var
uv = (gl_FragCoord.xy * vec2(2.0) - resolution.xy) / min(resolution.x, resolution.y)
a = vec2(sin(time), cos(time)) + uv
b = vec2(cos(time), sin(time)) + uv
c = dot(vec2(hashOld12(a - vec2(hash12(b)))), uv)
division = 3.0
d = length(vec3(division) * vec3(uv, 0.5) + fract3(vec3(division) * vec3(uv, 0.5)))
color = vec3(
smin(sin(c), cos(d), sin(time)) * sin(c + d),
smin(sin(c), cos(d), sin(time)) * sin(smin(c, d, time) + 5.0 * d),
smin(sin(c), cos(d), sin(time)) * sin(c + d + time + 10.0 * hash12(vec2(d)))
)
pixelColor = vec4(color, 1.0)
コンパイル時に表示される AST が以下になります。
code:proc_ast.nim
StmtList
ProcDef
Ident "fragmentShaderAbstractPattern0Proc"
Empty
Empty
FormalParams
Empty
IdentDefs
Ident "gl_FragCoord"
Ident "Vec4"
Empty
IdentDefs
Ident "resolution"
BracketExpr
Ident "Uniform"
Ident "Vec2"
Empty
IdentDefs
Ident "time"
BracketExpr
Ident "Uniform"
Ident "float32"
Empty
IdentDefs
Ident "pixelColor"
VarTy
Ident "Vec4"
Empty
Empty
Empty
StmtList
VarSection
IdentDefs
Ident "uv"
Empty
Infix
Ident "/"
Par
Infix
Ident "-"
Infix
Ident "*"
DotExpr
Ident "gl_FragCoord"
Ident "xy"
Call
Ident "vec2"
FloatLit 2.0
DotExpr
Ident "resolution"
Ident "xy"
Call
Ident "min"
DotExpr
Ident "resolution"
Ident "x"
DotExpr
Ident "resolution"
Ident "y"
IdentDefs
Ident "a"
Empty
Infix
Ident "+"
Call
Ident "vec2"
Call
Ident "sin"
Ident "time"
Call
Ident "cos"
Ident "time"
Ident "uv"
IdentDefs
Ident "b"
Empty
Infix
Ident "+"
Call
Ident "vec2"
Call
Ident "cos"
Ident "time"
Call
Ident "sin"
Ident "time"
Ident "uv"
IdentDefs
Ident "c"
Empty
Call
Ident "dot"
Call
Ident "vec2"
Call
Ident "hashOld12"
Infix
Ident "-"
Ident "a"
Call
Ident "vec2"
Call
Ident "hash12"
Ident "b"
Ident "uv"
IdentDefs
Ident "division"
Empty
FloatLit 3.0
IdentDefs
Ident "d"
Empty
Call
Ident "length"
Infix
Ident "+"
Infix
Ident "*"
Call
Ident "vec3"
Ident "division"
Call
Ident "vec3"
Ident "uv"
FloatLit 0.5
Call
Ident "fract3"
Infix
Ident "*"
Call
Ident "vec3"
Ident "division"
Call
Ident "vec3"
Ident "uv"
FloatLit 0.5
IdentDefs
Ident "color"
Empty
Call
Ident "vec3"
Infix
Ident "*"
Call
Ident "smin"
Call
Ident "sin"
Ident "c"
Call
Ident "cos"
Ident "d"
Call
Ident "sin"
Ident "time"
Call
Ident "sin"
Infix
Ident "+"
Ident "c"
Ident "d"
Infix
Ident "*"
Call
Ident "smin"
Call
Ident "sin"
Ident "c"
Call
Ident "cos"
Ident "d"
Call
Ident "sin"
Ident "time"
Call
Ident "sin"
Infix
Ident "+"
Call
Ident "smin"
Ident "c"
Ident "d"
Ident "time"
Infix
Ident "*"
FloatLit 5.0
Ident "d"
Infix
Ident "*"
Call
Ident "smin"
Call
Ident "sin"
Ident "c"
Call
Ident "cos"
Ident "d"
Call
Ident "sin"
Ident "time"
Call
Ident "sin"
Infix
Ident "+"
Infix
Ident "+"
Infix
Ident "+"
Ident "c"
Ident "d"
Ident "time"
Infix
Ident "*"
FloatLit 10.0
Call
Ident "hash12"
Call
Ident "vec2"
Ident "d"
Asgn
Ident "pixelColor"
Call
Ident "vec4"
Ident "color"
FloatLit 1.0
こんな感じで表現されます。NimNode を使って対象のコードを生成する方法を知りたい場合は、dumpAstGen を使うといいです。S 式で表示する dumpLisp もあります。夢が広がりますね。 ちなみに、先程の dumpTree 以下に書いた fragmentShaderAbstractPattern0Proc に対して toGLSL マクロを こんな感じに呼び出して パースし、トランスパイルした後の GLSL のコードは下記になります。proc から呼ばれている proc もちゃんと GLSL にトランスパイルされています。ようやりますね。 code: transpiled_glsl_from_nim.glsl
precision highp float;
// from vertexShaderBasic
in vec2 position;
void main() {
gl_Position = vec4(position.x, position.y, 0.0, 1.0);
}
precision highp float;
// from fragmentShaderAbstractPattern0Proc
float hash12(vec2 p);
float hashOld12(vec2 p);
float fract(float x);
float smin(float d1, float d2, float k);
vec3 fract3(vec3 x);
float hash12(
vec2 p
) {
float result;
vec3 p3 = fract3(vec3(p.xyx) * vec3(0.1031));
p3 += vec3(dot(p3, p3.yzx + 33.33));
result = fract((p3.x + p3.y) * p3.z);
return result;
}
float hashOld12(
vec2 p
) {
float result;
result = fract(float(float(sin(dot(p, vec2(12.9898, 78.233)))) * 43758.5453));
return result;
}
float fract(
float x
) {
float result;
result = x - floor(x);
return result;
}
float smin(
float d1,
float d2,
float k
) {
float result;
float h = clamp(0.5 + (0.5 * float(d2 - d1)) / float(k), 0.0, 1.0);
result = float(float(mix(d2, d1, float(h))) - (float(k) * h) * (1.0 - h));
return result;
}
vec3 fract3(
vec3 x
) {
vec3 result;
result = x - floor(x);
return result;
}
layout(origin_upper_left) in vec4 gl_FragCoord;
uniform vec2 resolution;
uniform float time;
out vec4 pixelColor;
void main() {
vec2 uv = (gl_FragCoord.xy * vec2(2.0) - resolution.xy) / min(resolution.x, resolution.y);
vec2 a = vec2(sin(time), cos(time)) + uv;
vec2 b = vec2(cos(time), sin(time)) + uv;
float c = dot(vec2(hashOld12(a - vec2(hash12(b)))), uv);
float division = 3.0;
float d = length(vec3(float(division)) * vec3(uv, 0.5) + fract3(vec3(float(division)) * vec3(uv, 0.5)));
vec3 color = vec3(smin(sin(c), cos(d), sin(time)) * sin(c + d), float(float(smin(sin(c), cos(d), sin(time))) * sin(float(smin(c, d, time)) + 5.0 * float(d))), float(float(smin(sin(c), cos(d), sin(time))) * sin(float((c + d) + time) + 10.0 * float(hash12(vec2(d))))));
pixelColor = vec4(color, 1.0);
}
おわり
Nim をよろしくお願いいたします。
明日は jellyfish_rumble さんの「今年、金を払ってよかったもの」です。