Browse Source

Added support to merge buffers of user specified size

Vicente Penades 4 years ago
parent
commit
4788e4715e

+ 9 - 4
src/SharpGLTF.Core/Schema2/Serialization.WriteContext.cs

@@ -184,7 +184,7 @@ namespace SharpGLTF.Schema2
             Guard.NotNullOrEmpty(baseName, nameof(baseName));
             Guard.NotNull(model, nameof(model));
 
-            model = this._PreprocessSchema2(model, this.ImageWriting == ResourceWriteMode.BufferView, this.MergeBuffers);
+            model = this._PreprocessSchema2(model, this.ImageWriting == ResourceWriteMode.BufferView, this.MergeBuffers, this.BuffersMaxSize);
             Guard.NotNull(model, nameof(model));
 
             model._PrepareBuffersForSatelliteWriting(this, baseName);
@@ -213,7 +213,7 @@ namespace SharpGLTF.Schema2
             Guard.NotNullOrEmpty(baseName, nameof(baseName));
             Guard.NotNull(model, nameof(model));
 
-            model = this._PreprocessSchema2(model, this.ImageWriting == ResourceWriteMode.BufferView, true);
+            model = this._PreprocessSchema2(model, this.ImageWriting == ResourceWriteMode.BufferView, true, int.MaxValue);
             Guard.NotNull(model, nameof(model));
 
             var ex = _BinarySerialization.IsBinaryCompatible(model);
@@ -270,8 +270,9 @@ namespace SharpGLTF.Schema2
         /// <param name="model">The source <see cref="MODEL"/> instance.</param>
         /// <param name="imagesAsBufferViews">true if images should be stored as buffer views.</param>
         /// <param name="mergeBuffers">true if it's required the model must have a single buffer.</param>
+        /// <param name="buffersMaxSize">When merging buffers, the max buffer size</param>
         /// <returns>The source <see cref="MODEL"/> instance, or a cloned and modified instance if current settings required it.</returns>
-        private MODEL _PreprocessSchema2(MODEL model, bool imagesAsBufferViews, bool mergeBuffers)
+        private MODEL _PreprocessSchema2(MODEL model, bool imagesAsBufferViews, bool mergeBuffers, int buffersMaxSize)
         {
             Guard.NotNull(model, nameof(model));
 
@@ -292,7 +293,11 @@ namespace SharpGLTF.Schema2
             }
 
             if (imagesAsBufferViews) model.MergeImages();
-            if (mergeBuffers) model.MergeBuffers();
+            if (mergeBuffers)
+            {
+                if (buffersMaxSize == int.MaxValue) model.MergeBuffers();
+                else model.MergeBuffers(buffersMaxSize);
+            }
 
             if (this._UpdateSupportedExtensions) model.UpdateExtensionsSupport();
 

+ 13 - 0
src/SharpGLTF.Core/Schema2/Serialization.WriteSettings.cs

@@ -81,6 +81,18 @@ namespace SharpGLTF.Schema2
         /// </summary>
         public Boolean MergeBuffers { get; set; } = true;
 
+        /// <summary>
+        /// Gets or sets the size used to split all the resources into individual buffers.
+        /// </summary>
+        /// <remarks>
+        /// It only has an effect when these conditions are met:
+        /// <list type="table">
+        /// <item><see cref="MergeBuffers"/> must be true.</item>
+        /// <item>Output format must be glTF, not GLB</item>
+        /// </list>
+        /// </remarks>
+        public int BuffersMaxSize { get; set; } = int.MaxValue;
+
         /// <summary>
         /// Gets or sets a value indicating whether the JSON formatting will include indentation.
         /// </summary>
@@ -115,6 +127,7 @@ namespace SharpGLTF.Schema2
             other.ImageWriting = this.ImageWriting;
             other.ImageWriteCallback = this.ImageWriteCallback;
             other.MergeBuffers = this.MergeBuffers;
+            other.BuffersMaxSize = this.BuffersMaxSize;
             other._JsonOptions = this._JsonOptions;
             other.Validation = this.Validation;
         }

+ 73 - 5
src/SharpGLTF.Core/Schema2/gltf.Buffer.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Collections.Generic;
 using System.Linq;
 
 namespace SharpGLTF.Schema2
@@ -187,27 +188,44 @@ namespace SharpGLTF.Schema2
         }
 
         /// <summary>
-        /// Merges all the <see cref="ModelRoot.LogicalBuffers"/> instances into a single big one.
+        /// Merges all the <see cref="LogicalBuffers"/> instances into a single big one.
         /// </summary>
         /// <remarks>
+        /// <para>
         /// When merging the buffers, it also adjusts the BufferView offsets so the data they point to remains the same.
-        /// If images are required to be included in the binary, call <see cref="ModelRoot.MergeImages"/> before calling <see cref="MergeBuffers"/>
-        /// This action cannot be reversed.
+        /// </para>
+        /// <para>
+        /// If images are required to be included in the binary, call <see cref="MergeImages"/>
+        /// before calling <see cref="MergeBuffers()"/>.
+        /// </para>
         /// </remarks>
+        /// <exception cref="InvalidOperationException">
+        /// .Net arrays have an upper limit of 2Gb, so this is the biggest a buffer can normally grow,
+        /// so attempting to merge buffers that sum more than 2Gb will throw this exception.
+        /// </exception>
         public void MergeBuffers()
         {
             // retrieve all buffers and merge them into a single, big buffer
 
             var views = _bufferViews
                 .OrderByDescending(item => item.Content.Count)
-                .ToArray();
+                .ToList();
 
-            if (views.Length <= 1) return; // nothing to do.
+            if (views.Count <= 1) return; // nothing to do.
+
+            // check final size
+
+            long totalLen = views.Sum(item => (long)item.Content.Count.WordPadded());
+            if (totalLen >= (long)int.MaxValue) throw new InvalidOperationException("Can't merge a buffer larger than 2Gb");
+
+            // begin merge
 
             var sbbuilder = new _StaticBufferBuilder(0);
 
             foreach (var bv in views) bv._IsolateBufferMemory(sbbuilder);
 
+            // build final buffer
+
             this._buffers.Clear();
 
             var b = new Buffer(sbbuilder.ToArray());
@@ -215,6 +233,56 @@ namespace SharpGLTF.Schema2
             this._buffers.Add(b);
         }
 
+        /// <summary>
+        /// Merges all the <see cref="LogicalBuffers"/> instances into buffers of <paramref name="maxSize"/> size.
+        /// </summary>
+        /// <param name="maxSize">
+        /// The maximum size of each buffer.
+        /// Notice that if a single BufferView is larger than <paramref name="maxSize"/>, that buffer will be also larger.
+        /// </param>
+        public void MergeBuffers(int maxSize)
+        {
+            // retrieve all buffers and merge them into a single, big buffer
+
+            var views = _bufferViews
+                .OrderByDescending(item => item.Content.Count)
+                .ToList();
+
+            if (views.Count <= 1) return; // nothing to do.
+
+            // begin merge
+
+            var buffers = new List<_StaticBufferBuilder>();
+            buffers.Add(new _StaticBufferBuilder(0));
+
+            foreach (var bv in views)
+            {
+                var last = buffers.Last();
+
+                var alreadyFull = last.BufferSize >= maxSize;
+                var notEmpty = last.BufferSize > 0;
+                var bvTooBig = bv.Content.Count >= maxSize;
+
+                if (alreadyFull || (notEmpty && bvTooBig))
+                {
+                    last = new _StaticBufferBuilder(buffers.Count);
+                    buffers.Add(last);
+                }
+
+                bv._IsolateBufferMemory(last);
+            }
+
+            // build final buffers
+
+            this._buffers.Clear();
+
+            foreach (var buffer in buffers)
+            {
+                var b = new Buffer(buffer.ToArray());
+                this._buffers.Add(b);
+            }
+        }
+
         /// <summary>
         /// Refreshes all internal memory buffers.
         /// </summary>

+ 2 - 0
src/SharpGLTF.Core/Schema2/gltf.BufferView.cs

@@ -368,6 +368,8 @@ namespace SharpGLTF.Schema2
 
         public int BufferIndex => _BufferIndex;
 
+        public int BufferSize => _Data.Count;
+
         #endregion
 
         #region API

+ 6 - 0
tests/SharpGLTF.Core.Tests/Schema2/LoadAndSave/LoadSpecialModelsTest.cs

@@ -105,6 +105,12 @@ namespace SharpGLTF.Schema2.LoadAndSave
             clonedModel.ApplyBasisTransform(basisTransform);
 
             clonedModel.AttachToCurrentTest("polly_out_transformed.glb");
+
+            var wsettings = new WriteSettings();
+            wsettings.ImageWriting = ResourceWriteMode.BufferView;
+            wsettings.MergeBuffers = true;
+            wsettings.BuffersMaxSize = 1024 * 1024 * 10;
+            clonedModel.AttachToCurrentTest("polly_out_merged_10mb.gltf", wsettings);
         }
 
         [Test]