Compare commits
2 Commits
1a91811621
...
e3a02437c3
| Author | SHA1 | Date | |
|---|---|---|---|
| e3a02437c3 | |||
| 5903ddda2b |
@@ -1,6 +1,7 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Engine;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
using System.Runtime.InteropServices;
|
||||
@@ -20,15 +21,15 @@ public class MeshNode : IDisposable
|
||||
}
|
||||
|
||||
public MeshNode? Parent
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public required IReadOnlyCollection<MeshNode> Children
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<MeshNode> Children
|
||||
{
|
||||
get; set;
|
||||
} = Array.Empty<MeshNode>();
|
||||
|
||||
~MeshNode()
|
||||
{
|
||||
Dispose(false);
|
||||
@@ -45,15 +46,36 @@ public class MeshNode : IDisposable
|
||||
child.Dispose();
|
||||
}
|
||||
|
||||
Parent = null;
|
||||
Children = Array.Empty<MeshNode>();
|
||||
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes one material partition within a unified vertex/index buffer.
|
||||
/// </summary>
|
||||
public struct MaterialPartInfo
|
||||
{
|
||||
/// <summary> The material slot index (from ufbx face_material). </summary>
|
||||
public int materialIndex;
|
||||
/// <summary> Byte offset into the unified index buffer. </summary>
|
||||
public int indexStart;
|
||||
/// <summary> Number of indices belonging to this part. </summary>
|
||||
public int indexCount;
|
||||
/// <summary> Byte offset into the unified vertex buffer. </summary>
|
||||
public int vertexStart;
|
||||
/// <summary> Number of unique vertices belonging to this part. </summary>
|
||||
public int vertexCount;
|
||||
}
|
||||
|
||||
public class GeometryMeshNode : MeshNode
|
||||
{
|
||||
private UnsafeList<Vertex> _vertices;
|
||||
private UnsafeList<uint> _indices;
|
||||
private UnsafeArray<MaterialPartInfo> _materialParts;
|
||||
|
||||
public UnsafeList<Vertex> Vertices
|
||||
{
|
||||
@@ -75,15 +97,21 @@ public class GeometryMeshNode : MeshNode
|
||||
}
|
||||
}
|
||||
|
||||
public int MaterialIndex
|
||||
public UnsafeArray<MaterialPartInfo> MaterialParts
|
||||
{
|
||||
get; set;
|
||||
get => _materialParts;
|
||||
set
|
||||
{
|
||||
_materialParts.Dispose();
|
||||
_materialParts = value;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
_vertices.Dispose();
|
||||
_indices.Dispose();
|
||||
_materialParts.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Engine.Utilities;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Ghost.Graphics.Utilities;
|
||||
using Ghost.MeshOptimizer;
|
||||
@@ -11,21 +12,32 @@ using Misaki.HighPerformance.LowLevel.Utilities;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Ghost.Editor.Core.Assets;
|
||||
|
||||
internal unsafe class MeshParsingWorkItem : IJob
|
||||
internal readonly unsafe struct MeshParsingWorkItem : IJob
|
||||
{
|
||||
private struct GeometryPart : IDisposable
|
||||
{
|
||||
public UnsafeList<Vertex> vertices;
|
||||
public UnsafeList<uint> indices;
|
||||
public int materialIndex;
|
||||
public bool missingNormals;
|
||||
public bool missingTangents;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
vertices.Dispose();
|
||||
indices.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly string _filePath;
|
||||
private readonly AllocationHandle _allocationHandle;
|
||||
private readonly MeshAssetSettings _settings;
|
||||
private readonly TaskCompletionSource<Result<MeshNode>> _taskCompletionSource;
|
||||
|
||||
public UnsafeList<Vertex> vertices;
|
||||
public UnsafeList<uint> indices;
|
||||
|
||||
public Task<Result<MeshNode>> Task => _taskCompletionSource.Task;
|
||||
public readonly Task<Result<MeshNode>> Task => _taskCompletionSource.Task;
|
||||
|
||||
public MeshParsingWorkItem(string filePath, AllocationHandle allocationHandle, MeshAssetSettings settings)
|
||||
{
|
||||
@@ -59,23 +71,64 @@ internal unsafe class MeshParsingWorkItem : IJob
|
||||
};
|
||||
}
|
||||
|
||||
private GeometryMeshNode ParseGeometry(ufbx_mesh* pMesh)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static float4x4 ToFloat4x4(ufbx_vec3 t, ufbx_quat q, ufbx_vec3 s)
|
||||
{
|
||||
var meshNode = new GeometryMeshNode
|
||||
return float4x4.TRS(
|
||||
new float3(t.x, t.y, t.z),
|
||||
new quaternion(q.x, q.y, q.z, q.w),
|
||||
new float3(s.x, s.y, s.z)
|
||||
);
|
||||
}
|
||||
|
||||
private MeshNode ParseHierarchy(ufbx_node* node)
|
||||
{
|
||||
var children = new List<MeshNode>();
|
||||
var meshNode = new MeshNode
|
||||
{
|
||||
Name = pMesh->name.ToString(),
|
||||
Children = Array.Empty<MeshNode>(),
|
||||
Name = node->name.ToString(),
|
||||
LocalTransform = ToFloat4x4(node->local_transform.translation, node->local_transform.rotation, node->local_transform.scale),
|
||||
Children = children
|
||||
};
|
||||
|
||||
if (pMesh->num_faces == 0)
|
||||
if (node->mesh != null)
|
||||
{
|
||||
return meshNode;
|
||||
var geoNode = ParseGeometry(node->mesh);
|
||||
if (geoNode != null)
|
||||
{
|
||||
children.Add(geoNode);
|
||||
}
|
||||
}
|
||||
|
||||
var missingNormals = false;
|
||||
var missingTangents = false;
|
||||
// TODO: Handle lights, cameras, and other node types.
|
||||
|
||||
using var flatVertices = new UnsafeList<Vertex>(1024, AllocationHandle.FreeList);
|
||||
for (var i = 0u; i < node->children.count; i++)
|
||||
{
|
||||
children.Add(ParseHierarchy(node->children.data[i]));
|
||||
}
|
||||
|
||||
return meshNode;
|
||||
}
|
||||
|
||||
private GeometryMeshNode? ParseGeometry(ufbx_mesh* pMesh)
|
||||
{
|
||||
if (pMesh->num_faces == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var numMaterials = pMesh->materials.count > 0 ? (int)pMesh->materials.count : 1;
|
||||
|
||||
// Bucket faces by material
|
||||
|
||||
using var materialBuckets = new UnsafeArray<UnsafeList<Vertex>>(numMaterials, AllocationHandle.FreeList);
|
||||
using var missingNormalsBucket = new UnsafeArray<bool>(numMaterials, AllocationHandle.FreeList);
|
||||
using var missingTangentsBucket = new UnsafeArray<bool>(numMaterials, AllocationHandle.FreeList);
|
||||
|
||||
for (var i = 0; i < numMaterials; i++)
|
||||
{
|
||||
materialBuckets[i] = new UnsafeList<Vertex>(1024, AllocationHandle.FreeList);
|
||||
}
|
||||
|
||||
var maxScratchIndices = (int)(pMesh->max_face_triangles * 3u);
|
||||
|
||||
@@ -84,9 +137,10 @@ internal unsafe class MeshParsingWorkItem : IJob
|
||||
for (var j = 0u; j < pMesh->num_faces; j++)
|
||||
{
|
||||
var face = pMesh->faces.data[j];
|
||||
var materialIdx = pMesh->face_material.count > j ? pMesh->face_material.data[j] : 0;
|
||||
|
||||
var numTris = UfbxApi.TriangulateFace(triIndicesArray.AsSpan(0, maxScratchIndices), pMesh, face);
|
||||
|
||||
|
||||
var totalIndices = numTris * 3;
|
||||
for (var k = 0; k < totalIndices; k++)
|
||||
{
|
||||
@@ -123,79 +177,151 @@ internal unsafe class MeshParsingWorkItem : IJob
|
||||
vertex.tangent = ComputeTangent(t, n, b);
|
||||
}
|
||||
|
||||
var newIndex = (uint)flatVertices.Count;
|
||||
materialBuckets[materialIdx].Add(vertex);
|
||||
|
||||
flatVertices.Add(vertex);
|
||||
|
||||
if (!missingNormals)
|
||||
if (!missingNormalsBucket[materialIdx])
|
||||
{
|
||||
missingNormals = normIdx == uint.MaxValue;
|
||||
missingNormalsBucket[materialIdx] = normIdx == uint.MaxValue;
|
||||
}
|
||||
|
||||
if (!missingTangents)
|
||||
if (!missingTangentsBucket[materialIdx])
|
||||
{
|
||||
missingTangents = tanIdx == uint.MaxValue || btanIdx == uint.MaxValue;
|
||||
missingTangentsBucket[materialIdx] = tanIdx == uint.MaxValue || btanIdx == uint.MaxValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var numIndices = (uint)flatVertices.Count;
|
||||
// Per-material weld + optimize, collect intermediate results
|
||||
|
||||
using var weldedIndices = new UnsafeArray<uint>((int)numIndices, AllocationHandle.FreeList);
|
||||
using var cachedIndices = new UnsafeArray<uint>((int)numIndices, AllocationHandle.FreeList);
|
||||
using var partResults = new UnsafeList<GeometryPart>(numMaterials, AllocationHandle.FreeList);
|
||||
|
||||
var stream = new ufbx_vertex_stream
|
||||
for (var m = 0; m < numMaterials; m++)
|
||||
{
|
||||
data = flatVertices.GetUnsafePtr(),
|
||||
vertex_count = numIndices,
|
||||
vertex_size = (nuint)sizeof(Vertex)
|
||||
ref var flatVertices = ref materialBuckets[m];
|
||||
if (flatVertices.Count == 0)
|
||||
{
|
||||
flatVertices.Dispose();
|
||||
continue;
|
||||
}
|
||||
|
||||
var numIndices = (uint)flatVertices.Count;
|
||||
|
||||
using var weldedIndices = new UnsafeArray<uint>((int)numIndices, AllocationHandle.FreeList);
|
||||
using var cachedIndices = new UnsafeArray<uint>((int)numIndices, AllocationHandle.FreeList);
|
||||
|
||||
var stream = new ufbx_vertex_stream
|
||||
{
|
||||
data = flatVertices.GetUnsafePtr(),
|
||||
vertex_count = numIndices,
|
||||
vertex_size = (nuint)sizeof(Vertex)
|
||||
};
|
||||
|
||||
var error = new ufbx_error();
|
||||
var numUniqueVertices = UfbxApi.GenerateIndices([stream], weldedIndices, null, &error);
|
||||
if (numUniqueVertices == 0 && error.type != ufbx_error_type.UFBX_ERROR_NONE)
|
||||
{
|
||||
flatVertices.Dispose();
|
||||
continue;
|
||||
}
|
||||
|
||||
MeshOptApi.OptimizeVertexCache((uint*)cachedIndices.GetUnsafePtr(), (uint*)weldedIndices.GetUnsafePtr(), numIndices, numUniqueVertices);
|
||||
|
||||
// Allocate temporary per-part buffers (will be merged then disposed)
|
||||
var partVertices = new UnsafeList<Vertex>((int)numUniqueVertices, AllocationHandle.FreeList);
|
||||
var partIndices = new UnsafeList<uint>((int)numIndices, AllocationHandle.FreeList);
|
||||
|
||||
var finalVertexCount = MeshOptApi.OptimizeVertexFetch(partVertices.GetUnsafePtr(), (uint*)cachedIndices.GetUnsafePtr(), numIndices, flatVertices.GetUnsafePtr(), numIndices, (nuint)sizeof(Vertex));
|
||||
|
||||
partVertices.UnsafeSetCount((int)finalVertexCount);
|
||||
|
||||
MemoryUtility.MemCpy(partIndices.GetUnsafePtr(), cachedIndices.GetUnsafePtr(), numIndices * sizeof(uint));
|
||||
partIndices.UnsafeSetCount((int)numIndices);
|
||||
|
||||
var part = new GeometryPart
|
||||
{
|
||||
vertices = partVertices,
|
||||
indices = partIndices,
|
||||
materialIndex = m,
|
||||
missingNormals = missingNormalsBucket[m],
|
||||
missingTangents = missingTangentsBucket[m]
|
||||
};
|
||||
|
||||
partResults.Add(part);
|
||||
flatVertices.Dispose();
|
||||
}
|
||||
|
||||
if (partResults.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Merge all material parts into one unified vertex/index buffer
|
||||
|
||||
var totalVertexCount = 0;
|
||||
var totalIndexCount = 0;
|
||||
for (var i = 0; i < partResults.Count; i++)
|
||||
{
|
||||
totalVertexCount += partResults[i].vertices.Count;
|
||||
totalIndexCount += partResults[i].indices.Count;
|
||||
}
|
||||
|
||||
var mergedVertices = new UnsafeList<Vertex>(totalVertexCount, _allocationHandle);
|
||||
var mergedIndices = new UnsafeList<uint>(totalIndexCount, _allocationHandle);
|
||||
var materialParts = new UnsafeArray<MaterialPartInfo>(partResults.Count, _allocationHandle);
|
||||
|
||||
var vertexOffset = 0;
|
||||
var indexOffset = 0;
|
||||
|
||||
for (var i = 0; i < partResults.Count; i++)
|
||||
{
|
||||
ref var part = ref partResults[i];
|
||||
|
||||
// Compute normals/tangents per-part before merge (requires local indices)
|
||||
if (_settings.NormalDataSource == VertexDataSource.Computed || (_settings.NormalDataSource == VertexDataSource.ComputedIfMissing && part.missingNormals))
|
||||
{
|
||||
MeshBuilder.ComputeNormal(part.vertices, part.indices);
|
||||
}
|
||||
|
||||
if (_settings.TangentDataSource == VertexDataSource.Computed || (_settings.TangentDataSource == VertexDataSource.ComputedIfMissing && part.missingTangents))
|
||||
{
|
||||
MeshBuilder.ComputeTangents(part.vertices, part.indices);
|
||||
}
|
||||
|
||||
materialParts[i] = new MaterialPartInfo
|
||||
{
|
||||
materialIndex = part.materialIndex,
|
||||
vertexStart = vertexOffset,
|
||||
vertexCount = part.vertices.Count,
|
||||
indexStart = indexOffset,
|
||||
indexCount = part.indices.Count,
|
||||
};
|
||||
|
||||
mergedVertices.AddRange(part.vertices.AsSpan());
|
||||
|
||||
// Rebase indices to global vertex space
|
||||
for (var j = 0; j < part.indices.Count; j++)
|
||||
{
|
||||
mergedIndices.Add(part.indices[j] + (uint)vertexOffset);
|
||||
}
|
||||
|
||||
vertexOffset += part.vertices.Count;
|
||||
indexOffset += part.indices.Count;
|
||||
|
||||
part.Dispose();
|
||||
}
|
||||
|
||||
return new GeometryMeshNode
|
||||
{
|
||||
Name = pMesh->name.ToString(),
|
||||
LocalTransform = float4x4.identity,
|
||||
Vertices = mergedVertices,
|
||||
Indices = mergedIndices,
|
||||
MaterialParts = materialParts,
|
||||
};
|
||||
|
||||
var error = new ufbx_error();
|
||||
var numUniqueVertices = UfbxApi.GenerateIndices([stream], weldedIndices, null, &error);
|
||||
if (numUniqueVertices == 0 && error.type != ufbx_error_type.UFBX_ERROR_NONE)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MeshOptApi.OptimizeVertexCache((uint*)cachedIndices.GetUnsafePtr(), (uint*)weldedIndices.GetUnsafePtr(), numIndices, numUniqueVertices);
|
||||
|
||||
vertices = new UnsafeList<Vertex>((int)numUniqueVertices, _allocationHandle);
|
||||
indices = new UnsafeList<uint>((int)numIndices, _allocationHandle);
|
||||
|
||||
var finalVertexCount = MeshOptApi.OptimizeVertexFetch(vertices.GetUnsafePtr(), (uint*)cachedIndices.GetUnsafePtr(), numIndices, flatVertices.GetUnsafePtr(), numIndices, (nuint)sizeof(Vertex));
|
||||
|
||||
vertices.UnsafeSetCount((int)finalVertexCount);
|
||||
|
||||
MemoryUtility.MemCpy(indices.GetUnsafePtr(), cachedIndices.GetUnsafePtr(), numIndices * sizeof(uint));
|
||||
indices.UnsafeSetCount((int)numIndices);
|
||||
|
||||
if (_settings.NormalDataSource == VertexDataSource.Computed || (_settings.NormalDataSource == VertexDataSource.ComputedIfMissing && missingNormals))
|
||||
{
|
||||
MeshBuilder.ComputeNormal(vertices, indices);
|
||||
}
|
||||
|
||||
if (_settings.TangentDataSource == VertexDataSource.Computed || (_settings.TangentDataSource == VertexDataSource.ComputedIfMissing && missingTangents))
|
||||
{
|
||||
MeshBuilder.ComputeTangents(vertices, indices);
|
||||
}
|
||||
}
|
||||
|
||||
public void Execute(ref readonly JobExecutionContext context)
|
||||
{
|
||||
if (!File.Exists(_filePath))
|
||||
{
|
||||
_taskCompletionSource.SetResult(Result.Failure("Invalid file path."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Path.GetExtension(_filePath).Equals(".obj", StringComparison.OrdinalIgnoreCase)
|
||||
&& !Path.GetExtension(_filePath).Equals(".fbx", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_taskCompletionSource.SetResult(Result.Failure("Unsupported file format. Only .obj and .fbx are supported."));
|
||||
return;
|
||||
}
|
||||
|
||||
var error = new ufbx_error();
|
||||
var load_Opts = new ufbx_load_opts
|
||||
{
|
||||
@@ -231,68 +357,10 @@ internal unsafe class MeshParsingWorkItem : IJob
|
||||
return;
|
||||
}
|
||||
|
||||
using var flatVertices = new UnsafeList<Vertex>(1024, AllocationHandle.FreeList);
|
||||
var rootNode = ParseHierarchy(scene.Get()->root_node);
|
||||
rootNode.Name = Path.GetFileNameWithoutExtension(_filePath);
|
||||
|
||||
var missingNormals = false;
|
||||
var missingTangents = false;
|
||||
|
||||
for (var i = 0u; i < scene.Get()->nodes.count; i++)
|
||||
{
|
||||
var data = scene.Get()->nodes.data;
|
||||
var node = scene.Get()->nodes.data[i];
|
||||
if (node->is_root)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (node->mesh != null)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var numIndices = (uint)flatVertices.Count;
|
||||
|
||||
using var weldedIndices = new UnsafeArray<uint>((int)numIndices, AllocationHandle.FreeList);
|
||||
using var cachedIndices = new UnsafeArray<uint>((int)numIndices, AllocationHandle.FreeList);
|
||||
|
||||
var stream = new ufbx_vertex_stream
|
||||
{
|
||||
data = flatVertices.GetUnsafePtr(),
|
||||
vertex_count = numIndices,
|
||||
vertex_size = (nuint)sizeof(Vertex)
|
||||
};
|
||||
|
||||
var numUniqueVertices = UfbxApi.GenerateIndices([stream], weldedIndices, null, &error);
|
||||
if (numUniqueVertices == 0 && error.type != ufbx_error_type.UFBX_ERROR_NONE)
|
||||
{
|
||||
_taskCompletionSource.SetResult(Result.Failure($"Welding failed: {error.description}"));
|
||||
return;
|
||||
}
|
||||
|
||||
MeshOptApi.OptimizeVertexCache((uint*)cachedIndices.GetUnsafePtr(), (uint*)weldedIndices.GetUnsafePtr(), numIndices, numUniqueVertices);
|
||||
|
||||
vertices = new UnsafeList<Vertex>((int)numUniqueVertices, _allocationHandle);
|
||||
indices = new UnsafeList<uint>((int)numIndices, _allocationHandle);
|
||||
|
||||
var finalVertexCount = MeshOptApi.OptimizeVertexFetch(vertices.GetUnsafePtr(), (uint*)cachedIndices.GetUnsafePtr(), numIndices, flatVertices.GetUnsafePtr(), numIndices, (nuint)sizeof(Vertex));
|
||||
|
||||
vertices.UnsafeSetCount((int)finalVertexCount);
|
||||
|
||||
MemoryUtility.MemCpy(indices.GetUnsafePtr(), cachedIndices.GetUnsafePtr(), numIndices * sizeof(uint));
|
||||
indices.UnsafeSetCount((int)numIndices);
|
||||
|
||||
if (_settings.NormalDataSource == VertexDataSource.Computed || (_settings.NormalDataSource == VertexDataSource.ComputedIfMissing && missingNormals))
|
||||
{
|
||||
MeshBuilder.ComputeNormal(vertices, indices);
|
||||
}
|
||||
|
||||
if (_settings.TangentDataSource == VertexDataSource.Computed || (_settings.TangentDataSource == VertexDataSource.ComputedIfMissing && missingTangents))
|
||||
{
|
||||
MeshBuilder.ComputeTangents(vertices, indices);
|
||||
}
|
||||
|
||||
_taskCompletionSource.SetResult(Result.Success());
|
||||
_taskCompletionSource.SetResult(Result.Success(rootNode));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -695,67 +695,17 @@ public static unsafe partial class MeshProcessor
|
||||
return finalClusterCount;
|
||||
}
|
||||
|
||||
public static void BuildMeshlets(MeshletMeshData* pMeshletData, ReadOnlyUnsafeCollection<Vertex> vertices, ReadOnlyUnsafeCollection<uint> indices)
|
||||
private struct MeshletContext
|
||||
{
|
||||
Logger.DebugAssert(pMeshletData->meshletCount > 0, "Mesh must have vertices to build meshlets.");
|
||||
|
||||
var config = new ClodConfig
|
||||
{
|
||||
maxVertices = 64,
|
||||
minTriangles = 32,
|
||||
maxTriangles = 124,
|
||||
|
||||
partitionSpatial = true,
|
||||
partitionSize = 16,
|
||||
|
||||
clusterSpatial = false,
|
||||
clusterSplitFactor = 2.0f,
|
||||
|
||||
optimizeClusters = true,
|
||||
optimizeClustersLevel = 1,
|
||||
|
||||
simplifyRatio = 0.5f,
|
||||
simplifyThreshold = 0.85f,
|
||||
simplifyErrorMergePrevious = 1.0f,
|
||||
simplifyErrorFactorSloppy = 2.0f,
|
||||
simplifyPermissive = true,
|
||||
simplifyFallbackPermissive = false,
|
||||
simplifyFallbackSloppy = true,
|
||||
};
|
||||
|
||||
var clodMesh = new ClodMesh
|
||||
{
|
||||
vertexPositions = (float*)Unsafe.AsPointer(in vertices[0].position),
|
||||
vertexCount = (nuint)vertices.Count,
|
||||
vertexPositionsStride = (nuint)sizeof(Vertex),
|
||||
vertexAttributes = (float*)Unsafe.AsPointer(in vertices[0].normal),
|
||||
vertexAttributesStride = (nuint)sizeof(Vertex),
|
||||
indices = (uint*)indices.GetUnsafePtr(),
|
||||
indexCount = (nuint)indices.Count,
|
||||
attributeProtectMask = 0, // TODO: We need to protect UVs and other vertex attributes to ensure they are not altered during simplification.
|
||||
};
|
||||
|
||||
Build(in config, in clodMesh, pMeshletData, MeshletOutputCallback);
|
||||
|
||||
pMeshletData->meshletCount = pMeshletData->meshlets.IsCreated ? pMeshletData->meshlets.Count : 0;
|
||||
|
||||
if (pMeshletData->groups.IsCreated && pMeshletData->groups.Count > 0)
|
||||
{
|
||||
var maxLodLevel = 0u;
|
||||
for (var i = 0; i < pMeshletData->groups.Count; i++)
|
||||
{
|
||||
maxLodLevel = Math.Max(maxLodLevel, pMeshletData->groups[i].lodLevel);
|
||||
}
|
||||
|
||||
pMeshletData->lodLevelCount = (int)maxLodLevel + 1;
|
||||
}
|
||||
|
||||
pMeshletData->materialSlotCount = 1;
|
||||
public MeshletMeshData* data;
|
||||
public int materialIndex;
|
||||
}
|
||||
|
||||
private static int MeshletOutputCallback(void* context, ClodGroup group, ReadOnlyUnsafeCollection<ClodCluster> clusters)
|
||||
private static int MeshletOutputCallback(void* contextPtr, ClodGroup group, ReadOnlyUnsafeCollection<ClodCluster> clusters)
|
||||
{
|
||||
var pMeshletData = (MeshletMeshData*)context;
|
||||
var context = (MeshletContext*)contextPtr;
|
||||
var pMeshletData = context->data;
|
||||
var materialIndex = context->materialIndex;
|
||||
|
||||
// Ensure lists are initialized
|
||||
if (!pMeshletData->groups.IsCreated) pMeshletData->groups = new UnsafeList<MeshletGroup>(16, AllocationHandle.Persistent);
|
||||
@@ -790,7 +740,7 @@ public static unsafe partial class MeshProcessor
|
||||
groupIndex = (uint)pMeshletData->groups.Count - 1,
|
||||
clusterError = cluster.bounds.error,
|
||||
parentError = group.simplified.error,
|
||||
localMaterialIndex = 0, // TODO: support multiple materials
|
||||
localMaterialIndex = (byte)materialIndex,
|
||||
lodLevel = (byte)group.depth,
|
||||
};
|
||||
pMeshletData->meshlets.Add(meshlet);
|
||||
@@ -815,6 +765,91 @@ public static unsafe partial class MeshProcessor
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds meshlets for a unified multi-material mesh.
|
||||
/// Each <see cref="MaterialPartInfo"/> describes a material partition's index range within the unified buffer.
|
||||
/// Meshlets are built per-part and tagged with the corresponding <c>localMaterialIndex</c>.
|
||||
/// </summary>
|
||||
public static void BuildMeshlets(MeshletMeshData* pMeshletData, ReadOnlyUnsafeCollection<Vertex> vertices, ReadOnlyUnsafeCollection<uint> indices, ReadOnlySpan<MaterialPartInfo> parts)
|
||||
{
|
||||
Logger.DebugAssert(pMeshletData->meshletCount == 0, "Meshlet data is not empty.");
|
||||
Logger.DebugAssert(vertices.Count > 0, "Mesh must have vertices to build meshlets.");
|
||||
Logger.DebugAssert(indices.Count > 0, "Mesh must have indices to build meshlets.");
|
||||
Logger.DebugAssert(parts.Length > 0, "Must have at least one material part.");
|
||||
|
||||
var config = new ClodConfig
|
||||
{
|
||||
maxVertices = 64,
|
||||
minTriangles = 32,
|
||||
maxTriangles = 124,
|
||||
|
||||
partitionSpatial = true,
|
||||
partitionSize = 16,
|
||||
|
||||
clusterSpatial = false,
|
||||
clusterSplitFactor = 2.0f,
|
||||
|
||||
optimizeClusters = true,
|
||||
optimizeClustersLevel = 1,
|
||||
|
||||
simplifyRatio = 0.5f,
|
||||
simplifyThreshold = 0.85f,
|
||||
simplifyErrorMergePrevious = 1.0f,
|
||||
simplifyErrorFactorSloppy = 2.0f,
|
||||
simplifyPermissive = true,
|
||||
simplifyFallbackPermissive = false,
|
||||
simplifyFallbackSloppy = true,
|
||||
};
|
||||
|
||||
for (var i = 0; i < parts.Length; i++)
|
||||
{
|
||||
ref readonly var part = ref parts[i];
|
||||
|
||||
// Each part references a slice of the global index buffer,
|
||||
// but vertex positions are the full unified buffer so global indices remain valid.
|
||||
var clodMesh = new ClodMesh
|
||||
{
|
||||
vertexPositions = (float*)Unsafe.AsPointer(in vertices[0].position),
|
||||
vertexCount = (nuint)vertices.Count,
|
||||
vertexPositionsStride = (nuint)sizeof(Vertex),
|
||||
vertexAttributes = (float*)Unsafe.AsPointer(in vertices[0].normal),
|
||||
vertexAttributesStride = (nuint)sizeof(Vertex),
|
||||
indices = (uint*)indices.GetUnsafePtr() + part.indexStart,
|
||||
indexCount = (nuint)part.indexCount,
|
||||
attributeProtectMask = 0, // TODO: Protect UVs at material boundaries.
|
||||
};
|
||||
|
||||
var context = new MeshletContext
|
||||
{
|
||||
data = pMeshletData,
|
||||
materialIndex = part.materialIndex
|
||||
};
|
||||
|
||||
Build(in config, in clodMesh, &context, MeshletOutputCallback);
|
||||
}
|
||||
|
||||
pMeshletData->meshletCount = pMeshletData->meshlets.IsCreated ? pMeshletData->meshlets.Count : 0;
|
||||
|
||||
if (pMeshletData->groups.IsCreated && pMeshletData->groups.Count > 0)
|
||||
{
|
||||
var maxLodLevel = 0u;
|
||||
for (var j = 0; j < pMeshletData->groups.Count; j++)
|
||||
{
|
||||
maxLodLevel = Math.Max(maxLodLevel, pMeshletData->groups[j].lodLevel);
|
||||
}
|
||||
|
||||
pMeshletData->lodLevelCount = (int)maxLodLevel + 1;
|
||||
}
|
||||
|
||||
var maxMaterialSlot = 0;
|
||||
for (var j = 0; j < parts.Length; j++)
|
||||
{
|
||||
maxMaterialSlot = Math.Max(maxMaterialSlot, parts[j].materialIndex);
|
||||
}
|
||||
|
||||
pMeshletData->materialSlotCount = maxMaterialSlot + 1;
|
||||
}
|
||||
|
||||
public static void BuildClusterLodHierarchy()
|
||||
{
|
||||
// TODO: Implement a function that builds a cluster LOD hierarchy for a mesh, which can be used for efficient rendering of large meshes with varying levels of detail.
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Engine;
|
||||
using Ghost.Nvtt;
|
||||
using Misaki.HighPerformance.Jobs;
|
||||
using Misaki.HighPerformance.LowLevel;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.Mathematics.SPMD;
|
||||
using System.IO.Hashing;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
@@ -191,45 +189,41 @@ internal static partial class TextureProcessor
|
||||
|
||||
pCtx.Get()->SetCudaAcceleration(NvttApi.IsCudaSupported());
|
||||
|
||||
int edgeLength;
|
||||
using (var cubeSurface0 = new DisposablePtr<NvttCubeSurface>(NvttCubeSurface.Create()))
|
||||
using (var mip0Surf = new DisposablePtr<NvttSurface>(NvttSurface.Create()))
|
||||
{
|
||||
if (!mip0Surf.Get()->SetImageData(NvttInputFormat.NVTT_InputFormat_RGBA_32F, _mipLevels[0].width, _mipLevels[0].height, 1, _mipLevels[0].data.GetUnsafePtr(), false, null))
|
||||
{
|
||||
return Result.Failure<int>("Failed to set image data for NVTT compression.");
|
||||
}
|
||||
var maxCubeMips = _mipLevels.Length;
|
||||
var w0 = _mipLevels[0].width;
|
||||
|
||||
cubeSurface0.Get()->Fold(mip0Surf.Get(), NvttCubeLayout.NVTT_CubeLayout_LatitudeLongitude);
|
||||
edgeLength = cubeSurface0.Get()->EdgeLength();
|
||||
if (!pCtx.Get()->OutputHeaderData(NvttTextureType.NVTT_TextureType_Cube, w0, w0, 1, maxCubeMips, false, pCompOpts.Get(), pOutOpts.Get()))
|
||||
{
|
||||
return Result.Failure("Failed to output header for cube map.");
|
||||
}
|
||||
|
||||
pCtx.Get()->OutputHeaderData(NvttTextureType.NVTT_TextureType_Cube, edgeLength, edgeLength, 1, _mipLevels.Length, false, pCompOpts.Get(), pOutOpts.Get());
|
||||
|
||||
for (var level = 0; level < _mipLevels.Length; level++)
|
||||
for (var face = 0; face < 6; face++)
|
||||
{
|
||||
using var cubeSurface = new DisposablePtr<NvttCubeSurface>(NvttCubeSurface.Create());
|
||||
using var mipSurf = new DisposablePtr<NvttSurface>(NvttSurface.Create());
|
||||
|
||||
mipSurf.Get()->SetImageData(NvttInputFormat.NVTT_InputFormat_RGBA_32F, _mipLevels[level].width, _mipLevels[level].height, 1, _mipLevels[level].data.GetUnsafePtr(), false, null);
|
||||
cubeSurface.Get()->Fold(mipSurf.Get(), NvttCubeLayout.NVTT_CubeLayout_LatitudeLongitude);
|
||||
|
||||
for (var face = 0; face < 6; face++)
|
||||
for (var level = 0; level < maxCubeMips; level++)
|
||||
{
|
||||
var faceSurf = cubeSurface.Get()->Face(face);
|
||||
using var faceSurf = new DisposablePtr<NvttSurface>(NvttSurface.Create());
|
||||
var w = _mipLevels[level].width;
|
||||
var faceSize = w * w * _textureInfo.colorComponents;
|
||||
var pSrcData = (float*)_mipLevels[level].data.GetUnsafePtr() + face * faceSize;
|
||||
|
||||
if (!faceSurf.Get()->SetImageData(NvttInputFormat.NVTT_InputFormat_RGBA_32F, w, w, 1, pSrcData, false, null))
|
||||
{
|
||||
return Result.Failure("Failed to set image data for NVTT compression.");
|
||||
}
|
||||
|
||||
if (_settings.Basic.IsSRGB)
|
||||
{
|
||||
faceSurf->ToSrgb(null);
|
||||
faceSurf.Get()->ToSrgb(null);
|
||||
}
|
||||
|
||||
if (!pCtx.Get()->Compress(faceSurf, face, level, pCompOpts.Get(), pOutOpts.Get()))
|
||||
if (!pCtx.Get()->Compress(faceSurf.Get(), face, level, pCompOpts.Get(), pOutOpts.Get()))
|
||||
{
|
||||
return Result.Failure("Failed to compress mipmap.");
|
||||
return Result.Failure("Failed to compress cube map face.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Result.Success(_mipLevels.Length);
|
||||
return Result.Success(maxCubeMips);
|
||||
}
|
||||
|
||||
public void Execute()
|
||||
@@ -316,8 +310,54 @@ internal static partial class TextureProcessor
|
||||
{
|
||||
if (settings.Basic.TextureShape == TextureShape.TextureCube)
|
||||
{
|
||||
var handle = GenerateMipHDRI(scheduler, textureInfo, out mipLevels);
|
||||
int maxCubeMips;
|
||||
int edge;
|
||||
UnsafeArray<float> baseCubeData;
|
||||
unsafe
|
||||
{
|
||||
using var cubeSurface0 = new DisposablePtr<NvttCubeSurface>(NvttCubeSurface.Create());
|
||||
using var mip0Surf = new DisposablePtr<NvttSurface>(NvttSurface.Create());
|
||||
if (!mip0Surf.Get()->SetImageData(NvttInputFormat.NVTT_InputFormat_RGBA_32F, textureInfo.width, textureInfo.height, 1, (void*)textureInfo.pixelData, false, null))
|
||||
{
|
||||
return Result.Failure("Failed to set image data for cube map.");
|
||||
}
|
||||
|
||||
cubeSurface0.Get()->Fold(mip0Surf.Get(), NvttCubeLayout.NVTT_CubeLayout_LatitudeLongitude);
|
||||
edge = cubeSurface0.Get()->EdgeLength();
|
||||
maxCubeMips = (int)Math.Floor(Math.Log2(edge)) + 1;
|
||||
|
||||
var pixelsPerFace = edge * edge;
|
||||
var faceSize = pixelsPerFace * textureInfo.colorComponents;
|
||||
baseCubeData = new UnsafeArray<float>(faceSize * 6, AllocationHandle.FreeList);
|
||||
|
||||
var channels = textureInfo.colorComponents;
|
||||
var channelPtrs = stackalloc float*[channels];
|
||||
for (var face = 0; face < 6; face++)
|
||||
{
|
||||
using var faceSurf = new DisposablePtr<NvttSurface>(cubeSurface0.Get()->Face(face));
|
||||
|
||||
// NVTT stores data in planar format: [RRRR...][GGGG...][BBBB...][AAAA...]
|
||||
// We need to interleave into RGBARGBA... for our sampling code.
|
||||
var pDst = (float*)baseCubeData.GetUnsafePtr() + face * faceSize;
|
||||
|
||||
for (var ch = 0; ch < channels; ch++)
|
||||
{
|
||||
channelPtrs[ch] = faceSurf.Get()->Channel(ch);
|
||||
}
|
||||
|
||||
for (var p = 0; p < pixelsPerFace; p++)
|
||||
{
|
||||
for (var ch = 0; ch < channels; ch++)
|
||||
{
|
||||
pDst[p * channels + ch] = channelPtrs[ch][p];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var handle = GenerateMipHDRI(scheduler, textureInfo, baseCubeData, edge, maxCubeMips, out mipLevels);
|
||||
await scheduler.WaitAsync(handle, cancellationToken);
|
||||
baseCubeData.Dispose();
|
||||
}
|
||||
|
||||
var workItem = new NvttPipelineTask(cachePath, textureInfo, settings, mipLevels);
|
||||
|
||||
@@ -2,6 +2,7 @@ using Ghost.Core;
|
||||
using Misaki.HighPerformance.Jobs;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
using Misaki.HighPerformance.Mathematics.SPMD;
|
||||
using System.Runtime.CompilerServices;
|
||||
using static Misaki.HighPerformance.Mathematics.math;
|
||||
@@ -72,34 +73,70 @@ internal static partial class TextureProcessor
|
||||
return MathV.Normalize(sampleVec);
|
||||
}
|
||||
|
||||
// Maps a 3D direction vector to 2D equirectangular UVs
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static Vector2<TFloat, float> DirToEquirectangularUV(Vector3<TFloat, float> dir)
|
||||
private static float3 CubemapUVToDir(int face, float u, float v)
|
||||
{
|
||||
var u = TFloat.Atan2(dir.z, dir.x);
|
||||
var v = TFloat.Asin(dir.y);
|
||||
var sc = 2.0f * u - 1.0f;
|
||||
var tc = 1.0f - 2.0f * v;
|
||||
|
||||
u = u / (2.0f * PI) + 0.5f;
|
||||
v = v / PI + 0.5f;
|
||||
return MathV.Create<TFloat, float>(u, v);
|
||||
float x = 0, y = 0, z = 0;
|
||||
switch (face)
|
||||
{
|
||||
case 0: x = 1.0f; y = tc; z = -sc; break;
|
||||
case 1: x = -1.0f; y = tc; z = sc; break;
|
||||
case 2: x = sc; y = 1.0f; z = -tc; break;
|
||||
case 3: x = sc; y = -1.0f; z = tc; break;
|
||||
case 4: x = sc; y = tc; z = 1.0f; break;
|
||||
case 5: x = -sc; y = tc; z = -1.0f; break;
|
||||
}
|
||||
|
||||
return normalize(float3(x, y, z));
|
||||
}
|
||||
|
||||
// Samples the source HDR image using bilinear interpolation (simplified to nearest neighbor for brevity here)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static Vector3<TFloat, float> SampleEquirectangularMap(float* img, int w, int h, int c, Vector3<TFloat, float> dir)
|
||||
private static Vector3<TFloat, float> SampleCubemap(float* img, int edge, int c, Vector3<TFloat, float> dir)
|
||||
{
|
||||
var uv = DirToEquirectangularUV(dir);
|
||||
var absX = TFloat.Abs(dir.x);
|
||||
var absY = TFloat.Abs(dir.y);
|
||||
var absZ = TFloat.Abs(dir.z);
|
||||
|
||||
// Nearest neighbor pixel coordinates
|
||||
var px = (uv.x * (w - 1.0f)).Cast<TInt, int>();
|
||||
var py = (uv.y * (h - 1.0f)).Cast<TInt, int>();
|
||||
var isXPos = dir.x >= TFloat.Zero;
|
||||
var isYPos = dir.y >= TFloat.Zero;
|
||||
var isZPos = dir.z >= TFloat.Zero;
|
||||
|
||||
// Clamp
|
||||
px = TInt.Clamp(px, TInt.Zero, w - 1);
|
||||
py = TInt.Clamp(py, TInt.Zero, h - 1);
|
||||
var maxAxis = TFloat.Max(TFloat.Max(absX, absY), absZ);
|
||||
|
||||
// Assuming float RGB array format
|
||||
var idx = (py * w + px) * c;
|
||||
var faceIndexF = TFloat.Select(maxAxis == absX,
|
||||
TFloat.Select(isXPos, 0.0f, 1.0f),
|
||||
TFloat.Select(maxAxis == absY,
|
||||
TFloat.Select(isYPos, 2.0f, 3.0f),
|
||||
TFloat.Select(isZPos, 4.0f, 5.0f)));
|
||||
|
||||
var faceIndex = faceIndexF.Cast<TInt, int>();
|
||||
|
||||
var sc = TFloat.Select(maxAxis == absX,
|
||||
TFloat.Select(isXPos, -dir.z, dir.z),
|
||||
TFloat.Select(maxAxis == absY,
|
||||
dir.x,
|
||||
TFloat.Select(isZPos, dir.x, -dir.x)));
|
||||
|
||||
var tc = TFloat.Select(maxAxis == absX,
|
||||
dir.y,
|
||||
TFloat.Select(maxAxis == absY,
|
||||
TFloat.Select(isYPos, -dir.z, dir.z),
|
||||
dir.y));
|
||||
|
||||
var u = 0.5f * (sc / maxAxis + 1.0f);
|
||||
var v = 0.5f * (1.0f - tc / maxAxis);
|
||||
|
||||
var px = (u * (edge - 1.0f)).Cast<TInt, int>();
|
||||
var py = (v * (edge - 1.0f)).Cast<TInt, int>();
|
||||
|
||||
px = TInt.Clamp(px, TInt.Zero, edge - 1);
|
||||
py = TInt.Clamp(py, TInt.Zero, edge - 1);
|
||||
|
||||
var faceOffset = faceIndex * (edge * edge);
|
||||
var idx = (faceOffset + py * edge + px) * c;
|
||||
return MathV.GatherVector3<TFloat, float>(img, idx.GetUnsafePtr(), 1);
|
||||
}
|
||||
|
||||
@@ -115,22 +152,20 @@ internal static partial class TextureProcessor
|
||||
var pLevel = &pMipLevels[m];
|
||||
|
||||
var w = pLevel->width;
|
||||
var h = pLevel->height;
|
||||
var pData = pLevel->data;
|
||||
var data = pLevel->data;
|
||||
|
||||
var local_i = loopIndex - pLevel->offset;
|
||||
var x = local_i % w;
|
||||
var y = local_i / w;
|
||||
var u = (float)x / (w - 1);
|
||||
var v = (float)y / (h - 1);
|
||||
|
||||
var phi = (u - 0.5f) * 2.0f * PI;
|
||||
var theta = (v - 0.5f) * PI;
|
||||
var faceArea = w * w;
|
||||
var face = local_i / faceArea;
|
||||
var face_local_i = local_i % faceArea;
|
||||
var x = face_local_i % w;
|
||||
var y = face_local_i / w;
|
||||
|
||||
sincos(theta, out var sinTheta, out var cosTheta);
|
||||
sincos(phi, out var sinPhi, out var cosPhi);
|
||||
var N = float3(cosTheta * cosPhi, sinTheta, cosTheta * sinPhi);
|
||||
N = normalize(N);
|
||||
var u = (x + 0.5f) / w;
|
||||
var v = (y + 0.5f) / w;
|
||||
|
||||
var N = CubemapUVToDir(face, u, v);
|
||||
|
||||
// For split-sum, we assume View and Reflection directions equal the Normal
|
||||
var V = N;
|
||||
@@ -173,7 +208,7 @@ internal static partial class TextureProcessor
|
||||
L = MathV.Normalize(L);
|
||||
|
||||
var NdotL = TFloat.Max(MathV.Dot(vN, L), TFloat.Zero);
|
||||
var sampleColor = SampleEquirectangularMap(pImage, imageWidth, imageHeight, channelCount, L);
|
||||
var sampleColor = SampleCubemap(pImage, imageWidth, channelCount, L);
|
||||
|
||||
NdotL &= validLaneMask;
|
||||
|
||||
@@ -208,10 +243,14 @@ internal static partial class TextureProcessor
|
||||
}
|
||||
|
||||
// Write to output mip array
|
||||
var out_idx = (y * w + x) * channelCount;
|
||||
pData[out_idx] = prefilteredColor.x;
|
||||
pData[out_idx + 1] = prefilteredColor.y;
|
||||
pData[out_idx + 2] = prefilteredColor.z;
|
||||
var out_idx = (face * (w * w) + y * w + x) * channelCount;
|
||||
data[out_idx] = prefilteredColor.x;
|
||||
data[out_idx + 1] = prefilteredColor.y;
|
||||
data[out_idx + 2] = prefilteredColor.z;
|
||||
if (channelCount == 4)
|
||||
{
|
||||
data[out_idx + 3] = 1.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,11 +290,10 @@ internal static partial class TextureProcessor
|
||||
return bits * 2.3283064365386963e-10f; // bits / 0x100000000
|
||||
}
|
||||
|
||||
private static JobHandle GenerateMipHDRI(JobScheduler scheduler, TextureAssetHandler.TextureInfo textureInfo, out UnsafeArray<MipLevel> mipLevels)
|
||||
private static JobHandle GenerateMipHDRI(JobScheduler scheduler, TextureAssetHandler.TextureInfo textureInfo, UnsafeArray<float> baseCubeData, int edge, int totalMipLevels, out UnsafeArray<MipLevel> mipLevels)
|
||||
{
|
||||
Logger.DebugAssert(textureInfo.isHDR, "GenerateMipHDRI should only be called for HDR textures.");
|
||||
|
||||
var totalMipLevels = (int)Math.Floor(Math.Log2(Math.Max(textureInfo.width, textureInfo.height))) + 1;
|
||||
Logger.DebugAssert(textureInfo.colorComponents >= 3, "Texture must have at least 3 color components for RGB.");
|
||||
|
||||
mipLevels = new UnsafeArray<MipLevel>(totalMipLevels, AllocationHandle.FreeList);
|
||||
var radicalInverse_VdCLut = new UnsafeArray<float>(_SAMPLE_COUNT, AllocationHandle.FreeList);
|
||||
@@ -265,24 +303,23 @@ internal static partial class TextureProcessor
|
||||
radicalInverse_VdCLut[i] = RadicalInverse_VdC(i);
|
||||
}
|
||||
|
||||
int w, h;
|
||||
int w;
|
||||
var totalPixel = 0;
|
||||
|
||||
for (var i = 0; i < totalMipLevels; i++)
|
||||
{
|
||||
w = Math.Max(1, textureInfo.width >> i);
|
||||
h = Math.Max(1, textureInfo.height >> i);
|
||||
w = Math.Max(1, edge >> i);
|
||||
|
||||
mipLevels[i] = new MipLevel
|
||||
{
|
||||
data = new UnsafeArray<float>(w * h * textureInfo.colorComponents, AllocationHandle.FreeList),
|
||||
data = new UnsafeArray<float>(w * w * 6 * textureInfo.colorComponents, AllocationHandle.FreeList),
|
||||
width = w,
|
||||
height = h,
|
||||
height = w,
|
||||
offset = totalPixel,
|
||||
roughness = (float)i / (totalMipLevels - 1) // Linear roughness from 0 to 1 across mip levels
|
||||
};
|
||||
|
||||
totalPixel += w * h;
|
||||
totalPixel += w * w * 6;
|
||||
}
|
||||
|
||||
JobHandle handle;
|
||||
@@ -292,11 +329,11 @@ internal static partial class TextureProcessor
|
||||
{
|
||||
var job = new GGXMipGenerationJobSPMD<WideLane<float>, WideLane<int>>
|
||||
{
|
||||
pImage = (float*)textureInfo.pixelData,
|
||||
pImage = (float*)baseCubeData.GetUnsafePtr(),
|
||||
pMipLevels = (MipLevel*)mipLevels.GetUnsafePtr(),
|
||||
pRadicalInverse_VdCLut = (float*)radicalInverse_VdCLut.GetUnsafePtr(),
|
||||
imageWidth = textureInfo.width,
|
||||
imageHeight = textureInfo.height,
|
||||
imageWidth = edge,
|
||||
imageHeight = edge,
|
||||
numMipLevels = totalMipLevels,
|
||||
channelCount = textureInfo.colorComponents,
|
||||
};
|
||||
@@ -307,11 +344,11 @@ internal static partial class TextureProcessor
|
||||
{
|
||||
var job = new GGXMipGenerationJobSPMD<ScalarLane<float>, ScalarLane<int>>
|
||||
{
|
||||
pImage = (float*)textureInfo.pixelData,
|
||||
pImage = (float*)baseCubeData.GetUnsafePtr(),
|
||||
pMipLevels = (MipLevel*)mipLevels.GetUnsafePtr(),
|
||||
pRadicalInverse_VdCLut = (float*)radicalInverse_VdCLut.GetUnsafePtr(),
|
||||
imageWidth = textureInfo.width,
|
||||
imageHeight = textureInfo.height,
|
||||
imageWidth = edge,
|
||||
imageHeight = edge,
|
||||
numMipLevels = totalMipLevels,
|
||||
channelCount = textureInfo.colorComponents,
|
||||
};
|
||||
|
||||
@@ -159,7 +159,7 @@ internal sealed partial class ImportCoordinator : IDisposable
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var hash = XxHash128.HashToUInt128(JsonSerializer.SerializeToUtf8Bytes(settings));
|
||||
var hash = XxHash128.HashToUInt128(JsonSerializer.SerializeToUtf8Bytes(settings, settings.GetType()));
|
||||
Span<byte> bytes = stackalloc byte[16];
|
||||
Unsafe.WriteUnaligned(ref bytes[0], hash);
|
||||
|
||||
|
||||
@@ -66,13 +66,13 @@ internal static class ActivationHandler
|
||||
|
||||
AllocationManager.Initialize(opts);
|
||||
|
||||
//var assetRegistry = App.GetService<IAssetRegistry>();
|
||||
//var engineCore = App.GetService<EngineCore>();
|
||||
var assetRegistry = App.GetService<IAssetRegistry>();
|
||||
var engineCore = App.GetService<EngineCore>();
|
||||
|
||||
//assetRegistry.OnAssetImported += (sender, e) =>
|
||||
//{
|
||||
// engineCore.AssetManager.ReimportAsset(e);
|
||||
//};
|
||||
assetRegistry.OnAssetImported += (sender, e) =>
|
||||
{
|
||||
engineCore.AssetManager.ReimportAsset(e);
|
||||
};
|
||||
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Ghost.Core.Utilities;
|
||||
using Ghost.Editor.Core;
|
||||
using Ghost.Editor.Core.Assets;
|
||||
using Ghost.Editor.Core.Contracts;
|
||||
using Ghost.Editor.Core.Utilities;
|
||||
using Ghost.Editor.Models;
|
||||
using Ghost.Editor.Core.AssetHandler;
|
||||
using System.Collections.ObjectModel;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Ghost.Engine;
|
||||
using Ghost.Core.Utilities;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Ghost.Editor.ViewModels.Controls;
|
||||
|
||||
@@ -69,7 +69,7 @@ internal partial class ContentBrowserViewModel : ObservableObject
|
||||
|
||||
var fullPath = PathUtility.Normalize(e.AssetPath);
|
||||
var dirPath = Path.GetDirectoryName(fullPath);
|
||||
|
||||
|
||||
if (string.Equals(dirPath, CurrentDirectoryPath, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
@@ -86,7 +86,7 @@ internal partial class ContentBrowserViewModel : ObservableObject
|
||||
if (!Files.Any(f => string.Equals(f.Path, fullPath, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
var isDir = Directory.Exists(fullPath);
|
||||
AssetType assetType = AssetType.Unknown;
|
||||
var assetType = AssetType.Unknown;
|
||||
if (!isDir)
|
||||
{
|
||||
var ext = Path.GetExtension(fullPath);
|
||||
|
||||
@@ -8,12 +8,13 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DefineConstants>$(DefineConstants);MHP_ENABLE_SAFETY_CHECKS</DefineConstants>
|
||||
<DefineConstants>$(DefineConstants);MHP_ENABLE_SAFETY_CHECKS;MHP_ENABLE_MIMALLOC</DefineConstants>
|
||||
<IsAotCompatible>True</IsAotCompatible>
|
||||
<IsTrimmable>True</IsTrimmable>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<DefineConstants>$(DefineConstants);MHP_ENABLE_MIMALLOC</DefineConstants>
|
||||
<IsAotCompatible>True</IsAotCompatible>
|
||||
<IsTrimmable>True</IsTrimmable>
|
||||
</PropertyGroup>
|
||||
@@ -28,6 +29,7 @@
|
||||
<PackageReference Include="Misaki.HighPerformance.Mathematics" Version="1.3.3" />
|
||||
<PackageReference Include="Misaki.HighPerformance.Mathematics.SPMD" Version="1.3.0" />
|
||||
<PackageReference Include="System.IO.Hashing" Version="10.0.7" />
|
||||
<PackageReference Include="TerraFX.Interop.Mimalloc" Version="1.6.7.2" />
|
||||
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.26100.6" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ internal class AssetHandlerRegistrationGenerator : IIncrementalGenerator
|
||||
|
||||
foreach (var symbol in array)
|
||||
{
|
||||
var attribute = symbol.GetAttributes().FirstOrDefault(a => a.AttributeClass?.ToDisplayString() == "Ghost.Editor.Core.AssetHandler.CustomAssetHandlerAttribute");
|
||||
var attribute = symbol.GetAttributes().FirstOrDefault(a => a.AttributeClass?.ToDisplayString() == "Ghost.Editor.Core.Assets.CustomAssetHandlerAttribute");
|
||||
if (attribute == null)
|
||||
{
|
||||
continue;
|
||||
@@ -44,7 +44,7 @@ internal class AssetHandlerRegistrationGenerator : IIncrementalGenerator
|
||||
var extensions = $"new string[] {{ {string.Join(", ", extensionsTypesConstants.Select(v => v.ToCSharpString()))} }}";
|
||||
var version = (int)attribute.ConstructorArguments[2].Value;
|
||||
|
||||
sb.Append($" global::Ghost.Editor.Core.AssetHandler.AssetHandlerRegistry.RegisterHandler(new {symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}(), Guid.Parse(\"{id}\"), {extensions}, {version});");
|
||||
sb.AppendLine($" global::Ghost.Editor.Core.Assets.AssetHandlerRegistry.RegisterHandler(new {symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}(), Guid.Parse(\"{id}\"), {extensions}, {version});");
|
||||
}
|
||||
|
||||
var registerTypeName = "g_assethandler_registeration";
|
||||
@@ -71,7 +71,7 @@ internal static partial class {registerTypeName}
|
||||
return null;
|
||||
}
|
||||
|
||||
var iHandlerSymbol = context.SemanticModel.Compilation.GetTypeByMetadataName("Ghost.Editor.Core.AssetHandler.IAssetHandler");
|
||||
var iHandlerSymbol = context.SemanticModel.Compilation.GetTypeByMetadataName("Ghost.Editor.Core.Assets.IAssetHandler");
|
||||
if (iHandlerSymbol == null)
|
||||
{
|
||||
return null;
|
||||
@@ -114,7 +114,7 @@ internal class IAssetSettingsRegistrationGenerator : IIncrementalGenerator
|
||||
|
||||
foreach (var iface in array)
|
||||
{
|
||||
sb.AppendLine($" global::Ghost.Editor.Core.AssetHandler.AssetHandlerRegistry.RegisterIAssetSettingsType(typeof({iface.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}), \"{iface.Name}\");");
|
||||
sb.AppendLine($" global::Ghost.Editor.Core.Assets.AssetHandlerRegistry.RegisterIAssetSettingsType(typeof({iface.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}), \"{iface.Name}\");");
|
||||
}
|
||||
|
||||
var registerTypeName = "g_iassetsettings_registeration";
|
||||
@@ -141,7 +141,7 @@ internal static partial class {registerTypeName}
|
||||
return null;
|
||||
}
|
||||
|
||||
var iSettingsSymbol = context.SemanticModel.Compilation.GetTypeByMetadataName("Ghost.Editor.Core.AssetHandler.IAssetSettings");
|
||||
var iSettingsSymbol = context.SemanticModel.Compilation.GetTypeByMetadataName("Ghost.Editor.Core.Assets.IAssetSettings");
|
||||
if (iSettingsSymbol == null)
|
||||
{
|
||||
return null;
|
||||
|
||||
@@ -190,19 +190,18 @@ public static unsafe class MeshBuilder
|
||||
/// </summary>
|
||||
/// <param name="vertices">The vertex list.</param>
|
||||
/// <param name="indices">The index list.</param>
|
||||
public static void ComputeNormal(UnsafeList<Vertex> vertices, UnsafeList<uint> indices)
|
||||
public static void ComputeNormal(Span<Vertex> vertices, Span<uint> indices)
|
||||
{
|
||||
if (!vertices.IsCreated || vertices.Count < 3
|
||||
|| !indices.IsCreated || indices.Count < 3)
|
||||
if (vertices.Length < 3 || indices.Length < 3)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < indices.Count; i += 3)
|
||||
for (var i = 0; i < indices.Length; i += 3)
|
||||
{
|
||||
var i0 = indices[i];
|
||||
var i1 = indices[i + 1];
|
||||
var i2 = indices[i + 2];
|
||||
var i0 = (int)indices[i];
|
||||
var i1 = (int)indices[i + 1];
|
||||
var i2 = (int)indices[i + 2];
|
||||
|
||||
var v0 = vertices[i0];
|
||||
var v1 = vertices[i1];
|
||||
@@ -217,7 +216,7 @@ public static unsafe class MeshBuilder
|
||||
vertices[i2].normal.xyz += faceNormal;
|
||||
}
|
||||
|
||||
for (var i = 0; i < vertices.Count; i++)
|
||||
for (var i = 0; i < vertices.Length; i++)
|
||||
{
|
||||
vertices[i].normal = math.normalize(vertices[i].normal);
|
||||
}
|
||||
@@ -228,16 +227,16 @@ public static unsafe class MeshBuilder
|
||||
/// </summary>
|
||||
/// <param name="vertices">The vertex list.</param>
|
||||
/// <param name="indices">The index list.</param>
|
||||
public static void ComputeTangents(UnsafeList<Vertex> vertices, UnsafeList<uint> indices)
|
||||
public static void ComputeTangents(Span<Vertex> vertices, Span<uint> indices)
|
||||
{
|
||||
using var scope = AllocationManager.CreateStackScope();
|
||||
var bitangents = new UnsafeArray<float3>(vertices.Count, scope.AllocationHandle, AllocationOption.Clear);
|
||||
var bitangents = new UnsafeArray<float3>(vertices.Length, scope.AllocationHandle, AllocationOption.Clear);
|
||||
|
||||
for (var i = 0; i < indices.Count; i += 3)
|
||||
for (var i = 0; i < indices.Length; i += 3)
|
||||
{
|
||||
var i0 = indices[i];
|
||||
var i1 = indices[i + 1];
|
||||
var i2 = indices[i + 2];
|
||||
var i0 = (int)indices[i];
|
||||
var i1 = (int)indices[i + 1];
|
||||
var i2 = (int)indices[i + 2];
|
||||
|
||||
var v0 = vertices[i0];
|
||||
var v1 = vertices[i1];
|
||||
@@ -258,7 +257,7 @@ public static unsafe class MeshBuilder
|
||||
|
||||
for (var j = 0; j < 3; j++)
|
||||
{
|
||||
var idx = indices[i + j];
|
||||
var idx = (int)indices[i + j];
|
||||
var t = vertices[idx].tangent;
|
||||
vertices[idx].tangent.xyz = t.xyz + tangent.xyz;
|
||||
|
||||
@@ -266,7 +265,7 @@ public static unsafe class MeshBuilder
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < vertices.Count; i++)
|
||||
for (var i = 0; i < vertices.Length; i++)
|
||||
{
|
||||
var n = vertices[i].normal;
|
||||
var t = vertices[i].tangent.xyz;
|
||||
|
||||
Reference in New Issue
Block a user