2
0
Эх сурвалжийг харах

WIP: improving how vertex buffers are packed to give room for morph target buffers...

Vicente Penades 6 жил өмнө
parent
commit
7377ce1854

+ 24 - 5
src/SharpGLTF.Core/Memory/MemoryAccessor.cs

@@ -62,13 +62,13 @@ namespace SharpGLTF.Memory
             this.Normalized = normalized;
         }
 
-        public MemoryAccessInfo Slice(int start, int count)
+        public MemoryAccessInfo Slice(int itemStart, int itemCount)
         {
-            var stride = Math.Max(this.ByteStride, this.Dimensions.DimCount() * this.Encoding.ByteLength());
+            var stride = GetRowByteLength();
 
             var clone = this;
-            clone.ByteOffset += start * stride;
-            clone.ItemsCount = Math.Min(clone.ItemsCount, count);
+            clone.ByteOffset += itemStart * stride;
+            clone.ItemsCount = Math.Min(clone.ItemsCount, itemCount);
 
             return clone;
         }
@@ -92,7 +92,20 @@ namespace SharpGLTF.Memory
         /// <summary>
         /// Gets the number of bytes of the current encoded Item, padded to 4 bytes.
         /// </summary>
-        public int PaddedByteLength => (this.Dimensions.DimCount() * this.Encoding.ByteLength()).WordPadded();
+        public int PaddedByteLength => GetRowByteLength();
+
+        public int GetRowByteLength()
+        {
+            var xlen = Encoding.ByteLength();
+
+            if (Dimensions != Schema2.DimensionType.SCALAR || Name != "INDEX")
+            {
+                xlen *= this.Dimensions.DimCount();
+                xlen = xlen.WordPadded();
+            }
+
+            return Math.Max(ByteStride, xlen);
+        }
 
         public Boolean IsValidVertexAttribute
         {
@@ -275,6 +288,12 @@ namespace SharpGLTF.Memory
 
         #region API
 
+        public void Update(ArraySegment<Byte> data, MemoryAccessInfo info)
+        {
+            this._Attribute = info;
+            this._Data = data;
+        }
+
         public IntegerArray AsIntegerArray()
         {
             Guard.IsTrue(_Attribute.IsValidIndexer, nameof(_Attribute));

+ 10 - 0
src/SharpGLTF.Toolkit/Geometry/MeshBuilderToolkit.cs

@@ -23,6 +23,16 @@ namespace SharpGLTF.Geometry
 
     static class MeshBuilderToolkit
     {
+        public static Schema2.EncodingType GetOptimalIndexEncoding<TMaterial>(this IEnumerable<IMeshBuilder<TMaterial>> meshes)
+        {
+            var maxIndex = meshes
+                .SelectMany(item => item.Primitives)
+                .Where(item => item.VerticesPerPrimitive >= 2) // points will never use index buffers
+                .SelectMany(prim => prim.GetIndices())
+                .Max();
+
+            return maxIndex < 65535 ? Schema2.EncodingType.UNSIGNED_SHORT : Schema2.EncodingType.UNSIGNED_INT;
+        }
         public static IMeshBuilder<TMaterial> CreateMeshBuilderFromVertexAttributes<TMaterial>(params string[] vertexAttributes)
         {
             Type meshType = GetMeshBuilderType(typeof(TMaterial), vertexAttributes);

+ 77 - 0
src/SharpGLTF.Toolkit/Geometry/PackedBuffer.cs

@@ -0,0 +1,77 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using SharpGLTF.Memory;
+
+namespace SharpGLTF.Geometry
+{
+    class PackedBuffer
+    {
+        private readonly List<MemoryAccessor> _Accessors = new List<MemoryAccessor>();
+
+        protected int? ByteStride
+        {
+            get
+            {
+                if (_Accessors.Count == 0) return null;
+
+                return _Accessors[0].Attribute.PaddedByteLength;
+            }
+        }
+
+        public void AddAccessors(params MemoryAccessor[] accessors)
+        {
+            foreach (var a in accessors)
+            {
+                // ensure that all accessors have the same byte stride
+                if (this.ByteStride.HasValue)
+                {
+                    var astride = a.Attribute.PaddedByteLength;
+                    Guard.IsTrue(this.ByteStride.Value == astride, nameof(accessors));
+                }
+
+                _Accessors.Add(a);
+            }
+        }
+
+        public void MergeBuffers()
+        {
+            if (_Accessors.Count == 0) return;
+
+            var srcBuffers = _Accessors
+                .Select(item => item.Data)
+                .Distinct()
+                .OrderByDescending(item => item.Count)
+                .ToList();
+
+            var array = new Byte[srcBuffers.Sum(item => item.Count)];
+
+            int offset = 0;
+
+            var dstOffsets = new Dictionary<ArraySegment<Byte>, int>();
+
+            foreach (var src in srcBuffers)
+            {
+                // find src in array
+
+                dstOffsets[src] = offset;
+
+                src.CopyTo(0, array, offset, src.Count);
+                offset += src.Count;
+            }
+
+            var dstBuffer = new ArraySegment<Byte>(array);
+
+            foreach (var a in _Accessors)
+            {
+                offset = dstOffsets[a.Data];
+
+                var attribute = a.Attribute;
+                attribute.ByteOffset += offset;
+
+                a.Update(dstBuffer, attribute);
+            }
+        }
+    }
+}

+ 74 - 29
src/SharpGLTF.Toolkit/Geometry/PackedMeshBuilder.cs

@@ -16,14 +16,7 @@ namespace SharpGLTF.Geometry
     {
         #region lifecycle
 
-        /// <summary>
-        /// Converts a collection of <see cref="MeshBuilder{TMaterial, TvP, TvM, TvS}"/>
-        /// to a collection of <see cref="PackedMeshBuilder{TMaterial}"/>, trying to use
-        /// a single vertex buffer and a single index buffer shared by all meshes.
-        /// </summary>
-        /// <param name="meshBuilders">A collection of <see cref="MeshBuilder{TMaterial, TvP, TvM, TvS}"/> instances.</param>
-        /// <returns>A collection of <see cref="PackedMeshBuilder{TMaterial}"/> instances.</returns>
-        internal static IEnumerable<PackedMeshBuilder<TMaterial>> PackMeshes(IEnumerable<IMeshBuilder<TMaterial>> meshBuilders)
+        internal static IEnumerable<PackedMeshBuilder<TMaterial>> PackMeshesColumnVertices(IEnumerable<IMeshBuilder<TMaterial>> meshBuilders)
         {
             try
             {
@@ -34,43 +27,95 @@ namespace SharpGLTF.Geometry
                 throw new ArgumentException(ex.Message, nameof(meshBuilders), ex);
             }
 
-            var meshPrimitives = meshBuilders
-                .SelectMany(item => item.Primitives)
-                .Where(item => !item.IsEmpty());
+            var encoding = meshBuilders.GetOptimalIndexEncoding();
 
-            if (!meshPrimitives.Any())
+            var vertexBuffers = new Dictionary<string, PackedBuffer>();
+            var indexBuffer = new PackedBuffer();
+
+            var dstMeshes = new List<PackedMeshBuilder<TMaterial>>();
+
+            foreach (var srcMesh in meshBuilders)
             {
-                foreach (var mb in meshBuilders) yield return new PackedMeshBuilder<TMaterial>(mb.Name);
-                yield break;
+                var dstMesh = new PackedMeshBuilder<TMaterial>(srcMesh.Name);
+
+                foreach (var srcPrim in srcMesh.Primitives)
+                {
+                    if (srcPrim.Vertices.Count == 0) continue;
+
+                    var vAccessors = new List<Memory.MemoryAccessor>();
+
+                    foreach (var an in new[] { "POSITION", "NORMAL" })
+                    {
+                        var vAccessor = VertexTypes.VertexUtils.CreateVertexMemoryAccessors(srcPrim.Vertices, an);
+                        if (vAccessor == null) continue;
+
+                        vAccessors.Add(vAccessor);
+
+                        if (!vertexBuffers.TryGetValue(an, out PackedBuffer packed))
+                        {
+                            vertexBuffers[an] = packed = new PackedBuffer();
+                        }
+
+                        packed.AddAccessors(vAccessor);
+                    }
+
+                    var iAccessor = VertexTypes.VertexUtils.CreateIndexMemoryAccessor(srcPrim.GetIndices(), encoding);
+                    if (iAccessor != null) indexBuffer.AddAccessors(iAccessor);
+
+                    dstMesh.AddPrimitive(srcPrim.Material, srcPrim.VerticesPerPrimitive, vAccessors.ToArray(), iAccessor);
+                }
+
+                dstMeshes.Add(dstMesh);
             }
 
-            var vertexBlocks = VertexTypes.VertexUtils
-                .CreateVertexMemoryAccessors( meshPrimitives.Select(item => item.Vertices) )
-                .ToList();
+            // vertexBuffer.MergeBuffers();
+            indexBuffer.MergeBuffers();
+
+            return dstMeshes;
 
-            var indexBlocks = VertexTypes.VertexUtils
-                .CreateIndexMemoryAccessors( meshPrimitives.Select(item => item.GetIndices()) )
-                .ToList();
+        }
+
+        internal static IEnumerable<PackedMeshBuilder<TMaterial>> PackMeshesRowVertices(IEnumerable<IMeshBuilder<TMaterial>> meshBuilders)
+        {
+            try
+            {
+                foreach (var m in meshBuilders) m.Validate();
+            }
+            catch (Exception ex)
+            {
+                throw new ArgumentException(ex.Message, nameof(meshBuilders), ex);
+            }
 
-            if (vertexBlocks.Count != indexBlocks.Count) throw new InvalidOperationException("Vertex and index blocks count mismatch");
+            var encoding = meshBuilders.GetOptimalIndexEncoding();
 
-            int idx = 0;
+            var vertexBuffer = new PackedBuffer();
+            var indexBuffer = new PackedBuffer();
 
-            foreach (var meshBuilder in meshBuilders)
+            var dstMeshes = new List<PackedMeshBuilder<TMaterial>>();
+
+            foreach (var srcMesh in meshBuilders)
             {
-                var dstMesh = new PackedMeshBuilder<TMaterial>(meshBuilder.Name);
+                var dstMesh = new PackedMeshBuilder<TMaterial>(srcMesh.Name);
 
-                foreach (var primitiveBuilder in meshBuilder.Primitives)
+                foreach (var srcPrim in srcMesh.Primitives)
                 {
-                    if (primitiveBuilder.IsEmpty()) continue;
+                    var vAccessors = VertexTypes.VertexUtils.CreateVertexMemoryAccessors(srcPrim.Vertices);
+                    if (vAccessors == null) continue;
+                    vertexBuffer.AddAccessors(vAccessors);
 
-                    dstMesh.AddPrimitive(primitiveBuilder.Material, primitiveBuilder.VerticesPerPrimitive, vertexBlocks[idx], indexBlocks[idx]);
+                    var iAccessor = VertexTypes.VertexUtils.CreateIndexMemoryAccessor(srcPrim.GetIndices(), encoding);
+                    if (iAccessor != null) indexBuffer.AddAccessors(iAccessor);
 
-                    ++idx;
+                    dstMesh.AddPrimitive(srcPrim.Material, srcPrim.VerticesPerPrimitive, vAccessors, iAccessor);
                 }
 
-                yield return dstMesh;
+                dstMeshes.Add(dstMesh);
             }
+
+            vertexBuffer.MergeBuffers();
+            indexBuffer.MergeBuffers();
+
+            return dstMeshes;
         }
 
         private PackedMeshBuilder(string name) { _MeshName = name; }

+ 62 - 55
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexUtils.cs

@@ -207,91 +207,98 @@ namespace SharpGLTF.Geometry.VertexTypes
 
         #region memory buffers API
 
-        public static IEnumerable<MemoryAccessor[]> CreateVertexMemoryAccessors<TVertex>(this IEnumerable<IReadOnlyList<TVertex>> vertexBlocks)
+        public static MemoryAccessor CreateVertexMemoryAccessors<TVertex>(this IReadOnlyList<TVertex> vertices, string attributeName)
             where TVertex : IVertexBuilder
         {
-            Guard.IsTrue(vertexBlocks.Any(), nameof(vertexBlocks));
-            Guard.IsTrue(vertexBlocks.All(item => item.Count > 0), nameof(vertexBlocks));
-            Guard.IsTrue(vertexBlocks.Select(item => item[0].GetType()).Distinct().Count() == 1, "multiple vertex types found");
-
-            // total number of vertices
-            var totalCount = vertexBlocks.Sum(item => item.Count);
+            if (vertices == null || vertices.Count == 0) return null;
 
             // determine the vertex attributes from the first vertex.
-            var firstVertex = vertexBlocks
-                .First(item => item.Count > 0)[0];
+            var firstVertex = vertices[0];
 
             var tvg = firstVertex.GetGeometry().GetType();
             var tvm = firstVertex.GetMaterial().GetType();
             var tvs = firstVertex.GetSkinning().GetType();
-            var attributes = _GetVertexAttributes(tvg, tvm, tvs, totalCount);
+            var attributes = _GetVertexAttributes(tvg, tvm, tvs, vertices.Count);
 
-            // create master vertex buffer
-            int byteStride = attributes[0].ByteStride;
-            var vbuffer = new ArraySegment<byte>(new Byte[byteStride * totalCount]);
+            var attribute = attributes.FirstOrDefault(item => item.Name == attributeName);
+            if (attribute.Name == null) return null;
+            attribute.ByteOffset = 0;
+            attribute.ByteStride = 0;
+
+            // create a buffer
+            var vbuffer = new ArraySegment<byte>(new Byte[attribute.PaddedByteLength * vertices.Count]);
 
             // fill the buffer with the vertex blocks.
 
-            var baseVertexIndex = 0;
+            var accessor = new MemoryAccessor(vbuffer, attribute);
 
-            foreach (var block in vertexBlocks)
-            {
-                var accessors = MemoryAccessInfo
-                    .Slice(attributes, baseVertexIndex, block.Count)
-                    .Select(item => new MemoryAccessor(vbuffer, item))
-                    .ToArray();
+            accessor.FillAccessor(vertices);
+
+            return accessor;
+        }
+
+        public static MemoryAccessor[] CreateVertexMemoryAccessors<TVertex>(this IReadOnlyList<TVertex> vertices)
+            where TVertex : IVertexBuilder
+        {
+            if (vertices == null || vertices.Count == 0) return null;
+
+            // determine the vertex attributes from the first vertex.
+            var firstVertex = vertices[0];
+
+            var tvg = firstVertex.GetGeometry().GetType();
+            var tvm = firstVertex.GetMaterial().GetType();
+            var tvs = firstVertex.GetSkinning().GetType();
+            var attributes = _GetVertexAttributes(tvg, tvm, tvs, vertices.Count);
 
-                foreach (var accessor in accessors)
-                {
-                    var columnFunc = _GetVertexBuilderAttributeFunc(accessor.Attribute.Name);
+            // create a buffer
+            int byteStride = attributes[0].ByteStride;
+            var vbuffer = new ArraySegment<byte>(new Byte[byteStride * vertices.Count]);
 
-                    if (accessor.Attribute.Dimensions == Schema2.DimensionType.SCALAR) accessor.AsScalarArray().Fill(block._GetColumn<TVertex, float>(columnFunc));
-                    if (accessor.Attribute.Dimensions == Schema2.DimensionType.VEC2) accessor.AsVector2Array().Fill(block._GetColumn<TVertex, Vector2>(columnFunc));
-                    if (accessor.Attribute.Dimensions == Schema2.DimensionType.VEC3) accessor.AsVector3Array().Fill(block._GetColumn<TVertex, Vector3>(columnFunc));
-                    if (accessor.Attribute.Dimensions == Schema2.DimensionType.VEC4) accessor.AsVector4Array().Fill(block._GetColumn<TVertex, Vector4>(columnFunc));
-                }
+            // fill the buffer with the vertex blocks.
 
-                yield return accessors;
+            var accessors = MemoryAccessInfo
+                .Slice(attributes, 0, vertices.Count)
+                .Select(item => new MemoryAccessor(vbuffer, item))
+                .ToArray();
 
-                baseVertexIndex += block.Count;
+            foreach (var accessor in accessors)
+            {
+                accessor.FillAccessor(vertices);
             }
+
+            return accessors;
         }
 
-        public static IEnumerable<MemoryAccessor> CreateIndexMemoryAccessors(this IEnumerable<IReadOnlyList<Int32>> indexBlocks)
+        private static void FillAccessor<TVertex>(this MemoryAccessor dstAccessor, IReadOnlyList<TVertex> srcVertices)
+            where TVertex : IVertexBuilder
         {
-            Guard.IsTrue(indexBlocks.Any(), nameof(indexBlocks));
+            var columnFunc = _GetVertexBuilderAttributeFunc(dstAccessor.Attribute.Name);
+
+            if (dstAccessor.Attribute.Dimensions == Schema2.DimensionType.SCALAR) dstAccessor.AsScalarArray().Fill(srcVertices._GetColumn<TVertex, Single>(columnFunc));
+            if (dstAccessor.Attribute.Dimensions == Schema2.DimensionType.VEC2) dstAccessor.AsVector2Array().Fill(srcVertices._GetColumn<TVertex, Vector2>(columnFunc));
+            if (dstAccessor.Attribute.Dimensions == Schema2.DimensionType.VEC3) dstAccessor.AsVector3Array().Fill(srcVertices._GetColumn<TVertex, Vector3>(columnFunc));
+            if (dstAccessor.Attribute.Dimensions == Schema2.DimensionType.VEC4) dstAccessor.AsVector4Array().Fill(srcVertices._GetColumn<TVertex, Vector4>(columnFunc));
+        }
 
-            // total number of indices
-            var totalCount = indexBlocks.Sum(item => item.Count);
+        public static MemoryAccessor CreateIndexMemoryAccessor(this IReadOnlyList<Int32> indices, Schema2.EncodingType encoding)
+        {
+            if (indices == null || indices.Count == 0) return null;
 
             // determine appropiate encoding
-            var maxIndex = totalCount == 0 ? 0 : indexBlocks.SelectMany(item => item).Max();
-            var encoding = maxIndex < 65535 ? Schema2.EncodingType.UNSIGNED_SHORT : Schema2.EncodingType.UNSIGNED_INT;
+            // var maxIndex = indices.Max();
+            // var encoding = maxIndex < 65535 ? Schema2.EncodingType.UNSIGNED_SHORT : Schema2.EncodingType.UNSIGNED_INT;
 
-            var attribute = new MemoryAccessInfo("INDEX", 0, totalCount, 0, Schema2.DimensionType.SCALAR, encoding);
+            var attribute = new MemoryAccessInfo("INDEX", 0, indices.Count, 0, Schema2.DimensionType.SCALAR, encoding);
 
-            // create master index buffer
-            var ibytes = new Byte[encoding.ByteLength() * totalCount];
+            // create buffer
+            var ibytes = new Byte[encoding.ByteLength() * indices.Count];
             var ibuffer = new ArraySegment<byte>(ibytes);
 
-            var baseIndicesIndex = 0;
-
-            foreach (var block in indexBlocks)
-            {
-                if (block.Count == 0)
-                {
-                    yield return null;
-                    continue;
-                }
+            var accessor = new MemoryAccessor(ibuffer, attribute.Slice(0, indices.Count));
 
-                var accessor = new MemoryAccessor(ibuffer, attribute.Slice(baseIndicesIndex, block.Count));
+            accessor.AsIntegerArray().Fill(indices);
 
-                accessor.AsIntegerArray().Fill(block);
-
-                yield return accessor;
-
-                baseIndicesIndex += block.Count;
-            }
+            return accessor;
         }
 
         private static MemoryAccessInfo[] _GetVertexAttributes(Type vertexType, Type valuesType, Type jointsType, int itemsCount)

+ 2 - 2
src/SharpGLTF.Toolkit/Schema2/MeshExtensions.cs

@@ -69,7 +69,7 @@ namespace SharpGLTF.Schema2
 
             // creates meshes and primitives using MemoryAccessors using a single, shared vertex and index buffer
             var srcMeshes = PackedMeshBuilder<TMaterial>
-                .PackMeshes(meshBuilders)
+                .PackMeshesRowVertices(meshBuilders)
                 .ToList();
 
             var dstMeshes = new List<Mesh>();
@@ -248,7 +248,7 @@ namespace SharpGLTF.Schema2
         public static MeshPrimitive WithVertexAccessors<TVertex>(this MeshPrimitive primitive, IReadOnlyList<TVertex> vertices)
             where TVertex : IVertexBuilder
         {
-            var memAccessors = VertexUtils.CreateVertexMemoryAccessors(new[] { vertices }).First();
+            var memAccessors = VertexUtils.CreateVertexMemoryAccessors( vertices );
 
             return primitive.WithVertexAccessors(memAccessors);
         }

+ 21 - 0
tests/SharpGLTF.Tests/Scenes/SceneBuilderTests.cs

@@ -156,6 +156,26 @@ namespace SharpGLTF.Scenes
             scene.AttachToCurrentTest("shapes.glb");
         }
 
+        [Test]
+        public void CreateSceneWithMixedVertexFormats()
+        {
+            TestContext.CurrentContext.AttachShowDirLink();
+            TestContext.CurrentContext.AttachGltfValidatorLinks();
+
+            var scene = new SceneBuilder();
+
+            var mesh1 = new MeshBuilder<VertexPosition, VertexEmpty, VertexEmpty>();
+            var mesh2 = new MeshBuilder<VertexPositionNormal, VertexEmpty, VertexEmpty>();
+
+            mesh1.AddCube(MaterialBuilder.CreateDefault(), Matrix4x4.Identity);
+            mesh2.AddCube(MaterialBuilder.CreateDefault(), Matrix4x4.Identity);
+
+            scene.AddMesh(mesh1, Matrix4x4.CreateTranslation(-2, 0, 0));
+            scene.AddMesh(mesh2, Matrix4x4.CreateTranslation(2, 0, 0));
+
+            scene.AttachToCurrentTest("scene.glb");
+        }
+
         [Test]
         public void CreateSceneWithEmptyMeshes()
         {
@@ -410,6 +430,7 @@ namespace SharpGLTF.Scenes
 
 
         [TestCase("Avocado.glb")]
+        [TestCase("GearboxAssy.glb")]
         [TestCase("RiggedFigure.glb")]
         [TestCase("RiggedSimple.glb")]
         [TestCase("BoxAnimated.glb")]