Files
Misaki.HighPerformance/Misaki.HighPerformance.Test/Jobs/NoiseJobVector.cs
Misaki 37d548085e Refactor job API: add JobExecutionContext, update tests
Major breaking change: job interfaces now use JobExecutionContext
instead of threadIndex, enabling thread-aware and dynamic job
dispatching. Updated all job system, SPMD, and test code to match.
Collections improved with new methods and clearer enumerators.
Renamed IJobScheduler.WaitComplete to Wait. Incremented project
versions. Includes bug fixes, documentation, and style updates.
2026-03-04 11:43:39 +09:00

307 lines
10 KiB
C#

using Misaki.HighPerformance.Jobs;
using Misaki.HighPerformance.Mathematics;
using Misaki.HighPerformance.Mathematics.SPMD;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
namespace Misaki.HighPerformance.Test.Jobs;
internal unsafe struct NoiseJobVectorFor : IJobParallelFor
{
public float* buffers;
public int width;
public int height;
public void Execute(int loopIndex, ref readonly JobExecutionContext ctx)
{
var x = loopIndex % width;
var y = loopIndex / height;
var uv = new Vector2(x, y) / new Vector2(width, height);
buffers[loopIndex] = NoiseJobVector.GradientNoise(uv);
}
}
internal unsafe struct NoiseJobVector : IJobParallel
{
public float* buffers;
public int width;
public int height;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Frac(float x)
{
return x - MathF.Floor(x);
}
private static float Mod289(float x)
{
return x - MathF.Floor(x / 289) * 289;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector2 GradientNoiseDirect(Vector2 uv)
{
uv.X = Mod289(uv.X);
uv.Y = Mod289(uv.Y);
var x = (34 * uv.X + 1) * Mod289(uv.X) + uv.Y;
x = (34 * x + 1) * Mod289(x);
x = Frac(x / 41) * 2 - 1;
return Vector2.Normalize(new Vector2(x - MathF.Floor(x + 0.5f), MathF.Abs(x) - 0.5f));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float GradientNoise(Vector2 uv)
{
var ip = new Vector2(MathF.Floor(uv.X), MathF.Floor(uv.Y));
var fp = new Vector2(Frac(uv.X), Frac(uv.Y));
var d00 = Vector2.Dot(GradientNoiseDirect(ip), fp);
var d01 = Vector2.Dot(GradientNoiseDirect(ip + new Vector2(0, 1)), fp - new Vector2(0, 1));
var d10 = Vector2.Dot(GradientNoiseDirect(ip + new Vector2(1, 0)), fp - new Vector2(1, 0));
var d11 = Vector2.Dot(GradientNoiseDirect(ip + new Vector2(1, 1)), fp - new Vector2(1, 1));
fp = fp * fp * fp * (fp * (fp * new Vector2(6.0f) - new Vector2(15.0f)) + new Vector2(10.0f));
return float.Lerp(float.Lerp(d00, d10, fp.Y), float.Lerp(d01, d11, fp.Y), fp.X);
}
public void Execute(int startIndex, int endIndex, ref readonly JobExecutionContext ctx)
{
for (int i = startIndex; i < endIndex; i++)
{
var x = i % width;
var y = i / height;
var uv = new Vector2(x, y) / new Vector2(width, height);
buffers[i] = GradientNoise(uv);
}
}
}
internal unsafe struct NoiseJobMath : IJobParallel
{
public float* buffers;
public int width;
public int height;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static float2 GradientNoiseDirect(float2 uv)
{
uv = noise.mod289(uv);
var x = (34 * uv.x + 1) * noise.mod289(uv.x) + uv.y;
x = (34 * x + 1) * noise.mod289(x);
x = math.frac(x / 41) * 2 - 1;
return math.normalize(new float2(x - math.floor(x + 0.5f), math.abs(x) - 0.5f));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float GradientNoise(float2 uv)
{
var ip = new float2(math.floor(uv.x), math.floor(uv.y));
var fp = new float2(math.frac(uv.x), math.frac(uv.y));
var d00 = math.dot(GradientNoiseDirect(ip), fp);
var d01 = math.dot(GradientNoiseDirect(ip + new float2(0, 1)), fp - new float2(0, 1));
var d10 = math.dot(GradientNoiseDirect(ip + new float2(1, 0)), fp - new float2(1, 0));
var d11 = math.dot(GradientNoiseDirect(ip + new float2(1, 1)), fp - new float2(1, 1));
fp = fp * fp * fp * (fp * (fp * new float2(6.0f) - new float2(15.0f)) + new float2(10.0f));
return float.Lerp(float.Lerp(d00, d10, fp.y), float.Lerp(d01, d11, fp.y), fp.x);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Execute(int loopIndex, int threadIndex)
{
var x = loopIndex % width;
var y = loopIndex / height;
var uv = new float2(x, y) / new float2(width, height);
buffers[loopIndex] = GradientNoise(uv);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Execute(int startIndex, int endIndex, ref readonly JobExecutionContext ctx)
{
for (var i = startIndex; i < endIndex; i++)
{
var x = i % width;
var y = i / height;
var uv = new float2(x, y) / new float2(width, height);
buffers[i] = GradientNoise(uv);
}
}
}
internal unsafe struct NoiseJobMathV : IJobParallel
{
public float* buffers;
public int width;
public int height;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector256<float> Mod289(Vector256<float> x)
{
var div = x / Vector256.Create(289.0f);
var flr = Vector256.Truncate(div);
return x - (flr * Vector256.Create(289.0f));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector256<float> Fade(Vector256<float> t)
{
return t * t * t * (t * (t * Vector256.Create(6.0f) - Vector256.Create(15.0f)) + Vector256.Create(10.0f));
}
// HELPER: Calculate gradients for 8 pixels at once
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector256<float> GradDot(Vector256<float> ix, Vector256<float> iy, Vector256<float> fx, Vector256<float> fy)
{
var hx = Mod289(ix);
var hy = Mod289(iy);
var p = hx * Vector256.Create(34.0f) + Vector256.Create(1.0f);
p = Mod289(p * hx) + hy;
var pPrev = p;
p = p * Vector256.Create(34.0f) + Vector256.Create(1.0f);
p = Mod289(p * pPrev);
var r = (p / 41.0f);
r = (r - Vector256.Truncate(r)) * 2.0f - Vector256<float>.One;
var gx = r - Vector256.Floor(r + Vector256.Create(0.5f));
var gy = Vector256.Abs(r) - Vector256.Create(0.5f);
// Normalize
var len = Vector256.Sqrt(gx * gx + gy * gy);
gx /= len;
gy /= len;
return gx * fx + gy * fy;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> GradientNoiseAVX(Vector256<float> uvX, Vector256<float> uvY)
{
var ipX = Vector256.Floor(uvX);
var ipY = Vector256.Floor(uvY);
var fpX = uvX - ipX;
var fpY = uvY - ipY;
var uX = Fade(fpX);
var uY = Fade(fpY);
var d00 = GradDot(ipX, ipY, fpX, fpY);
var d01 = GradDot(ipX, ipY + Vector256<float>.One, fpX, fpY - Vector256<float>.One);
var d10 = GradDot(ipX + Vector256<float>.One, ipY, fpX - Vector256<float>.One, fpY);
var d11 = GradDot(ipX + Vector256<float>.One, ipY + Vector256<float>.One, fpX - Vector256<float>.One, fpY - Vector256<float>.One);
var lerpY1 = d00 + (d10 - d00) * uY;
var lerpY2 = d01 + (d11 - d01) * uY;
return lerpY1 + (lerpY2 - lerpY1) * uX;
}
public void Execute(int startIndex, int endIndex, ref readonly JobExecutionContext ctx)
{
for (int i = startIndex; i < endIndex; i++)
{
var baseIndex = i * 8;
// Safety check
if (baseIndex + 7 >= width * height)
{
return;
}
// Calculate Coords
var y = baseIndex / width;
var x = baseIndex % width;
// Sequence: 0, 1, 2, 3, 4, 5, 6, 7
var vSeqX = Vector256.Create(0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f);
var vBaseX = Vector256.Create((float)x) + vSeqX;
var vBaseY = Vector256.Create((float)y);
var vWidth = Vector256.Create((float)width);
var vHeight = Vector256.Create((float)height);
var result = GradientNoiseAVX(vBaseX / vWidth, vBaseY / vHeight);
// Store 8 floats (32 bytes)
result.Store(buffers + baseIndex);
}
}
}
internal unsafe struct NoiseJobMathSPMD : IJobSPMD<float>
{
public float* buffers;
public int width;
public int height;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static T GradDot<T>(T ix, T iy, T fx, T fy)
where T : ISPMD<T, float>
{
var c289 = T.Create(289f);
var c34 = T.Create(34f);
var c1 = T.Create(1f);
var c41 = T.Create(41f);
var c2 = T.Create(2f);
var half = T.Create(0.5f);
ix %= c289;
iy %= c289;
var x = (c34 * ix + c1) * ix % c289 + iy;
x = (c34 * x + c1) * x % c289;
x = T.Frac(x / c41) * c2 - c1;
var gx = x - T.Floor(x + half);
var gy = T.Abs(x) - half;
// normalize
var len = T.Sqrt(gx * gx + gy * gy);
gx /= len;
gy /= len;
return gx * fx + gy * fy;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T Noise<T>(T uvX, T uvY)
where T : ISPMD<T, float>
{
var c1 = T.Create(1f);
var c6 = T.Create(6f);
var c10 = T.Create(10f);
var c15 = T.Create(15f);
var ipX = T.Floor(uvX);
var ipY = T.Floor(uvY);
var fpX = uvX - ipX;
var fpY = uvY - ipY;
var d00 = GradDot(ipX, ipY, fpX, fpY);
var d01 = GradDot(ipX, ipY + c1, fpX, fpY - c1);
var d10 = GradDot(ipX + c1, ipY, fpX - c1, fpY);
var d11 = GradDot(ipX + c1, ipY + c1, fpX - c1, fpY - c1);
// fade
var uX = fpX * fpX * fpX * (fpX * (fpX * c6 - c15) + c10);
var uY = fpY * fpY * fpY * (fpY * (fpY * c6 - c15) + c10);
return T.Lerp(T.Lerp(d00, d10, uY), T.Lerp(d01, d11, uY), uX);
}
public readonly void Execute<TLane>(int baseIndex, ref readonly JobExecutionContext ctx)
where TLane : ISPMD<TLane, float>
{
var indices = TLane.Sequence(baseIndex, 1f);
var w = TLane.Create(width);
var h = TLane.Create(height);
var uvX = (indices % w) / w;
var uvY = TLane.Floor(indices / w) / h;
var result = Noise(uvX, uvY);
result.Store(buffers + baseIndex);
}
}