Explorar o código

Added option to specify the capacity of the height field material list (#1122)

When modifying materials after creation using HeightFieldShape::SetMaterials this can be used to prevent reallocation of the buffers. Note that if the capacity is not high enough, a realloc will still take place.
Jorrit Rouwe hai 1 ano
pai
achega
343687a614

+ 1 - 0
Docs/ReleaseNotes.md

@@ -19,6 +19,7 @@ For breaking API changes see [this document](https://github.com/jrouwe/JoltPhysi
 * Sped up deserialization of HeightFieldShape/MeshShape classes by optimizing reading a vector of data in StreamIn, by switching std::vector out for a custom Array class and by combining a number of allocations into one.
 * Added HeightFieldShape::GetMinHeightValue/GetMaxHeightValue that can be used to know which range of heights are accepted by SetHeights.
 * Allowing negative stride when getting/setting height field shape heights or materials. This improves performance if your data happens to be layed out the wrong way around.
+* Added HeightFieldShapeSettings::mMaterialsCapacity which can enlarge the internal materials array capacity to avoid resizing when HeightFieldShape::SetMaterials is called with materials that weren't in use by the height field yet.
 
 #### Character
 

+ 33 - 23
Jolt/Physics/Collision/Shape/HeightFieldShape.cpp

@@ -52,6 +52,7 @@ JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(HeightFieldShapeSettings)
 	JPH_ADD_ATTRIBUTE(HeightFieldShapeSettings, mScale)
 	JPH_ADD_ATTRIBUTE(HeightFieldShapeSettings, mMinHeightValue)
 	JPH_ADD_ATTRIBUTE(HeightFieldShapeSettings, mMaxHeightValue)
+	JPH_ADD_ATTRIBUTE(HeightFieldShapeSettings, mMaterialsCapacity)
 	JPH_ADD_ATTRIBUTE(HeightFieldShapeSettings, mSampleCount)
 	JPH_ADD_ATTRIBUTE(HeightFieldShapeSettings, mBlockSize)
 	JPH_ADD_ATTRIBUTE(HeightFieldShapeSettings, mBitsPerSample)
@@ -354,27 +355,28 @@ void HeightFieldShape::StoreMaterialIndices(const HeightFieldShapeSettings &inSe
 	uint in_count_min_1 = inSettings.mSampleCount - 1;
 	uint out_count_min_1 = mSampleCount - 1;
 
-	mNumBitsPerMaterialIndex = 32 - CountLeadingZeros((uint32)mMaterials.size() - 1);
+	mNumBitsPerMaterialIndex = 32 - CountLeadingZeros(max((uint32)mMaterials.size(), inSettings.mMaterialsCapacity) - 1);
 	mMaterialIndices.resize(((Square(out_count_min_1) * mNumBitsPerMaterialIndex + 7) >> 3) + 1, 0); // Add 1 byte so we don't read out of bounds when reading an uint16
 
-	for (uint y = 0; y < out_count_min_1; ++y)
-		for (uint x = 0; x < out_count_min_1; ++x)
-		{
-			// Read material
-			uint16 material_index = x < in_count_min_1 && y < in_count_min_1? uint16(inSettings.mMaterialIndices[x + y * in_count_min_1]) : 0;
-
-			// Calculate byte and bit position where the material index needs to go
-			uint sample_pos = x + y * out_count_min_1;
-			uint bit_pos = sample_pos * mNumBitsPerMaterialIndex;
-			uint byte_pos = bit_pos >> 3;
-			bit_pos &= 0b111;
-
-			// Write the material index
-			material_index <<= bit_pos;
-			JPH_ASSERT(byte_pos + 1 < mMaterialIndices.size());
-			mMaterialIndices[byte_pos] |= uint8(material_index);
-			mMaterialIndices[byte_pos + 1] |= uint8(material_index >> 8);
-		}
+	if (mMaterials.size() > 1)
+		for (uint y = 0; y < out_count_min_1; ++y)
+			for (uint x = 0; x < out_count_min_1; ++x)
+			{
+				// Read material
+				uint16 material_index = x < in_count_min_1 && y < in_count_min_1? uint16(inSettings.mMaterialIndices[x + y * in_count_min_1]) : 0;
+
+				// Calculate byte and bit position where the material index needs to go
+				uint sample_pos = x + y * out_count_min_1;
+				uint bit_pos = sample_pos * mNumBitsPerMaterialIndex;
+				uint byte_pos = bit_pos >> 3;
+				bit_pos &= 0b111;
+
+				// Write the material index
+				material_index <<= bit_pos;
+				JPH_ASSERT(byte_pos + 1 < mMaterialIndices.size());
+				mMaterialIndices[byte_pos] |= uint8(material_index);
+				mMaterialIndices[byte_pos + 1] |= uint8(material_index >> 8);
+			}
 }
 
 void HeightFieldShape::CacheValues()
@@ -403,11 +405,15 @@ HeightFieldShape::HeightFieldShape(const HeightFieldShapeSettings &inSettings, S
 	mScale(inSettings.mScale),
 	mSampleCount(((inSettings.mSampleCount + inSettings.mBlockSize - 1) / inSettings.mBlockSize) * inSettings.mBlockSize), // Round sample count to nearest block size
 	mBlockSize(inSettings.mBlockSize),
-	mBitsPerSample(uint8(inSettings.mBitsPerSample)),
-	mMaterials(inSettings.mMaterials)
+	mBitsPerSample(uint8(inSettings.mBitsPerSample))
 {
 	CacheValues();
 
+	// Reserve a bigger materials list if requested
+	if (inSettings.mMaterialsCapacity > 0)
+		mMaterials.reserve(inSettings.mMaterialsCapacity);
+	mMaterials = inSettings.mMaterials;
+
 	// Check block size
 	if (mBlockSize < 2 || mBlockSize > 8)
 	{
@@ -696,7 +702,7 @@ HeightFieldShape::HeightFieldShape(const HeightFieldShapeSettings &inSettings, S
 	CalculateActiveEdges(inSettings);
 
 	// Compress material indices
-	if (mMaterials.size() > 1)
+	if (mMaterials.size() > 1 || inSettings.mMaterialsCapacity > 1)
 		StoreMaterialIndices(inSettings);
 
 	outResult.Set(this);
@@ -1265,7 +1271,7 @@ bool HeightFieldShape::SetMaterials(uint inX, uint inY, uint inSizeX, uint inSiz
 	uint count_min_1 = mSampleCount - 1;
 	uint32 new_bits_per_material_index = 32 - CountLeadingZeros((uint32)mMaterials.size() - 1);
 	JPH_ASSERT(mNumBitsPerMaterialIndex <= 8 && new_bits_per_material_index <= 8);
-	if (new_bits_per_material_index != mNumBitsPerMaterialIndex)
+	if (new_bits_per_material_index > mNumBitsPerMaterialIndex)
 	{
 		// Resize the material indices array
 		mMaterialIndices.resize(((Square(count_min_1) * new_bits_per_material_index + 7) >> 3) + 1, 0); // Add 1 byte so we don't read out of bounds when reading an uint16
@@ -2607,6 +2613,10 @@ void HeightFieldShape::RestoreBinaryState(StreamIn &inStream)
 	inStream.Read(mMaterialIndices);
 	inStream.Read(mNumBitsPerMaterialIndex);
 
+	// We don't have the exact number of reserved materials anymore, but ensure that our array is big enough
+	// TODO: Next time when we bump the binary serialization format of this class we should store the capacity and allocate the right amount, for now we accept a little bit of waste
+	mMaterials.reserve(PhysicsMaterialList::size_type(1) << mNumBitsPerMaterialIndex);
+
 	CacheValues();
 
 	bool has_heights = false;

+ 4 - 0
Jolt/Physics/Collision/Shape/HeightFieldShape.h

@@ -78,6 +78,10 @@ public:
 	/// Artificial maximum value of mHeightSamples, used for compression and can be used to update the terrain after creating with higher height values. If there are any higher values in mHeightSamples, this value will be ignored.
 	float							mMaxHeightValue = -FLT_MAX;
 
+	/// When bigger than mMaterials.size() the internal material list will be preallocated to support this number of materials.
+	/// This avoids reallocations when calling HeightFieldShape::SetMaterials with new materials later.
+	uint32							mMaterialsCapacity = 0;
+
 	/// The heightfield is divided in blocks of mBlockSize * mBlockSize * 2 triangles and the acceleration structure culls blocks only,
 	/// bigger block sizes reduce memory consumption but also reduce query performance. Sensible values are [2, 8], does not need to be
 	/// a power of 2. Note that at run-time we'll perform one more grid subdivision, so the effective block size is half of what is provided here.