Files
AR-Menu/Library/PackageCache/com.unity.cloud.draco@9c0decae37ec/Runtime/Scripts/Encoder/DracoEncoder.cs
2025-11-30 08:35:03 +02:00

574 lines
25 KiB
C#

// SPDX-FileCopyrightText: 2023 Unity Technologies and the Draco for Unity authors
// SPDX-License-Identifier: Apache-2.0
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Profiling;
using UnityEngine.Rendering;
namespace Draco.Encode
{
/// <summary>
/// Provides Draco encoding capabilities.
/// </summary>
public static class DracoEncoder
{
struct AttributeData
{
public int stream;
public int offset;
}
/// <summary>
/// Applies Draco compression to a given mesh and returns the encoded result (one per sub-mesh)
/// </summary>
/// <param name="unityMesh">Input mesh</param>
/// <returns>Encoded data (one per sub-mesh)</returns>
public static async Task<EncodeResult[]> EncodeMesh(Mesh unityMesh)
{
return await EncodeMesh(unityMesh, QuantizationSettings.Default, SpeedSettings.Default);
}
/// <inheritdoc cref="EncodeMesh(UnityEngine.Mesh)"/>
/// <param name="quantization">Quantization settings</param>
/// <param name="speed">Encode/decode speed settings</param>
// ReSharper disable once MemberCanBePrivate.Global
public static async Task<EncodeResult[]> EncodeMesh(
Mesh unityMesh,
QuantizationSettings quantization,
SpeedSettings speed
)
{
DracoDecoder.CertifySupportedPlatform(
#if UNITY_EDITOR
false
#endif
);
#if !UNITY_EDITOR
if (!unityMesh.isReadable)
{
Debug.LogError("Mesh is not readable");
return null;
}
#endif
var dataArray = Mesh.AcquireReadOnlyMeshData(unityMesh);
var data = dataArray[0];
var result = await EncodeMesh(unityMesh, data, quantization, speed);
dataArray.Dispose();
return result;
}
/// <summary>
/// Applies Draco compression to a given mesh/meshData and returns the encoded result (one per sub-mesh).
/// The user is responsible for
/// <see cref="UnityEngine.Mesh.AcquireReadOnlyMeshData(Mesh)">acquiring the readable MeshData</see>
/// and disposing it.
/// </summary>
/// <param name="mesh">Input mesh</param>
/// <param name="meshData">Previously acquired readable mesh data</param>
/// <returns>Encoded data (one per sub-mesh)</returns>
// ReSharper disable once MemberCanBePrivate.Global
public static async Task<EncodeResult[]> EncodeMesh(Mesh mesh, Mesh.MeshData meshData)
{
return await EncodeMesh(mesh, meshData, QuantizationSettings.Default, SpeedSettings.Default);
}
/// <inheritdoc cref="EncodeMesh(Mesh,Mesh.MeshData)"/>
/// <param name="quantization">Quantization settings</param>
/// <param name="speed">Encode/decode speed settings</param>
// ReSharper disable once MemberCanBePrivate.Global
public static async Task<EncodeResult[]> EncodeMesh(
Mesh mesh,
Mesh.MeshData meshData,
QuantizationSettings quantization,
SpeedSettings speed
)
{
#if !UNITY_EDITOR
if (!mesh.isReadable)
{
Debug.LogError("Mesh is not readable");
return null;
}
#endif
if (!quantization.IsValid)
{
// Could be ill-configured or accidental use of QuantizationSettings's implicit default constructor.
Debug.LogError($"Invalid {quantization}! Falling back to Default");
quantization = QuantizationSettings.Default;
}
Profiler.BeginSample("EncodeMesh.Prepare");
var result = new EncodeResult[meshData.subMeshCount];
var vertexAttributes = mesh.GetVertexAttributes();
var strides = new int[DracoNative.maxStreamCount];
var attributeDataDict = new Dictionary<VertexAttribute, AttributeData>();
foreach (var attribute in vertexAttributes)
{
var attributeData = new AttributeData { offset = strides[attribute.stream], stream = attribute.stream };
var size = attribute.dimension * GetAttributeSize(attribute.format);
strides[attribute.stream] += size;
attributeDataDict[attribute.attribute] = attributeData;
}
var streamCount = 1;
for (var stream = 0; stream < strides.Length; stream++)
{
var stride = strides[stream];
if (stride <= 0) continue;
streamCount = stream + 1;
}
var vData = new NativeArray<byte>[streamCount];
for (var stream = 0; stream < streamCount; stream++)
{
vData[stream] = meshData.GetVertexData<byte>(stream);
}
var vDataPtr = GetReadOnlyPointers(streamCount, vData);
Profiler.EndSample(); // EncodeMesh.Prepare
for (var subMeshIndex = 0; subMeshIndex < mesh.subMeshCount; subMeshIndex++)
{
Profiler.BeginSample("EncodeMesh.SubMesh.Prepare");
var subMesh = mesh.GetSubMesh(subMeshIndex);
if (subMesh.topology != MeshTopology.Triangles && subMesh.topology != MeshTopology.Points)
{
Debug.LogError($"Mesh topology {subMesh.topology} is not supported");
return null;
}
var dracoEncoder = subMesh.topology == MeshTopology.Triangles
? dracoEncoderCreate(mesh.vertexCount)
: dracoEncoderCreatePointCloud(mesh.vertexCount);
var attributeIds = new Dictionary<VertexAttribute, (uint identifier, int dimensions)>();
foreach (var attributeTuple in attributeDataDict)
{
var attribute = attributeTuple.Key;
var attrData = attributeTuple.Value;
var format = mesh.GetVertexAttributeFormat(attribute);
var dimension = mesh.GetVertexAttributeDimension(attribute);
var stride = strides[attrData.stream];
var baseAddr = vDataPtr[attrData.stream] + attrData.offset;
var id = dracoEncoderSetAttribute(
dracoEncoder,
(int)GetAttributeType(attribute),
GetDataType(format),
dimension,
stride,
DracoNative.ConvertSpace(attribute),
baseAddr
);
attributeIds[attribute] = (id, dimension);
}
if (subMesh.topology == MeshTopology.Triangles)
{
var indices = mesh.GetIndices(subMeshIndex);
var indicesData = PinArray(indices, out var gcHandle);
dracoEncoderSetIndices(
dracoEncoder,
DataType.UInt32,
(uint)indices.Length,
true,
indicesData
);
UnsafeUtility.ReleaseGCObject(gcHandle);
}
// For both encoding and decoding (0 = slow and best compression; 10 = fast)
dracoEncoderSetCompressionSpeed(
dracoEncoder,
speed.encodingSpeed,
speed.decodingSpeed
);
dracoEncoderSetQuantizationBits(
dracoEncoder,
quantization.positionQuantization,
quantization.normalQuantization,
quantization.texCoordQuantization,
quantization.colorQuantization,
QuantizationSettings.genericQuantization
);
var encodeJob = new EncodeJob
{
dracoEncoder = dracoEncoder
};
Profiler.EndSample(); //EncodeMesh.SubMesh.Prepare
var jobHandle = encodeJob.Schedule();
while (!jobHandle.IsCompleted)
{
await Task.Yield();
}
jobHandle.Complete();
Profiler.BeginSample("EncodeMesh.SubMesh.Aftermath");
result[subMeshIndex] = new EncodeResult(
dracoEncoder,
dracoEncoderGetEncodedIndexCount(dracoEncoder),
dracoEncoderGetEncodedVertexCount(dracoEncoder),
attributeIds
);
Profiler.EndSample(); // EncodeMesh.SubMesh.Aftermath
}
Profiler.BeginSample("EncodeMesh.Aftermath");
for (var stream = 0; stream < streamCount; stream++)
{
vData[stream].Dispose();
}
Profiler.EndSample();
return result;
}
/// <summary>
/// Applies Draco compression to a given mesh and returns the encoded result (one per sub-mesh)
/// The quality and quantization parameters are calculated from the mesh's bounds, its worldScale and desired precision.
/// The quantization parameters help to find a balance between compressed size and quality / precision.
/// </summary>
/// <param name="unityMesh">Input mesh</param>
/// <param name="worldScale">Local-to-world scale this mesh is present in the scene</param>
/// <param name="precision">Desired minimum precision in world units</param>
/// <param name="encodingSpeed">Encoding speed level. 0 means slow and small. 10 is fastest.</param>
/// <param name="decodingSpeed">Decoding speed level. 0 means slow and small. 10 is fastest.</param>
/// <param name="normalQuantization">Normal quantization</param>
/// <param name="texCoordQuantization">Texture coordinate quantization</param>
/// <param name="colorQuantization">Color quantization</param>
/// <param name="genericQuantization">Generic quantization (e.g. blend weights and indices). unused at the moment</param>
/// <returns>Encoded data (one per sub-mesh)</returns>
/// <seealso cref="EncodeMesh(Mesh,QuantizationSettings,SpeedSettings)"/>
/// <seealso cref="QuantizationSettings.FromWorldSize"/>
[Obsolete("Use EncodeMesh(Mesh,QuantizationSettings,SpeedSettings) instead")]
public static async Task<EncodeResult[]> EncodeMesh(
Mesh unityMesh,
Vector3 worldScale,
float precision = .001f,
int encodingSpeed = 0,
int decodingSpeed = 4,
int normalQuantization = 10,
int texCoordQuantization = 12,
int colorQuantization = 8,
int genericQuantization = 12
)
{
return await EncodeMesh(
unityMesh,
QuantizationSettings.FromWorldSize(
unityMesh.bounds,
worldScale,
precision,
normalQuantization,
texCoordQuantization,
colorQuantization
),
new SpeedSettings(encodingSpeed, decodingSpeed)
);
}
/// <summary>
/// Applies Draco compression to a given mesh/meshData and returns the encoded result (one per sub-mesh)
/// The user is responsible for
/// <see cref="UnityEngine.Mesh.AcquireReadOnlyMeshData(Mesh)">acquiring the readable MeshData</see>
/// and disposing it.
/// The quality and quantization parameters are calculated from the mesh's bounds, its worldScale and desired precision.
/// The quantization parameters help to find a balance between compressed size and quality / precision.
/// </summary>
/// <param name="mesh">Input mesh</param>
/// <param name="meshData">Previously acquired readable mesh data</param>
/// <param name="worldScale">Local-to-world scale this mesh is present in the scene</param>
/// <param name="precision">Desired minimum precision in world units</param>
/// <param name="encodingSpeed">Encoding speed level. 0 means slow and small. 10 is fastest.</param>
/// <param name="decodingSpeed">Decoding speed level. 0 means slow and small. 10 is fastest.</param>
/// <param name="normalQuantization">Normal quantization</param>
/// <param name="texCoordQuantization">Texture coordinate quantization</param>
/// <param name="colorQuantization">Color quantization</param>
/// <param name="genericQuantization">Generic quantization (e.g. blend weights and indices). unused at the moment</param>
/// <returns>Encoded data (one per sub-mesh)</returns>
/// <seealso cref="EncodeMesh(Mesh,Mesh.MeshData,QuantizationSettings,SpeedSettings)"/>
/// <seealso cref="QuantizationSettings.FromWorldSize"/>
[Obsolete("Use EncodeMesh(Mesh,Mesh.MeshData,QuantizationSettings,SpeedSettings) instead")]
public static async Task<EncodeResult[]> EncodeMesh(
Mesh mesh,
Mesh.MeshData meshData,
Vector3 worldScale,
float precision = .001f,
int encodingSpeed = 0,
int decodingSpeed = 4,
int normalQuantization = 10,
int texCoordQuantization = 12,
int colorQuantization = 8,
int genericQuantization = 12
)
{
return await EncodeMesh(
mesh,
meshData,
QuantizationSettings.FromWorldSize(
mesh.bounds,
worldScale,
precision,
normalQuantization,
texCoordQuantization,
colorQuantization
),
new SpeedSettings(encodingSpeed, decodingSpeed)
);
}
/// <summary>
/// Applies Draco compression to a given mesh and returns the encoded result (one per sub-mesh)
/// The quantization parameters help to find a balance between encoded size and quality / precision.
/// </summary>
/// <param name="unityMesh">Input mesh</param>
/// <param name="encodingSpeed">Encoding speed level. 0 means slow and small. 10 is fastest.</param>
/// <param name="decodingSpeed">Decoding speed level. 0 means slow and small. 10 is fastest.</param>
/// <param name="positionQuantization">Vertex position quantization</param>
/// <param name="normalQuantization">Normal quantization</param>
/// <param name="texCoordQuantization">Texture coordinate quantization</param>
/// <param name="colorQuantization">Color quantization</param>
/// <param name="genericQuantization">Generic quantization (e.g. blend weights and indices). unused at the moment</param>
/// <returns>Encoded data (one per sub-mesh)</returns>
/// <seealso cref="EncodeMesh(Mesh,QuantizationSettings,SpeedSettings)"/>
[Obsolete("Use EncodeMesh(Mesh,QuantizationSettings,SpeedSettings) instead")]
public static async Task<EncodeResult[]> EncodeMesh(
Mesh unityMesh,
// ReSharper disable once MethodOverloadWithOptionalParameter
int encodingSpeed = 0,
int decodingSpeed = 4,
int positionQuantization = 14,
int normalQuantization = 10,
int texCoordQuantization = 12,
int colorQuantization = 8,
int genericQuantization = 12
)
{
return await EncodeMesh(
unityMesh,
new QuantizationSettings(
positionQuantization: positionQuantization,
normalQuantization: normalQuantization,
texCoordQuantization: texCoordQuantization,
colorQuantization: colorQuantization
),
new SpeedSettings(encodingSpeed, decodingSpeed));
}
/// <summary>
/// Applies Draco compression to a given mesh/meshData and returns the encoded result (one per sub-mesh)
/// The user is responsible for
/// <see cref="UnityEngine.Mesh.AcquireReadOnlyMeshData(Mesh)">acquiring the readable MeshData</see>
/// and disposing it.
/// The quantization parameters help to find a balance between encoded size and quality / precision.
/// </summary>
/// <param name="mesh">Input mesh</param>
/// <param name="meshData">Previously acquired readable mesh data</param>
/// <param name="encodingSpeed">Encoding speed level. 0 means slow and small. 10 is fastest.</param>
/// <param name="decodingSpeed">Decoding speed level. 0 means slow and small. 10 is fastest.</param>
/// <param name="positionQuantization">Vertex position quantization</param>
/// <param name="normalQuantization">Normal quantization</param>
/// <param name="texCoordQuantization">Texture coordinate quantization</param>
/// <param name="colorQuantization">Color quantization</param>
/// <param name="genericQuantization">Generic quantization (e.g. blend weights and indices). unused at the moment</param>
/// <returns>Encoded data (one per sub-mesh)</returns>
/// <seealso cref="EncodeMesh(Mesh,Mesh.MeshData,QuantizationSettings,SpeedSettings)"/>
// ReSharper disable once MemberCanBePrivate.Global
[Obsolete("Use EncodeMesh(Mesh,Mesh.MeshData,QuantizationSettings,SpeedSettings) instead")]
public static async Task<EncodeResult[]> EncodeMesh(
Mesh mesh,
Mesh.MeshData meshData,
// ReSharper disable once MethodOverloadWithOptionalParameter
int encodingSpeed = 0,
int decodingSpeed = 4,
int positionQuantization = 14,
int normalQuantization = 10,
int texCoordQuantization = 12,
int colorQuantization = 8,
int genericQuantization = 12
)
{
return await EncodeMesh(
mesh,
meshData,
new QuantizationSettings(
positionQuantization: positionQuantization,
normalQuantization: normalQuantization,
texCoordQuantization: texCoordQuantization,
colorQuantization: colorQuantization
),
new SpeedSettings(encodingSpeed, decodingSpeed));
}
static unsafe IntPtr PinArray(int[] indices, out ulong gcHandle)
{
return (IntPtr)UnsafeUtility.PinGCArrayAndGetDataAddress(indices, out gcHandle);
}
static unsafe IntPtr[] GetReadOnlyPointers(int count, NativeArray<byte>[] vData)
{
var result = new IntPtr[count];
for (var stream = 0; stream < count; stream++)
{
result[stream] = (IntPtr)vData[stream].GetUnsafeReadOnlyPtr();
}
return result;
}
static DataType GetDataType(VertexAttributeFormat format)
{
switch (format)
{
case VertexAttributeFormat.Float32:
case VertexAttributeFormat.Float16:
return DataType.Float32;
case VertexAttributeFormat.UNorm8:
case VertexAttributeFormat.UInt8:
return DataType.UInt8;
case VertexAttributeFormat.SNorm8:
case VertexAttributeFormat.SInt8:
return DataType.Int8;
case VertexAttributeFormat.UInt16:
case VertexAttributeFormat.UNorm16:
return DataType.UInt16;
case VertexAttributeFormat.SInt16:
case VertexAttributeFormat.SNorm16:
return DataType.Int16;
case VertexAttributeFormat.UInt32:
case VertexAttributeFormat.SInt32:
return DataType.Int32;
default:
throw new ArgumentOutOfRangeException(nameof(format), format, null);
}
}
static AttributeType GetAttributeType(VertexAttribute attribute)
{
switch (attribute)
{
case VertexAttribute.Position:
return AttributeType.Position;
case VertexAttribute.Normal:
return AttributeType.Normal;
case VertexAttribute.Color:
return AttributeType.Color;
case VertexAttribute.TexCoord0:
case VertexAttribute.TexCoord1:
case VertexAttribute.TexCoord2:
case VertexAttribute.TexCoord3:
case VertexAttribute.TexCoord4:
case VertexAttribute.TexCoord5:
case VertexAttribute.TexCoord6:
case VertexAttribute.TexCoord7:
return AttributeType.TextureCoordinate;
case VertexAttribute.Tangent:
case VertexAttribute.BlendWeight:
case VertexAttribute.BlendIndices:
return AttributeType.Generic;
default:
throw new ArgumentOutOfRangeException(nameof(attribute), attribute, null);
}
}
static unsafe int GetAttributeSize(VertexAttributeFormat format)
{
switch (format)
{
case VertexAttributeFormat.Float32:
return sizeof(float);
case VertexAttributeFormat.Float16:
return sizeof(half);
case VertexAttributeFormat.UNorm8:
return sizeof(byte);
case VertexAttributeFormat.SNorm8:
return sizeof(sbyte);
case VertexAttributeFormat.UNorm16:
return sizeof(ushort);
case VertexAttributeFormat.SNorm16:
return sizeof(short);
case VertexAttributeFormat.UInt8:
return sizeof(byte);
case VertexAttributeFormat.SInt8:
return sizeof(sbyte);
case VertexAttributeFormat.UInt16:
return sizeof(ushort);
case VertexAttributeFormat.SInt16:
return sizeof(short);
case VertexAttributeFormat.UInt32:
return sizeof(uint);
case VertexAttributeFormat.SInt32:
return sizeof(int);
default:
throw new ArgumentOutOfRangeException(nameof(format), format, null);
}
}
[DllImport(DracoNative.dracoUnityLib)]
static extern IntPtr dracoEncoderCreate(int vertexCount);
[DllImport(DracoNative.dracoUnityLib)]
static extern IntPtr dracoEncoderCreatePointCloud(int vertexCount);
[DllImport(DracoNative.dracoUnityLib)]
internal static extern void dracoEncoderRelease(IntPtr encoder);
[DllImport(DracoNative.dracoUnityLib)]
static extern void dracoEncoderSetCompressionSpeed(IntPtr encoder, int encodingSpeed, int decodingSpeed);
[DllImport(DracoNative.dracoUnityLib)]
static extern void dracoEncoderSetQuantizationBits(IntPtr encoder, int position, int normal, int uv, int color, int generic);
[DllImport(DracoNative.dracoUnityLib)]
internal static extern bool dracoEncoderEncode(IntPtr encoder, bool preserveTriangleOrder);
[DllImport(DracoNative.dracoUnityLib)]
static extern uint dracoEncoderGetEncodedVertexCount(IntPtr encoder);
[DllImport(DracoNative.dracoUnityLib)]
static extern uint dracoEncoderGetEncodedIndexCount(IntPtr encoder);
[DllImport(DracoNative.dracoUnityLib)]
internal static extern unsafe void dracoEncoderGetEncodeBuffer(IntPtr encoder, out void* data, out ulong size);
[DllImport(DracoNative.dracoUnityLib)]
static extern bool dracoEncoderSetIndices(
IntPtr encoder,
DataType indexComponentType,
uint indexCount,
bool flip,
IntPtr indices
);
[DllImport(DracoNative.dracoUnityLib)]
static extern uint dracoEncoderSetAttribute(
IntPtr encoder,
int attributeType,
DataType dracoDataType,
int componentCount,
int stride,
bool flip,
IntPtr data);
}
}