GPU カーネルの実行
対応(18)
Section titled “対応(18)”| 書き方 | 説明 | 注意 |
|---|---|---|
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 回するだけで同じバッファへ戻ってきます。 |
非対応(18)
Section titled “非対応(18)”| 書き方 | 説明 | エラー | 理由 | 代替 |
|---|---|---|---|---|
カーネルが 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 で取り出して実行環境の非同期の読み戻しへ渡します。
画像をバッファへ読み込む
Section titled “画像をバッファへ読み込む”静止画でも、動画やカメラの出力でもかまいません。
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); }}バッファの生成
Section titled “バッファの生成”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); }}形式を選んだ生成
Section titled “形式を選んだ生成”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); }}2 つの入れ替え
Section titled “2 つの入れ替え”前の結果を次の入力にするときに使います。
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); }}実行の呼び出し
Section titled “実行の呼び出し”第 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); }}値を渡した実行
Section titled “値を渡した実行”渡した値の個数と型は、コンパイルのときにカーネルの引数と照合されます。
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); }}名前の文字列指定
Section titled “名前の文字列指定”カーネルの名前は文字列でも渡せます。
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); }}複数のカーネル
Section titled “複数のカーネル”カーネルを複数書いて、順に実行できます。
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); }}表を渡した実行
Section titled “表を渡した実行”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); }}読み元を取らない実行
Section titled “読み元を取らない実行”カーネルがバッファを 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); }}リダクション
Section titled “リダクション”まとめ方の指定
Section titled “まとめ方の指定”[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); }}成分ごとの最大と最小
Section titled “成分ごとの最大と最小”まとめ方を書かずに使える近道です。
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); }}表示と取り出し
Section titled “表示と取り出し”Renderer への表示
Section titled “Renderer への表示”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); }}テクスチャとしての取り出し
Section titled “テクスチャとしての取り出し”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); }}書き込み先を外から挿す口
Section titled “書き込み先を外から挿す口”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); }}値としての読み戻し
Section titled “値としての読み戻し”バッファの中身を数として受け取るには、読み戻しを頼みます。
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; }}