コンテンツにスキップ

GPU カーネルの実行

書き方説明注意
Gpu.Load(current, source)画像をバッファへ読み込む大きさが違うときは拡大縮小して入ります。画像を数として使うときは、その画像のインポート設定で sRGB を切ります。
Gpu.Buffer(64, 64)バッファの生成作ったあとの大きさは Udon 側からも buffer.width / buffer.height で読めます。
Gpu.Buffer(64, 64, GpuFormat.Half)形式を選んだ生成0..1 の外の値(負の値・1 を超える値)も持てて、刻みも細かくなります。そのぶん面積は倍になります。形式は書くときに決まっている必要があります(変数では渡せません)。Gpu.Pack16x2 は 8 ビットの成分に値を詰め直す書き方なので、この形式では使いません。**持てる大きさには上限があり、65504 を超えた値は 65504 のまま留まります。**無限大にはならず、エラーも警告も出ません。**このバッファへ値を書き込むときは、元の側も 16 ビットの小数にします。**8 ビットの画像を Gpu.Load で写すと、0..1 の外の値は届く前に潰れます。潰れたことは何も知らせません。
Gpu.Swap(ref a, ref b)2 つの入れ替え
Gpu.Run(nameof(Step), next, current)実行の呼び出し第 3 引数以降が、カーネルの第 2 引数以降に順番どおり対応します。
Gpu.Run(nameof(Step), next, current, phase)値を渡した実行
Gpu.Run("Step", next, current)名前の文字列指定nameof を使うと、綴りを間違えたときに C# の側で気づけます。
Gpu.Run(nameof(Fade), current, next)複数のカーネル
Gpu.Run(nameof(Blend), next, current, weights)表を渡した実行大きさは引数に [Capacity(16)] のように書きます(シェーダの中に定数として置かれるため、大きさが決まっていないと作れません)。大きさは 2 の冪で、上限は 1024 です。添字がその範囲を出たときは端から回り込みます。表はカーネルの中では読むだけです。1 つのカーネルに何枚でも取れますが、大きさの合計は 4000 までです(表はシェーダの定数として置かれます。定数は全体で 4096 までで、表以外の引数もそこに入ります)。渡した配列が無いときと、長さが書いた数と違うときは、その実行は何も起きません(何枚か取るときは、1 枚でも当てはまれば起きません)。
[Capacity(4)] Vector4[] w小さい表
Gpu.Run(nameof(Seed), board, 0.25f)読み元を取らない実行担当するセルの座標は書き込み先で決まるため、前のフレームを読まない絵(初期状態の書き込み・座標だけで作る模様)はこれで書けます。
Gpu.Reduce(nameof(Brighter), middle, full)まとめ方の指定書き込み先の 1 セルには、読み元のうちそのセルが受け持つ範囲をまとめた値が入ります。段は自分で並べます(64 → 8 → 1 と書いた回数が、そのまま実行の回数になります)。
Gpu.Max(peak, full)成分ごとの最大と最小4 つの成分をそれぞれ独立にまとめます。書き込み先は呼びごとに別のバッファにします(同じバッファへ Gpu.Max と Gpu.Min を続けると、後の呼びが前の結果を上書きします)。「ある成分が最も大きいセルの色そのもの」が必要な場合は Gpu.Reduce で書きます。
Gpu.Show(current, display)Renderer への表示
Gpu.Texture(current)テクスチャとしての取り出し返るのはバッファそのもので、複製ではありません。
VRCGraphics.Blit(Gpu.Texture(current), target)自前の RenderTexture への書き出し写す先は RenderTextureReadWrite.Linear で作ります(既定では sRGB が付き、カーネルが返した値がそのまま入りません)。FilterMode を Point にしておくと、Gpu.Buffer が作るバッファと同じ読み方になります。
public GpuBuffer2D target;書き込み先を外から挿す口そこへ自分で用意した RenderTexture を挿すと、カーネルの書き込み先がそれになります。挿したものの設定はそのまま使われるので、sRGB と FilterMode は上と同じように揃えます。
VRCAsyncGPUReadback.Request(Gpu.Texture(current), ...)値としての読み戻し頼んだその場では返らず、GPU の仕事が終わってから OnAsyncGpuReadbackComplete が呼ばれます。既定のバッファでは、届くのは 0..255 のバイトで、1 セルは R, G, B, A の 4 バイトです(カーネルが返した値は 255 で割った値)。GpuFormat.Half で作ったバッファは 1 セルが 8 バイトなので、受け皿の大きさと頼む形式を揃えます。TextureFormat.RGBAFloat で頼むと Color[] で受け取れます。**揃っていないと、エラーも警告も出ないまま受け皿の後ろが 0 のままになります。**頼む範囲は必要な分だけにします(面ごと毎フレーム持ち帰ると、GPU で節約した分を使い切ります)。返る前にもう一度頼んでも早くはならず、遅れて届いた答えには、どの依頼のものかを言う手掛かりがありません。**頼んだバッファは、答えが返るまでそのままにします。**飛んでいる間に上書きすると、届く中身が変わります。エラーにはなりません。裏表で入れ替える形では、実行を 2 回するだけで同じバッファへ戻ってきます。
書き方説明エラー理由代替
カーネルが static でない[Kernel] を付けたメソッドがインスタンスのものTUKI0001設計static にする
戻り値が Color4 でないカーネルが別の型を返しているTUKI0001設計Color4 を返す
第 1 引数が KernelId でないカーネルの引数の並びが違うTUKI0001設計第 1 引数を KernelId にする
Gpu.Run の引数の数が合わないカーネルの引数と数が違うTUKI0001設計数を合わせる
表に大きさを書いていないVector4[] の引数に [Capacity(N)] が無いTUKI0001設計[Capacity(N)] を付ける
表の大きさが 2 の冪でない[Capacity(20)] のような数TUKI0001設計2 の冪にする
表の大きさが 1024 を超える[Capacity(2048)] のような数TUKI0001設計1024 以下にする
表に書き込むカーネルの中で表の要素へ代入TUKI0001設計読むだけにする
Gpu.Run の引数の型が合わないカーネルの引数と型が違うTUKI0001設計型を合わせる
Gpu.Run(k, next, next)出力先のバッファを入力にも渡しているTUKI0001設計別のバッファを渡す
Gpu.Run(warm ? nameof(A) : nameof(B), ...)カーネル名を実行時に選んでいる(条件式・配列・フィールド)TUKI0001設計分岐して、それぞれの枝で Gpu.Run を呼ぶ
Gpu.Run("NotAKernel", ...)[Kernel] の付いていないメソッド名を渡しているTUKI0001設計nameof で綴りを固定する
Gpu.Run(nameof(まとめ方), ...)[Reduce] を付けたメソッドを Gpu.Run に渡しているTUKI0001設計Gpu.Reduce で回す
Gpu.Reduce(nameof(カーネル), ...)[Kernel] を付けたメソッドを Gpu.Reduce に渡しているTUKI0001設計Gpu.Run で回す
Gpu.Reduce(f, full, full)読み元と書き込み先に同じバッファを渡しているTUKI0001設計別のバッファへ畳む
Gpu.Pack16x2 をカーネルの外で呼ぶ詰め込みをカーネルの外に書いているTUKI0001, TUKI0099, TUKI0102設計カーネルの中で詰める
バッファのセルをカーネルの外で読むまとめた値を Udon 側の変数へ取り出そうとしているTUKI0099, TUKI0101, TUKI0102設計次のカーネルへ渡すか Gpu.Show で出す
カーネルを直接呼ぶ[Kernel] を付けたメソッドをそのまま呼んでいるTUKI0001, TUKI0099, TUKI0102設計Gpu.Run(nameof(…), …) で起動する

セルの値をそのまま Udon の変数へ代入する口はなく(Gpu.Reduce や Gpu.Max がまとめた値も同じです)、数として使うときは Gpu.Texture で取り出して実行環境の非同期の読み戻しへ渡します。

静止画でも、動画やカメラの出力でもかまいません。

using UnityEngine;
using Tsukimi;
public class ParHostLoad : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
public Texture source;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
Gpu.Load(current, source);
}
[Kernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
return new Color4(prev[id].R, 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}

1 セル 4 成分・各 8 ビットで、内容はゼロで始まります。

using UnityEngine;
using Tsukimi;
public class ParHostBuffer : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[Kernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
return new Color4(prev[id].R, 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
}
}

1 成分を 16 ビットの小数にします。

using UnityEngine;
using Tsukimi;
public class ParHostBufferFormat : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
// 1 成分 16 ビットの小数で作ります。0..1 の外の値も持てます。
// 形式は書くときに決まっている必要があります(変数では渡せません)。
current = Gpu.Buffer(64, 64, GpuFormat.Half);
next = Gpu.Buffer(64, 64, GpuFormat.Half);
}
[Kernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
// 8 ビットのバッファでは 0..1 に丸められる値も、そのまま持てます。
Color4 c = prev[id];
return new Color4(c.R * 2f - 0.5f, c.G, c.B, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}

前の結果を次の入力にするときに使います。

using UnityEngine;
using Tsukimi;
public class ParHostSwap : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[Kernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
return new Color4(prev[id].R, 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}

第 2 引数が出力先です。

using UnityEngine;
using Tsukimi;
public class ParHostRun : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[Kernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
return new Color4(prev[id].R, 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
}
}

渡した値の個数と型は、コンパイルのときにカーネルの引数と照合されます。

using UnityEngine;
using Tsukimi;
public class ParHostRunExtraArgs : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
private float phase;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[Kernel]
static Color4 Step(KernelId id, GpuBuffer2D prev, float t)
{
return new Color4(prev[id].R * t, 0f, 0f, 1f);
}
void Update()
{
phase = phase + Time.deltaTime;
Gpu.Run(nameof(Step), next, current, phase);
}
}

カーネルの名前は文字列でも渡せます。

using UnityEngine;
using Tsukimi;
public class ParHostRunStringName : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[Kernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
return new Color4(prev[id].R, 0f, 0f, 1f);
}
void Update()
{
Gpu.Run("Step", next, current);
}
}

カーネルを複数書いて、順に実行できます。

using UnityEngine;
using Tsukimi;
public class ParHostTwoKernels : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[Kernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
return new Color4(prev[id].R, 0f, 0f, 1f);
}
[Kernel]
static Color4 Fade(KernelId id, GpuBuffer2D prev)
{
return new Color4(prev[id].R * 0.5f, 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Run(nameof(Fade), current, next);
}
}

Vector4 の配列を渡すと、カーネルの中では添字で参照できる表になります。

using UnityEngine;
using Tsukimi;
public class ParHostRunTable : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
// インスペクタで 16 個ぶん入れておきます(長さが 16 でないと実行されません)。
public Vector4[] weights;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[Kernel]
static Color4 Blend(KernelId id, GpuBuffer2D prev, [Capacity(16)] Vector4[] w)
{
Color4 acc = Color4.Black;
for (int i = 0; i < 16; i++)
{
acc = acc + prev[id.Offset(i - 8, 0)] * w[i].x;
}
return new Color4(Mathf.Clamp01(acc.R), Mathf.Clamp01(acc.G), Mathf.Clamp01(acc.B), 1f);
}
void Update()
{
Gpu.Run(nameof(Blend), next, current, weights);
Gpu.Swap(ref current, ref next);
}
}

大きさは 2 の冪なら 1 から 1024 まで書けます。

using UnityEngine;
using Tsukimi;
public class ParHostRunSmallTable : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
// 大きさは 2 の冪なら 1 から 1024 まで書けます(長さが 4 でないと実行されません)。
public Vector4[] weights;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[Kernel]
static Color4 Blend(KernelId id, GpuBuffer2D prev, [Capacity(4)] Vector4[] w)
{
Color4 c = prev[id];
// 添字が 4 以上なら端から回り込みます(w[4] は w[0])
float v = c.R * w[0].x + c.G * w[1].x + c.B * w[2].x + c.A * w[3].x;
return new Color4(v, v, v, 1f);
}
void Update()
{
Gpu.Run(nameof(Blend), next, current, weights);
Gpu.Swap(ref current, ref next);
}
}

カーネルがバッファを 1 つも取らなくてもかまいません。

using UnityEngine;
using Tsukimi;
public class ParHostRunNoBuffer : TsukimiBehaviour
{
private GpuBuffer2D board;
void Start()
{
board = Gpu.Buffer(64, 64);
Gpu.Run(nameof(Seed), board, 0.25f);
}
[Kernel]
static Color4 Seed(KernelId id, float scale)
{
float u = (float)id.X / Gpu.OutWidth;
return new Color4(u * scale, 0f, 0f, 1f);
}
}

[Reduce] を付けたメソッドがまとめ方で、2 つのセルの値を受けて 1 つを返します。

using UnityEngine;
using Tsukimi;
public class ParHostReduce : TsukimiBehaviour
{
private GpuBuffer2D full;
private GpuBuffer2D middle;
private GpuBuffer2D one;
void Start()
{
full = Gpu.Buffer(64, 64);
middle = Gpu.Buffer(8, 8);
one = Gpu.Buffer(1, 1);
}
[Reduce]
static Color4 Brighter(Color4 a, Color4 b)
{
if (a.R != b.R) return a.R > b.R ? a : b;
if (a.G != b.G) return a.G > b.G ? a : b;
return a.B > b.B ? a : b;
}
public void Find()
{
Gpu.Reduce(nameof(Brighter), middle, full);
Gpu.Reduce(nameof(Brighter), one, middle);
}
}

まとめ方を書かずに使える近道です。

using UnityEngine;
using Tsukimi;
public class ParHostMax : TsukimiBehaviour
{
public Renderer display;
private GpuBuffer2D full;
private GpuBuffer2D next;
private GpuBuffer2D peak;
private GpuBuffer2D floor;
[Kernel]
static Color4 Stretch(KernelId id, GpuBuffer2D src, GpuBuffer2D peak, GpuBuffer2D floor)
{
Color4 hi = peak[new KernelId(0, 0)];
Color4 lo = floor[new KernelId(0, 0)];
float span = hi.R - lo.R;
if (span < 0.0001f) return src[id];
return (src[id] - lo) / span;
}
void Start()
{
full = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
peak = Gpu.Buffer(1, 1);
floor = Gpu.Buffer(1, 1);
}
public void Rebalance()
{
Gpu.Max(peak, full);
Gpu.Min(floor, full);
Gpu.Run(nameof(Stretch), next, full, peak, floor);
Gpu.Show(next, display);
}
}

Gpu.Show を呼ぶと、対象のマテリアルの主テクスチャがバッファに差し替わります(マテリアルは対象ごとの複製になります)。

using UnityEngine;
using Tsukimi;
public class ParHostShow : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
public Renderer display;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[Kernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
return new Color4(prev[id].R, 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
Gpu.Show(current, display);
}
}

Gpu.Texture は、Unity や VRChat 側の API へ渡すときに使います。

using UnityEngine;
using Tsukimi;
public class ParHostTexture : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
private RenderTexture tex;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[Kernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
return new Color4(prev[id].R, 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
tex = Gpu.Texture(current);
}
}

自前の RenderTexture への書き出し

Section titled “自前の RenderTexture への書き出し”

取り出したバッファを、自分で用意した RenderTexture へ写せます。

using UnityEngine;
using VRC.SDKBase;
using Tsukimi;
public class ParHostBlitOwnRt : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
public RenderTexture target;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
}
[Kernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
return new Color4(prev[id].R, 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
VRCGraphics.Blit(Gpu.Texture(current), target);
}
}

GpuBuffer2D の公開フィールドは、画面では RenderTexture の欄になります。

using UnityEngine;
using Tsukimi;
public class ParHostBufferSlot : TsukimiBehaviour
{
public GpuBuffer2D target;
private GpuBuffer2D source;
void Start()
{
source = Gpu.Buffer(64, 64);
}
[Kernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
return new Color4(prev[id].R, 0f, 0f, 1f);
}
void Update()
{
Gpu.Run(nameof(Step), target, source);
}
}

バッファの中身を数として受け取るには、読み戻しを頼みます。

using UnityEngine;
using VRC.SDK3.Rendering;
using VRC.Udon.Common.Interfaces;
using Tsukimi;
public class ParHostReadback : TsukimiBehaviour
{
private GpuBuffer2D current;
private GpuBuffer2D next;
private byte[] cell;
private bool waiting;
public float level;
void Start()
{
current = Gpu.Buffer(64, 64);
next = Gpu.Buffer(64, 64);
cell = new byte[4]; // 1 セル = RGBA の 4 バイト
}
[Kernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
return new Color4(prev[id].R, 0f, 0f, 1f);
}
void Update()
{
// 頼んだぶんが返るまで、次の実行もしません。
// 読み戻しを頼んだバッファは、答えが返るまで上書きしないためです。
// 裏表で入れ替える形では、実行を 2 回するだけで同じバッファへ戻ってきます。
if (waiting) return;
Gpu.Run(nameof(Step), next, current);
Gpu.Swap(ref current, ref next);
waiting = true;
// x = 32, y = 32 の 1 セルだけを頼む
VRCAsyncGPUReadback.Request(
Gpu.Texture(current), 0, 32, 1, 32, 1, 0, 1,
TextureFormat.RGBA32, (IUdonEventReceiver)this);
}
public override void OnAsyncGpuReadbackComplete(VRCAsyncGPUReadbackRequest request)
{
waiting = false;
if (request.hasError) return;
if (!request.TryGetData(cell, 0)) return;
// 0..255 で届く(並びは R, G, B, A)
level = cell[0] / 255f;
}
}