コンテンツにスキップ

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();バッファの生成生成したバッファは、中断をまたいで使えます。

ループの各周で 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();
}
}

内側のループの回数だけ 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();
}
}

リダクションの段の間に 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();
}
}

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();
}
}

サーフェスシェーダー(試験中)へ渡す値を周ごとに変えて 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();
}
}

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();
}
}