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 BlockSize = new Dictionary { { 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 BytesPerBlock = new Dictionary { { 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 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(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 texBacking = tex.GetPixelData(mip); //if (uncmpSliceBytes[mip] != texBacking.Length) Debug.LogError("Expected size: " + uncmpSliceBytes[mip] + " got: " + texBacking.Length); NativeArray.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 texBacking = tex.GetPixelData(mip); if (cmpSliceBytes[mip] != texBacking.Length) Debug.LogError("Cmp Expected size: " + cmpSliceBytes[mip] + " got: " + texBacking.Length); NativeArray vcData = volumeCompressed.GetPixelData(mip); if (vcData.Length / mipDepth != texBacking.Length) Debug.LogError("VC Expected size: " + vcData.Length + " got: " + texBacking.Length); NativeArray.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(NativeArray 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 texBacking = tex.GetPixelData(mip); //if (uncmpSliceBytes[mip] != texBacking.Length) Debug.LogError("Expected size: " + uncmpSliceBytes[mip] + " got: " + texBacking.Length); NativeArray.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 texBacking = tex.GetPixelData(math.min(mip, maxXyMip)); NativeArray vcData = tex3D.GetPixelData(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.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 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 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 texBacking = tex.GetPixelData(0); //if (uncmpSliceBytes[mip] != texBacking.Length) Debug.LogError("Expected size: " + uncmpSliceBytes[mip] + " got: " + texBacking.Length); NativeArray.Copy(paddedBytes, uncompressedMipPtr[mip] + slice * uncmpSliceLen[mip], texBacking, 0, uncmpSliceLen[mip]); texBacking.Dispose(); //if ((mip == mipLevels - 2) && slice == 0) //{ // Span bytes = stackalloc byte[pixelSize]; // int ptr = uncompressedMipPtr[mip] + slice * uncmpSliceLen[mip]; // for (int i = 0; i < pixelSize; i++) // { // bytes[i] = paddedBytes[ptr + i]; // } // ReadOnlySpan halfVec = MemoryMarshal.Cast(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 texBacking2 = tex.GetPixelData(0); //if (cmpSliceLen[mip] != texBacking.Length) Debug.LogError("Cmp Expected size: " + cmpSliceLen[mip] + " got: " + texBacking.Length); NativeArray vcData = tex3D.GetPixelData(mip); //if (vcData.Length / mipDepth != texBacking.Length) Debug.LogError("VC Expected size: " + vcData.Length + " got: " + texBacking.Length); NativeArray.Copy(texBacking2, 0, vcData, slice * cmpSliceLen[mip], cmpSliceLen[mip]); texBacking2.Dispose(); vcData.Dispose(); //Resources.UnloadAsset(tex); } } DestroyImmediate(tex); } finally { paddedBytes.Dispose(); } } NativeArray GetPaddedTexture(NativeArray 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 mipDim = stackalloc int3[numMips]; Span padMipDim = stackalloc int3[numMips]; Span mipPtr = stackalloc int[numMips]; Span 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 padTex = new NativeArray(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.Copy(rawImage, rowPtr, padTex, padRowPtr, rowCount); if (rowPad > 0) { if (xLargerThanBlock) { NativeArray.Copy(rawImage, rowPtr + rowCount - rowPad, padTex, padRowPtr + rowCount, rowPad); } else { for (int p = 0; p < rowPad; p++) { NativeArray.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.Copy(padTex, padColumnPtr - columnPad, padTex, padColumnPtr, columnPad); } else { int rowSize = padMipDim[mip].x * pixelSize; for (int p = 0; p < rowPad; p++) { NativeArray.Copy(padTex, padColumnPtr - rowSize, padTex, padColumnPtr + p * rowSize, columnPad); } } } } } return padTex; } string PrintBytesAsHalf3(ref NativeArray array, int index) { Span bytes = stackalloc byte[6]; for (int i = 0; i < 6; i++) { bytes[i] = array[index + i]; } ReadOnlySpan halfVec = MemoryMarshal.Cast(bytes); return string.Format("{0}, {1}, {2}", Mathf.HalfToFloat(halfVec[0]), Mathf.HalfToFloat(halfVec[1]), Mathf.HalfToFloat(halfVec[2])); } /// /// Manually pad mips of the texture to work around a bug in Unity's BC6H compressor not padding mip levels before compressing /// /// /// NativeArray containing the entire uncompressed image /// width, height, and depth of the image's lowest mip /// Number of mip levels /// number of elements in the nativeArray that represent a single pixel /// number of pixels in a block that we need to pad to be a multiple of, 4 for BC6H /// NativeArray GetPaddedTextureJobs(NativeArray 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(); int rawPtr = dimensions.x * dimensions.y * dimensions.z * pixelSize; int paddedPtr = rawPtr; NativeArray rowInfos = new NativeArray(numMips - 1, Allocator.Temp); NativeArray mipRowOffsets = new NativeArray(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 outImage = new NativeArray(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 : IJobParallelFor where T : struct { [NativeDisableContainerSafetyRestriction] public NativeArray paddedData; [ReadOnly] public NativeArray rawData; [ReadOnly] public NativeArray mipRowOffset; [ReadOnly] public NativeArray 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.Copy(rawData, mipRow * m.rowLength + m.rawPointer, paddedData, slicePtr, m.rowLength); NativeArray.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 FileBytes { get; set; } public JobHandle ScheduleI(int i, int i2); } [BurstCompile] struct RGB24Linear2SRGB : IJobParallelFor, IsRGB2Linear { [NativeDisableContainerSafetyRestriction] public NativeArray fileBytes; public NativeArray 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 colorSpan = stackalloc RGB24[1] { new RGB24((byte)color.x, (byte)color.y, (byte)color.z) }; //ReadOnlySpan 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 fileBytes; const float fixedToFloat = (1.0f / 255.0f); public void Execute(int i) { int idx = 6 * i; ReadOnlySpan rb = stackalloc byte[2] { fileBytes[idx], fileBytes[idx + 1] }; ReadOnlySpan gb = stackalloc byte[2] { fileBytes[idx + 2], fileBytes[idx + 3] }; ReadOnlySpan bb = stackalloc byte[2] { fileBytes[idx + 4], fileBytes[idx + 5] }; ReadOnlySpan ru = MemoryMarshal.Cast(rb); float3 color = new float3(fileBytes[idx], fileBytes[idx] + 1, fileBytes[idx + 2]); color *= fixedToFloat; color = Linear2sRGB(color); color *= 255.0f; //ReadOnlySpan colorSpan = stackalloc RGB24[1] { new RGB24((byte)color.x, (byte)color.y, (byte)color.z) }; //ReadOnlySpan 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[] data = new NativeArray[tex.mipmapCount]; int dataLength = 0; for (int mip = 0; mip < tex.mipmapCount; mip++) { data[mip] = tex.GetPixelData(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.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 ReadVol3DToNative(string path, out ImageInfo imageInfo) { path = Path.GetFullPath(path); using FileStream inStream = File.OpenRead(path); Span headerByte = stackalloc byte[headerSize]; inStream.Read(headerByte); ReadOnlySpan
header = MemoryMarshal.Cast(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 vol3d = new NativeArray(dfilesize, Allocator.Persistent); NativeArray.Copy(decompressedBytes, 0, vol3d, 0, dfilesize); return vol3d; } else { //Debug.Log("UncompressedData"); NativeArray vol3d = new NativeArray(dfilesize, Allocator.Persistent); byte[] uncompressedBytes = new byte[dfilesize]; inStream.Read(uncompressedBytes, 0, dfilesize); NativeArray.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; } } }