Files
2025-11-30 08:35:03 +02:00

500 lines
17 KiB
C#

// SPDX-FileCopyrightText: 2023 Unity Technologies and the glTFast authors
// SPDX-License-Identifier: Apache-2.0
using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using GLTFast.Logging;
using GLTFast.Schema;
using GLTFast.Vertex;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.Profiling;
using UnityEngine.Rendering;
using Mesh = UnityEngine.Mesh;
namespace GLTFast
{
class VertexBufferGenerator<TMainBuffer> :
VertexBufferGeneratorBase
where TMainBuffer : unmanaged
{
NativeArray<TMainBuffer> m_Data;
bool m_HasNormals;
bool m_HasTangents;
bool m_HasColors;
bool m_HasBones;
VertexBufferTexCoordsBase m_TexCoords;
VertexBufferColors m_Colors;
VertexBufferBones m_Bones;
AccessorBase[] m_PositionAccessors;
public override int VertexCount => VertexIntervals != null ? VertexIntervals[VertexIntervals.Length - 1] : 0;
public override int[] VertexIntervals { get; protected set; }
public override void GetVertexRange(int subMesh, out int baseVertex, out int vertexCount)
{
Assert.IsNotNull(VertexIntervals);
Assert.IsTrue(subMesh >= 0);
Assert.IsTrue(subMesh < VertexIntervals.Length);
baseVertex = VertexIntervals[subMesh];
vertexCount = VertexIntervals[subMesh + 1] - baseVertex;
}
public override bool TryGetBounds(int subMesh, out Bounds bounds)
{
Assert.IsNotNull(m_PositionAccessors);
var boundsOpt = m_PositionAccessors[subMesh].TryGetBounds();
if (boundsOpt.HasValue)
{
bounds = boundsOpt.Value;
return true;
}
m_GltfImport.Logger?.Error(LogCode.MeshBoundsMissing, m_Attributes[subMesh].POSITION.ToString());
bounds = default;
return false;
}
public VertexBufferGenerator(int primitiveCount, GltfImportBase gltfImport)
: base(primitiveCount, gltfImport)
{ }
public override void AddPrimitive(Attributes att)
{
m_Attributes[m_AttributeCount++] = att;
}
public override void Initialize()
{
Assert.AreEqual(m_Attributes.Length, m_AttributeCount);
var vertexCount = 0;
m_PositionAccessors = new AccessorBase[m_Attributes.Length];
VertexIntervals = new int[m_Attributes.Length + 1];
for (var i = 0; i < m_Attributes.Length; i++)
{
VertexIntervals[i] = vertexCount;
m_PositionAccessors[i] = ((IGltfBuffers)m_GltfImport).GetAccessor(m_Attributes[i].POSITION);
vertexCount += m_PositionAccessors[i].count;
}
VertexIntervals[m_Attributes.Length] = vertexCount;
}
public override async Task<bool> CreateVertexBuffer()
{
var jh = CreateVertexBufferHandle();
if (!jh.HasValue)
return false;
while (!jh.Value.IsCompleted)
{
await Task.Yield();
}
jh.Value.Complete();
return true;
}
unsafe JobHandle? CreateVertexBufferHandle()
{
Profiler.BeginSample("AllocateNativeArray");
m_Data = new NativeArray<TMainBuffer>(VertexCount, defaultAllocator);
var vDataPtr = (byte*)m_Data.GetUnsafeReadOnlyPtr();
Profiler.EndSample();
var jobCount = 0;
var firstAttributes = m_Attributes[0];
var uvSetCount = firstAttributes.GetTexCoordsCount();
if (uvSetCount > 0)
{
if (uvSetCount > 8)
{
// More than eight UV sets are not supported yet
m_GltfImport.Logger?.Warning(LogCode.UVLimit);
}
jobCount += uvSetCount * m_Attributes.Length;
m_TexCoords = uvSetCount switch
{
1 => new VertexBufferTexCoords<VTexCoord1>(uvSetCount, VertexCount, m_GltfImport.Logger),
2 => new VertexBufferTexCoords<VTexCoord2>(uvSetCount, VertexCount, m_GltfImport.Logger),
3 => new VertexBufferTexCoords<VTexCoord3>(uvSetCount, VertexCount, m_GltfImport.Logger),
4 => new VertexBufferTexCoords<VTexCoord4>(uvSetCount, VertexCount, m_GltfImport.Logger),
5 => new VertexBufferTexCoords<VTexCoord5>(uvSetCount, VertexCount, m_GltfImport.Logger),
6 => new VertexBufferTexCoords<VTexCoord6>(uvSetCount, VertexCount, m_GltfImport.Logger),
7 => new VertexBufferTexCoords<VTexCoord7>(uvSetCount, VertexCount, m_GltfImport.Logger),
_ => new VertexBufferTexCoords<VTexCoord8>(uvSetCount, VertexCount, m_GltfImport.Logger)
};
}
m_HasColors = firstAttributes.COLOR_0 >= 0;
if (m_HasColors)
{
jobCount += m_Attributes.Length;
m_Colors = new VertexBufferColors(VertexCount, m_GltfImport.Logger);
}
m_HasBones = firstAttributes.WEIGHTS_0 >= 0 && firstAttributes.JOINTS_0 >= 0;
if (m_HasBones)
{
jobCount += m_Attributes.Length;
m_Bones = new VertexBufferBones(VertexCount, m_GltfImport.Logger);
}
for (var i = 0; i < m_Attributes.Length; i++)
{
jobCount += 1; // Positions
var att = m_Attributes[i];
if (m_PositionAccessors[i].IsSparse && m_PositionAccessors[i].bufferView >= 0)
jobCount++;
if (att.NORMAL >= 0)
{
jobCount++;
m_HasNormals = true;
}
m_HasNormals |= calculateNormals;
if (att.TANGENT >= 0)
{
jobCount++;
m_HasTangents = true;
}
m_HasTangents |= calculateTangents;
}
var handles = new NativeArray<JobHandle>(jobCount, defaultAllocator);
var handleIndex = 0;
var outputByteStride = Marshal.SizeOf(typeof(TMainBuffer));
for (var i = 0; i < m_Attributes.Length; i++)
{
var att = m_Attributes[i];
if (!SchedulePositionsJobs(i, vDataPtr, outputByteStride, handles, ref handleIndex))
return null;
if (att.NORMAL >= 0
&& !ScheduleNormalsJobs(att, vDataPtr, outputByteStride, i, handles, ref handleIndex)
)
return null;
if (att.TANGENT >= 0
&& !ScheduleTangentsJobs(att, vDataPtr, outputByteStride, i, handles, ref handleIndex)
)
return null;
if (m_TexCoords != null)
{
handleIndex = ScheduleTexCoordJobs(att, uvSetCount, i, handles, handleIndex);
}
if (m_HasColors && !ScheduleColorsJobs(att, i, handles, ref handleIndex))
return null;
if (m_HasBones && !ScheduleVertexBonesJobs(att, i, handles, handleIndex))
return null;
}
var handle = jobCount > 1 ? JobHandle.CombineDependencies(handles) : handles[0];
handles.Dispose();
return handle;
}
unsafe bool SchedulePositionsJobs(int i, byte* vDataPtr, int outputByteStride, NativeArray<JobHandle> handles, ref int handleIndex)
{
JobHandle? h = null;
if (m_PositionAccessors[i].bufferView >= 0)
{
h = GetVector3Job(
m_GltfImport,
m_PositionAccessors[i],
(float3*)(vDataPtr + outputByteStride * VertexIntervals[i]),
outputByteStride,
m_PositionAccessors[i].normalized,
false // positional data never needs to be normalized
);
}
if (m_PositionAccessors[i].IsSparse)
{
m_GltfImport.GetAccessorSparseIndices(m_PositionAccessors[i].Sparse.Indices, out var posIndexData);
m_GltfImport.GetAccessorSparseValues(m_PositionAccessors[i].Sparse.Values, out var posValueData);
var sparseJobHandle = GetVector3SparseJob(
posIndexData,
posValueData,
m_PositionAccessors[i].Sparse.count,
m_PositionAccessors[i].Sparse.Indices.componentType,
m_PositionAccessors[i].componentType,
(float3*)(vDataPtr + outputByteStride * VertexIntervals[i]),
outputByteStride,
dependsOn: ref h,
m_PositionAccessors[i].normalized
);
if (sparseJobHandle.HasValue)
{
handles[handleIndex] = sparseJobHandle.Value;
handleIndex++;
}
else
{
Profiler.EndSample();
return false;
}
}
if (h.HasValue)
{
handles[handleIndex] = h.Value;
handleIndex++;
}
else
{
Profiler.EndSample();
return false;
}
return true;
}
unsafe bool ScheduleNormalsJobs(Attributes att, byte* vDataPtr, int outputByteStride, int i, NativeArray<JobHandle> handles, ref int handleIndex)
{
((IGltfBuffers)m_GltfImport).GetAccessorAndData(
att.NORMAL,
out var nrmAcc,
out var input,
out var inputByteStride
);
if (nrmAcc.IsSparse)
{
m_GltfImport.Logger?.Error(LogCode.SparseAccessor, "normals");
}
var h = GetVector3Job(
m_GltfImport,
nrmAcc,
(float3*)(vDataPtr + outputByteStride * VertexIntervals[i] + 12),
outputByteStride,
nrmAcc.normalized
//, normals need to be unit length
);
if (h.HasValue)
{
handles[handleIndex] = h.Value;
handleIndex++;
}
else
{
Profiler.EndSample();
return false;
}
return true;
}
unsafe bool ScheduleTangentsJobs(Attributes att, byte* vDataPtr, int outputByteStride, int i, NativeArray<JobHandle> handles, ref int handleIndex)
{
((IGltfBuffers)m_GltfImport).GetAccessorAndData(
att.TANGENT,
out var tanAcc,
out var input,
out var inputByteStride
);
if (tanAcc.IsSparse)
{
m_GltfImport.Logger?.Error(LogCode.SparseAccessor, "tangents");
}
var h = GetTangentsJob(
input,
tanAcc.count,
tanAcc.componentType,
inputByteStride,
(float4*)(vDataPtr + outputByteStride * VertexIntervals[i] + 24),
outputByteStride,
tanAcc.normalized
);
if (h.HasValue)
{
handles[handleIndex] = h.Value;
handleIndex++;
}
else
{
Profiler.EndSample();
return false;
}
return true;
}
int ScheduleTexCoordJobs(Attributes att, int uvSetCount, int i, NativeArray<JobHandle> handles, int handleIndex)
{
var uvSuccess = att.TryGetAllUVAccessors(out var uvAccessors, out _);
Assert.IsTrue(uvSuccess);
Assert.AreEqual(uvSetCount, uvAccessors.Length);
m_TexCoords.ScheduleVertexUVJobs(
VertexIntervals[i],
uvAccessors,
handles.GetSubArray(handleIndex, uvAccessors.Length),
m_GltfImport
);
handleIndex += uvAccessors.Length;
return handleIndex;
}
bool ScheduleColorsJobs(Attributes att, int i, NativeArray<JobHandle> handles, ref int handleIndex)
{
var success = m_Colors.ScheduleVertexColorJob(
att.COLOR_0,
VertexIntervals[i],
handles.GetSubArray(handleIndex, 1),
m_GltfImport
);
if (!success)
{
Profiler.EndSample();
return false;
}
handleIndex++;
return true;
}
bool ScheduleVertexBonesJobs(Attributes att, int i, NativeArray<JobHandle> handles, int handleIndex)
{
var h = m_Bones.ScheduleVertexBonesJob(
att.WEIGHTS_0,
att.JOINTS_0,
VertexIntervals[i],
m_GltfImport
);
if (h.HasValue)
{
handles[handleIndex] = h.Value;
}
else
{
Profiler.EndSample();
return false;
}
return true;
}
void CreateDescriptors()
{
int vadLen = 1;
if (m_HasNormals) vadLen++;
if (m_HasTangents) vadLen++;
if (m_TexCoords != null) vadLen += m_TexCoords.UVSetCount;
if (m_Colors != null) vadLen++;
if (m_Bones != null) vadLen += 2;
m_Descriptors = new VertexAttributeDescriptor[vadLen];
var vadCount = 0;
int stream = 0;
m_Descriptors[vadCount] = new VertexAttributeDescriptor(VertexAttribute.Position, VertexAttributeFormat.Float32, 3, stream);
vadCount++;
if (m_HasNormals)
{
m_Descriptors[vadCount] = new VertexAttributeDescriptor(VertexAttribute.Normal, VertexAttributeFormat.Float32, 3, stream);
vadCount++;
}
if (m_HasTangents)
{
m_Descriptors[vadCount] = new VertexAttributeDescriptor(VertexAttribute.Tangent, VertexAttributeFormat.Float32, 4, stream);
vadCount++;
}
stream++;
if (m_Colors != null)
{
m_Colors.AddDescriptors(m_Descriptors, vadCount, stream);
vadCount++;
stream++;
}
if (m_TexCoords != null)
{
m_TexCoords.AddDescriptors(m_Descriptors, ref vadCount, stream);
stream++;
}
if (m_Bones != null)
{
m_Bones.AddDescriptors(m_Descriptors, vadCount, stream);
// vadCount+=2;
// stream++;
}
}
public override void ApplyOnMesh(Mesh msh, MeshUpdateFlags flags = MeshGeneratorBase.defaultMeshUpdateFlags)
{
Profiler.BeginSample("ApplyOnMesh");
if (m_Descriptors == null)
{
CreateDescriptors();
}
Profiler.BeginSample("SetVertexBufferParams");
msh.SetVertexBufferParams(m_Data.Length, m_Descriptors);
Profiler.EndSample();
Profiler.BeginSample("SetVertexBufferData");
int stream = 0;
msh.SetVertexBufferData(m_Data, 0, 0, m_Data.Length, stream, flags);
stream++;
Profiler.EndSample();
if (m_Colors != null)
{
m_Colors.ApplyOnMesh(msh, stream, flags);
stream++;
}
if (m_TexCoords != null)
{
m_TexCoords.ApplyOnMesh(msh, stream, flags);
stream++;
}
if (m_Bones != null)
{
m_Bones.ApplyOnMesh(msh, stream, flags);
// stream++;
}
Profiler.EndSample();
}
protected override void Dispose(bool disposing)
{
if (m_Data.IsCreated)
{
m_Data.Dispose();
}
if (disposing)
{
m_Colors?.Dispose();
m_TexCoords?.Dispose();
m_Bones?.Dispose();
}
}
}
}