Skip to content

Commit e036a62

Browse files
committed
Added option to have deterministic math.
When enabled, compiler optimizations to use fused multiply-add operations are disabled. SIMD operations that can change behavior (FMA, approximate reciprocal and reciprocal square root) are removed. Unit tests for base math types now have strict equality tests when determinism is enabled. This ensures that both the scalar and SIMD operations give exactly the same results across platforms. Selective builds enable deterministic math to ensure full test coverage while avoiding more total builds than what are present today.
1 parent 747009e commit e036a62

38 files changed

Lines changed: 3568 additions & 2681 deletions

File tree

.github/workflows/main.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
- lib_type: Static
2525
cmake_args: "-DDEEPSEA_SHARED=OFF -DDEEPSEA_X86_ARCH_LEVEL=0"
2626
- lib_type: Shared
27-
cmake_args: "-DDEEPSEA_SHARED=ON"
27+
cmake_args: "-DDEEPSEA_SHARED=ON -DDEEPSEA_DETERMINISTIC_MATH=ON"
2828
- lib_type: Single-Shared
2929
cmake_args: "-DDEEPSEA_SINGLE_SHARED=ON"
3030
steps:
@@ -97,7 +97,7 @@ jobs:
9797
- lib_type: Static
9898
cmake_args: "-DDEEPSEA_SHARED=OFF"
9999
- lib_type: Shared
100-
cmake_args: "-DDEEPSEA_SHARED=ON"
100+
cmake_args: "-DDEEPSEA_SHARED=ON -DDEEPSEA_DETERMINISTIC_MATH=ON"
101101
- lib_type: Single-Shared
102102
cmake_args: "-DDEEPSEA_SINGLE_SHARED=ON"
103103
steps:
@@ -151,7 +151,7 @@ jobs:
151151
- arch: Win32
152152
libs: win32
153153
lib_type: Static
154-
cmake_args: "-DDEEPSEA_SHARED=OFF -DDEEPSEA_X86_ARCH_LEVEL=0"
154+
cmake_args: "-DDEEPSEA_SHARED=OFF -DDEEPSEA_X86_ARCH_LEVEL=0 -DDEEPSEA_DETERMINISTIC_MATH=ON"
155155
- arch: Win32
156156
libs: win32
157157
lib_type: Shared
@@ -167,7 +167,7 @@ jobs:
167167
- arch: x64
168168
libs: win64
169169
lib_type: Shared
170-
cmake_args: "-DDEEPSEA_SHARED=ON"
170+
cmake_args: "-DDEEPSEA_SHARED=ON -DDEEPSEA_DETERMINISTIC_MATH=ON"
171171
- arch: x64
172172
libs: win64
173173
lib_type: Single-Shared
@@ -183,7 +183,7 @@ jobs:
183183
run: |-
184184
mkdir build
185185
cd build
186-
cmake ${{ matrix.cmake_args }} -A ${{ matrix.arch }} -T v142 ${{ github.workspace }}
186+
cmake ${{ matrix.cmake_args }} -A ${{ matrix.arch }} -T v143 ${{ github.workspace }}
187187
working-directory: "${{ github.workspace }}"
188188
- name: Build debug
189189
run: cmake --build . --config Debug

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ set(DEEPSEA_INSTALL_SET_RPATH ON CACHE BOOL "Set rpath for library and tool on i
7070
set(DEEPSEA_ANDROID_ASSETS_DIR src/main/assets CACHE PATH
7171
"Folder relative to project app directory to place assets for Android.")
7272
set(DEEPSEA_NO_PREBUILT_LIBS OFF CACHE BOOL "Don't use any pre-built library dependencies.")
73+
set(DEEPSEA_DETERMINISTIC_MATH OFF CACHE BOOL
74+
"Ensure determinisitic math results across all platforms at the expense of performance.")
7375

7476
if (CMAKE_SIZEOF_VOID_P EQUAL 8)
7577
set(defaultX86ArchLevel 3)

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ To build the examples, an Android Studio project is provided in the android subd
169169
* `-DDEEPSEA_INSTALL_SET_RPATH=ON|OFF`: Set rpath during install for the library and tool on installation. Set to `OFF` if including in another project that wants to control the rpath. Default is `ON`.
170170
* `-DDEEPSEA_ANDROID_ASSETS_DIR=folder`: Folder relative to project app directory to place assets for Android. Defaults to `src/main/assets`.
171171
* `-DDEEPSEA_NO_PREBUILT_LIBS=ON|OFF`: Don't use any pre-built library dependencies. Default is `OFF`.
172+
* `-DDEEPSEA_DETERMINISTIC_MATH=ON|OFF`: Ensure deterministic math results across all platforms at the expense of performance. The most common use is to have fully deterministic physics simulations when combined with fixed update intervals. Default is `OFF`.
172173
* `-DDEEPSEA_X86_ARCH_LEVEL=level`: The [x86-64 architecture level](https://en.wikipedia.org/wiki/X86-64), valid for both 32 and 64 bit-targets. A value of 0 indicates the base level, which means that 32-bit targets will have no SSE instructions enabled and will be the same as level 1 for 64-bit targets. In many cases runtime checks are used to choose an implementation with instructions beyond the level set here, but raising the base architecture level allows for these instructions to be used where such checks aren't practical or possible, and also allows for more compiler optimizations. On 64-bit targets this defaults to 3, which supports Intel Haswell (released in 2013) and AMD Excavator (released in 2015) CPUs, and is expected to have the largest speedup beyond level 1. On 32-bit targets this defaults to 1, which has basic SSE1 and SSE2 support. This will be ignored for non-x86 platforms.
173174
* `-DCMAKE_OSX_DEPLOYMENT_TARGET=version`: Minimum version of macOS to target when building for Mac. Defaults to 10.13, or 11.0 for ARM only, but may be set as low as 10.11.
174175

cmake/config.cmake

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,20 @@ if (MSVC)
5555
"It is not recommended to have DEEPSEA_SHARED and DEEPSEA_STATIC_RUNTIME both set to ON.")
5656
endif()
5757
endif()
58-
# NOTE: Warning 5105 is to work around an (embarrassing) bug win the Windows headers for
58+
# NOTE: Warning 5105 is to work around an (embarrassing) bug with the Windows headers for
5959
# Visual Studio 2019 16.8.0.
6060
add_compile_options(/W3 /WX /wd4146 /wd5105 /MP)
6161
add_definitions(-D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_WARNINGS)
6262

63+
# Allow floating-point contractions when not determinsitic math, which matches GCC behavior.
64+
# This option was added in Visual Studio 2022. For previous versions, contractions were enabled
65+
# for all modes except strict.
66+
if (DEEPSEA_DETERMINISTIC_MATH AND MSVC_VERSION LESS 1930)
67+
add_compile_options(/fp:strict)
68+
elseif (NOT DEEPSEA_DETERMINISTIC_MATH AND MSVC_VERSION GREATER_EQUAL 1930)
69+
add_compile_options(/fp:contract)
70+
endif()
71+
6372
# Disable RTTI, but enable exceptions
6473
if (CMAKE_CXX_FLAGS MATCHES "/EHs-c- ")
6574
string(REPLACE "/EHs-c-" "/EHsc" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
@@ -98,6 +107,9 @@ else()
98107
add_compile_options(-fvisibility=hidden)
99108
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility-inlines-hidden")
100109
endif()
110+
if (DEEPSEA_DETERMINISTIC_MATH)
111+
add_compile_options(-ffp-contract=off)
112+
endif()
101113

102114
# GCC has problems determining array sizes in some versions.
103115
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION GREATER_EQUAL 11.0)

modules/Animation/include/DeepSea/Animation/AnimationTree.h

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2022-2025 Aaron Barany
2+
* Copyright 2022-2026 Aaron Barany
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -44,8 +44,8 @@ extern "C"
4444
* nodes.
4545
* @return The animation tree or NULL if an error occurred.
4646
*/
47-
DS_ANIMATION_EXPORT dsAnimationTree* dsAnimationTree_create(dsAllocator* allocator,
48-
const dsAnimationBuildNode* const* rootNodes, uint32_t rootNodeCount);
47+
DS_ANIMATION_EXPORT dsAnimationTree* dsAnimationTree_create(
48+
dsAllocator* allocator, const dsAnimationBuildNode* const* rootNodes, uint32_t rootNodeCount);
4949

5050
/**
5151
* @brief Creates an animation tree of joints.
@@ -55,8 +55,8 @@ DS_ANIMATION_EXPORT dsAnimationTree* dsAnimationTree_create(dsAllocator* allocat
5555
* @param nodeCount The number of nodes. It is not valid to have an animation tree with no nodes.
5656
* @return The animation tree or NULL if an error occurred.
5757
*/
58-
DS_ANIMATION_EXPORT dsAnimationTree* dsAnimationTree_createJoints(dsAllocator* allocator,
59-
const dsAnimationJointBuildNode* nodes, uint32_t nodeCount);
58+
DS_ANIMATION_EXPORT dsAnimationTree* dsAnimationTree_createJoints(
59+
dsAllocator* allocator, const dsAnimationJointBuildNode* nodes, uint32_t nodeCount);
6060

6161
/**
6262
* @brief Loads an animation tree from a file.
@@ -67,8 +67,8 @@ DS_ANIMATION_EXPORT dsAnimationTree* dsAnimationTree_createJoints(dsAllocator* a
6767
* @param filePath The file path for the animation tree to load.
6868
* @return The loaded animation tree or NULL if it couldn't be loaded.
6969
*/
70-
DS_ANIMATION_EXPORT dsAnimationTree* dsAnimationTree_loadFile(dsAllocator* allocator,
71-
dsAllocator* scratchAllocator, const char* filePath);
70+
DS_ANIMATION_EXPORT dsAnimationTree* dsAnimationTree_loadFile(
71+
dsAllocator* allocator, dsAllocator* scratchAllocator, const char* filePath);
7272

7373
/**
7474
* @brief Loads an animation tree from a resource file.
@@ -106,8 +106,8 @@ DS_ANIMATION_EXPORT dsAnimationTree* dsAnimationTree_loadArchive(dsAllocator* al
106106
* current position until the end.
107107
* @return The loaded animation tree or NULL if it couldn't be loaded.
108108
*/
109-
DS_ANIMATION_EXPORT dsAnimationTree* dsAnimationTree_loadStream(dsAllocator* allocator,
110-
dsAllocator* scratchAllocator, dsStream* stream);
109+
DS_ANIMATION_EXPORT dsAnimationTree* dsAnimationTree_loadStream(
110+
dsAllocator* allocator, dsAllocator* scratchAllocator, dsStream* stream);
111111

112112
/**
113113
* @brief Loads an animation tree from a data buffer.
@@ -119,8 +119,8 @@ DS_ANIMATION_EXPORT dsAnimationTree* dsAnimationTree_loadStream(dsAllocator* all
119119
* @param size The size of the data buffer.
120120
* @return The loaded animation tree or NULL if it couldn't be loaded.
121121
*/
122-
DS_ANIMATION_EXPORT dsAnimationTree* dsAnimationTree_loadData(dsAllocator* allocator,
123-
dsAllocator* scratchAllocator, const void* data, size_t size);
122+
DS_ANIMATION_EXPORT dsAnimationTree* dsAnimationTree_loadData(
123+
dsAllocator* allocator, dsAllocator* scratchAllocator, const void* data, size_t size);
124124

125125
/**
126126
* @brief Clones an animation tree.
@@ -133,8 +133,8 @@ DS_ANIMATION_EXPORT dsAnimationTree* dsAnimationTree_loadData(dsAllocator* alloc
133133
* @param tree The animation tree.
134134
* @return The cloned animation tree or NULL if an error occurred.
135135
*/
136-
DS_ANIMATION_EXPORT dsAnimationTree* dsAnimationTree_clone(dsAllocator* allocator,
137-
const dsAnimationTree* tree);
136+
DS_ANIMATION_EXPORT dsAnimationTree* dsAnimationTree_clone(
137+
dsAllocator* allocator, const dsAnimationTree* tree);
138138

139139
/**
140140
* @brief Finds an animation node by name.
@@ -143,8 +143,8 @@ DS_ANIMATION_EXPORT dsAnimationTree* dsAnimationTree_clone(dsAllocator* allocato
143143
* @param name The name of the node.
144144
* @return The found node or NULL if not found.
145145
*/
146-
DS_ANIMATION_EXPORT const dsAnimationNode* dsAnimationTree_findNodeName(const dsAnimationTree* tree,
147-
const char* name);
146+
DS_ANIMATION_EXPORT const dsAnimationNode* dsAnimationTree_findNodeName(
147+
const dsAnimationTree* tree, const char* name);
148148

149149
/**
150150
* @brief Finds an animation node by name ID.
@@ -153,8 +153,8 @@ DS_ANIMATION_EXPORT const dsAnimationNode* dsAnimationTree_findNodeName(const ds
153153
* @param nameID The hash of the name of the node.
154154
* @return The found node or NULL if not found.
155155
*/
156-
DS_ANIMATION_EXPORT const dsAnimationNode* dsAnimationTree_findNodeID(const dsAnimationTree* tree,
157-
uint32_t nameID);
156+
DS_ANIMATION_EXPORT const dsAnimationNode* dsAnimationTree_findNodeID(
157+
const dsAnimationTree* tree, uint32_t nameID);
158158

159159
/**
160160
* @brief Finds an animation node index by name.
@@ -163,8 +163,8 @@ DS_ANIMATION_EXPORT const dsAnimationNode* dsAnimationTree_findNodeID(const dsAn
163163
* @param name The name of the node.
164164
* @return The found node index or DS_NO_ANIMATION_NODE if not found.
165165
*/
166-
DS_ANIMATION_EXPORT uint32_t dsAnimationTree_findNodeIndexName(const dsAnimationTree* tree,
167-
const char* name);
166+
DS_ANIMATION_EXPORT uint32_t dsAnimationTree_findNodeIndexName(
167+
const dsAnimationTree* tree, const char* name);
168168

169169
/**
170170
* @brief Finds an animation node index by name ID.
@@ -173,8 +173,8 @@ DS_ANIMATION_EXPORT uint32_t dsAnimationTree_findNodeIndexName(const dsAnimation
173173
* @param nameID The hash of the name of the node.
174174
* @return The found node index or DS_NO_ANIMATION_NODE if not found.
175175
*/
176-
DS_ANIMATION_EXPORT uint32_t dsAnimationTree_findNodeIndexID(const dsAnimationTree* tree,
177-
uint32_t nameID);
176+
DS_ANIMATION_EXPORT uint32_t dsAnimationTree_findNodeIndexID(
177+
const dsAnimationTree* tree, uint32_t nameID);
178178

179179
/**
180180
* @brief Updates the transforms for an animation tree.

0 commit comments

Comments
 (0)