WuhuIslandTesting/Library/PackageCache/com.unity.render-pipelines.universal@8148.0.7-4/Editor/VolumetricAsset.cs
2025-01-07 02:06:59 +01:00

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;
}
}
}