fgm24/Assets/Scripts/Compute Helper/ComputeHelper.cs

373 lines
12 KiB
C#

namespace ComputeShaderUtility
{
using UnityEngine;
using UnityEngine.Experimental.Rendering;
// This class contains some helper functions to make life a little easier working with compute shaders
// (Very work-in-progress!)
public static class ComputeHelper
{
public const FilterMode defaultFilterMode = FilterMode.Bilinear;
public const GraphicsFormat defaultGraphicsFormat = GraphicsFormat.R32G32B32A32_SFloat;
static ComputeShader normalizeTextureCompute;
static ComputeShader clearTextureCompute;
static ComputeShader swizzleTextureCompute;
static ComputeShader copy3DCompute;
// Subscribe to this event to be notified when buffers created in edit mode should be released
// (i.e before script compilation occurs, and when exitting edit mode)
public static event System.Action shouldReleaseEditModeBuffers;
/// Convenience method for dispatching a compute shader.
/// It calculates the number of thread groups based on the number of iterations needed.
public static void Dispatch(ComputeShader cs, int numIterationsX, int numIterationsY = 1, int numIterationsZ = 1, int kernelIndex = 0)
{
Vector3Int threadGroupSizes = GetThreadGroupSizes(cs, kernelIndex);
int numGroupsX = Mathf.CeilToInt(numIterationsX / (float)threadGroupSizes.x);
int numGroupsY = Mathf.CeilToInt(numIterationsY / (float)threadGroupSizes.y);
int numGroupsZ = Mathf.CeilToInt(numIterationsZ / (float)threadGroupSizes.y);
cs.Dispatch(kernelIndex, numGroupsX, numGroupsY, numGroupsZ);
}
public static int GetStride<T>()
{
return System.Runtime.InteropServices.Marshal.SizeOf(typeof(T));
}
public static void CreateStructuredBuffer<T>(ref ComputeBuffer buffer, int count)
{
int stride = System.Runtime.InteropServices.Marshal.SizeOf(typeof(T));
bool createNewBuffer = buffer == null || !buffer.IsValid() || buffer.count != count || buffer.stride != stride;
if (createNewBuffer)
{
Release(buffer);
buffer = new ComputeBuffer(count, stride);
}
}
public static void CreateStructuredBuffer<T>(ref ComputeBuffer buffer, T[] data)
{
CreateStructuredBuffer<T>(ref buffer, data.Length);
buffer.SetData(data);
}
public static ComputeBuffer CreateAndSetBuffer<T>(T[] data, ComputeShader cs, string nameID, int kernelIndex = 0)
{
ComputeBuffer buffer = null;
CreateAndSetBuffer<T>(ref buffer, data, cs, nameID, kernelIndex);
return buffer;
}
public static void CreateAndSetBuffer<T>(ref ComputeBuffer buffer, T[] data, ComputeShader cs, string nameID, int kernelIndex = 0)
{
int stride = System.Runtime.InteropServices.Marshal.SizeOf(typeof(T));
CreateStructuredBuffer<T>(ref buffer, data.Length);
buffer.SetData(data);
cs.SetBuffer(kernelIndex, nameID, buffer);
}
public static ComputeBuffer CreateAndSetBuffer<T>(int length, ComputeShader cs, string nameID, int kernelIndex = 0)
{
ComputeBuffer buffer = null;
CreateAndSetBuffer<T>(ref buffer, length, cs, nameID, kernelIndex);
return buffer;
}
public static void CreateAndSetBuffer<T>(ref ComputeBuffer buffer, int length, ComputeShader cs, string nameID, int kernelIndex = 0)
{
CreateStructuredBuffer<T>(ref buffer, length);
cs.SetBuffer(kernelIndex, nameID, buffer);
}
/// Releases supplied buffer/s if not null
public static void Release(params ComputeBuffer[] buffers)
{
for (int i = 0; i < buffers.Length; i++)
{
if (buffers[i] != null)
{
buffers[i].Release();
}
}
}
/// Releases supplied render textures/s if not null
public static void Release(params RenderTexture[] textures)
{
for (int i = 0; i < textures.Length; i++)
{
if (textures[i] != null)
{
textures[i].Release();
}
}
}
public static Vector3Int GetThreadGroupSizes(ComputeShader compute, int kernelIndex = 0)
{
uint x, y, z;
compute.GetKernelThreadGroupSizes(kernelIndex, out x, out y, out z);
return new Vector3Int((int)x, (int)y, (int)z);
}
// ------ Texture Helpers ------
public static void CreateRenderTexture(ref RenderTexture texture, int width, int height)
{
CreateRenderTexture(ref texture, width, height, defaultFilterMode, defaultGraphicsFormat);
}
public static void CreateRenderTexture(ref RenderTexture texture, int width, int height, FilterMode filterMode, GraphicsFormat format, string name = "Unnamed")
{
if (texture == null || !texture.IsCreated() || texture.width != width || texture.height != height || texture.graphicsFormat != format)
{
if (texture != null)
{
texture.Release();
}
texture = new RenderTexture(width, height, 0);
texture.graphicsFormat = format;
texture.enableRandomWrite = true;
texture.autoGenerateMips = false;
texture.Create();
}
texture.name = name;
texture.wrapMode = TextureWrapMode.Clamp;
texture.filterMode = filterMode;
}
public static void CreateRenderTexture3D(ref RenderTexture texture, int size, string name = "Untitled")
{
var format = UnityEngine.Experimental.Rendering.GraphicsFormat.R16_SFloat;
if (texture == null || !texture.IsCreated() || texture.width != size || texture.height != size || texture.volumeDepth != size || texture.graphicsFormat != format)
{
//Debug.Log ("Create tex: update noise: " + updateNoise);
if (texture != null)
{
texture.Release();
}
const int numBitsInDepthBuffer = 0;
texture = new RenderTexture(size, size, numBitsInDepthBuffer);
texture.graphicsFormat = format;
texture.volumeDepth = size;
texture.enableRandomWrite = true;
texture.dimension = UnityEngine.Rendering.TextureDimension.Tex3D;
texture.Create();
}
texture.wrapMode = TextureWrapMode.Repeat;
texture.filterMode = FilterMode.Bilinear;
texture.name = name;
}
/// Copy the contents of one render texture into another. Assumes textures are the same size.
public static void CopyRenderTexture(Texture source, RenderTexture target)
{
Graphics.Blit(source, target);
}
/// Copy the contents of one render texture into another. Assumes textures are the same size.
public static void CopyRenderTexture3D(Texture source, RenderTexture target)
{
LoadComputeShader(ref copy3DCompute, "Copy3D");
copy3DCompute.SetInts("dimensions", target.width, target.height, target.volumeDepth);
copy3DCompute.SetTexture(0, "Source", source);
copy3DCompute.SetTexture(0, "Target", target);
Dispatch(copy3DCompute, target.width, target.height, target.volumeDepth);//
}
/// Swap channels of texture, or set to zero. For example, if inputs are: (green, red, zero, zero)
/// then red and green channels will be swapped, and blue and alpha channels will be set to zero.
public static void SwizzleTexture(Texture texture, Channel x, Channel y, Channel z, Channel w)
{
if (swizzleTextureCompute == null)
{
swizzleTextureCompute = (ComputeShader)Resources.Load("Swizzle");
}
swizzleTextureCompute.SetInt("width", texture.width);
swizzleTextureCompute.SetInt("height", texture.height);
swizzleTextureCompute.SetTexture(0, "Source", texture);
swizzleTextureCompute.SetVector("x", ChannelToMask(x));
swizzleTextureCompute.SetVector("y", ChannelToMask(y));
swizzleTextureCompute.SetVector("z", ChannelToMask(z));
swizzleTextureCompute.SetVector("w", ChannelToMask(w));
Dispatch(swizzleTextureCompute, texture.width, texture.height, 1, 0);
}
/// Sets all pixels of supplied texture to 0
public static void ClearRenderTexture(RenderTexture source)
{
LoadComputeShader(ref clearTextureCompute, "ClearTexture");
clearTextureCompute.SetInt("width", source.width);
clearTextureCompute.SetInt("height", source.height);
clearTextureCompute.SetTexture(0, "Source", source);
Dispatch(clearTextureCompute, source.width, source.height, 1, 0);
}
/// Work in progress, currently only works with one channel and very slow
public static void NormalizeRenderTexture(RenderTexture source)
{
LoadComputeShader(ref normalizeTextureCompute, "NormalizeTexture");
normalizeTextureCompute.SetInt("width", source.width);
normalizeTextureCompute.SetInt("height", source.height);
normalizeTextureCompute.SetTexture(0, "Source", source);
normalizeTextureCompute.SetTexture(1, "Source", source);
ComputeBuffer minMaxBuffer = CreateAndSetBuffer<int>(new int[] { int.MaxValue, 0 }, normalizeTextureCompute, "minMaxBuffer", 0);
normalizeTextureCompute.SetBuffer(1, "minMaxBuffer", minMaxBuffer);
Dispatch(normalizeTextureCompute, source.width, source.height, 1, 0);
Dispatch(normalizeTextureCompute, source.width, source.height, 1, 1);
//int[] data = new int[2];
//minMaxBuffer.GetData(data);
//Debug.Log(data[0] + " " + data[1]);
Release(minMaxBuffer);
}
// https://cmwdexint.com/2017/12/04/computeshader-setfloats/
public static float[] PackFloats(params float[] values)
{
float[] packed = new float[values.Length * 4];
for (int i = 0; i < values.Length; i++)
{
packed[i * 4] = values[i];
}
return values;
}
// Only run compute shaders if this is true
// This is only relevant for compute shaders that run outside of playmode
public static bool CanRunEditModeCompute
{
get
{
return CheckIfCanRunInEditMode();
}
}
// Set all values from settings object on the shader. Note, variable names must be an exact match in the shader.
// Settings object can be any class/struct containing vectors/ints/floats/bools
public static void SetParams(System.Object settings, ComputeShader shader, string variableNamePrefix = "", string variableNameSuffix = "")
{
var fields = settings.GetType().GetFields();
foreach (var field in fields)
{
var fieldType = field.FieldType;
string shaderVariableName = variableNamePrefix + field.Name + variableNameSuffix;
if (fieldType == typeof(UnityEngine.Vector4) || fieldType == typeof(Vector3) || fieldType == typeof(Vector2))
{
shader.SetVector(shaderVariableName, (Vector4)field.GetValue(settings));
}
else if (fieldType == typeof(int))
{
shader.SetInt(shaderVariableName, (int)field.GetValue(settings));
}
else if (fieldType == typeof(float))
{
shader.SetFloat(shaderVariableName, (float)field.GetValue(settings));
}
else if (fieldType == typeof(bool))
{
shader.SetBool(shaderVariableName, (bool)field.GetValue(settings));
}
else
{
Debug.Log($"Type {fieldType} not implemented");
}
}
}
static Vector4 ChannelToMask(Channel channel)
{
switch (channel)
{
case Channel.Red:
return new Vector4(1, 0, 0, 0);
case Channel.Green:
return new Vector4(0, 1, 0, 0);
case Channel.Blue:
return new Vector4(0, 0, 1, 0);
case Channel.Alpha:
return new Vector4(0, 0, 0, 1);
case Channel.Zero:
return new Vector4(0, 0, 0, 0);
default:
return Vector4.zero;
}
}
static void LoadComputeShader(ref ComputeShader shader, string name)
{
if (shader == null)
{
shader = (ComputeShader)Resources.Load(name);
}
}
// Editor helpers:
#if UNITY_EDITOR
static UnityEditor.PlayModeStateChange playModeState;
static ComputeHelper()
{
// Monitor play mode state
UnityEditor.EditorApplication.playModeStateChanged -= MonitorPlayModeState;
UnityEditor.EditorApplication.playModeStateChanged += MonitorPlayModeState;
// Monitor script compilation
UnityEditor.Compilation.CompilationPipeline.compilationStarted -= OnCompilationStarted;
UnityEditor.Compilation.CompilationPipeline.compilationStarted += OnCompilationStarted;
}
static void MonitorPlayModeState(UnityEditor.PlayModeStateChange state)
{
playModeState = state;
if (state == UnityEditor.PlayModeStateChange.ExitingEditMode)
{
if (shouldReleaseEditModeBuffers != null)
{
shouldReleaseEditModeBuffers(); //
}
}
}
static void OnCompilationStarted(System.Object obj)
{
if (shouldReleaseEditModeBuffers != null)
{
shouldReleaseEditModeBuffers();
}
}
#endif
static bool CheckIfCanRunInEditMode()
{
bool isCompilingOrExitingEditMode = false;
#if UNITY_EDITOR
isCompilingOrExitingEditMode |= UnityEditor.EditorApplication.isCompiling;
isCompilingOrExitingEditMode |= playModeState == UnityEditor.PlayModeStateChange.ExitingEditMode;
#endif
bool canRun = !isCompilingOrExitingEditMode;
return canRun;
}
}
}