830 lines
No EOL
37 KiB
C#
830 lines
No EOL
37 KiB
C#
using System.IO;
|
|
using UnityEngine;
|
|
using UnityEditor.AssetImporters;
|
|
using UnityEditor;
|
|
using Unity.Collections;
|
|
using UnityEngine.Experimental.Rendering;
|
|
using System;
|
|
using System.Runtime.InteropServices;
|
|
using System.IO.Compression;
|
|
using Unity.Jobs;
|
|
using Unity.Collections.LowLevel.Unsafe;
|
|
using Unity.Mathematics;
|
|
using Unity.Burst;
|
|
using Unity.Burst.CompilerServices;
|
|
using UnityEngine.Rendering.Universal;
|
|
using System.Collections.Generic;
|
|
using static UnityEngine.Rendering.Universal.Internal.CopyColorPass;
|
|
|
|
|
|
namespace SLZ.SLZEditorTools
|
|
{
|
|
[ScriptedImporter(1, new string[] { "vol3d" }, new string[] { "" }, AllowCaching = true)]
|
|
public class Vol3dImporter : ScriptedImporter
|
|
{
|
|
public AndroidCompression androidCompression;
|
|
public PCCompression pcCompression;
|
|
public TextureWrapMode wrapMode = TextureWrapMode.Clamp;
|
|
public FilterMode filterMode = FilterMode.Trilinear;
|
|
public TextureCompressionQuality CompressionQuality = TextureCompressionQuality.Best;
|
|
|
|
const TextureFormat defaultAndroidCompression = TextureFormat.ASTC_HDR_4x4;
|
|
const TextureFormat defaultPCCompression = TextureFormat.BC6H;
|
|
|
|
|
|
public enum AndroidCompression : int
|
|
{
|
|
Default = 0,
|
|
ASTC_HDR_4x4 = TextureFormat.ASTC_HDR_4x4,
|
|
ASTC_HDR_5x5 = TextureFormat.ASTC_HDR_5x5,
|
|
ASTC_HDR_6x6 = TextureFormat.ASTC_HDR_6x6,
|
|
ASTC_HDR_8x8 = TextureFormat.ASTC_HDR_8x8,
|
|
ASTC_HDR_10x10 = TextureFormat.ASTC_HDR_10x10,
|
|
ASTC_HDR_12x12 = TextureFormat.ASTC_HDR_12x12,
|
|
ASTC_4x4 = TextureFormat.ASTC_4x4,
|
|
}
|
|
|
|
public static Dictionary<TextureFormat, int> BlockSize = new Dictionary<TextureFormat, int>
|
|
{
|
|
{ TextureFormat.DXT1, 4},
|
|
{ TextureFormat.DXT5, 4},
|
|
{ TextureFormat.BC6H, 4},
|
|
{ TextureFormat.BC7, 4},
|
|
{ TextureFormat.ASTC_HDR_4x4, 4 },
|
|
{ TextureFormat.ASTC_HDR_5x5, 5 },
|
|
{ TextureFormat.ASTC_HDR_6x6, 6 },
|
|
{ TextureFormat.ASTC_HDR_8x8, 8 },
|
|
{ TextureFormat.ASTC_HDR_10x10, 10 },
|
|
{ TextureFormat.ASTC_HDR_12x12, 12 },
|
|
{ TextureFormat.ASTC_4x4, 4 },
|
|
};
|
|
|
|
public static Dictionary<TextureFormat, int> BytesPerBlock = new Dictionary<TextureFormat, int>
|
|
{
|
|
{ TextureFormat.RGB24, 3},
|
|
{ TextureFormat.RGBA32, 4},
|
|
{ TextureFormat.RGBA64, 8},
|
|
{ TextureFormat.RGBAHalf, 8},
|
|
{ TextureFormat.RGBAFloat, 16},
|
|
{ TextureFormat.DXT1, 8},
|
|
{ TextureFormat.DXT5, 16},
|
|
{ TextureFormat.BC6H, 16},
|
|
{ TextureFormat.BC7, 16},
|
|
{ TextureFormat.ASTC_HDR_4x4, 16 },
|
|
{ TextureFormat.ASTC_HDR_5x5, 16 },
|
|
{ TextureFormat.ASTC_HDR_6x6, 16 },
|
|
{ TextureFormat.ASTC_HDR_8x8, 16 },
|
|
{ TextureFormat.ASTC_HDR_10x10, 16 },
|
|
{ TextureFormat.ASTC_HDR_12x12, 16 },
|
|
{ TextureFormat.ASTC_4x4, 16 },
|
|
};
|
|
|
|
public enum PCCompression : int
|
|
{
|
|
Default = 0,
|
|
BC6H = TextureFormat.BC6H,
|
|
NonHdrBC7 = TextureFormat.BC7
|
|
}
|
|
|
|
public override void OnImportAsset(AssetImportContext ctx)
|
|
{
|
|
var buildTarget = ctx.selectedBuildTarget;
|
|
string path = Path.GetFullPath(ctx.assetPath);
|
|
bool dispFileBytes = false;
|
|
NativeArray<byte> fileBytes = Vol3d.ReadVol3DToNative(path, out Vol3d.ImageInfo info);
|
|
try
|
|
{
|
|
TextureFormat compressedFmt = GetPlatformFormat(buildTarget);
|
|
TextureFormat uncompFmt = GraphicsFormatUtility.GetTextureFormat(info.graphicsFormat);
|
|
//Debug.Log("Uncompressed Format: " + uncompFmt.ToString() + " " + (GraphicsFormat)info.graphicsFormat);
|
|
int rawSliceSize = fileBytes.Length / info.depth;
|
|
bool needsPadding = compressedFmt == TextureFormat.BC6H || compressedFmt == TextureFormat.BC7;
|
|
|
|
|
|
int mipLevels = math.max(1, info.mipLevels);
|
|
//Debug.Log("Mip Levels: " + mipLevels);
|
|
int blockSize = BlockSize[compressedFmt];
|
|
int bytesPerBlock = BytesPerBlock[compressedFmt];
|
|
int bytesPerRawPixel = BytesPerBlock[uncompFmt];
|
|
int2[] mipBlocks = new int2[mipLevels];
|
|
|
|
int3[] mipDim = new int3[mipLevels];
|
|
|
|
int[] cmpSliceLen = new int[mipLevels];
|
|
int[] uncmpSliceLen = new int[mipLevels];
|
|
|
|
int3 imageDim = new int3(info.width, info.height, info.depth);
|
|
for (int mip = 0; mip < mipLevels; mip++)
|
|
{
|
|
mipDim[mip] = math.max(imageDim >> mip, 1);
|
|
|
|
// closest multiple of the block size
|
|
mipBlocks[mip] = (mipDim[mip].xy + (blockSize - 1)) / blockSize;
|
|
}
|
|
if (needsPadding)
|
|
{
|
|
for (int mip = 1; mip < mipLevels; mip++)
|
|
{
|
|
mipDim[mip] = new int3(mipBlocks[mip] * blockSize, mipDim[mip].z);
|
|
}
|
|
}
|
|
|
|
int[] compressedMipPtr = new int[mipLevels + 1];
|
|
int[] uncompressedMipPtr = new int[mipLevels + 1];
|
|
compressedMipPtr[0] = 0;
|
|
uncompressedMipPtr[0] = 0;
|
|
for (int mip = 0; mip < mipLevels; mip++)
|
|
{
|
|
cmpSliceLen[mip] = bytesPerBlock * ( mipBlocks[mip].x * mipBlocks[mip].y);
|
|
compressedMipPtr[mip + 1] = compressedMipPtr[mip] + mipDim[mip].z * cmpSliceLen[mip];
|
|
|
|
uncmpSliceLen[mip] = bytesPerRawPixel * mipDim[mip].x * mipDim[mip].y;
|
|
uncompressedMipPtr[mip + 1] = uncompressedMipPtr[mip] + mipDim[mip].z * uncmpSliceLen[mip];
|
|
}
|
|
|
|
bool compress = true;
|
|
|
|
Texture3D volumeCompressed = new Texture3D(info.width, info.height, info.depth,
|
|
compress ? compressedFmt : uncompFmt, info.mipLevels > 1);
|
|
volumeCompressed.wrapMode = wrapMode;
|
|
volumeCompressed.filterMode = filterMode;
|
|
|
|
//if (compress)
|
|
|
|
if (needsPadding)
|
|
{
|
|
CopyAndCompressTextureBC6H(fileBytes, volumeCompressed, info, uncompFmt, compressedFmt,
|
|
mipLevels, ref mipDim, ref uncompressedMipPtr, ref compressedMipPtr, ref uncmpSliceLen, ref cmpSliceLen, bytesPerRawPixel);
|
|
}
|
|
else
|
|
{
|
|
CopyAndCompressTexture<byte, byte>(fileBytes, volumeCompressed, info, uncompFmt, compressedFmt,
|
|
mipLevels, ref mipDim, ref uncompressedMipPtr, ref compressedMipPtr, ref uncmpSliceLen, ref cmpSliceLen, compress);
|
|
}
|
|
//}
|
|
|
|
/*
|
|
for (int slice = 0; slice < info.depth; slice++)
|
|
{
|
|
Texture2D tex;
|
|
tex = new Texture2D(info.width, info.height, uncompFmt, true, true);
|
|
for (int mip = 0; mip < mipLevels; mip++)
|
|
{
|
|
if (slice < mipDim[mip].z)
|
|
{
|
|
Debug.Log("Mip: " + mip + " Slice Pointer: " + uncompressedMipPtr[mip] + slice * uncmpSliceBytes[mip]);
|
|
NativeArray<byte> texBacking = tex.GetPixelData<byte>(mip);
|
|
//if (uncmpSliceBytes[mip] != texBacking.Length) Debug.LogError("Expected size: " + uncmpSliceBytes[mip] + " got: " + texBacking.Length);
|
|
NativeArray<byte>.Copy(fileBytes, uncompressedMipPtr[mip] + slice * uncmpSliceBytes[mip], texBacking, 0, uncmpSliceBytes[mip]);
|
|
}
|
|
}
|
|
//rawPtr += texBacking.Length;
|
|
//texBacking.Dispose();
|
|
if (compress)
|
|
{
|
|
EditorUtility.CompressTexture(tex, compressedFmt, (int)CompressionQuality);
|
|
}
|
|
if (slice == 0)
|
|
{
|
|
AssetDatabase.CreateAsset(tex, "Assets/TestTexture.asset");
|
|
}
|
|
//if (d == 0) Debug.Log("Dimensions: " + tex.width + tex.height);
|
|
for (int mip = 0; mip < mipLevels; mip++)
|
|
{
|
|
int mipDepth = mipDim[mip].z;
|
|
if (slice < mipDepth)
|
|
{
|
|
NativeArray<byte> texBacking = tex.GetPixelData<byte>(mip);
|
|
if (cmpSliceBytes[mip] != texBacking.Length) Debug.LogError("Cmp Expected size: " + cmpSliceBytes[mip] + " got: " + texBacking.Length);
|
|
NativeArray<byte> vcData = volumeCompressed.GetPixelData<byte>(mip);
|
|
if (vcData.Length / mipDepth != texBacking.Length) Debug.LogError("VC Expected size: " + vcData.Length + " got: " + texBacking.Length);
|
|
NativeArray<byte>.Copy(texBacking, 0, vcData,
|
|
compress ? slice * cmpSliceBytes[mip] : slice * uncmpSliceBytes[mip],
|
|
compress ? cmpSliceBytes[mip] : uncmpSliceBytes[mip]);
|
|
}
|
|
}
|
|
|
|
DestroyImmediate(tex);
|
|
}
|
|
*/
|
|
///GraphicsFormat graphicsFormat = GraphicsFormatUtility.GetGraphicsFormat(compressedFmt, false);
|
|
|
|
|
|
SerializedObject volSerial = new SerializedObject(volumeCompressed);
|
|
SerializedProperty isReadable = volSerial.FindProperty("m_IsReadable");
|
|
isReadable.boolValue = false;
|
|
//SerializedProperty isSRGB = volSerial.FindProperty("m_IsReadable");
|
|
volSerial.ApplyModifiedProperties();
|
|
ctx.AddObjectToAsset("MainTex", volumeCompressed);
|
|
ctx.SetMainObject(volumeCompressed);
|
|
}
|
|
finally
|
|
{
|
|
fileBytes.Dispose();
|
|
}
|
|
}
|
|
|
|
TextureFormat GetPlatformFormat(BuildTarget target)
|
|
{
|
|
if (target == BuildTarget.Android)
|
|
{
|
|
if (androidCompression == AndroidCompression.Default)
|
|
{
|
|
return defaultAndroidCompression;
|
|
}
|
|
else
|
|
{
|
|
return (TextureFormat)androidCompression;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (pcCompression == PCCompression.Default)
|
|
{
|
|
return defaultPCCompression;
|
|
}
|
|
else
|
|
{
|
|
return (TextureFormat)pcCompression;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CopyAndCompressTexture<T,T2>(NativeArray<T> fileBytes, Texture3D tex3D,
|
|
Vol3d.ImageInfo info, TextureFormat uncompFmt, TextureFormat compressedFmt,
|
|
int mipLevels, ref int3[] mipDim, ref int[] uncompressedMipPtr, ref int[] compressedMipPtr,
|
|
ref int[] uncmpSliceLen, ref int[] cmpSliceLen, bool compress
|
|
) where T : struct
|
|
where T2 : struct
|
|
{
|
|
int maxXyMip = (int)math.log2(math.max(info.width, info.height));
|
|
for (int slice = 0; slice < info.depth; slice++)
|
|
{
|
|
Texture2D tex;
|
|
tex = new Texture2D(info.width, info.height, uncompFmt, true, true);
|
|
for (int mip = 0; mip <= maxXyMip; mip++)
|
|
{
|
|
if (slice < mipDim[mip].z)
|
|
{
|
|
//Debug.Log("Mip: " + mip + " Slice Pointer: " + uncompressedMipPtr[mip] + slice * uncmpSliceLen[mip]);
|
|
NativeArray<T> texBacking = tex.GetPixelData<T>(mip);
|
|
//if (uncmpSliceBytes[mip] != texBacking.Length) Debug.LogError("Expected size: " + uncmpSliceBytes[mip] + " got: " + texBacking.Length);
|
|
NativeArray<T>.Copy(fileBytes, uncompressedMipPtr[mip] + slice * uncmpSliceLen[mip], texBacking, 0, uncmpSliceLen[mip]);
|
|
texBacking.Dispose();
|
|
}
|
|
}
|
|
//rawPtr += texBacking.Length;
|
|
//texBacking.Dispose();
|
|
//if (slice == 0)
|
|
//{
|
|
// AssetDatabase.CreateAsset(tex, "Assets/TestTexture.asset");
|
|
//}
|
|
if (compress)
|
|
{
|
|
EditorUtility.CompressTexture(tex, compressedFmt, (int)CompressionQuality);
|
|
}
|
|
|
|
//if (d == 0) Debug.Log("Dimensions: " + tex.width + tex.height);
|
|
for (int mip = 0; mip < mipLevels; mip++)
|
|
{
|
|
|
|
int mipDepth = mipDim[mip].z;
|
|
if (slice < mipDepth)
|
|
{
|
|
NativeArray<T2> texBacking = tex.GetPixelData<T2>(math.min(mip, maxXyMip));
|
|
NativeArray<T2> vcData = tex3D.GetPixelData<T2>(mip);
|
|
//if (cmpSliceLen[mip] != texBacking.Length) Debug.LogError("Cmp Expected size: " + cmpSliceLen[mip] + " got: " + texBacking.Length);
|
|
//if (vcData.Length / mipDepth != texBacking.Length) Debug.LogError("VC Expected size: " + vcData.Length + " got: " + texBacking.Length);
|
|
NativeArray<T2>.Copy(texBacking, 0, vcData,
|
|
compress ? slice * cmpSliceLen[mip] : slice * uncmpSliceLen[mip],
|
|
compress ? cmpSliceLen[mip] : uncmpSliceLen[mip]);
|
|
texBacking.Dispose();
|
|
vcData.Dispose();
|
|
}
|
|
}
|
|
DestroyImmediate(tex);
|
|
}
|
|
}
|
|
|
|
void CopyAndCompressTextureBC6H(NativeArray<byte> fileBytes, Texture3D tex3D,
|
|
Vol3d.ImageInfo info, TextureFormat uncompFmt, TextureFormat compressedFmt,
|
|
int mipLevels, ref int3[] mipDim, ref int[] uncompressedMipPtr, ref int[] compressedMipPtr,
|
|
ref int[] uncmpSliceLen, ref int[] cmpSliceLen, int pixelSize
|
|
)
|
|
{
|
|
NativeArray<byte> paddedBytes = GetPaddedTexture(fileBytes, mipDim[0], mipLevels, pixelSize, 4);
|
|
//File.WriteAllBytes("C:/temp/TestFile.bin", paddedBytes.ToArray());
|
|
try
|
|
{
|
|
Texture2D tex;
|
|
GraphicsFormat gformat = GraphicsFormatUtility.GetGraphicsFormat(uncompFmt, false);
|
|
tex = new Texture2D(mipDim[0].x, mipDim[0].y, uncompFmt, false, true);
|
|
for (int mip = 0; mip < mipLevels; mip++)
|
|
{
|
|
|
|
for (int slice = 0; slice < mipDim[mip].z; slice++)
|
|
{
|
|
tex.Reinitialize(mipDim[mip].x, mipDim[mip].y, gformat, false);
|
|
NativeArray<byte> texBacking = tex.GetPixelData<byte>(0);
|
|
//if (uncmpSliceBytes[mip] != texBacking.Length) Debug.LogError("Expected size: " + uncmpSliceBytes[mip] + " got: " + texBacking.Length);
|
|
NativeArray<byte>.Copy(paddedBytes, uncompressedMipPtr[mip] + slice * uncmpSliceLen[mip], texBacking, 0, uncmpSliceLen[mip]);
|
|
texBacking.Dispose();
|
|
//if ((mip == mipLevels - 2) && slice == 0)
|
|
//{
|
|
// Span<byte> bytes = stackalloc byte[pixelSize];
|
|
// int ptr = uncompressedMipPtr[mip] + slice * uncmpSliceLen[mip];
|
|
// for (int i = 0; i < pixelSize; i++)
|
|
// {
|
|
// bytes[i] = paddedBytes[ptr + i];
|
|
// }
|
|
// ReadOnlySpan<ushort> halfVec = MemoryMarshal.Cast<byte, ushort>(bytes);
|
|
//}
|
|
//rawPtr += texBacking.Length;
|
|
//texBacking.Dispose();
|
|
|
|
EditorUtility.CompressTexture(tex, compressedFmt, (int)CompressionQuality);
|
|
|
|
//if (d == 0) Debug.Log("Dimensions: " + tex.width + tex.height);
|
|
|
|
int mipDepth = mipDim[mip].z;
|
|
|
|
NativeArray<byte> texBacking2 = tex.GetPixelData<byte>(0);
|
|
//if (cmpSliceLen[mip] != texBacking.Length) Debug.LogError("Cmp Expected size: " + cmpSliceLen[mip] + " got: " + texBacking.Length);
|
|
NativeArray<byte> vcData = tex3D.GetPixelData<byte>(mip);
|
|
//if (vcData.Length / mipDepth != texBacking.Length) Debug.LogError("VC Expected size: " + vcData.Length + " got: " + texBacking.Length);
|
|
NativeArray<byte>.Copy(texBacking2, 0, vcData, slice * cmpSliceLen[mip], cmpSliceLen[mip]);
|
|
texBacking2.Dispose();
|
|
vcData.Dispose();
|
|
//Resources.UnloadAsset(tex);
|
|
}
|
|
}
|
|
DestroyImmediate(tex);
|
|
}
|
|
finally
|
|
{
|
|
paddedBytes.Dispose();
|
|
|
|
}
|
|
}
|
|
|
|
NativeArray<byte> GetPaddedTexture(NativeArray<byte> rawImage, int3 dimensions, int numMips, int pixelSize, int padSize = 4)
|
|
{
|
|
int3 padAdd = new int3(padSize - 1, padSize - 1, 0);
|
|
int3 padMul = new int3(padSize, padSize, 1);
|
|
Span<int3> mipDim = stackalloc int3[numMips];
|
|
Span<int3> padMipDim = stackalloc int3[numMips];
|
|
Span<int> mipPtr = stackalloc int[numMips];
|
|
Span<int> padMipPtr = stackalloc int[numMips];
|
|
|
|
mipDim[0] = padMipDim[0] = dimensions; // mip 0 guaranteed to be multiple of 4 on x and y
|
|
mipPtr[0] = padMipPtr[0] = 0;
|
|
int totalPadTSize = dimensions.x * dimensions.y * dimensions.z * pixelSize;
|
|
for (int mip = 1; mip < numMips; mip++)
|
|
{
|
|
int prevMip = mip - 1;
|
|
mipDim[mip] = math.max(dimensions >> mip, 1);
|
|
padMipDim[mip] = ((mipDim[mip] + padAdd) / padMul) * padMul;
|
|
mipPtr[mip] = mipPtr[prevMip] + mipDim[prevMip].x * mipDim[prevMip].y * mipDim[prevMip].z * pixelSize;
|
|
padMipPtr[mip] = padMipPtr[prevMip] + padMipDim[prevMip].x * padMipDim[prevMip].y * padMipDim[prevMip].z * pixelSize;
|
|
totalPadTSize += padMipPtr[mip];
|
|
|
|
}
|
|
|
|
NativeArray<byte> padTex = new NativeArray<byte>(totalPadTSize, Allocator.Temp);
|
|
for (int mip = 0; mip < numMips; mip++)
|
|
{
|
|
int rowPad = (padMipDim[mip].x - mipDim[mip].x) * pixelSize;
|
|
int columnPad = padMipDim[mip].x * (padMipDim[mip].y - mipDim[mip].y) * pixelSize;
|
|
bool xLargerThanBlock = mipDim[mip].x >= padSize;
|
|
bool yLargerThanBlock = mipDim[mip].y >= padSize;
|
|
|
|
for (int z = 0; z < mipDim[mip].z; z++)
|
|
{
|
|
int slicePtr = mipPtr[mip] + z * mipDim[mip].x * mipDim[mip].y * pixelSize;
|
|
int padSlicePtr = padMipPtr[mip] + z * padMipDim[mip].x * padMipDim[mip].y * pixelSize;
|
|
//Debug.Log(padMipDim[mip].x);
|
|
for (int y = 0; y < mipDim[mip].y; y++)
|
|
{
|
|
int rowPtr = slicePtr + y * mipDim[mip].x * pixelSize;
|
|
int padRowPtr = padSlicePtr + y * padMipDim[mip].x * pixelSize;
|
|
int rowCount = mipDim[mip].x * pixelSize;
|
|
NativeArray<byte>.Copy(rawImage, rowPtr, padTex, padRowPtr, rowCount);
|
|
if (rowPad > 0)
|
|
{
|
|
if (xLargerThanBlock)
|
|
{
|
|
NativeArray<byte>.Copy(rawImage, rowPtr + rowCount - rowPad, padTex, padRowPtr + rowCount, rowPad);
|
|
}
|
|
else
|
|
{
|
|
for (int p = 0; p < rowPad; p++)
|
|
{
|
|
NativeArray<byte>.Copy(rawImage, rowPtr + rowCount - pixelSize, padTex, padRowPtr + rowCount + p * pixelSize, pixelSize);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (columnPad > 0)
|
|
{
|
|
int padColumnPtr = padSlicePtr + padMipDim[mip].x * mipDim[mip].y * pixelSize;
|
|
if (yLargerThanBlock)
|
|
{
|
|
NativeArray<byte>.Copy(padTex, padColumnPtr - columnPad, padTex, padColumnPtr, columnPad);
|
|
}
|
|
else
|
|
{
|
|
int rowSize = padMipDim[mip].x * pixelSize;
|
|
for (int p = 0; p < rowPad; p++)
|
|
{
|
|
NativeArray<byte>.Copy(padTex, padColumnPtr - rowSize, padTex, padColumnPtr + p * rowSize, columnPad);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
return padTex;
|
|
}
|
|
|
|
string PrintBytesAsHalf3(ref NativeArray<byte> array, int index)
|
|
{
|
|
Span<byte> bytes = stackalloc byte[6];
|
|
|
|
for (int i = 0; i < 6; i++)
|
|
{
|
|
bytes[i] = array[index + i];
|
|
}
|
|
ReadOnlySpan<ushort> halfVec = MemoryMarshal.Cast<byte, ushort>(bytes);
|
|
return string.Format("{0}, {1}, {2}", Mathf.HalfToFloat(halfVec[0]), Mathf.HalfToFloat(halfVec[1]), Mathf.HalfToFloat(halfVec[2]));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Manually pad mips of the texture to work around a bug in Unity's BC6H compressor not padding mip levels before compressing
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
/// <param name="rawImage">NativeArray containing the entire uncompressed image</param>
|
|
/// <param name="dimensions">width, height, and depth of the image's lowest mip</param>
|
|
/// <param name="mips">Number of mip levels</param>
|
|
/// <param name="pixelSize">number of elements in the nativeArray that represent a single pixel</param>
|
|
/// <param name="padSize">number of pixels in a block that we need to pad to be a multiple of, 4 for BC6H</param>
|
|
/// <returns></returns>
|
|
NativeArray<T> GetPaddedTextureJobs<T>(NativeArray<T> rawImage, int3 dimensions, int numMips, int pixelSize, int padSize = 4) where T : struct
|
|
{
|
|
int3 padAdd = new int3(padSize - 1, padSize - 1, 0);
|
|
int3 padMul = new int3(padSize, padSize, 1);
|
|
|
|
//int tSize = Marshal.SizeOf<T>();
|
|
int rawPtr = dimensions.x * dimensions.y * dimensions.z * pixelSize;
|
|
int paddedPtr = rawPtr;
|
|
NativeArray<rowPadInfo> rowInfos = new NativeArray<rowPadInfo>(numMips - 1, Allocator.Temp);
|
|
NativeArray<int> mipRowOffsets = new NativeArray<int>(numMips - 1, Allocator.Temp);
|
|
int3 mipDim = new int3(0,0,0);
|
|
int3 paddedDim = new int3(0,0,0);
|
|
int rowOffset = 0;
|
|
int padRowCount = 0;
|
|
for (int level = 0; level < numMips - 1; level++)
|
|
{
|
|
rowOffset += mipDim.y * mipDim.z;
|
|
mipDim = math.max(dimensions >> level, 1);
|
|
paddedDim = ((mipDim + padAdd) / padMul) * padMul;
|
|
padRowCount += mipDim.y * mipDim.z;
|
|
mipRowOffsets[level] = rowOffset;
|
|
rowInfos[level] = new rowPadInfo
|
|
(
|
|
rawPtr,
|
|
paddedPtr,
|
|
paddedDim.x * paddedDim.y * pixelSize,
|
|
(ushort)mipDim.y,
|
|
(ushort)(mipDim.x * pixelSize),
|
|
(ushort)(paddedDim.x * pixelSize),
|
|
(ushort)((paddedDim.x - mipDim.x) * pixelSize)
|
|
);
|
|
|
|
rawPtr += mipDim.x * mipDim.y * mipDim.z * pixelSize;
|
|
paddedPtr += paddedDim.x * paddedDim.y * paddedDim.z * pixelSize;
|
|
}
|
|
|
|
|
|
NativeArray<T> outImage = new NativeArray<T>(paddedPtr, Allocator.TempJob);
|
|
for (int mip = 0; mip < numMips - 1; mip++)
|
|
{
|
|
|
|
}
|
|
|
|
return outImage;
|
|
}
|
|
|
|
|
|
readonly struct rowPadInfo
|
|
{
|
|
public readonly int rawPointer; // Start of the mip in the unpadded array
|
|
public readonly int paddedPointer; // Start of the mip in the padded array
|
|
public readonly int paddedSliceSize; // Size of a single padded Z slice, including the padding on both the rows and columns
|
|
public readonly ushort rowsPerSlice; // number of rows per Z slice in the unpadded array (ie the height)
|
|
public readonly ushort rowLength; // number of elements in the array of a single unpadded row (pixels * bytes per pixel / bytes per array element)
|
|
public readonly ushort paddedRowLength; // number of elements in the array of a single padded row
|
|
public readonly ushort padLength; // paddedRowLength - rowLength
|
|
|
|
public rowPadInfo(
|
|
|
|
int rawPointer,
|
|
int paddedPointer,
|
|
int paddedSliceSize,
|
|
ushort rowsPerSlice,
|
|
ushort rowLength,
|
|
ushort paddedRowLength,
|
|
ushort padLength)
|
|
{
|
|
this.rawPointer = rawPointer;
|
|
this.paddedPointer = paddedPointer;
|
|
this.paddedSliceSize = paddedSliceSize;
|
|
this.rowsPerSlice = rowsPerSlice;
|
|
this.rowLength = rowLength;
|
|
this.paddedRowLength = paddedRowLength;
|
|
this.padLength = padLength;
|
|
}
|
|
|
|
}
|
|
|
|
[BurstCompile]
|
|
struct PadRow<T> : IJobParallelFor where T : struct
|
|
{
|
|
[NativeDisableContainerSafetyRestriction]
|
|
public NativeArray<T> paddedData;
|
|
[ReadOnly]
|
|
public NativeArray<T> rawData;
|
|
[ReadOnly]
|
|
public NativeArray<int> mipRowOffset;
|
|
[ReadOnly]
|
|
public NativeArray<rowPadInfo> mipInfos;
|
|
|
|
public void Execute(int i)
|
|
{
|
|
int mip = GetMipLevel(mipRowOffset.Length, i);
|
|
rowPadInfo m = mipInfos[mip];
|
|
int mipRow = i - mipRowOffset[mip];
|
|
int rowSliceIdx = mipRow % m.rowsPerSlice; // index of the row in the slice
|
|
int sliceOffset = (mipRow / m.rowsPerSlice) * m.paddedSliceSize + m.paddedPointer;
|
|
int slicePtr = rowSliceIdx * m.paddedRowLength + sliceOffset;
|
|
NativeArray<T>.Copy(rawData, mipRow * m.rowLength + m.rawPointer,
|
|
paddedData, slicePtr, m.rowLength);
|
|
NativeArray<T>.Copy(rawData, (mipRow + 1) * m.rowLength - m.padLength,
|
|
paddedData, slicePtr + m.rowLength, m.padLength);
|
|
}
|
|
|
|
int GetMipLevel([AssumeRange(0,12)] int numMips, int threadIdx)
|
|
{
|
|
int mip = 0;
|
|
for (; mip < numMips; mip++)
|
|
{
|
|
if (mipRowOffset[mip] > threadIdx) break;
|
|
}
|
|
mip = math.max(0, mip - 1);
|
|
return mip;
|
|
}
|
|
|
|
}
|
|
|
|
#region SRGB_EXPERIMENT_NOT_USED
|
|
/*
|
|
static float3 Linear2sRGB(float3 val)
|
|
{
|
|
bool3 disc = val < 0.0031308f;
|
|
float3 small = val * 12.92f;
|
|
float3 big = (1.055f * math.pow(val, 1.0f / 2.4f)) - 0.055f;
|
|
float3 final = new float3(disc.x ? small.x : big.x, disc.y ? small.y : big.y, disc.z ? small.z : big.z);
|
|
return final;
|
|
}
|
|
|
|
interface IsRGB2Linear
|
|
{
|
|
public NativeArray<byte> FileBytes { get; set; }
|
|
public JobHandle ScheduleI(int i, int i2);
|
|
}
|
|
|
|
[BurstCompile]
|
|
struct RGB24Linear2SRGB : IJobParallelFor, IsRGB2Linear
|
|
{
|
|
[NativeDisableContainerSafetyRestriction]
|
|
public NativeArray<byte> fileBytes;
|
|
public NativeArray<byte> FileBytes { get => fileBytes; set => fileBytes = value; }
|
|
const float fixedToFloat = (1.0f / 255.0f);
|
|
public void Execute(int i)
|
|
{
|
|
int idx = 3 * i;
|
|
float3 color = new float3(fileBytes[idx], fileBytes[idx] + 1, fileBytes[idx + 2]);
|
|
color *= fixedToFloat;
|
|
color = Linear2sRGB(color);
|
|
color *= 255.0f;
|
|
|
|
//ReadOnlySpan<RGB24> colorSpan = stackalloc RGB24[1] { new RGB24((byte)color.x, (byte)color.y, (byte)color.z) };
|
|
//ReadOnlySpan<byte> castbyte = MemoryMarshal.AsBytes(colorSpan);
|
|
//NativeArraySpanExt.Copy(castbyte, 0, fileBytes, idx, 3);
|
|
fileBytes[idx] = (byte)color.x;
|
|
fileBytes[idx + 1] = (byte)color.y;
|
|
fileBytes[idx + 2] = (byte)color.z;
|
|
}
|
|
|
|
public JobHandle ScheduleI(int i, int i2)
|
|
{
|
|
return this.Schedule(i, i2);
|
|
}
|
|
}
|
|
|
|
[BurstCompile]
|
|
struct RGBHalf2SRGB : IJobParallelFor
|
|
{
|
|
[NativeDisableContainerSafetyRestriction]
|
|
public NativeArray<byte> fileBytes;
|
|
const float fixedToFloat = (1.0f / 255.0f);
|
|
public void Execute(int i)
|
|
{
|
|
int idx = 6 * i;
|
|
ReadOnlySpan<byte> rb = stackalloc byte[2] { fileBytes[idx], fileBytes[idx + 1] };
|
|
ReadOnlySpan<byte> gb = stackalloc byte[2] { fileBytes[idx + 2], fileBytes[idx + 3] };
|
|
ReadOnlySpan<byte> bb = stackalloc byte[2] { fileBytes[idx + 4], fileBytes[idx + 5] };
|
|
ReadOnlySpan<half> ru = MemoryMarshal.Cast<byte, half>(rb);
|
|
|
|
float3 color = new float3(fileBytes[idx], fileBytes[idx] + 1, fileBytes[idx + 2]);
|
|
color *= fixedToFloat;
|
|
color = Linear2sRGB(color);
|
|
color *= 255.0f;
|
|
|
|
//ReadOnlySpan<RGB24> colorSpan = stackalloc RGB24[1] { new RGB24((byte)color.x, (byte)color.y, (byte)color.z) };
|
|
//ReadOnlySpan<byte> castbyte = MemoryMarshal.AsBytes(colorSpan);
|
|
//NativeArraySpanExt.Copy(castbyte, 0, fileBytes, idx, 3);
|
|
fileBytes[idx] = (byte)color.x;
|
|
fileBytes[idx + 1] = (byte)color.y;
|
|
fileBytes[idx + 2] = (byte)color.z;
|
|
}
|
|
}
|
|
*/
|
|
#endregion
|
|
}
|
|
|
|
|
|
public static class Vol3d
|
|
{
|
|
const int headerSize = 32;
|
|
public const string fileExtension = ".vol3d";
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
struct Header
|
|
{
|
|
public UInt32 name;
|
|
public UInt16 graphicsFormat;
|
|
public UInt16 mipLevels;
|
|
public UInt16 width;
|
|
public UInt16 height;
|
|
public UInt16 depth;
|
|
public UInt16 compressionFormat;
|
|
public UInt64 compressedLength;
|
|
public UInt64 uncompressedLength;
|
|
|
|
public Header(UInt16 graphicsFormat, UInt16 mipLevels, UInt16 width, UInt16 height, UInt16 depth, UInt64 compressedLength, UInt64 uncompressedLength)
|
|
{
|
|
this.name = ((uint)'V') | ((uint)'O' << 8) | ((uint)'L' << 16) | ((uint)'3' << 24);
|
|
this.graphicsFormat = graphicsFormat;
|
|
this.mipLevels = mipLevels;
|
|
this.width = width;
|
|
this.height = height;
|
|
this.depth = depth;
|
|
this.compressionFormat = 0;
|
|
this.compressedLength = compressedLength;
|
|
this.uncompressedLength = uncompressedLength;
|
|
}
|
|
}
|
|
|
|
public enum CompressionFormat : UInt16
|
|
{
|
|
NONE = 0,
|
|
DEFLATE = 1,
|
|
}
|
|
|
|
public struct ImageInfo
|
|
{
|
|
public GraphicsFormat graphicsFormat;
|
|
public int mipLevels;
|
|
public int width;
|
|
public int height;
|
|
public int depth;
|
|
}
|
|
/*
|
|
[MenuItem("Tools/Test Create Vol3d")]
|
|
public static void TestSave3DTex()
|
|
{
|
|
UnityEngine.Object selection = Selection.activeObject;
|
|
if (selection.GetType() == typeof(Texture3D))
|
|
{
|
|
Texture3D texture = (Texture3D)selection;
|
|
string path = Path.GetFullPath(AssetDatabase.GetAssetPath(texture));
|
|
path += ".vol3d";
|
|
WriteTex3DToVol3D(texture, path);
|
|
}
|
|
}
|
|
*/
|
|
public static void WriteTex3DToVol3D(Texture3D tex, string path)
|
|
{
|
|
NativeArray<byte>[] data = new NativeArray<byte>[tex.mipmapCount];
|
|
int dataLength = 0;
|
|
for (int mip = 0; mip < tex.mipmapCount; mip++)
|
|
{
|
|
data[mip] = tex.GetPixelData<byte>(mip);
|
|
dataLength += data[mip].Length;
|
|
}
|
|
int graphicsFormat = (int)tex.graphicsFormat;
|
|
ulong length = (ulong)dataLength;
|
|
if (length == 0)
|
|
{
|
|
throw new Exception("Vol3d Saver: Failed to create vol3d. Texture3D has no data!");
|
|
}
|
|
Header header = new Header((ushort)graphicsFormat, (ushort)tex.mipmapCount, (ushort)tex.width, (ushort)tex.height, (ushort)tex.depth, length, length);
|
|
bool needsDirty = File.Exists(path);
|
|
using FileStream outStream = File.Create(path);
|
|
//byte[] fileOut = new byte[headerSize + data.Length];
|
|
|
|
byte[] fileUncompressed = new byte[dataLength];
|
|
int ptr = 0;
|
|
for (int mip = 0; mip < tex.mipmapCount; mip++)
|
|
{
|
|
NativeArray<byte>.Copy(data[mip], 0, fileUncompressed, ptr, data[mip].Length); ;
|
|
ptr += data[mip].Length;
|
|
}
|
|
if (true)
|
|
{
|
|
|
|
header.compressionFormat = (UInt16)CompressionFormat.DEFLATE;
|
|
byte[] headerBytes = HeaderToBytes(header);
|
|
outStream.Write(headerBytes, 0, headerBytes.Length);
|
|
using MemoryStream rawStream = new MemoryStream(fileUncompressed);
|
|
using DeflateStream compress = new DeflateStream(outStream, CompressionMode.Compress);
|
|
rawStream.CopyTo(compress);
|
|
}
|
|
//else
|
|
//{
|
|
// byte[] headerBytes = HeaderToBytes(header);
|
|
// outStream.Write(headerBytes, 0, headerBytes.Length);
|
|
// outStream.Write(fileUncompressed, 0, fileUncompressed.Length);
|
|
//}
|
|
}
|
|
|
|
public static NativeArray<byte> ReadVol3DToNative(string path, out ImageInfo imageInfo)
|
|
{
|
|
path = Path.GetFullPath(path);
|
|
using FileStream inStream = File.OpenRead(path);
|
|
Span<byte> headerByte = stackalloc byte[headerSize];
|
|
inStream.Read(headerByte);
|
|
ReadOnlySpan<Header> header = MemoryMarshal.Cast<byte, Header>(headerByte);
|
|
|
|
imageInfo = new ImageInfo();
|
|
imageInfo.graphicsFormat = (GraphicsFormat)header[0].graphicsFormat;
|
|
imageInfo.mipLevels = header[0].mipLevels;
|
|
imageInfo.width = header[0].width;
|
|
imageInfo.height = header[0].height;
|
|
imageInfo.depth = header[0].depth;
|
|
int cfilesize = (int)header[0].compressedLength;
|
|
int dfilesize = (int)header[0].uncompressedLength;
|
|
bool compressed = header[0].compressionFormat > 0;
|
|
|
|
if (compressed)
|
|
{
|
|
using MemoryStream decompressedStream = new MemoryStream(dfilesize);
|
|
using DeflateStream decompressor = new DeflateStream(inStream, CompressionMode.Decompress);
|
|
decompressor.CopyTo(decompressedStream);
|
|
byte[] decompressedBytes = decompressedStream.ToArray();
|
|
NativeArray<byte> vol3d = new NativeArray<byte>(dfilesize, Allocator.Persistent);
|
|
NativeArray<byte>.Copy(decompressedBytes, 0, vol3d, 0, dfilesize);
|
|
return vol3d;
|
|
}
|
|
else
|
|
{
|
|
//Debug.Log("UncompressedData");
|
|
NativeArray<byte> vol3d = new NativeArray<byte>(dfilesize, Allocator.Persistent);
|
|
byte[] uncompressedBytes = new byte[dfilesize];
|
|
inStream.Read(uncompressedBytes, 0, dfilesize);
|
|
NativeArray<byte>.Copy(uncompressedBytes, 0, vol3d, 0, dfilesize);
|
|
return vol3d;
|
|
}
|
|
}
|
|
|
|
static byte[] HeaderToBytes(Header header)
|
|
{
|
|
IntPtr headerPtr = IntPtr.Zero;
|
|
byte[] headerBytes = new byte[headerSize];
|
|
try
|
|
{
|
|
headerPtr = Marshal.AllocHGlobal(headerSize);
|
|
Marshal.StructureToPtr(header, headerPtr, false);
|
|
Marshal.Copy(headerPtr, headerBytes, 0, headerBytes.Length);
|
|
}
|
|
finally
|
|
{
|
|
if (headerPtr != IntPtr.Zero)
|
|
{
|
|
Marshal.FreeHGlobal(headerPtr);
|
|
}
|
|
}
|
|
return headerBytes;
|
|
}
|
|
}
|
|
} |