156 lines
7 KiB
C#
156 lines
7 KiB
C#
using System;
|
|
using System.Runtime.InteropServices;
|
|
using UnityEngine;
|
|
|
|
namespace Aetheria.Editor
|
|
{
|
|
internal static class AetheriaStardustContinuityVerifier
|
|
{
|
|
private const int Span = 16;
|
|
private const int GroupSize = 128;
|
|
private const float Spacing = 6f;
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
private struct Particle
|
|
{
|
|
public Vector3 Position;
|
|
public Vector3 Color;
|
|
public float Size;
|
|
}
|
|
|
|
internal static void VerifyOneCellShift(ComputeShader program)
|
|
{
|
|
if (program == null) throw new ArgumentNullException(nameof(program));
|
|
if (!SystemInfo.supportsComputeShaders)
|
|
throw new InvalidOperationException("Stardust continuity proof requires compute-shader support.");
|
|
|
|
var kernel = program.FindKernel("UpdateParticles");
|
|
using var particles = new ComputeBuffer(Span * Span, 7 * sizeof(float), ComputeBufferType.Structured);
|
|
var field = ConstantTexture(new Color(0.5f, 0.5f, 0.5f, 1f));
|
|
var tint = ConstantTexture(Color.white);
|
|
var hue = ConstantTexture(Color.white);
|
|
try
|
|
{
|
|
program.EnableKeyword("FLOW_GLOBAL");
|
|
program.EnableKeyword("NOISE_SLOPE");
|
|
program.SetBuffer(kernel, "particles", particles);
|
|
program.SetTexture(kernel, "_NebulaSurfaceHeight", field);
|
|
program.SetTexture(kernel, "_NebulaTint", tint);
|
|
program.SetTexture(kernel, "HueTexture", hue);
|
|
program.SetVector("_NebulaSurfaceHeight_TexelSize", new Vector4(0.25f, 0.25f, 4f, 4f));
|
|
program.SetVector("_NebulaTint_TexelSize", new Vector4(0.25f, 0.25f, 4f, 4f));
|
|
program.SetVector("_Time", new Vector4(0.625f, 12.5f, 25f, 37.5f));
|
|
program.SetFloat("time", 12.5f);
|
|
program.SetFloat("period", 2f);
|
|
program.SetFloat("spacing", Spacing);
|
|
program.SetFloat("ceilingHeight", 0f);
|
|
program.SetFloat("floorHeight", -10f);
|
|
program.SetFloat("heightExponent", 3f);
|
|
program.SetFloat("maximumSize", 0.75f);
|
|
program.SetFloat("minimumSize", 0.25f);
|
|
program.SetFloat("minHeadroom", 25f);
|
|
program.SetFloat("maxHeadroom", 100f);
|
|
program.SetFloat("_FlowScale", 512f);
|
|
program.SetFloat("_FlowAmplitude", 15f);
|
|
program.SetFloat("_FlowScroll", 0.3125f);
|
|
program.SetFloat("_DynamicLodHigh", 7f);
|
|
program.SetFloat("_DynamicLodLow", 2f);
|
|
program.SetInt("span", Span);
|
|
|
|
var before = DispatchAndRead(program, kernel, particles, 0f, 0f);
|
|
AssertWholeBlockRemapIsInvisible(
|
|
before,
|
|
DispatchAndRead(program, kernel, particles, Spacing, 0f),
|
|
1,
|
|
0);
|
|
AssertWholeBlockRemapIsInvisible(
|
|
before,
|
|
DispatchAndRead(program, kernel, particles, 0f, Spacing),
|
|
0,
|
|
1);
|
|
AssertWholeBlockRemapIsInvisible(
|
|
before,
|
|
DispatchAndRead(program, kernel, particles, Spacing, Spacing),
|
|
1,
|
|
1);
|
|
Debug.Log("Aetheria Stardust continuity: 705 overlapping world cells remained bit-identical " +
|
|
"across X, Y, and diagonal whole-buffer remaps.");
|
|
}
|
|
finally
|
|
{
|
|
UnityEngine.Object.DestroyImmediate(field);
|
|
UnityEngine.Object.DestroyImmediate(tint);
|
|
UnityEngine.Object.DestroyImmediate(hue);
|
|
}
|
|
}
|
|
|
|
private static Particle[] DispatchAndRead(
|
|
ComputeShader program,
|
|
int kernel,
|
|
ComputeBuffer particles,
|
|
float gridCenterX,
|
|
float gridCenterY)
|
|
{
|
|
program.SetVector("_GridTransform", new Vector4(gridCenterX, gridCenterY, 1536f, 1536f));
|
|
program.Dispatch(kernel, Mathf.CeilToInt((float)(Span * Span) / GroupSize), 1, 1);
|
|
var result = new Particle[Span * Span];
|
|
particles.GetData(result);
|
|
return result;
|
|
}
|
|
|
|
private static void AssertWholeBlockRemapIsInvisible(
|
|
Particle[] before,
|
|
Particle[] after,
|
|
int cellDeltaX,
|
|
int cellDeltaY)
|
|
{
|
|
var half = Span / 2;
|
|
var sameSlot = half * Span + half;
|
|
if (BitwiseEqual(before[sameSlot], after[sameSlot]))
|
|
throw new InvalidOperationException(
|
|
"Stardust continuity fixture did not reassign the sampled buffer slot to a new world cell.");
|
|
|
|
var minimumWorldX = Math.Max(-half, -half + cellDeltaX);
|
|
var maximumWorldX = Math.Min(half - 1, half - 1 + cellDeltaX);
|
|
var minimumWorldY = Math.Max(-half, -half + cellDeltaY);
|
|
var maximumWorldY = Math.Min(half - 1, half - 1 + cellDeltaY);
|
|
for (var worldY = minimumWorldY; worldY <= maximumWorldY; worldY++)
|
|
for (var worldX = minimumWorldX; worldX <= maximumWorldX; worldX++)
|
|
{
|
|
var beforeIndex = (worldY + half) * Span + worldX + half;
|
|
var afterIndex = (worldY - cellDeltaY + half) * Span +
|
|
(worldX - cellDeltaX) + half;
|
|
if (!BitwiseEqual(before[beforeIndex], after[afterIndex]))
|
|
throw new InvalidOperationException(
|
|
$"Stardust cell ({worldX},{worldY}) changed while crossing one spatial cell; " +
|
|
$"buffer slots {beforeIndex} and {afterIndex} must describe the same stateless particle.");
|
|
}
|
|
}
|
|
|
|
private static bool BitwiseEqual(Particle left, Particle right) =>
|
|
Bits(left.Position.x) == Bits(right.Position.x) &&
|
|
Bits(left.Position.y) == Bits(right.Position.y) &&
|
|
Bits(left.Position.z) == Bits(right.Position.z) &&
|
|
Bits(left.Color.x) == Bits(right.Color.x) &&
|
|
Bits(left.Color.y) == Bits(right.Color.y) &&
|
|
Bits(left.Color.z) == Bits(right.Color.z) &&
|
|
Bits(left.Size) == Bits(right.Size);
|
|
|
|
private static int Bits(float value) => BitConverter.SingleToInt32Bits(value);
|
|
|
|
private static Texture2D ConstantTexture(Color color)
|
|
{
|
|
var texture = new Texture2D(4, 4, TextureFormat.RGBA32, false, true)
|
|
{
|
|
filterMode = FilterMode.Bilinear,
|
|
wrapMode = TextureWrapMode.Clamp,
|
|
hideFlags = HideFlags.HideAndDontSave
|
|
};
|
|
var pixels = new Color[16];
|
|
for (var index = 0; index < pixels.Length; index++) pixels[index] = color;
|
|
texture.SetPixels(pixels);
|
|
texture.Apply(false, false);
|
|
return texture;
|
|
}
|
|
}
|
|
}
|