From fe2e5b6e42af94130eb2e50824f5bef10a53e0cf Mon Sep 17 00:00:00 2001 From: peterino2 Date: Mon, 13 Oct 2025 21:35:14 -0700 Subject: [PATCH] upgrading zphysics --- lib/miniaudio/src/miniaudio-test.zig | 13 + lib/test-libs.py | 54 + lib/zgltf/src/Gltf.zig | 1633 +++++++++++ lib/zgltf/src/zgltf.zig | 5 + lib/zphysics/.gitignore | 6 + lib/zphysics/README.md | 32 +- lib/zphysics/build.zig | 116 +- lib/zphysics/build.zig.zon | 6 +- .../libs/Jolt/AABBTree/AABBTreeBuilder.cpp | 149 +- .../libs/Jolt/AABBTree/AABBTreeBuilder.h | 50 +- .../libs/Jolt/AABBTree/AABBTreeToBuffer.h | 177 +- .../NodeCodec/NodeCodecQuadTreeHalfFloat.h | 118 +- .../TriangleCodecIndexed8BitPackSOA4Flags.h | 269 +- lib/zphysics/libs/Jolt/ConfigurationString.h | 42 +- lib/zphysics/libs/Jolt/Core/ARMNeon.h | 34 +- lib/zphysics/libs/Jolt/Core/Array.h | 713 +++++ lib/zphysics/libs/Jolt/Core/BinaryHeap.h | 96 + lib/zphysics/libs/Jolt/Core/ByteBuffer.h | 6 +- lib/zphysics/libs/Jolt/Core/Color.h | 17 +- lib/zphysics/libs/Jolt/Core/Core.h | 319 ++- lib/zphysics/libs/Jolt/Core/FPControlWord.h | 20 +- lib/zphysics/libs/Jolt/Core/FPException.h | 46 +- .../libs/Jolt/Core/FPFlushDenormals.h | 14 +- lib/zphysics/libs/Jolt/Core/Factory.cpp | 25 +- lib/zphysics/libs/Jolt/Core/Factory.h | 4 +- .../libs/Jolt/Core/FixedSizeFreeList.h | 32 +- .../libs/Jolt/Core/FixedSizeFreeList.inl | 16 +- lib/zphysics/libs/Jolt/Core/HashCombine.h | 205 +- lib/zphysics/libs/Jolt/Core/HashTable.h | 872 ++++++ .../libs/Jolt/Core/IssueReporting.cpp | 12 +- lib/zphysics/libs/Jolt/Core/IssueReporting.h | 14 +- lib/zphysics/libs/Jolt/Core/JobSystem.h | 73 +- lib/zphysics/libs/Jolt/Core/JobSystem.inl | 2 +- .../Jolt/Core/JobSystemSingleThreaded.cpp | 65 + .../libs/Jolt/Core/JobSystemSingleThreaded.h | 62 + .../libs/Jolt/Core/JobSystemThreadPool.cpp | 116 +- .../libs/Jolt/Core/JobSystemThreadPool.h | 19 +- .../libs/Jolt/Core/JobSystemWithBarrier.cpp | 11 +- .../libs/Jolt/Core/JobSystemWithBarrier.h | 4 +- lib/zphysics/libs/Jolt/Core/LinearCurve.cpp | 2 +- lib/zphysics/libs/Jolt/Core/LinearCurve.h | 10 +- lib/zphysics/libs/Jolt/Core/LockFreeHashMap.h | 14 +- .../libs/Jolt/Core/LockFreeHashMap.inl | 24 +- lib/zphysics/libs/Jolt/Core/Memory.cpp | 27 +- lib/zphysics/libs/Jolt/Core/Memory.h | 29 +- lib/zphysics/libs/Jolt/Core/Mutex.h | 2 +- lib/zphysics/libs/Jolt/Core/MutexArray.h | 12 +- lib/zphysics/libs/Jolt/Core/NonCopyable.h | 2 +- lib/zphysics/libs/Jolt/Core/Profiler.cpp | 491 +++- lib/zphysics/libs/Jolt/Core/Profiler.h | 105 +- lib/zphysics/libs/Jolt/Core/Profiler.inl | 12 +- lib/zphysics/libs/Jolt/Core/QuickSort.h | 12 +- lib/zphysics/libs/Jolt/Core/RTTI.cpp | 52 +- lib/zphysics/libs/Jolt/Core/RTTI.h | 94 +- lib/zphysics/libs/Jolt/Core/Reference.h | 75 +- lib/zphysics/libs/Jolt/Core/Result.h | 47 +- .../libs/Jolt/Core/STLAlignedAllocator.h | 6 + lib/zphysics/libs/Jolt/Core/STLAllocator.h | 37 +- .../libs/Jolt/Core/STLLocalAllocator.h | 170 ++ .../libs/Jolt/Core/STLTempAllocator.h | 15 +- lib/zphysics/libs/Jolt/Core/ScopeExit.h | 49 + lib/zphysics/libs/Jolt/Core/Semaphore.cpp | 72 +- lib/zphysics/libs/Jolt/Core/Semaphore.h | 59 +- lib/zphysics/libs/Jolt/Core/StaticArray.h | 73 +- lib/zphysics/libs/Jolt/Core/StreamIn.h | 48 +- lib/zphysics/libs/Jolt/Core/StreamOut.h | 42 +- lib/zphysics/libs/Jolt/Core/StreamUtils.h | 168 ++ lib/zphysics/libs/Jolt/Core/StridedPtr.h | 63 + lib/zphysics/libs/Jolt/Core/StringTools.cpp | 4 +- lib/zphysics/libs/Jolt/Core/StringTools.h | 31 +- lib/zphysics/libs/Jolt/Core/TempAllocator.h | 99 +- lib/zphysics/libs/Jolt/Core/TickCounter.cpp | 90 +- lib/zphysics/libs/Jolt/Core/TickCounter.h | 13 +- lib/zphysics/libs/Jolt/Core/UnorderedMap.h | 75 +- lib/zphysics/libs/Jolt/Core/UnorderedSet.h | 27 +- lib/zphysics/libs/Jolt/Geometry/AABox.h | 33 +- lib/zphysics/libs/Jolt/Geometry/AABox4.h | 55 +- lib/zphysics/libs/Jolt/Geometry/ClipPoly.h | 18 +- .../libs/Jolt/Geometry/ClosestPoint.h | 223 +- .../libs/Jolt/Geometry/ConvexHullBuilder.cpp | 74 +- .../libs/Jolt/Geometry/ConvexHullBuilder.h | 12 +- .../Jolt/Geometry/ConvexHullBuilder2D.cpp | 15 +- .../libs/Jolt/Geometry/ConvexHullBuilder2D.h | 12 +- .../libs/Jolt/Geometry/ConvexSupport.h | 6 +- .../libs/Jolt/Geometry/EPAConvexHullBuilder.h | 50 +- .../libs/Jolt/Geometry/EPAPenetrationDepth.h | 150 +- lib/zphysics/libs/Jolt/Geometry/Ellipse.h | 12 +- .../libs/Jolt/Geometry/GJKClosestPoint.h | 121 +- .../libs/Jolt/Geometry/IndexedTriangle.h | 41 +- lib/zphysics/libs/Jolt/Geometry/Indexify.cpp | 216 +- lib/zphysics/libs/Jolt/Geometry/Indexify.h | 4 +- .../libs/Jolt/Geometry/OrientedBox.cpp | 34 +- lib/zphysics/libs/Jolt/Geometry/OrientedBox.h | 8 +- lib/zphysics/libs/Jolt/Geometry/Plane.h | 24 +- lib/zphysics/libs/Jolt/Geometry/RayAABox.h | 14 +- lib/zphysics/libs/Jolt/Geometry/RayAABox8.h | 76 - lib/zphysics/libs/Jolt/Geometry/RaySphere.h | 8 +- lib/zphysics/libs/Jolt/Geometry/RayTriangle.h | 12 +- .../libs/Jolt/Geometry/RayTriangle8.h | 91 - lib/zphysics/libs/Jolt/Geometry/Sphere.h | 8 +- lib/zphysics/libs/Jolt/Geometry/Triangle.h | 6 +- lib/zphysics/libs/Jolt/Jolt.h | 2 +- lib/zphysics/libs/Jolt/Math/BVec16.h | 99 + lib/zphysics/libs/Jolt/Math/BVec16.inl | 177 ++ lib/zphysics/libs/Jolt/Math/DMat44.h | 8 +- lib/zphysics/libs/Jolt/Math/DMat44.inl | 18 +- lib/zphysics/libs/Jolt/Math/DVec3.h | 29 +- lib/zphysics/libs/Jolt/Math/DVec3.inl | 169 +- lib/zphysics/libs/Jolt/Math/Double3.h | 13 +- .../libs/Jolt/Math/EigenValueSymmetric.h | 100 +- lib/zphysics/libs/Jolt/Math/FindRoot.h | 2 +- lib/zphysics/libs/Jolt/Math/Float2.h | 3 +- lib/zphysics/libs/Jolt/Math/Float3.h | 15 +- lib/zphysics/libs/Jolt/Math/Float4.h | 10 +- .../libs/Jolt/Math/GaussianElimination.h | 26 +- lib/zphysics/libs/Jolt/Math/HalfFloat.h | 12 +- lib/zphysics/libs/Jolt/Math/Mat44.h | 13 +- lib/zphysics/libs/Jolt/Math/Mat44.inl | 397 +-- lib/zphysics/libs/Jolt/Math/Math.h | 25 +- lib/zphysics/libs/Jolt/Math/MathTypes.h | 16 +- lib/zphysics/libs/Jolt/Math/Matrix.h | 44 +- lib/zphysics/libs/Jolt/Math/Quat.h | 54 +- lib/zphysics/libs/Jolt/Math/Quat.inl | 94 +- lib/zphysics/libs/Jolt/Math/Real.h | 4 +- lib/zphysics/libs/Jolt/Math/Trigonometry.h | 20 + lib/zphysics/libs/Jolt/Math/UVec4.cpp | 19 - lib/zphysics/libs/Jolt/Math/UVec4.h | 18 +- lib/zphysics/libs/Jolt/Math/UVec4.inl | 119 +- lib/zphysics/libs/Jolt/Math/UVec8.h | 100 - lib/zphysics/libs/Jolt/Math/UVec8.inl | 138 - lib/zphysics/libs/Jolt/Math/Vec3.cpp | 42 +- lib/zphysics/libs/Jolt/Math/Vec3.h | 26 +- lib/zphysics/libs/Jolt/Math/Vec3.inl | 205 +- lib/zphysics/libs/Jolt/Math/Vec4.h | 25 +- lib/zphysics/libs/Jolt/Math/Vec4.inl | 211 +- lib/zphysics/libs/Jolt/Math/Vec8.h | 112 - lib/zphysics/libs/Jolt/Math/Vec8.inl | 148 - lib/zphysics/libs/Jolt/Math/Vector.h | 38 +- .../ObjectStream/GetPrimitiveTypeOfType.h | 30 +- .../libs/Jolt/ObjectStream/ObjectStream.cpp | 4 + .../libs/Jolt/ObjectStream/ObjectStream.h | 109 +- .../ObjectStream/ObjectStreamBinaryIn.cpp | 6 +- .../Jolt/ObjectStream/ObjectStreamBinaryIn.h | 8 +- .../ObjectStream/ObjectStreamBinaryOut.cpp | 5 + .../Jolt/ObjectStream/ObjectStreamBinaryOut.h | 10 +- .../libs/Jolt/ObjectStream/ObjectStreamIn.cpp | 214 +- .../libs/Jolt/ObjectStream/ObjectStreamIn.h | 22 +- .../Jolt/ObjectStream/ObjectStreamOut.cpp | 48 +- .../libs/Jolt/ObjectStream/ObjectStreamOut.h | 27 +- .../Jolt/ObjectStream/ObjectStreamTextIn.cpp | 80 +- .../Jolt/ObjectStream/ObjectStreamTextIn.h | 6 +- .../Jolt/ObjectStream/ObjectStreamTextOut.cpp | 6 +- .../Jolt/ObjectStream/ObjectStreamTextOut.h | 8 +- .../Jolt/ObjectStream/SerializableAttribute.h | 6 +- .../ObjectStream/SerializableAttributeEnum.h | 19 +- .../ObjectStream/SerializableAttributeTyped.h | 17 +- .../Jolt/ObjectStream/SerializableObject.h | 85 +- .../Jolt/ObjectStream/TypeDeclarations.cpp | 11 +- .../libs/Jolt/ObjectStream/TypeDeclarations.h | 43 +- .../libs/Jolt/Physics/Body/AllowedDOFs.h | 68 + lib/zphysics/libs/Jolt/Physics/Body/Body.cpp | 184 +- lib/zphysics/libs/Jolt/Physics/Body/Body.h | 243 +- lib/zphysics/libs/Jolt/Physics/Body/Body.inl | 60 +- .../libs/Jolt/Physics/Body/BodyAccess.cpp | 18 - .../libs/Jolt/Physics/Body/BodyAccess.h | 35 +- .../Physics/Body/BodyCreationSettings.cpp | 83 +- .../Jolt/Physics/Body/BodyCreationSettings.h | 32 +- .../libs/Jolt/Physics/Body/BodyFilter.h | 50 +- lib/zphysics/libs/Jolt/Physics/Body/BodyID.h | 23 +- .../libs/Jolt/Physics/Body/BodyInterface.cpp | 231 +- .../libs/Jolt/Physics/Body/BodyInterface.h | 96 +- .../libs/Jolt/Physics/Body/BodyLock.h | 8 +- .../Jolt/Physics/Body/BodyLockInterface.h | 26 +- .../libs/Jolt/Physics/Body/BodyLockMulti.h | 10 +- .../libs/Jolt/Physics/Body/BodyManager.cpp | 560 ++-- .../libs/Jolt/Physics/Body/BodyManager.h | 119 +- .../libs/Jolt/Physics/Body/BodyType.h | 19 + .../libs/Jolt/Physics/Body/MassProperties.cpp | 4 +- .../libs/Jolt/Physics/Body/MassProperties.h | 8 +- .../Jolt/Physics/Body/MotionProperties.cpp | 63 +- .../libs/Jolt/Physics/Body/MotionProperties.h | 133 +- .../Jolt/Physics/Body/MotionProperties.inl | 137 +- .../libs/Jolt/Physics/Body/MotionQuality.h | 6 +- .../libs/Jolt/Physics/Character/Character.cpp | 39 +- .../libs/Jolt/Physics/Character/Character.h | 22 +- .../Jolt/Physics/Character/CharacterBase.cpp | 14 + .../Jolt/Physics/Character/CharacterBase.h | 21 +- .../libs/Jolt/Physics/Character/CharacterID.h | 98 + .../Physics/Character/CharacterVirtual.cpp | 652 ++++- .../Jolt/Physics/Character/CharacterVirtual.h | 338 ++- .../Jolt/Physics/Collision/ActiveEdgeMode.h | 2 +- .../libs/Jolt/Physics/Collision/ActiveEdges.h | 11 +- .../Physics/Collision/BroadPhase/BroadPhase.h | 15 +- .../BroadPhase/BroadPhaseBruteForce.cpp | 34 +- .../BroadPhase/BroadPhaseBruteForce.h | 5 +- .../Collision/BroadPhase/BroadPhaseLayer.h | 27 +- .../BroadPhase/BroadPhaseLayerInterfaceMask.h | 92 + .../BroadPhaseLayerInterfaceTable.h | 64 + .../BroadPhase/BroadPhaseQuadTree.cpp | 106 +- .../Collision/BroadPhase/BroadPhaseQuadTree.h | 5 +- .../Collision/BroadPhase/BroadPhaseQuery.h | 2 +- .../ObjectVsBroadPhaseLayerFilterMask.h | 35 + .../ObjectVsBroadPhaseLayerFilterTable.h | 66 + .../Physics/Collision/BroadPhase/QuadTree.cpp | 256 +- .../Physics/Collision/BroadPhase/QuadTree.h | 72 +- .../Collision/CastConvexVsTriangles.cpp | 10 +- .../Physics/Collision/CastConvexVsTriangles.h | 4 +- .../libs/Jolt/Physics/Collision/CastResult.h | 5 +- .../Collision/CastSphereVsTriangles.cpp | 41 +- .../Physics/Collision/CastSphereVsTriangles.h | 4 +- .../Collision/CollideConvexVsTriangles.cpp | 28 +- .../Collision/CollideConvexVsTriangles.h | 4 +- .../Jolt/Physics/Collision/CollideShape.h | 23 +- .../Collision/CollideShapeVsShapePerLeaf.h | 93 + .../Collision/CollideSoftBodyVertexIterator.h | 110 + .../CollideSoftBodyVerticesVsTriangles.h | 90 + .../Collision/CollideSphereVsTriangles.cpp | 18 +- .../Collision/CollideSphereVsTriangles.h | 4 +- .../Physics/Collision/CollisionCollector.h | 32 +- .../Collision/CollisionCollectorImpl.h | 91 +- .../Physics/Collision/CollisionDispatch.cpp | 4 +- .../Physics/Collision/CollisionDispatch.h | 14 +- .../Jolt/Physics/Collision/CollisionGroup.cpp | 2 + .../Jolt/Physics/Collision/CollisionGroup.h | 11 +- .../Jolt/Physics/Collision/ContactListener.h | 37 +- .../Collision/EstimateCollisionResponse.cpp | 56 +- .../Collision/EstimateCollisionResponse.h | 4 +- .../Jolt/Physics/Collision/GroupFilter.cpp | 40 +- .../libs/Jolt/Physics/Collision/GroupFilter.h | 6 +- .../Jolt/Physics/Collision/GroupFilterTable.h | 42 +- .../Collision/InternalEdgeRemovingCollector.h | 261 ++ .../Collision/ManifoldBetweenTwoFaces.cpp | 53 +- .../Collision/ManifoldBetweenTwoFaces.h | 6 +- .../Physics/Collision/NarrowPhaseQuery.cpp | 68 +- .../Jolt/Physics/Collision/NarrowPhaseQuery.h | 7 +- .../Physics/Collision/NarrowPhaseStats.cpp | 29 +- .../Jolt/Physics/Collision/NarrowPhaseStats.h | 6 +- .../libs/Jolt/Physics/Collision/ObjectLayer.h | 12 +- .../Collision/ObjectLayerPairFilterMask.h | 52 + .../Collision/ObjectLayerPairFilterTable.h | 78 + .../Physics/Collision/PhysicsMaterial.cpp | 35 +- .../Jolt/Physics/Collision/PhysicsMaterial.h | 6 +- .../Physics/Collision/PhysicsMaterialSimple.h | 6 +- .../libs/Jolt/Physics/Collision/RayCast.h | 16 +- .../Jolt/Physics/Collision/Shape/BoxShape.cpp | 95 +- .../Jolt/Physics/Collision/Shape/BoxShape.h | 13 +- .../Physics/Collision/Shape/CapsuleShape.cpp | 147 +- .../Physics/Collision/Shape/CapsuleShape.h | 16 +- .../Physics/Collision/Shape/CompoundShape.cpp | 95 +- .../Physics/Collision/Shape/CompoundShape.h | 45 +- .../Collision/Shape/CompoundShapeVisitors.h | 36 +- .../Collision/Shape/ConvexHullShape.cpp | 276 +- .../Physics/Collision/Shape/ConvexHullShape.h | 21 +- .../Physics/Collision/Shape/ConvexShape.cpp | 81 +- .../Physics/Collision/Shape/ConvexShape.h | 23 +- .../Physics/Collision/Shape/CylinderShape.cpp | 174 +- .../Physics/Collision/Shape/CylinderShape.h | 15 +- .../Collision/Shape/DecoratedShape.cpp | 6 +- .../Physics/Collision/Shape/DecoratedShape.h | 16 +- .../Physics/Collision/Shape/EmptyShape.cpp | 65 + .../Jolt/Physics/Collision/Shape/EmptyShape.h | 75 + .../Collision/Shape/GetTrianglesContext.h | 26 +- .../Collision/Shape/HeightFieldShape.cpp | 1275 +++++++-- .../Collision/Shape/HeightFieldShape.h | 198 +- .../Physics/Collision/Shape/MeshShape.cpp | 303 ++- .../Jolt/Physics/Collision/Shape/MeshShape.h | 67 +- .../Collision/Shape/MutableCompoundShape.cpp | 79 +- .../Collision/Shape/MutableCompoundShape.h | 46 +- .../Shape/OffsetCenterOfMassShape.cpp | 19 +- .../Collision/Shape/OffsetCenterOfMassShape.h | 17 +- .../Physics/Collision/Shape/PlaneShape.cpp | 541 ++++ .../Jolt/Physics/Collision/Shape/PlaneShape.h | 147 + .../PolyhedronSubmergedVolumeCalculator.h | 2 +- .../Shape/RotatedTranslatedShape.cpp | 79 +- .../Collision/Shape/RotatedTranslatedShape.h | 40 +- .../Physics/Collision/Shape/ScaleHelpers.h | 17 +- .../Physics/Collision/Shape/ScaledShape.cpp | 37 +- .../Physics/Collision/Shape/ScaledShape.h | 20 +- .../Jolt/Physics/Collision/Shape/Shape.cpp | 139 +- .../libs/Jolt/Physics/Collision/Shape/Shape.h | 145 +- .../Physics/Collision/Shape/SphereShape.cpp | 90 +- .../Physics/Collision/Shape/SphereShape.h | 23 +- .../Collision/Shape/StaticCompoundShape.cpp | 88 +- .../Collision/Shape/StaticCompoundShape.h | 14 +- .../Jolt/Physics/Collision/Shape/SubShapeID.h | 14 +- .../Physics/Collision/Shape/SubShapeIDPair.h | 24 +- .../Collision/Shape/TaperedCapsuleShape.cpp | 121 +- .../Shape/TaperedCapsuleShape.gliffy | 1 + .../Collision/Shape/TaperedCapsuleShape.h | 27 +- .../Collision/Shape/TaperedCylinderShape.cpp | 703 +++++ .../Collision/Shape/TaperedCylinderShape.h | 132 + .../Physics/Collision/Shape/TriangleShape.cpp | 105 +- .../Physics/Collision/Shape/TriangleShape.h | 24 +- .../libs/Jolt/Physics/Collision/ShapeCast.h | 11 +- .../libs/Jolt/Physics/Collision/ShapeFilter.h | 7 +- .../Jolt/Physics/Collision/SimShapeFilter.h | 40 + .../Physics/Collision/SimShapeFilterWrapper.h | 81 + .../Physics/Collision/SortReverseAndStore.h | 11 +- .../Physics/Collision/TransformedShape.cpp | 6 +- .../Jolt/Physics/Collision/TransformedShape.h | 18 +- .../Constraints/CalculateSolverSteps.h | 66 + .../Physics/Constraints/ConeConstraint.cpp | 36 +- .../Jolt/Physics/Constraints/ConeConstraint.h | 17 +- .../Jolt/Physics/Constraints/Constraint.cpp | 41 +- .../Jolt/Physics/Constraints/Constraint.h | 65 +- .../Physics/Constraints/ConstraintManager.cpp | 144 +- .../Physics/Constraints/ConstraintManager.h | 21 +- .../ConstraintPart/AngleConstraintPart.h | 93 +- .../ConstraintPart/AxisConstraintPart.h | 437 ++- .../ConstraintPart/DualAxisConstraintPart.h | 89 +- .../ConstraintPart/GearConstraintPart.h | 18 +- .../HingeRotationConstraintPart.h | 75 +- .../IndependentAxisConstraintPart.h | 23 +- .../ConstraintPart/PointConstraintPart.h | 50 +- .../RackAndPinionConstraintPart.h | 18 +- .../RotationEulerConstraintPart.h | 43 +- .../RotationQuatConstraintPart.h | 58 +- .../Constraints/ConstraintPart/SpringPart.h | 171 +- .../ConstraintPart/SwingTwistConstraintPart.h | 323 ++- .../Constraints/ContactConstraintManager.cpp | 652 +++-- .../Constraints/ContactConstraintManager.h | 81 +- .../Constraints/DistanceConstraint.cpp | 77 +- .../Physics/Constraints/DistanceConstraint.h | 39 +- .../Physics/Constraints/FixedConstraint.cpp | 20 +- .../Physics/Constraints/FixedConstraint.h | 17 +- .../Physics/Constraints/GearConstraint.cpp | 21 +- .../Jolt/Physics/Constraints/GearConstraint.h | 15 +- .../Physics/Constraints/HingeConstraint.cpp | 83 +- .../Physics/Constraints/HingeConstraint.h | 57 +- .../Physics/Constraints/MotorSettings.cpp | 11 +- .../Jolt/Physics/Constraints/MotorSettings.h | 19 +- .../Physics/Constraints/PathConstraint.cpp | 42 +- .../Jolt/Physics/Constraints/PathConstraint.h | 33 +- .../Constraints/PathConstraintPath.cpp | 37 +- .../Physics/Constraints/PathConstraintPath.h | 13 +- .../Constraints/PathConstraintPathHermite.cpp | 14 +- .../Constraints/PathConstraintPathHermite.h | 12 +- .../Physics/Constraints/PointConstraint.cpp | 11 +- .../Physics/Constraints/PointConstraint.h | 19 +- .../Physics/Constraints/PulleyConstraint.cpp | 9 +- .../Physics/Constraints/PulleyConstraint.h | 17 +- .../Constraints/RackAndPinionConstraint.cpp | 21 +- .../Constraints/RackAndPinionConstraint.h | 15 +- .../Physics/Constraints/SixDOFConstraint.cpp | 338 ++- .../Physics/Constraints/SixDOFConstraint.h | 84 +- .../Physics/Constraints/SliderConstraint.cpp | 92 +- .../Physics/Constraints/SliderConstraint.h | 41 +- .../Physics/Constraints/SpringSettings.cpp | 35 + .../Jolt/Physics/Constraints/SpringSettings.h | 70 + .../Constraints/SwingTwistConstraint.cpp | 83 +- .../Constraints/SwingTwistConstraint.h | 44 +- .../Physics/Constraints/TwoBodyConstraint.cpp | 6 +- .../Physics/Constraints/TwoBodyConstraint.h | 10 +- .../libs/Jolt/Physics/DeterminismLog.h | 8 +- .../libs/Jolt/Physics/IslandBuilder.cpp | 22 +- .../libs/Jolt/Physics/IslandBuilder.h | 12 +- .../libs/Jolt/Physics/LargeIslandSplitter.cpp | 49 +- .../libs/Jolt/Physics/LargeIslandSplitter.h | 7 +- .../libs/Jolt/Physics/PhysicsLock.cpp | 17 - lib/zphysics/libs/Jolt/Physics/PhysicsLock.h | 24 +- .../libs/Jolt/Physics/PhysicsScene.cpp | 58 +- lib/zphysics/libs/Jolt/Physics/PhysicsScene.h | 24 +- .../libs/Jolt/Physics/PhysicsSettings.h | 26 +- .../libs/Jolt/Physics/PhysicsStepListener.h | 14 +- .../libs/Jolt/Physics/PhysicsSystem.cpp | 1463 ++++++---- .../libs/Jolt/Physics/PhysicsSystem.h | 150 +- .../libs/Jolt/Physics/PhysicsUpdateContext.h | 119 +- .../libs/Jolt/Physics/Ragdoll/Ragdoll.cpp | 119 +- .../libs/Jolt/Physics/Ragdoll/Ragdoll.h | 64 +- .../SoftBody/SoftBodyContactListener.h | 55 + .../SoftBody/SoftBodyCreationSettings.cpp | 122 + .../SoftBody/SoftBodyCreationSettings.h | 73 + .../Jolt/Physics/SoftBody/SoftBodyManifold.h | 74 + .../SoftBody/SoftBodyMotionProperties.cpp | 1324 +++++++++ .../SoftBody/SoftBodyMotionProperties.h | 298 ++ .../Jolt/Physics/SoftBody/SoftBodyShape.cpp | 341 +++ .../Jolt/Physics/SoftBody/SoftBodyShape.h | 73 + .../SoftBody/SoftBodySharedSettings.cpp | 1179 ++++++++ .../Physics/SoftBody/SoftBodySharedSettings.h | 341 +++ .../Physics/SoftBody/SoftBodyUpdateContext.h | 59 + .../Jolt/Physics/SoftBody/SoftBodyVertex.h | 36 + .../libs/Jolt/Physics/StateRecorder.h | 110 +- .../libs/Jolt/Physics/StateRecorderImpl.cpp | 12 +- .../libs/Jolt/Physics/StateRecorderImpl.h | 10 +- .../Physics/Vehicle/MotorcycleController.cpp | 194 +- .../Physics/Vehicle/MotorcycleController.h | 56 +- .../Vehicle/TrackedVehicleController.cpp | 49 +- .../Vehicle/TrackedVehicleController.h | 42 +- .../Jolt/Physics/Vehicle/VehicleAntiRollBar.h | 10 +- .../Vehicle/VehicleCollisionTester.cpp | 120 +- .../Physics/Vehicle/VehicleCollisionTester.h | 26 +- .../Physics/Vehicle/VehicleConstraint.cpp | 281 +- .../Jolt/Physics/Vehicle/VehicleConstraint.h | 124 +- .../Jolt/Physics/Vehicle/VehicleController.h | 25 +- .../Physics/Vehicle/VehicleDifferential.h | 6 +- .../Jolt/Physics/Vehicle/VehicleEngine.cpp | 10 +- .../libs/Jolt/Physics/Vehicle/VehicleEngine.h | 16 +- .../libs/Jolt/Physics/Vehicle/VehicleTrack.h | 8 +- .../Physics/Vehicle/VehicleTransmission.h | 15 +- .../libs/Jolt/Physics/Vehicle/Wheel.cpp | 33 +- .../libs/Jolt/Physics/Vehicle/Wheel.h | 15 +- .../Vehicle/WheeledVehicleController.cpp | 200 +- .../Vehicle/WheeledVehicleController.h | 64 +- lib/zphysics/libs/Jolt/RegisterTypes.cpp | 120 +- lib/zphysics/libs/Jolt/RegisterTypes.h | 20 +- .../libs/Jolt/Renderer/DebugRenderer.cpp | 381 ++- .../libs/Jolt/Renderer/DebugRenderer.h | 297 +- .../Jolt/Renderer/DebugRendererPlayback.cpp | 21 +- .../Jolt/Renderer/DebugRendererPlayback.h | 4 +- .../Jolt/Renderer/DebugRendererRecorder.cpp | 17 +- .../Jolt/Renderer/DebugRendererRecorder.h | 9 +- .../Jolt/Renderer/DebugRendererSimple.cpp | 80 + .../libs/Jolt/Renderer/DebugRendererSimple.h | 88 + .../libs/Jolt/Skeleton/SkeletalAnimation.cpp | 59 +- .../libs/Jolt/Skeleton/SkeletalAnimation.h | 38 +- lib/zphysics/libs/Jolt/Skeleton/Skeleton.h | 10 +- .../libs/Jolt/Skeleton/SkeletonMapper.cpp | 6 +- .../libs/Jolt/Skeleton/SkeletonMapper.h | 4 +- .../libs/Jolt/Skeleton/SkeletonPose.cpp | 2 +- .../libs/Jolt/Skeleton/SkeletonPose.h | 2 +- .../Jolt/TriangleGrouper/TriangleGrouper.h | 26 - .../TriangleGrouperClosestCentroid.cpp | 95 - .../TriangleGrouperClosestCentroid.h | 21 - .../TriangleGrouper/TriangleGrouperMorton.cpp | 49 - .../TriangleGrouper/TriangleGrouperMorton.h | 20 - .../TriangleSplitter/TriangleSplitter.cpp | 32 +- .../Jolt/TriangleSplitter/TriangleSplitter.h | 13 +- .../TriangleSplitterBinning.cpp | 75 +- .../TriangleSplitterBinning.h | 10 +- .../TriangleSplitterFixedLeafSize.cpp | 170 -- .../TriangleSplitterFixedLeafSize.h | 55 - .../TriangleSplitterLongestAxis.cpp | 31 - .../TriangleSplitterLongestAxis.h | 28 - .../TriangleSplitter/TriangleSplitterMean.cpp | 13 +- .../TriangleSplitter/TriangleSplitterMean.h | 2 +- .../TriangleSplitterMorton.cpp | 63 - .../TriangleSplitter/TriangleSplitterMorton.h | 32 - lib/zphysics/libs/JoltC/JoltPhysicsC.cpp | 527 +++- lib/zphysics/libs/JoltC/JoltPhysicsC.h | 514 +++- .../libs/JoltC/JoltPhysicsC_Extensions.cpp | 43 +- lib/zphysics/libs/JoltC/JoltPhysicsC_Tests.c | 335 ++- lib/zphysics/src/zphysics.zig | 2413 ++++++++++------- 442 files changed, 30390 insertions(+), 10630 deletions(-) create mode 100644 lib/miniaudio/src/miniaudio-test.zig create mode 100644 lib/test-libs.py create mode 100644 lib/zgltf/src/Gltf.zig create mode 100644 lib/zgltf/src/zgltf.zig create mode 100644 lib/zphysics/.gitignore create mode 100644 lib/zphysics/libs/Jolt/Core/Array.h create mode 100644 lib/zphysics/libs/Jolt/Core/BinaryHeap.h create mode 100644 lib/zphysics/libs/Jolt/Core/HashTable.h create mode 100644 lib/zphysics/libs/Jolt/Core/JobSystemSingleThreaded.cpp create mode 100644 lib/zphysics/libs/Jolt/Core/JobSystemSingleThreaded.h create mode 100644 lib/zphysics/libs/Jolt/Core/STLLocalAllocator.h create mode 100644 lib/zphysics/libs/Jolt/Core/ScopeExit.h create mode 100644 lib/zphysics/libs/Jolt/Core/StreamUtils.h create mode 100644 lib/zphysics/libs/Jolt/Core/StridedPtr.h delete mode 100644 lib/zphysics/libs/Jolt/Geometry/RayAABox8.h delete mode 100644 lib/zphysics/libs/Jolt/Geometry/RayTriangle8.h create mode 100644 lib/zphysics/libs/Jolt/Math/BVec16.h create mode 100644 lib/zphysics/libs/Jolt/Math/BVec16.inl delete mode 100644 lib/zphysics/libs/Jolt/Math/UVec4.cpp delete mode 100644 lib/zphysics/libs/Jolt/Math/UVec8.h delete mode 100644 lib/zphysics/libs/Jolt/Math/UVec8.inl delete mode 100644 lib/zphysics/libs/Jolt/Math/Vec8.h delete mode 100644 lib/zphysics/libs/Jolt/Math/Vec8.inl create mode 100644 lib/zphysics/libs/Jolt/Physics/Body/AllowedDOFs.h delete mode 100644 lib/zphysics/libs/Jolt/Physics/Body/BodyAccess.cpp create mode 100644 lib/zphysics/libs/Jolt/Physics/Body/BodyType.h create mode 100644 lib/zphysics/libs/Jolt/Physics/Character/CharacterID.h create mode 100644 lib/zphysics/libs/Jolt/Physics/Collision/BroadPhase/BroadPhaseLayerInterfaceMask.h create mode 100644 lib/zphysics/libs/Jolt/Physics/Collision/BroadPhase/BroadPhaseLayerInterfaceTable.h create mode 100644 lib/zphysics/libs/Jolt/Physics/Collision/BroadPhase/ObjectVsBroadPhaseLayerFilterMask.h create mode 100644 lib/zphysics/libs/Jolt/Physics/Collision/BroadPhase/ObjectVsBroadPhaseLayerFilterTable.h create mode 100644 lib/zphysics/libs/Jolt/Physics/Collision/CollideShapeVsShapePerLeaf.h create mode 100644 lib/zphysics/libs/Jolt/Physics/Collision/CollideSoftBodyVertexIterator.h create mode 100644 lib/zphysics/libs/Jolt/Physics/Collision/CollideSoftBodyVerticesVsTriangles.h create mode 100644 lib/zphysics/libs/Jolt/Physics/Collision/InternalEdgeRemovingCollector.h create mode 100644 lib/zphysics/libs/Jolt/Physics/Collision/ObjectLayerPairFilterMask.h create mode 100644 lib/zphysics/libs/Jolt/Physics/Collision/ObjectLayerPairFilterTable.h create mode 100644 lib/zphysics/libs/Jolt/Physics/Collision/Shape/EmptyShape.cpp create mode 100644 lib/zphysics/libs/Jolt/Physics/Collision/Shape/EmptyShape.h create mode 100644 lib/zphysics/libs/Jolt/Physics/Collision/Shape/PlaneShape.cpp create mode 100644 lib/zphysics/libs/Jolt/Physics/Collision/Shape/PlaneShape.h create mode 100644 lib/zphysics/libs/Jolt/Physics/Collision/Shape/TaperedCapsuleShape.gliffy create mode 100644 lib/zphysics/libs/Jolt/Physics/Collision/Shape/TaperedCylinderShape.cpp create mode 100644 lib/zphysics/libs/Jolt/Physics/Collision/Shape/TaperedCylinderShape.h create mode 100644 lib/zphysics/libs/Jolt/Physics/Collision/SimShapeFilter.h create mode 100644 lib/zphysics/libs/Jolt/Physics/Collision/SimShapeFilterWrapper.h create mode 100644 lib/zphysics/libs/Jolt/Physics/Constraints/CalculateSolverSteps.h create mode 100644 lib/zphysics/libs/Jolt/Physics/Constraints/SpringSettings.cpp create mode 100644 lib/zphysics/libs/Jolt/Physics/Constraints/SpringSettings.h delete mode 100644 lib/zphysics/libs/Jolt/Physics/PhysicsLock.cpp create mode 100644 lib/zphysics/libs/Jolt/Physics/SoftBody/SoftBodyContactListener.h create mode 100644 lib/zphysics/libs/Jolt/Physics/SoftBody/SoftBodyCreationSettings.cpp create mode 100644 lib/zphysics/libs/Jolt/Physics/SoftBody/SoftBodyCreationSettings.h create mode 100644 lib/zphysics/libs/Jolt/Physics/SoftBody/SoftBodyManifold.h create mode 100644 lib/zphysics/libs/Jolt/Physics/SoftBody/SoftBodyMotionProperties.cpp create mode 100644 lib/zphysics/libs/Jolt/Physics/SoftBody/SoftBodyMotionProperties.h create mode 100644 lib/zphysics/libs/Jolt/Physics/SoftBody/SoftBodyShape.cpp create mode 100644 lib/zphysics/libs/Jolt/Physics/SoftBody/SoftBodyShape.h create mode 100644 lib/zphysics/libs/Jolt/Physics/SoftBody/SoftBodySharedSettings.cpp create mode 100644 lib/zphysics/libs/Jolt/Physics/SoftBody/SoftBodySharedSettings.h create mode 100644 lib/zphysics/libs/Jolt/Physics/SoftBody/SoftBodyUpdateContext.h create mode 100644 lib/zphysics/libs/Jolt/Physics/SoftBody/SoftBodyVertex.h create mode 100644 lib/zphysics/libs/Jolt/Renderer/DebugRendererSimple.cpp create mode 100644 lib/zphysics/libs/Jolt/Renderer/DebugRendererSimple.h delete mode 100644 lib/zphysics/libs/Jolt/TriangleGrouper/TriangleGrouper.h delete mode 100644 lib/zphysics/libs/Jolt/TriangleGrouper/TriangleGrouperClosestCentroid.cpp delete mode 100644 lib/zphysics/libs/Jolt/TriangleGrouper/TriangleGrouperClosestCentroid.h delete mode 100644 lib/zphysics/libs/Jolt/TriangleGrouper/TriangleGrouperMorton.cpp delete mode 100644 lib/zphysics/libs/Jolt/TriangleGrouper/TriangleGrouperMorton.h delete mode 100644 lib/zphysics/libs/Jolt/TriangleSplitter/TriangleSplitterFixedLeafSize.cpp delete mode 100644 lib/zphysics/libs/Jolt/TriangleSplitter/TriangleSplitterFixedLeafSize.h delete mode 100644 lib/zphysics/libs/Jolt/TriangleSplitter/TriangleSplitterLongestAxis.cpp delete mode 100644 lib/zphysics/libs/Jolt/TriangleSplitter/TriangleSplitterLongestAxis.h delete mode 100644 lib/zphysics/libs/Jolt/TriangleSplitter/TriangleSplitterMorton.cpp delete mode 100644 lib/zphysics/libs/Jolt/TriangleSplitter/TriangleSplitterMorton.h diff --git a/lib/miniaudio/src/miniaudio-test.zig b/lib/miniaudio/src/miniaudio-test.zig new file mode 100644 index 0000000..77e879a --- /dev/null +++ b/lib/miniaudio/src/miniaudio-test.zig @@ -0,0 +1,13 @@ +const c = @import("miniaudio").c; +const std = @import("std"); + +test "miniaudio-compile-check" { + try std.testing.expect(c.MA_VERSION_MAJOR == 0); + try std.testing.expect(c.MA_VERSION_MINOR == 11); + + var engine: c.ma_engine = undefined; + const result = c.ma_engine_init(null, &engine); + c.ma_engine_uninit(&engine); + + try std.testing.expect(result == c.MA_SUCCESS); +} diff --git a/lib/test-libs.py b/lib/test-libs.py new file mode 100644 index 0000000..a383bd0 --- /dev/null +++ b/lib/test-libs.py @@ -0,0 +1,54 @@ + +import argparse +import shutil +import os +import sys +import subprocess + +orig_dir = os.path.abspath(os.path.dirname(__file__)) + + +dryRun = False + +def run(cmd, cwd=None): + global dryRun + print(" ".join(cmd)) + c = os.getcwd() + + if cwd is not None: + c = cwd + + if not dryRun: + print(subprocess.check_output(" ".join(cmd), shell=True, cwd=c).decode(), file=sys.stderr) + +def launchTests(): + run(['zig', 'env']) + + dirs = list(filter(None, [x if os.path.isdir(x) else None for x in os.listdir(orig_dir)])) + + results = {} + + for d in dirs: + print(">>>>> testing " , d) + try: + run(['zig', 'build', 'test', '-fincremental'], cwd=os.path.join(orig_dir, d)) + results[d] = "success πŸ†πŸ’¦πŸ˜" + except: + results[d] = "test failed. ❌️🀑🍿🍦πŸŽͺ🎈" + + for e in results: + print(e, results[e]) + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='run automation tasks for the repo') + + parser.add_argument('--dry', action="store_true", help='dont run anything') + + + args = parser.parse_args() + if args.dry: + dryRun = True + + + launchTests() + diff --git a/lib/zgltf/src/Gltf.zig b/lib/zgltf/src/Gltf.zig new file mode 100644 index 0000000..2cf26f6 --- /dev/null +++ b/lib/zgltf/src/Gltf.zig @@ -0,0 +1,1633 @@ +/// +/// glTFβ„’ 2.0 Specification is available here: +/// https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html +/// +const Gltf = @This(); + +const std = @import("std"); +const helpers = @import("helpers.zig"); +const types = @import("types.zig"); + +const mem = std.mem; +const math = std.math; +const json = std.json; +const fmt = std.fmt; +const panic = std.debug.panic; +const print = std.debug.print; +const assert = std.debug.assert; +const ArrayList = std.ArrayList; +const Allocator = std.mem.Allocator; +const ArenaAllocator = std.heap.ArenaAllocator; +const Mat4 = helpers.Mat4; +const Vec3 = helpers.Vec3; +const Quat = helpers.Quat; + +pub const Scene = types.Scene; +pub const Node = types.Node; +pub const Index = types.Index; +pub const Mesh = types.Mesh; +pub const Material = types.Material; +pub const Skin = types.Skin; +pub const TextureSampler = types.TextureSampler; +pub const Image = types.Image; +pub const Camera = types.Camera; +pub const Animation = types.Animation; +pub const Texture = types.Texture; +pub const Accessor = types.Accessor; +pub const AccessorType = types.AccessorType; +pub const AccessorIterator = types.AccessorIterator; +pub const BufferView = types.BufferView; +pub const Buffer = types.Buffer; +pub const Primitive = types.Primitive; +pub const Attribute = types.Attribute; +pub const Mode = types.Mode; +pub const ComponentType = types.ComponentType; +pub const Target = types.Target; +pub const MetallicRoughness = types.MetallicRoughness; +pub const AnimationSampler = types.AnimationSampler; +pub const Channel = types.Channel; +pub const MagFilter = types.MagFilter; +pub const MinFilter = types.MinFilter; +pub const WrapMode = types.WrapMode; +pub const TargetProperty = types.TargetProperty; +pub const Asset = types.Asset; +pub const LightType = types.LightType; +pub const Light = types.Light; +pub const LightSpot = types.LightSpot; + +pub const Data = struct { + asset: Asset, + scene: ?Index = null, + scenes: []Scene, + cameras: []Camera, + nodes: []Node, + meshes: []Mesh, + materials: []Material, + skins: []Skin, + samplers: []TextureSampler, + images: []Image, + animations: []Animation, + textures: []Texture, + accessors: []Accessor, + buffer_views: []BufferView, + buffers: []Buffer, + lights: []Light, +}; + +arena: *ArenaAllocator, +data: Data, + +glb_binary: ?[]align(4) const u8 = null, + +pub fn init(allocator: Allocator) Gltf { + const arena = allocator.create(ArenaAllocator) catch { + panic("Error while allocating memory for gltf arena.", .{}); + }; + arena.* = ArenaAllocator.init(allocator); + + return Gltf{ + .arena = arena, + .data = .{ + .asset = Asset{ .version = "Undefined" }, + .scenes = &[_]Scene{}, + .nodes = &[_]Node{}, + .cameras = &[_]Camera{}, + .meshes = &[_]Mesh{}, + .materials = &[_]Material{}, + .skins = &[_]Skin{}, + .samplers = &[_]TextureSampler{}, + .images = &[_]Image{}, + .animations = &[_]Animation{}, + .textures = &[_]Texture{}, + .accessors = &[_]Accessor{}, + .buffer_views = &[_]BufferView{}, + .buffers = &[_]Buffer{}, + .lights = &[_]Light{}, + }, + }; +} + +/// Fill data by parsing a glTF file's buffer. +pub fn parse(self: *Gltf, file_buffer: []align(4) const u8) !void { + if (isGlb(file_buffer)) { + try self.parseGlb(file_buffer); + } else { + try self.parseGltfJson(file_buffer); + } +} + +pub fn debugPrint(self: *const Gltf) void { + const msg = + \\ + \\ glTF file info: + \\ + \\ Node {} + \\ Mesh {} + \\ Skin {} + \\ Animation {} + \\ Texture {} + \\ Material {} + \\ Accessor {} + \\ BufferView {} + \\ Buffer {} + \\ Camera {} + \\ Light {} + \\ Scene {} + \\ + ; + + print(msg, .{ + self.data.nodes.len, + self.data.meshes.len, + self.data.skins.len, + self.data.animations.len, + self.data.textures.len, + self.data.materials.len, + self.data.accessors.len, + self.data.buffer_views.len, + self.data.buffers.len, + self.data.cameras.len, + self.data.lights.len, + self.data.scenes.len, + }); + + print(" Details:\n\n", .{}); + + if (self.data.skins.len > 0) { + print(" Skins found:\n", .{}); + + for (self.data.skins) |skin| { + print(" '{s}' found with {} joint(s).\n", .{ + skin.name.?, + skin.joints.len, + }); + } + + print("\n", .{}); + } + + if (self.data.animations.len > 0) { + print(" Animations found:\n", .{}); + + for (self.data.animations) |anim| { + print( + " '{s}' found with {} sampler(s) and {} channel(s).\n", + .{ anim.name.?, anim.samplers.len, anim.channels.len }, + ); + } + + print("\n", .{}); + } +} + +/// Retrieve actual data from a glTF BufferView through a given glTF Accessor. +/// Note: This library won't pull to memory the binary buffer corresponding +/// to the BufferView. +pub fn getDataFromBufferView( + self: *const Gltf, + comptime T: type, + allocator: std.mem.Allocator, + accessor: Accessor, + binary: []const u8, +) std.mem.Allocator.Error![]T { + if (ComponentType.fromType(T) != accessor.component_type) { + panic( + "Mismatch between gltf component '{}' and given type '{}'.", + .{ accessor.component_type, T }, + ); + } + + if (accessor.buffer_view == null) { + panic("Accessors without buffer_view are not supported yet.", .{}); + } + + const buffer_view = self.data.buffer_views[accessor.buffer_view.?]; + + const total_offset = accessor.byte_offset + buffer_view.byte_offset; + + const stride = if (buffer_view.byte_stride) |byte_stride| (byte_stride / @sizeOf(T)) else accessor.type.componentCount(); + + const total_count = accessor.count; + const datum_count = accessor.type.componentCount(); + + const data = @as([]const T, @ptrCast(@alignCast(binary[total_offset..]))); + var list = try ArrayList(T).initCapacity(allocator, total_count * datum_count); + defer list.deinit(allocator); + + var current_count: usize = 0; + while (current_count < total_count) : (current_count += 1) { + const slice = data[current_count * stride ..][0..datum_count]; + list.appendSliceAssumeCapacity(slice); + } + + return try list.toOwnedSlice(allocator); +} + +pub fn deinit(self: *Gltf) void { + self.arena.deinit(); + self.arena.child_allocator.destroy(self.arena); +} + +pub fn getLocalTransform(node: Node) Mat4 { + return blk: { + if (node.matrix) |mat4x4| { + break :blk .{ + mat4x4[0..4].*, + mat4x4[4..8].*, + mat4x4[8..12].*, + mat4x4[12..16].*, + }; + } + + break :blk helpers.recompose( + node.translation, + node.rotation, + node.scale, + ); + }; +} + +pub fn getGlobalTransform(data: *const Data, node: Node) Mat4 { + var parent_index = node.parent; + var node_transform: Mat4 = getLocalTransform(node); + + while (parent_index != null) { + const parent = data.nodes[parent_index.?]; + const parent_transform = getLocalTransform(parent); + + node_transform = helpers.mul(parent_transform, node_transform); + parent_index = parent.parent; + } + + return node_transform; +} + +fn isGlb(glb_buffer: []align(4) const u8) bool { + const GLB_MAGIC_NUMBER: u32 = 0x46546C67; // 'gltf' in ASCII. + const fields = @as([*]const u32, @ptrCast(glb_buffer)); + + return fields[0] == GLB_MAGIC_NUMBER; +} + +fn parseGlb(self: *Gltf, glb_buffer: []align(4) const u8) !void { + const GLB_CHUNK_TYPE_JSON: u32 = 0x4E4F534A; // 'JSON' in ASCII. + const GLB_CHUNK_TYPE_BIN: u32 = 0x004E4942; // 'BIN' in ASCII. + + // Keep track of the moving index in the glb buffer. + var index: usize = 0; + + // 'cause most of the interesting fields are u32s in the buffer, it's + // easier to read them with a pointer cast. + const fields = @as([*]const u32, @ptrCast(glb_buffer)); + + // The 12-byte header consists of three 4-byte entries: + // u32 magic + // u32 version + // u32 length + const total_length = blk: { + const header = fields[0..3]; + + const version = header[1]; + const length = header[2]; + + if (!isGlb(glb_buffer)) { + panic("First 32 bits are not equal to magic number.", .{}); + } + + if (version != 2) { + panic("Only glTF spec v2 is supported.", .{}); + } + + index = header.len * @sizeOf(u32); + break :blk length; + }; + + // Each chunk has the following structure: + // u32 chunkLength + // u32 chunkType + // ubyte[] chunkData + const json_buffer = blk: { + const json_chunk = fields[3..6]; + + if (json_chunk[1] != GLB_CHUNK_TYPE_JSON) { + panic("First GLB chunk must be JSON data.", .{}); + } + + const json_bytes: u32 = fields[3]; + const start = index + 2 * @sizeOf(u32); + const end = start + json_bytes; + + const json_buffer = glb_buffer[start..end]; + + index = end; + break :blk json_buffer; + }; + + const binary_buffer = blk: { + const fields_index = index / @sizeOf(u32); + + const binary_bytes = fields[fields_index]; + const start = index + 2 * @sizeOf(u32); + const end = start + binary_bytes; + + assert(end == total_length); + + std.debug.assert(start % 4 == 0); + std.debug.assert(end % 4 == 0); + const binary: []align(4) const u8 = @alignCast(glb_buffer[start..end]); + + if (fields[fields_index + 1] != GLB_CHUNK_TYPE_BIN) { + panic("Second GLB chunk must be binary data.", .{}); + } + + index = end; + break :blk binary; + }; + + try self.parseGltfJson(json_buffer); + self.glb_binary = binary_buffer; + + const buffer_views = self.data.buffer_views; + + for (self.data.images) |*image| { + if (image.buffer_view) |buffer_view_index| { + const buffer_view = buffer_views[buffer_view_index]; + const start = buffer_view.byte_offset; + const end = start + buffer_view.byte_length; + image.data = binary_buffer[start..end]; + } + } +} + +fn parseGltfJson(self: *Gltf, gltf_json: []const u8) !void { + const alloc = self.arena.allocator(); + + var gltf_parsed = try json.parseFromSlice(json.Value, alloc, gltf_json, .{}); + defer gltf_parsed.deinit(); + + const gltf: *json.Value = &gltf_parsed.value; + + if (gltf.object.get("asset")) |json_value| { + var asset = &self.data.asset; + + if (json_value.object.get("version")) |version| { + asset.version = version.string; + } else { + panic("Asset's version is missing.", .{}); + } + + if (json_value.object.get("generator")) |generator| { + asset.generator = generator.string; + } + + if (json_value.object.get("copyright")) |copyright| { + asset.copyright = copyright.string; + } + } + + if (gltf.object.get("nodes")) |nodes| { + self.data.nodes = try alloc.alloc(Node, nodes.array.items.len); + for (nodes.array.items, 0..) |item, index| { + const object = item.object; + + var node = Node{}; + + if (object.get("name")) |name| { + node.name = name.string; + } + + if (object.get("mesh")) |mesh| { + node.mesh = parseIndex(mesh); + } + + if (object.get("camera")) |camera_index| { + node.camera = parseIndex(camera_index); + } + + if (object.get("skin")) |skin| { + node.skin = parseIndex(skin); + } + + if (object.get("children")) |children| { + node.children = try alloc.alloc(Index, children.array.items.len); + for (children.array.items, 0..) |value, child_index| { + node.children[child_index] = parseIndex(value); + } + } + + if (object.get("rotation")) |rotation| { + for (rotation.array.items, 0..) |component, i| { + node.rotation[i] = parseFloat(f32, component); + } + } + + if (object.get("translation")) |translation| { + for (translation.array.items, 0..) |component, i| { + node.translation[i] = parseFloat(f32, component); + } + } + + if (object.get("scale")) |scale| { + for (scale.array.items, 0..) |component, i| { + node.scale[i] = parseFloat(f32, component); + } + } + + if (object.get("matrix")) |matrix| { + node.matrix = [16]f32{ + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1, + }; + + for (matrix.array.items, 0..) |component, i| { + node.matrix.?[i] = parseFloat(f32, component); + } + } + + if (object.get("extensions")) |extensions| { + if (extensions.object.get("KHR_lights_punctual")) |lights_punctual| { + if (lights_punctual.object.get("light")) |light| { + node.light = @as(Index, @intCast(light.integer)); + } + } + } + + if (object.get("extras")) |extras| { + node.extras = extras.object; + } + + self.data.nodes[index] = node; + } + } + + if (gltf.object.get("cameras")) |cameras| { + self.data.cameras = try alloc.alloc(Camera, cameras.array.items.len); + for (cameras.array.items, 0..) |item, i| { + const object = item.object; + + var camera = Camera{ + .type = undefined, + }; + + if (object.get("name")) |name| { + camera.name = name.string; + } + + if (object.get("extras")) |extras| { + camera.extras = extras.object; + } + + if (object.get("type")) |name| { + if (mem.eql(u8, name.string, "perspective")) { + if (object.get("perspective")) |perspective| { + var value = perspective.object; + + camera.type = .{ + .perspective = .{ + .aspect_ratio = if (value.get("aspectRatio")) |aspect_ratio| parseFloat( + f32, + aspect_ratio, + ) else null, + .yfov = parseFloat(f32, value.get("yfov").?), + .zfar = if (value.get("zfar")) |zfar| parseFloat( + f32, + zfar, + ) else null, + .znear = parseFloat(f32, value.get("znear").?), + }, + }; + } else { + panic("Camera's perspective value is missing.", .{}); + } + } else if (mem.eql(u8, name.string, "orthographic")) { + if (object.get("orthographic")) |orthographic| { + var value = orthographic.object; + + camera.type = .{ + .orthographic = .{ + .xmag = parseFloat(f32, value.get("xmag").?), + .ymag = parseFloat(f32, value.get("ymag").?), + .zfar = parseFloat(f32, value.get("zfar").?), + .znear = parseFloat(f32, value.get("znear").?), + }, + }; + } else { + panic("Camera's orthographic value is missing.", .{}); + } + } else { + panic( + "Camera's type must be perspective or orthographic.", + .{}, + ); + } + } + + self.data.cameras[i] = camera; + } + } + + if (gltf.object.get("skins")) |skins| { + self.data.skins = try alloc.alloc(Skin, skins.array.items.len); + for (skins.array.items, 0..) |item, i| { + const object = item.object; + + var skin = Skin{}; + + if (object.get("name")) |name| { + skin.name = name.string; + } + + if (object.get("joints")) |joints| { + skin.joints = try alloc.alloc(Index, joints.array.items.len); + for (joints.array.items, 0..) |joint, joint_index| { + skin.joints[joint_index] = parseIndex(joint); + } + } + + if (object.get("skeleton")) |skeleton| { + skin.skeleton = parseIndex(skeleton); + } + + if (object.get("inverseBindMatrices")) |inv_bind_mat4| { + skin.inverse_bind_matrices = parseIndex(inv_bind_mat4); + } + + if (object.get("extras")) |extras| { + skin.extras = extras.object; + } + + self.data.skins[i] = skin; + } + } + + if (gltf.object.get("meshes")) |meshes| { + self.data.meshes = try alloc.alloc(Mesh, meshes.array.items.len); + for (meshes.array.items, 0..) |item, i| { + const object = item.object; + + var mesh: Mesh = .{}; + + if (object.get("name")) |name| { + mesh.name = name.string; + } + + if (object.get("primitives")) |primitives| { + mesh.primitives = try alloc.alloc(Primitive, primitives.array.items.len); + for (primitives.array.items, 0..) |prim_item, prim_index| { + var primitive: Primitive = .{}; + + if (prim_item.object.get("mode")) |mode| { + primitive.mode = @as(Mode, @enumFromInt(mode.integer)); + } + + if (prim_item.object.get("indices")) |indices| { + primitive.indices = parseIndex(indices); + } + + if (prim_item.object.get("material")) |material| { + primitive.material = parseIndex(material); + } + + if (prim_item.object.get("attributes")) |attributes| { + var list = try ArrayList(Attribute).initCapacity(alloc, attributes.object.count()); + defer list.deinit(alloc); + + if (attributes.object.get("POSITION")) |position| { + list.appendAssumeCapacity(.{ + .position = parseIndex(position), + }); + } + + if (attributes.object.get("NORMAL")) |normal| { + list.appendAssumeCapacity(.{ + .normal = parseIndex(normal), + }); + } + + if (attributes.object.get("TANGENT")) |tangent| { + list.appendAssumeCapacity(.{ + .tangent = parseIndex(tangent), + }); + } + + const texcoords = [_][]const u8{ + "TEXCOORD_0", + "TEXCOORD_1", + "TEXCOORD_2", + "TEXCOORD_3", + "TEXCOORD_4", + "TEXCOORD_5", + "TEXCOORD_6", + }; + + for (texcoords) |tex_name| { + if (attributes.object.get(tex_name)) |texcoord| { + list.appendAssumeCapacity(.{ + .texcoord = parseIndex(texcoord), + }); + } + } + + const joints = [_][]const u8{ + "JOINTS_0", + "JOINTS_1", + "JOINTS_2", + "JOINTS_3", + "JOINTS_4", + "JOINTS_5", + "JOINTS_6", + }; + + for (joints) |joint_name| { + if (attributes.object.get(joint_name)) |joint| { + list.appendAssumeCapacity(.{ + .joints = parseIndex(joint), + }); + } + } + + const weights = [_][]const u8{ + "WEIGHTS_0", + "WEIGHTS_1", + "WEIGHTS_2", + "WEIGHTS_3", + "WEIGHTS_4", + "WEIGHTS_5", + "WEIGHTS_6", + }; + + for (weights) |weight_count| { + if (attributes.object.get(weight_count)) |weight| { + list.appendAssumeCapacity(.{ + .weights = parseIndex(weight), + }); + } + } + + primitive.attributes = try list.toOwnedSlice(alloc); + } + + if (prim_item.object.get("extras")) |extras| { + primitive.extras = extras.object; + } + + mesh.primitives[prim_index] = primitive; + } + } + + if (object.get("extras")) |extras| { + mesh.extras = extras.object; + } + + self.data.meshes[i] = mesh; + } + } + + if (gltf.object.get("accessors")) |accessors| { + self.data.accessors = try alloc.alloc(Accessor, accessors.array.items.len); + for (accessors.array.items, 0..) |item, i| { + const object = item.object; + + var accessor = Accessor{ + .component_type = undefined, + .type = undefined, + .count = undefined, + }; + + if (object.get("componentType")) |component_type| { + accessor.component_type = @as(ComponentType, @enumFromInt(component_type.integer)); + } else { + panic("Accessor's componentType is missing.", .{}); + } + + if (object.get("count")) |count| { + accessor.count = @as(usize, @intCast(count.integer)); + } else { + panic("Accessor's count is missing.", .{}); + } + + if (object.get("type")) |accessor_type| { + if (mem.eql(u8, accessor_type.string, "SCALAR")) { + accessor.type = .scalar; + } else if (mem.eql(u8, accessor_type.string, "VEC2")) { + accessor.type = .vec2; + } else if (mem.eql(u8, accessor_type.string, "VEC3")) { + accessor.type = .vec3; + } else if (mem.eql(u8, accessor_type.string, "VEC4")) { + accessor.type = .vec4; + } else if (mem.eql(u8, accessor_type.string, "MAT2")) { + accessor.type = .mat2x2; + } else if (mem.eql(u8, accessor_type.string, "MAT3")) { + accessor.type = .mat3x3; + } else if (mem.eql(u8, accessor_type.string, "MAT4")) { + accessor.type = .mat4x4; + } else { + panic("Accessor's type '{s}' is invalid.", .{accessor_type.string}); + } + } else { + panic("Accessor's type is missing.", .{}); + } + + if (object.get("normalized")) |normalized| { + accessor.normalized = normalized.bool; + } + + if (object.get("bufferView")) |buffer_view| { + accessor.buffer_view = parseIndex(buffer_view); + } + + if (object.get("byteOffset")) |byte_offset| { + accessor.byte_offset = @as(usize, @intCast(byte_offset.integer)); + } + + if (object.get("extras")) |extras| { + accessor.extras = extras.object; + } + + self.data.accessors[i] = accessor; + } + } + + if (gltf.object.get("bufferViews")) |buffer_views| { + self.data.buffer_views = try alloc.alloc(BufferView, buffer_views.array.items.len); + for (buffer_views.array.items, 0..) |item, i| { + const object = item.object; + + var buffer_view = BufferView{ + .buffer = undefined, + .byte_length = undefined, + }; + + if (object.get("buffer")) |buffer| { + buffer_view.buffer = parseIndex(buffer); + } + + if (object.get("byteLength")) |byte_length| { + buffer_view.byte_length = @as(usize, @intCast(byte_length.integer)); + } + + if (object.get("byteOffset")) |byte_offset| { + buffer_view.byte_offset = @as(usize, @intCast(byte_offset.integer)); + } + + if (object.get("byteStride")) |byte_stride| { + buffer_view.byte_stride = @as(usize, @intCast(byte_stride.integer)); + } + + if (object.get("target")) |target| { + buffer_view.target = @as(Target, @enumFromInt(target.integer)); + } + + if (object.get("extras")) |extras| { + buffer_view.extras = extras.object; + } + + self.data.buffer_views[i] = buffer_view; + } + } + + if (gltf.object.get("buffers")) |buffers| { + self.data.buffers = try alloc.alloc(Buffer, buffers.array.items.len); + for (buffers.array.items, 0..) |item, i| { + const object = item.object; + + var buffer = Buffer{ + .byte_length = undefined, + }; + + if (object.get("uri")) |uri| { + buffer.uri = uri.string; + } + + if (object.get("byteLength")) |byte_length| { + buffer.byte_length = @as(usize, @intCast(byte_length.integer)); + } else { + panic("Buffer's byteLength is missing.", .{}); + } + + if (object.get("extras")) |extras| { + buffer.extras = extras.object; + } + + self.data.buffers[i] = buffer; + } + } + + if (gltf.object.get("scene")) |default_scene| { + self.data.scene = parseIndex(default_scene); + } + + if (gltf.object.get("scenes")) |scenes| { + self.data.scenes = try alloc.alloc(Scene, scenes.array.items.len); + for (scenes.array.items, 0..) |item, i| { + const object = item.object; + + var scene = Scene{}; + + if (object.get("name")) |name| { + scene.name = name.string; + } + + if (object.get("nodes")) |nodes| { + scene.nodes = try alloc.alloc(Index, nodes.array.items.len); + + for (nodes.array.items, 0..) |node, node_index| { + scene.nodes.?[node_index] = parseIndex(node); + } + } + + if (object.get("extras")) |extras| { + scene.extras = extras.object; + } + + self.data.scenes[i] = scene; + } + } + + if (gltf.object.get("materials")) |materials| { + self.data.materials = try alloc.alloc(Material, materials.array.items.len); + for (materials.array.items, 0..) |item, mat_index| { + const object = item.object; + + var material = Material{}; + + if (object.get("name")) |name| { + material.name = name.string; + } + + if (object.get("pbrMetallicRoughness")) |pbrMetallicRoughness| { + var metallic_roughness: MetallicRoughness = .{}; + if (pbrMetallicRoughness.object.get("baseColorFactor")) |color_factor| { + for (color_factor.array.items, 0..) |factor, i| { + metallic_roughness.base_color_factor[i] = parseFloat(f32, factor); + } + } + + if (pbrMetallicRoughness.object.get("metallicFactor")) |factor| { + metallic_roughness.metallic_factor = parseFloat(f32, factor); + } + + if (pbrMetallicRoughness.object.get("roughnessFactor")) |factor| { + metallic_roughness.roughness_factor = parseFloat(f32, factor); + } + + if (pbrMetallicRoughness.object.get("baseColorTexture")) |texture_info| { + metallic_roughness.base_color_texture = .{ + .index = undefined, + }; + + if (texture_info.object.get("index")) |index| { + metallic_roughness.base_color_texture.?.index = parseIndex(index); + } + + if (texture_info.object.get("texCoord")) |texcoord| { + metallic_roughness.base_color_texture.?.texcoord = @as(i32, @intCast(texcoord.integer)); + } + } + + if (pbrMetallicRoughness.object.get("metallicRoughnessTexture")) |texture_info| { + metallic_roughness.metallic_roughness_texture = .{ + .index = undefined, + }; + + if (texture_info.object.get("index")) |index| { + metallic_roughness.metallic_roughness_texture.?.index = parseIndex(index); + } + + if (texture_info.object.get("texCoord")) |texcoord| { + metallic_roughness.metallic_roughness_texture.?.texcoord = @as(i32, @intCast(texcoord.integer)); + } + } + + material.metallic_roughness = metallic_roughness; + } + + if (object.get("normalTexture")) |normal_texture| { + material.normal_texture = .{ + .index = undefined, + }; + + if (normal_texture.object.get("index")) |index| { + material.normal_texture.?.index = parseIndex(index); + } + + if (normal_texture.object.get("texCoord")) |index| { + material.normal_texture.?.texcoord = @as(i32, @intCast(index.integer)); + } + + if (normal_texture.object.get("scale")) |scale| { + material.normal_texture.?.scale = parseFloat(f32, scale); + } + } + + if (object.get("emissiveTexture")) |emissive_texture| { + material.emissive_texture = .{ + .index = undefined, + }; + + if (emissive_texture.object.get("index")) |index| { + material.emissive_texture.?.index = parseIndex(index); + } + + if (emissive_texture.object.get("texCoord")) |index| { + material.emissive_texture.?.texcoord = @as(i32, @intCast(index.integer)); + } + } + + if (object.get("occlusionTexture")) |occlusion_texture| { + material.occlusion_texture = .{ + .index = undefined, + }; + + if (occlusion_texture.object.get("index")) |index| { + material.occlusion_texture.?.index = parseIndex(index); + } + + if (occlusion_texture.object.get("texCoord")) |index| { + material.occlusion_texture.?.texcoord = @as(i32, @intCast(index.integer)); + } + + if (occlusion_texture.object.get("strength")) |strength| { + material.occlusion_texture.?.strength = parseFloat(f32, strength); + } + } + + if (object.get("alphaMode")) |alpha_mode| { + if (mem.eql(u8, alpha_mode.string, "OPAQUE")) { + material.alpha_mode = .@"opaque"; + } + if (mem.eql(u8, alpha_mode.string, "MASK")) { + material.alpha_mode = .mask; + } + if (mem.eql(u8, alpha_mode.string, "BLEND")) { + material.alpha_mode = .blend; + } + } + + if (object.get("doubleSided")) |double_sided| { + material.is_double_sided = double_sided.bool; + } + + if (object.get("alphaCutoff")) |alpha_cutoff| { + material.alpha_cutoff = parseFloat(f32, alpha_cutoff); + } + + if (object.get("emissiveFactor")) |emissive_factor| { + for (emissive_factor.array.items, 0..) |factor, i| { + material.emissive_factor[i] = parseFloat(f32, factor); + } + } + + if (object.get("extensions")) |extensions| { + if (extensions.object.get("KHR_materials_emissive_strength")) |materials_emissive_strength| { + if (materials_emissive_strength.object.get("emissiveStrength")) |emissive_strength| { + material.emissive_strength = parseFloat(f32, emissive_strength); + } + } + + if (extensions.object.get("KHR_materials_ior")) |materials_ior| { + if (materials_ior.object.get("ior")) |ior| { + material.ior = parseFloat(f32, ior); + } + } + + if (extensions.object.get("KHR_materials_transmission")) |materials_transmission| { + if (materials_transmission.object.get("transmissionFactor")) |transmission_factor| { + material.transmission_factor = parseFloat(f32, transmission_factor); + } + + if (materials_transmission.object.get("transmissionTexture")) |transmission_texture| { + material.transmission_texture = .{ + .index = undefined, + }; + + if (transmission_texture.object.get("index")) |index| { + material.transmission_texture.?.index = parseIndex(index); + } + + if (transmission_texture.object.get("texCoord")) |index| { + material.transmission_texture.?.texcoord = @as(i32, @intCast(index.integer)); + } + } + } + + if (extensions.object.get("KHR_materials_volume")) |materials_volume| { + if (materials_volume.object.get("thicknessFactor")) |thickness_factor| { + material.thickness_factor = parseFloat(f32, thickness_factor); + } + + if (materials_volume.object.get("thicknessTexture")) |thickness_texture| { + material.thickness_texture = .{ + .index = undefined, + }; + + if (thickness_texture.object.get("index")) |index| { + material.thickness_texture.?.index = parseIndex(index); + } + + if (thickness_texture.object.get("texCoord")) |index| { + material.thickness_texture.?.texcoord = @as(i32, @intCast(index.integer)); + } + } + + if (materials_volume.object.get("attenuationDistance")) |attenuation_distance| { + material.attenuation_distance = parseFloat(f32, attenuation_distance); + } + + if (materials_volume.object.get("attenuationColor")) |attenuation_color| { + for (&material.attenuation_color, attenuation_color.array.items) |*dst, src| { + dst.* = parseFloat(f32, src); + } + } + } + + if (extensions.object.get("KHR_materials_dispersion")) |materials_dispersion| { + if (materials_dispersion.object.get("dispersion")) |dispersion| { + material.dispersion = parseFloat(f32, dispersion); + } + } + } + + if (object.get("extras")) |extras| { + material.extras = extras.object; + } + + self.data.materials[mat_index] = material; + } + } + + if (gltf.object.get("textures")) |textures| { + self.data.textures = try alloc.alloc(Texture, textures.array.items.len); + for (textures.array.items, 0..) |item, i| { + var texture = Texture{}; + + if (item.object.get("source")) |source| { + texture.source = parseIndex(source); + } + + if (item.object.get("sampler")) |sampler| { + texture.sampler = parseIndex(sampler); + } + + if (item.object.get("extensions")) |extension| { + if (extension.object.get("EXT_texture_webp")) |webp| { + if (webp.object.get("source")) |source| { + texture.extensions.EXT_texture_webp = .{ .source = parseIndex(source) }; + } + } + } + + if (item.object.get("extras")) |extras| { + texture.extras = extras.object; + } + + self.data.textures[i] = texture; + } + } + + if (gltf.object.get("animations")) |animations| { + self.data.animations = try alloc.alloc(Animation, animations.array.items.len); + for (animations.array.items, 0..) |item, i| { + const object = item.object; + + var animation = Animation{}; + + if (item.object.get("name")) |name| { + animation.name = name.string; + } + + if (object.get("samplers")) |samplers| { + animation.samplers = try alloc.alloc(AnimationSampler, samplers.array.items.len); + for (samplers.array.items, 0..) |sampler_item, smapler_index| { + var sampler: AnimationSampler = .{ + .input = undefined, + .output = undefined, + }; + + if (sampler_item.object.get("input")) |input| { + sampler.input = parseIndex(input); + } else { + panic("Animation sampler's input is missing.", .{}); + } + + if (sampler_item.object.get("output")) |output| { + sampler.output = parseIndex(output); + } else { + panic("Animation sampler's output is missing.", .{}); + } + + if (sampler_item.object.get("interpolation")) |interpolation| { + if (mem.eql(u8, interpolation.string, "LINEAR")) { + sampler.interpolation = .linear; + } + + if (mem.eql(u8, interpolation.string, "STEP")) { + sampler.interpolation = .step; + } + + if (mem.eql(u8, interpolation.string, "CUBICSPLINE")) { + sampler.interpolation = .cubicspline; + } + } + + if (sampler_item.object.get("extras")) |extras| { + sampler.extras = extras.object; + } + + animation.samplers[smapler_index] = sampler; + } + } + + if (object.get("channels")) |channels| { + animation.channels = try alloc.alloc(Channel, channels.array.items.len); + for (channels.array.items, 0..) |channel_item, channel_index| { + var channel: Channel = .{ .sampler = undefined, .target = .{ + .node = undefined, + .property = undefined, + } }; + + if (channel_item.object.get("sampler")) |sampler_index| { + channel.sampler = parseIndex(sampler_index); + } else { + panic("Animation channel's sampler is missing.", .{}); + } + + if (channel_item.object.get("target")) |target_item| { + if (target_item.object.get("node")) |node_index| { + channel.target.node = parseIndex(node_index); + } else { + panic("Animation target's node is missing.", .{}); + } + + if (target_item.object.get("path")) |path| { + if (mem.eql(u8, path.string, "translation")) { + channel.target.property = .translation; + } else if (mem.eql(u8, path.string, "rotation")) { + channel.target.property = .rotation; + } else if (mem.eql(u8, path.string, "scale")) { + channel.target.property = .scale; + } else if (mem.eql(u8, path.string, "weights")) { + channel.target.property = .weights; + } else { + panic("Animation path/property is invalid.", .{}); + } + } else { + panic("Animation target's path/property is missing.", .{}); + } + } else { + panic("Animation channel's target is missing.", .{}); + } + + if (channel_item.object.get("extras")) |extras| { + channel.extras = extras.object; + } + + animation.channels[channel_index] = channel; + } + } + + if (object.get("extras")) |extras| { + animation.extras = extras.object; + } + + self.data.animations[i] = animation; + } + } + + if (gltf.object.get("samplers")) |samplers| { + self.data.samplers = try alloc.alloc(TextureSampler, samplers.array.items.len); + for (samplers.array.items, 0..) |item, i| { + const object = item.object; + var sampler = TextureSampler{}; + + if (object.get("magFilter")) |mag_filter| { + sampler.mag_filter = @as(MagFilter, @enumFromInt(mag_filter.integer)); + } + + if (object.get("minFilter")) |min_filter| { + sampler.min_filter = @as(MinFilter, @enumFromInt(min_filter.integer)); + } + + if (object.get("wrapS")) |wrap_s| { + sampler.wrap_s = @as(WrapMode, @enumFromInt(wrap_s.integer)); + } + + if (object.get("wrapt")) |wrap_t| { + sampler.wrap_t = @as(WrapMode, @enumFromInt(wrap_t.integer)); + } + + if (object.get("extras")) |extras| { + sampler.extras = extras.object; + } + + self.data.samplers[i] = sampler; + } + } + + if (gltf.object.get("images")) |images| { + self.data.images = try alloc.alloc(Image, images.array.items.len); + for (images.array.items, 0..) |item, i| { + const object = item.object; + var image = Image{}; + + if (object.get("name")) |name| { + image.name = name.string; + } + + if (object.get("uri")) |uri| { + image.uri = uri.string; + } + + if (object.get("mimeType")) |mime_type| { + image.mime_type = mime_type.string; + } + + if (object.get("bufferView")) |buffer_view| { + image.buffer_view = parseIndex(buffer_view); + } + + if (object.get("extras")) |extras| { + image.extras = extras.object; + } + + self.data.images[i] = image; + } + } + + if (gltf.object.get("extensions")) |extensions| { + if (extensions.object.get("KHR_lights_punctual")) |lights_punctual| { + if (lights_punctual.object.get("lights")) |lights| { + self.data.lights = try alloc.alloc(Light, lights.array.items.len); + for (lights.array.items, 0..) |item, light_index| { + const object: json.ObjectMap = item.object; + + var light = Light{ + .type = undefined, + .range = math.inf(f32), + .spot = null, + }; + + if (object.get("name")) |name| { + light.name = name.string; + } + + if (object.get("color")) |color| { + for (color.array.items, 0..) |component, i| { + light.color[i] = parseFloat(f32, component); + } + } + + if (object.get("intensity")) |intensity| { + light.intensity = parseFloat(f32, intensity); + } + + if (object.get("type")) |@"type"| { + if (std.meta.stringToEnum(LightType, @"type".string)) |light_type| { + light.type = light_type; + } else panic("Light's type invalid", .{}); + } + + if (object.get("range")) |range| { + light.range = parseFloat(f32, range); + } + + if (object.get("spot")) |spot| { + light.spot = .{}; + + if (spot.object.get("innerConeAngle")) |inner_cone_angle| { + light.spot.?.inner_cone_angle = parseFloat(f32, inner_cone_angle); + } + + if (spot.object.get("outerConeAngle")) |outer_cone_angle| { + light.spot.?.outer_cone_angle = parseFloat(f32, outer_cone_angle); + } + } + + if (object.get("extras")) |extras| { + light.extras = extras.object; + } + + self.data.lights[light_index] = light; + } + } + } + } + + // For each node, fill parent indexes. + for (self.data.scenes) |scene| { + if (scene.nodes) |nodes| { + for (nodes) |node_index| { + const node = &self.data.nodes[node_index]; + fillParents(&self.data, node, node_index); + } + } + } +} + +// In 'gltf' files, often values are array indexes; +// this function casts Integer to 'usize'. +fn parseIndex(component: json.Value) usize { + return switch (component) { + .integer => |val| @as(usize, @intCast(val)), + else => panic( + "The json component '{any}' is not valid number.", + .{component}, + ), + }; +} + +// Exact values could be interpreted as Integer, often we want only +// floating numbers. +fn parseFloat(comptime T: type, component: json.Value) T { + const type_info = @typeInfo(T); + if (type_info != .float) { + panic( + "Given type '{any}' is not a floating number.", + .{type_info}, + ); + } + + return switch (component) { + .float => |val| @as(T, @floatCast(val)), + .integer => |val| @as(T, @floatFromInt(val)), + else => panic( + "The json component '{any}' is not a number.", + .{component}, + ), + }; +} + +fn fillParents(data: *Data, node: *Node, parent_index: Index) void { + for (node.children) |child_index| { + var child_node = &data.nodes[child_index]; + child_node.parent = parent_index; + fillParents(data, child_node, child_index); + } +} + +test "gltf.parseGlb" { + const allocator = std.testing.allocator; + const expectEqualSlices = std.testing.expectEqualSlices; + + // This is the '.glb' file. + const glb_buf = try std.fs.cwd().readFileAllocOptions(allocator, "test-samples/box_binary/Box.glb", 512_000, null, .@"4", null); + defer allocator.free(glb_buf); + + var gltf = Gltf.init(allocator); + defer gltf.deinit(); + + try expectEqualSlices(u8, gltf.data.asset.version, "Undefined"); + + try gltf.parseGlb(glb_buf); + + const mesh = gltf.data.meshes[0]; + for (mesh.primitives) |primitive| { + for (primitive.attributes) |attribute| { + switch (attribute) { + .position => |accessor_index| { + const accessor = gltf.data.accessors[accessor_index]; + const tmp = try gltf.getDataFromBufferView(f32, allocator, accessor, gltf.glb_binary.?); + defer allocator.free(tmp); + + try expectEqualSlices(f32, tmp, &[72]f32{ + // zig fmt: off + -0.50, -0.50, 0.50, 0.50, -0.50, 0.50, -0.50, 0.50, 0.50, + 0.50, 0.50, 0.50, 0.50, -0.50, 0.50, -0.50, -0.50, 0.50, + 0.50, -0.50, -0.50, -0.50, -0.50, -0.50, 0.50, 0.50, 0.50, + 0.50, -0.50, 0.50, 0.50, 0.50, -0.50, 0.50, -0.50, -0.50, + -0.50, 0.50, 0.50, 0.50, 0.50, 0.50, -0.50, 0.50, -0.50, + 0.50, 0.50, -0.50, -0.50, -0.50, 0.50, -0.50, 0.50, 0.50, + -0.50, -0.50, -0.50, -0.50, 0.50, -0.50, -0.50, -0.50, -0.50, + -0.50, 0.50, -0.50, 0.50, -0.50, -0.50, 0.50, 0.50, -0.50, + }); + }, + else => {}, + } + } + } +} + +test "gltf.parseGlbTextured" { + const allocator = std.testing.allocator; + const expectEqualSlices = std.testing.expectEqualSlices; + + // This is the '.glb' file. + const glb_buf = try std.fs.cwd().readFileAllocOptions( + allocator, + "test-samples/box_binary_textured/BoxTextured.glb", + 512_000, + null, + .@"4", + null + ); + defer allocator.free(glb_buf); + + var gltf = Gltf.init(allocator); + defer gltf.deinit(); + + try gltf.parseGlb(glb_buf); + + const test_to_check = try std.fs.cwd().readFileAlloc( + allocator, + "test-samples/box_binary_textured/test.png", + 512_000 + ); + defer allocator.free(test_to_check); + + const data = gltf.data.images[0].data.?; + try expectEqualSlices(u8, test_to_check, data); +} + +test "gltf.parse" { + const allocator = std.testing.allocator; + const expectEqualSlices = std.testing.expectEqualSlices; + const expectEqual = std.testing.expectEqual; + + // This is the '.gltf' file, a json specifying what information is in the + // model and how to retrieve it inside binary file(s). + const buf = try std.fs.cwd().readFileAllocOptions( + allocator, + "test-samples/rigged_simple/RiggedSimple.gltf", + 512_000, + null, + .@"4", + null + ); + defer allocator.free(buf); + + var gltf = Gltf.init(allocator); + defer gltf.deinit(); + + try expectEqualSlices(u8, gltf.data.asset.version, "Undefined"); + + try gltf.parse(buf); + + try expectEqualSlices(u8, gltf.data.asset.version, "2.0"); + try expectEqualSlices(u8, gltf.data.asset.generator.?, "COLLADA2GLTF"); + + try expectEqual(gltf.data.scene, 0); + + // Nodes. + const nodes = gltf.data.nodes; + try expectEqualSlices(u8, nodes[0].name orelse "", "Z_UP"); + try expectEqualSlices(usize, nodes[0].children, &[_]usize{1}); + try expectEqualSlices(u8, nodes[2].name orelse "", "Cylinder"); + try expectEqual(nodes[2].skin, 0); + + try expectEqual(gltf.data.buffers.len > 0, true); + + // Skin + const skin = gltf.data.skins[0]; + try expectEqualSlices(u8, skin.name.?, "Armature"); +} + +test "gltf.parse (cameras)" { + const allocator = std.testing.allocator; + const expectEqual = std.testing.expectEqual; + + const buf = try std.fs.cwd().readFileAllocOptions( + allocator, + "test-samples/cameras/Cameras.gltf", + 512_000, + null, + .@"4", + null + ); + defer allocator.free(buf); + + var gltf = Gltf.init(allocator); + defer gltf.deinit(); + + try gltf.parse(buf); + + try expectEqual(gltf.data.nodes[1].camera, 0); + try expectEqual(gltf.data.nodes[2].camera, 1); + + const camera_0 = gltf.data.cameras[0]; + try expectEqual(camera_0.type.perspective, Camera.Perspective{ + .aspect_ratio = 1.0, + .yfov = 0.7, + .zfar = 100, + .znear = 0.01, + }); + + const camera_1 = gltf.data.cameras[1]; + try expectEqual(camera_1.type.orthographic, Camera.Orthographic{ + .xmag = 1.0, + .ymag = 1.0, + .zfar = 100, + .znear = 0.01, + }); +} + +test "gltf.getDataFromBufferView" { + const allocator = std.testing.allocator; + const expectEqualSlices = std.testing.expectEqualSlices; + + const buf = try std.fs.cwd().readFileAllocOptions( + allocator, + "test-samples/box/Box.gltf", + 512_000, + null, + .@"4", + null + ); + defer allocator.free(buf); + + // This is the '.bin' file containing all the gltf underneath data. + const binary = try std.fs.cwd().readFileAllocOptions( + allocator, + "test-samples/box/Box0.bin", + 5_000_000, + null, + // From gltf spec, data from BufferView should be 4 bytes aligned. + .@"4", + null, + ); + defer allocator.free(binary); + + var gltf = Gltf.init(allocator); + defer gltf.deinit(); + + try gltf.parse(buf); + + const mesh = gltf.data.meshes[0]; + for (mesh.primitives) |primitive| { + for (primitive.attributes) |attribute| { + switch (attribute) { + .position => |accessor_index| { + const accessor = gltf.data.accessors[accessor_index]; + const tmp = try gltf.getDataFromBufferView(f32, allocator, accessor, binary); + defer allocator.free(tmp); + + try expectEqualSlices(f32, tmp, &[72]f32{ + // zig fmt: off + -0.50, -0.50, 0.50, 0.50, -0.50, 0.50, -0.50, 0.50, 0.50, + 0.50, 0.50, 0.50, 0.50, -0.50, 0.50, -0.50, -0.50, 0.50, + 0.50, -0.50, -0.50, -0.50, -0.50, -0.50, 0.50, 0.50, 0.50, + 0.50, -0.50, 0.50, 0.50, 0.50, -0.50, 0.50, -0.50, -0.50, + -0.50, 0.50, 0.50, 0.50, 0.50, 0.50, -0.50, 0.50, -0.50, + 0.50, 0.50, -0.50, -0.50, -0.50, 0.50, -0.50, 0.50, 0.50, + -0.50, -0.50, -0.50, -0.50, 0.50, -0.50, -0.50, -0.50, -0.50, + -0.50, 0.50, -0.50, 0.50, -0.50, -0.50, 0.50, 0.50, -0.50, + }); + }, + else => {}, + } + } + } +} + +test "gltf.parse (lights)" { + const allocator = std.testing.allocator; + const expect = std.testing.expect; + const expectEqual = std.testing.expectEqual; + + const buf = try std.fs.cwd().readFileAllocOptions( + allocator, + "test-samples/khr_lights_punctual/Lights.gltf", + 512_000, + null, + .@"4", + null + ); + defer allocator.free(buf); + + var gltf = Gltf.init(allocator); + defer gltf.deinit(); + + try gltf.parse(buf); + + try expectEqual(@as(usize, 3), gltf.data.lights.len); + + try expect(gltf.data.lights[0].name != null); + try expect(std.mem.eql(u8, "Light", gltf.data.lights[0].name.?)); + try expectEqual([3]f32 { 1, 1, 1 }, gltf.data.lights[0].color); + try expectEqual(@as(f32, 1000), gltf.data.lights[0].intensity); + try expectEqual(LightType.point, gltf.data.lights[0].type); + + try expect(gltf.data.lights[1].name != null); + try expect(std.mem.eql(u8, "Light.001", gltf.data.lights[1].name.?)); + try expectEqual([3]f32 { 1, 1, 1 }, gltf.data.lights[1].color); + try expectEqual(@as(f32, 1000), gltf.data.lights[1].intensity); + try expectEqual(LightType.spot, gltf.data.lights[1].type); + + try expect(gltf.data.lights[1].spot != null); + try expectEqual(@as(f32, 0), gltf.data.lights[1].spot.?.inner_cone_angle); + try expectEqual(@as(f32, 1), gltf.data.lights[1].spot.?.outer_cone_angle); + + try expect(gltf.data.lights[2].name != null); + try expect(std.mem.eql(u8, "Light.002", gltf.data.lights[2].name.?)); + try expectEqual([3]f32 { 1, 1, 1 }, gltf.data.lights[2].color); + try expectEqual(@as(f32, 1000), gltf.data.lights[2].intensity); + try expectEqual(LightType.directional, gltf.data.lights[2].type); + + try expect(gltf.data.nodes[0].light != null); + try expectEqual(@as(?Index, 0), gltf.data.nodes[0].light); +} + +test "gltf refAllDecls" { + std.testing.refAllDecls(@This()); +} diff --git a/lib/zgltf/src/zgltf.zig b/lib/zgltf/src/zgltf.zig new file mode 100644 index 0000000..54f727b --- /dev/null +++ b/lib/zgltf/src/zgltf.zig @@ -0,0 +1,5 @@ +pub const Gltf = @import("Gltf.zig"); + +test { + @import("std").testing.refAllDecls(@This()); +} diff --git a/lib/zphysics/.gitignore b/lib/zphysics/.gitignore new file mode 100644 index 0000000..7b1675a --- /dev/null +++ b/lib/zphysics/.gitignore @@ -0,0 +1,6 @@ +# Ignore some special directories +.zig-cache +zig-out + +# Ignore some special OS files +*.DS_Store diff --git a/lib/zphysics/README.md b/lib/zphysics/README.md index 1ff362e..51c750c 100644 --- a/lib/zphysics/README.md +++ b/lib/zphysics/README.md @@ -1,19 +1,12 @@ -# zphysics v0.1.0 - Build package, [C API](libs/JoltC) and bindings for Jolt Physics +# [zphysics](https://github.com/zig-gamedev/zphysics) -[Jolt Physics](https://github.com/jrouwe/JoltPhysics) is a fast and modern physics library written in C++. +Zig build package, bindings and [C API](libs/JoltC) for [Jolt Physics](https://github.com/jrouwe/JoltPhysics). -This project aims to provide high-performance, consistent and roboust [C API](libs/JoltC) and Zig API for Jolt. - -For a simple sample applications please see [here](https://github.com/michal-z/zig-gamedev/tree/main/samples/physics_test_wgpu/src/physics_test_wgpu.zig). +For a simple sample applications please see [here](https://github.com/zig-gamedev/zig-gamedev/tree/main/samples/physics_test_wgpu/src/physics_test_wgpu.zig). ## Getting started -Copy `zphysics` to a subdirectory of your project and add the following to your `build.zig.zon` .dependencies: -```zig - .zphysics = .{ .path = "libs/zphysics" }, -``` - -Then in your `build.zig` add: +Example `build.zig`: ```zig pub fn build(b: *std.Build) void { const exe = b.addExecutable(.{ ... }); @@ -106,3 +99,20 @@ pub fn main() !void { } } ``` + +## Usage in a shared library + +The `joltc` artifact can be built as a shared library by specifying the `shared` build option: + +``` + const zphysics = b.dependency("zphysics", .{ + .shared = true, + }); +``` + +If your zig module uses `zphysics` and is itself part of a shared library that is reloaded at runtime, then some additional steps are required: + +- Before unloading the shared library, call `preUnload` to export the internal global state +- After reloading the shared library, call `postReload` to import the internal state and update allocator vtables + +If you use `registerTrace` or `registerAssertFailed`, these must also be called again to update their function pointers. diff --git a/lib/zphysics/build.zig b/lib/zphysics/build.zig index 4d78e27..69d922d 100644 --- a/lib/zphysics/build.zig +++ b/lib/zphysics/build.zig @@ -1,9 +1,19 @@ const std = @import("std"); +fn addMacros(module: *std.Build.Module, options: anytype) void { + if (options.enable_cross_platform_determinism) + module.addCMacro("JPH_CROSS_PLATFORM_DETERMINISTIC", ""); + if (options.enable_debug_renderer) + module.addCMacro("JPH_DEBUG_RENDERER", ""); + if (options.use_double_precision) + module.addCMacro("JPH_DOUBLE_PRECISION", ""); + if (options.enable_asserts) + module.addCMacro("JPH_ENABLE_ASSERTS", ""); +} + pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const target = b.standardTargetOptions(.{}); - const static_build = b.option(bool, "static_build", "builds backlog dependencies for static linking") orelse false; const options = .{ .use_double_precision = b.option( @@ -26,8 +36,24 @@ pub fn build(b: *std.Build) void { "enable_debug_renderer", "Enable debug renderer", ) orelse false, + .shared = b.option( + bool, + "shared", + "Build JoltC as shared lib", + ) orelse false, + .no_exceptions = b.option( + bool, + "no_exceptions", + "Disable C++ Exceptions", + ) orelse true, }; + const user_extensions = b.option( + []const std.Build.LazyPath, + "user_extensions", + "List of user source files to add to the joltc library", + ) orelse &.{}; + const options_step = b.addOptions(); inline for (std.meta.fields(@TypeOf(options))) |field| { options_step.addOption(field.type, field.name, @field(options, field.name)); @@ -43,24 +69,38 @@ pub fn build(b: *std.Build) void { }); zjolt.addIncludePath(b.path("libs/JoltC")); - const joltc = if (static_build) b.addStaticLibrary(.{ + const joltc = b.addLibrary(.{ .name = "joltc", - .target = target, - .optimize = optimize, - }) else b.addSharedLibrary(.{ - .name = "joltc", - .target = target, - .optimize = optimize, + .linkage = if (options.shared) .dynamic else .static, + .root_module = b.createModule(.{ + .target = target, + .optimize = optimize, + }), }); + + if (options.shared and target.result.os.tag == .windows) + joltc.root_module.addCMacro("JPC_API", "extern __declspec(dllexport)"); + b.installArtifact(joltc); joltc.addIncludePath(b.path("libs")); joltc.addIncludePath(b.path("libs/JoltC")); joltc.linkLibC(); - if (target.result.abi != .msvc) + if (target.result.abi != .msvc) { joltc.linkLibCpp(); + } else { + joltc.linkSystemLibrary("advapi32"); + } const src_dir = "libs/Jolt"; + const c_flags = &.{ + "-std=c++17", + if (options.no_exceptions) "-fno-exceptions" else "", + "-fno-access-control", + "-fno-sanitize=undefined", + }; + + addMacros(joltc.root_module, options); joltc.addCSourceFiles(.{ .files = &.{ "libs/JoltC/JoltPhysicsC.cpp", @@ -69,6 +109,7 @@ pub fn build(b: *std.Build) void { src_dir ++ "/Core/Color.cpp", src_dir ++ "/Core/Factory.cpp", src_dir ++ "/Core/IssueReporting.cpp", + src_dir ++ "/Core/JobSystemSingleThreaded.cpp", src_dir ++ "/Core/JobSystemThreadPool.cpp", src_dir ++ "/Core/JobSystemWithBarrier.cpp", src_dir ++ "/Core/LinearCurve.cpp", @@ -82,7 +123,6 @@ pub fn build(b: *std.Build) void { src_dir ++ "/Geometry/ConvexHullBuilder2D.cpp", src_dir ++ "/Geometry/Indexify.cpp", src_dir ++ "/Geometry/OrientedBox.cpp", - src_dir ++ "/Math/UVec4.cpp", src_dir ++ "/Math/Vec3.cpp", src_dir ++ "/ObjectStream/ObjectStream.cpp", src_dir ++ "/ObjectStream/ObjectStreamBinaryIn.cpp", @@ -94,7 +134,6 @@ pub fn build(b: *std.Build) void { src_dir ++ "/ObjectStream/SerializableObject.cpp", src_dir ++ "/ObjectStream/TypeDeclarations.cpp", src_dir ++ "/Physics/Body/Body.cpp", - src_dir ++ "/Physics/Body/BodyAccess.cpp", src_dir ++ "/Physics/Body/BodyCreationSettings.cpp", src_dir ++ "/Physics/Body/BodyInterface.cpp", src_dir ++ "/Physics/Body/BodyManager.cpp", @@ -113,6 +152,7 @@ pub fn build(b: *std.Build) void { src_dir ++ "/Physics/Collision/CollideSphereVsTriangles.cpp", src_dir ++ "/Physics/Collision/CollisionDispatch.cpp", src_dir ++ "/Physics/Collision/CollisionGroup.cpp", + src_dir ++ "/Physics/Collision/EstimateCollisionResponse.cpp", src_dir ++ "/Physics/Collision/GroupFilter.cpp", src_dir ++ "/Physics/Collision/GroupFilterTable.cpp", src_dir ++ "/Physics/Collision/ManifoldBetweenTwoFaces.cpp", @@ -127,16 +167,19 @@ pub fn build(b: *std.Build) void { src_dir ++ "/Physics/Collision/Shape/ConvexShape.cpp", src_dir ++ "/Physics/Collision/Shape/CylinderShape.cpp", src_dir ++ "/Physics/Collision/Shape/DecoratedShape.cpp", + src_dir ++ "/Physics/Collision/Shape/EmptyShape.cpp", src_dir ++ "/Physics/Collision/Shape/HeightFieldShape.cpp", src_dir ++ "/Physics/Collision/Shape/MeshShape.cpp", src_dir ++ "/Physics/Collision/Shape/MutableCompoundShape.cpp", src_dir ++ "/Physics/Collision/Shape/OffsetCenterOfMassShape.cpp", + src_dir ++ "/Physics/Collision/Shape/PlaneShape.cpp", src_dir ++ "/Physics/Collision/Shape/RotatedTranslatedShape.cpp", src_dir ++ "/Physics/Collision/Shape/ScaledShape.cpp", src_dir ++ "/Physics/Collision/Shape/Shape.cpp", src_dir ++ "/Physics/Collision/Shape/SphereShape.cpp", src_dir ++ "/Physics/Collision/Shape/StaticCompoundShape.cpp", src_dir ++ "/Physics/Collision/Shape/TaperedCapsuleShape.cpp", + src_dir ++ "/Physics/Collision/Shape/TaperedCylinderShape.cpp", src_dir ++ "/Physics/Collision/Shape/TriangleShape.cpp", src_dir ++ "/Physics/Collision/TransformedShape.cpp", src_dir ++ "/Physics/Constraints/ConeConstraint.cpp", @@ -152,21 +195,26 @@ pub fn build(b: *std.Build) void { src_dir ++ "/Physics/Constraints/PathConstraintPath.cpp", src_dir ++ "/Physics/Constraints/PathConstraintPathHermite.cpp", src_dir ++ "/Physics/Constraints/PointConstraint.cpp", + src_dir ++ "/Physics/Constraints/PulleyConstraint.cpp", src_dir ++ "/Physics/Constraints/RackAndPinionConstraint.cpp", src_dir ++ "/Physics/Constraints/SixDOFConstraint.cpp", src_dir ++ "/Physics/Constraints/SliderConstraint.cpp", + src_dir ++ "/Physics/Constraints/SpringSettings.cpp", src_dir ++ "/Physics/Constraints/SwingTwistConstraint.cpp", src_dir ++ "/Physics/Constraints/TwoBodyConstraint.cpp", - src_dir ++ "/Physics/Constraints/PulleyConstraint.cpp", src_dir ++ "/Physics/DeterminismLog.cpp", src_dir ++ "/Physics/IslandBuilder.cpp", src_dir ++ "/Physics/LargeIslandSplitter.cpp", src_dir ++ "/Physics/PhysicsScene.cpp", src_dir ++ "/Physics/PhysicsSystem.cpp", src_dir ++ "/Physics/PhysicsUpdateContext.cpp", - src_dir ++ "/Physics/PhysicsLock.cpp", src_dir ++ "/Physics/Ragdoll/Ragdoll.cpp", + src_dir ++ "/Physics/SoftBody/SoftBodyCreationSettings.cpp", + src_dir ++ "/Physics/SoftBody/SoftBodyMotionProperties.cpp", + src_dir ++ "/Physics/SoftBody/SoftBodyShape.cpp", + src_dir ++ "/Physics/SoftBody/SoftBodySharedSettings.cpp", src_dir ++ "/Physics/StateRecorderImpl.cpp", + src_dir ++ "/Physics/Vehicle/MotorcycleController.cpp", src_dir ++ "/Physics/Vehicle/TrackedVehicleController.cpp", src_dir ++ "/Physics/Vehicle/VehicleAntiRollBar.cpp", src_dir ++ "/Physics/Vehicle/VehicleCollisionTester.cpp", @@ -178,43 +226,38 @@ pub fn build(b: *std.Build) void { src_dir ++ "/Physics/Vehicle/VehicleTransmission.cpp", src_dir ++ "/Physics/Vehicle/Wheel.cpp", src_dir ++ "/Physics/Vehicle/WheeledVehicleController.cpp", - src_dir ++ "/Physics/Vehicle/MotorcycleController.cpp", src_dir ++ "/RegisterTypes.cpp", src_dir ++ "/Renderer/DebugRenderer.cpp", src_dir ++ "/Renderer/DebugRendererPlayback.cpp", src_dir ++ "/Renderer/DebugRendererRecorder.cpp", + src_dir ++ "/Renderer/DebugRendererSimple.cpp", src_dir ++ "/Skeleton/SkeletalAnimation.cpp", src_dir ++ "/Skeleton/Skeleton.cpp", src_dir ++ "/Skeleton/SkeletonMapper.cpp", src_dir ++ "/Skeleton/SkeletonPose.cpp", - src_dir ++ "/TriangleGrouper/TriangleGrouperClosestCentroid.cpp", - src_dir ++ "/TriangleGrouper/TriangleGrouperMorton.cpp", src_dir ++ "/TriangleSplitter/TriangleSplitter.cpp", src_dir ++ "/TriangleSplitter/TriangleSplitterBinning.cpp", - src_dir ++ "/TriangleSplitter/TriangleSplitterFixedLeafSize.cpp", - src_dir ++ "/TriangleSplitter/TriangleSplitterLongestAxis.cpp", src_dir ++ "/TriangleSplitter/TriangleSplitterMean.cpp", - src_dir ++ "/TriangleSplitter/TriangleSplitterMorton.cpp", - }, - .flags = &.{ - "-std=c++17", - if (@import("builtin").abi != .msvc) "-DJPH_COMPILER_MINGW" else "", - if (options.enable_cross_platform_determinism) "-DJPH_CROSS_PLATFORM_DETERMINISTIC" else "", - if (options.enable_debug_renderer) "-DJPH_DEBUG_RENDERER" else "", - if (options.use_double_precision) "-DJPH_DOUBLE_PRECISION" else "", - if (options.enable_asserts) "-DJPH_ENABLE_ASSERTS" else "", - "-fno-access-control", - "-fno-sanitize=undefined", }, + .flags = c_flags, }); + for (user_extensions) |user_extension| { + joltc.addCSourceFile(.{ + .file = user_extension, + .flags = c_flags, + }); + } + const test_step = b.step("test", "Run zphysics tests"); const tests = b.addTest(.{ .name = "zphysics-tests", - .root_source_file = b.path("src/zphysics.zig"), - .target = target, - .optimize = optimize, + .root_module = b.createModule(.{ + .root_source_file = b.path("src/zphysics.zig"), + .target = target, + .optimize = optimize, + }), }); b.installArtifact(tests); @@ -223,18 +266,17 @@ pub fn build(b: *std.Build) void { tests.want_lto = false; } + addMacros(tests.root_module, options); tests.addCSourceFile(.{ .file = b.path("libs/JoltC/JoltPhysicsC_Tests.c"), .flags = &.{ - if (@import("builtin").abi != .msvc) "-DJPH_COMPILER_MINGW" else "", - if (options.enable_cross_platform_determinism) "-DJPH_CROSS_PLATFORM_DETERMINISTIC" else "", - if (options.enable_debug_renderer) "-DJPH_DEBUG_RENDERER" else "", - if (options.use_double_precision) "-DJPH_DOUBLE_PRECISION" else "", - if (options.enable_asserts) "-DJPH_ENABLE_ASSERTS" else "", "-fno-sanitize=undefined", }, }); + if (b.option(bool, "verbose", "Print verbose test debug output to stderr") orelse false) + tests.root_module.addCMacro("PRINT_OUTPUT", ""); + tests.root_module.addImport("zphysics_options", options_module); tests.addIncludePath(b.path("libs/JoltC")); tests.linkLibrary(joltc); diff --git a/lib/zphysics/build.zig.zon b/lib/zphysics/build.zig.zon index 1203156..2011316 100644 --- a/lib/zphysics/build.zig.zon +++ b/lib/zphysics/build.zig.zon @@ -1,12 +1,14 @@ .{ .name = .zphysics, - .version = "0.1.0", + .fingerprint = 0x1def6aac00c4909d, + .version = "0.2.0-dev", + .minimum_zig_version = "0.15.1", .paths = .{ "build.zig", "build.zig.zon", "libs", "src", "README.md", + "LICENSE", }, - .fingerprint = 0x1def6aace4359182, } diff --git a/lib/zphysics/libs/Jolt/AABBTree/AABBTreeBuilder.cpp b/lib/zphysics/libs/Jolt/AABBTree/AABBTreeBuilder.cpp index f62e609..4024132 100644 --- a/lib/zphysics/libs/Jolt/AABBTree/AABBTreeBuilder.cpp +++ b/lib/zphysics/libs/Jolt/AABBTree/AABBTreeBuilder.cpp @@ -8,84 +8,72 @@ JPH_NAMESPACE_BEGIN -AABBTreeBuilder::Node::Node() -{ - mChild[0] = nullptr; - mChild[1] = nullptr; -} - -AABBTreeBuilder::Node::~Node() -{ - delete mChild[0]; - delete mChild[1]; -} - -uint AABBTreeBuilder::Node::GetMinDepth() const +uint AABBTreeBuilder::Node::GetMinDepth(const Array &inNodes) const { if (HasChildren()) { - uint left = mChild[0]->GetMinDepth(); - uint right = mChild[1]->GetMinDepth(); + uint left = inNodes[mChild[0]].GetMinDepth(inNodes); + uint right = inNodes[mChild[1]].GetMinDepth(inNodes); return min(left, right) + 1; } else return 1; } -uint AABBTreeBuilder::Node::GetMaxDepth() const +uint AABBTreeBuilder::Node::GetMaxDepth(const Array &inNodes) const { if (HasChildren()) { - uint left = mChild[0]->GetMaxDepth(); - uint right = mChild[1]->GetMaxDepth(); + uint left = inNodes[mChild[0]].GetMaxDepth(inNodes); + uint right = inNodes[mChild[1]].GetMaxDepth(inNodes); return max(left, right) + 1; } else return 1; } -uint AABBTreeBuilder::Node::GetNodeCount() const +uint AABBTreeBuilder::Node::GetNodeCount(const Array &inNodes) const { if (HasChildren()) - return mChild[0]->GetNodeCount() + mChild[1]->GetNodeCount() + 1; + return inNodes[mChild[0]].GetNodeCount(inNodes) + inNodes[mChild[1]].GetNodeCount(inNodes) + 1; else return 1; } -uint AABBTreeBuilder::Node::GetLeafNodeCount() const +uint AABBTreeBuilder::Node::GetLeafNodeCount(const Array &inNodes) const { if (HasChildren()) - return mChild[0]->GetLeafNodeCount() + mChild[1]->GetLeafNodeCount(); + return inNodes[mChild[0]].GetLeafNodeCount(inNodes) + inNodes[mChild[1]].GetLeafNodeCount(inNodes); else return 1; } -uint AABBTreeBuilder::Node::GetTriangleCountInTree() const +uint AABBTreeBuilder::Node::GetTriangleCountInTree(const Array &inNodes) const { if (HasChildren()) - return mChild[0]->GetTriangleCountInTree() + mChild[1]->GetTriangleCountInTree(); + return inNodes[mChild[0]].GetTriangleCountInTree(inNodes) + inNodes[mChild[1]].GetTriangleCountInTree(inNodes); else return GetTriangleCount(); } -void AABBTreeBuilder::Node::GetTriangleCountPerNode(float &outAverage, uint &outMin, uint &outMax) const +void AABBTreeBuilder::Node::GetTriangleCountPerNode(const Array &inNodes, float &outAverage, uint &outMin, uint &outMax) const { outMin = INT_MAX; outMax = 0; outAverage = 0; uint avg_divisor = 0; - GetTriangleCountPerNodeInternal(outAverage, avg_divisor, outMin, outMax); + GetTriangleCountPerNodeInternal(inNodes, outAverage, avg_divisor, outMin, outMax); if (avg_divisor > 0) outAverage /= avg_divisor; } -float AABBTreeBuilder::Node::CalculateSAHCost(float inCostTraversal, float inCostLeaf) const +float AABBTreeBuilder::Node::CalculateSAHCost(const Array &inNodes, float inCostTraversal, float inCostLeaf) const { float surface_area = mBounds.GetSurfaceArea(); - return surface_area > 0.0f? CalculateSAHCostInternal(inCostTraversal / surface_area, inCostLeaf / surface_area) : 0.0f; + return surface_area > 0.0f? CalculateSAHCostInternal(inNodes, inCostTraversal / surface_area, inCostLeaf / surface_area) : 0.0f; } -void AABBTreeBuilder::Node::GetNChildren(uint inN, Array &outChildren) const +void AABBTreeBuilder::Node::GetNChildren(const Array &inNodes, uint inN, Array &outChildren) const { JPH_ASSERT(outChildren.empty()); @@ -94,8 +82,8 @@ void AABBTreeBuilder::Node::GetNChildren(uint inN, Array &outChild return; // Start with the children of this node - outChildren.push_back(mChild[0]); - outChildren.push_back(mChild[1]); + outChildren.push_back(&inNodes[mChild[0]]); + outChildren.push_back(&inNodes[mChild[1]]); size_t next = 0; bool all_triangles = true; @@ -106,7 +94,7 @@ void AABBTreeBuilder::Node::GetNChildren(uint inN, Array &outChild { // If there only triangle nodes left, we have to terminate if (all_triangles) - return; + return; next = 0; all_triangles = true; } @@ -116,8 +104,8 @@ void AABBTreeBuilder::Node::GetNChildren(uint inN, Array &outChild if (to_expand->HasChildren()) { outChildren.erase(outChildren.begin() + next); - outChildren.push_back(to_expand->mChild[0]); - outChildren.push_back(to_expand->mChild[1]); + outChildren.push_back(&inNodes[to_expand->mChild[0]]); + outChildren.push_back(&inNodes[to_expand->mChild[1]]); all_triangles = false; } else @@ -127,22 +115,22 @@ void AABBTreeBuilder::Node::GetNChildren(uint inN, Array &outChild } } -float AABBTreeBuilder::Node::CalculateSAHCostInternal(float inCostTraversalDivSurfaceArea, float inCostLeafDivSurfaceArea) const +float AABBTreeBuilder::Node::CalculateSAHCostInternal(const Array &inNodes, float inCostTraversalDivSurfaceArea, float inCostLeafDivSurfaceArea) const { if (HasChildren()) - return inCostTraversalDivSurfaceArea * mBounds.GetSurfaceArea() - + mChild[0]->CalculateSAHCostInternal(inCostTraversalDivSurfaceArea, inCostLeafDivSurfaceArea) - + mChild[1]->CalculateSAHCostInternal(inCostTraversalDivSurfaceArea, inCostLeafDivSurfaceArea); + return inCostTraversalDivSurfaceArea * mBounds.GetSurfaceArea() + + inNodes[mChild[0]].CalculateSAHCostInternal(inNodes, inCostTraversalDivSurfaceArea, inCostLeafDivSurfaceArea) + + inNodes[mChild[1]].CalculateSAHCostInternal(inNodes, inCostTraversalDivSurfaceArea, inCostLeafDivSurfaceArea); else return inCostLeafDivSurfaceArea * mBounds.GetSurfaceArea() * GetTriangleCount(); } -void AABBTreeBuilder::Node::GetTriangleCountPerNodeInternal(float &outAverage, uint &outAverageDivisor, uint &outMin, uint &outMax) const +void AABBTreeBuilder::Node::GetTriangleCountPerNodeInternal(const Array &inNodes, float &outAverage, uint &outAverageDivisor, uint &outMin, uint &outMax) const { if (HasChildren()) { - mChild[0]->GetTriangleCountPerNodeInternal(outAverage, outAverageDivisor, outMin, outMax); - mChild[1]->GetTriangleCountPerNodeInternal(outAverage, outAverageDivisor, outMin, outMax); + inNodes[mChild[0]].GetTriangleCountPerNodeInternal(inNodes, outAverage, outAverageDivisor, outMin, outMax); + inNodes[mChild[1]].GetTriangleCountPerNodeInternal(inNodes, outAverage, outAverageDivisor, outMin, outMax); } else { @@ -153,37 +141,45 @@ void AABBTreeBuilder::Node::GetTriangleCountPerNodeInternal(float &outAverage, u } } -AABBTreeBuilder::AABBTreeBuilder(TriangleSplitter &inSplitter, uint inMaxTrianglesPerLeaf) : +AABBTreeBuilder::AABBTreeBuilder(TriangleSplitter &inSplitter, uint inMaxTrianglesPerLeaf) : mTriangleSplitter(inSplitter), - mMaxTrianglesPerLeaf(inMaxTrianglesPerLeaf) -{ + mMaxTrianglesPerLeaf(inMaxTrianglesPerLeaf) +{ } AABBTreeBuilder::Node *AABBTreeBuilder::Build(AABBTreeBuilderStats &outStats) { TriangleSplitter::Range initial = mTriangleSplitter.GetInitialRange(); - Node *root = BuildInternal(initial); + // Worst case for number of nodes: 1 leaf node per triangle. At each level above, the number of nodes is half that of the level below. + // This means that at most we'll be allocating 2x the number of triangles in nodes. + mNodes.reserve(2 * initial.Count()); + mTriangles.reserve(initial.Count()); + + // Build the tree + Node &root = mNodes[BuildInternal(initial)]; + + // Collect stats float avg_triangles_per_leaf; uint min_triangles_per_leaf, max_triangles_per_leaf; - root->GetTriangleCountPerNode(avg_triangles_per_leaf, min_triangles_per_leaf, max_triangles_per_leaf); + root.GetTriangleCountPerNode(mNodes, avg_triangles_per_leaf, min_triangles_per_leaf, max_triangles_per_leaf); mTriangleSplitter.GetStats(outStats.mSplitterStats); - outStats.mSAHCost = root->CalculateSAHCost(1.0f, 1.0f); - outStats.mMinDepth = root->GetMinDepth(); - outStats.mMaxDepth = root->GetMaxDepth(); - outStats.mNodeCount = root->GetNodeCount(); - outStats.mLeafNodeCount = root->GetLeafNodeCount(); + outStats.mSAHCost = root.CalculateSAHCost(mNodes, 1.0f, 1.0f); + outStats.mMinDepth = root.GetMinDepth(mNodes); + outStats.mMaxDepth = root.GetMaxDepth(mNodes); + outStats.mNodeCount = root.GetNodeCount(mNodes); + outStats.mLeafNodeCount = root.GetLeafNodeCount(mNodes); outStats.mMaxTrianglesPerLeaf = mMaxTrianglesPerLeaf; outStats.mTreeMinTrianglesPerLeaf = min_triangles_per_leaf; outStats.mTreeMaxTrianglesPerLeaf = max_triangles_per_leaf; outStats.mTreeAvgTrianglesPerLeaf = avg_triangles_per_leaf; - return root; + return &root; } -AABBTreeBuilder::Node *AABBTreeBuilder::BuildInternal(const TriangleSplitter::Range &inTriangles) +uint AABBTreeBuilder::BuildInternal(const TriangleSplitter::Range &inTriangles) { // Check if there are too many triangles left if (inTriangles.Count() > mMaxTrianglesPerLeaf) @@ -192,7 +188,21 @@ AABBTreeBuilder::Node *AABBTreeBuilder::BuildInternal(const TriangleSplitter::Ra TriangleSplitter::Range left, right; if (!mTriangleSplitter.Split(inTriangles, left, right)) { - JPH_IF_DEBUG(Trace("AABBTreeBuilder: Doing random split for %d triangles (max per node: %d)!", (int)inTriangles.Count(), mMaxTrianglesPerLeaf);) + // When the trace below triggers: + // + // This code builds a tree structure to accelerate collision detection. + // At top level it will start with all triangles in a mesh and then divides the triangles into two batches. + // This process repeats until until the batch size is smaller than mMaxTrianglePerLeaf. + // + // It uses a TriangleSplitter to find a good split. When this warning triggers, the splitter was not able + // to create a reasonable split for the triangles. This usually happens when the triangles in a batch are + // intersecting. They could also be overlapping when projected on the 3 coordinate axis. + // + // To solve this issue, you could try to pass your mesh through a mesh cleaning / optimization algorithm. + // You could also inspect the triangles that cause this issue and see if that part of the mesh can be fixed manually. + // + // When you do not fix this warning, the tree will be less efficient for collision detection, but it will still work. + JPH_IF_DEBUG(Trace("AABBTreeBuilder: Doing random split for %d triangles (max per node: %u)!", (int)inTriangles.Count(), mMaxTrianglesPerLeaf);) int half = inTriangles.Count() / 2; JPH_ASSERT(half > 0); left = TriangleSplitter::Range(inTriangles.mBegin, inTriangles.mBegin + half); @@ -200,26 +210,33 @@ AABBTreeBuilder::Node *AABBTreeBuilder::BuildInternal(const TriangleSplitter::Ra } // Recursively build - Node *node = new Node(); - node->mChild[0] = BuildInternal(left); - node->mChild[1] = BuildInternal(right); - node->mBounds = node->mChild[0]->mBounds; - node->mBounds.Encapsulate(node->mChild[1]->mBounds); - return node; + const uint node_index = (uint)mNodes.size(); + mNodes.push_back(Node()); + uint left_index = BuildInternal(left); + uint right_index = BuildInternal(right); + Node &node = mNodes[node_index]; + node.mChild[0] = left_index; + node.mChild[1] = right_index; + node.mBounds = mNodes[node.mChild[0]].mBounds; + node.mBounds.Encapsulate(mNodes[node.mChild[1]].mBounds); + return node_index; } // Create leaf node - Node *node = new Node(); - node->mTriangles.reserve(inTriangles.Count()); + const uint node_index = (uint)mNodes.size(); + mNodes.push_back(Node()); + Node &node = mNodes.back(); + node.mTrianglesBegin = (uint)mTriangles.size(); + node.mNumTriangles = inTriangles.mEnd - inTriangles.mBegin; + const VertexList &v = mTriangleSplitter.GetVertices(); for (uint i = inTriangles.mBegin; i < inTriangles.mEnd; ++i) { const IndexedTriangle &t = mTriangleSplitter.GetTriangle(i); - const VertexList &v = mTriangleSplitter.GetVertices(); - node->mTriangles.push_back(t); - node->mBounds.Encapsulate(v, t); + mTriangles.push_back(t); + node.mBounds.Encapsulate(v, t); } - return node; + return node_index; } JPH_NAMESPACE_END diff --git a/lib/zphysics/libs/Jolt/AABBTree/AABBTreeBuilder.h b/lib/zphysics/libs/Jolt/AABBTree/AABBTreeBuilder.h index c8281f4..3b0635c 100644 --- a/lib/zphysics/libs/Jolt/AABBTree/AABBTreeBuilder.h +++ b/lib/zphysics/libs/Jolt/AABBTree/AABBTreeBuilder.h @@ -32,66 +32,66 @@ struct AABBTreeBuilderStats }; /// Helper class to build an AABB tree -class AABBTreeBuilder +class JPH_EXPORT AABBTreeBuilder { public: /// A node in the tree, contains the AABox for the tree and any child nodes or triangles - class Node : public NonCopyable + class Node { public: JPH_OVERRIDE_NEW_DELETE - /// Constructor - Node(); - ~Node(); + /// Indicates that there is no child + static constexpr uint cInvalidNodeIndex = ~uint(0); /// Get number of triangles in this node - inline uint GetTriangleCount() const { return uint(mTriangles.size()); } + inline uint GetTriangleCount() const { return mNumTriangles; } /// Check if this node has any children - inline bool HasChildren() const { return mChild[0] != nullptr || mChild[1] != nullptr; } + inline bool HasChildren() const { return mChild[0] != cInvalidNodeIndex || mChild[1] != cInvalidNodeIndex; } /// Min depth of tree - uint GetMinDepth() const; + uint GetMinDepth(const Array &inNodes) const; /// Max depth of tree - uint GetMaxDepth() const; + uint GetMaxDepth(const Array &inNodes) const; /// Number of nodes in tree - uint GetNodeCount() const; + uint GetNodeCount(const Array &inNodes) const; /// Number of leaf nodes in tree - uint GetLeafNodeCount() const; + uint GetLeafNodeCount(const Array &inNodes) const; /// Get triangle count in tree - uint GetTriangleCountInTree() const; + uint GetTriangleCountInTree(const Array &inNodes) const; /// Calculate min and max triangles per node - void GetTriangleCountPerNode(float &outAverage, uint &outMin, uint &outMax) const; + void GetTriangleCountPerNode(const Array &inNodes, float &outAverage, uint &outMin, uint &outMax) const; /// Calculate the total cost of the tree using the surface area heuristic - float CalculateSAHCost(float inCostTraversal, float inCostLeaf) const; + float CalculateSAHCost(const Array &inNodes, float inCostTraversal, float inCostLeaf) const; /// Recursively get children (breadth first) to get in total inN children (or less if there are no more) - void GetNChildren(uint inN, Array &outChildren) const; + void GetNChildren(const Array &inNodes, uint inN, Array &outChildren) const; /// Bounding box AABox mBounds; /// Triangles (if no child nodes) - IndexedTriangleList mTriangles; + uint mTrianglesBegin; // Index into mTriangles + uint mNumTriangles = 0; - /// Child nodes (if no triangles) - Node * mChild[2]; + /// Child node indices (if no triangles) + uint mChild[2] = { cInvalidNodeIndex, cInvalidNodeIndex }; private: friend class AABBTreeBuilder; /// Recursive helper function to calculate cost of the tree - float CalculateSAHCostInternal(float inCostTraversalDivSurfaceArea, float inCostLeafDivSurfaceArea) const; + float CalculateSAHCostInternal(const Array &inNodes, float inCostTraversalDivSurfaceArea, float inCostLeafDivSurfaceArea) const; /// Recursive helper function to calculate min and max triangles per node - void GetTriangleCountPerNodeInternal(float &outAverage, uint &outAverageDivisor, uint &outMin, uint &outMax) const; + void GetTriangleCountPerNodeInternal(const Array &inNodes, float &outAverage, uint &outAverageDivisor, uint &outMin, uint &outMax) const; }; /// Constructor @@ -100,11 +100,19 @@ public: /// Recursively build tree, returns the root node of the tree Node * Build(AABBTreeBuilderStats &outStats); + /// Get all nodes + const Array & GetNodes() const { return mNodes; } + + /// Get all triangles + const Array &GetTriangles() const { return mTriangles; } + private: - Node * BuildInternal(const TriangleSplitter::Range &inTriangles); + uint BuildInternal(const TriangleSplitter::Range &inTriangles); TriangleSplitter & mTriangleSplitter; const uint mMaxTrianglesPerLeaf; + Array mNodes; + Array mTriangles; }; JPH_NAMESPACE_END diff --git a/lib/zphysics/libs/Jolt/AABBTree/AABBTreeToBuffer.h b/lib/zphysics/libs/Jolt/AABBTree/AABBTreeToBuffer.h index 032141f..d50c750 100644 --- a/lib/zphysics/libs/Jolt/AABBTree/AABBTreeToBuffer.h +++ b/lib/zphysics/libs/Jolt/AABBTree/AABBTreeToBuffer.h @@ -8,14 +8,8 @@ #include #include -JPH_SUPPRESS_WARNINGS_STD_BEGIN -#include -JPH_SUPPRESS_WARNINGS_STD_END - JPH_NAMESPACE_BEGIN -template using Deque = std::deque>; - /// Conversion algorithm that converts an AABB tree to an optimized binary buffer template class AABBTreeToBuffer @@ -37,20 +31,89 @@ public: static const int TriangleHeaderSize = TriangleCodec::TriangleHeaderSize; /// Convert AABB tree. Returns false if failed. - bool Convert(const VertexList &inVertices, const AABBTreeBuilder::Node *inRoot, const char *&outError) + bool Convert(const Array &inTriangles, const Array &inNodes, const VertexList &inVertices, const AABBTreeBuilder::Node *inRoot, bool inStoreUserData, const char *&outError) { - const typename NodeCodec::EncodingContext node_ctx; + typename NodeCodec::EncodingContext node_ctx; typename TriangleCodec::EncodingContext tri_ctx(inVertices); - // Estimate the amount of memory required - uint tri_count = inRoot->GetTriangleCountInTree(); - uint node_count = inRoot->GetNodeCount(); - uint nodes_size = node_ctx.GetPessimisticMemoryEstimate(node_count); - uint total_size = HeaderSize + TriangleHeaderSize + nodes_size + tri_ctx.GetPessimisticMemoryEstimate(tri_count); - mTree.reserve(total_size); + // Child nodes out of loop so we don't constantly realloc it + Array child_nodes; + child_nodes.reserve(NumChildrenPerNode); - // Reset counters - mNodesSize = 0; + // First calculate how big the tree is going to be. + // Since the tree can be huge for very large meshes, we don't want + // to reallocate the buffer as it may cause out of memory situations. + // This loop mimics the construction loop below. + uint64 total_size = HeaderSize + TriangleHeaderSize; + size_t node_count = 1; // Start with root node + size_t to_process_max_size = 1; // Track size of queues so we can do a single reserve below + size_t to_process_triangles_max_size = 0; + { // A scope to free the memory associated with to_estimate and to_estimate_triangles + Array to_estimate; + Array to_estimate_triangles; + to_estimate.push_back(inRoot); + for (;;) + { + while (!to_estimate.empty()) + { + // Get the next node to process + const AABBTreeBuilder::Node *node = to_estimate.back(); + to_estimate.pop_back(); + + // Update total size + node_ctx.PrepareNodeAllocate(node, total_size); + + if (node->HasChildren()) + { + // Collect the first NumChildrenPerNode sub-nodes in the tree + child_nodes.clear(); // Won't free the memory + node->GetNChildren(inNodes, NumChildrenPerNode, child_nodes); + + // Increment the number of nodes we're going to store + node_count += child_nodes.size(); + + // Insert in reverse order so we estimate left child first when taking nodes from the back + for (int idx = int(child_nodes.size()) - 1; idx >= 0; --idx) + { + // Store triangles in separate list so we process them last + const AABBTreeBuilder::Node *child = child_nodes[idx]; + if (child->HasChildren()) + { + to_estimate.push_back(child); + to_process_max_size = max(to_estimate.size(), to_process_max_size); + } + else + { + to_estimate_triangles.push_back(child); + to_process_triangles_max_size = max(to_estimate_triangles.size(), to_process_triangles_max_size); + } + } + } + else + { + // Update total size + tri_ctx.PreparePack(&inTriangles[node->mTrianglesBegin], node->mNumTriangles, inStoreUserData, total_size); + } + } + + // If we've got triangles to estimate, loop again with just the triangles + if (to_estimate_triangles.empty()) + break; + else + to_estimate.swap(to_estimate_triangles); + } + } + + // Finalize the prepare stage for the triangle context + tri_ctx.FinalizePreparePack(total_size); + + // Reserve the buffer + if (size_t(total_size) != total_size) + { + outError = "AABBTreeToBuffer: Out of memory!"; + return false; + } + mTree.reserve(size_t(total_size)); // Add headers NodeHeader *header = HeaderSize > 0? mTree.Allocate() : nullptr; @@ -61,21 +124,22 @@ public: const AABBTreeBuilder::Node * mNode = nullptr; // Node that this entry belongs to Vec3 mNodeBoundsMin; // Quantized node bounds Vec3 mNodeBoundsMax; - uint mNodeStart = uint(-1); // Start of node in mTree - uint mTriangleStart = uint(-1); // Start of the triangle data in mTree + size_t mNodeStart = size_t(-1); // Start of node in mTree + size_t mTriangleStart = size_t(-1); // Start of the triangle data in mTree + size_t mChildNodeStart[NumChildrenPerNode]; // Start of the children of the node in mTree + size_t mChildTrianglesStart[NumChildrenPerNode]; // Start of the triangle data in mTree + size_t * mParentChildNodeStart = nullptr; // Where to store mNodeStart (to patch mChildNodeStart of my parent) + size_t * mParentTrianglesStart = nullptr; // Where to store mTriangleStart (to patch mChildTrianglesStart of my parent) uint mNumChildren = 0; // Number of children - uint mChildNodeStart[NumChildrenPerNode]; // Start of the children of the node in mTree - uint mChildTrianglesStart[NumChildrenPerNode]; // Start of the triangle data in mTree - uint * mParentChildNodeStart = nullptr; // Where to store mNodeStart (to patch mChildNodeStart of my parent) - uint * mParentTrianglesStart = nullptr; // Where to store mTriangleStart (to patch mChildTrianglesStart of my parent) }; - - Deque to_process; - Deque to_process_triangles; - Array node_list; + Array to_process; + to_process.reserve(to_process_max_size); + Array to_process_triangles; + to_process_triangles.reserve(to_process_triangles_max_size); + Array node_list; node_list.reserve(node_count); // Needed to ensure that array is not reallocated, so we can keep pointers in the array - + NodeData root; root.mNode = inRoot; root.mNodeBoundsMin = inRoot->mBounds.mMin; @@ -83,10 +147,6 @@ public: node_list.push_back(root); to_process.push_back(&node_list.back()); - // Child nodes out of loop so we don't constantly realloc it - Array child_nodes; - child_nodes.reserve(NumChildrenPerNode); - for (;;) { while (!to_process.empty()) @@ -100,7 +160,7 @@ public: // Collect the first NumChildrenPerNode sub-nodes in the tree child_nodes.clear(); // Won't free the memory - node_data->mNode->GetNChildren(NumChildrenPerNode, child_nodes); + node_data->mNode->GetNChildren(inNodes, NumChildrenPerNode, child_nodes); node_data->mNumChildren = (uint)child_nodes.size(); // Fill in default child bounds @@ -118,47 +178,41 @@ public: } // Start a new node - uint old_size = (uint)mTree.size(); node_data->mNodeStart = node_ctx.NodeAllocate(node_data->mNode, node_data->mNodeBoundsMin, node_data->mNodeBoundsMax, child_nodes, child_bounds_min, child_bounds_max, mTree, outError); - if (node_data->mNodeStart == uint(-1)) + if (node_data->mNodeStart == size_t(-1)) return false; - mNodesSize += (uint)mTree.size() - old_size; if (node_data->mNode->HasChildren()) { // Insert in reverse order so we process left child first when taking nodes from the back for (int idx = int(child_nodes.size()) - 1; idx >= 0; --idx) { + const AABBTreeBuilder::Node *child_node = child_nodes[idx]; + // Due to quantization box could have become bigger, not smaller - JPH_ASSERT(AABox(child_bounds_min[idx], child_bounds_max[idx]).Contains(child_nodes[idx]->mBounds), "AABBTreeToBuffer: Bounding box became smaller!"); + JPH_ASSERT(AABox(child_bounds_min[idx], child_bounds_max[idx]).Contains(child_node->mBounds), "AABBTreeToBuffer: Bounding box became smaller!"); // Add child to list of nodes to be processed NodeData child; - child.mNode = child_nodes[idx]; + child.mNode = child_node; child.mNodeBoundsMin = child_bounds_min[idx]; child.mNodeBoundsMax = child_bounds_max[idx]; child.mParentChildNodeStart = &node_data->mChildNodeStart[idx]; child.mParentTrianglesStart = &node_data->mChildTrianglesStart[idx]; - NodeData *old = &node_list[0]; node_list.push_back(child); - if (old != &node_list[0]) - { - outError = "Internal Error: Array reallocated, memory corruption!"; - return false; - } // Store triangles in separate list so we process them last - if (node_list.back().mNode->HasChildren()) + if (child_node->HasChildren()) to_process.push_back(&node_list.back()); else to_process_triangles.push_back(&node_list.back()); } } else - { + { // Add triangles - node_data->mTriangleStart = tri_ctx.Pack(node_data->mNode->mTriangles, mTree, outError); - if (node_data->mTriangleStart == uint(-1)) + node_data->mTriangleStart = tri_ctx.Pack(&inTriangles[node_data->mNode->mTrianglesBegin], node_data->mNode->mNumTriangles, inStoreUserData, mTree, outError); + if (node_data->mTriangleStart == size_t(-1)) return false; } @@ -176,35 +230,33 @@ public: else to_process.swap(to_process_triangles); } - + + // Assert that our reservation was correct (we don't know if we swapped the arrays or not) + JPH_ASSERT(to_process_max_size == to_process.capacity() || to_process_triangles_max_size == to_process.capacity()); + JPH_ASSERT(to_process_max_size == to_process_triangles.capacity() || to_process_triangles_max_size == to_process_triangles.capacity()); + // Finalize all nodes for (NodeData &n : node_list) if (!node_ctx.NodeFinalize(n.mNode, n.mNodeStart, n.mNumChildren, n.mChildNodeStart, n.mChildTrianglesStart, mTree, outError)) return false; - + // Finalize the triangles tri_ctx.Finalize(inVertices, triangle_header, mTree); - // Validate that we reserved enough memory - if (nodes_size < mNodesSize) + // Validate that our reservations were correct + if (node_count != node_list.size()) { - outError = "Internal Error: Not enough memory reserved for nodes!"; + outError = "Internal Error: Node memory estimate was incorrect, memory corruption!"; return false; } - if (total_size < (uint)mTree.size()) + if (total_size != mTree.size()) { - outError = "Internal Error: Not enough memory reserved for triangles!"; + outError = "Internal Error: Tree memory estimate was incorrect, memory corruption!"; return false; } // Finalize the nodes - if (!node_ctx.Finalize(header, inRoot, node_list[0].mNodeStart, node_list[0].mTriangleStart, outError)) - return false; - - // Shrink the tree, this will invalidate the header and triangle_header variables - mTree.shrink_to_fit(); - - return true; + return node_ctx.Finalize(header, inRoot, node_list[0].mNodeStart, node_list[0].mTriangleStart, outError); } /// Get resulting data @@ -236,10 +288,9 @@ public: { return mTree.Get(HeaderSize + TriangleHeaderSize); } - + private: ByteBuffer mTree; ///< Resulting tree structure - uint mNodesSize; ///< Size in bytes of the nodes in the buffer }; JPH_NAMESPACE_END diff --git a/lib/zphysics/libs/Jolt/AABBTree/NodeCodec/NodeCodecQuadTreeHalfFloat.h b/lib/zphysics/libs/Jolt/AABBTree/NodeCodec/NodeCodecQuadTreeHalfFloat.h index 4b1e320..c0feea7 100644 --- a/lib/zphysics/libs/Jolt/AABBTree/NodeCodec/NodeCodecQuadTreeHalfFloat.h +++ b/lib/zphysics/libs/Jolt/AABBTree/NodeCodec/NodeCodecQuadTreeHalfFloat.h @@ -10,7 +10,6 @@ JPH_NAMESPACE_BEGIN -template class NodeCodecQuadTreeHalfFloat { public: @@ -23,11 +22,13 @@ public: Float3 mRootBoundsMin; Float3 mRootBoundsMax; uint32 mRootProperties; + uint8 mBlockIDBits; ///< Number of bits to address a triangle block + uint8 mPadding[3] = { 0 }; }; /// Size of the header (an empty struct is always > 0 bytes so this needs a separate variable) static constexpr int HeaderSize = sizeof(Header); - + /// Stack size to use during DecodingContext::sWalkTree static constexpr int StackSize = 128; @@ -54,33 +55,37 @@ public: HalfFloat mBoundsMaxZ[4]; uint32 mNodeProperties[4]; ///< 4 child node properties }; - + static_assert(sizeof(Node) == 64, "Node should be 64 bytes"); /// This class encodes and compresses quad tree nodes class EncodingContext { public: - /// Get an upper bound on the amount of bytes needed for a node tree with inNodeCount nodes - uint GetPessimisticMemoryEstimate(uint inNodeCount) const - { - return inNodeCount * (sizeof(Node) + Alignment - 1); - } - - /// Allocate a new node for inNode. - /// Algorithm can modify the order of ioChildren to indicate in which order children should be compressed - /// Algorithm can enlarge the bounding boxes of the children during compression and returns these in outChildBoundsMin, outChildBoundsMax - /// inNodeBoundsMin, inNodeBoundsMax is the bounding box if inNode possibly widened by compressing the parent node - /// Returns uint(-1) on error and reports the error in outError - uint NodeAllocate(const AABBTreeBuilder::Node *inNode, Vec3Arg inNodeBoundsMin, Vec3Arg inNodeBoundsMax, Array &ioChildren, Vec3 outChildBoundsMin[NumChildrenPerNode], Vec3 outChildBoundsMax[NumChildrenPerNode], ByteBuffer &ioBuffer, const char *&outError) const + /// Mimics the size a call to NodeAllocate() would add to the buffer + void PrepareNodeAllocate(const AABBTreeBuilder::Node *inNode, uint64 &ioBufferSize) const { // We don't emit nodes for leafs if (!inNode->HasChildren()) - return (uint)ioBuffer.size(); - - // Align the buffer - ioBuffer.Align(Alignment); - uint node_start = (uint)ioBuffer.size(); + return; + + // Add size of node + ioBufferSize += sizeof(Node); + } + + /// Allocate a new node for inNode. + /// Algorithm can modify the order of ioChildren to indicate in which order children should be compressed + /// Algorithm can enlarge the bounding boxes of the children during compression and returns these in outChildBoundsMin, outChildBoundsMax + /// inNodeBoundsMin, inNodeBoundsMax is the bounding box if inNode possibly widened by compressing the parent node + /// Returns size_t(-1) on error and reports the error in outError + size_t NodeAllocate(const AABBTreeBuilder::Node *inNode, Vec3Arg inNodeBoundsMin, Vec3Arg inNodeBoundsMax, Array &ioChildren, Vec3 outChildBoundsMin[NumChildrenPerNode], Vec3 outChildBoundsMax[NumChildrenPerNode], ByteBuffer &ioBuffer, const char *&outError) const + { + // We don't emit nodes for leafs + if (!inNode->HasChildren()) + return ioBuffer.size(); + + // Remember the start of the node + size_t node_start = ioBuffer.size(); // Fill in bounds Node *node = ioBuffer.Allocate(); @@ -104,13 +109,13 @@ public: if (this_node->GetTriangleCount() >= TRIANGLE_COUNT_MASK) { outError = "NodeCodecQuadTreeHalfFloat: Too many triangles"; - return uint(-1); + return size_t(-1); } } else { // Make this an invalid triangle node - node->mNodeProperties[i] = uint32(TRIANGLE_COUNT_MASK) << TRIANGLE_COUNT_SHIFT; + node->mNodeProperties[i] = uint32(TRIANGLE_COUNT_MASK) << TRIANGLE_COUNT_SHIFT; // Make bounding box invalid node->mBoundsMinX[i] = HALF_FLT_MAX; @@ -133,7 +138,7 @@ public: } /// Once all nodes have been added, this call finalizes all nodes by patching in the offsets of the child nodes (that were added after the node itself was added) - bool NodeFinalize(const AABBTreeBuilder::Node *inNode, uint inNodeStart, uint inNumChildren, const uint *inChildrenNodeStart, const uint *inChildrenTrianglesStart, ByteBuffer &ioBuffer, const char *&outError) const + bool NodeFinalize(const AABBTreeBuilder::Node *inNode, size_t inNodeStart, uint inNumChildren, const size_t *inChildrenNodeStart, const size_t *inChildrenTrianglesStart, ByteBuffer &ioBuffer, const char *&outError) { if (!inNode->HasChildren()) return true; @@ -141,46 +146,64 @@ public: Node *node = ioBuffer.Get(inNodeStart); for (uint i = 0; i < inNumChildren; ++i) { - // If there are triangles, use the triangle offset otherwise use the node offset - uint offset = node->mNodeProperties[i] != 0? inChildrenTrianglesStart[i] : inChildrenNodeStart[i]; + size_t offset; + if (node->mNodeProperties[i] != 0) + { + // This is a triangle block + offset = inChildrenTrianglesStart[i]; + + // Store highest block with triangles so we can count the number of bits we need + mHighestTriangleBlock = max(mHighestTriangleBlock, offset); + } + else + { + // This is a node block + offset = inChildrenNodeStart[i]; + } + + // Store offset of next node / triangles if (offset & OFFSET_NON_SIGNIFICANT_MASK) { - outError = "NodeCodecQuadTreeHalfFloat: Internal Error: Offset has non-signifiant bits set"; + outError = "NodeCodecQuadTreeHalfFloat: Internal Error: Offset has non-significant bits set"; return false; } offset >>= OFFSET_NON_SIGNIFICANT_BITS; - if (offset & ~OFFSET_MASK) + if (offset > OFFSET_MASK) { outError = "NodeCodecQuadTreeHalfFloat: Offset too large. Too much data."; return false; } - - // Store offset of next node / triangles - node->mNodeProperties[i] |= offset; + node->mNodeProperties[i] |= uint32(offset); } return true; } /// Once all nodes have been finalized, this will finalize the header of the nodes - bool Finalize(Header *outHeader, const AABBTreeBuilder::Node *inRoot, uint inRootNodeStart, uint inRootTrianglesStart, const char *&outError) const + bool Finalize(Header *outHeader, const AABBTreeBuilder::Node *inRoot, size_t inRootNodeStart, size_t inRootTrianglesStart, const char *&outError) const { - uint offset = inRoot->HasChildren()? inRootNodeStart : inRootTrianglesStart; + // Check if we can address the root node + size_t offset = inRoot->HasChildren()? inRootNodeStart : inRootTrianglesStart; if (offset & OFFSET_NON_SIGNIFICANT_MASK) { - outError = "NodeCodecQuadTreeHalfFloat: Internal Error: Offset has non-signifiant bits set"; + outError = "NodeCodecQuadTreeHalfFloat: Internal Error: Offset has non-significant bits set"; return false; } offset >>= OFFSET_NON_SIGNIFICANT_BITS; - if (offset & ~OFFSET_MASK) + if (offset > OFFSET_MASK) { outError = "NodeCodecQuadTreeHalfFloat: Offset too large. Too much data."; return false; } + // If the root has triangles, we need to take that offset instead since the mHighestTriangleBlock will be zero + size_t highest_triangle_block = inRootTrianglesStart != size_t(-1)? inRootTrianglesStart : mHighestTriangleBlock; + highest_triangle_block >>= OFFSET_NON_SIGNIFICANT_BITS; + inRoot->mBounds.mMin.StoreFloat3(&outHeader->mRootBoundsMin); inRoot->mBounds.mMax.StoreFloat3(&outHeader->mRootBoundsMax); - outHeader->mRootProperties = offset + (inRoot->GetTriangleCount() << TRIANGLE_COUNT_SHIFT); + outHeader->mRootProperties = uint32(offset) + (inRoot->GetTriangleCount() << TRIANGLE_COUNT_SHIFT); + outHeader->mBlockIDBits = uint8(32 - CountLeadingZeros(uint32(highest_triangle_block))); if (inRoot->GetTriangleCount() >= TRIANGLE_COUNT_MASK) { outError = "NodeCodecQuadTreeHalfFloat: Too many triangles"; @@ -188,7 +211,10 @@ public: } return true; - } + } + + private: + size_t mHighestTriangleBlock = 0; }; /// This class decodes and decompresses quad tree nodes @@ -196,9 +222,9 @@ public: { public: /// Get the amount of bits needed to store an ID to a triangle block - inline static uint sTriangleBlockIDBits(const ByteBuffer &inTree) + inline static uint sTriangleBlockIDBits(const Header *inHeader) { - return 32 - CountLeadingZeros((uint32)inTree.size()) - OFFSET_NON_SIGNIFICANT_BITS; + return inHeader->mBlockIDBits; } /// Convert a triangle block ID to the start of the triangle buffer @@ -228,10 +254,19 @@ public: const Node *node = reinterpret_cast(inBufferStart + (node_properties << OFFSET_NON_SIGNIFICANT_BITS)); // Unpack bounds + #ifdef JPH_CPU_BIG_ENDIAN + Vec4 bounds_minx = HalfFloatConversion::ToFloat(UVec4(node->mBoundsMinX[0] + (node->mBoundsMinX[1] << 16), node->mBoundsMinX[2] + (node->mBoundsMinX[3] << 16), 0, 0)); + Vec4 bounds_miny = HalfFloatConversion::ToFloat(UVec4(node->mBoundsMinY[0] + (node->mBoundsMinY[1] << 16), node->mBoundsMinY[2] + (node->mBoundsMinY[3] << 16), 0, 0)); + Vec4 bounds_minz = HalfFloatConversion::ToFloat(UVec4(node->mBoundsMinZ[0] + (node->mBoundsMinZ[1] << 16), node->mBoundsMinZ[2] + (node->mBoundsMinZ[3] << 16), 0, 0)); + + Vec4 bounds_maxx = HalfFloatConversion::ToFloat(UVec4(node->mBoundsMaxX[0] + (node->mBoundsMaxX[1] << 16), node->mBoundsMaxX[2] + (node->mBoundsMaxX[3] << 16), 0, 0)); + Vec4 bounds_maxy = HalfFloatConversion::ToFloat(UVec4(node->mBoundsMaxY[0] + (node->mBoundsMaxY[1] << 16), node->mBoundsMaxY[2] + (node->mBoundsMaxY[3] << 16), 0, 0)); + Vec4 bounds_maxz = HalfFloatConversion::ToFloat(UVec4(node->mBoundsMaxZ[0] + (node->mBoundsMaxZ[1] << 16), node->mBoundsMaxZ[2] + (node->mBoundsMaxZ[3] << 16), 0, 0)); + #else UVec4 bounds_minxy = UVec4::sLoadInt4(reinterpret_cast(&node->mBoundsMinX[0])); Vec4 bounds_minx = HalfFloatConversion::ToFloat(bounds_minxy); Vec4 bounds_miny = HalfFloatConversion::ToFloat(bounds_minxy.Swizzle()); - + UVec4 bounds_minzmaxx = UVec4::sLoadInt4(reinterpret_cast(&node->mBoundsMinZ[0])); Vec4 bounds_minz = HalfFloatConversion::ToFloat(bounds_minzmaxx); Vec4 bounds_maxx = HalfFloatConversion::ToFloat(bounds_minzmaxx.Swizzle()); @@ -239,6 +274,7 @@ public: UVec4 bounds_maxyz = UVec4::sLoadInt4(reinterpret_cast(&node->mBoundsMaxY[0])); Vec4 bounds_maxy = HalfFloatConversion::ToFloat(bounds_maxyz); Vec4 bounds_maxz = HalfFloatConversion::ToFloat(bounds_maxyz.Swizzle()); + #endif // Load properties for 4 children UVec4 properties = UVec4::sLoadInt4(&node->mNodeProperties[0]); @@ -252,7 +288,7 @@ public: mTop += num_results; } else if (tri_count != TRIANGLE_COUNT_MASK) // TRIANGLE_COUNT_MASK indicates a padding node, normally we shouldn't visit these nodes but when querying with a big enough box you could touch HALF_FLT_MAX (about 65K) - { + { // Node contains triangles, do individual tests uint32 triangle_block_id = node_properties & OFFSET_MASK; const void *triangles = sGetTriangleBlockStart(inBufferStart, triangle_block_id); @@ -265,7 +301,7 @@ public: break; // Fetch next node until we find one that the visitor wants to see - do + do --mTop; while (mTop >= 0 && !ioVisitor.ShouldVisitNode(mTop)); } diff --git a/lib/zphysics/libs/Jolt/AABBTree/TriangleCodec/TriangleCodecIndexed8BitPackSOA4Flags.h b/lib/zphysics/libs/Jolt/AABBTree/TriangleCodec/TriangleCodecIndexed8BitPackSOA4Flags.h index 8597634..b3c33c5 100644 --- a/lib/zphysics/libs/Jolt/AABBTree/TriangleCodec/TriangleCodecIndexed8BitPackSOA4Flags.h +++ b/lib/zphysics/libs/Jolt/AABBTree/TriangleCodec/TriangleCodecIndexed8BitPackSOA4Flags.h @@ -13,6 +13,7 @@ JPH_NAMESPACE_BEGIN /// TriangleBlockHeader, /// TriangleBlock (4 triangles and their flags in 16 bytes), /// TriangleBlock... +/// [Optional] UserData (4 bytes per triangle) /// /// Vertices are stored: /// @@ -34,7 +35,7 @@ public: static constexpr int TriangleHeaderSize = sizeof(TriangleHeader); /// If this codec could return a different offset than the current buffer size when calling Pack() - static constexpr bool ChangesOffsetOnPack = false; + static constexpr bool ChangesOffsetOnPack = false; /// Amount of bits per component enum EComponentData : uint32 @@ -67,7 +68,7 @@ public: }; static_assert(sizeof(VertexData) == 8, "Compiler added padding"); - + /// A block of 4 triangles struct TriangleBlock { @@ -77,59 +78,168 @@ public: static_assert(sizeof(TriangleBlock) == 16, "Compiler added padding"); + enum ETriangleBlockHeaderFlags : uint32 + { + OFFSET_TO_VERTICES_BITS = 29, ///< Offset from current block to start of vertices in bytes + OFFSET_TO_VERTICES_MASK = (1 << OFFSET_TO_VERTICES_BITS) - 1, + OFFSET_NON_SIGNIFICANT_BITS = 2, ///< The offset from the current block to the start of the vertices must be a multiple of 4 bytes + OFFSET_NON_SIGNIFICANT_MASK = (1 << OFFSET_NON_SIGNIFICANT_BITS) - 1, + OFFSET_TO_USERDATA_BITS = 3, ///< When user data is stored, this is the number of blocks to skip to get to the user data (0 = no user data) + OFFSET_TO_USERDATA_MASK = (1 << OFFSET_TO_USERDATA_BITS) - 1, + }; + /// A triangle header, will be followed by one or more TriangleBlocks struct TriangleBlockHeader { - const VertexData * GetVertexData() const { return reinterpret_cast(reinterpret_cast(this) + mOffsetToVertices); } + const VertexData * GetVertexData() const { return reinterpret_cast(reinterpret_cast(this) + ((mFlags & OFFSET_TO_VERTICES_MASK) << OFFSET_NON_SIGNIFICANT_BITS)); } const TriangleBlock * GetTriangleBlock() const { return reinterpret_cast(reinterpret_cast(this) + sizeof(TriangleBlockHeader)); } + const uint32 * GetUserData() const { uint32 offset = mFlags >> OFFSET_TO_VERTICES_BITS; return offset == 0? nullptr : reinterpret_cast(GetTriangleBlock() + offset); } - uint32 mOffsetToVertices; ///< Offset from current block to start of vertices in bytes + uint32 mFlags; }; static_assert(sizeof(TriangleBlockHeader) == 4, "Compiler added padding"); + /// This class is used to validate that the triangle data will not be degenerate after compression + class ValidationContext + { + public: + /// Constructor + ValidationContext(const IndexedTriangleList &inTriangles, const VertexList &inVertices) : + mVertices(inVertices) + { + // Only used the referenced triangles, just like EncodingContext::Finalize does + for (const IndexedTriangle &i : inTriangles) + for (uint32 idx : i.mIdx) + mBounds.Encapsulate(Vec3(inVertices[idx])); + } + + /// Test if a triangle will be degenerate after quantization + bool IsDegenerate(const IndexedTriangle &inTriangle) const + { + // Quantize the triangle in the same way as EncodingContext::Finalize does + UVec4 quantized_vertex[3]; + Vec3 compress_scale = Vec3::sReplicate(COMPONENT_MASK) / Vec3::sMax(mBounds.GetSize(), Vec3::sReplicate(1.0e-20f)); + for (int i = 0; i < 3; ++i) + quantized_vertex[i] = ((Vec3(mVertices[inTriangle.mIdx[i]]) - mBounds.mMin) * compress_scale + Vec3::sReplicate(0.5f)).ToInt(); + return quantized_vertex[0] == quantized_vertex[1] || quantized_vertex[1] == quantized_vertex[2] || quantized_vertex[0] == quantized_vertex[2]; + } + + private: + const VertexList & mVertices; + AABox mBounds; + }; + /// This class is used to encode and compress triangle data into a byte buffer class EncodingContext { public: + /// Indicates a vertex hasn't been seen yet in the triangle list + static constexpr uint32 cNotFound = 0xffffffff; + /// Construct the encoding context explicit EncodingContext(const VertexList &inVertices) : - mVertexMap(inVertices.size(), 0xffffffff) // Fill vertex map with 'not found' + mVertexMap(inVertices.size(), cNotFound) { - // Reserve for worst case to avoid allocating in the inner loop - mVertices.reserve(inVertices.size()); } - /// Get an upper bound on the amount of bytes needed to store inTriangleCount triangles - uint GetPessimisticMemoryEstimate(uint inTriangleCount) const + /// Mimics the size a call to Pack() would add to the buffer + void PreparePack(const IndexedTriangle *inTriangles, uint inNumTriangles, bool inStoreUserData, uint64 &ioBufferSize) { - // Worst case each triangle is alone in a block, none of the vertices are shared and we need to add 3 bytes to align the vertices - return inTriangleCount * (sizeof(TriangleBlockHeader) + sizeof(TriangleBlock) + 3 * sizeof(VertexData)) + 3; + // Add triangle block header + ioBufferSize += sizeof(TriangleBlockHeader); + + // Compute first vertex that this batch will use (ensuring there's enough room if none of the vertices are shared) + uint start_vertex = Clamp((int)mVertexCount - 256 + (int)inNumTriangles * 3, 0, (int)mVertexCount); + + // Pack vertices + uint padded_triangle_count = AlignUp(inNumTriangles, 4); + for (uint t = 0; t < padded_triangle_count; t += 4) + { + // Add triangle block header + ioBufferSize += sizeof(TriangleBlock); + + for (uint vertex_nr = 0; vertex_nr < 3; ++vertex_nr) + for (uint block_tri_idx = 0; block_tri_idx < 4; ++block_tri_idx) + { + // Fetch vertex index. Create degenerate triangles for padding triangles. + bool triangle_available = t + block_tri_idx < inNumTriangles; + uint32 src_vertex_index = triangle_available? inTriangles[t + block_tri_idx].mIdx[vertex_nr] : inTriangles[inNumTriangles - 1].mIdx[0]; + + // Check if we've seen this vertex before and if it is in the range that we can encode + uint32 &vertex_index = mVertexMap[src_vertex_index]; + if (vertex_index == cNotFound || vertex_index < start_vertex) + { + // Add vertex + vertex_index = mVertexCount; + mVertexCount++; + } + } + } + + // Add user data + if (inStoreUserData) + ioBufferSize += inNumTriangles * sizeof(uint32); + } + + /// Mimics the size the Finalize() call would add to ioBufferSize + void FinalizePreparePack(uint64 &ioBufferSize) + { + // Remember where the vertices are going to start in the output buffer + JPH_ASSERT(IsAligned(ioBufferSize, 4)); + mVerticesStartIdx = size_t(ioBufferSize); + + // Add vertices to buffer + ioBufferSize += uint64(mVertexCount) * sizeof(VertexData); + + // Reserve the amount of memory we need for the vertices + mVertices.reserve(mVertexCount); + + // Set vertex map back to 'not found' + for (uint32 &v : mVertexMap) + v = cNotFound; } /// Pack the triangles in inContainer to ioBuffer. This stores the mMaterialIndex of a triangle in the 8 bit flags. - /// Returns uint(-1) on error. - uint Pack(const IndexedTriangleList &inTriangles, ByteBuffer &ioBuffer, const char *&outError) + /// Returns size_t(-1) on error. + size_t Pack(const IndexedTriangle *inTriangles, uint inNumTriangles, bool inStoreUserData, ByteBuffer &ioBuffer, const char *&outError) { - // Determine position of triangles start - uint offset = (uint)ioBuffer.size(); + JPH_ASSERT(inNumTriangles > 0); - // Update stats - uint tri_count = (uint)inTriangles.size(); - mNumTriangles += tri_count; + // Determine position of triangles start + size_t triangle_block_start = ioBuffer.size(); // Allocate triangle block header TriangleBlockHeader *header = ioBuffer.Allocate(); // Compute first vertex that this batch will use (ensuring there's enough room if none of the vertices are shared) - uint start_vertex = Clamp((int)mVertices.size() - 256 + (int)tri_count * 3, 0, (int)mVertices.size()); + uint start_vertex = Clamp((int)mVertices.size() - 256 + (int)inNumTriangles * 3, 0, (int)mVertices.size()); - // Store the start vertex offset, this will later be patched to give the delta offset relative to the triangle block - mOffsetsToPatch.push_back(uint((uint8 *)&header->mOffsetToVertices - &ioBuffer[0])); - header->mOffsetToVertices = start_vertex * sizeof(VertexData); + // Store the start vertex offset relative to TriangleBlockHeader + size_t offset_to_vertices = mVerticesStartIdx - triangle_block_start + size_t(start_vertex) * sizeof(VertexData); + if (offset_to_vertices & OFFSET_NON_SIGNIFICANT_MASK) + { + outError = "TriangleCodecIndexed8BitPackSOA4Flags: Internal Error: Offset has non-significant bits set"; + return size_t(-1); + } + offset_to_vertices >>= OFFSET_NON_SIGNIFICANT_BITS; + if (offset_to_vertices > OFFSET_TO_VERTICES_MASK) + { + outError = "TriangleCodecIndexed8BitPackSOA4Flags: Offset to vertices doesn't fit. Too much data."; + return size_t(-1); + } + header->mFlags = uint32(offset_to_vertices); + + // When we store user data we need to store the offset to the user data in TriangleBlocks + uint padded_triangle_count = AlignUp(inNumTriangles, 4); + if (inStoreUserData) + { + uint32 num_blocks = padded_triangle_count >> 2; + JPH_ASSERT(num_blocks <= OFFSET_TO_USERDATA_MASK); + header->mFlags |= num_blocks << OFFSET_TO_VERTICES_BITS; + } // Pack vertices - uint padded_triangle_count = AlignUp(tri_count, 4); for (uint t = 0; t < padded_triangle_count; t += 4) { TriangleBlock *block = ioBuffer.Allocate(); @@ -137,12 +247,12 @@ public: for (uint block_tri_idx = 0; block_tri_idx < 4; ++block_tri_idx) { // Fetch vertex index. Create degenerate triangles for padding triangles. - bool triangle_available = t + block_tri_idx < tri_count; - uint32 src_vertex_index = triangle_available? inTriangles[t + block_tri_idx].mIdx[vertex_nr] : inTriangles[tri_count - 1].mIdx[0]; + bool triangle_available = t + block_tri_idx < inNumTriangles; + uint32 src_vertex_index = triangle_available? inTriangles[t + block_tri_idx].mIdx[vertex_nr] : inTriangles[inNumTriangles - 1].mIdx[0]; // Check if we've seen this vertex before and if it is in the range that we can encode uint32 &vertex_index = mVertexMap[src_vertex_index]; - if (vertex_index == 0xffffffff || vertex_index < start_vertex) + if (vertex_index == cNotFound || vertex_index < start_vertex) { // Add vertex vertex_index = (uint32)mVertices.size(); @@ -154,7 +264,7 @@ public: if (vertex_offset > 0xff) { outError = "TriangleCodecIndexed8BitPackSOA4Flags: Offset doesn't fit in 8 bit"; - return uint(-1); + return size_t(-1); } block->mIndices[vertex_nr][block_tri_idx] = (uint8)vertex_offset; @@ -163,29 +273,34 @@ public: if (flags > 0xff) { outError = "TriangleCodecIndexed8BitPackSOA4Flags: Material index doesn't fit in 8 bit"; - return uint(-1); + return size_t(-1); } block->mFlags[block_tri_idx] = (uint8)flags; } } - return offset; + // Store user data + if (inStoreUserData) + { + uint32 *user_data = ioBuffer.Allocate(inNumTriangles); + for (uint t = 0; t < inNumTriangles; ++t) + user_data[t] = inTriangles[t].mUserData; + } + + return triangle_block_start; } /// After all triangles have been packed, this finalizes the header and triangle buffer void Finalize(const VertexList &inVertices, TriangleHeader *ioHeader, ByteBuffer &ioBuffer) const { + // Assert that our reservations were correct + JPH_ASSERT(mVertices.size() == mVertexCount); + JPH_ASSERT(ioBuffer.size() == mVerticesStartIdx); + // Check if anything to do if (mVertices.empty()) return; - // Align buffer to 4 bytes - uint vertices_idx = (uint)ioBuffer.Align(4); - - // Patch the offsets - for (uint o : mOffsetsToPatch) - *ioBuffer.Get(o) += vertices_idx - o; - // Calculate bounding box AABox bounds; for (uint32 v : mVertices) @@ -213,17 +328,17 @@ public: private: using VertexMap = Array; - uint mNumTriangles = 0; - Array mVertices; ///< Output vertices as an index into the original vertex list (inVertices), sorted according to occurrence - VertexMap mVertexMap; ///< Maps from the original mesh vertex index (inVertices) to the index in our output vertices (mVertices) - Array mOffsetsToPatch; ///< Offsets to the vertex buffer that need to be patched in once all nodes have been packed + uint32 mVertexCount = 0; ///< Number of vertices calculated during PreparePack + size_t mVerticesStartIdx = 0; ///< Start of the vertices in the output buffer, calculated during PreparePack + Array mVertices; ///< Output vertices as an index into the original vertex list (inVertices), sorted according to occurrence + VertexMap mVertexMap; ///< Maps from the original mesh vertex index (inVertices) to the index in our output vertices (mVertices) }; /// This class is used to decode and decompress triangle data packed by the EncodingContext class DecodingContext { private: - /// Private helper functions to unpack the 1 vertex of 4 triangles (outX contains the x coordinate of triangle 0 .. 3 etc.) + /// Private helper function to unpack the 1 vertex of 4 triangles (outX contains the x coordinate of triangle 0 .. 3 etc.) JPH_INLINE void Unpack(const VertexData *inVertices, UVec4Arg inIndex, Vec4 &outX, Vec4 &outY, Vec4 &outZ) const { // Get compressed data @@ -241,6 +356,28 @@ public: outZ = Vec4::sFusedMultiplyAdd(zc.ToFloat(), mScaleZ, mOffsetZ); } + /// Private helper function to unpack 4 triangles from a triangle block + JPH_INLINE void Unpack(const TriangleBlock *inBlock, const VertexData *inVertices, Vec4 &outX1, Vec4 &outY1, Vec4 &outZ1, Vec4 &outX2, Vec4 &outY2, Vec4 &outZ2, Vec4 &outX3, Vec4 &outY3, Vec4 &outZ3) const + { + // Get the indices for the three vertices (reads 4 bytes extra, but these are the flags so that's ok) + UVec4 indices = UVec4::sLoadInt4(reinterpret_cast(&inBlock->mIndices[0])); + UVec4 iv1 = indices.Expand4Byte0(); + UVec4 iv2 = indices.Expand4Byte4(); + UVec4 iv3 = indices.Expand4Byte8(); + + #ifdef JPH_CPU_BIG_ENDIAN + // On big endian systems we need to reverse the bytes + iv1 = iv1.Swizzle(); + iv2 = iv2.Swizzle(); + iv3 = iv3.Swizzle(); + #endif + + // Decompress the triangle data + Unpack(inVertices, iv1, outX1, outY1, outZ1); + Unpack(inVertices, iv2, outX2, outY2, outZ2); + Unpack(inVertices, iv3, outX3, outY3, outZ3); + } + public: JPH_INLINE explicit DecodingContext(const TriangleHeader *inHeader) : mOffsetX(Vec4::sReplicate(inHeader->mOffset.x)), @@ -257,25 +394,17 @@ public: { JPH_ASSERT(inNumTriangles > 0); const TriangleBlockHeader *header = reinterpret_cast(inTriangleStart); - const VertexData *vertices = header->GetVertexData(); + const VertexData *vertices = header->GetVertexData(); const TriangleBlock *t = header->GetTriangleBlock(); const TriangleBlock *end = t + ((inNumTriangles + 3) >> 2); - + int triangles_left = inNumTriangles; do { - // Get the indices for the three vertices (reads 4 bytes extra, but these are the flags so that's ok) - UVec4 indices = UVec4::sLoadInt4(reinterpret_cast(&t->mIndices[0])); - UVec4 iv1 = indices.Expand4Byte0(); - UVec4 iv2 = indices.Expand4Byte4(); - UVec4 iv3 = indices.Expand4Byte8(); - - // Decompress the triangle data + // Unpack the vertices for 4 triangles Vec4 v1x, v1y, v1z, v2x, v2y, v2z, v3x, v3y, v3z; - Unpack(vertices, iv1, v1x, v1y, v1z); - Unpack(vertices, iv2, v2x, v2y, v2z); - Unpack(vertices, iv3, v3x, v3y, v3z); + Unpack(t, vertices, v1x, v1y, v1z, v2x, v2y, v2z, v3x, v3y, v3z); // Transpose it so we get normal vectors Mat44 v1 = Mat44(v1x, v1y, v1z, Vec4::sZero()).Transposed(); @@ -291,7 +420,7 @@ public: } ++t; - } + } while (t < end); } @@ -300,7 +429,7 @@ public: { JPH_ASSERT(inNumTriangles > 0); const TriangleBlockHeader *header = reinterpret_cast(inTriangleStart); - const VertexData *vertices = header->GetVertexData(); + const VertexData *vertices = header->GetVertexData(); const TriangleBlock *t = header->GetTriangleBlock(); const TriangleBlock *end = t + ((inNumTriangles + 3) >> 2); @@ -310,17 +439,9 @@ public: UVec4 start_triangle_idx = UVec4::sZero(); do { - // Get the indices for the three vertices (reads 4 bytes extra, but these are the flags so that's ok) - UVec4 indices = UVec4::sLoadInt4(reinterpret_cast(&t->mIndices[0])); - UVec4 iv1 = indices.Expand4Byte0(); - UVec4 iv2 = indices.Expand4Byte4(); - UVec4 iv3 = indices.Expand4Byte8(); - - // Decompress the triangle data + // Unpack the vertices for 4 triangles Vec4 v1x, v1y, v1z, v2x, v2y, v2z, v3x, v3y, v3z; - Unpack(vertices, iv1, v1x, v1y, v1z); - Unpack(vertices, iv2, v2x, v2y, v2z); - Unpack(vertices, iv3, v3x, v3y, v3z); + Unpack(t, vertices, v1x, v1y, v1z, v2x, v2y, v2z, v3x, v3y, v3z); // Perform ray vs triangle test Vec4 distance = RayTriangle4(inRayOrigin, inRayDirection, v1x, v1y, v1z, v2x, v2y, v2z, v3x, v3y, v3z); @@ -336,7 +457,7 @@ public: // Next block ++t; start_triangle_idx += UVec4::sReplicate(4); - } + } while (t < end); // Get the smallest component @@ -379,14 +500,22 @@ public: outV3 = trans.GetAxisZ(); } + /// Get user data for a triangle + JPH_INLINE uint32 GetUserData(const void *inTriangleStart, uint32 inTriangleIdx) const + { + const TriangleBlockHeader *header = reinterpret_cast(inTriangleStart); + const uint32 *user_data = header->GetUserData(); + return user_data != nullptr? user_data[inTriangleIdx] : 0; + } + /// Get flags for entire triangle block - JPH_INLINE static void sGetFlags(const void *inTriangleStart, uint32 inNumTriangles, uint8 *outTriangleFlags) + JPH_INLINE static void sGetFlags(const void *inTriangleStart, uint32 inNumTriangles, uint8 *outTriangleFlags) { JPH_ASSERT(inNumTriangles > 0); const TriangleBlockHeader *header = reinterpret_cast(inTriangleStart); const TriangleBlock *t = header->GetTriangleBlock(); const TriangleBlock *end = t + ((inNumTriangles + 3) >> 2); - + int triangles_left = inNumTriangles; do { @@ -394,7 +523,7 @@ public: *outTriangleFlags++ = t->mFlags[i]; ++t; - } + } while (t < end); } @@ -406,7 +535,7 @@ public: return first_block[inTriangleIndex >> 2].mFlags[inTriangleIndex & 0b11]; } - /// Unpacks triangles and flags, convencience function + /// Unpacks triangles and flags, convenience function JPH_INLINE void Unpack(const void *inTriangleStart, uint32 inNumTriangles, Vec3 *outTriangles, uint8 *outTriangleFlags) const { Unpack(inTriangleStart, inNumTriangles, outTriangles); diff --git a/lib/zphysics/libs/Jolt/ConfigurationString.h b/lib/zphysics/libs/Jolt/ConfigurationString.h index b6556ce..1ff1969 100644 --- a/lib/zphysics/libs/Jolt/ConfigurationString.h +++ b/lib/zphysics/libs/Jolt/ConfigurationString.h @@ -14,8 +14,23 @@ inline const char *GetConfigurationString() "x86 " #elif defined(JPH_CPU_ARM) "ARM " -#elif defined(JPH_PLATFORM_WASM) +#elif defined(JPH_CPU_RISCV) + "RISC-V " +#elif defined(JPH_CPU_PPC) + "PowerPC " + #ifdef JPH_CPU_BIG_ENDIAN + "(Big Endian) " + #else + "(Little Endian) " + #endif +#elif defined(JPH_CPU_LOONGARCH) + "LoongArch " +#elif defined(JPH_CPU_E2K) + "E2K " +#elif defined(JPH_CPU_WASM) "WASM " +#else + #error Unknown CPU architecture #endif #if JPH_CPU_ADDRESS_BITS == 64 "64-bit " @@ -62,8 +77,31 @@ inline const char *GetConfigurationString() #ifdef JPH_FLOATING_POINT_EXCEPTIONS_ENABLED "(FP Exceptions) " #endif -#ifdef _DEBUG +#ifdef JPH_DEBUG_RENDERER + "(Debug Renderer) " +#endif +#ifdef JPH_PROFILE_ENABLED + "(Profile) " +#endif +#if defined(JPH_OBJECT_LAYER_BITS) && JPH_OBJECT_LAYER_BITS == 32 + "(32-bit ObjectLayer) " +#else + "(16-bit ObjectLayer) " +#endif +#ifdef JPH_ENABLE_ASSERTS + "(Assertions) " +#endif +#ifdef JPH_OBJECT_STREAM + "(ObjectStream) " +#endif +#ifdef JPH_DEBUG "(Debug) " +#endif +#if defined(__cpp_rtti) && __cpp_rtti + "(C++ RTTI) " +#endif +#if defined(__cpp_exceptions) && __cpp_exceptions + "(C++ Exceptions) " #endif ; } diff --git a/lib/zphysics/libs/Jolt/Core/ARMNeon.h b/lib/zphysics/libs/Jolt/Core/ARMNeon.h index ee4d552..6273945 100644 --- a/lib/zphysics/libs/Jolt/Core/ARMNeon.h +++ b/lib/zphysics/libs/Jolt/Core/ARMNeon.h @@ -6,13 +6,22 @@ #ifdef JPH_USE_NEON +// Constructing NEON values #ifdef JPH_COMPILER_MSVC - JPH_NAMESPACE_BEGIN - - // Constructing NEON values #define JPH_NEON_INT32x4(v1, v2, v3, v4) { int64_t(v1) + (int64_t(v2) << 32), int64_t(v3) + (int64_t(v4) << 32) } #define JPH_NEON_UINT32x4(v1, v2, v3, v4) { uint64_t(v1) + (uint64_t(v2) << 32), uint64_t(v3) + (uint64_t(v4) << 32) } #define JPH_NEON_INT8x16(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) { int64_t(v1) + (int64_t(v2) << 8) + (int64_t(v3) << 16) + (int64_t(v4) << 24) + (int64_t(v5) << 32) + (int64_t(v6) << 40) + (int64_t(v7) << 48) + (int64_t(v8) << 56), int64_t(v9) + (int64_t(v10) << 8) + (int64_t(v11) << 16) + (int64_t(v12) << 24) + (int64_t(v13) << 32) + (int64_t(v14) << 40) + (int64_t(v15) << 48) + (int64_t(v16) << 56) } + #define JPH_NEON_UINT8x16(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) { uint64_t(v1) + (uint64_t(v2) << 8) + (uint64_t(v3) << 16) + (uint64_t(v4) << 24) + (uint64_t(v5) << 32) + (uint64_t(v6) << 40) + (uint64_t(v7) << 48) + (uint64_t(v8) << 56), uint64_t(v9) + (uint64_t(v10) << 8) + (uint64_t(v11) << 16) + (uint64_t(v12) << 24) + (uint64_t(v13) << 32) + (uint64_t(v14) << 40) + (uint64_t(v15) << 48) + (uint64_t(v16) << 56) } +#else + #define JPH_NEON_INT32x4(v1, v2, v3, v4) { v1, v2, v3, v4 } + #define JPH_NEON_UINT32x4(v1, v2, v3, v4) { v1, v2, v3, v4 } + #define JPH_NEON_INT8x16(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16 } + #define JPH_NEON_UINT8x16(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16 } +#endif + +// MSVC and GCC prior to version 12 don't define __builtin_shufflevector +#if defined(JPH_COMPILER_MSVC) || (defined(JPH_COMPILER_GCC) && __GNUC__ < 12) + JPH_NAMESPACE_BEGIN // Generic shuffle vector template template @@ -30,13 +39,13 @@ template <> JPH_INLINE float32x4_t NeonShuffleFloat32x4<0, 1, 2, 2>(float32x4_t inV1, float32x4_t inV2) { - return vcombine_f32(vget_low_f32(inV1), vdup_lane_s32(vget_high_f32(inV1), 0)); + return vcombine_f32(vget_low_f32(inV1), vdup_lane_f32(vget_high_f32(inV1), 0)); } template <> JPH_INLINE float32x4_t NeonShuffleFloat32x4<0, 1, 3, 3>(float32x4_t inV1, float32x4_t inV2) { - return vcombine_f32(vget_low_f32(inV1), vdup_lane_s32(vget_high_f32(inV1), 1)); + return vcombine_f32(vget_low_f32(inV1), vdup_lane_f32(vget_high_f32(inV1), 1)); } template <> @@ -48,13 +57,13 @@ template <> JPH_INLINE float32x4_t NeonShuffleFloat32x4<1, 0, 3, 2>(float32x4_t inV1, float32x4_t inV2) { - return vcombine_f32(vrev64_f32(vget_low_f32(inV1)), vrev64_f32(vget_high_f32(inV1))); + return vcombine_f32(vrev64_f32(vget_low_f32(inV1)), vrev64_f32(vget_high_f32(inV1))); } template <> JPH_INLINE float32x4_t NeonShuffleFloat32x4<2, 2, 1, 0>(float32x4_t inV1, float32x4_t inV2) { - return vcombine_f32(vdup_lane_s32(vget_high_f32(inV1), 0), vrev64_f32(vget_low_f32(inV1))); + return vcombine_f32(vdup_lane_f32(vget_high_f32(inV1), 0), vrev64_f32(vget_low_f32(inV1))); } template <> @@ -67,22 +76,19 @@ template <> JPH_INLINE float32x4_t NeonShuffleFloat32x4<1, 2, 0, 0>(float32x4_t inV1, float32x4_t inV2) { - static int8x16_t table = JPH_NEON_INT8x16(0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x02, 0x03); - return vreinterpretq_f32_u8(vqtbl1q_u8(vreinterpretq_u8_f32(inV1), table)); + static uint8x16_t table = JPH_NEON_UINT8x16(0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x02, 0x03); + return vreinterpretq_f32_u8(vqtbl1q_u8(vreinterpretq_u8_f32(inV1), table)); } // Shuffle a vector #define JPH_NEON_SHUFFLE_F32x4(vec1, vec2, index1, index2, index3, index4) NeonShuffleFloat32x4(vec1, vec2) + #define JPH_NEON_SHUFFLE_U32x4(vec1, vec2, index1, index2, index3, index4) vreinterpretq_u32_f32((NeonShuffleFloat32x4(vreinterpretq_f32_u32(vec1), vreinterpretq_f32_u32(vec2)))) JPH_NAMESPACE_END #else - // Constructing NEON values - #define JPH_NEON_INT32x4(v1, v2, v3, v4) { v1, v2, v3, v4 } - #define JPH_NEON_UINT32x4(v1, v2, v3, v4) { v1, v2, v3, v4 } - #define JPH_NEON_INT8x16(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16 } - // Shuffle a vector #define JPH_NEON_SHUFFLE_F32x4(vec1, vec2, index1, index2, index3, index4) __builtin_shufflevector(vec1, vec2, index1, index2, index3, index4) + #define JPH_NEON_SHUFFLE_U32x4(vec1, vec2, index1, index2, index3, index4) __builtin_shufflevector(vec1, vec2, index1, index2, index3, index4) #endif #endif // JPH_USE_NEON diff --git a/lib/zphysics/libs/Jolt/Core/Array.h b/lib/zphysics/libs/Jolt/Core/Array.h new file mode 100644 index 0000000..b8dbce5 --- /dev/null +++ b/lib/zphysics/libs/Jolt/Core/Array.h @@ -0,0 +1,713 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2024 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#pragma once + +#include +#include + +#ifdef JPH_USE_STD_VECTOR + +JPH_SUPPRESS_WARNINGS_STD_BEGIN +#include +JPH_SUPPRESS_WARNINGS_STD_END + +JPH_NAMESPACE_BEGIN + +template > using Array = std::vector; + +JPH_NAMESPACE_END + +#else + +JPH_NAMESPACE_BEGIN + +/// Simple replacement for std::vector +/// +/// Major differences: +/// - Memory is not initialized to zero (this was causing a lot of page faults when deserializing large MeshShapes / HeightFieldShapes) +/// - Iterators are simple pointers (for now) +/// - No exception safety +/// - No specialization like std::vector has +/// - Not all functions have been implemented +template > +class [[nodiscard]] Array : private Allocator +{ +public: + using value_type = T; + using allocator_type = Allocator; + using size_type = size_t; + using difference_type = typename Allocator::difference_type; + using pointer = T *; + using const_pointer = const T *; + using reference = T &; + using const_reference = const T &; + + using const_iterator = const T *; + using iterator = T *; + + /// An iterator that traverses the array in reverse order + class rev_it + { + public: + /// Constructor + rev_it() = default; + explicit rev_it(T *inValue) : mValue(inValue) { } + + /// Copying + rev_it(const rev_it &) = default; + rev_it & operator = (const rev_it &) = default; + + /// Comparison + bool operator == (const rev_it &inRHS) const { return mValue == inRHS.mValue; } + bool operator != (const rev_it &inRHS) const { return mValue != inRHS.mValue; } + + /// Arithmetics + rev_it & operator ++ () { --mValue; return *this; } + rev_it operator ++ (int) { return rev_it(mValue--); } + rev_it & operator -- () { ++mValue; return *this; } + rev_it operator -- (int) { return rev_it(mValue++); } + + rev_it operator + (int inValue) { return rev_it(mValue - inValue); } + rev_it operator - (int inValue) { return rev_it(mValue + inValue); } + + rev_it & operator += (int inValue) { mValue -= inValue; return *this; } + rev_it & operator -= (int inValue) { mValue += inValue; return *this; } + + /// Access + T & operator * () const { return *mValue; } + T & operator -> () const { return *mValue; } + + private: + T * mValue; + }; + + /// A const iterator that traverses the array in reverse order + class crev_it + { + public: + /// Constructor + crev_it() = default; + explicit crev_it(const T *inValue) : mValue(inValue) { } + + /// Copying + crev_it(const crev_it &) = default; + explicit crev_it(const rev_it &inValue) : mValue(inValue.mValue) { } + crev_it & operator = (const crev_it &) = default; + crev_it & operator = (const rev_it &inRHS) { mValue = inRHS.mValue; return *this; } + + /// Comparison + bool operator == (const crev_it &inRHS) const { return mValue == inRHS.mValue; } + bool operator != (const crev_it &inRHS) const { return mValue != inRHS.mValue; } + + /// Arithmetics + crev_it & operator ++ () { --mValue; return *this; } + crev_it operator ++ (int) { return crev_it(mValue--); } + crev_it & operator -- () { ++mValue; return *this; } + crev_it operator -- (int) { return crev_it(mValue++); } + + crev_it operator + (int inValue) { return crev_it(mValue - inValue); } + crev_it operator - (int inValue) { return crev_it(mValue + inValue); } + + crev_it & operator += (int inValue) { mValue -= inValue; return *this; } + crev_it & operator -= (int inValue) { mValue += inValue; return *this; } + + /// Access + const T & operator * () const { return *mValue; } + const T & operator -> () const { return *mValue; } + + private: + const T * mValue; + }; + + using reverse_iterator = rev_it; + using const_reverse_iterator = crev_it; + +private: + /// Move elements from one location to another + inline void move(pointer inDestination, pointer inSource, size_type inCount) + { + if constexpr (std::is_trivially_copyable()) + memmove(inDestination, inSource, inCount * sizeof(T)); + else + { + if (inDestination < inSource) + { + for (T *destination_end = inDestination + inCount; inDestination < destination_end; ++inDestination, ++inSource) + { + new (inDestination) T(std::move(*inSource)); + inSource->~T(); + } + } + else + { + for (T *destination = inDestination + inCount - 1, *source = inSource + inCount - 1; destination >= inDestination; --destination, --source) + { + new (destination) T(std::move(*source)); + source->~T(); + } + } + } + } + + /// Reallocate the data block to inNewCapacity + inline void reallocate(size_type inNewCapacity) + { + JPH_ASSERT(inNewCapacity > 0 && inNewCapacity >= mSize); + + pointer ptr; + if constexpr (AllocatorHasReallocate::sValue) + { + // Reallocate data block + ptr = get_allocator().reallocate(mElements, mCapacity, inNewCapacity); + } + else + { + // Copy data to a new location + ptr = get_allocator().allocate(inNewCapacity); + if (mElements != nullptr) + { + move(ptr, mElements, mSize); + get_allocator().deallocate(mElements, mCapacity); + } + } + mElements = ptr; + mCapacity = inNewCapacity; + } + + /// Destruct elements [inStart, inEnd - 1] + inline void destruct(size_type inStart, size_type inEnd) + { + if constexpr (!std::is_trivially_destructible()) + if (inStart < inEnd) + for (T *element = mElements + inStart, *element_end = mElements + inEnd; element < element_end; ++element) + element->~T(); + } + +public: + /// Reserve array space + inline void reserve(size_type inNewSize) + { + if (mCapacity < inNewSize) + reallocate(inNewSize); + } + + /// Resize array to new length + inline void resize(size_type inNewSize) + { + destruct(inNewSize, mSize); + reserve(inNewSize); + + if constexpr (!std::is_trivially_constructible()) + for (T *element = mElements + mSize, *element_end = mElements + inNewSize; element < element_end; ++element) + new (element) T; + mSize = inNewSize; + } + + /// Resize array to new length and initialize all elements with inValue + inline void resize(size_type inNewSize, const T &inValue) + { + JPH_ASSERT(&inValue < mElements || &inValue >= mElements + mSize, "Can't pass an element from the array to resize"); + + destruct(inNewSize, mSize); + reserve(inNewSize); + + for (T *element = mElements + mSize, *element_end = mElements + inNewSize; element < element_end; ++element) + new (element) T(inValue); + mSize = inNewSize; + } + + /// Destruct all elements and set length to zero + inline void clear() + { + destruct(0, mSize); + mSize = 0; + } + +private: + /// Grow the array by at least inAmount elements + inline void grow(size_type inAmount = 1) + { + size_type min_size = mSize + inAmount; + if (min_size > mCapacity) + { + size_type new_capacity = max(min_size, mCapacity * 2); + reserve(new_capacity); + } + } + + /// Free memory + inline void free() + { + get_allocator().deallocate(mElements, mCapacity); + mElements = nullptr; + mCapacity = 0; + } + + /// Destroy all elements and free memory + inline void destroy() + { + if (mElements != nullptr) + { + clear(); + free(); + } + } + +public: + /// Replace the contents of this array with inBegin .. inEnd + template + inline void assign(Iterator inBegin, Iterator inEnd) + { + clear(); + reserve(size_type(std::distance(inBegin, inEnd))); + + for (Iterator element = inBegin; element != inEnd; ++element) + new (&mElements[mSize++]) T(*element); + } + + /// Replace the contents of this array with inList + inline void assign(std::initializer_list inList) + { + clear(); + reserve(size_type(inList.size())); + + for (const T &v : inList) + new (&mElements[mSize++]) T(v); + } + + /// Default constructor + Array() = default; + + /// Constructor with allocator + explicit inline Array(const Allocator &inAllocator) : + Allocator(inAllocator) + { + } + + /// Constructor with length + explicit inline Array(size_type inLength, const Allocator &inAllocator = { }) : + Allocator(inAllocator) + { + resize(inLength); + } + + /// Constructor with length and value + inline Array(size_type inLength, const T &inValue, const Allocator &inAllocator = { }) : + Allocator(inAllocator) + { + resize(inLength, inValue); + } + + /// Constructor from initializer list + inline Array(std::initializer_list inList, const Allocator &inAllocator = { }) : + Allocator(inAllocator) + { + assign(inList); + } + + /// Constructor from iterator + inline Array(const_iterator inBegin, const_iterator inEnd, const Allocator &inAllocator = { }) : + Allocator(inAllocator) + { + assign(inBegin, inEnd); + } + + /// Copy constructor + inline Array(const Array &inRHS) : + Allocator(inRHS.get_allocator()) + { + assign(inRHS.begin(), inRHS.end()); + } + + /// Move constructor + inline Array(Array &&inRHS) noexcept : + Allocator(std::move(inRHS.get_allocator())), + mSize(inRHS.mSize), + mCapacity(inRHS.mCapacity), + mElements(inRHS.mElements) + { + inRHS.mSize = 0; + inRHS.mCapacity = 0; + inRHS.mElements = nullptr; + } + + /// Destruct all elements + inline ~Array() + { + destroy(); + } + + /// Get the allocator + inline Allocator & get_allocator() + { + return *this; + } + + inline const Allocator &get_allocator() const + { + return *this; + } + + /// Add element to the back of the array + inline void push_back(const T &inValue) + { + JPH_ASSERT(&inValue < mElements || &inValue >= mElements + mSize, "Can't pass an element from the array to push_back"); + + grow(); + + T *element = mElements + mSize++; + new (element) T(inValue); + } + + inline void push_back(T &&inValue) + { + grow(); + + T *element = mElements + mSize++; + new (element) T(std::move(inValue)); + } + + /// Construct element at the back of the array + template + inline T & emplace_back(A &&... inValue) + { + grow(); + + T *element = mElements + mSize++; + new (element) T(std::forward(inValue)...); + return *element; + } + + /// Remove element from the back of the array + inline void pop_back() + { + JPH_ASSERT(mSize > 0); + mElements[--mSize].~T(); + } + + /// Returns true if there are no elements in the array + inline bool empty() const + { + return mSize == 0; + } + + /// Returns amount of elements in the array + inline size_type size() const + { + return mSize; + } + + /// Returns maximum amount of elements the array can hold + inline size_type capacity() const + { + return mCapacity; + } + + /// Reduce the capacity of the array to match its size + void shrink_to_fit() + { + if (mElements != nullptr) + { + if (mSize == 0) + free(); + else if (mCapacity > mSize) + reallocate(mSize); + } + } + + /// Swap the contents of two arrays + void swap(Array &inRHS) noexcept + { + std::swap(get_allocator(), inRHS.get_allocator()); + std::swap(mSize, inRHS.mSize); + std::swap(mCapacity, inRHS.mCapacity); + std::swap(mElements, inRHS.mElements); + } + + template + void insert(const_iterator inPos, Iterator inBegin, Iterator inEnd) + { + size_type num_elements = size_type(std::distance(inBegin, inEnd)); + if (num_elements > 0) + { + // After grow() inPos may be invalid + size_type first_element = inPos - mElements; + + grow(num_elements); + + T *element_begin = mElements + first_element; + T *element_end = element_begin + num_elements; + move(element_end, element_begin, mSize - first_element); + + for (T *element = element_begin; element < element_end; ++element, ++inBegin) + new (element) T(*inBegin); + + mSize += num_elements; + } + } + + void insert(const_iterator inPos, const T &inValue) + { + JPH_ASSERT(&inValue < mElements || &inValue >= mElements + mSize, "Can't pass an element from the array to insert"); + + // After grow() inPos may be invalid + size_type first_element = inPos - mElements; + + grow(); + + T *element = mElements + first_element; + move(element + 1, element, mSize - first_element); + + new (element) T(inValue); + mSize++; + } + + /// Remove one element from the array + iterator erase(const_iterator inIter) + { + size_type p = size_type(inIter - begin()); + JPH_ASSERT(p < mSize); + mElements[p].~T(); + if (p + 1 < mSize) + move(mElements + p, mElements + p + 1, mSize - p - 1); + --mSize; + return const_cast(inIter); + } + + /// Remove multiple element from the array + iterator erase(const_iterator inBegin, const_iterator inEnd) + { + size_type p = size_type(inBegin - begin()); + size_type n = size_type(inEnd - inBegin); + JPH_ASSERT(inEnd <= end()); + destruct(p, p + n); + if (p + n < mSize) + move(mElements + p, mElements + p + n, mSize - p - n); + mSize -= n; + return const_cast(inBegin); + } + + /// Iterators + inline const_iterator begin() const + { + return mElements; + } + + inline const_iterator end() const + { + return mElements + mSize; + } + + inline crev_it rbegin() const + { + return crev_it(mElements + mSize - 1); + } + + inline crev_it rend() const + { + return crev_it(mElements - 1); + } + + inline const_iterator cbegin() const + { + return begin(); + } + + inline const_iterator cend() const + { + return end(); + } + + inline crev_it crbegin() const + { + return rbegin(); + } + + inline crev_it crend() const + { + return rend(); + } + + inline iterator begin() + { + return mElements; + } + + inline iterator end() + { + return mElements + mSize; + } + + inline rev_it rbegin() + { + return rev_it(mElements + mSize - 1); + } + + inline rev_it rend() + { + return rev_it(mElements - 1); + } + + inline const T * data() const + { + return mElements; + } + + inline T * data() + { + return mElements; + } + + /// Access element + inline T & operator [] (size_type inIdx) + { + JPH_ASSERT(inIdx < mSize); + return mElements[inIdx]; + } + + inline const T & operator [] (size_type inIdx) const + { + JPH_ASSERT(inIdx < mSize); + return mElements[inIdx]; + } + + /// Access element + inline T & at(size_type inIdx) + { + JPH_ASSERT(inIdx < mSize); + return mElements[inIdx]; + } + + inline const T & at(size_type inIdx) const + { + JPH_ASSERT(inIdx < mSize); + return mElements[inIdx]; + } + + /// First element in the array + inline const T & front() const + { + JPH_ASSERT(mSize > 0); + return mElements[0]; + } + + inline T & front() + { + JPH_ASSERT(mSize > 0); + return mElements[0]; + } + + /// Last element in the array + inline const T & back() const + { + JPH_ASSERT(mSize > 0); + return mElements[mSize - 1]; + } + + inline T & back() + { + JPH_ASSERT(mSize > 0); + return mElements[mSize - 1]; + } + + /// Assignment operator + Array & operator = (const Array &inRHS) + { + if (static_cast(this) != static_cast(&inRHS)) + assign(inRHS.begin(), inRHS.end()); + + return *this; + } + + /// Assignment move operator + Array & operator = (Array &&inRHS) noexcept + { + if (static_cast(this) != static_cast(&inRHS)) + { + destroy(); + + get_allocator() = std::move(inRHS.get_allocator()); + + mSize = inRHS.mSize; + mCapacity = inRHS.mCapacity; + mElements = inRHS.mElements; + + inRHS.mSize = 0; + inRHS.mCapacity = 0; + inRHS.mElements = nullptr; + } + + return *this; + } + + /// Assignment operator + Array & operator = (std::initializer_list inRHS) + { + assign(inRHS); + + return *this; + } + + /// Comparing arrays + bool operator == (const Array &inRHS) const + { + if (mSize != inRHS.mSize) + return false; + for (size_type i = 0; i < mSize; ++i) + if (!(mElements[i] == inRHS.mElements[i])) + return false; + return true; + } + + bool operator != (const Array &inRHS) const + { + if (mSize != inRHS.mSize) + return true; + for (size_type i = 0; i < mSize; ++i) + if (mElements[i] != inRHS.mElements[i]) + return true; + return false; + } + + /// Get hash for this array + uint64 GetHash() const + { + // Hash length first + uint64 ret = Hash { } (uint32(size())); + + // Then hash elements + for (const T *element = mElements, *element_end = mElements + mSize; element < element_end; ++element) + HashCombine(ret, *element); + + return ret; + } + +private: + size_type mSize = 0; + size_type mCapacity = 0; + T * mElements = nullptr; +}; + +JPH_NAMESPACE_END + +JPH_SUPPRESS_WARNING_PUSH +JPH_CLANG_SUPPRESS_WARNING("-Wc++98-compat") + +namespace std +{ + /// Declare std::hash for Array + template + struct hash> + { + size_t operator () (const JPH::Array &inRHS) const + { + return std::size_t(inRHS.GetHash()); + } + }; +} + +JPH_SUPPRESS_WARNING_POP + +#endif // JPH_USE_STD_VECTOR diff --git a/lib/zphysics/libs/Jolt/Core/BinaryHeap.h b/lib/zphysics/libs/Jolt/Core/BinaryHeap.h new file mode 100644 index 0000000..3c542e7 --- /dev/null +++ b/lib/zphysics/libs/Jolt/Core/BinaryHeap.h @@ -0,0 +1,96 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2024 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#pragma once + +JPH_NAMESPACE_BEGIN + +/// Push a new element into a binary max-heap. +/// [inBegin, inEnd - 1) must be a a valid heap. Element inEnd - 1 will be inserted into the heap. The heap will be [inBegin, inEnd) after this call. +/// inPred is a function that returns true if the first element is less or equal than the second element. +/// See: https://en.wikipedia.org/wiki/Binary_heap +template +void BinaryHeapPush(Iterator inBegin, Iterator inEnd, Pred inPred) +{ + using diff_t = typename std::iterator_traits::difference_type; + using elem_t = typename std::iterator_traits::value_type; + + // New heap size + diff_t count = std::distance(inBegin, inEnd); + + // Start from the last element + diff_t current = count - 1; + while (current > 0) + { + // Get current element + elem_t ¤t_elem = *(inBegin + current); + + // Get parent element + diff_t parent = (current - 1) >> 1; + elem_t &parent_elem = *(inBegin + parent); + + // Sort them so that the parent is larger than the child + if (inPred(parent_elem, current_elem)) + { + std::swap(parent_elem, current_elem); + current = parent; + } + else + { + // When there's no change, we're done + break; + } + } +} + +/// Pop an element from a binary max-heap. +/// [inBegin, inEnd) must be a valid heap. The largest element will be removed from the heap. The heap will be [inBegin, inEnd - 1) after this call. +/// inPred is a function that returns true if the first element is less or equal than the second element. +/// See: https://en.wikipedia.org/wiki/Binary_heap +template +void BinaryHeapPop(Iterator inBegin, Iterator inEnd, Pred inPred) +{ + using diff_t = typename std::iterator_traits::difference_type; + + // Begin by moving the highest element to the end, this is the popped element + std::swap(*(inEnd - 1), *inBegin); + + // New heap size + diff_t count = std::distance(inBegin, inEnd) - 1; + + // Start from the root + diff_t largest = 0; + for (;;) + { + // Get first child + diff_t child = (largest << 1) + 1; + + // Check if we're beyond the end of the heap, if so the 2nd child is also beyond the end + if (child >= count) + break; + + // Remember the largest element from the previous iteration + diff_t prev_largest = largest; + + // Check if first child is bigger, if so select it + if (inPred(*(inBegin + largest), *(inBegin + child))) + largest = child; + + // Switch to the second child + ++child; + + // Check if second child is bigger, if so select it + if (child < count && inPred(*(inBegin + largest), *(inBegin + child))) + largest = child; + + // If there was no change, we're done + if (prev_largest == largest) + break; + + // Swap element + std::swap(*(inBegin + prev_largest), *(inBegin + largest)); + } +} + +JPH_NAMESPACE_END diff --git a/lib/zphysics/libs/Jolt/Core/ByteBuffer.h b/lib/zphysics/libs/Jolt/Core/ByteBuffer.h index 32e871a..610b151 100644 --- a/lib/zphysics/libs/Jolt/Core/ByteBuffer.h +++ b/lib/zphysics/libs/Jolt/Core/ByteBuffer.h @@ -9,7 +9,7 @@ JPH_NAMESPACE_BEGIN /// Underlying data type for ByteBuffer -using ByteBufferVector = std::vector>; +using ByteBufferVector = Array>; /// Simple byte buffer, aligned to a cache line class ByteBuffer : public ByteBufferVector @@ -23,7 +23,7 @@ public: // Calculate new size and resize buffer size_t s = AlignUp(size(), inSize); - resize(s); + resize(s, 0); return s; } @@ -41,7 +41,7 @@ public: // Construct elements for (Type *d = data, *d_end = data + inSize; d < d_end; ++d) - ::new (d) Type; + new (d) Type; // Return pointer return data; diff --git a/lib/zphysics/libs/Jolt/Core/Color.h b/lib/zphysics/libs/Jolt/Core/Color.h index ff55c7d..7706ca8 100644 --- a/lib/zphysics/libs/Jolt/Core/Color.h +++ b/lib/zphysics/libs/Jolt/Core/Color.h @@ -12,20 +12,21 @@ class Color; using ColorArg = Color; /// Class that holds an RGBA color with 8-bits per component -class [[nodiscard]] Color +class JPH_EXPORT_GCC_BUG_WORKAROUND [[nodiscard]] Color { public: /// Constructors Color() = default; ///< Intentionally not initialized for performance reasons Color(const Color &inRHS) = default; + Color & operator = (const Color &inRHS) = default; explicit constexpr Color(uint32 inColor) : mU32(inColor) { } constexpr Color(uint8 inRed, uint8 inGreen, uint8 inBlue, uint8 inAlpha = 255) : r(inRed), g(inGreen), b(inBlue), a(inAlpha) { } constexpr Color(ColorArg inRHS, uint8 inAlpha) : r(inRHS.r), g(inRHS.g), b(inRHS.b), a(inAlpha) { } - - /// Comparison + + /// Comparison inline bool operator == (ColorArg inRHS) const { return mU32 == inRHS.mU32; } inline bool operator != (ColorArg inRHS) const { return mU32 != inRHS.mU32; } - + /// Convert to uint32 uint32 GetUInt32() const { return mU32; } @@ -33,6 +34,12 @@ public: inline uint8 operator () (uint inIdx) const { JPH_ASSERT(inIdx < 4); return (&r)[inIdx]; } inline uint8 & operator () (uint inIdx) { JPH_ASSERT(inIdx < 4); return (&r)[inIdx]; } + /// Multiply two colors + inline Color operator * (const Color &inRHS) const { return Color(uint8((uint32(r) * inRHS.r) >> 8), uint8((uint32(g) * inRHS.g) >> 8), uint8((uint32(b) * inRHS.b) >> 8), uint8((uint32(a) * inRHS.a) >> 8)); } + + /// Multiply color with intensity in the range [0, 1] + inline Color operator * (float inIntensity) const { return Color(uint8(r * inIntensity), uint8(g * inIntensity), uint8(b * inIntensity), a); } + /// Convert to Vec4 with range [0, 1] inline Vec4 ToVec4() const { return Vec4(r, g, b, a) / 255.0f; } @@ -72,6 +79,6 @@ public: }; }; -static_assert(is_trivial(), "Is supposed to be a trivial type!"); +static_assert(std::is_trivial(), "Is supposed to be a trivial type!"); JPH_NAMESPACE_END diff --git a/lib/zphysics/libs/Jolt/Core/Core.h b/lib/zphysics/libs/Jolt/Core/Core.h index 4fec480..5be68b1 100644 --- a/lib/zphysics/libs/Jolt/Core/Core.h +++ b/lib/zphysics/libs/Jolt/Core/Core.h @@ -4,6 +4,72 @@ #pragma once +// Jolt library version +#define JPH_VERSION_MAJOR 5 +#define JPH_VERSION_MINOR 3 +#define JPH_VERSION_PATCH 1 + +// Determine which features the library was compiled with +#ifdef JPH_DOUBLE_PRECISION + #define JPH_VERSION_FEATURE_BIT_1 1 +#else + #define JPH_VERSION_FEATURE_BIT_1 0 +#endif +#ifdef JPH_CROSS_PLATFORM_DETERMINISTIC + #define JPH_VERSION_FEATURE_BIT_2 1 +#else + #define JPH_VERSION_FEATURE_BIT_2 0 +#endif +#ifdef JPH_FLOATING_POINT_EXCEPTIONS_ENABLED + #define JPH_VERSION_FEATURE_BIT_3 1 +#else + #define JPH_VERSION_FEATURE_BIT_3 0 +#endif +#ifdef JPH_PROFILE_ENABLED + #define JPH_VERSION_FEATURE_BIT_4 1 +#else + #define JPH_VERSION_FEATURE_BIT_4 0 +#endif +#ifdef JPH_EXTERNAL_PROFILE + #define JPH_VERSION_FEATURE_BIT_5 1 +#else + #define JPH_VERSION_FEATURE_BIT_5 0 +#endif +#ifdef JPH_DEBUG_RENDERER + #define JPH_VERSION_FEATURE_BIT_6 1 +#else + #define JPH_VERSION_FEATURE_BIT_6 0 +#endif +#ifdef JPH_DISABLE_TEMP_ALLOCATOR + #define JPH_VERSION_FEATURE_BIT_7 1 +#else + #define JPH_VERSION_FEATURE_BIT_7 0 +#endif +#ifdef JPH_DISABLE_CUSTOM_ALLOCATOR + #define JPH_VERSION_FEATURE_BIT_8 1 +#else + #define JPH_VERSION_FEATURE_BIT_8 0 +#endif +#if defined(JPH_OBJECT_LAYER_BITS) && JPH_OBJECT_LAYER_BITS == 32 + #define JPH_VERSION_FEATURE_BIT_9 1 +#else + #define JPH_VERSION_FEATURE_BIT_9 0 +#endif +#ifdef JPH_ENABLE_ASSERTS + #define JPH_VERSION_FEATURE_BIT_10 1 +#else + #define JPH_VERSION_FEATURE_BIT_10 0 +#endif +#ifdef JPH_OBJECT_STREAM + #define JPH_VERSION_FEATURE_BIT_11 1 +#else + #define JPH_VERSION_FEATURE_BIT_11 0 +#endif +#define JPH_VERSION_FEATURES (uint64(JPH_VERSION_FEATURE_BIT_1) | (JPH_VERSION_FEATURE_BIT_2 << 1) | (JPH_VERSION_FEATURE_BIT_3 << 2) | (JPH_VERSION_FEATURE_BIT_4 << 3) | (JPH_VERSION_FEATURE_BIT_5 << 4) | (JPH_VERSION_FEATURE_BIT_6 << 5) | (JPH_VERSION_FEATURE_BIT_7 << 6) | (JPH_VERSION_FEATURE_BIT_8 << 7) | (JPH_VERSION_FEATURE_BIT_9 << 8) | (JPH_VERSION_FEATURE_BIT_10 << 9) | (JPH_VERSION_FEATURE_BIT_11 << 10)) + +// Combine the version and features in a single ID +#define JPH_VERSION_ID ((JPH_VERSION_FEATURES << 24) | (JPH_VERSION_MAJOR << 16) | (JPH_VERSION_MINOR << 8) | JPH_VERSION_PATCH) + // Determine platform #if defined(JPH_PLATFORM_BLUE) // Correct define already defined, this overrides everything else @@ -17,13 +83,15 @@ #define JPH_PLATFORM_ANDROID #elif defined(__linux__) #define JPH_PLATFORM_LINUX +#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) + #define JPH_PLATFORM_BSD #elif defined(__APPLE__) - #include - #if defined(TARGET_OS_IPHONE) && !TARGET_OS_IPHONE - #define JPH_PLATFORM_MACOS - #else - #define JPH_PLATFORM_IOS - #endif + #include + #if defined(TARGET_OS_IPHONE) && !TARGET_OS_IPHONE + #define JPH_PLATFORM_MACOS + #else + #define JPH_PLATFORM_IOS + #endif #elif defined(__EMSCRIPTEN__) #define JPH_PLATFORM_WASM #endif @@ -112,25 +180,128 @@ #define JPH_VECTOR_ALIGNMENT 8 // 32-bit ARM does not support aligning on the stack on 16 byte boundaries #define JPH_DVECTOR_ALIGNMENT 8 #endif +#elif defined(__riscv) + // RISC-V CPU architecture + #define JPH_CPU_RISCV + #if __riscv_xlen == 64 + #define JPH_CPU_ADDRESS_BITS 64 + #define JPH_VECTOR_ALIGNMENT 16 + #define JPH_DVECTOR_ALIGNMENT 32 + #else + #define JPH_CPU_ADDRESS_BITS 32 + #define JPH_VECTOR_ALIGNMENT 16 + #define JPH_DVECTOR_ALIGNMENT 8 + #endif #elif defined(JPH_PLATFORM_WASM) // WebAssembly CPU architecture #define JPH_CPU_WASM - #define JPH_CPU_ADDRESS_BITS 32 + #if defined(__wasm64__) + #define JPH_CPU_ADDRESS_BITS 64 + #else + #define JPH_CPU_ADDRESS_BITS 32 + #endif #define JPH_VECTOR_ALIGNMENT 16 #define JPH_DVECTOR_ALIGNMENT 32 - #define JPH_DISABLE_CUSTOM_ALLOCATOR + #ifdef __wasm_simd128__ + #define JPH_USE_SSE + #define JPH_USE_SSE4_1 + #define JPH_USE_SSE4_2 + #endif +#elif defined(__powerpc__) || defined(__powerpc64__) + // PowerPC CPU architecture + #define JPH_CPU_PPC + #if defined(__powerpc64__) + #define JPH_CPU_ADDRESS_BITS 64 + #else + #define JPH_CPU_ADDRESS_BITS 32 + #endif + #ifdef _BIG_ENDIAN + #define JPH_CPU_BIG_ENDIAN + #endif + #define JPH_VECTOR_ALIGNMENT 16 + #define JPH_DVECTOR_ALIGNMENT 8 +#elif defined(__loongarch__) + // LoongArch CPU architecture + #define JPH_CPU_LOONGARCH + #if defined(__loongarch64) + #define JPH_CPU_ADDRESS_BITS 64 + #else + #define JPH_CPU_ADDRESS_BITS 32 + #endif + #define JPH_VECTOR_ALIGNMENT 16 + #define JPH_DVECTOR_ALIGNMENT 8 +#elif defined(__e2k__) + // E2K CPU architecture (MCST Elbrus 2000) + #define JPH_CPU_E2K + #define JPH_CPU_ADDRESS_BITS 64 + #define JPH_VECTOR_ALIGNMENT 16 + #define JPH_DVECTOR_ALIGNMENT 32 + + // Compiler flags on e2k arch determine CPU features + #if defined(__SSE__) && !defined(JPH_USE_SSE) + #define JPH_USE_SSE + #endif #else #error Unsupported CPU architecture #endif +// If this define is set, Jolt is compiled as a shared library +#ifdef JPH_SHARED_LIBRARY + #ifdef JPH_BUILD_SHARED_LIBRARY + // While building the shared library, we must export these symbols + #if defined(JPH_PLATFORM_WINDOWS) && !defined(JPH_COMPILER_MINGW) + #define JPH_EXPORT __declspec(dllexport) + #else + #define JPH_EXPORT __attribute__ ((visibility ("default"))) + #if defined(JPH_COMPILER_GCC) + // Prevents an issue with GCC attribute parsing (see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=69585) + #define JPH_EXPORT_GCC_BUG_WORKAROUND [[gnu::visibility("default")]] + #endif + #endif + #else + // When linking against Jolt, we must import these symbols + #if defined(JPH_PLATFORM_WINDOWS) && !defined(JPH_COMPILER_MINGW) + #define JPH_EXPORT __declspec(dllimport) + #else + #define JPH_EXPORT __attribute__ ((visibility ("default"))) + #if defined(JPH_COMPILER_GCC) + // Prevents an issue with GCC attribute parsing (see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=69585) + #define JPH_EXPORT_GCC_BUG_WORKAROUND [[gnu::visibility("default")]] + #endif + #endif + #endif +#else + // If the define is not set, we use static linking and symbols don't need to be imported or exported + #define JPH_EXPORT +#endif + +#ifndef JPH_EXPORT_GCC_BUG_WORKAROUND + #define JPH_EXPORT_GCC_BUG_WORKAROUND JPH_EXPORT +#endif + +// Macro used by the RTTI macros to not export a function +#define JPH_NO_EXPORT + // Pragmas to store / restore the warning state and to disable individual warnings #ifdef JPH_COMPILER_CLANG #define JPH_PRAGMA(x) _Pragma(#x) #define JPH_SUPPRESS_WARNING_PUSH JPH_PRAGMA(clang diagnostic push) #define JPH_SUPPRESS_WARNING_POP JPH_PRAGMA(clang diagnostic pop) #define JPH_CLANG_SUPPRESS_WARNING(w) JPH_PRAGMA(clang diagnostic ignored w) +#if __clang_major__ >= 13 + #define JPH_CLANG_13_PLUS_SUPPRESS_WARNING(w) JPH_CLANG_SUPPRESS_WARNING(w) +#else + #define JPH_CLANG_13_PLUS_SUPPRESS_WARNING(w) +#endif +#if __clang_major__ >= 16 + #define JPH_CLANG_16_PLUS_SUPPRESS_WARNING(w) JPH_CLANG_SUPPRESS_WARNING(w) +#else + #define JPH_CLANG_16_PLUS_SUPPRESS_WARNING(w) +#endif #else #define JPH_CLANG_SUPPRESS_WARNING(w) +#define JPH_CLANG_13_PLUS_SUPPRESS_WARNING(w) +#define JPH_CLANG_16_PLUS_SUPPRESS_WARNING(w) #endif #ifdef JPH_COMPILER_GCC #define JPH_PRAGMA(x) _Pragma(#x) @@ -176,17 +347,24 @@ JPH_CLANG_SUPPRESS_WARNING("-Wgnu-zero-variadic-macro-arguments") \ JPH_CLANG_SUPPRESS_WARNING("-Wdocumentation-unknown-command") \ JPH_CLANG_SUPPRESS_WARNING("-Wctad-maybe-unsupported") \ - JPH_CLANG_SUPPRESS_WARNING("-Wdeprecated-copy") \ + JPH_CLANG_SUPPRESS_WARNING("-Wswitch-default") \ + JPH_CLANG_13_PLUS_SUPPRESS_WARNING("-Wdeprecated-copy") \ + JPH_CLANG_13_PLUS_SUPPRESS_WARNING("-Wdeprecated-copy-with-dtor") \ + JPH_CLANG_16_PLUS_SUPPRESS_WARNING("-Wunsafe-buffer-usage") \ JPH_IF_NOT_ANDROID(JPH_CLANG_SUPPRESS_WARNING("-Wimplicit-int-float-conversion")) \ \ JPH_GCC_SUPPRESS_WARNING("-Wcomment") \ JPH_GCC_SUPPRESS_WARNING("-Winvalid-offsetof") \ JPH_GCC_SUPPRESS_WARNING("-Wclass-memaccess") \ + JPH_GCC_SUPPRESS_WARNING("-Wpedantic") \ + JPH_GCC_SUPPRESS_WARNING("-Wunused-parameter") \ + JPH_GCC_SUPPRESS_WARNING("-Wmaybe-uninitialized") \ \ JPH_MSVC_SUPPRESS_WARNING(4619) /* #pragma warning: there is no warning number 'XXXX' */ \ JPH_MSVC_SUPPRESS_WARNING(4514) /* 'X' : unreferenced inline function has been removed */ \ JPH_MSVC_SUPPRESS_WARNING(4710) /* 'X' : function not inlined */ \ JPH_MSVC_SUPPRESS_WARNING(4711) /* function 'X' selected for automatic inline expansion */ \ + JPH_MSVC_SUPPRESS_WARNING(4714) /* function 'X' marked as __forceinline not inlined */ \ JPH_MSVC_SUPPRESS_WARNING(4820) /* 'X': 'Y' bytes padding added after data member 'Z' */ \ JPH_MSVC_SUPPRESS_WARNING(4100) /* 'X' : unreferenced formal parameter */ \ JPH_MSVC_SUPPRESS_WARNING(4626) /* 'X' : assignment operator was implicitly defined as deleted because a base class assignment operator is inaccessible or deleted */ \ @@ -204,22 +382,26 @@ JPH_MSVC_SUPPRESS_WARNING(5219) /* implicit conversion from 'X' to 'Y', possible loss of data */ \ JPH_MSVC_SUPPRESS_WARNING(4826) /* Conversion from 'X *' to 'JPH::uint64' is sign-extended. This may cause unexpected runtime behavior. (32-bit) */ \ JPH_MSVC_SUPPRESS_WARNING(5264) /* 'X': 'const' variable is not used */ \ + JPH_MSVC_SUPPRESS_WARNING(4251) /* class 'X' needs to have DLL-interface to be used by clients of class 'Y' */ \ + JPH_MSVC_SUPPRESS_WARNING(4738) /* storing 32-bit float result in memory, possible loss of performance */ \ JPH_MSVC2019_SUPPRESS_WARNING(5246) /* the initialization of a subobject should be wrapped in braces */ // OS-specific includes #if defined(JPH_PLATFORM_WINDOWS) #define JPH_BREAKPOINT __debugbreak() -#elif defined(JPH_PLATFORM_BLUE) +#elif defined(JPH_PLATFORM_BLUE) // Configuration for a popular game console. - // This file is not distributed because it would violate an NDA. - // Creating one should only be a couple of minutes of work if you have the documentation for the platform - // (you only need to define JPH_BREAKPOINT, JPH_PLATFORM_BLUE_GET_TICKS and JPH_PLATFORM_BLUE_GET_TICK_FREQUENCY and include the right header). - #include -#elif defined(JPH_PLATFORM_LINUX) || defined(JPH_PLATFORM_ANDROID) || defined(JPH_PLATFORM_MACOS) || defined(JPH_PLATFORM_IOS) + // This file is not distributed because it would violate an NDA. + // Creating one should only be a couple of minutes of work if you have the documentation for the platform + // (you only need to define JPH_BREAKPOINT, JPH_PLATFORM_BLUE_GET_TICKS, JPH_PLATFORM_BLUE_MUTEX*, JPH_PLATFORM_BLUE_RWLOCK*, JPH_PLATFORM_BLUE_SEMAPHORE* and include the right header). + #include +#elif defined(JPH_PLATFORM_LINUX) || defined(JPH_PLATFORM_ANDROID) || defined(JPH_PLATFORM_MACOS) || defined(JPH_PLATFORM_IOS) || defined(JPH_PLATFORM_BSD) #if defined(JPH_CPU_X86) - #define JPH_BREAKPOINT __asm volatile ("int $0x3") - #elif defined(JPH_CPU_ARM) - #define JPH_BREAKPOINT __builtin_trap() + #define JPH_BREAKPOINT __asm volatile ("int $0x3") + #elif defined(JPH_CPU_ARM) || defined(JPH_CPU_RISCV) || defined(JPH_CPU_E2K) || defined(JPH_CPU_PPC) || defined(JPH_CPU_LOONGARCH) + #define JPH_BREAKPOINT __builtin_trap() + #else + #error Unknown CPU architecture #endif #elif defined(JPH_PLATFORM_WASM) #define JPH_BREAKPOINT do { } while (false) // Not supported @@ -227,9 +409,6 @@ #error Unknown platform #endif -// Crashes the application -#define JPH_CRASH do { int *ptr = nullptr; *ptr = 0; } while (false) - // Begin the JPH namespace #define JPH_NAMESPACE_BEGIN \ JPH_SUPPRESS_WARNING_PUSH \ @@ -244,29 +423,34 @@ // Suppress warnings generated by the standard template library #define JPH_SUPPRESS_WARNINGS_STD_BEGIN \ JPH_SUPPRESS_WARNING_PUSH \ + JPH_MSVC_SUPPRESS_WARNING(4365) \ JPH_MSVC_SUPPRESS_WARNING(4619) \ JPH_MSVC_SUPPRESS_WARNING(4710) \ JPH_MSVC_SUPPRESS_WARNING(4711) \ JPH_MSVC_SUPPRESS_WARNING(4820) \ JPH_MSVC_SUPPRESS_WARNING(4514) \ JPH_MSVC_SUPPRESS_WARNING(5262) \ - JPH_MSVC_SUPPRESS_WARNING(5264) + JPH_MSVC_SUPPRESS_WARNING(5264) \ + JPH_MSVC_SUPPRESS_WARNING(4738) \ + JPH_MSVC_SUPPRESS_WARNING(5045) #define JPH_SUPPRESS_WARNINGS_STD_END \ JPH_SUPPRESS_WARNING_POP // Standard C++ includes JPH_SUPPRESS_WARNINGS_STD_BEGIN -#include +#include +#include +#include #include #include #include #include #include -JPH_SUPPRESS_WARNINGS_STD_END -#include -#include -#include +#include +#ifdef JPH_COMPILER_MSVC + #include // for alloca +#endif #if defined(JPH_USE_SSE) #include #elif defined(JPH_USE_NEON) @@ -277,11 +461,11 @@ JPH_SUPPRESS_WARNINGS_STD_END #include #endif #endif +JPH_SUPPRESS_WARNINGS_STD_END JPH_NAMESPACE_BEGIN // Commonly used STL types -using std::pair; using std::min; using std::max; using std::abs; @@ -291,26 +475,20 @@ using std::floor; using std::trunc; using std::round; using std::fmod; -using std::swap; -using std::size; -using std::string; using std::string_view; using std::function; using std::numeric_limits; using std::isfinite; using std::isnan; -using std::is_trivial; -using std::is_trivially_constructible; -using std::is_trivially_destructible; using std::ostream; using std::istream; // Standard types using uint = unsigned int; -using uint8 = uint8_t; -using uint16 = uint16_t; -using uint32 = uint32_t; -using uint64 = uint64_t; +using uint8 = std::uint8_t; +using uint16 = std::uint16_t; +using uint32 = std::uint32_t; +using uint64 = std::uint64_t; // Assert sizes of types static_assert(sizeof(uint) >= 4, "Invalid size of uint"); @@ -320,9 +498,24 @@ static_assert(sizeof(uint32) == 4, "Invalid size of uint32"); static_assert(sizeof(uint64) == 8, "Invalid size of uint64"); static_assert(sizeof(void *) == (JPH_CPU_ADDRESS_BITS == 64? 8 : 4), "Invalid size of pointer" ); +// Determine if we want extra debugging code to be active +#if !defined(NDEBUG) && !defined(JPH_NO_DEBUG) + #define JPH_DEBUG +#endif + // Define inline macro -#if defined(JPH_COMPILER_CLANG) || defined(JPH_COMPILER_GCC) +#if defined(JPH_NO_FORCE_INLINE) + #define JPH_INLINE inline +#elif defined(JPH_COMPILER_CLANG) #define JPH_INLINE __inline__ __attribute__((always_inline)) +#elif defined(JPH_COMPILER_GCC) + // On gcc 14 using always_inline in debug mode causes error: "inlining failed in call to 'always_inline' 'XXX': function not considered for inlining" + // See: https://github.com/jrouwe/JoltPhysics/issues/1096 + #if __GNUC__ >= 14 && defined(JPH_DEBUG) + #define JPH_INLINE inline + #else + #define JPH_INLINE __inline__ __attribute__((always_inline)) + #endif #elif defined(JPH_COMPILER_MSVC) #define JPH_INLINE __forceinline #else @@ -345,9 +538,9 @@ static_assert(sizeof(void *) == (JPH_CPU_ADDRESS_BITS == 64? 8 : 4), "Invalid si // Stack allocation #define JPH_STACK_ALLOC(n) alloca(n) - -// Shorthand for #ifdef _DEBUG / #endif -#ifdef _DEBUG + +// Shorthand for #ifdef JPH_DEBUG / #endif +#ifdef JPH_DEBUG #define JPH_IF_DEBUG(...) __VA_ARGS__ #define JPH_IF_NOT_DEBUG(...) #else @@ -391,12 +584,18 @@ static_assert(sizeof(void *) == (JPH_CPU_ADDRESS_BITS == 64? 8 : 4), "Invalid si #define JPH_PRECISE_MATH_ON #define JPH_PRECISE_MATH_OFF #elif defined(JPH_COMPILER_CLANG) - // We compile without -ffast-math because it cannot be turned off for a single compilation unit - // On clang 14 and later we can turn off float contraction through a pragma, so if FMA is on we can disable it through this macro - #if __clang_major__ >= 14 && defined(JPH_USE_FMADD) - #define JPH_PRECISE_MATH_ON \ + // We compile without -ffast-math because pragma float_control(precise, on) doesn't seem to actually negate all of the -ffast-math effects and causes the unit tests to fail (even if the pragma is added to all files) + // On clang 14 and later we can turn off float contraction through a pragma (before it was buggy), so if FMA is on we can disable it through this macro + #if (defined(JPH_CPU_ARM) && !defined(JPH_PLATFORM_ANDROID) && __clang_major__ >= 16) || (defined(JPH_CPU_X86) && __clang_major__ >= 14) + #define JPH_PRECISE_MATH_ON \ + _Pragma("float_control(precise, on, push)") \ _Pragma("clang fp contract(off)") - #define JPH_PRECISE_MATH_OFF \ + #define JPH_PRECISE_MATH_OFF \ + _Pragma("float_control(pop)") + #elif __clang_major__ >= 14 && (defined(JPH_USE_FMADD) || defined(FP_FAST_FMA)) + #define JPH_PRECISE_MATH_ON \ + _Pragma("clang fp contract(off)") + #define JPH_PRECISE_MATH_OFF \ _Pragma("clang fp contract(on)") #else #define JPH_PRECISE_MATH_ON @@ -404,14 +603,32 @@ static_assert(sizeof(void *) == (JPH_CPU_ADDRESS_BITS == 64? 8 : 4), "Invalid si #endif #elif defined(JPH_COMPILER_MSVC) // Unfortunately there is no way to push the state of fp_contract, so we have to assume it was turned on before JPH_PRECISE_MATH_ON - #define JPH_PRECISE_MATH_ON \ - __pragma(float_control(precise, on, push)) \ + #define JPH_PRECISE_MATH_ON \ + __pragma(float_control(precise, on, push)) \ __pragma(fp_contract(off)) - #define JPH_PRECISE_MATH_OFF \ - __pragma(fp_contract(on)) \ + #define JPH_PRECISE_MATH_OFF \ + __pragma(fp_contract(on)) \ __pragma(float_control(pop)) #else #error Undefined #endif +// Check if Thread Sanitizer is enabled +#ifdef __has_feature + #if __has_feature(thread_sanitizer) + #define JPH_TSAN_ENABLED + #endif +#else + #ifdef __SANITIZE_THREAD__ + #define JPH_TSAN_ENABLED + #endif +#endif + +// Attribute to disable Thread Sanitizer for a particular function +#ifdef JPH_TSAN_ENABLED + #define JPH_TSAN_NO_SANITIZE __attribute__((no_sanitize("thread"))) +#else + #define JPH_TSAN_NO_SANITIZE +#endif + JPH_NAMESPACE_END diff --git a/lib/zphysics/libs/Jolt/Core/FPControlWord.h b/lib/zphysics/libs/Jolt/Core/FPControlWord.h index b66344d..9fceee4 100644 --- a/lib/zphysics/libs/Jolt/Core/FPControlWord.h +++ b/lib/zphysics/libs/Jolt/Core/FPControlWord.h @@ -8,7 +8,11 @@ JPH_NAMESPACE_BEGIN -#ifdef JPH_USE_SSE +#if defined(JPH_CPU_WASM) + +// Not supported + +#elif defined(JPH_USE_SSE) /// Helper class that needs to be put on the stack to update the state of the floating point control word. /// This state is kept per thread. @@ -28,7 +32,7 @@ public: } private: - uint mPrevState; + uint mPrevState; }; #elif defined(JPH_CPU_ARM) && defined(JPH_COMPILER_MSVC) @@ -71,11 +75,11 @@ public: FPControlWord() { uint64 val; - asm volatile("mrs %0, fpcr" : "=r" (val)); + asm volatile("mrs %0, fpcr" : "=r" (val)); mPrevState = val; val &= ~Mask; val |= Value; - asm volatile("msr fpcr, %0" : /* no output */ : "r" (val)); + asm volatile("msr fpcr, %0" : /* no output */ : "r" (val)); } ~FPControlWord() @@ -122,9 +126,13 @@ private: uint32 mPrevState; }; -#elif defined(JPH_CPU_WASM) +#elif defined(JPH_CPU_RISCV) -// Not supported +// RISC-V only implements manually checking if exceptions occurred by reading the fcsr register. It doesn't generate exceptions. + +#elif defined(JPH_CPU_PPC) || defined(JPH_CPU_LOONGARCH) + +// Not implemented right now #else diff --git a/lib/zphysics/libs/Jolt/Core/FPException.h b/lib/zphysics/libs/Jolt/Core/FPException.h index 0c5980c..9e22f54 100644 --- a/lib/zphysics/libs/Jolt/Core/FPException.h +++ b/lib/zphysics/libs/Jolt/Core/FPException.h @@ -10,10 +10,18 @@ JPH_NAMESPACE_BEGIN #ifdef JPH_FLOATING_POINT_EXCEPTIONS_ENABLED -#if defined(JPH_USE_SSE) +#if defined(JPH_CPU_WASM) -/// Enable floating point divide by zero exception and exceptions on invalid numbers -class FPExceptionsEnable : public FPControlWord<0, _MM_MASK_DIV_ZERO | _MM_MASK_INVALID> { }; +// Not supported +class FPExceptionsEnable { }; +class FPExceptionDisableInvalid { }; +class FPExceptionDisableDivByZero { }; +class FPExceptionDisableOverflow { }; + +#elif defined(JPH_USE_SSE) + +/// Enable floating point divide by zero exception, overflow exceptions and exceptions on invalid numbers +class FPExceptionsEnable : public FPControlWord<0, _MM_MASK_DIV_ZERO | _MM_MASK_INVALID | _MM_MASK_OVERFLOW> { }; /// Disable invalid floating point value exceptions class FPExceptionDisableInvalid : public FPControlWord<_MM_MASK_INVALID, _MM_MASK_INVALID> { }; @@ -21,10 +29,13 @@ class FPExceptionDisableInvalid : public FPControlWord<_MM_MASK_INVALID, _MM_MAS /// Disable division by zero floating point exceptions class FPExceptionDisableDivByZero : public FPControlWord<_MM_MASK_DIV_ZERO, _MM_MASK_DIV_ZERO> { }; +/// Disable floating point overflow exceptions +class FPExceptionDisableOverflow : public FPControlWord<_MM_MASK_OVERFLOW, _MM_MASK_OVERFLOW> { }; + #elif defined(JPH_CPU_ARM) && defined(JPH_COMPILER_MSVC) -/// Enable floating point divide by zero exception and exceptions on invalid numbers -class FPExceptionsEnable : public FPControlWord<0, _EM_INVALID | _EM_ZERODIVIDE> { }; +/// Enable floating point divide by zero exception, overflow exceptions and exceptions on invalid numbers +class FPExceptionsEnable : public FPControlWord<0, _EM_INVALID | _EM_ZERODIVIDE | _EM_OVERFLOW> { }; /// Disable invalid floating point value exceptions class FPExceptionDisableInvalid : public FPControlWord<_EM_INVALID, _EM_INVALID> { }; @@ -32,6 +43,9 @@ class FPExceptionDisableInvalid : public FPControlWord<_EM_INVALID, _EM_INVALID> /// Disable division by zero floating point exceptions class FPExceptionDisableDivByZero : public FPControlWord<_EM_ZERODIVIDE, _EM_ZERODIVIDE> { }; +/// Disable floating point overflow exceptions +class FPExceptionDisableOverflow : public FPControlWord<_EM_OVERFLOW, _EM_OVERFLOW> { }; + #elif defined(JPH_CPU_ARM) /// Invalid operation exception bit @@ -40,8 +54,11 @@ static constexpr uint64 FP_IOE = 1 << 8; /// Enable divide by zero exception bit static constexpr uint64 FP_DZE = 1 << 9; -/// Enable floating point divide by zero exception and exceptions on invalid numbers -class FPExceptionsEnable : public FPControlWord { }; +/// Enable floating point overflow bit +static constexpr uint64 FP_OFE = 1 << 10; + +/// Enable floating point divide by zero exception, overflow exceptions and exceptions on invalid numbers +class FPExceptionsEnable : public FPControlWord { }; /// Disable invalid floating point value exceptions class FPExceptionDisableInvalid : public FPControlWord<0, FP_IOE> { }; @@ -49,12 +66,16 @@ class FPExceptionDisableInvalid : public FPControlWord<0, FP_IOE> { }; /// Disable division by zero floating point exceptions class FPExceptionDisableDivByZero : public FPControlWord<0, FP_DZE> { }; -#elif defined(JPH_CPU_WASM) +/// Disable floating point overflow exceptions +class FPExceptionDisableOverflow : public FPControlWord<0, FP_OFE> { }; -// Not supported -class FPExceptionsEnable { }; -class FPExceptionDisableInvalid { }; -class FPExceptionDisableDivByZero { }; +#elif defined(JPH_CPU_RISCV) + +#error "RISC-V only implements manually checking if exceptions occurred by reading the fcsr register. It doesn't generate exceptions. JPH_FLOATING_POINT_EXCEPTIONS_ENABLED must be disabled." + +#elif defined(JPH_CPU_PPC) + +#error PowerPC floating point exception handling to be implemented. JPH_FLOATING_POINT_EXCEPTIONS_ENABLED must be disabled. #else @@ -68,6 +89,7 @@ class FPExceptionDisableDivByZero { }; class FPExceptionsEnable { }; class FPExceptionDisableInvalid { }; class FPExceptionDisableDivByZero { }; +class FPExceptionDisableOverflow { }; #endif diff --git a/lib/zphysics/libs/Jolt/Core/FPFlushDenormals.h b/lib/zphysics/libs/Jolt/Core/FPFlushDenormals.h index 12e6c36..74a2c10 100644 --- a/lib/zphysics/libs/Jolt/Core/FPFlushDenormals.h +++ b/lib/zphysics/libs/Jolt/Core/FPFlushDenormals.h @@ -8,7 +8,12 @@ JPH_NAMESPACE_BEGIN -#if defined(JPH_USE_SSE) +#if defined(JPH_CPU_WASM) || defined(JPH_CPU_RISCV) || defined(JPH_CPU_PPC) || defined(JPH_CPU_LOONGARCH) + +// Not supported +class FPFlushDenormals { }; + +#elif defined(JPH_USE_SSE) /// Helper class that needs to be put on the stack to enable flushing denormals to zero /// This can make floating point operations much faster when working with very small numbers @@ -16,6 +21,8 @@ class FPFlushDenormals : public FPControlWord<_MM_FLUSH_ZERO_ON, _MM_FLUSH_ZERO_ #elif defined(JPH_CPU_ARM) && defined(JPH_COMPILER_MSVC) +/// Helper class that needs to be put on the stack to enable flushing denormals to zero +/// This can make floating point operations much faster when working with very small numbers class FPFlushDenormals : public FPControlWord<_DN_FLUSH, _MCW_DN> { }; #elif defined(JPH_CPU_ARM) @@ -27,11 +34,6 @@ static constexpr uint64 FP_FZ = 1 << 24; /// This can make floating point operations much faster when working with very small numbers class FPFlushDenormals : public FPControlWord { }; -#elif defined(JPH_CPU_WASM) - -// Not supported -class FPFlushDenormals { }; - #else #error Unsupported CPU architecture diff --git a/lib/zphysics/libs/Jolt/Core/Factory.cpp b/lib/zphysics/libs/Jolt/Core/Factory.cpp index bbcdd95..1890c03 100644 --- a/lib/zphysics/libs/Jolt/Core/Factory.cpp +++ b/lib/zphysics/libs/Jolt/Core/Factory.cpp @@ -11,25 +11,25 @@ JPH_NAMESPACE_BEGIN Factory *Factory::sInstance = nullptr; void *Factory::CreateObject(const char *inName) -{ - const RTTI *ci = Find(inName); - return ci != nullptr? ci->CreateObject() : nullptr; +{ + const RTTI *ci = Find(inName); + return ci != nullptr? ci->CreateObject() : nullptr; } const RTTI *Factory::Find(const char *inName) -{ - ClassNameMap::iterator c = mClassNameMap.find(inName); - return c != mClassNameMap.end()? c->second : nullptr; +{ + ClassNameMap::iterator c = mClassNameMap.find(inName); + return c != mClassNameMap.end()? c->second : nullptr; } const RTTI *Factory::Find(uint32 inHash) -{ - ClassHashMap::iterator c = mClassHashMap.find(inHash); - return c != mClassHashMap.end()? c->second : nullptr; +{ + ClassHashMap::iterator c = mClassHashMap.find(inHash); + return c != mClassHashMap.end()? c->second : nullptr; } bool Factory::Register(const RTTI *inRTTI) -{ +{ // Check if we already know the type if (Find(inRTTI->GetName()) != nullptr) return true; @@ -49,6 +49,7 @@ bool Factory::Register(const RTTI *inRTTI) if (!Register(inRTTI->GetBaseClass(i))) return false; +#ifdef JPH_OBJECT_STREAM // Register attribute classes for (int i = 0; i < inRTTI->GetAttributeCount(); ++i) { @@ -56,12 +57,16 @@ bool Factory::Register(const RTTI *inRTTI) if (rtti != nullptr && !Register(rtti)) return false; } +#endif // JPH_OBJECT_STREAM return true; } bool Factory::Register(const RTTI **inRTTIs, uint inNumber) { + mClassHashMap.reserve(mClassHashMap.size() + inNumber); + mClassNameMap.reserve(mClassNameMap.size() + inNumber); + for (const RTTI **rtti = inRTTIs; rtti < inRTTIs + inNumber; ++rtti) if (!Register(*rtti)) return false; diff --git a/lib/zphysics/libs/Jolt/Core/Factory.h b/lib/zphysics/libs/Jolt/Core/Factory.h index d9f1ef0..557f381 100644 --- a/lib/zphysics/libs/Jolt/Core/Factory.h +++ b/lib/zphysics/libs/Jolt/Core/Factory.h @@ -9,8 +9,8 @@ JPH_NAMESPACE_BEGIN -/// Factory, to create RTTI objects -class Factory +/// This class is responsible for creating instances of classes based on their name or hash and is mainly used for deserialization of saved data. +class JPH_EXPORT Factory { public: JPH_OVERRIDE_NEW_DELETE diff --git a/lib/zphysics/libs/Jolt/Core/FixedSizeFreeList.h b/lib/zphysics/libs/Jolt/Core/FixedSizeFreeList.h index 6f3839b..51f2d13 100644 --- a/lib/zphysics/libs/Jolt/Core/FixedSizeFreeList.h +++ b/lib/zphysics/libs/Jolt/Core/FixedSizeFreeList.h @@ -34,17 +34,6 @@ private: const ObjectStorage & GetStorage(uint32 inObjectIndex) const { return mPages[inObjectIndex >> mPageShift][inObjectIndex & mObjectMask]; } ObjectStorage & GetStorage(uint32 inObjectIndex) { return mPages[inObjectIndex >> mPageShift][inObjectIndex & mObjectMask]; } - /// Number of objects that we currently have in the free list / new pages -#ifdef JPH_ENABLE_ASSERTS - atomic mNumFreeObjects; -#endif // JPH_ENABLE_ASSERTS - - /// Simple counter that makes the first free object pointer update with every CAS so that we don't suffer from the ABA problem - atomic mAllocationTag; - - /// Index of first free object, the first 32 bits of an object are used to point to the next free object - atomic mFirstFreeObjectAndTag; - /// Size (in objects) of a single page uint32 mPageSize; @@ -60,14 +49,27 @@ private: /// Total number of objects that have been allocated uint32 mNumObjectsAllocated; - /// The first free object to use when the free list is empty (may need to allocate a new page) - atomic mFirstFreeObjectInNewPage; - /// Array of pages of objects ObjectStorage ** mPages = nullptr; /// Mutex that is used to allocate a new page if the storage runs out - Mutex mPageMutex; + /// This variable is aligned to the cache line to prevent false sharing with + /// the constants used to index into the list via `Get()`. + alignas(JPH_CACHE_LINE_SIZE) Mutex mPageMutex; + + /// Number of objects that we currently have in the free list / new pages +#ifdef JPH_ENABLE_ASSERTS + atomic mNumFreeObjects; +#endif // JPH_ENABLE_ASSERTS + + /// Simple counter that makes the first free object pointer update with every CAS so that we don't suffer from the ABA problem + atomic mAllocationTag; + + /// Index of first free object, the first 32 bits of an object are used to point to the next free object + atomic mFirstFreeObjectAndTag; + + /// The first free object to use when the free list is empty (may need to allocate a new page) + atomic mFirstFreeObjectInNewPage; public: /// Invalid index diff --git a/lib/zphysics/libs/Jolt/Core/FixedSizeFreeList.inl b/lib/zphysics/libs/Jolt/Core/FixedSizeFreeList.inl index 6741441..3fe40b8 100644 --- a/lib/zphysics/libs/Jolt/Core/FixedSizeFreeList.inl +++ b/lib/zphysics/libs/Jolt/Core/FixedSizeFreeList.inl @@ -79,7 +79,7 @@ uint32 FixedSizeFreeList::ConstructObject(Parameters &&... inParameters) // Allocation successful JPH_IF_ENABLE_ASSERTS(mNumFreeObjects.fetch_sub(1, memory_order_relaxed);) ObjectStorage &storage = GetStorage(first_free); - ::new (&storage.mObject) Object(std::forward(inParameters)...); + new (&storage.mObject) Object(std::forward(inParameters)...); storage.mNextFreeObject.store(first_free, memory_order_release); return first_free; } @@ -97,7 +97,7 @@ uint32 FixedSizeFreeList::ConstructObject(Parameters &&... inParameters) // Allocation successful JPH_IF_ENABLE_ASSERTS(mNumFreeObjects.fetch_sub(1, memory_order_relaxed);) ObjectStorage &storage = GetStorage(first_free); - ::new (&storage.mObject) Object(std::forward(inParameters)...); + new (&storage.mObject) Object(std::forward(inParameters)...); storage.mNextFreeObject.store(first_free, memory_order_release); return first_free; } @@ -108,9 +108,13 @@ uint32 FixedSizeFreeList::ConstructObject(Parameters &&... inParameters) template void FixedSizeFreeList::AddObjectToBatch(Batch &ioBatch, uint32 inObjectIndex) { - JPH_ASSERT(GetStorage(inObjectIndex).mNextFreeObject.load(memory_order_relaxed) == inObjectIndex, "Trying to add a object to the batch that is already in a free list"); JPH_ASSERT(ioBatch.mNumObjects != uint32(-1), "Trying to reuse a batch that has already been freed"); + // Reset next index + atomic &next_free_object = GetStorage(inObjectIndex).mNextFreeObject; + JPH_ASSERT(next_free_object.load(memory_order_relaxed) == inObjectIndex, "Trying to add a object to the batch that is already in a free list"); + next_free_object.store(cInvalidObjectIndex, memory_order_release); + // Link object in batch to free if (ioBatch.mFirstObjectIndex == cInvalidObjectIndex) ioBatch.mFirstObjectIndex = inObjectIndex; @@ -126,7 +130,7 @@ void FixedSizeFreeList::DestructObjectBatch(Batch &ioBatch) if (ioBatch.mFirstObjectIndex != cInvalidObjectIndex) { // Call destructors - if constexpr (!is_trivially_destructible()) + if constexpr (!std::is_trivially_destructible()) { uint32 object_idx = ioBatch.mFirstObjectIndex; do @@ -161,7 +165,7 @@ void FixedSizeFreeList::DestructObjectBatch(Batch &ioBatch) // Mark the batch as freed #ifdef JPH_ENABLE_ASSERTS ioBatch.mNumObjects = uint32(-1); -#endif +#endif return; } } @@ -174,7 +178,7 @@ void FixedSizeFreeList::DestructObject(uint32 inObjectIndex) JPH_ASSERT(inObjectIndex != cInvalidObjectIndex); // Call destructor - ObjectStorage &storage = GetStorage(inObjectIndex); + ObjectStorage &storage = GetStorage(inObjectIndex); storage.mObject.~Object(); // Add to object free list diff --git a/lib/zphysics/libs/Jolt/Core/HashCombine.h b/lib/zphysics/libs/Jolt/Core/HashCombine.h index c38fe95..ab62084 100644 --- a/lib/zphysics/libs/Jolt/Core/HashCombine.h +++ b/lib/zphysics/libs/Jolt/Core/HashCombine.h @@ -17,8 +17,21 @@ inline uint64 HashBytes(const void *inData, uint inSize, uint64 inSeed = 0xcbf29 uint64 hash = inSeed; for (const uint8 *data = reinterpret_cast(inData); data < reinterpret_cast(inData) + inSize; ++data) { - hash = hash ^ uint64(*data); - hash = hash * 0x100000001b3UL; + hash ^= uint64(*data); + hash *= 0x100000001b3UL; + } + return hash; +} + +/// Calculate the FNV-1a hash of inString. +/// @see https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function +constexpr uint64 HashString(const char *inString, uint64 inSeed = 0xcbf29ce484222325UL) +{ + uint64 hash = inSeed; + for (const char *c = inString; *c != 0; ++c) + { + hash ^= uint64(*c); + hash *= 0x100000001b3UL; } return hash; } @@ -40,58 +53,182 @@ inline uint64 Hash64(uint64 inValue) return hash; } -/// @brief Helper function that hashes a single value into ioSeed -/// Taken from: https://stackoverflow.com/questions/2590677/how-do-i-combine-hash-values-in-c0x -template -inline void HashCombineHelper(size_t &ioSeed, const T &inValue) +/// Fallback hash function that calls T::GetHash() +template +struct Hash { - std::hash hasher; - ioSeed ^= hasher(inValue) + 0x9e3779b9 + (ioSeed << 6) + (ioSeed >> 2); + uint64 operator () (const T &inValue) const + { + return inValue.GetHash(); + } +}; + +/// A hash function for floats +template <> +struct Hash +{ + uint64 operator () (float inValue) const + { + float value = inValue == 0.0f? 0.0f : inValue; // Convert -0.0f to 0.0f + return HashBytes(&value, sizeof(value)); + } +}; + +/// A hash function for doubles +template <> +struct Hash +{ + uint64 operator () (double inValue) const + { + double value = inValue == 0.0? 0.0 : inValue; // Convert -0.0 to 0.0 + return HashBytes(&value, sizeof(value)); + } +}; + +/// A hash function for character pointers +template <> +struct Hash +{ + uint64 operator () (const char *inValue) const + { + return HashString(inValue); + } +}; + +/// A hash function for std::string_view +template <> +struct Hash +{ + uint64 operator () (const std::string_view &inValue) const + { + return HashBytes(inValue.data(), uint(inValue.size())); + } +}; + +/// A hash function for String +template <> +struct Hash +{ + uint64 operator () (const String &inValue) const + { + return HashBytes(inValue.data(), uint(inValue.size())); + } +}; + +/// A fallback function for generic pointers +template +struct Hash +{ + uint64 operator () (T *inValue) const + { + return HashBytes(&inValue, sizeof(inValue)); + } +}; + +/// Helper macro to define a hash function for trivial types +#define JPH_DEFINE_TRIVIAL_HASH(type) \ +template <> \ +struct Hash \ +{ \ + uint64 operator () (const type &inValue) const \ + { \ + return HashBytes(&inValue, sizeof(inValue)); \ + } \ +}; + +/// Commonly used types +JPH_DEFINE_TRIVIAL_HASH(char) +JPH_DEFINE_TRIVIAL_HASH(int) +JPH_DEFINE_TRIVIAL_HASH(uint32) +JPH_DEFINE_TRIVIAL_HASH(uint64) + +/// Helper function that hashes a single value into ioSeed +/// Based on https://github.com/jonmaiga/mx3 by Jon Maiga +template +inline void HashCombine(uint64 &ioSeed, const T &inValue) +{ + constexpr uint64 c = 0xbea225f9eb34556dUL; + + uint64 h = ioSeed; + uint64 x = Hash { } (inValue); + + // See: https://github.com/jonmaiga/mx3/blob/master/mx3.h + // mix_stream(h, x) + x *= c; + x ^= x >> 39; + h += x * c; + h *= c; + + // mix(h) + h ^= h >> 32; + h *= c; + h ^= h >> 29; + h *= c; + h ^= h >> 32; + h *= c; + h ^= h >> 29; + + ioSeed = h; } /// Hash combiner to use a custom struct in an unordered map or set /// /// Usage: /// -/// struct SomeHashKey +/// struct SomeHashKey /// { -/// std::string key1; -/// std::string key2; -/// bool key3; +/// std::string key1; +/// std::string key2; +/// bool key3; /// }; -/// +/// /// JPH_MAKE_HASHABLE(SomeHashKey, t.key1, t.key2, t.key3) -template -inline void HashCombine(std::size_t &ioSeed, Values... inValues) +template +inline uint64 HashCombineArgs(const FirstValue &inFirstValue, Values... inValues) { - // Hash all values together using a fold expression - (HashCombineHelper(ioSeed, inValues), ...); + // Prime the seed by hashing the first value + uint64 seed = Hash { } (inFirstValue); + + // Hash all remaining values together using a fold expression + (HashCombine(seed, inValues), ...); + + return seed; } -JPH_NAMESPACE_END - -JPH_SUPPRESS_WARNING_PUSH -JPH_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic") - #define JPH_MAKE_HASH_STRUCT(type, name, ...) \ struct [[nodiscard]] name \ { \ - std::size_t operator()(const type &t) const \ + ::JPH::uint64 operator()(const type &t) const \ { \ - std::size_t ret = 0; \ - ::JPH::HashCombine(ret, __VA_ARGS__); \ - return ret; \ - } \ - }; + return ::JPH::HashCombineArgs(__VA_ARGS__); \ + } \ + }; + +#define JPH_MAKE_STD_HASH(type) \ + JPH_SUPPRESS_WARNING_PUSH \ + JPH_SUPPRESS_WARNINGS \ + namespace std \ + { \ + template<> \ + struct [[nodiscard]] hash \ + { \ + size_t operator()(const type &t) const \ + { \ + return size_t(::JPH::Hash{ }(t)); \ + } \ + }; \ + } \ + JPH_SUPPRESS_WARNING_POP #define JPH_MAKE_HASHABLE(type, ...) \ JPH_SUPPRESS_WARNING_PUSH \ JPH_SUPPRESS_WARNINGS \ - namespace std \ + namespace JPH \ { \ - template<> \ - JPH_MAKE_HASH_STRUCT(type, hash, __VA_ARGS__) \ - } \ - JPH_SUPPRESS_WARNING_POP + template<> \ + JPH_MAKE_HASH_STRUCT(type, Hash, __VA_ARGS__) \ + } \ + JPH_SUPPRESS_WARNING_POP \ + JPH_MAKE_STD_HASH(type) -JPH_SUPPRESS_WARNING_POP +JPH_NAMESPACE_END diff --git a/lib/zphysics/libs/Jolt/Core/HashTable.h b/lib/zphysics/libs/Jolt/Core/HashTable.h new file mode 100644 index 0000000..d2d766f --- /dev/null +++ b/lib/zphysics/libs/Jolt/Core/HashTable.h @@ -0,0 +1,872 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2024 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#pragma once + +#include + +JPH_NAMESPACE_BEGIN + +/// Helper class for implementing an UnorderedSet or UnorderedMap +/// Based on CppCon 2017: Matt Kulukundis "Designing a Fast, Efficient, Cache-friendly Hash Table, Step by Step" +/// See: https://www.youtube.com/watch?v=ncHmEUmJZf4 +template +class HashTable +{ +public: + /// Properties + using value_type = KeyValue; + using size_type = uint32; + using difference_type = ptrdiff_t; + +private: + /// Base class for iterators + template + class IteratorBase + { + public: + /// Properties + using difference_type = typename Table::difference_type; + using value_type = typename Table::value_type; + using iterator_category = std::forward_iterator_tag; + + /// Copy constructor + IteratorBase(const IteratorBase &inRHS) = default; + + /// Assignment operator + IteratorBase & operator = (const IteratorBase &inRHS) = default; + + /// Iterator at start of table + explicit IteratorBase(Table *inTable) : + mTable(inTable), + mIndex(0) + { + while (mIndex < mTable->mMaxSize && (mTable->mControl[mIndex] & cBucketUsed) == 0) + ++mIndex; + } + + /// Iterator at specific index + IteratorBase(Table *inTable, size_type inIndex) : + mTable(inTable), + mIndex(inIndex) + { + } + + /// Prefix increment + Iterator & operator ++ () + { + JPH_ASSERT(IsValid()); + + do + { + ++mIndex; + } + while (mIndex < mTable->mMaxSize && (mTable->mControl[mIndex] & cBucketUsed) == 0); + + return static_cast(*this); + } + + /// Postfix increment + Iterator operator ++ (int) + { + Iterator result(mTable, mIndex); + ++(*this); + return result; + } + + /// Access to key value pair + const KeyValue & operator * () const + { + JPH_ASSERT(IsValid()); + return mTable->mData[mIndex]; + } + + /// Access to key value pair + const KeyValue * operator -> () const + { + JPH_ASSERT(IsValid()); + return mTable->mData + mIndex; + } + + /// Equality operator + bool operator == (const Iterator &inRHS) const + { + return mIndex == inRHS.mIndex && mTable == inRHS.mTable; + } + + /// Inequality operator + bool operator != (const Iterator &inRHS) const + { + return !(*this == inRHS); + } + + /// Check that the iterator is valid + bool IsValid() const + { + return mIndex < mTable->mMaxSize + && (mTable->mControl[mIndex] & cBucketUsed) != 0; + } + + Table * mTable; + size_type mIndex; + }; + + /// Get the maximum number of elements that we can support given a number of buckets + static constexpr size_type sGetMaxLoad(size_type inBucketCount) + { + return uint32((cMaxLoadFactorNumerator * inBucketCount) / cMaxLoadFactorDenominator); + } + + /// Update the control value for a bucket + JPH_INLINE void SetControlValue(size_type inIndex, uint8 inValue) + { + JPH_ASSERT(inIndex < mMaxSize); + mControl[inIndex] = inValue; + + // Mirror the first 15 bytes to the 15 bytes beyond mMaxSize + // Note that this is equivalent to: + // if (inIndex < 15) + // mControl[inIndex + mMaxSize] = inValue + // else + // mControl[inIndex] = inValue + // Which performs a needless write if inIndex >= 15 but at least it is branch-less + mControl[((inIndex - 15) & (mMaxSize - 1)) + 15] = inValue; + } + + /// Get the index and control value for a particular key + JPH_INLINE void GetIndexAndControlValue(const Key &inKey, size_type &outIndex, uint8 &outControl) const + { + // Calculate hash + uint64 hash_value = Hash { } (inKey); + + // Split hash into index and control value + outIndex = size_type(hash_value >> 7) & (mMaxSize - 1); + outControl = cBucketUsed | uint8(hash_value); + } + + /// Allocate space for the hash table + void AllocateTable(size_type inMaxSize) + { + JPH_ASSERT(mData == nullptr); + + mMaxSize = inMaxSize; + mLoadLeft = sGetMaxLoad(inMaxSize); + size_t required_size = size_t(mMaxSize) * (sizeof(KeyValue) + 1) + 15; // Add 15 bytes to mirror the first 15 bytes of the control values + if constexpr (cNeedsAlignedAllocate) + mData = reinterpret_cast(AlignedAllocate(required_size, alignof(KeyValue))); + else + mData = reinterpret_cast(Allocate(required_size)); + mControl = reinterpret_cast(mData + mMaxSize); + } + + /// Copy the contents of another hash table + void CopyTable(const HashTable &inRHS) + { + if (inRHS.empty()) + return; + + AllocateTable(inRHS.mMaxSize); + + // Copy control bytes + memcpy(mControl, inRHS.mControl, mMaxSize + 15); + + // Copy elements + uint index = 0; + for (const uint8 *control = mControl, *control_end = mControl + mMaxSize; control != control_end; ++control, ++index) + if (*control & cBucketUsed) + new (mData + index) KeyValue(inRHS.mData[index]); + mSize = inRHS.mSize; + } + + /// Grow the table to a new size + void GrowTable(size_type inNewMaxSize) + { + // Move the old table to a temporary structure + size_type old_max_size = mMaxSize; + KeyValue *old_data = mData; + const uint8 *old_control = mControl; + mData = nullptr; + mControl = nullptr; + mSize = 0; + mMaxSize = 0; + mLoadLeft = 0; + + // Allocate new table + AllocateTable(inNewMaxSize); + + // Reset all control bytes + memset(mControl, cBucketEmpty, mMaxSize + 15); + + if (old_data != nullptr) + { + // Copy all elements from the old table + for (size_type i = 0; i < old_max_size; ++i) + if (old_control[i] & cBucketUsed) + { + size_type index; + KeyValue *element = old_data + i; + JPH_IF_ENABLE_ASSERTS(bool inserted =) InsertKey(HashTableDetail::sGetKey(*element), index); + JPH_ASSERT(inserted); + new (mData + index) KeyValue(std::move(*element)); + element->~KeyValue(); + } + + // Free memory + if constexpr (cNeedsAlignedAllocate) + AlignedFree(old_data); + else + Free(old_data); + } + } + +protected: + /// Get an element by index + KeyValue & GetElement(size_type inIndex) const + { + return mData[inIndex]; + } + + /// Insert a key into the map, returns true if the element was inserted, false if it already existed. + /// outIndex is the index at which the element should be constructed / where it is located. + template + bool InsertKey(const Key &inKey, size_type &outIndex) + { + // Ensure we have enough space + if (mLoadLeft == 0) + { + // Should not be growing if we're already growing! + if constexpr (InsertAfterGrow) + JPH_ASSERT(false); + + // Decide if we need to clean up all tombstones or if we need to grow the map + size_type num_deleted = sGetMaxLoad(mMaxSize) - mSize; + if (num_deleted * cMaxDeletedElementsDenominator > mMaxSize * cMaxDeletedElementsNumerator) + rehash(0); + else + { + // Grow by a power of 2 + size_type new_max_size = max(mMaxSize << 1, 16); + if (new_max_size < mMaxSize) + { + JPH_ASSERT(false, "Overflow in hash table size, can't grow!"); + return false; + } + GrowTable(new_max_size); + } + } + + // Split hash into index and control value + size_type index; + uint8 control; + GetIndexAndControlValue(inKey, index, control); + + // Keeps track of the index of the first deleted bucket we found + constexpr size_type cNoDeleted = ~size_type(0); + size_type first_deleted_index = cNoDeleted; + + // Linear probing + KeyEqual equal; + size_type bucket_mask = mMaxSize - 1; + BVec16 control16 = BVec16::sReplicate(control); + BVec16 bucket_empty = BVec16::sZero(); + BVec16 bucket_deleted = BVec16::sReplicate(cBucketDeleted); + for (;;) + { + // Read 16 control values (note that we added 15 bytes at the end of the control values that mirror the first 15 bytes) + BVec16 control_bytes = BVec16::sLoadByte16(mControl + index); + + // Check if we must find the element before we can insert + if constexpr (!InsertAfterGrow) + { + // Check for the control value we're looking for + // Note that when deleting we can create empty buckets instead of deleted buckets. + // This means we must unconditionally check all buckets in this batch for equality + // (also beyond the first empty bucket). + uint32 control_equal = uint32(BVec16::sEquals(control_bytes, control16).GetTrues()); + + // Index within the 16 buckets + size_type local_index = index; + + // Loop while there's still buckets to process + while (control_equal != 0) + { + // Get the first equal bucket + uint first_equal = CountTrailingZeros(control_equal); + + // Skip to the bucket + local_index += first_equal; + + // Make sure that our index is not beyond the end of the table + local_index &= bucket_mask; + + // We found a bucket with same control value + if (equal(HashTableDetail::sGetKey(mData[local_index]), inKey)) + { + // Element already exists + outIndex = local_index; + return false; + } + + // Skip past this bucket + control_equal >>= first_equal + 1; + local_index++; + } + + // Check if we're still scanning for deleted buckets + if (first_deleted_index == cNoDeleted) + { + // Check if any buckets have been deleted, if so store the first one + uint32 control_deleted = uint32(BVec16::sEquals(control_bytes, bucket_deleted).GetTrues()); + if (control_deleted != 0) + first_deleted_index = index + CountTrailingZeros(control_deleted); + } + } + + // Check for empty buckets + uint32 control_empty = uint32(BVec16::sEquals(control_bytes, bucket_empty).GetTrues()); + if (control_empty != 0) + { + // If we found a deleted bucket, use it. + // It doesn't matter if it is before or after the first empty bucket we found + // since we will always be scanning in batches of 16 buckets. + if (first_deleted_index == cNoDeleted || InsertAfterGrow) + { + index += CountTrailingZeros(control_empty); + --mLoadLeft; // Using an empty bucket decreases the load left + } + else + { + index = first_deleted_index; + } + + // Make sure that our index is not beyond the end of the table + index &= bucket_mask; + + // Update control byte + SetControlValue(index, control); + ++mSize; + + // Return index to newly allocated bucket + outIndex = index; + return true; + } + + // Move to next batch of 16 buckets + index = (index + 16) & bucket_mask; + } + } + +public: + /// Non-const iterator + class iterator : public IteratorBase + { + using Base = IteratorBase; + + public: + /// Properties + using reference = typename Base::value_type &; + using pointer = typename Base::value_type *; + + /// Constructors + explicit iterator(HashTable *inTable) : Base(inTable) { } + iterator(HashTable *inTable, size_type inIndex) : Base(inTable, inIndex) { } + iterator(const iterator &inIterator) : Base(inIterator) { } + + /// Assignment + iterator & operator = (const iterator &inRHS) { Base::operator = (inRHS); return *this; } + + using Base::operator *; + + /// Non-const access to key value pair + KeyValue & operator * () + { + JPH_ASSERT(this->IsValid()); + return this->mTable->mData[this->mIndex]; + } + + using Base::operator ->; + + /// Non-const access to key value pair + KeyValue * operator -> () + { + JPH_ASSERT(this->IsValid()); + return this->mTable->mData + this->mIndex; + } + }; + + /// Const iterator + class const_iterator : public IteratorBase + { + using Base = IteratorBase; + + public: + /// Properties + using reference = const typename Base::value_type &; + using pointer = const typename Base::value_type *; + + /// Constructors + explicit const_iterator(const HashTable *inTable) : Base(inTable) { } + const_iterator(const HashTable *inTable, size_type inIndex) : Base(inTable, inIndex) { } + const_iterator(const const_iterator &inRHS) : Base(inRHS) { } + const_iterator(const iterator &inIterator) : Base(inIterator.mTable, inIterator.mIndex) { } + + /// Assignment + const_iterator & operator = (const iterator &inRHS) { this->mTable = inRHS.mTable; this->mIndex = inRHS.mIndex; return *this; } + const_iterator & operator = (const const_iterator &inRHS) { Base::operator = (inRHS); return *this; } + }; + + /// Default constructor + HashTable() = default; + + /// Copy constructor + HashTable(const HashTable &inRHS) + { + CopyTable(inRHS); + } + + /// Move constructor + HashTable(HashTable &&ioRHS) noexcept : + mData(ioRHS.mData), + mControl(ioRHS.mControl), + mSize(ioRHS.mSize), + mMaxSize(ioRHS.mMaxSize), + mLoadLeft(ioRHS.mLoadLeft) + { + ioRHS.mData = nullptr; + ioRHS.mControl = nullptr; + ioRHS.mSize = 0; + ioRHS.mMaxSize = 0; + ioRHS.mLoadLeft = 0; + } + + /// Assignment operator + HashTable & operator = (const HashTable &inRHS) + { + if (this != &inRHS) + { + clear(); + + CopyTable(inRHS); + } + + return *this; + } + + /// Move assignment operator + HashTable & operator = (HashTable &&ioRHS) noexcept + { + if (this != &ioRHS) + { + clear(); + + mData = ioRHS.mData; + mControl = ioRHS.mControl; + mSize = ioRHS.mSize; + mMaxSize = ioRHS.mMaxSize; + mLoadLeft = ioRHS.mLoadLeft; + + ioRHS.mData = nullptr; + ioRHS.mControl = nullptr; + ioRHS.mSize = 0; + ioRHS.mMaxSize = 0; + ioRHS.mLoadLeft = 0; + } + + return *this; + } + + /// Destructor + ~HashTable() + { + clear(); + } + + /// Reserve memory for a certain number of elements + void reserve(size_type inMaxSize) + { + // Calculate max size based on load factor + size_type max_size = GetNextPowerOf2(max((cMaxLoadFactorDenominator * inMaxSize) / cMaxLoadFactorNumerator, 16)); + if (max_size <= mMaxSize) + return; + + GrowTable(max_size); + } + + /// Destroy the entire hash table + void clear() + { + // Delete all elements + if constexpr (!std::is_trivially_destructible()) + if (!empty()) + for (size_type i = 0; i < mMaxSize; ++i) + if (mControl[i] & cBucketUsed) + mData[i].~KeyValue(); + + if (mData != nullptr) + { + // Free memory + if constexpr (cNeedsAlignedAllocate) + AlignedFree(mData); + else + Free(mData); + + // Reset members + mData = nullptr; + mControl = nullptr; + mSize = 0; + mMaxSize = 0; + mLoadLeft = 0; + } + } + + /// Destroy the entire hash table but keeps the memory allocated + void ClearAndKeepMemory() + { + // Destruct elements + if constexpr (!std::is_trivially_destructible()) + if (!empty()) + for (size_type i = 0; i < mMaxSize; ++i) + if (mControl[i] & cBucketUsed) + mData[i].~KeyValue(); + mSize = 0; + + // If there are elements that are not marked cBucketEmpty, we reset them + size_type max_load = sGetMaxLoad(mMaxSize); + if (mLoadLeft != max_load) + { + // Reset all control bytes + memset(mControl, cBucketEmpty, mMaxSize + 15); + mLoadLeft = max_load; + } + } + + /// Iterator to first element + iterator begin() + { + return iterator(this); + } + + /// Iterator to one beyond last element + iterator end() + { + return iterator(this, mMaxSize); + } + + /// Iterator to first element + const_iterator begin() const + { + return const_iterator(this); + } + + /// Iterator to one beyond last element + const_iterator end() const + { + return const_iterator(this, mMaxSize); + } + + /// Iterator to first element + const_iterator cbegin() const + { + return const_iterator(this); + } + + /// Iterator to one beyond last element + const_iterator cend() const + { + return const_iterator(this, mMaxSize); + } + + /// Number of buckets in the table + size_type bucket_count() const + { + return mMaxSize; + } + + /// Max number of buckets that the table can have + constexpr size_type max_bucket_count() const + { + return size_type(1) << (sizeof(size_type) * 8 - 1); + } + + /// Check if there are no elements in the table + bool empty() const + { + return mSize == 0; + } + + /// Number of elements in the table + size_type size() const + { + return mSize; + } + + /// Max number of elements that the table can hold + constexpr size_type max_size() const + { + return size_type((uint64(max_bucket_count()) * cMaxLoadFactorNumerator) / cMaxLoadFactorDenominator); + } + + /// Get the max load factor for this table (max number of elements / number of buckets) + constexpr float max_load_factor() const + { + return float(cMaxLoadFactorNumerator) / float(cMaxLoadFactorDenominator); + } + + /// Insert a new element, returns iterator and if the element was inserted + std::pair insert(const value_type &inValue) + { + size_type index; + bool inserted = InsertKey(HashTableDetail::sGetKey(inValue), index); + if (inserted) + new (mData + index) KeyValue(inValue); + return std::make_pair(iterator(this, index), inserted); + } + + /// Find an element, returns iterator to element or end() if not found + const_iterator find(const Key &inKey) const + { + // Check if we have any data + if (empty()) + return cend(); + + // Split hash into index and control value + size_type index; + uint8 control; + GetIndexAndControlValue(inKey, index, control); + + // Linear probing + KeyEqual equal; + size_type bucket_mask = mMaxSize - 1; + BVec16 control16 = BVec16::sReplicate(control); + BVec16 bucket_empty = BVec16::sZero(); + for (;;) + { + // Read 16 control values + // (note that we added 15 bytes at the end of the control values that mirror the first 15 bytes) + BVec16 control_bytes = BVec16::sLoadByte16(mControl + index); + + // Check for the control value we're looking for + // Note that when deleting we can create empty buckets instead of deleted buckets. + // This means we must unconditionally check all buckets in this batch for equality + // (also beyond the first empty bucket). + uint32 control_equal = uint32(BVec16::sEquals(control_bytes, control16).GetTrues()); + + // Index within the 16 buckets + size_type local_index = index; + + // Loop while there's still buckets to process + while (control_equal != 0) + { + // Get the first equal bucket + uint first_equal = CountTrailingZeros(control_equal); + + // Skip to the bucket + local_index += first_equal; + + // Make sure that our index is not beyond the end of the table + local_index &= bucket_mask; + + // We found a bucket with same control value + if (equal(HashTableDetail::sGetKey(mData[local_index]), inKey)) + { + // Element found + return const_iterator(this, local_index); + } + + // Skip past this bucket + control_equal >>= first_equal + 1; + local_index++; + } + + // Check for empty buckets + uint32 control_empty = uint32(BVec16::sEquals(control_bytes, bucket_empty).GetTrues()); + if (control_empty != 0) + { + // An empty bucket was found, we didn't find the element + return cend(); + } + + // Move to next batch of 16 buckets + index = (index + 16) & bucket_mask; + } + } + + /// @brief Erase an element by iterator + void erase(const const_iterator &inIterator) + { + JPH_ASSERT(inIterator.IsValid()); + + // Read 16 control values before and after the current index + // (note that we added 15 bytes at the end of the control values that mirror the first 15 bytes) + BVec16 control_bytes_before = BVec16::sLoadByte16(mControl + ((inIterator.mIndex - 16) & (mMaxSize - 1))); + BVec16 control_bytes_after = BVec16::sLoadByte16(mControl + inIterator.mIndex); + BVec16 bucket_empty = BVec16::sZero(); + uint32 control_empty_before = uint32(BVec16::sEquals(control_bytes_before, bucket_empty).GetTrues()); + uint32 control_empty_after = uint32(BVec16::sEquals(control_bytes_after, bucket_empty).GetTrues()); + + // If (this index including) there exist 16 consecutive non-empty slots (represented by a bit being 0) then + // a probe looking for some element needs to continue probing so we cannot mark the bucket as empty + // but must mark it as deleted instead. + // Note that we use: CountLeadingZeros(uint16) = CountLeadingZeros(uint32) - 16. + uint8 control_value = CountLeadingZeros(control_empty_before) - 16 + CountTrailingZeros(control_empty_after) < 16? cBucketEmpty : cBucketDeleted; + + // Mark the bucket as empty/deleted + SetControlValue(inIterator.mIndex, control_value); + + // Destruct the element + mData[inIterator.mIndex].~KeyValue(); + + // If we marked the bucket as empty we can increase the load left + if (control_value == cBucketEmpty) + ++mLoadLeft; + + // Decrease size + --mSize; + } + + /// @brief Erase an element by key + size_type erase(const Key &inKey) + { + const_iterator it = find(inKey); + if (it == cend()) + return 0; + + erase(it); + return 1; + } + + /// Swap the contents of two hash tables + void swap(HashTable &ioRHS) noexcept + { + std::swap(mData, ioRHS.mData); + std::swap(mControl, ioRHS.mControl); + std::swap(mSize, ioRHS.mSize); + std::swap(mMaxSize, ioRHS.mMaxSize); + std::swap(mLoadLeft, ioRHS.mLoadLeft); + } + + /// In place re-hashing of all elements in the table. Removes all cBucketDeleted elements + /// The std version takes a bucket count, but we just re-hash to the same size. + void rehash(size_type) + { + // Update the control value for all buckets + for (size_type i = 0; i < mMaxSize; ++i) + { + uint8 &control = mControl[i]; + switch (control) + { + case cBucketDeleted: + // Deleted buckets become empty + control = cBucketEmpty; + break; + case cBucketEmpty: + // Remains empty + break; + default: + // Mark all occupied as deleted, to indicate it needs to move to the correct place + control = cBucketDeleted; + break; + } + } + + // Replicate control values to the last 15 entries + for (size_type i = 0; i < 15; ++i) + mControl[mMaxSize + i] = mControl[i]; + + // Loop over all elements that have been 'deleted' and move them to their new spot + BVec16 bucket_used = BVec16::sReplicate(cBucketUsed); + size_type bucket_mask = mMaxSize - 1; + uint32 probe_mask = bucket_mask & ~uint32(0b1111); // Mask out lower 4 bits because we test 16 buckets at a time + for (size_type src = 0; src < mMaxSize; ++src) + if (mControl[src] == cBucketDeleted) + for (;;) + { + // Split hash into index and control value + size_type src_index; + uint8 src_control; + GetIndexAndControlValue(HashTableDetail::sGetKey(mData[src]), src_index, src_control); + + // Linear probing + size_type dst = src_index; + for (;;) + { + // Check if any buckets are free + BVec16 control_bytes = BVec16::sLoadByte16(mControl + dst); + uint32 control_free = uint32(BVec16::sAnd(control_bytes, bucket_used).GetTrues()) ^ 0xffff; + if (control_free != 0) + { + // Select this bucket as destination + dst += CountTrailingZeros(control_free); + dst &= bucket_mask; + break; + } + + // Move to next batch of 16 buckets + dst = (dst + 16) & bucket_mask; + } + + // Check if we stay in the same probe group + if (((dst - src_index) & probe_mask) == ((src - src_index) & probe_mask)) + { + // We stay in the same group, we can stay where we are + SetControlValue(src, src_control); + break; + } + else if (mControl[dst] == cBucketEmpty) + { + // There's an empty bucket, move us there + SetControlValue(dst, src_control); + SetControlValue(src, cBucketEmpty); + new (mData + dst) KeyValue(std::move(mData[src])); + mData[src].~KeyValue(); + break; + } + else + { + // There's an element in the bucket we want to move to, swap them + JPH_ASSERT(mControl[dst] == cBucketDeleted); + SetControlValue(dst, src_control); + std::swap(mData[src], mData[dst]); + // Iterate again with the same source bucket + } + } + + // Reinitialize load left + mLoadLeft = sGetMaxLoad(mMaxSize) - mSize; + } + +private: + /// If this allocator needs to fall back to aligned allocations because the type requires it + static constexpr bool cNeedsAlignedAllocate = alignof(KeyValue) > (JPH_CPU_ADDRESS_BITS == 32? 8 : 16); + + /// Max load factor is cMaxLoadFactorNumerator / cMaxLoadFactorDenominator + static constexpr uint64 cMaxLoadFactorNumerator = 7; + static constexpr uint64 cMaxLoadFactorDenominator = 8; + + /// If we can recover this fraction of deleted elements, we'll reshuffle the buckets in place rather than growing the table + static constexpr uint64 cMaxDeletedElementsNumerator = 1; + static constexpr uint64 cMaxDeletedElementsDenominator = 8; + + /// Values that the control bytes can have + static constexpr uint8 cBucketEmpty = 0; + static constexpr uint8 cBucketDeleted = 0x7f; + static constexpr uint8 cBucketUsed = 0x80; // Lowest 7 bits are lowest 7 bits of the hash value + + /// The buckets, an array of size mMaxSize + KeyValue * mData = nullptr; + + /// Control bytes, an array of size mMaxSize + 15 + uint8 * mControl = nullptr; + + /// Number of elements in the table + size_type mSize = 0; + + /// Max number of elements that can be stored in the table + size_type mMaxSize = 0; + + /// Number of elements we can add to the table before we need to grow + size_type mLoadLeft = 0; +}; + +JPH_NAMESPACE_END diff --git a/lib/zphysics/libs/Jolt/Core/IssueReporting.cpp b/lib/zphysics/libs/Jolt/Core/IssueReporting.cpp index ec7860a..ff32448 100644 --- a/lib/zphysics/libs/Jolt/Core/IssueReporting.cpp +++ b/lib/zphysics/libs/Jolt/Core/IssueReporting.cpp @@ -4,15 +4,11 @@ #include -JPH_SUPPRESS_WARNINGS_STD_BEGIN -#include -JPH_SUPPRESS_WARNINGS_STD_END - JPH_NAMESPACE_BEGIN -static void DummyTrace([[maybe_unused]] const char *inFMT, ...) -{ - JPH_ASSERT(false); +static void DummyTrace([[maybe_unused]] const char *inFMT, ...) +{ + JPH_ASSERT(false); }; TraceFunction Trace = DummyTrace; @@ -20,7 +16,7 @@ TraceFunction Trace = DummyTrace; #ifdef JPH_ENABLE_ASSERTS static bool DummyAssertFailed(const char *inExpression, const char *inMessage, const char *inFile, uint inLine) -{ +{ return true; // Trigger breakpoint }; diff --git a/lib/zphysics/libs/Jolt/Core/IssueReporting.h b/lib/zphysics/libs/Jolt/Core/IssueReporting.h index 089c528..efbf9a0 100644 --- a/lib/zphysics/libs/Jolt/Core/IssueReporting.h +++ b/lib/zphysics/libs/Jolt/Core/IssueReporting.h @@ -5,20 +5,20 @@ #pragma once JPH_NAMESPACE_BEGIN - + /// Trace function, needs to be overridden by application. This should output a line of text to the log / TTY. using TraceFunction = void (*)(const char *inFMT, ...); -extern TraceFunction Trace; +JPH_EXPORT extern TraceFunction Trace; // Always turn on asserts in Debug mode -#if defined(_DEBUG) && !defined(JPH_ENABLE_ASSERTS) +#if defined(JPH_DEBUG) && !defined(JPH_ENABLE_ASSERTS) #define JPH_ENABLE_ASSERTS #endif #ifdef JPH_ENABLE_ASSERTS /// Function called when an assertion fails. This function should return true if a breakpoint needs to be triggered using AssertFailedFunction = bool(*)(const char *inExpression, const char *inMessage, const char *inFile, uint inLine); - extern AssertFailedFunction AssertFailed; + JPH_EXPORT extern AssertFailedFunction AssertFailed; // Helper functions to pass message on to failed function struct AssertLastParam { }; @@ -26,13 +26,13 @@ extern TraceFunction Trace; inline bool AssertFailedParamHelper(const char *inExpression, const char *inFile, uint inLine, const char *inMessage, AssertLastParam) { return AssertFailed(inExpression, inMessage, inFile, inLine); } /// Main assert macro, usage: JPH_ASSERT(condition, message) or JPH_ASSERT(condition) - #define JPH_ASSERT(inExpression, ...) do { if (!(inExpression) && AssertFailedParamHelper(#inExpression, __FILE__, uint(__LINE__), ##__VA_ARGS__, AssertLastParam())) JPH_BREAKPOINT; } while (false) + #define JPH_ASSERT(inExpression, ...) do { if (!(inExpression) && AssertFailedParamHelper(#inExpression, __FILE__, JPH::uint(__LINE__), ##__VA_ARGS__, JPH::AssertLastParam())) JPH_BREAKPOINT; } while (false) #define JPH_IF_ENABLE_ASSERTS(...) __VA_ARGS__ #else - #define JPH_ASSERT(...) ((void)0) + #define JPH_ASSERT(...) ((void)0) - #define JPH_IF_ENABLE_ASSERTS(...) + #define JPH_IF_ENABLE_ASSERTS(...) #endif // JPH_ENABLE_ASSERTS JPH_NAMESPACE_END diff --git a/lib/zphysics/libs/Jolt/Core/JobSystem.h b/lib/zphysics/libs/Jolt/Core/JobSystem.h index 1f82b33..1bd621a 100644 --- a/lib/zphysics/libs/Jolt/Core/JobSystem.h +++ b/lib/zphysics/libs/Jolt/Core/JobSystem.h @@ -15,29 +15,29 @@ JPH_NAMESPACE_BEGIN /// A class that allows units of work (Jobs) to be scheduled across multiple threads. /// It allows dependencies between the jobs so that the jobs form a graph. -/// +/// /// The pattern for using this class is: -/// +/// /// // Create job system /// JobSystem *job_system = new JobSystemThreadPool(...); -/// +/// /// // Create some jobs /// JobHandle second_job = job_system->CreateJob("SecondJob", Color::sRed, []() { ... }, 1); // Create a job with 1 dependency /// JobHandle first_job = job_system->CreateJob("FirstJob", Color::sGreen, [second_job]() { ....; second_job.RemoveDependency(); }, 0); // Job can start immediately, will start second job when it's done /// JobHandle third_job = job_system->CreateJob("ThirdJob", Color::sBlue, []() { ... }, 0); // This job can run immediately as well and can run in parallel to job 1 and 2 -/// +/// /// // Add the jobs to the barrier so that we can execute them while we're waiting /// Barrier *barrier = job_system->CreateBarrier(); /// barrier->AddJob(first_job); /// barrier->AddJob(second_job); /// barrier->AddJob(third_job); /// job_system->WaitForJobs(barrier); -/// -/// // Clean up -/// job_system->DestroyBarrier(barrier); -/// delete job_system; -/// -/// Jobs are guaranteed to be started in the order that their dependency counter becomes zero (in case they're scheduled on a background thread) +/// +/// // Clean up +/// job_system->DestroyBarrier(barrier); +/// delete job_system; +/// +/// Jobs are guaranteed to be started in the order that their dependency counter becomes zero (in case they're scheduled on a background thread) /// or in the order they're added to the barrier (when dependency count is zero and when executing on the thread that calls WaitForJobs). /// /// If you want to implement your own job system, inherit from JobSystem and implement: @@ -51,22 +51,22 @@ JPH_NAMESPACE_BEGIN /// /// JobSystem::Barrier is used to track the completion of a set of jobs. Jobs will be created by other jobs and added to the barrier while it is being waited on. This means that you cannot /// create a dependency graph beforehand as the graph changes while jobs are running. Implement the following functions: -/// +/// /// * Barrier::AddJob/AddJobs - Add a job to the barrier, any call to WaitForJobs will now also wait for this job to complete. /// If you store the job in a data structure in the Barrier you need to call AddRef() on the job to keep it alive and Release() after you're done with it. /// * Barrier::OnJobFinished - This function is called when a job has finished executing, you can use this to track completion and remove the job from the list of jobs to wait on. /// /// The functions on JobSystem that need to be implemented to support barriers are: -/// +/// /// * JobSystem::CreateBarrier - Create a new barrier. /// * JobSystem::DestroyBarrier - Destroy a barrier. /// * JobSystem::WaitForJobs - This is the main function that is used to wait for all jobs that have been added to a Barrier. WaitForJobs can execute jobs that have -/// been added to the barrier while waiting. It is not wise to execute other jobs that touch physics structures as this can cause race conditions and deadlocks. Please keep in mind that the barrier is -/// only intended to wait on the completion of the Jolt jobs added to it, if you scheduled any jobs in your engine's job system to execute the Jolt jobs as part of QueueJob/QueueJobs, you might still need +/// been added to the barrier while waiting. It is not wise to execute other jobs that touch physics structures as this can cause race conditions and deadlocks. Please keep in mind that the barrier is +/// only intended to wait on the completion of the Jolt jobs added to it, if you scheduled any jobs in your engine's job system to execute the Jolt jobs as part of QueueJob/QueueJobs, you might still need /// to wait for these in this function after the barrier is finished waiting. /// /// An example implementation is JobSystemThreadPool. If you don't want to write the Barrier class you can also inherit from JobSystemWithBarrier. -class JobSystem : public NonCopyable +class JPH_EXPORT JobSystem : public NonCopyable { protected: class Job; @@ -79,7 +79,7 @@ public: class JobHandle : private Ref { public: - /// Constructor + /// Constructor inline JobHandle() = default; inline JobHandle(const JobHandle &inHandle) = default; inline JobHandle(JobHandle &&inHandle) noexcept : Ref(std::move(inHandle)) { } @@ -97,7 +97,7 @@ public: /// Check if this job has finished executing inline bool IsDone() const { return GetPtr() != nullptr && GetPtr()->IsDone(); } - /// Add to the dependency counter. + /// Add to the dependency counter. inline void AddDependency(int inCount = 1) const { GetPtr()->AddDependency(inCount); } /// Remove from the dependency counter. Job will start whenever the dependency counter reaches zero @@ -105,7 +105,7 @@ public: inline void RemoveDependency(int inCount = 1) const { GetPtr()->RemoveDependencyAndQueue(inCount); } /// Remove a dependency from a batch of jobs at once, this can be more efficient than removing them one by one as it requires less locking - static inline void sRemoveDependencies(JobHandle *inHandles, uint inNumHandles, int inCount = 1); + static inline void sRemoveDependencies(const JobHandle *inHandles, uint inNumHandles, int inCount = 1); /// Helper function to remove dependencies on a static array of job handles template @@ -173,15 +173,15 @@ protected: JPH_OVERRIDE_NEW_DELETE /// Constructor - Job([[maybe_unused]] const char *inJobName, [[maybe_unused]] ColorArg inColor, JobSystem *inJobSystem, const JobFunction &inJobFunction, uint32 inNumDependencies) : + Job([[maybe_unused]] const char *inJobName, [[maybe_unused]] ColorArg inColor, JobSystem *inJobSystem, const JobFunction &inJobFunction, uint32 inNumDependencies) : #if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED) - mJobName(inJobName), - mColor(inColor), + mJobName(inJobName), + mColor(inColor), #endif // defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED) - mJobSystem(inJobSystem), - mJobFunction(inJobFunction), - mNumDependencies(inNumDependencies) - { + mJobSystem(inJobSystem), + mJobFunction(inJobFunction), + mNumDependencies(inNumDependencies) + { } /// Get the jobs system to which this job belongs @@ -192,19 +192,25 @@ protected: { // Adding a reference can use relaxed memory ordering mReferenceCount.fetch_add(1, memory_order_relaxed); - } + } inline void Release() { + #ifndef JPH_TSAN_ENABLED // Releasing a reference must use release semantics... if (mReferenceCount.fetch_sub(1, memory_order_release) == 1) { - // ... so that we can use aquire to ensure that we see any updates from other threads that released a ref before freeing the job + // ... so that we can use acquire to ensure that we see any updates from other threads that released a ref before freeing the job atomic_thread_fence(memory_order_acquire); mJobSystem->FreeJob(this); } + #else + // But under TSAN, we cannot use atomic_thread_fence, so we use an acq_rel operation unconditionally instead + if (mReferenceCount.fetch_sub(1, memory_order_acq_rel) == 1) + mJobSystem->FreeJob(this); + #endif } - /// Add to the dependency counter. + /// Add to the dependency counter. inline void AddDependency(int inCount); /// Remove from the dependency counter. Returns true whenever the dependency counter reaches zero @@ -217,10 +223,10 @@ protected: /// Set the job barrier that this job belongs to and returns false if this was not possible because the job already finished inline bool SetBarrier(Barrier *inBarrier) - { - intptr_t barrier = 0; + { + intptr_t barrier = 0; if (mBarrier.compare_exchange_strong(barrier, reinterpret_cast(inBarrier), memory_order_relaxed)) - return true; + return true; JPH_ASSERT(barrier == cBarrierDoneState, "A job can only belong to 1 barrier"); return false; } @@ -266,6 +272,11 @@ protected: /// Test if the job finished executing inline bool IsDone() const { return mNumDependencies.load(memory_order_relaxed) == cDoneState; } + #if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED) + /// Get the name of the job + const char * GetName() const { return mJobName; } + #endif // defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED) + static constexpr uint32 cExecutingState = 0xe0e0e0e0; ///< Value of mNumDependencies when job is executing static constexpr uint32 cDoneState = 0xd0d0d0d0; ///< Value of mNumDependencies when job is done executing diff --git a/lib/zphysics/libs/Jolt/Core/JobSystem.inl b/lib/zphysics/libs/Jolt/Core/JobSystem.inl index c1ebcd2..bee4f13 100644 --- a/lib/zphysics/libs/Jolt/Core/JobSystem.inl +++ b/lib/zphysics/libs/Jolt/Core/JobSystem.inl @@ -25,7 +25,7 @@ void JobSystem::Job::RemoveDependencyAndQueue(int inCount) mJobSystem->QueueJob(this); } -void JobSystem::JobHandle::sRemoveDependencies(JobHandle *inHandles, uint inNumHandles, int inCount) +void JobSystem::JobHandle::sRemoveDependencies(const JobHandle *inHandles, uint inNumHandles, int inCount) { JPH_PROFILE_FUNCTION(); diff --git a/lib/zphysics/libs/Jolt/Core/JobSystemSingleThreaded.cpp b/lib/zphysics/libs/Jolt/Core/JobSystemSingleThreaded.cpp new file mode 100644 index 0000000..5f09d1a --- /dev/null +++ b/lib/zphysics/libs/Jolt/Core/JobSystemSingleThreaded.cpp @@ -0,0 +1,65 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2023 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#include + +#include + +JPH_NAMESPACE_BEGIN + +void JobSystemSingleThreaded::Init(uint inMaxJobs) +{ + mJobs.Init(inMaxJobs, inMaxJobs); +} + +JobHandle JobSystemSingleThreaded::CreateJob(const char *inJobName, ColorArg inColor, const JobFunction &inJobFunction, uint32 inNumDependencies) +{ + // Construct an object + uint32 index = mJobs.ConstructObject(inJobName, inColor, this, inJobFunction, inNumDependencies); + JPH_ASSERT(index != AvailableJobs::cInvalidObjectIndex); + Job *job = &mJobs.Get(index); + + // Construct handle to keep a reference, the job is queued below and will immediately complete + JobHandle handle(job); + + // If there are no dependencies, queue the job now + if (inNumDependencies == 0) + QueueJob(job); + + // Return the handle + return handle; +} + +void JobSystemSingleThreaded::FreeJob(Job *inJob) +{ + mJobs.DestructObject(inJob); +} + +void JobSystemSingleThreaded::QueueJob(Job *inJob) +{ + inJob->Execute(); +} + +void JobSystemSingleThreaded::QueueJobs(Job **inJobs, uint inNumJobs) +{ + for (uint i = 0; i < inNumJobs; ++i) + QueueJob(inJobs[i]); +} + +JobSystem::Barrier *JobSystemSingleThreaded::CreateBarrier() +{ + return &mDummyBarrier; +} + +void JobSystemSingleThreaded::DestroyBarrier(Barrier *inBarrier) +{ + // There's nothing to do here, the barrier is just a dummy +} + +void JobSystemSingleThreaded::WaitForJobs(Barrier *inBarrier) +{ + // There's nothing to do here, the barrier is just a dummy, we just execute the jobs immediately +} + +JPH_NAMESPACE_END diff --git a/lib/zphysics/libs/Jolt/Core/JobSystemSingleThreaded.h b/lib/zphysics/libs/Jolt/Core/JobSystemSingleThreaded.h new file mode 100644 index 0000000..4db599b --- /dev/null +++ b/lib/zphysics/libs/Jolt/Core/JobSystemSingleThreaded.h @@ -0,0 +1,62 @@ +// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) +// SPDX-FileCopyrightText: 2023 Jorrit Rouwe +// SPDX-License-Identifier: MIT + +#pragma once + +#include +#include + +JPH_NAMESPACE_BEGIN + +/// Implementation of a JobSystem without threads, runs jobs as soon as they are added +class JPH_EXPORT JobSystemSingleThreaded final : public JobSystem +{ +public: + JPH_OVERRIDE_NEW_DELETE + + /// Constructor + JobSystemSingleThreaded() = default; + explicit JobSystemSingleThreaded(uint inMaxJobs) { Init(inMaxJobs); } + + /// Initialize the job system + /// @param inMaxJobs Max number of jobs that can be allocated at any time + void Init(uint inMaxJobs); + + // See JobSystem + virtual int GetMaxConcurrency() const override { return 1; } + virtual JobHandle CreateJob(const char *inName, ColorArg inColor, const JobFunction &inJobFunction, uint32 inNumDependencies = 0) override; + virtual Barrier * CreateBarrier() override; + virtual void DestroyBarrier(Barrier *inBarrier) override; + virtual void WaitForJobs(Barrier *inBarrier) override; + +protected: + // Dummy implementation of Barrier, all jobs are executed immediately + class BarrierImpl : public Barrier + { + public: + JPH_OVERRIDE_NEW_DELETE + + // See Barrier + virtual void AddJob(const JobHandle &inJob) override { /* We don't need to track jobs */ } + virtual void AddJobs(const JobHandle *inHandles, uint inNumHandles) override { /* We don't need to track jobs */ } + + protected: + /// Called by a Job to mark that it is finished + virtual void OnJobFinished(Job *inJob) override { /* We don't need to track jobs */ } + }; + + // See JobSystem + virtual void QueueJob(Job *inJob) override; + virtual void QueueJobs(Job **inJobs, uint inNumJobs) override; + virtual void FreeJob(Job *inJob) override; + + /// Shared barrier since the barrier implementation does nothing + BarrierImpl mDummyBarrier; + + /// Array of jobs (fixed size) + using AvailableJobs = FixedSizeFreeList; + AvailableJobs mJobs; +}; + +JPH_NAMESPACE_END diff --git a/lib/zphysics/libs/Jolt/Core/JobSystemThreadPool.cpp b/lib/zphysics/libs/Jolt/Core/JobSystemThreadPool.cpp index 5a2d4c3..6d4dc10 100644 --- a/lib/zphysics/libs/Jolt/Core/JobSystemThreadPool.cpp +++ b/lib/zphysics/libs/Jolt/Core/JobSystemThreadPool.cpp @@ -11,7 +11,9 @@ #ifdef JPH_PLATFORM_WINDOWS JPH_SUPPRESS_WARNING_PUSH JPH_MSVC_SUPPRESS_WARNING(5039) // winbase.h(13179): warning C5039: 'TpSetCallbackCleanupGroup': pointer or reference to potentially throwing function passed to 'extern "C"' function under -EHc. Undefined behavior may occur if this function throws an exception. - #define WIN32_LEAN_AND_MEAN + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif #ifndef JPH_COMPILER_MINGW #include #else @@ -20,6 +22,9 @@ JPH_SUPPRESS_WARNING_POP #endif +#ifdef JPH_PLATFORM_LINUX + #include +#endif JPH_NAMESPACE_BEGIN @@ -43,8 +48,9 @@ JobSystemThreadPool::JobSystemThreadPool(uint inMaxJobs, uint inMaxBarriers, int Init(inMaxJobs, inMaxBarriers, inNumThreads); } -void JobSystemThreadPool::StartThreads(int inNumThreads) +void JobSystemThreadPool::StartThreads([[maybe_unused]] int inNumThreads) { +#if !defined(JPH_CPU_WASM) || defined(__EMSCRIPTEN_PTHREADS__) // If we're running without threads support we cannot create threads and we ignore the inNumThreads parameter // Auto detect number of threads if (inNumThreads < 0) inNumThreads = thread::hardware_concurrency() - 1; @@ -66,6 +72,7 @@ void JobSystemThreadPool::StartThreads(int inNumThreads) mThreads.reserve(inNumThreads); for (int i = 0; i < inNumThreads; ++i) mThreads.emplace_back([this, i] { ThreadMain(i); }); +#endif } JobSystemThreadPool::~JobSystemThreadPool() @@ -125,10 +132,10 @@ JobHandle JobSystemThreadPool::CreateJob(const char *inJobName, ColorArg inColor std::this_thread::sleep_for(std::chrono::microseconds(100)); } Job *job = &mJobs.Get(index); - + // Construct handle to keep a reference, the job is queued below and may immediately complete JobHandle handle(job); - + // If there are no dependencies, queue the job now if (inNumDependencies == 0) QueueJob(job); @@ -170,12 +177,12 @@ void JobSystemThreadPool::QueueJobInternal(Job *inJob) // We calculated the head outside of the loop, update head (and we also need to update tail to prevent it from passing head) head = GetHead(); old_value = mTail; - + // Second check if there's space in the queue if (old_value - head >= cQueueLength) { // Wake up all threads in order to ensure that they can clear any nullptrs they may not have processed yet - mSemaphore.Release((uint)mThreads.size()); + mSemaphore.Release((uint)mThreads.size()); // Sleep a little (we have to wait for other threads to update their head pointer in order for us to be able to continue) std::this_thread::sleep_for(std::chrono::microseconds(100)); @@ -187,7 +194,7 @@ void JobSystemThreadPool::QueueJobInternal(Job *inJob) Job *expected_job = nullptr; bool success = mQueue[old_value & (cQueueLength - 1)].compare_exchange_strong(expected_job, inJob); - // Regardless of who wrote the slot, we will update the tail (if the successful thread got scheduled out + // Regardless of who wrote the slot, we will update the tail (if the successful thread got scheduled out // after writing the pointer we still want to be able to continue) mTail.compare_exchange_strong(old_value, old_value + 1); @@ -230,39 +237,74 @@ void JobSystemThreadPool::QueueJobs(Job **inJobs, uint inNumJobs) mSemaphore.Release(min(inNumJobs, (uint)mThreads.size())); } -#if defined(JPH_PLATFORM_WINDOWS) && !defined(JPH_COMPILER_MINGW) // MinGW doesn't support __try/__except +#if defined(JPH_PLATFORM_WINDOWS) -// Sets the current thread name in MSVC debugger -static void SetThreadName(const char *inName) -{ - #pragma pack(push, 8) - - struct THREADNAME_INFO +#if !defined(JPH_COMPILER_MINGW) // MinGW doesn't support __try/__except) + // Sets the current thread name in MSVC debugger + static void RaiseThreadNameException(const char *inName) { - DWORD dwType; // Must be 0x1000. - LPCSTR szName; // Pointer to name (in user addr space). - DWORD dwThreadID; // Thread ID (-1=caller thread). - DWORD dwFlags; // Reserved for future use, must be zero. - }; + #pragma pack(push, 8) - #pragma pack(pop) + struct THREADNAME_INFO + { + DWORD dwType; // Must be 0x1000. + LPCSTR szName; // Pointer to name (in user addr space). + DWORD dwThreadID; // Thread ID (-1=caller thread). + DWORD dwFlags; // Reserved for future use, must be zero. + }; - THREADNAME_INFO info; - info.dwType = 0x1000; - info.szName = inName; - info.dwThreadID = (DWORD)-1; - info.dwFlags = 0; + #pragma pack(pop) - __try - { - RaiseException(0x406D1388, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR *)&info); + THREADNAME_INFO info; + info.dwType = 0x1000; + info.szName = inName; + info.dwThreadID = (DWORD)-1; + info.dwFlags = 0; + + __try + { + RaiseException(0x406D1388, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR *)&info); + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + } } - __except(EXCEPTION_EXECUTE_HANDLER) - { - } -} +#endif // !JPH_COMPILER_MINGW -#endif // JPH_PLATFORM_WINDOWS && !JPH_COMPILER_MINGW + static void SetThreadName(const char* inName) + { + JPH_SUPPRESS_WARNING_PUSH + + // Suppress casting warning, it's fine here as GetProcAddress doesn't really return a FARPROC + JPH_CLANG_SUPPRESS_WARNING("-Wcast-function-type") // error : cast from 'FARPROC' (aka 'long long (*)()') to 'SetThreadDescriptionFunc' (aka 'long (*)(void *, const wchar_t *)') converts to incompatible function type + JPH_CLANG_SUPPRESS_WARNING("-Wcast-function-type-strict") // error : cast from 'FARPROC' (aka 'long long (*)()') to 'SetThreadDescriptionFunc' (aka 'long (*)(void *, const wchar_t *)') converts to incompatible function type + JPH_MSVC_SUPPRESS_WARNING(4191) // reinterpret_cast' : unsafe conversion from 'FARPROC' to 'SetThreadDescriptionFunc'. Calling this function through the result pointer may cause your program to fail + + using SetThreadDescriptionFunc = HRESULT(WINAPI*)(HANDLE hThread, PCWSTR lpThreadDescription); + static SetThreadDescriptionFunc SetThreadDescription = reinterpret_cast(GetProcAddress(GetModuleHandleW(L"Kernel32.dll"), "SetThreadDescription")); + + JPH_SUPPRESS_WARNING_POP + + if (SetThreadDescription) + { + wchar_t name_buffer[64] = { 0 }; + if (MultiByteToWideChar(CP_UTF8, 0, inName, -1, name_buffer, sizeof(name_buffer) / sizeof(wchar_t) - 1) == 0) + return; + + SetThreadDescription(GetCurrentThread(), name_buffer); + } +#if !defined(JPH_COMPILER_MINGW) + else if (IsDebuggerPresent()) + RaiseThreadNameException(inName); +#endif // !JPH_COMPILER_MINGW + } +#elif defined(JPH_PLATFORM_LINUX) + static void SetThreadName(const char *inName) + { + JPH_ASSERT(strlen(inName) < 16); // String will be truncated if it is longer + prctl(PR_SET_NAME, inName, 0, 0, 0); + } +#endif // JPH_PLATFORM_LINUX void JobSystemThreadPool::ThreadMain(int inThreadIndex) { @@ -270,7 +312,7 @@ void JobSystemThreadPool::ThreadMain(int inThreadIndex) char name[64]; snprintf(name, sizeof(name), "Worker %d", int(inThreadIndex + 1)); -#if defined(JPH_PLATFORM_WINDOWS) && !defined(JPH_COMPILER_MINGW) +#if defined(JPH_PLATFORM_WINDOWS) || defined(JPH_PLATFORM_LINUX) SetThreadName(name); #endif // JPH_PLATFORM_WINDOWS && !JPH_COMPILER_MINGW @@ -280,6 +322,9 @@ void JobSystemThreadPool::ThreadMain(int inThreadIndex) JPH_PROFILE_THREAD_START(name); + // Call the thread init function + mThreadInitFunction(inThreadIndex); + atomic &head = mHeads[inThreadIndex]; while (!mQuit) @@ -310,6 +355,9 @@ void JobSystemThreadPool::ThreadMain(int inThreadIndex) } } + // Call the thread exit function + mThreadExitFunction(inThreadIndex); + JPH_PROFILE_THREAD_END(); } diff --git a/lib/zphysics/libs/Jolt/Core/JobSystemThreadPool.h b/lib/zphysics/libs/Jolt/Core/JobSystemThreadPool.h index f415269..2e44cb6 100644 --- a/lib/zphysics/libs/Jolt/Core/JobSystemThreadPool.h +++ b/lib/zphysics/libs/Jolt/Core/JobSystemThreadPool.h @@ -18,11 +18,11 @@ JPH_NAMESPACE_BEGIN using std::thread; /// Implementation of a JobSystem using a thread pool -/// +/// /// Note that this is considered an example implementation. It is expected that when you integrate /// the physics engine into your own project that you'll provide your own implementation of the /// JobSystem built on top of whatever job system your project uses. -class JobSystemThreadPool final : public JobSystemWithBarrier +class JPH_EXPORT JobSystemThreadPool final : public JobSystemWithBarrier { public: JPH_OVERRIDE_NEW_DELETE @@ -33,10 +33,15 @@ public: JobSystemThreadPool() = default; virtual ~JobSystemThreadPool() override; + /// Functions to call when a thread is initialized or exits, must be set before calling Init() + using InitExitFunction = function; + void SetThreadInitFunction(const InitExitFunction &inInitFunction) { mThreadInitFunction = inInitFunction; } + void SetThreadExitFunction(const InitExitFunction &inExitFunction) { mThreadExitFunction = inExitFunction; } + /// Initialize the thread pool /// @param inMaxJobs Max number of jobs that can be allocated at any time /// @param inMaxBarriers Max number of barriers that can be allocated at any time - /// @param inNumThreads Number of threads to start (the number of concurrent jobs is 1 more because the main thread will also run jobs while waiting for a barrier to complete). Use -1 to autodetect the amount of CPU's. + /// @param inNumThreads Number of threads to start (the number of concurrent jobs is 1 more because the main thread will also run jobs while waiting for a barrier to complete). Use -1 to auto detect the amount of CPU's. void Init(uint inMaxJobs, uint inMaxBarriers, int inNumThreads = -1); // See JobSystem @@ -45,7 +50,7 @@ public: /// Change the max concurrency after initialization void SetNumThreads(int inNumThreads) { StopThreads(); StartThreads(inNumThreads); } - + protected: // See JobSystem virtual void QueueJob(Job *inJob) override; @@ -56,7 +61,7 @@ private: /// Start/stop the worker threads void StartThreads(int inNumThreads); void StopThreads(); - + /// Entry point for a thread void ThreadMain(int inThreadIndex); @@ -66,6 +71,10 @@ private: /// Internal helper function to queue a job inline void QueueJobInternal(Job *inJob); + /// Functions to call when initializing or exiting a thread + InitExitFunction mThreadInitFunction = [](int) { }; + InitExitFunction mThreadExitFunction = [](int) { }; + /// Array of jobs (fixed size) using AvailableJobs = FixedSizeFreeList; AvailableJobs mJobs; diff --git a/lib/zphysics/libs/Jolt/Core/JobSystemWithBarrier.cpp b/lib/zphysics/libs/Jolt/Core/JobSystemWithBarrier.cpp index 7a0c61f..3d4c726 100644 --- a/lib/zphysics/libs/Jolt/Core/JobSystemWithBarrier.cpp +++ b/lib/zphysics/libs/Jolt/Core/JobSystemWithBarrier.cpp @@ -117,7 +117,7 @@ void JobSystemWithBarrier::BarrierImpl::Wait() // Loop through the jobs and erase jobs from the beginning of the list that are done while (mJobReadIndex < mJobWriteIndex) - { + { atomic &job = mJobs[mJobReadIndex & (cMaxJobs - 1)]; Job *job_ptr = job.load(); if (job_ptr == nullptr || !job_ptr->IsDone()) @@ -146,15 +146,18 @@ void JobSystemWithBarrier::BarrierImpl::Wait() } while (has_executed); } - // Wait for another thread to wake us when either there is more work to do or when all jobs have completed - int num_to_acquire = max(1, mSemaphore.GetValue()); // When there have been multiple releases, we acquire them all at the same time to avoid needlessly spinning on executing jobs + // Wait for another thread to wake us when either there is more work to do or when all jobs have completed. + // When there have been multiple releases, we acquire them all at the same time to avoid needlessly spinning on executing jobs. + // Note that using GetValue is inherently unsafe since we can read a stale value, but this is not an issue here as this is the only + // place where we acquire the semaphore. Other threads only release it, so we can only read a value that is lower or equal to the actual value. + int num_to_acquire = max(1, mSemaphore.GetValue()); mSemaphore.Acquire(num_to_acquire); mNumToAcquire -= num_to_acquire; } // All jobs should be done now, release them while (mJobReadIndex < mJobWriteIndex) - { + { atomic &job = mJobs[mJobReadIndex & (cMaxJobs - 1)]; Job *job_ptr = job.load(); JPH_ASSERT(job_ptr != nullptr && job_ptr->IsDone()); diff --git a/lib/zphysics/libs/Jolt/Core/JobSystemWithBarrier.h b/lib/zphysics/libs/Jolt/Core/JobSystemWithBarrier.h index 633e82e..0fc6633 100644 --- a/lib/zphysics/libs/Jolt/Core/JobSystemWithBarrier.h +++ b/lib/zphysics/libs/Jolt/Core/JobSystemWithBarrier.h @@ -20,7 +20,7 @@ JPH_NAMESPACE_BEGIN /// * JobSystem::QueueJob/QueueJobs /// /// See instructions in JobSystem for more information on how to implement these. -class JobSystemWithBarrier : public JobSystem +class JPH_EXPORT JobSystemWithBarrier : public JobSystem { public: JPH_OVERRIDE_NEW_DELETE @@ -70,7 +70,7 @@ private: /// Jobs queue for the barrier static constexpr uint cMaxJobs = 2048; static_assert(IsPowerOf2(cMaxJobs)); // We do bit operations and require max jobs to be a power of 2 - atomic mJobs[cMaxJobs]; ///< List of jobs that are part of this barrier, nullptrs for empty slots + atomic mJobs[cMaxJobs]; ///< List of jobs that are part of this barrier, nullptrs for empty slots alignas(JPH_CACHE_LINE_SIZE) atomic mJobReadIndex { 0 }; ///< First job that could be valid (modulo cMaxJobs), can be nullptr if other thread is still working on adding the job alignas(JPH_CACHE_LINE_SIZE) atomic mJobWriteIndex { 0 }; ///< First job that can be written (modulo cMaxJobs) atomic mNumToAcquire { 0 }; ///< Number of times the semaphore has been released, the barrier should acquire the semaphore this many times (written at the same time as mJobWriteIndex so ok to put in same cache line) diff --git a/lib/zphysics/libs/Jolt/Core/LinearCurve.cpp b/lib/zphysics/libs/Jolt/Core/LinearCurve.cpp index a5e21d9..8c8f768 100644 --- a/lib/zphysics/libs/Jolt/Core/LinearCurve.cpp +++ b/lib/zphysics/libs/Jolt/Core/LinearCurve.cpp @@ -27,7 +27,7 @@ float LinearCurve::GetValue(float inX) const if (mPoints.empty()) return 0.0f; - Points::const_iterator i2 = lower_bound(mPoints.begin(), mPoints.end(), inX, [](const Point &inPoint, float inValue) { return inPoint.mX < inValue; }); + Points::const_iterator i2 = std::lower_bound(mPoints.begin(), mPoints.end(), inX, [](const Point &inPoint, float inValue) { return inPoint.mX < inValue; }); if (i2 == mPoints.begin()) return mPoints.front().mY; diff --git a/lib/zphysics/libs/Jolt/Core/LinearCurve.h b/lib/zphysics/libs/Jolt/Core/LinearCurve.h index 69df57d..8700144 100644 --- a/lib/zphysics/libs/Jolt/Core/LinearCurve.h +++ b/lib/zphysics/libs/Jolt/Core/LinearCurve.h @@ -13,17 +13,17 @@ class StreamOut; class StreamIn; // A set of points (x, y) that form a linear curve -class LinearCurve +class JPH_EXPORT LinearCurve { -public: - JPH_DECLARE_SERIALIZABLE_NON_VIRTUAL(LinearCurve) + JPH_DECLARE_SERIALIZABLE_NON_VIRTUAL(JPH_EXPORT, LinearCurve) +public: /// A point on the curve class Point { - public: - JPH_DECLARE_SERIALIZABLE_NON_VIRTUAL(Point) + JPH_DECLARE_SERIALIZABLE_NON_VIRTUAL(JPH_EXPORT, Point) + public: float mX = 0.0f; float mY = 0.0f; }; diff --git a/lib/zphysics/libs/Jolt/Core/LockFreeHashMap.h b/lib/zphysics/libs/Jolt/Core/LockFreeHashMap.h index 066c251..ac1557b 100644 --- a/lib/zphysics/libs/Jolt/Core/LockFreeHashMap.h +++ b/lib/zphysics/libs/Jolt/Core/LockFreeHashMap.h @@ -38,7 +38,7 @@ public: inline T * FromOffset(uint32 inOffset) const; private: - uint8 * mObjectStore = nullptr; ///< This contains a contigous list of objects (possibly of varying size) + uint8 * mObjectStore = nullptr; ///< This contains a contiguous list of objects (possibly of varying size) uint32 mObjectStoreSizeBytes = 0; ///< The size of mObjectStore in bytes atomic mWriteOffset { 0 }; ///< Next offset to write to in mObjectStore }; @@ -84,7 +84,7 @@ public: /// Remove all elements. /// Note that this cannot happen simultaneously with adding new elements. void Clear(); - + /// Get the current amount of buckets that the map is using uint32 GetNumBuckets() const { return mNumBuckets; } @@ -115,7 +115,7 @@ public: /// Multiple threads can be inserting in the map at the same time. template inline KeyValue * Create(LFHMAllocatorContext &ioContext, const Key &inKey, uint64 inKeyHash, int inExtraBytes, Params &&... inConstructorParams); - + /// Find an element, returns null if not found inline const KeyValue * Find(const Key &inKey, uint64 inKeyHash) const; @@ -145,22 +145,22 @@ public: bool operator != (const Iterator &inRHS) const { return !(*this == inRHS); } /// Convert to key value pair - KeyValue & operator * (); + KeyValue & operator * (); /// Next item Iterator & operator ++ (); - MapType * mMap; + MapType * mMap; uint32 mBucket; uint32 mOffset; }; - /// Iterate over the map, note that it is not safe to do this in parallel to Clear(). + /// Iterate over the map, note that it is not safe to do this in parallel to Clear(). /// It is safe to do this while adding elements to the map, but newly added elements may or may not be returned by the iterator. Iterator begin(); Iterator end(); -#ifdef _DEBUG +#ifdef JPH_DEBUG /// Output stats about this map to the log void TraceStats() const; #endif diff --git a/lib/zphysics/libs/Jolt/Core/LockFreeHashMap.inl b/lib/zphysics/libs/Jolt/Core/LockFreeHashMap.inl index 28ebf47..aa4b875 100644 --- a/lib/zphysics/libs/Jolt/Core/LockFreeHashMap.inl +++ b/lib/zphysics/libs/Jolt/Core/LockFreeHashMap.inl @@ -77,10 +77,10 @@ inline T *LFHMAllocator::FromOffset(uint32 inOffset) const // LFHMAllocatorContext /////////////////////////////////////////////////////////////////////////////////// -inline LFHMAllocatorContext::LFHMAllocatorContext(LFHMAllocator &inAllocator, uint32 inBlockSize) : - mAllocator(inAllocator), - mBlockSize(inBlockSize) -{ +inline LFHMAllocatorContext::LFHMAllocatorContext(LFHMAllocator &inAllocator, uint32 inBlockSize) : + mAllocator(inAllocator), + mBlockSize(inBlockSize) +{ } inline bool LFHMAllocatorContext::Allocate(uint32 inSize, uint32 inAlignment, uint32 &outWriteOffset) @@ -89,7 +89,7 @@ inline bool LFHMAllocatorContext::Allocate(uint32 inSize, uint32 inAlignment, ui JPH_ASSERT(IsPowerOf2(inAlignment)); uint32 alignment_mask = inAlignment - 1; uint32 alignment = (inAlignment - (mBegin & alignment_mask)) & alignment_mask; - + // Check if we have space if (mEnd - mBegin < inSize + alignment) { @@ -98,7 +98,7 @@ inline bool LFHMAllocatorContext::Allocate(uint32 inSize, uint32 inAlignment, ui // Update alignment alignment = (inAlignment - (mBegin & alignment_mask)) & alignment_mask; - + // Check if we have space again if (mEnd - mBegin < inSize + alignment) return false; @@ -163,7 +163,7 @@ void LockFreeHashMap::SetNumBuckets(uint32 inNumBuckets) JPH_ASSERT(inNumBuckets <= mMaxBuckets); JPH_ASSERT(inNumBuckets >= 4 && IsPowerOf2(inNumBuckets)); - mNumBuckets = inNumBuckets; + mNumBuckets = inNumBuckets; } template @@ -189,7 +189,7 @@ inline typename LockFreeHashMap::KeyValue *LockFreeHashMap(write_offset); JPH_ASSERT(intptr_t(kv) % alignof(KeyValue) == 0); -#ifdef _DEBUG +#ifdef JPH_DEBUG memset(kv, 0xcd, size); #endif kv->mKey = inKey; @@ -280,7 +280,7 @@ typename LockFreeHashMap::KeyValue &LockFreeHashMap::Ite JPH_ASSERT(mOffset != cInvalidHandle); return *mMap->mAllocator.template FromOffset(mOffset); -} +} template typename LockFreeHashMap::Iterator &LockFreeHashMap::Iterator::operator++ () @@ -311,7 +311,7 @@ typename LockFreeHashMap::Iterator &LockFreeHashMap::Ite } } -#ifdef _DEBUG +#ifdef JPH_DEBUG template void LockFreeHashMap::TraceStats() const @@ -339,8 +339,8 @@ void LockFreeHashMap::TraceStats() const histogram[min(objects_in_bucket, cMaxPerBucket - 1)]++; } - Trace("max_objects_per_bucket = %d, num_buckets = %d, num_objects = %d", max_objects_per_bucket, mNumBuckets, num_objects); - + Trace("max_objects_per_bucket = %d, num_buckets = %u, num_objects = %d", max_objects_per_bucket, mNumBuckets, num_objects); + for (int i = 0; i < cMaxPerBucket; ++i) if (histogram[i] != 0) Trace("%d: %d", i, histogram[i]); diff --git a/lib/zphysics/libs/Jolt/Core/Memory.cpp b/lib/zphysics/libs/Jolt/Core/Memory.cpp index 998c03b..6e642c4 100644 --- a/lib/zphysics/libs/Jolt/Core/Memory.cpp +++ b/lib/zphysics/libs/Jolt/Core/Memory.cpp @@ -21,9 +21,16 @@ JPH_NAMESPACE_BEGIN JPH_ALLOC_SCOPE void *JPH_ALLOC_FN(Allocate)(size_t inSize) { + JPH_ASSERT(inSize > 0); return malloc(inSize); } +JPH_ALLOC_SCOPE void *JPH_ALLOC_FN(Reallocate)(void *inBlock, [[maybe_unused]] size_t inOldSize, size_t inNewSize) +{ + JPH_ASSERT(inNewSize > 0); + return realloc(inBlock, inNewSize); +} + JPH_ALLOC_SCOPE void JPH_ALLOC_FN(Free)(void *inBlock) { free(inBlock); @@ -31,13 +38,19 @@ JPH_ALLOC_SCOPE void JPH_ALLOC_FN(Free)(void *inBlock) JPH_ALLOC_SCOPE void *JPH_ALLOC_FN(AlignedAllocate)(size_t inSize, size_t inAlignment) { + JPH_ASSERT(inSize > 0 && inAlignment > 0); + #if defined(JPH_PLATFORM_WINDOWS) - // Microsoft doesn't implement C++17 std::aligned_alloc + // Microsoft doesn't implement posix_memalign return _aligned_malloc(inSize, inAlignment); -#elif defined(JPH_PLATFORM_ANDROID) - return memalign(inAlignment, AlignUp(inSize, inAlignment)); #else - return std::aligned_alloc(inAlignment, AlignUp(inSize, inAlignment)); + void *block = nullptr; + JPH_SUPPRESS_WARNING_PUSH + JPH_GCC_SUPPRESS_WARNING("-Wunused-result") + JPH_CLANG_SUPPRESS_WARNING("-Wunused-result") + posix_memalign(&block, inAlignment, inSize); + JPH_SUPPRESS_WARNING_POP + return block; #endif } @@ -45,16 +58,15 @@ JPH_ALLOC_SCOPE void JPH_ALLOC_FN(AlignedFree)(void *inBlock) { #if defined(JPH_PLATFORM_WINDOWS) _aligned_free(inBlock); -#elif defined(JPH_PLATFORM_ANDROID) - free(inBlock); #else - std::free(inBlock); + free(inBlock); #endif } #ifndef JPH_DISABLE_CUSTOM_ALLOCATOR AllocateFunction Allocate = nullptr; +ReallocateFunction Reallocate = nullptr; FreeFunction Free = nullptr; AlignedAllocateFunction AlignedAllocate = nullptr; AlignedFreeFunction AlignedFree = nullptr; @@ -62,6 +74,7 @@ AlignedFreeFunction AlignedFree = nullptr; void RegisterDefaultAllocator() { Allocate = AllocateImpl; + Reallocate = ReallocateImpl; Free = FreeImpl; AlignedAllocate = AlignedAllocateImpl; AlignedFree = AlignedFreeImpl; diff --git a/lib/zphysics/libs/Jolt/Core/Memory.h b/lib/zphysics/libs/Jolt/Core/Memory.h index 3b83ec0..b5f318d 100644 --- a/lib/zphysics/libs/Jolt/Core/Memory.h +++ b/lib/zphysics/libs/Jolt/Core/Memory.h @@ -10,6 +10,7 @@ JPH_NAMESPACE_BEGIN // Normal memory allocation, must be at least 8 byte aligned on 32 bit platform and 16 byte aligned on 64 bit platform using AllocateFunction = void *(*)(size_t inSize); +using ReallocateFunction = void *(*)(void *inBlock, size_t inOldSize, size_t inNewSize); using FreeFunction = void (*)(void *inBlock); // Aligned memory allocation @@ -17,13 +18,14 @@ using AlignedAllocateFunction = void *(*)(size_t inSize, size_t inAlignment); using AlignedFreeFunction = void (*)(void *inBlock); // User defined allocation / free functions -extern AllocateFunction Allocate; -extern FreeFunction Free; -extern AlignedAllocateFunction AlignedAllocate; -extern AlignedFreeFunction AlignedFree; +JPH_EXPORT extern AllocateFunction Allocate; +JPH_EXPORT extern ReallocateFunction Reallocate; +JPH_EXPORT extern FreeFunction Free; +JPH_EXPORT extern AlignedAllocateFunction AlignedAllocate; +JPH_EXPORT extern AlignedFreeFunction AlignedFree; /// Register platform default allocation / free functions -void RegisterDefaultAllocator(); +JPH_EXPORT void RegisterDefaultAllocator(); /// Macro to override the new and delete functions #define JPH_OVERRIDE_NEW_DELETE \ @@ -32,17 +34,22 @@ void RegisterDefaultAllocator(); JPH_INLINE void *operator new[] (size_t inCount) { return JPH::Allocate(inCount); } \ JPH_INLINE void operator delete[] (void *inPointer) noexcept { JPH::Free(inPointer); } \ JPH_INLINE void *operator new (size_t inCount, std::align_val_t inAlignment) { return JPH::AlignedAllocate(inCount, static_cast(inAlignment)); } \ - JPH_INLINE void operator delete (void *inPointer, std::align_val_t inAlignment) noexcept { JPH::AlignedFree(inPointer); } \ + JPH_INLINE void operator delete (void *inPointer, [[maybe_unused]] std::align_val_t inAlignment) noexcept { JPH::AlignedFree(inPointer); } \ JPH_INLINE void *operator new[] (size_t inCount, std::align_val_t inAlignment) { return JPH::AlignedAllocate(inCount, static_cast(inAlignment)); } \ - JPH_INLINE void operator delete[] (void *inPointer, std::align_val_t inAlignment) noexcept { JPH::AlignedFree(inPointer); } + JPH_INLINE void operator delete[] (void *inPointer, [[maybe_unused]] std::align_val_t inAlignment) noexcept { JPH::AlignedFree(inPointer); } \ + JPH_INLINE void *operator new ([[maybe_unused]] size_t inCount, void *inPointer) noexcept { return inPointer; } \ + JPH_INLINE void operator delete ([[maybe_unused]] void *inPointer, [[maybe_unused]] void *inPlace) noexcept { /* Do nothing */ } \ + JPH_INLINE void *operator new[] ([[maybe_unused]] size_t inCount, void *inPointer) noexcept { return inPointer; } \ + JPH_INLINE void operator delete[] ([[maybe_unused]] void *inPointer, [[maybe_unused]] void *inPlace) noexcept { /* Do nothing */ } #else // Directly define the allocation functions -void *Allocate(size_t inSize); -void Free(void *inBlock); -void *AlignedAllocate(size_t inSize, size_t inAlignment); -void AlignedFree(void *inBlock); +JPH_EXPORT void *Allocate(size_t inSize); +JPH_EXPORT void *Reallocate(void *inBlock, size_t inOldSize, size_t inNewSize); +JPH_EXPORT void Free(void *inBlock); +JPH_EXPORT void *AlignedAllocate(size_t inSize, size_t inAlignment); +JPH_EXPORT void AlignedFree(void *inBlock); // Don't implement allocator registering inline void RegisterDefaultAllocator() { } diff --git a/lib/zphysics/libs/Jolt/Core/Mutex.h b/lib/zphysics/libs/Jolt/Core/Mutex.h index 86c9458..6969aa4 100644 --- a/lib/zphysics/libs/Jolt/Core/Mutex.h +++ b/lib/zphysics/libs/Jolt/Core/Mutex.h @@ -116,7 +116,7 @@ using SharedMutexBase = shared_mutex; #if defined(JPH_ENABLE_ASSERTS) || defined(JPH_PROFILE_ENABLED) || defined(JPH_EXTERNAL_PROFILE) -/// Very simple wrapper around MutexBase which tracks lock contention in the profiler +/// Very simple wrapper around MutexBase which tracks lock contention in the profiler /// and asserts that locks/unlocks take place on the same thread class Mutex : public MutexBase { diff --git a/lib/zphysics/libs/Jolt/Core/MutexArray.h b/lib/zphysics/libs/Jolt/Core/MutexArray.h index c1b6936..3a0b558 100644 --- a/lib/zphysics/libs/Jolt/Core/MutexArray.h +++ b/lib/zphysics/libs/Jolt/Core/MutexArray.h @@ -27,13 +27,13 @@ public: /// Initialization /// @param inNumMutexes The amount of mutexes to allocate - void Init(uint inNumMutexes) - { - JPH_ASSERT(mMutexStorage == nullptr); + void Init(uint inNumMutexes) + { + JPH_ASSERT(mMutexStorage == nullptr); JPH_ASSERT(inNumMutexes > 0 && IsPowerOf2(inNumMutexes)); - mMutexStorage = new MutexStorage[inNumMutexes]; - mNumMutexes = inNumMutexes; + mMutexStorage = new MutexStorage[inNumMutexes]; + mNumMutexes = inNumMutexes; } /// Get the number of mutexes that were allocated @@ -45,7 +45,7 @@ public: /// Convert an object index to a mutex index inline uint32 GetMutexIndex(uint32 inObjectIndex) const { - std::hash hasher; + Hash hasher; return hasher(inObjectIndex) & (mNumMutexes - 1); } diff --git a/lib/zphysics/libs/Jolt/Core/NonCopyable.h b/lib/zphysics/libs/Jolt/Core/NonCopyable.h index 0284ec5..18b431e 100644 --- a/lib/zphysics/libs/Jolt/Core/NonCopyable.h +++ b/lib/zphysics/libs/Jolt/Core/NonCopyable.h @@ -7,7 +7,7 @@ JPH_NAMESPACE_BEGIN /// Class that makes another class non-copyable. Usage: Inherit from NonCopyable. -class NonCopyable +class JPH_EXPORT NonCopyable { public: NonCopyable() = default; diff --git a/lib/zphysics/libs/Jolt/Core/Profiler.cpp b/lib/zphysics/libs/Jolt/Core/Profiler.cpp index 6f76378..6536607 100644 --- a/lib/zphysics/libs/Jolt/Core/Profiler.cpp +++ b/lib/zphysics/libs/Jolt/Core/Profiler.cpp @@ -13,19 +13,56 @@ JPH_SUPPRESS_WARNINGS_STD_BEGIN #include JPH_SUPPRESS_WARNINGS_STD_END -#ifdef JPH_PROFILE_ENABLED - JPH_NAMESPACE_BEGIN +#if defined(JPH_EXTERNAL_PROFILE) && defined(JPH_SHARED_LIBRARY) + +ProfileStartMeasurementFunction ProfileStartMeasurement = [](const char *, uint32, uint8 *) { }; +ProfileEndMeasurementFunction ProfileEndMeasurement = [](uint8 *) { }; + +#elif defined(JPH_PROFILE_ENABLED) + ////////////////////////////////////////////////////////////////////////////////////////// // Profiler ////////////////////////////////////////////////////////////////////////////////////////// Profiler *Profiler::sInstance = nullptr; -thread_local ProfileThread *ProfileThread::sInstance = nullptr; + +#ifdef JPH_SHARED_LIBRARY + static thread_local ProfileThread *sInstance = nullptr; + + ProfileThread *ProfileThread::sGetInstance() + { + return sInstance; + } + + void ProfileThread::sSetInstance(ProfileThread *inInstance) + { + sInstance = inInstance; + } +#else + thread_local ProfileThread *ProfileThread::sInstance = nullptr; +#endif bool ProfileMeasurement::sOutOfSamplesReported = false; +void Profiler::UpdateReferenceTime() +{ + mReferenceTick = GetProcessorTickCount(); + mReferenceTime = std::chrono::high_resolution_clock::now(); +} + +uint64 Profiler::GetProcessorTicksPerSecond() const +{ + uint64 ticks = GetProcessorTickCount(); + std::chrono::high_resolution_clock::time_point time = std::chrono::high_resolution_clock::now(); + + return (ticks - mReferenceTick) * 1000000000ULL / std::chrono::duration_cast(time - mReferenceTime).count(); +} + +// This function assumes that none of the threads are active while we're dumping the profile, +// otherwise there will be a race condition on mCurrentSample and the profile data. +JPH_TSAN_NO_SANITIZE void Profiler::NextFrame() { std::lock_guard lock(mLock); @@ -38,6 +75,8 @@ void Profiler::NextFrame() for (ProfileThread *t : mThreads) t->mCurrentSample = 0; + + UpdateReferenceTime(); } void Profiler::Dump(const string_view &inTag) @@ -46,20 +85,20 @@ void Profiler::Dump(const string_view &inTag) mDumpTag = inTag; } -void Profiler::AddThread(ProfileThread *inThread) -{ - std::lock_guard lock(mLock); +void Profiler::AddThread(ProfileThread *inThread) +{ + std::lock_guard lock(mLock); - mThreads.push_back(inThread); + mThreads.push_back(inThread); } -void Profiler::RemoveThread(ProfileThread *inThread) -{ - std::lock_guard lock(mLock); - - Array::iterator i = find(mThreads.begin(), mThreads.end(), inThread); - JPH_ASSERT(i != mThreads.end()); - mThreads.erase(i); +void Profiler::RemoveThread(ProfileThread *inThread) +{ + std::lock_guard lock(mLock); + + Array::iterator i = std::find(mThreads.begin(), mThreads.end(), inThread); + JPH_ASSERT(i != mThreads.end()); + mThreads.erase(i); } void Profiler::sAggregate(int inDepth, uint32 inColor, ProfileSample *&ioSample, const ProfileSample *inEnd, Aggregators &ioAggregators, KeyToAggregator &ioKeyToAggregator) @@ -75,7 +114,6 @@ void Profiler::sAggregate(int inDepth, uint32 inColor, ProfileSample *&ioSample, // Start accumulating totals uint64 cycles_this_with_children = ioSample->mEndCycle - ioSample->mStartCycle; - uint64 cycles_in_children = 0; // Loop over following samples until we find a sample that starts on or after our end ProfileSample *sample; @@ -85,9 +123,6 @@ void Profiler::sAggregate(int inDepth, uint32 inColor, ProfileSample *&ioSample, JPH_ASSERT(sample->mStartCycle >= ioSample->mStartCycle); JPH_ASSERT(sample->mEndCycle <= ioSample->mEndCycle); - // This is a direct child of us, accumulate time - cycles_in_children += sample->mEndCycle - sample->mStartCycle; - // Recurse and skip over the children of this child sAggregate(inDepth + 1, inColor, sample, inEnd, ioAggregators, ioKeyToAggregator); } @@ -109,7 +144,7 @@ void Profiler::sAggregate(int inDepth, uint32 inColor, ProfileSample *&ioSample, } // Add the measurement to the aggregator - aggregator->AccumulateMeasurement(cycles_this_with_children, cycles_in_children); + aggregator->AccumulateMeasurement(cycles_this_with_children); // Update ioSample to the last child of ioSample JPH_ASSERT(sample[-1].mStartCycle <= ioSample->mEndCycle); @@ -126,7 +161,7 @@ void Profiler::DumpInternal() Threads threads; for (ProfileThread *t : mThreads) threads.push_back({ t->mThreadName, t->mSamples, t->mSamples + t->mCurrentSample }); - + // Shift all samples so that the first sample is at zero uint64 min_cycle = 0xffffffffffffffffUL; for (const ThreadSamples &t : threads) @@ -162,9 +197,6 @@ void Profiler::DumpInternal() for (ProfileSample *s = t.mSamplesBegin, *end = t.mSamplesEnd; s < end; ++s) sAggregate(0, Color::sGetDistinctColor(0).GetUInt32(), s, end, aggregators, key_to_aggregators); - // Dump as list - DumpList(tag.c_str(), aggregators); - // Dump as chart DumpChart(tag.c_str(), threads, key_to_aggregators, aggregators); } @@ -177,92 +209,12 @@ static String sHTMLEncode(const char *inString) return str; } -void Profiler::DumpList(const char *inTag, const Aggregators &inAggregators) -{ - // Open file - std::ofstream f; - f.open(StringFormat("profile_list_%s.html", inTag).c_str(), std::ofstream::out | std::ofstream::trunc); - if (!f.is_open()) - return; - - // Write header - f << R"( - - - Profile List - - - - - - - - - - - - - - - - - - - - - -)"; - - // Get total time - uint64 total_time = 0; - for (const Aggregator &item : inAggregators) - total_time += item.mTotalCyclesInCallWithChildren - item.mTotalCyclesInChildren; - - // Get cycles per second - uint64 cycles_per_second = GetProcessorTicksPerSecond(); - - // Sort the list - Aggregators aggregators = inAggregators; - QuickSort(aggregators.begin(), aggregators.end()); - - // Write all aggregators - for (const Aggregator &item : aggregators) - { - uint64 cycles_in_call_no_children = item.mTotalCyclesInCallWithChildren - item.mTotalCyclesInChildren; - - char str[2048]; - snprintf(str, sizeof(str), R"( - - - - - - - - -)", - sHTMLEncode(item.mName).c_str(), // Description - 100.0 * item.mTotalCyclesInCallWithChildren / total_time, // Total time with children - 100.0 * cycles_in_call_no_children / total_time, // Total time no children - item.mCallCounter, // Calls - 1000000.0 * item.mTotalCyclesInCallWithChildren / cycles_per_second / item.mCallCounter, // us / call with children - 1000000.0 * cycles_in_call_no_children / cycles_per_second / item.mCallCounter, // us / call no children - 1000000.0 * item.mMinCyclesInCallWithChildren / cycles_per_second, // Min. us / call with children - 1000000.0 * item.mMaxCyclesInCallWithChildren / cycles_per_second); // Max. us / call with children - - f << str; - } - - // End table - f << R"(
DescriptionTotal time with children (%)Total time (%)Callsµs / call with childrenµs / callMin. µs / callMax. µs / call
%s%.1f%.1f%u%.2f%.2f%.2f%.2f
)"; -} - void Profiler::DumpChart(const char *inTag, const Threads &inThreads, const KeyToAggregator &inKeyToAggregators, const Aggregators &inAggregators) { // Open file std::ofstream f; f.open(StringFormat("profile_chart_%s.html", inTag).c_str(), std::ofstream::out | std::ofstream::trunc); - if (!f.is_open()) + if (!f.is_open()) return; // Write header @@ -270,8 +222,331 @@ void Profiler::DumpChart(const char *inTag, const Threads &inThreads, const KeyT Profile Chart - - + +