型
MFGでは変数や定数には型があります。 基本的には数値型とそれのタプルのみです。
テンソルやsamplerは型とは別の扱いとなります。
また、タプルの特殊なケースをベクトルとして扱う、という機能もあります。 ここではそうした型に関する話を見ていきます。
Enum型もありますが、これは現時点では非常に限定的な場所でしか使う事が出来ないので、最後に軽く触れるに留めます。
数値型
MFGの基本となる数値型には、符号無し整数、符号あり整数、浮動小数点数の3つがあり、それぞれu, i, fと呼ばれます。 これにビット数をつけて、i32, u8, f32などと使われます。
fは32のみサポートしています。
iは8, 16, 32をサポートしていますが、 下のハードウェアとしては32ビットとして扱うものも多いため、 あまり小さい型を使うメリットはありません。 基本的にはiはi32を使う事を推奨しています。
また、u32はあまり使わず、整数はなるべくi32を使う事を推奨しています。
例外はu8とu16を色のBGRA成分として使う場合で、この場合はu8とu16を使う事になります。
つまり、数値型は以下が基本となります。
- f32
- i32
- u8とu16 (色成分として)
数値のリテラル
123 などと整数を書くと、これはi32とみなされます。 サイズがいくつでもi32とみなしますし、u8を要求する所に123などと書くと型のミスマッチでエラーとなります。
123.0 などの小数のリテラルはf32となります。
16進数リテラルもあり、0xで始めます。0xffなどです。これもi32です。
u32のリテラルとして末尾にuをつける、123u というものも存在していますが、 あまり使用は推奨しません。整数はなるべくi32で計算をするようにしましょう。
数値型のキャスト
数値型のキャストは、型名のあとにカッコをくくって行います。 型名と同じ関数を呼び出す、と考えても構いません。
例えば以下。
let a = i32(12.0)
キャスト演算子のベクトライズ
キャスト演算子はタプルに対してベクトライズされます。
let a = i32([12.0, 3u, 1])
aはi32のタプルとなります。 ベクトライズに関しては、式とベクトライズ演算を参照ください。
論理値としての整数
bool型は無く、i32が代わりに使われる。 0がfalse, 0以外をtrueとして扱う。
例えば && や != などはi32を返す。
タプル型
タプルは数値を並べたものです。 タプルの要素は別々の型でも構いません。
タプルは大括弧で要素をカンマで区切って作ります。
let tup = [1, 12.0, 3u]
タプルの要素は全て数値型で、現時点ではネストはサポートしていません。
タプルの要素のアクセスにはdestructuringとswizzle演算子、インデックスでのアクセスがあります。
destructuring
タプルの中身を直接変数に取り出す、destructuringがあります。
let tup = [1, 12.0, 3u]
# destructuringの例
let [a, b, c] = tup
上記の例では、a, b, cという変数が出来ます。型はそれぞれi32, f32, u32となります。
使わない要素に関してはアンダースコアを使う事が出来ます。1番目と3番目の要素だけ使いたい場合は以下のようになります。
let [a, _, c] = tup
swizzle演算子
タプルはswizzle演算子をサポートしています。
- xyzwの組み合わせのみサポート
- 右辺値のみ(そもそもタプルのmutationは対応してない)
- ベクトルである必要は無い
let a = [1, 2, 3].xxyy
aは[1, 1, 2, 2]のタプルとなります。
一つだけ指定した場合はスカラーになります。
インデックスでのアクセス
destructuringとswizzleがメインであまり使う機会は多くありませんが、 数値のインデックスを指定して1要素だけ取り出す事も出来ます。
ドットのあとに数字で取り出します。0オリジンです。
let tup = [1, 2, 3, 4, 5, 6]
# 数値インデックスのアクセス、0オリジンなのでaは5になる
let a = tup.4
この数値を変数にする方法はありません。 それは左辺の型が定まらない為です。
ベクトルなどどこをアクセスしても同じタプルの場合は理論的には可能なので将来のバージョンではサポートされるかもしれませんが、 現時点では定数でのインデックスのみサポートしています。
タプルの型は静的に決まらないといけない
少し細かい話ですが、MFGではタプルの型は静的に決まる必要があります。 要素の個数と、各要素の型がパースの時点で確定していなくてはいけません。 動的に個数や中身が変わる型などはサポートしていません。
タプルとベクトル
特別な条件を満たしたタプルを、ベクトルと呼びます。
特別な条件とは以下になります。
- 1次元以上、4次元以下
- 全部の要素が同じ数値型
この2つの条件を満たすタプルを、ベクトルと呼びます。
MFGではいくつかの関数はスカラーまたはベクトルのみをサポートしているため、 ある変数がベクトルかどうかが重要になる事があります。
組み込み関数の各関数の項目でベクトルをサポートしているものにはその旨記載があります。
ベクトルの条件を満たしていれば、基本的にはタプルとして扱えます。 例えばベクトルをノーマライズする関数、normalizeは以下のように呼ぶ事が出来ます。
normalize([1.2, 3.2])
結果はタプルとして扱えます。
let [x0, y0] = normalize([1.2, 3.2])
以下のように他の関数の引数にも使う事が出来ます。
let len = length(normalize([1.2, 3.2]))
ただしベクトル対応の関数は4次元までしかサポートしない為、例えば以下のようなコードは動きません。
# NG! 5次元はベクトルとしては使えない
normalize([1.0, 2,0, 3.0, 4.0, 5.0])
ベクトルの型の表記
ベクトルの型は、f32v3やf32v4などのように、 構成要素の型(この場合はf32)のあとにベクトル表すv、そして次元の数の3や4をつけて表します。
i32v2などもあります。
vec2, vec3, vec4関数
スカラーから2次元ベクトルを作るvec2、3次元ベクトルを作るvec3と、4次元ベクトルを作るvec4という関数があります。
let fvec = vec3(3.0)
let ivec = vec4(1)
複数要素を指定してベクトルを作りたい場合は、これでは無く次のキャストのシンタックスシュガーを用います。
タプルのキャストのシンタックスシュガー
ベクトルは同じ型で無くてはいけない為、ベクトルを作る手っ取り早い方法としてキャストをする、という事は良くあります。 また、色はu8の4次元ベクトルで扱う事が多いため、 この場合もu8にキャストする必要があります。
タプルのリテラルをキャストしたい事は良くある為、 シンタックスシュガーを提供しています。
具体的には型名のあとのカッコを省いて、直接大括弧を書く事が出来ます。
例えば以下の2つのコードは同じ意味になります。
u8([0, 0, 0xff, 0xff])
u8[0, 0, 0xff, 0xff]
このシンタックスシュガーは、u8のベクトルのリテラルであるかのように読めるようにと導入されました。 リテラルと言いましたが、中は変数でも使えます。
u8[b, g, r, a]
タプルのsplat演算子
大括弧の中と関数呼び出しのカッコの中には、splat演算子*と呼ばれるものを使う事が出来ます。 これはsplat演算子のつけられたタプルを展開したものをそこに書いたかのように振る舞います。
例えば以下です。
let tup1 = [1, 2, 3]
# [1, 2, 3, 4]になる
let a1 = [*tup1, 4]
tup1を展開したものを並べたかのようになる為、a1は4要素のタプルとなります。
swizzle演算子と組み合わせて、BGRAのBGRだけを処理する場合に良く使われます。
let col = input_u8(x, y)
let bgr = col.xyz
let a = col.a
let bgr2 = bgr/128
u8[*bgr2, a]
Enum型
現在の所、一部の関数の引数でしか使う事は出来ませんが、 内部的にはEnum型も存在しています。
良く使うのはsamplerの引数です。
let finput = sampler<input_u8>(address=.ClampToEdge, coord=.NormalizedLinear)
.ClampToEdge や、 .NormalizedLinear がEnum型の値となります。
この例ではaddress引数はSamplerAddressModeというEnum型の値を取る事が出来、coord引数は SamplerCoord というEnum型の値を取る事が出来るのですが、 これらの型を指定する方法が型推論以外に無いため、現時点ではEnumは関数呼び出しやsamplerの引数でしか使う事が出来ません。
Enum型の値は.で始まる識別子で、その引数の型推論で判明したEnum型で許される識別子だけが来る事が出来ます。
Range型
型としてはRange型というのも存在します。 ですがこれも特殊な場所でしか使う事が出来ない特殊な型です。
Range型はrsumなどのループ系関数の引数に使用します。
let s = rsum(0..<5) |i| { i*2 }
詳細はifelとループの「範囲」のセクションを参照ください。