For additional testing, I've created a handful of CPU and ComputeShader methods as shown below. I definitely welcome any improvements in the comments.
Texture2D_Mod.compute:
#pragma kernel ChannelSwapFunc
#pragma kernel ChannelSwap4x4
RWTexture2D<float4> result;
Texture2D<float4> input;
uint channelSwapR;
uint channelSwapG;
uint channelSwapB;
uint channelSwapA;
matrix swapmatrix;
float4 swapmatrixoffset;
[numthreads(8,8,1)]
void ChannelSwapFunc(uint3 id : SV_DispatchThreadID)
{
float r;
float g;
float b;
float a;
if (channelSwapR == 4)
r = 0;
else if (channelSwapR == 5)
r = 0.5;
else if (channelSwapR == 6)
r = 1;
else
r = input[id.xy][channelSwapR];
if (channelSwapG == 4)
g = 0;
else if (channelSwapG == 5)
g = 0.5;
else if (channelSwapG == 6)
g = 1;
else
g = input[id.xy][channelSwapG];
if (channelSwapB == 4)
b = 0;
else if (channelSwapB == 5)
b = 0.5;
else if (channelSwapB == 6)
b = 1;
else
b = input[id.xy][channelSwapB];
if (channelSwapA == 4)
a = 0;
else if (channelSwapA == 5)
a = 0.5;
else if (channelSwapA == 6)
a = 1;
else
a = input[id.xy][channelSwapA];
result[id.xy] = float4(r, g, b, a);
}
[numthreads(8, 8, 1)]
void ChannelSwap4x4(uint3 id : SV_DispatchThreadID)
{
result[id.xy] = mul(input[id.xy], swapmatrix) + swapmatrixoffset;
}
PerformFunction() - this is the standard CPU method, my initial attempt. Average time was 854.6ms:
private void PerformFunction()
{
if (valueIn != null)
{
float r;
float g;
float b;
float a;
Texture2D _tempIn = valueIn.DuplicateTexture();
Color _tempColorHold = new Color();
Texture2D _tempOut = new Texture2D(_tempIn.width, _tempIn.height);
_tempOut.alphaIsTransparency = true;
for (int x = 0; x < _tempIn.width; x++)
for (int y = 0; y < _tempIn.height; y++)
{
_tempColorHold = _tempIn.GetPixel(x, y);
r = (rSlider.value == (int)RGBA.r) ? _tempColorHold.r :
(rSlider.value == (int)RGBA.g) ? _tempColorHold.g :
(rSlider.value == (int)RGBA.b) ? _tempColorHold.b :
(rSlider.value == (int)RGBA.a) ? _tempColorHold.a :
(rSlider.value == (int)RGBA.zero) ? 0f :
(rSlider.value == (int)RGBA.half) ? 0.5f : 1f;
g = (gSlider.value == (int)RGBA.r) ? _tempColorHold.r :
(gSlider.value == (int)RGBA.g) ? _tempColorHold.g :
(gSlider.value == (int)RGBA.b) ? _tempColorHold.b :
(gSlider.value == (int)RGBA.a) ? _tempColorHold.a :
(gSlider.value == (int)RGBA.zero) ? 0f :
(gSlider.value == (int)RGBA.half) ? 0.5f : 1f;
b = (bSlider.value == (int)RGBA.r) ? _tempColorHold.r :
(bSlider.value == (int)RGBA.g) ? _tempColorHold.g :
(bSlider.value == (int)RGBA.b) ? _tempColorHold.b :
(bSlider.value == (int)RGBA.a) ? _tempColorHold.a :
(bSlider.value == (int)RGBA.zero) ? 0f :
(bSlider.value == (int)RGBA.half) ? 0.5f : 1f;
a = (aSlider.value == (int)RGBA.r) ? _tempColorHold.r :
(aSlider.value == (int)RGBA.g) ? _tempColorHold.g :
(aSlider.value == (int)RGBA.b) ? _tempColorHold.b :
(aSlider.value == (int)RGBA.a) ? _tempColorHold.a :
(aSlider.value == (int)RGBA.zero) ? 0f :
(aSlider.value == (int)RGBA.half) ? 0.5f : 1f;
_tempOut.SetPixel(x, y, new Color(r, g, b, a));
}
_tempOut.Apply(false, false);
this.ValueOut = _tempOut;
portOut.Value = this.ValueOut;
}
}
PerformFunctionCompute() - this was the same basic principle, but run using ChannelSwapFunc() in the ComputeShader. Average time was 55.2ms:
private void PerformFunctionCompute()
{
shader = (ComputeShader)Resources.Load("Texture2D_Mod");
int _kernelHandle = shader.FindKernel("ChannelSwapFunc");
Texture2D _tempIn = valueIn.DuplicateTexture();
RenderTexture _tex = new RenderTexture(_tempIn.width, _tempIn.height, 24);
_tex.enableRandomWrite = true;
_tex.Create();
shader.SetTexture(_kernelHandle, "result", _tex);
shader.SetTexture(_kernelHandle, "input", _tempIn);
shader.SetInt("channelSwapR", rSlider.value);
shader.SetInt("channelSwapG", gSlider.value);
shader.SetInt("channelSwapB", bSlider.value);
shader.SetInt("channelSwapA", aSlider.value);
shader.Dispatch(_kernelHandle, _tempIn.width / 8, _tempIn.height / 8, 1);
RenderTexture.active = _tex;
Texture2D _result = new Texture2D(_tempIn.width, _tempIn.height);
_result.ReadPixels(new Rect(0, 0, _tempIn.width, _tempIn.height), 0, 0);
_result.Apply(false, false);
this.ValueOut = _result;
portOut.Value = this.ValueOut;
}
PerformFunctionMatrix4x4() - this was the Matrix4x4 version from @DMGregory. Average time was 395.4ms:
private void PerformFunctionMatrix4x4()
{
if (valueIn != null)
{
Texture2D _tempIn = valueIn.DuplicateTexture();
Color32[] pixelData = _tempIn.GetPixels32();
var matrix = new Matrix4x4(
ToMatrixRow((RGBA)rRGBA),
ToMatrixRow((RGBA)gRGBA),
ToMatrixRow((RGBA)bRGBA),
ToMatrixRow((RGBA)aRGBA)
);
matrix = matrix.transpose;
var offset = new Vector4(ToOffset((RGBA)rRGBA), ToOffset((RGBA)gRGBA), ToOffset((RGBA)bRGBA), ToOffset((RGBA)aRGBA));
for (int i = 0; i < pixelData.Length; i++)
{
Vector4 c = (Color)pixelData[i];
c = (matrix * c) + offset;
pixelData[i] = (Color)c;
}
Texture2D _tempOut = new Texture2D(_tempIn.width, _tempIn.height);
_tempOut.alphaIsTransparency = true;
_tempOut.SetPixels32(pixelData);
_tempOut.Apply(false, false);
this.ValueOut = _tempOut;
portOut.Value = this.ValueOut;
}
}
PerformFunctionComputeMatrix4x4() - this is the ComputeShader version of the Matrix4x4 version of the function. Average time was 54.5ms:
private void PerformFunctionComputeMatrix4x4()
{
if (valueIn != null)
{
shader = (ComputeShader)Resources.Load("Texture2D_Mod");
int _kernelHandle = shader.FindKernel("ChannelSwap4x4");
Texture2D _tempIn = valueIn.DuplicateTexture();
RenderTexture _tex = new RenderTexture(_tempIn.width, _tempIn.height, 24);
_tex.enableRandomWrite = true;
_tex.Create();
var matrix = new Matrix4x4(
ToMatrixRow((RGBA)rRGBA),
ToMatrixRow((RGBA)gRGBA),
ToMatrixRow((RGBA)bRGBA),
ToMatrixRow((RGBA)aRGBA)
);
matrix = matrix.transpose;
var offset = new Vector4(ToOffset((RGBA)rRGBA), ToOffset((RGBA)gRGBA), ToOffset((RGBA)bRGBA), ToOffset((RGBA)aRGBA));
shader.SetTexture(_kernelHandle, "result", _tex);
shader.SetTexture(_kernelHandle, "input", _tempIn);
shader.SetMatrix("swapmatrix", matrix);
shader.SetVector("swapmatrixoffset", offset);
shader.Dispatch(_kernelHandle, _tempIn.width / 8, _tempIn.height / 8, 1);
RenderTexture.active = _tex;
Texture2D _result = new Texture2D(_tempIn.width, _tempIn.height);
_result.ReadPixels(new Rect(0, 0, _tempIn.width, _tempIn.height), 0, 0);
_result.Apply(false, false);
this.ValueOut = _result;
portOut.Value = this.ValueOut;
}
}