GPU カーネルと await
GPU カーネルの実行の呼び出しは、async / awaitの中に書けます。await Async.Frame() を置いた位置で、1 フレームに呼ぶ回数が決まります。
| 書き方 | 説明 | 注意 |
|---|---|---|
Gpu.Run(nameof(Step), next, current); await Async.Frame(); | 1 フレーム 1 段 | |
for (int i = 0; i < stepsPerFrame; i++) { Gpu.Run(...); } | 1 フレームの回数 | 回数をフィールドで持つと、待っている間に変更した値が次のフレームに反映されます。 |
Gpu.Reduce(nameof(Brighter), middle, full); await Async.Frame(); | リダクションの段の分割 | 1 段目の結果は middle に残り、次のフレームの 2 段目がそれを読みます。 |
Gpu.Load(current, source); await Async.Frame(); | 読み込みと実行の分離 | |
Gpu.Show(nameof(Glow), surface, heat, gain); | 渡す値の時間変化 | 表示に使われるのは Gpu.Show に渡した時点の値なので、値を変えるたびに呼び直します。 |
board = Gpu.Buffer(64, 64); await Async.Frame(); | バッファの生成 | 生成したバッファは、中断をまたいで使えます。 |
1 フレーム 1 段
Section titled “1 フレーム 1 段”ループの各周で Gpu.Run を 1 回呼んでから中断すると、1 フレームに 1 段ずつ進みます。
using UnityEngine;using Tsukimi;
public class ParGpuStepPerFrame : TsukimiBehaviour{ public Renderer display; private GpuBuffer2D current; private GpuBuffer2D next;
[Kernel] static Color4 Step(KernelId id, GpuBuffer2D prev) { Color4 c = prev[id]; return new Color4(c.R * 0.99f, c.G, c.B, 1f); }
private async FrameTask Simulate() { for (int i = 0; i < 600; i++) { Gpu.Run(nameof(Step), next, current); Gpu.Swap(ref current, ref next); Gpu.Show(current, display); await Async.Frame(); } }
void Start() { current = Gpu.Buffer(128, 128); next = Gpu.Buffer(128, 128); Simulate(); }}1 フレームの回数
Section titled “1 フレームの回数”内側のループの回数だけ Gpu.Run を呼び、外側で 1 フレーム待つと、1 フレームあたりの回数を決められます。
using UnityEngine;using Tsukimi;
public class ParGpuBudget : TsukimiBehaviour{ public int stepsPerFrame; private GpuBuffer2D current; private GpuBuffer2D next;
[Kernel] static Color4 Relax(KernelId id, GpuBuffer2D prev) { Color4 a = prev[id.Offset(-1, 0)]; Color4 b = prev[id.Offset(1, 0)]; return (a + b) * 0.5f; }
private async FrameTask Settle() { int done = 0; while (done < 2000) { for (int i = 0; i < stepsPerFrame; i++) { Gpu.Run(nameof(Relax), next, current); Gpu.Swap(ref current, ref next); done = done + 1; } await Async.Frame(); } }
void Start() { stepsPerFrame = 4; current = Gpu.Buffer(64, 64); next = Gpu.Buffer(64, 64); Settle(); }}リダクションの段の分割
Section titled “リダクションの段の分割”リダクションの段の間に await を置くと、64 → 8 → 1 の 2 段を 2 フレームに分けられます。
using UnityEngine;using Tsukimi;
public class ParGpuReduceStages : TsukimiBehaviour{ private GpuBuffer2D full; private GpuBuffer2D middle; private GpuBuffer2D one;
[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; if (a.B != b.B) return a.B > b.B ? a : b; return a.A > b.A ? a : b; }
private async FrameTask Fold() { Gpu.Reduce(nameof(Brighter), middle, full); await Async.Frame(); Gpu.Reduce(nameof(Brighter), one, middle); }
void Start() { full = Gpu.Buffer(64, 64); middle = Gpu.Buffer(8, 8); one = Gpu.Buffer(1, 1); Fold(); }}読み込みと実行の分離
Section titled “読み込みと実行の分離”Gpu.Load で画像を読み込むフレームと、それを読む Gpu.Run のフレームを分けられます。
using UnityEngine;using Tsukimi;
public class ParGpuLoadThenRun : TsukimiBehaviour{ public Texture source; private GpuBuffer2D current; private GpuBuffer2D next;
[Kernel] static Color4 Blur(KernelId id, GpuBuffer2D prev) { Color4 a = prev[id.Offset(-1, 0)]; Color4 b = prev[id.Offset(1, 0)]; return (a + b) * 0.5f; }
private async FrameTask Prepare() { Gpu.Load(current, source); await Async.Frame(); Gpu.Run(nameof(Blur), next, current); Gpu.Swap(ref current, ref next); }
void Start() { current = Gpu.Buffer(256, 256); next = Gpu.Buffer(256, 256); Prepare(); }}渡す値の時間変化
Section titled “渡す値の時間変化”サーフェスシェーダー(試験中)へ渡す値を周ごとに変えて Gpu.Show を呼び直すと、見た目が時間で変わります。
using UnityEngine;using Tsukimi;
public class ParGpuSurfaceOverTime : TsukimiBehaviour{ public Renderer surface; public float gain; private GpuBuffer2D heat;
[Surface] static Color4 Glow(SurfaceId id, GpuBuffer2D buf, float gain) { float t = buf.Sample(id.UV).R * gain; return new Color4(t, t * 0.5f, 0f, 1f); }
private async FrameTask FadeIn() { for (int i = 0; i < 120; i++) { gain = i / 120f; Gpu.Show(nameof(Glow), surface, heat, gain); await Async.Frame(); } }
void Start() { heat = Gpu.Buffer(64, 64); FadeIn(); }}バッファの生成
Section titled “バッファの生成”Gpu.Buffer による生成も、フレームを跨ぐメソッドの中に書けます。
using UnityEngine;using Tsukimi;
public class ParGpuBufferInAsync : TsukimiBehaviour{ public Renderer display; private GpuBuffer2D board;
[Kernel] static Color4 Seed(KernelId id) { return new Color4(id.X * 0.01f, id.Y * 0.01f, 0f, 1f); }
private async FrameTask Boot() { board = Gpu.Buffer(64, 64); await Async.Frame(); Gpu.Run(nameof(Seed), board); Gpu.Show(board, display); }
void Start() { Boot(); }}