diff options
253 files changed, 6110 insertions, 3912 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 351768c5425..98635bd198a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -333,7 +333,7 @@ option(WITH_ALEMBIC "Enable Alembic Support" OFF) option(WITH_ALEMBIC_HDF5 "Enable Legacy Alembic Support (not officially supported)" OFF) if(APPLE) - option(WITH_CODEC_QUICKTIME "Enable Quicktime Support" ON) + option(WITH_CODEC_QUICKTIME "Enable Quicktime Support" OFF) endif() # 3D format support @@ -508,6 +508,12 @@ mark_as_advanced(WITH_C11) option(WITH_CXX11 "Build with C++11 standard enabled, for development use only!" ${_cxx11_init}) mark_as_advanced(WITH_CXX11) +# Compiler toolchain +if(CMAKE_COMPILER_IS_GNUCC) + option(WITH_LINKER_GOLD "Use ld.gold linker which is usually faster than ld.bfd" ON) + mark_as_advanced(WITH_LINKER_GOLD) +endif() + # Dependency graph option(WITH_LEGACY_DEPSGRAPH "Build Blender with legacy dependency graph" ON) mark_as_advanced(WITH_LEGACY_DEPSGRAPH) @@ -731,7 +737,7 @@ elseif(WITH_CYCLES OR WITH_OPENIMAGEIO OR WITH_AUDASPACE OR WITH_INTERNATIONAL O # Keep enabled else() # New dependency graph needs either Boost or C++11 for function bindings. - if(NOT USE_CXX11) + if(NOT WITH_CXX11) # Enabled but we don't need it set(WITH_BOOST OFF) endif() diff --git a/build_files/build_environment/install_deps.sh b/build_files/build_environment/install_deps.sh index d6ea7d99f04..5a02a96bdff 100755 --- a/build_files/build_environment/install_deps.sh +++ b/build_files/build_environment/install_deps.sh @@ -1858,6 +1858,9 @@ compile_OSL() { cmake_d="$cmake_d -D OSL_BUILD_PLUGINS=OFF" cmake_d="$cmake_d -D OSL_BUILD_TESTS=OFF" cmake_d="$cmake_d -D USE_SIMD=sse2" + if [ "$USE_CXX11" = true ]; then + cmake_d="$cmake_d -D OSL_BUILD_CPP11=1" + fi #~ cmake_d="$cmake_d -D ILMBASE_VERSION=$ILMBASE_VERSION" @@ -4023,9 +4026,6 @@ install_OTHER() { fi if [ "$_do_compile_llvm" = true ]; then - install_packages_DEB libffi-dev - # LLVM can't find the debian ffi header dir - _FFI_INCLUDE_DIR=`dpkg -L libffi-dev | grep -e ".*/ffi.h" | sed -r 's/(.*)\/ffi.h/\1/'` PRINT "" compile_LLVM have_llvm=true @@ -4044,7 +4044,6 @@ install_OTHER() { if [ "$_do_compile_osl" = true ]; then if [ "$have_llvm" = true ]; then - install_packages_DEB flex bison libtbb-dev PRINT "" compile_OSL else @@ -4063,7 +4062,6 @@ install_OTHER() { fi if [ "$_do_compile_osd" = true ]; then - install_packages_DEB flex bison libtbb-dev PRINT "" compile_OSD fi @@ -4080,10 +4078,6 @@ install_OTHER() { fi if [ "$_do_compile_collada" = true ]; then - install_packages_DEB libpcre3-dev - # Find path to libxml shared lib... - _XML2_LIB=`dpkg -L libxml2-dev | grep -e ".*/libxml2.so"` - # No package PRINT "" compile_OpenCOLLADA fi diff --git a/build_files/buildbot/master.cfg b/build_files/buildbot/master.cfg index 8bd23357fc6..6b7191cd57b 100644 --- a/build_files/buildbot/master.cfg +++ b/build_files/buildbot/master.cfg @@ -94,6 +94,7 @@ all_repositories = { r'git://git.blender.org/blender-translations.git': 'blender-translations', r'git://git.blender.org/blender-addons.git': 'blender-addons', r'git://git.blender.org/blender-addons-contrib.git': 'blender-addons-contrib', + r'git://git.blender.org/blender-dev-tools.git': 'blender-dev-tools', r'https://svn.blender.org/svnroot/bf-blender/': 'lib svn', } @@ -128,6 +129,7 @@ def schedule_force_build(name): forcesched.CodebaseParameter(hide=True, codebase="blender-translations"), forcesched.CodebaseParameter(hide=True, codebase="blender-addons"), forcesched.CodebaseParameter(hide=True, codebase="blender-addons-contrib"), + forcesched.CodebaseParameter(hide=True, codebase="blender-dev-tools"), forcesched.CodebaseParameter(hide=True, codebase="lib svn")], properties=[])) @@ -143,6 +145,7 @@ def schedule_build(name, hour, minute=0): "blender-translations": {"repository": "", "branch": "master"}, "blender-addons": {"repository": "", "branch": "master"}, "blender-addons-contrib": {"repository": "", "branch": "master"}, + "blender-dev-tools": {"repository": "", "branch": "master"}, "lib svn": {"repository": "", "branch": "trunk"}}, branch=current_branch, builderNames=[name], @@ -264,7 +267,8 @@ def generic_builder(id, libdir='', branch='', rsync=False): for submodule in ('blender-translations', 'blender-addons', - 'blender-addons-contrib'): + 'blender-addons-contrib', + 'blender-dev-tools'): f.addStep(git_submodule_step(submodule)) f.addStep(git_step(branch)) @@ -299,7 +303,8 @@ add_builder(c, 'linux_glibc219_i686_cmake', '', generic_builder, hour=3) add_builder(c, 'linux_glibc219_x86_64_cmake', '', generic_builder, hour=4) add_builder(c, 'win32_cmake_vc2013', 'windows_vc12', generic_builder, hour=3) add_builder(c, 'win64_cmake_vc2013', 'win64_vc12', generic_builder, hour=4) -add_builder(c, 'win64_cmake_vc2015', 'win64_vc14', generic_builder, hour=5) +add_builder(c, 'win32_cmake_vc2015', 'windows_vc14', generic_builder, hour=5) +add_builder(c, 'win64_cmake_vc2015', 'win64_vc14', generic_builder, hour=6) # STATUS TARGETS # diff --git a/build_files/buildbot/slave_compile.py b/build_files/buildbot/slave_compile.py index c2bfd882fde..76d538ad578 100644 --- a/build_files/buildbot/slave_compile.py +++ b/build_files/buildbot/slave_compile.py @@ -183,10 +183,8 @@ if 'cmake' in builder: print('Condifuration FAILED!') sys.exit(retcode) - if 'win32' in builder: - command = ['msbuild', 'INSTALL.vcxproj', '/Property:PlatformToolset=v120_xp', '/p:Configuration=Release'] - elif 'win64' in builder: - command = ['msbuild', 'INSTALL.vcxproj', '/p:Configuration=Release'] + if 'win32' in builder or 'win64' in builder: + command = ['cmake', '--build', '.', '--target', target_name, '--config', 'Release'] else: command = target_chroot_prefix + ['make', '-s', '-j2', target_name] diff --git a/build_files/cmake/platform/platform_unix.cmake b/build_files/cmake/platform/platform_unix.cmake index e33141f8012..62e0caa7c43 100644 --- a/build_files/cmake/platform/platform_unix.cmake +++ b/build_files/cmake/platform/platform_unix.cmake @@ -384,17 +384,18 @@ add_definitions(-D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE if(CMAKE_COMPILER_IS_GNUCC) set(PLATFORM_CFLAGS "-pipe -fPIC -funsigned-char -fno-strict-aliasing") - # use ld.gold linker if available, could make optional - execute_process( - COMMAND ${CMAKE_C_COMPILER} -fuse-ld=gold -Wl,--version - ERROR_QUIET OUTPUT_VARIABLE LD_VERSION) - if("${LD_VERSION}" MATCHES "GNU gold") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fuse-ld=gold") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fuse-ld=gold") - else() - message(STATUS "GNU gold linker isn't available, using the default system linker.") + if(WITH_LINKER_GOLD) + execute_process( + COMMAND ${CMAKE_C_COMPILER} -fuse-ld=gold -Wl,--version + ERROR_QUIET OUTPUT_VARIABLE LD_VERSION) + if("${LD_VERSION}" MATCHES "GNU gold") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fuse-ld=gold") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fuse-ld=gold") + else() + message(STATUS "GNU gold linker isn't available, using the default system linker.") + endif() + unset(LD_VERSION) endif() - unset(LD_VERSION) # CLang is the same as GCC for now. elseif(CMAKE_C_COMPILER_ID MATCHES "Clang") diff --git a/doc/python_api/sphinx_doc_update.py b/doc/python_api/sphinx_doc_update.py index 5301f39b2e3..c7f0367a2a0 100755 --- a/doc/python_api/sphinx_doc_update.py +++ b/doc/python_api/sphinx_doc_update.py @@ -142,8 +142,11 @@ def main(): zip_name = "blender_python_reference_%s" % blenver_zip # We can't use 'release' postfix here... zip_path = os.path.join(args.mirror_dir, zip_name) with zipfile.ZipFile(zip_path, 'w') as zf: - for de in os.scandir(api_dir): - zf.write(de.path, arcname=os.path.join(zip_name, de.name)) + for dirname, _, filenames in os.walk(api_dir): + for filename in filenames: + filepath = os.path.join(dirname, filename) + zip_filepath = os.path.join(zip_name, os.path.relpath(filepath, api_dir)) + zf.write(filepath, arcname=zip_filepath) os.rename(zip_path, os.path.join(api_dir, "%s.zip" % zip_name)) # VII) Create symlinks and html redirects. diff --git a/extern/ceres/CMakeLists.txt b/extern/ceres/CMakeLists.txt index 2ad8c543088..a6e9cd9c356 100644 --- a/extern/ceres/CMakeLists.txt +++ b/extern/ceres/CMakeLists.txt @@ -73,10 +73,12 @@ set(SRC internal/ceres/file.cc internal/ceres/generated/partitioned_matrix_view_d_d_d.cc internal/ceres/generated/schur_eliminator_d_d_d.cc + internal/ceres/gradient_checker.cc internal/ceres/gradient_checking_cost_function.cc internal/ceres/gradient_problem.cc internal/ceres/gradient_problem_solver.cc internal/ceres/implicit_schur_complement.cc + internal/ceres/is_close.cc internal/ceres/iterative_schur_complement_solver.cc internal/ceres/lapack.cc internal/ceres/levenberg_marquardt_strategy.cc @@ -116,6 +118,7 @@ set(SRC internal/ceres/triplet_sparse_matrix.cc internal/ceres/trust_region_minimizer.cc internal/ceres/trust_region_preprocessor.cc + internal/ceres/trust_region_step_evaluator.cc internal/ceres/trust_region_strategy.cc internal/ceres/types.cc internal/ceres/wall_time.cc @@ -204,6 +207,7 @@ set(SRC internal/ceres/householder_vector.h internal/ceres/implicit_schur_complement.h internal/ceres/integral_types.h + internal/ceres/is_close.h internal/ceres/iterative_schur_complement_solver.h internal/ceres/lapack.h internal/ceres/levenberg_marquardt_strategy.h @@ -248,6 +252,7 @@ set(SRC internal/ceres/triplet_sparse_matrix.h internal/ceres/trust_region_minimizer.h internal/ceres/trust_region_preprocessor.h + internal/ceres/trust_region_step_evaluator.h internal/ceres/trust_region_strategy.h internal/ceres/visibility_based_preconditioner.h internal/ceres/wall_time.h diff --git a/extern/ceres/ChangeLog b/extern/ceres/ChangeLog index 0e6c195174c..ae8d42a7c95 100644 --- a/extern/ceres/ChangeLog +++ b/extern/ceres/ChangeLog @@ -1,659 +1,588 @@ -commit aef9c9563b08d5f39eee1576af133a84749d1b48 -Author: Alessandro Gentilini <agentilini@gmail.com> -Date: Tue Oct 6 20:43:45 2015 +0200 +commit 8590e6e8e057adba4ec0083446d00268565bb444 +Author: Sameer Agarwal <sameeragarwal@google.com> +Date: Thu Oct 27 12:29:37 2016 -0700 - Add test for Bessel functions. + Remove two checks from rotation.h + + This allows rotation.h to remove its dependency on glog. - Change-Id: Ief5881e8027643d7ef627e60a88fdbad17f3d884 + Change-Id: Ia6aede93ee51a4bd4039570dc8edd100a7045329 -commit 49c86018e00f196c4aa9bd25daccb9919917efee -Author: Alessandro Gentilini <agentilini@gmail.com> -Date: Wed Sep 23 21:59:44 2015 +0200 +commit e892499e8d8977b9178a760348bdd201ec5f3489 +Author: Je Hyeong Hong <jhh37@outlook.com> +Date: Tue Oct 18 22:49:11 2016 +0100 - Add Bessel functions in order to use them in residual code. + Relax the tolerance in QuaternionParameterizationTestHelper. - See "How can I use the Bessel function in the residual function?" at - https://groups.google.com/d/msg/ceres-solver/Vh1gpqac8v0/NIK1EiWJCAAJ + This commit relaxes the tolerance value for comparing between the actual + local matrix and the expected local matrix. Without this fix, + EigenQuaternionParameterization.ZeroTest could fail as the difference + exactly matches the value of std::numeric_limits<double>::epsilon(). - Change-Id: I3e80d9f9d1cadaf7177076e493ff46ace5233b76 + Change-Id: Ic4d3f26c0acdf5f16fead80dfdc53df9e7dabbf9 -commit dfb201220c034fde00a242d0533bef3f73b2907d -Author: Simon Rutishauser <simon.rutishauser@pix4d.com> -Date: Tue Oct 13 07:33:58 2015 +0200 +commit 7ed9e2fb7f1dff264c5e4fbaa89ee1c4c99df269 +Author: Sameer Agarwal <sameeragarwal@google.com> +Date: Wed Oct 19 04:45:23 2016 -0700 - Make miniglog threadsafe on non-windows system by using - localtime_r() instead of localtime() for time formatting + Occured -> Occurred. - Change-Id: Ib8006c685cd8ed4f374893bef56c4061ca2c9747 + Thanks to Phillip Huebner for reporting this. + + Change-Id: I9cddfbb373aeb496961d08e434fe661bff4abd29 -commit 41455566ac633e55f222bce7c4d2cb4cc33d5c72 -Author: Alex Stewart <alexs.mac@gmail.com> -Date: Mon Sep 28 22:43:42 2015 +0100 +commit b82f97279682962d8c8ae1b6d9e801ba072a0ab1 +Author: Je Hyeong Hong <jhh37@outlook.com> +Date: Tue Oct 18 21:18:32 2016 +0100 - Remove link-time optimisation (LTO). + Fix a test error in autodiff_test.cc. - - On GCC 4.9+ although GCC supports LTO, it requires use of the - non-default gcc-ar & gcc-ranlib. Whilst we can ensure Ceres is - compiled with these, doing so with GCC 4.9 causes multiple definition - linker errors of static ints inside Eigen when compiling the tests - and examples when they are not also built with LTO. - - On OS X (Xcode 6 & 7) after the latest update to gtest, if LTO - is used when compiling the tests (& examples), two tests fail - due to typeinfo::operator== (things are fine if only Ceres itself is - compiled with LTO). - - This patch disables LTO for all compilers. It should be revisited when - the performance is more stable across our supported compilers. + Previously, the test for the projective camera model would fail as no + tolerance is set in line 144. To resolve this, this commit changes + assert_equal to assert_near. - Change-Id: I17b52957faefbdeff0aa40846dc9b342db1b02e3 + Change-Id: I6cd3379083b1a10c7cd0a9cc83fd6962bb993cc9 -commit 89c40005bfceadb4163bd16b7464b3c2ce740daf -Author: Alex Stewart <alexs.mac@gmail.com> -Date: Sun Sep 27 13:37:26 2015 +0100 - - Only use LTO when compiling Ceres itself, not tests or examples. - - - If Ceres is built as a shared library, and LTO is enabled for Ceres - and the tests, then type_info::operator==() incorrectly returns false - in gtests' CheckedDowncastToActualType() in the following tests: - -- levenberg_marquardt_strategy_test. - -- gradient_checking_cost_function_test. - on at least Xcode 6 & 7 as reported here: - https://github.com/google/googletest/issues/595. - - This does not appear to be a gtest issue, but is perhaps an LLVM bug - or an RTTI shared library issue. Either way, disabling the use of - LTO when compiling the test application resolves the issue. - - Allow LTO to be enabled for GCC, if it is supported. - - Add CMake function to allow easy appending to target properties s/t - Ceres library-specific compile flags can be iteratively constructed. - - Change-Id: I923e6aae4f7cefa098cf32b2f8fc19389e7918c9 - -commit 0794f41cca440f7f65d9a44e671f66f6e498ef7c +commit 5690b447de5beed6bdda99b7f30f372283c2fb1a Author: Sameer Agarwal <sameeragarwal@google.com> -Date: Sat Sep 26 14:10:15 2015 -0700 +Date: Thu Oct 13 09:52:02 2016 -0700 - Documentation updates. + Fix documentation source for templated functions in rotation.h - 1. Fix a typo in the Trust Region algorithm. - 2. Add ARL in the list of users. - 3. Update the version history. + Change-Id: Ic1b2e6f0e6eb9914f419fd0bb5af77b66252e57c + +commit 2f8f98f7e8940e465de126fb51282396f42bea20 +Author: Sameer Agarwal <sameeragarwal@google.com> +Date: Thu Oct 13 09:35:18 2016 -0700 + + Prepare for 1.12.0RC1 - Change-Id: Ic286e8ef1a71af07f3890b7592dd3aed9c5f87ce + Change-Id: I23eaf0b46117a01440143001b74dacfa5e57cbf0 -commit 90e32a8dc437dfb0e6747ce15a1f3193c13b7d5b -Author: Alex Stewart <alexs.mac@gmail.com> -Date: Mon Sep 21 21:08:25 2015 +0100 +commit 55c12d2e9569fe4aeac3ba688ac36810935a37ba +Author: Damon Kohler <damonkohler@google.com> +Date: Wed Oct 5 16:30:31 2016 +0200 + + Adds package.xml to support Catkin. + + Change-Id: I8ad4d36a8b036417604a54644e0bb70dd1615feb + +commit 0bcce6565202f5476e40f12afc0a99eb44bd9dfb +Author: Sameer Agarwal <sameeragarwal@google.com> +Date: Mon Oct 10 23:30:42 2016 -0700 - Use old minimum iOS version flags on Xcode < 7.0. + Fix tabs in Android.mk - - The newer style, which are more specific and match the SDK names - are not available on Xcode < 7.0. + Change-Id: Ie5ab9a8ba2b727721565e1ded242609b6df5f8f5 + +commit e6ffe2667170d2fc435443685c0163396fc52d7b +Author: Sameer Agarwal <sameeragarwal@google.com> +Date: Mon Oct 10 22:47:08 2016 -0700 + + Update the version history. - Change-Id: I2f07a0365183d2781157cdb05fd49b30ae001ac5 + Change-Id: I9a57b0541d6cebcb695ecb364a1d4ca04ea4e06c -commit 26cd5326a1fb99ae02c667eab9942e1308046984 -Author: Alex Stewart <alexs.mac@gmail.com> -Date: Mon Sep 21 10:16:01 2015 +0100 +commit 0a4ccb7ee939ab35b22e26758401e039b033b176 +Author: David Gossow <dgossow@google.com> +Date: Wed Sep 7 21:38:12 2016 +0200 - Add gtest-specific flags when building/using as a shared library. + Relaxing Jacobian matching in Gradient Checker test. - - Currently these flags are only used to define the relevant DLL export - prefix for Windows. + Any result of an arithmetic operation on floating-point matrices + should never be checked for strict equality with some expected + value, due to limited floating point precision on different machines. + This fixes some occurences of exact checks in the gradient checker + unit test that were causing problems on some platforms. - Change-Id: I0c05207b512cb4a985390aefc779b91febdabb38 + Change-Id: I48e804c9c705dc485ce74ddfe51037d4957c8fcb -commit c4c79472112a49bc1340da0074af2d15b1c89749 -Author: Alex Stewart <alexs.mac@gmail.com> -Date: Sun Sep 20 18:26:59 2015 +0100 +commit ee44fc91b59584921c1d1c8db153fda6d633b092 +Author: Je Hyeong Hong <jhh37@outlook.com> +Date: Mon Oct 3 12:19:30 2016 +0100 - Clean up iOS.cmake to use xcrun/xcodebuild & libtool. + Fix an Intel compiler error in covariance_impl.cc. - - Substantial cleanup of iOS.cmake to use xcrun & xcodebuild to - determine the SDK & tool paths. - - Use libtool -static to link libraries instead of ar + ranlib, which - is not compatible with Xcode 7+, this change should be backwards - compatible to at least Xcode 6. - - Force locations of unordered_map & shared_ptr on iOS to work around - check_cxx_source_compiles() running in a forked CMake instance without - access to the variables (IOS_PLATFORM) defined by the user. - - Minor CMake style updates. + Intel C compiler strictly asks for parallel loops with collapse to be + perfectly nested. Otherwise, compiling Ceres with ICC will throw an + error at line 348 of covariance_impl.cc. - Change-Id: I5f83a60607db34d461ebe85f9dce861f53d98277 + Change-Id: I1ecb68e89b7faf79e4153dfe6675c390d1780db4 -commit 155765bbb358f1d19f072a4b54825faf1c059910 +commit 9026d69d1ce1e0bcd21debd54a38246d85c7c6e4 Author: Sameer Agarwal <sameeragarwal@google.com> -Date: Wed Sep 16 06:56:08 2015 -0700 +Date: Thu Sep 22 17:20:14 2016 -0700 + + Allow SubsetParameterization to hold all parameters constant + + 1. SubsetParameterization can now be constructed such that all + parameters are constant. This is required for it be used as part + of a ProductParameterization to hold a part of parameter block + constant. For example, a parameter block consisting of a rotation + as a quaternion and a translation vector can now have a local + parameterization where the translation part is constant and the + quaternion part has a QuaternionParameterization associated with it. + + 2. The check for the tangent space of a parameterization being + positive dimensional. We were not doing this check up till now + and the user could accidentally create parameterizations like this + and create a problem for themselves. This will ensure that even + though one can construct a SubsetParameterization where all + parameters are constant, you cannot actually use it as a local + parameterization for an entire parameter block. Which is how + it was before, but the check was inside the SubsetParameterization + constructor. + + 3. Added more tests and refactored existing tests to be more + granular. + + Change-Id: Ic0184a1f30e3bd8a416b02341781a9d98e855ff7 + +commit a36693f83da7a3fd19dce473d060231d4cc97499 +Author: Sameer Agarwal <sameeragarwal@google.com> +Date: Sat Sep 17 16:31:41 2016 -0700 - Import the latest version of gtest and gmock. + Update version history - Change-Id: I4b686c44bba823cab1dae40efa99e31340d2b52a + Change-Id: Ib2f0138ed7a1879ca3b2173e54092f7ae8dd5c9d -commit 0c4647b8f1496c97c6b9376d9c49ddc204aa08dd -Author: Alex Stewart <alexs.mac@gmail.com> -Date: Wed Sep 16 20:01:11 2015 +0100 +commit 01e23e3d33178fdd050973666505c1080cfe04c3 +Author: David Gossow <dgossow@google.com> +Date: Thu Sep 8 12:22:28 2016 +0200 - Remove FAQ about increasing inlining threshold for Clang. + Removing duplicate include directive. - - Changing the inlining threshold for Clang as described has a minimal - effect on user performance. - - The problem that originally prompted the belief that it did was - due to an erroneous CXX flag configuration (in user code). - - Change-Id: I03017241c0f87b8dcefb8c984ec3b192afd97fc2 + Change-Id: I729ae6501497746d1bb615cb893ad592e16ddf3f -commit f4b768b69afcf282568f9ab3a3f0eb8078607468 +commit 99b8210cee92cb972267537fb44bebf56f812d52 Author: Sameer Agarwal <sameeragarwal@google.com> -Date: Mon Sep 14 13:53:24 2015 -0700 +Date: Wed Sep 7 15:31:30 2016 -0700 - Lint changes from William Rucklidge + Update Android.mk to include new files. - Change-Id: I0dac2549a8fa2bfd12f745a8d8a0db623b7ec1ac + Change-Id: Id543ee7d2a65b65c868554a17f593c0a4958e873 -commit 5f2f05c726443e35767d677daba6d25dbc2d7ff8 +commit 195d8d13a6a3962ac39ef7fcdcc6add0216eb8bc Author: Sameer Agarwal <sameeragarwal@google.com> -Date: Fri Sep 11 22:19:38 2015 -0700 +Date: Tue Sep 6 07:12:23 2016 -0700 - Refactor system_test + Remove two DCHECKs from CubicHermiteSpline. - 1. Move common test infrastructure into test_util. - 2. system_test now only contains powells function. - 3. Add bundle_adjustment_test. + They were present as debugging checks but were causing problems + with the build on 32bit i386 due to numerical cancellation issues, + where x ~ -epsilon. - Instead of a single function which computes everything, - there is now a test for each solver configuration which - uses the reference solution computed by the fixture. + Removing these checks only changes the behaviour in Debug mode. + We are already handling such small negative numbers in production + if they occur. All that this change does is to remove the crash. - Change-Id: I16a9a9a83a845a7aaf28762bcecf1a8ff5aee805 - -commit 1936d47e213142b8bf29d3f548905116092b093d -Author: Alex Stewart <alexs.mac@gmail.com> -Date: Tue Sep 8 23:27:42 2015 +0100 - - Revert increased inline threshold (iff Clang) to exported Ceres target. + https://github.com/ceres-solver/ceres-solver/issues/212 - - Increasing the inline threshold results in very variable performance - improvements, and could potentially confuse users if they are trying - to set the inline threshold themselves. - - As such, we no longer export our inline threshold configuration for - Clang, but instead document how to change it in the FAQs. + Thanks to @NeroBurner and @debalance for reporting this. - Change-Id: I88e2e0001e4586ba2718535845ed1e4b1a5b72bc + Change-Id: I66480e86d4fa0a4b621204f2ff44cc3ff8d01c04 -commit a66d89dcda47cefda83758bfb9e7374bec4ce866 +commit 83041ac84f2d67c28559c67515e0e596a3f3aa20 Author: Sameer Agarwal <sameeragarwal@google.com> -Date: Sat Sep 5 16:50:20 2015 -0700 +Date: Fri Sep 2 19:10:35 2016 -0700 - Get ready for 1.11.0RC1 + Fix some compiler warnings. - Update version numbers. - Drop CERES_VERSION_ABI macro. + Reported by Richard Trieu. - Change-Id: Ib3eadabb318afe206bb196a5221b195d26cbeaa0 + Change-Id: I202b7a7df09cc19c92582d276ccf171edf88a9fb -commit 1ac3dd223c179fbadaed568ac532af4139c75d84 +commit 8c4623c63a2676e79e7917bb0561f903760f19b9 Author: Sameer Agarwal <sameeragarwal@google.com> -Date: Sat Sep 5 15:30:01 2015 -0700 +Date: Thu Sep 1 00:05:09 2016 -0700 - Fix a bug in CompressedRowSparseMatrix::AppendRows + Update ExpectArraysClose to use ExpectClose instead of EXPECT_NEAR - The test for CompressedRowSparseMatrix::AppendRows tries to add - a matrix of size zero, which results in an invalid pointer deferencing - even though that pointer is never written to. + The documentation for ExpectArraysClose and its implementation + did not match. - Change-Id: I97dba37082bd5dad242ae1af0447a9178cd92027 - -commit 67622b080c8d37b5e932120a53d4ce76b80543e5 -Author: Sameer Agarwal <sameeragarwal@google.com> -Date: Sat Sep 5 13:18:38 2015 -0700 - - Fix a pointer access bug in Ridders' algorithm. + This change makes the polynomial_test not fail on 64bit AMD builds. - A pointer to an Eigen matrix was being used as an array. + Thanks to Phillip Huebner for reporting this. - Change-Id: Ifaea14fa3416eda5953de49afb78dc5a6ea816eb + Change-Id: I503f2d3317a28d5885a34f8bdbccd49d20ae9ba2 -commit 5742b7d0f14d2d170054623ccfee09ea214b8ed9 +commit 2fd39fcecb47eebce727081c9ffb8edf86c33669 Author: Sameer Agarwal <sameeragarwal@google.com> -Date: Wed Aug 26 09:24:33 2015 -0700 +Date: Thu Sep 1 16:05:06 2016 -0700 - Improve performance of SPARSE_NORMAL_CHOLESKY + dynamic_sparsity + FindWithDefault returns by value rather than reference. + + Returning by reference leads to lifetime issues with the default + value which may go out of scope by the time it is used. + + Thanks to @Ardavel for reporting this, as this causes graph_test + to fail on VS2015x64. - The outer product computation logic in SparseNormalCholeskySolver - does not work well with dynamic sparsity. The overhead of computing - the sparsity pattern of the normal equations is only amortized if - the sparsity is constant. If the sparsity can change from call to call - SparseNormalCholeskySolver will actually be more expensive. + https://github.com/ceres-solver/ceres-solver/issues/216 - For Eigen and for CXSparse we now explicitly compute the normal - equations using their respective matrix-matrix product routines and solve. - Change-Id: Ifbd8ed78987cdf71640e66ed69500442526a23d4 + Change-Id: I596481219cfbf7622d49a6511ea29193b82c8ba3 -commit d0b6cf657d6ef0dd739e958af9a5768f2eecfd35 -Author: Keir Mierle <mierle@gmail.com> -Date: Fri Sep 4 18:43:41 2015 -0700 +commit 716f049a7b91a8f3a4632c367d9534d1d9190a81 +Author: Mike Vitus <vitus@google.com> +Date: Wed Aug 31 13:38:30 2016 -0700 - Fix incorrect detect structure test + Convert pose graph 2D example to glog and gflags. - Change-Id: I7062f3639147c40b57947790d3b18331a39a366b + Change-Id: I0ed75a60718ef95199bb36f33d9eb99157d11d40 -commit 0e8264cc47661651a11e2dd8570c210082963545 -Author: Alex Stewart <alexs.mac@gmail.com> -Date: Sat Aug 22 16:23:05 2015 +0100 +commit 46c5ce89dda308088a5fdc238d0c126fdd2c2b58 +Author: David Gossow <dgossow@google.com> +Date: Wed Aug 31 18:40:57 2016 +0200 - Add increased inline threshold (iff Clang) to exported Ceres target. + Fix compiler errors on some systems - - When compiled with Clang, Ceres and all of the examples are compiled - with an increased inlining-threshold, as the default value can result - in poor Eigen performance. - - Previously, client code using Ceres would typically not use an - increased inlining-threshold (unless the user has specifically added - it themselves). However, increasing the inlining threshold can result - in significant performance improvements in auto-diffed CostFunctions. - - This patch adds the inlining-threshold flags to the interface flags - for the Ceres CMake target s/t any client code using Ceres (via - CMake), and compiled with Clang, will now be compiled with the same - increased inlining threshold as used by Ceres itself. + This fixes some signed-unsigned comparisons and a missing header + include. - Change-Id: I31e8f1abfda140d22e85bb48aa57f028a68a415e + Change-Id: Ieb2bf6e905faa74851bc4ac4658d2f1da24b6ecc -commit a1b3fce9e0a4141b973f6b4dd9b08c4c13052d52 -Author: Alex Stewart <alexs.mac@gmail.com> -Date: Mon Aug 31 14:14:56 2015 +0100 +commit b102d53e1dd7dab132e58411183b6fffc2090590 +Author: David Gossow <dgossow@google.com> +Date: Wed Aug 31 10:21:20 2016 +0200 - Add optional export of Ceres build directory to new features list. + Gradient checker multithreading bugfix. - Change-Id: I6f1e42b41957ae9cc98fd9dcd1969ef64c4cd96f - -commit e46777d8df068866ef80902401a03e29348d11ae -Author: Alex Stewart <alexs.mac@gmail.com> -Date: Mon Aug 31 12:41:54 2015 +0100 - - Credit reporters of buildsystem bugs in version history. + This is a follow-up on c/7470. GradientCheckingCostFunction calls + callback_->SetGradientErrorDetected() in its Evaluate method, + which will run in multiple threads simultaneously when enabling + this option in the solver. Thus, the string append operation + inside that method has to be protected by a mutex. - Change-Id: I16fe7973534cd556d97215e84268ae0b8ec4e11a + Change-Id: I314ef1df2be52595370d9af05851bf6da39bb45e -commit 01548282cb620e5e3ac79a63a391cd0afd5433e4 +commit 79a28d1e49af53f67af7f3387d07e7c9b7339433 Author: Sameer Agarwal <sameeragarwal@google.com> -Date: Sun Aug 30 22:29:27 2015 -0700 +Date: Wed Aug 31 06:47:45 2016 -0700 - Update the version history. + Rename a confusingly named member of Solver::Options + + Solver::Options::numeric_derivative_relative_step_size to + Solver::Options::gradient_check_numeric_derivative_relative_step_size - Change-Id: I29873bed31675e0108f1a44f53f7bc68976b7f98 + Change-Id: Ib89ae3f87e588d4aba2a75361770d2cec26f07aa -commit 2701429f770fce69ed0c77523fa43d7bc20ac6dc +commit 358ae741c8c4545b03d95c91fa546d9a36683677 Author: Sameer Agarwal <sameeragarwal@google.com> -Date: Sun Aug 30 21:33:57 2015 -0700 +Date: Wed Aug 31 06:58:41 2016 -0700 - Use Eigen::Dynamic instead of ceres::DYNAMIC in numeric_diff.h + Note that Problem::Evaluate cannot be called from an IterationCallback - Change-Id: Iccb0284a8fb4c2160748dfae24bcd595f1d4cb5c + Change-Id: Ieabdc2d40715e6b547ab22156ba32e9c8444b7ed -commit 4f049db7c2a3ee8cf9910c6eac96be6a28a5999c -Author: Tal Ben-Nun <tbennun@gmail.com> -Date: Wed May 13 15:43:51 2015 +0300 +commit 44044e25b14d7e623baae4505a17c913bdde59f8 +Author: Sameer Agarwal <sameeragarwal@google.com> +Date: Wed Aug 31 05:50:58 2016 -0700 - Adaptive numeric differentiation using Ridders' method. + Update the NumTraits for Jets - This method numerically computes function derivatives in different - scales, extrapolating between intermediate results to conserve function - evaluations. Adaptive differentiation is essential to produce accurate - results for functions with noisy derivatives. + 1. Use AVX if EIGEN_VECTORIZE_AVX is defined. + 2. Make the cost of division same as the cost of multiplication. - Full changelist: - -Created a new type of NumericDiffMethod (RIDDERS). - -Implemented EvaluateRiddersJacobianColumn in NumericDiff. - -Created unit tests with f(x) = x^2 + [random noise] and - f(x) = exp(x). + These are updates to the original numtraits update needed for eigen 3.3 + that Shaheen Gandhi sent out. - Change-Id: I2d6e924d7ff686650272f29a8c981351e6f72091 + Change-Id: Ic1e3ed7d05a659c7badc79a894679b2dd61c51b9 -commit 070bba4b43b4b7449628bf456a10452fd2b34d28 +commit 4b6ad5d88e45ce8638c882d3e8f08161089b6dba Author: Sameer Agarwal <sameeragarwal@google.com> -Date: Tue Aug 25 13:37:33 2015 -0700 +Date: Sat Aug 27 23:21:55 2016 -0700 - Lint fixes from William Rucklidge + Use ProductParameterization in bundle_adjuster.cc - Change-Id: I719e8852859c970091df842e59c44e02e2c65827 - -commit 887a20ca7f02a1504e35f7cabbdfb2e0842a0b0b -Author: Alex Stewart <alexs.mac@gmail.com> -Date: Wed Aug 12 21:41:43 2015 +0100 - - Build position independent code when compiling Ceres statically. + Previously, when using a quaternion to parameterize the camera + orientation, the camera parameter block was split into two + parameter blocks. One for the rotation and another for the + translation and intrinsics. This was to enable the use of the + Quaternion parameterization. - - Previously, when Ceres was built as a static library we did not - compile position independent code. This means that the resulting - static library could not be linked against shared libraries, but - could be used by executables. - - To enable the use of a static Ceres library by other shared libraries - as reported in [1], the static library must be generated from - position independent code (except on Windows, where PIC does not - apply). + Now that we have a ProductParameterization which allows us + to compose multiple parameterizations, this is no longer needed + and we use a size 10 parameter block instead. - [1] https://github.com/Itseez/opencv_contrib/pull/290#issuecomment-130389471 + This leads to a more than 2x improvements in the linear solver time. - Change-Id: I99388f1784ece688f91b162d009578c5c97ddaf6 + Change-Id: I78b8f06696f81fee54cfe1a4ae193ee8a5f8e920 -commit 860bba588b981a5718f6b73e7e840e5b8757fe65 -Author: Sameer Agarwal <sameeragarwal@google.com> -Date: Tue Aug 25 09:43:21 2015 -0700 +commit bfc916cf1cf753b85c1e2ac037e2019ee891f6f9 +Author: Shaheen Gandhi <visigoth@gmail.com> +Date: Thu Aug 4 12:10:14 2016 -0700 - Fix a bug in DetectStructure + Allow ceres to be used with the latest version of Eigen - The logic for determing static/dynamic f-block size in - DetectStructure was broken in a corner case, where the very first - row block which was used to initialize the f_block_size contained - more than one f blocks of varying sizes. The way the if block - was structured, no iteration was performed on the remaining - f-blocks and the loop failed to detect that the f-block size - was actually changing. - - If in the remaining row blocks, there were no row blocks - with varying f-block sizes, the function will erroneously - return a static f-block size. - - Thanks to Johannes Schonberger for providing a reproduction for this - rather tricky corner case. - - Change-Id: Ib442a041d8b7efd29f9653be6a11a69d0eccd1ec + Change-Id: Ief3b0f6b405484ec04ecd9ab6a1e1e5409a594c2 -commit b0cbc0f0b0a22f01724b7b647a4a94db959cc4e4 -Author: Johannes Schönberger <hannesschoenberger@gmail.com> -Date: Thu Aug 20 14:21:30 2015 -0400 +commit edbd48ab502aa418ad9700ee5c3ada5f9268b90a +Author: Alex Stewart <alexs.mac@gmail.com> +Date: Sun Jul 10 14:13:51 2016 +0100 - Reduce memory footprint of SubsetParameterization + Enable support for OpenMP in Clang if detected. + + - Previously we disabled OpenMP if Clang was detected, as it did not + support it. However as of Clang 3.8 (and potentially Xcode 8) OpenMP + is supported. - Change-Id: If113cb4696d5aef3e50eed01fba7a3d4143b7ec8 + Change-Id: Ia39dac9fe746f1fc6310e08553f85f3c37349707 -commit ad2a99777786101411a971e59576ca533a297013 -Author: Sergey Sharybin <sergey.vfx@gmail.com> -Date: Sat Aug 22 11:18:45 2015 +0200 +commit f6df6c05dd83b19fa90044106ebaca40957998ae +Author: Mike Vitus <vitus@google.com> +Date: Thu Aug 18 19:27:43 2016 -0700 - Fix for reoder program unit test when built without suitesparse - - This commit fixes failure of reorder_program_test when Ceres is built without - any suitesparse. + Add an example for modeling and solving a 3D pose graph SLAM problem. - Change-Id: Ia23ae8dfd20c482cb9cd1301f17edf9a34df3235 + Change-Id: I750ca5f20c495edfee5f60ffedccc5bd8ba2bb37 -commit 4bf3868beca9c17615f72ec03730cddb3676acaa -Author: Sameer Agarwal <sameeragarwal@google.com> -Date: Sun Aug 9 15:24:45 2015 -0700 +commit ac3b8e82175122e38bafaaa9cd419ba3cee11087 +Author: David Gossow <dgossow@google.com> +Date: Fri Apr 29 16:07:11 2016 +0200 - Fix a bug in the Schur eliminator + Gradient checking cleanup and local parameterization bugfix - The schur eliminator treats rows with e blocks and row with - no e blocks separately. The template specialization logic only - applies to the rows with e blocks. + Change the Ceres gradient checking API to make is useful for + unit testing, clean up code duplication and fix interaction between + gradient checking and local parameterizations. - So, in cases where the rows with e-blocks have a fixed size f-block - but the rows without e-blocks have f-blocks of varying sizes, - DetectStructure will return a static f-block size, but we need to be - careful that we do not blindly use that static f-block size everywhere. + There were two gradient checking implementations, one being used + when using the check_gradients flag in the Solver, the other + being a standalone class. The standalone version was restricted + to cost functions with fixed parameter sizes at compile time, which + is being lifted here. This enables it to be used inside the + GradientCheckingCostFunction as well. - This patch fixes a bug where such care was not being taken, where - it was assumed that the static f-block size could be assumed for all - f-block sizes. + In addition, this installs new hooks in the Solver to ensure + that Solve will fail if any incorrect gradients are detected. This + way, you can set the check_gradient flags to true and detect + errors in an automated way, instead of just printing error information + to the log. The error log is now also returned in the Solver summary + instead of being printed directly. The user can then decide what to + do with it. The existing hooks for user callbacks are used for + this purpose to keep the internal API changes minimal and non-invasive. - A new test is added, which triggers an exception in debug mode. In - release mode this error does not present itself, due to a peculiarity - of the way Eigen works. + The last and biggest change is the way the the interaction between + local parameterizations and the gradient checker works. Before, + local parameterizations would be ignored by the checker. However, + if a cost function does not compute its Jacobian along the null + space of the local parameterization, this wil not have any effect + on the solver, but would result in a gradient checker error. + With this change, the Jacobians are multiplied by the Jacobians + of the respective local parameterization and thus being compared + in the tangent space only. - Thanks to Werner Trobin for reporting this bug. + The typical use case for this are quaternion parameters, where + a cost function will typically assume that the quaternion is + always normalized, skipping the correct computation of the Jacobian + along the normal to save computation cost. - Change-Id: I8ae7aabf8eed8c3f9cf74b6c74d632ba44f82581 + Change-Id: I5e1bb97b8a899436cea25101efe5011b0bb13282 -commit 1635ce726078f00264b89d7fb6e76fd1c2796e59 -Author: Sameer Agarwal <sameeragarwal@google.com> -Date: Wed Aug 19 00:26:02 2015 -0700 +commit d4264ec10d9a270b53b5db86c0245ae8cbd2cf18 +Author: Mike Vitus <vitus@google.com> +Date: Wed Aug 17 13:39:05 2016 -0700 - Fix a bug in the reordering code. - - When the user provides an ordering which starts at a non-zero group id, - or has gaps in the groups, then CAMD, the algorithm used to reorder - the program can crash or return garbage results. - - The solution is to map the ordering into grouping constraints, and then - to re-number the groups to be contiguous using a call to - MapValuesToContiguousRange. This was already done for CAMD based - ordering for Schur type solvers, but was not done for SPARSE_NORMAL_CHOLESKY. - - Thanks to Bernhard Zeisl for not only reporting the bug but also - providing a reproduction. + Add a quaternion local parameterization for Eigen's quaternion element convention. - Change-Id: I5cfae222d701dfdb8e1bda7f0b4670a30417aa89 + Change-Id: I7046e8b24805313c5fb6a767de581d0054fcdb83 -commit 4c3f8987e7f0c51fd367cf6d43d7eb879e79589f -Author: Simon Rutishauser <simon.rutishauser@pix4d.com> -Date: Thu Aug 13 11:10:44 2015 +0200 +commit fd7cab65ef30fbc33612220abed52dd5073413c4 +Author: Mike Vitus <vitus@google.com> +Date: Wed Aug 10 09:29:12 2016 -0700 - Add missing CERES_EXPORT to ComposedLoss + Fix typos in the pose graph 2D example. - Change-Id: Id7db388d41bf53e6e5704039040c9d2c6bf4c29c + Change-Id: Ie024ff6b6cab9f2e8011d21121a91931bd987bd1 -commit 1a740cc787b85b883a0703403a99fe49662acb79 -Author: Sameer Agarwal <sameeragarwal@google.com> -Date: Tue Aug 11 18:08:05 2015 -0700 +commit 375dc348745081f89693607142d8b6744a7fb6b4 +Author: Mike Vitus <vitus@google.com> +Date: Wed Aug 3 18:51:16 2016 -0700 - Add the option to use numeric differentiation to nist and more_garbow_hillstrom + Remove duplicate entry for the NIST example in the docs. - Change-Id: If0a5caef90b524dcf5e2567c5b681987f5459401 + Change-Id: Ic4e8f9b68b77b5235b5c96fe588cc56866dab759 -commit ea667ede5c038d6bf3d1c9ec3dbdc5072d1beec6 -Author: Alex Stewart <alexs.mac@gmail.com> -Date: Sun Aug 9 16:56:13 2015 +0100 +commit f554681bf22d769abc12dd6d346ef65f9bb22431 +Author: Mike Vitus <vitus@google.com> +Date: Mon Jul 25 18:30:48 2016 -0700 - Fix EIGENSPARSE option help s/t it displays in CMake ncurses GUI. - - - Shorten description for EIGENSPARSE to a single line, as otherwise - it is not correctly displayed in the ncurses CMake GUI. - - Made explicit in description that this results in an LGPL licensed - version of Ceres (this is also made clear in the CMake log output if - EIGENSPARSE is enabled). + Add an example for modeling and solving a 2D pose graph SLAM problem. - Change-Id: I11678a9cbc7a817133c22128da01055a3cb8a26d + Change-Id: Ia89b12af7afa33e7b1b9a68d69cf2a0b53416737 -commit a14ec27fb28ab2e8d7f1c9d88e41101dc6c0aab5 -Author: Richard Stebbing <richie.stebbing@gmail.com> -Date: Fri Aug 7 08:42:03 2015 -0700 +commit e1bcc6e0f51512f43aa7bfb7b0d62f7ac1d0cd4b +Author: Sameer Agarwal <sameeragarwal@google.com> +Date: Wed May 18 07:52:48 2016 -0700 - Fix SparseNormalCholeskySolver with dynamic sparsity. - - The previous implementation incorrectly cached the outer product matrix - pattern even when `dynamic_sparsity = true`. + Add additional logging for analyzing orderings - Change-Id: I1e58315a9b44f2f457d07c56b203ab2668bfb8a2 + Change-Id: Ic68d2959db35254e2895f11294fb25de4d4b8a81 -commit 3dd7fced44ff00197fa9fcb1f2081d12be728062 -Author: Alex Stewart <alexs.mac@gmail.com> -Date: Sun Aug 9 16:38:50 2015 +0100 +commit 16980b4fec846f86910c18772b8145bcb55f4728 +Author: Mike Vitus <vitus@google.com> +Date: Fri Jul 15 13:37:49 2016 -0700 - Remove legacy dependency detection macros. + Delete the remove_definitons command from sampled_functions + CMakeLists.txt because it will be inherited from the top level examples + CMakeLists.txt. - - Before the new CMake buildsystem in 1.8, Ceres used non-standard - HINTS variables for dependencies. For backwards compatibility CMake - macros were added to translate these legacy variables into the new - (standard) variables. - - As it has now been multiple releases since the legacy variables - were used and they no longer appear in any of the documentation - support for them has now expired. - - Change-Id: I2cc72927ed711142ba7943df334ee008181f86a2 + Change-Id: I25593587df0ae84fd8ddddc589bc2a13f3777427 -commit 8b32e258ccce1eed2a50bb002add16cad13aff1e -Author: Alex Stewart <alexs.mac@gmail.com> -Date: Sun Aug 9 15:42:39 2015 +0100 +commit a04490be97800e78e59db5eb67fa46226738ffba +Author: Mike Vitus <vitus@google.com> +Date: Thu Jul 14 10:10:13 2016 -0700 - Fix failed if() condition expansion if gflags is not found. - - - If a CMake-ified version of gflags is not detected, then - gflags_LIBRARIES is not set and the TARGET condition within a - multiconditional if() statement prevents configuration. + Add readme for the sampled_function example. - Change-Id: Ia92e97523d7a1478ab36539726b9540d7cfee5d0 + Change-Id: I9468b6a7b9f2ffdd2bf9f0dd1f4e1d5f894e540c -commit cc8d47aabb9d63ba4588ba7295058a6191c2df83 +commit ff11d0e63d4678188e8cabd40a532ba06912fe5a Author: Alex Stewart <alexs.mac@gmail.com> -Date: Sun Aug 9 15:18:42 2015 +0100 +Date: Wed Jun 29 09:31:45 2016 +0100 - Update all CMake to lowercase function name style. + Use _j[0,1,n]() Bessel functions on MSVC to avoid deprecation errors. - - Updated to new CMake style where function names are all lowercase, - this will be backwards compatible as CMake function names are - case insensitive. - - Updated using Emacs' M-x unscreamify-cmake-buffer. + - Microsoft deprecated the POSIX Bessel functions: j[0,1,n]() in favour + of _j[0,1,n](), it appears since at least MSVC 2005: + https://msdn.microsoft.com/en-us/library/ms235384(v=vs.100).aspx. + - As this occurs in jet.h (templated public header), although Ceres + suppresses the warning when it itself is built (to suppress a warning + about the insecurity of using std::copy), it will crop up again in + client code (without this fix) unless it is explicitly suppressed + there also. + - Raised as Issue #190: + https://github.com/ceres-solver/ceres-solver/issues/190. - Change-Id: If7219816f560270e59212813aeb021353a64a0e2 + Change-Id: If7ac5dbb856748f9900be93ec0452a40c0b00524 -commit 1f106904c1f47460c35ac03258d6506bb2d60838 -Author: Alex Stewart <alexs.mac@gmail.com> -Date: Sun Aug 9 14:55:02 2015 +0100 +commit 8ea86e1614cf77644ce782e43cde6565a54444f5 +Author: Nicolai Wojke <nwojke@uni-koblenz.de> +Date: Mon Apr 25 14:24:41 2016 +0200 - Update minimum iOS version to 7.0 for shared_ptr/unordered_map. - - - In order to correctly detect shared_ptr (& unordered_map) - the iOS version must be >= 7.0 (Xcode 5.0+). This only affects the - SIMULATOR(64) platform builds, as the OS (device) build uses the - latest SDK which is now likely 8.0+. + Fix: Copy minimizer option 'is_silent' to LinSearchDirection::Options - Change-Id: Iefec8f03408b8cdc7a495f442ebba081f800adb0 + Change-Id: I23b4c3383cad30033c539ac93883d77c8dd4ba1a -commit 16ecd40523a408e7705c9fdb0e159cef2007b8ab -Author: Alex Stewart <alexs.mac@gmail.com> -Date: Sat Aug 8 17:32:31 2015 +0100 +commit 080ca4c5f2ac42620971a07f06d2d13deb7befa8 +Author: Sameer Agarwal <sameeragarwal@google.com> +Date: Sun Apr 24 22:46:54 2016 -0700 - Fix bug in gflags' <= 2.1.2 exported CMake configuration. - - - gflags <= 2.1.2 has a bug in its exported gflags-config.cmake: - https://github.com/gflags/gflags/issues/110 whereby it sets - gflags_LIBRARIES to a non-existent 'gflags' target. - - This causes linker errors if gflags is installed in a non-standard - location (as otherwise CMake resolves gflags to -lgflags which - links if gflags is installed somewhere on the current path). - - We now check for this case, and search for the correct gflags imported - target and update gflags_LIBRARIES to reference it if found, otherwise - proceed on to the original manual search to try to find gflags. + Fix typos in users.rst - Change-Id: Iceccc3ee53c7c2010e41cc45255f966e7b13d526 + Change-Id: Ifdc67638a39403354bc9589f42a1b42cb9984dd2 -commit 56be8de007dfd65ed5a31c795eb4a08ad765f411 -Author: Alex Stewart <alexs.mac@gmail.com> -Date: Thu Jun 25 21:31:00 2015 +0100 +commit 21ab397dc55335c147fdd795899b1f8981037b09 +Author: Sameer Agarwal <sameeragarwal@google.com> +Date: Sun Apr 24 21:13:00 2016 -0700 - Add docs for new CXX11 option & mask option for Windows. - - - The CXX11 option has no effect on Windows, as there, any new C++11 - features are enabled by default, as such to avoid confusion we only - present the option for non-Windows. + Make some Jet comparisons exact. - Change-Id: I38925ae3bb8c16682d404468ba95c611a519b9b9 + Change-Id: Ia08c72f3b8779df96f5c0d5a954b2c0a1dd3a061 -commit cf863b6415ac4dbf3626e70adeac1ac0f3d87ee5 +commit ee40f954cf464087eb8943abf4d9db8917a33fbe Author: Sameer Agarwal <sameeragarwal@google.com> -Date: Thu Aug 6 14:52:18 2015 -0700 +Date: Sun Apr 24 07:49:55 2016 -0700 - Remove the spec file needed for generating RPMs. + Add colmap to users.rst - Now that ceres is part of RawHide, there is no need to carry - this spec file with the ceres distribution. - - Change-Id: Icc400b9874ba05ba05b353e2658f1de94c72299e + Change-Id: I452a8c1dc6a3bc55734b2fc3a4002ff7939ba863 -commit 560940fa277a469c1ab34f1aa303ff1af9c3cacf +commit 9665e099022bd06e53b0779550e9aebded7f274d Author: Sameer Agarwal <sameeragarwal@google.com> -Date: Sat Jul 11 22:21:31 2015 -0700 +Date: Mon Apr 18 06:00:58 2016 -0700 - A refactor of the cubic interpolation code + Fix step norm evaluation in LineSearchMinimizer - 1. Push the boundary handling logic into the underlying array - object. This has two very significant impacts: + TrustRegionMinimizer evaluates the size of the step + taken in the ambient space, where as the LineSearchMinimizer + was using the norm in the tangent space. This change fixes + this discrepancy. - a. The interpolation code becomes extremely simple to write - and to test. + Change-Id: I9fef64cbb5622c9769c0413003cfb1dc6e89cfa3 + +commit 620ca9d0668cd4a00402264fddca3cf6bd2e7265 +Author: Alex Stewart <alexs.mac@gmail.com> +Date: Mon Apr 18 15:14:11 2016 +0100 + + Remove use of -Werror when compiling Ceres. - b. The user has more flexibility in implementing how out of bounds - values are handled. We provide one default implementation. + - As noted in Issue #193 (in that case for GCC 6), Ceres' use of -Werror + when compiling on *nix can prevent compilation on new compilers that + add new warnings and there is an inevitable delay between new compiler + versions and Ceres versions. + - Removing the explicit use of -Werror, and relying on indirect + verification by maintainers should fix build issues for Ceres releases + on newer compilers. - Change-Id: Ic2f6cf9257ce7110c62e492688e5a6c8be1e7df2 + Change-Id: I38e9ade28d4a90e53dcd918a7d470f1a1debd7b4 -commit dfdf19e111c2b0e6daeb6007728ec2f784106d49 -Author: Sameer Agarwal <sameeragarwal@google.com> -Date: Wed Aug 5 15:20:57 2015 -0700 +commit 0c63bd3efbf1d41151c9fab41d4b77dc64c572c8 +Author: Mike Vitus <vitus@google.com> +Date: Thu Apr 14 10:25:52 2016 -0700 - Lint cleanup from Jim Roseborough + Add floor and ceil functions to the Jet implementation. - Change-Id: Id6845c85644d40e635ed196ca74fc51a387aade4 + Change-Id: I72ebfb0e9ade2964dbf3a014225ead345d5ae352 -commit 7444f23ae245476a7ac8421cc2f88d6947fd3e5f -Author: Sameer Agarwal <sameeragarwal@google.com> -Date: Mon Aug 3 12:22:44 2015 -0700 +commit 9843f3280356c158d23c06a16085c6c5ba35e053 +Author: Alex Stewart <alexs.mac@gmail.com> +Date: Mon Mar 7 21:24:32 2016 +0000 - Fix a typo in small_blas.h - - The reason this rather serious looking typo has not - caused any problems uptil now is because NUM_ROW_B is - computed but never actually used. + Report Ceres compile options as components in find_package(). - Thanks to Werner Trobin for pointing this out. + - Users can now specify particular components from Ceres, such as + SuiteSparse support) that must be present in a detected version of + Ceres in order for it to be reported as found by find_package(). + - This allows users to specify for example that they require a version + of Ceres with SuiteSparse support at configure time, rather than + finding out only at run time that Ceres was not compiled with the + options they require. + - The list of available components are built directly from the Ceres + compile options. + - The meta-module SparseLinearAlgebraLibrary is present if at least + one sparse linear algebra backend is available. - Change-Id: Id2b4d9326ec21baec8a85423e3270aefbafb611e + Change-Id: I65f1ddfd7697e6dd25bb4ac7e54f5097d3ca6266 -commit 5a48b92123b30a437f031eb24b0deaadc8f60d26 -Author: Alex Stewart <alexs.mac@gmail.com> -Date: Sat Jul 4 17:59:52 2015 +0100 +commit e4d4d88bbe51b9cc0f7450171511abbea0779790 +Author: Timer <linyicx@126.com> +Date: Fri Apr 8 15:42:18 2016 +0800 - Export Ceres build directory into local CMake package registry. - - - Optionally use CMake's export() functionality to export the Ceres - build directory as a package into the local CMake package registry. - - This enables the detection & use of Ceres from CMake *without* - requiring that Ceres be installed. + Fix a spelling error in nnls_modeling.rst - Change-Id: Ib5a7588446f490e1b405878475b6b1dd13accd1f + Change-Id: I341d901d3df993bc5397ed15e6cb330b0c38fd72 -commit d9790e77894ea99d38137d359d6118315b2d1601 -Author: Sameer Agarwal <sameeragarwal@google.com> -Date: Sun Jul 12 19:39:47 2015 -0700 +commit 5512f58536e1be0d92010d8325b606e7b4733a08 +Author: Keir Mierle <mierle@gmail.com> +Date: Thu Apr 7 12:03:16 2016 -0700 - Add ProductParameterization + Only use collapse() directive with OpenMP 3.0 or higher - Often a parameter block is the Cartesian product of a number of - manifolds. For example, a rigid transformation SE(3) = SO(3) x R^3 - In such cases, where you have the local parameterization - of the individual manifolds available, - ProductParameterization can be used to construct a local - parameterization of the cartesian product. + Change-Id: Icba544c0494763c57eb6dc61e98379312ca15972 + +commit d61e94da5225217cab7b4f93b72f97055094681f +Author: Thomas Schneider <schneith@ethz.ch> +Date: Wed Apr 6 10:40:29 2016 +0200 + + Add IsParameterBlockConstant to the ceres::Problem class. - Change-Id: I4b5bcbd2407a38739c7725b129789db5c3d65a20 + Change-Id: I7d0e828e81324443209c17fa54dd1d37605e5bfe -commit 7b4fb69dad49eaefb5d2d47ef0d76f48ad7fef73 +commit 77d94b34741574e958a417561702d6093fba87fb Author: Alex Stewart <alexs.mac@gmail.com> -Date: Sun Jun 28 21:43:46 2015 +0100 - - Cleanup FindGflags & use installed gflags CMake config if present. - - - Split out gflags namespace detection methods: - check_cxx_source_compiles() & regex, into separate functions. - - Use installed/exported gflags CMake configuration (present for - versions >= 2.1) if available, unless user expresses a preference not - to, or specifies search directories, in which case fall back to manual - search for components. - -- Prefer installed gflags CMake configurations over exported gflags - build directories on all OSs. - - Remove custom version of check_cxx_source_compiles() that attempted - to force the build type of the test project. This only worked for - NMake on Windows, not MSVC as msbuild ignored our attempts to force - the build type. Now we always use the regex method on Windows if - we cannot find an installed gflags CMake configuration which works - even on MSVC by bypassing msbuild. - - Add default search paths for gflags on Windows. - - Change-Id: I083b267d97a7a5838a1314f3d41a61ae48d5a2d7 - -commit b3063c047906d4a44503dc0187fdcbbfcdda5f38 -Author: Alex Stewart <alexs.mac@gmail.com> -Date: Wed Jul 15 20:56:56 2015 +0100 +Date: Sun Feb 14 16:54:03 2016 +0000 - Add default glog install location on Windows to search paths. + Fix install path for CeresConfig.cmake to be architecture-aware. + + - Previously we were auto-detecting a "64" suffix for the install path + for the Ceres library on non-Debian/Arch Linux distributions, but + we were installing CeresConfig.cmake to an architecture independent + location. + - We now install CeresConfig.cmake to lib${LIB_SUFFIX}/cmake/Ceres. + - Also make LIB_SUFFIX visible to the user in the CMake GUI s/t they can + easily override the auto-detected value if desired. + - Reported by jpgr87@gmail.com as Issue #194. - Change-Id: I083d368be48986e6780c11460f5a07b2f3b6c900 + Change-Id: If126260d7af685779487c01220ae178ac31f7aea diff --git a/extern/ceres/bundle.sh b/extern/ceres/bundle.sh index 0eaf00f3989..a4f703ac33d 100755 --- a/extern/ceres/bundle.sh +++ b/extern/ceres/bundle.sh @@ -173,26 +173,5 @@ if(WITH_OPENMP) ) endif() -TEST_UNORDERED_MAP_SUPPORT() -if(HAVE_STD_UNORDERED_MAP_HEADER) - if(HAVE_UNORDERED_MAP_IN_STD_NAMESPACE) - add_definitions(-DCERES_STD_UNORDERED_MAP) - else() - if(HAVE_UNORDERED_MAP_IN_TR1_NAMESPACE) - add_definitions(-DCERES_STD_UNORDERED_MAP_IN_TR1_NAMESPACE) - else() - add_definitions(-DCERES_NO_UNORDERED_MAP) - message(STATUS "Replacing unordered_map/set with map/set (warning: slower!)") - endif() - endif() -else() - if(HAVE_UNORDERED_MAP_IN_TR1_NAMESPACE) - add_definitions(-DCERES_TR1_UNORDERED_MAP) - else() - add_definitions(-DCERES_NO_UNORDERED_MAP) - message(STATUS "Replacing unordered_map/set with map/set (warning: slower!)") - endif() -endif() - blender_add_lib(extern_ceres "\${SRC}" "\${INC}" "\${INC_SYS}") EOF diff --git a/extern/ceres/files.txt b/extern/ceres/files.txt index f49f1fb0ded..4d973bbcdc2 100644 --- a/extern/ceres/files.txt +++ b/extern/ceres/files.txt @@ -149,6 +149,7 @@ internal/ceres/generated/schur_eliminator_4_4_d.cc internal/ceres/generated/schur_eliminator_d_d_d.cc internal/ceres/generate_eliminator_specialization.py internal/ceres/generate_partitioned_matrix_view_specializations.py +internal/ceres/gradient_checker.cc internal/ceres/gradient_checking_cost_function.cc internal/ceres/gradient_checking_cost_function.h internal/ceres/gradient_problem.cc @@ -160,6 +161,8 @@ internal/ceres/householder_vector.h internal/ceres/implicit_schur_complement.cc internal/ceres/implicit_schur_complement.h internal/ceres/integral_types.h +internal/ceres/is_close.cc +internal/ceres/is_close.h internal/ceres/iterative_schur_complement_solver.cc internal/ceres/iterative_schur_complement_solver.h internal/ceres/lapack.cc @@ -243,6 +246,8 @@ internal/ceres/trust_region_minimizer.cc internal/ceres/trust_region_minimizer.h internal/ceres/trust_region_preprocessor.cc internal/ceres/trust_region_preprocessor.h +internal/ceres/trust_region_step_evaluator.cc +internal/ceres/trust_region_step_evaluator.h internal/ceres/trust_region_strategy.cc internal/ceres/trust_region_strategy.h internal/ceres/types.cc diff --git a/extern/ceres/include/ceres/cost_function_to_functor.h b/extern/ceres/include/ceres/cost_function_to_functor.h index 6c67ac0f937..d2dc94725e4 100644 --- a/extern/ceres/include/ceres/cost_function_to_functor.h +++ b/extern/ceres/include/ceres/cost_function_to_functor.h @@ -130,7 +130,8 @@ class CostFunctionToFunctor { const int num_parameter_blocks = (N0 > 0) + (N1 > 0) + (N2 > 0) + (N3 > 0) + (N4 > 0) + (N5 > 0) + (N6 > 0) + (N7 > 0) + (N8 > 0) + (N9 > 0); - CHECK_EQ(parameter_block_sizes.size(), num_parameter_blocks); + CHECK_EQ(static_cast<int>(parameter_block_sizes.size()), + num_parameter_blocks); CHECK_EQ(N0, parameter_block_sizes[0]); if (parameter_block_sizes.size() > 1) CHECK_EQ(N1, parameter_block_sizes[1]); // NOLINT diff --git a/extern/ceres/include/ceres/covariance.h b/extern/ceres/include/ceres/covariance.h index dd20dc36ba1..930f96cf3ae 100644 --- a/extern/ceres/include/ceres/covariance.h +++ b/extern/ceres/include/ceres/covariance.h @@ -357,6 +357,28 @@ class CERES_EXPORT Covariance { const double*> >& covariance_blocks, Problem* problem); + // Compute a part of the covariance matrix. + // + // The vector parameter_blocks contains the parameter blocks that + // are used for computing the covariance matrix. From this vector + // all covariance pairs are generated. This allows the covariance + // estimation algorithm to only compute and store these blocks. + // + // parameter_blocks cannot contain duplicates. Bad things will + // happen if they do. + // + // Note that the list of covariance_blocks is only used to determine + // what parts of the covariance matrix are computed. The full + // Jacobian is used to do the computation, i.e. they do not have an + // impact on what part of the Jacobian is used for computation. + // + // The return value indicates the success or failure of the + // covariance computation. Please see the documentation for + // Covariance::Options for more on the conditions under which this + // function returns false. + bool Compute(const std::vector<const double*>& parameter_blocks, + Problem* problem); + // Return the block of the cross-covariance matrix corresponding to // parameter_block1 and parameter_block2. // @@ -394,6 +416,40 @@ class CERES_EXPORT Covariance { const double* parameter_block2, double* covariance_block) const; + // Return the covariance matrix corresponding to all parameter_blocks. + // + // Compute must be called before calling GetCovarianceMatrix and all + // parameter_blocks must have been present in the vector + // parameter_blocks when Compute was called. Otherwise + // GetCovarianceMatrix returns false. + // + // covariance_matrix must point to a memory location that can store + // the size of the covariance matrix. The covariance matrix will be + // a square matrix whose row and column count is equal to the sum of + // the sizes of the individual parameter blocks. The covariance + // matrix will be a row-major matrix. + bool GetCovarianceMatrix(const std::vector<const double *> ¶meter_blocks, + double *covariance_matrix); + + // Return the covariance matrix corresponding to parameter_blocks + // in the tangent space if a local parameterization is associated + // with one of the parameter blocks else returns the covariance + // matrix in the ambient space. + // + // Compute must be called before calling GetCovarianceMatrix and all + // parameter_blocks must have been present in the vector + // parameters_blocks when Compute was called. Otherwise + // GetCovarianceMatrix returns false. + // + // covariance_matrix must point to a memory location that can store + // the size of the covariance matrix. The covariance matrix will be + // a square matrix whose row and column count is equal to the sum of + // the sizes of the tangent spaces of the individual parameter + // blocks. The covariance matrix will be a row-major matrix. + bool GetCovarianceMatrixInTangentSpace( + const std::vector<const double*>& parameter_blocks, + double* covariance_matrix); + private: internal::scoped_ptr<internal::CovarianceImpl> impl_; }; diff --git a/extern/ceres/include/ceres/dynamic_numeric_diff_cost_function.h b/extern/ceres/include/ceres/dynamic_numeric_diff_cost_function.h index c852d57a3fc..5770946a115 100644 --- a/extern/ceres/include/ceres/dynamic_numeric_diff_cost_function.h +++ b/extern/ceres/include/ceres/dynamic_numeric_diff_cost_function.h @@ -85,22 +85,6 @@ class DynamicNumericDiffCostFunction : public CostFunction { options_(options) { } - // Deprecated. New users should avoid using this constructor. Instead, use the - // constructor with NumericDiffOptions. - DynamicNumericDiffCostFunction( - const CostFunctor* functor, - Ownership ownership, - double relative_step_size) - : functor_(functor), - ownership_(ownership), - options_() { - LOG(WARNING) << "This constructor is deprecated and will be removed in " - "a future version. Please use the NumericDiffOptions " - "constructor instead."; - - options_.relative_step_size = relative_step_size; - } - virtual ~DynamicNumericDiffCostFunction() { if (ownership_ != TAKE_OWNERSHIP) { functor_.release(); @@ -138,19 +122,19 @@ class DynamicNumericDiffCostFunction : public CostFunction { std::vector<double> parameters_copy(parameters_size); std::vector<double*> parameters_references_copy(block_sizes.size()); parameters_references_copy[0] = ¶meters_copy[0]; - for (int block = 1; block < block_sizes.size(); ++block) { + for (size_t block = 1; block < block_sizes.size(); ++block) { parameters_references_copy[block] = parameters_references_copy[block - 1] + block_sizes[block - 1]; } // Copy the parameters into the local temp space. - for (int block = 0; block < block_sizes.size(); ++block) { + for (size_t block = 0; block < block_sizes.size(); ++block) { memcpy(parameters_references_copy[block], parameters[block], block_sizes[block] * sizeof(*parameters[block])); } - for (int block = 0; block < block_sizes.size(); ++block) { + for (size_t block = 0; block < block_sizes.size(); ++block) { if (jacobians[block] != NULL && !NumericDiff<CostFunctor, method, DYNAMIC, DYNAMIC, DYNAMIC, DYNAMIC, DYNAMIC, DYNAMIC, diff --git a/extern/ceres/include/ceres/gradient_checker.h b/extern/ceres/include/ceres/gradient_checker.h index 28304159b44..6d285daf1d9 100644 --- a/extern/ceres/include/ceres/gradient_checker.h +++ b/extern/ceres/include/ceres/gradient_checker.h @@ -27,194 +27,121 @@ // POSSIBILITY OF SUCH DAMAGE. // Copyright 2007 Google Inc. All Rights Reserved. // -// Author: wjr@google.com (William Rucklidge) -// -// This file contains a class that exercises a cost function, to make sure -// that it is computing reasonable derivatives. It compares the Jacobians -// computed by the cost function with those obtained by finite -// differences. +// Authors: wjr@google.com (William Rucklidge), +// keir@google.com (Keir Mierle), +// dgossow@google.com (David Gossow) #ifndef CERES_PUBLIC_GRADIENT_CHECKER_H_ #define CERES_PUBLIC_GRADIENT_CHECKER_H_ -#include <cstddef> -#include <algorithm> #include <vector> +#include <string> +#include "ceres/cost_function.h" +#include "ceres/dynamic_numeric_diff_cost_function.h" #include "ceres/internal/eigen.h" #include "ceres/internal/fixed_array.h" #include "ceres/internal/macros.h" #include "ceres/internal/scoped_ptr.h" -#include "ceres/numeric_diff_cost_function.h" +#include "ceres/local_parameterization.h" #include "glog/logging.h" namespace ceres { -// An object that exercises a cost function, to compare the answers that it -// gives with derivatives estimated using finite differencing. +// GradientChecker compares the Jacobians returned by a cost function against +// derivatives estimated using finite differencing. // -// The only likely usage of this is for testing. +// The condition enforced is that // -// How to use: Fill in an array of pointers to parameter blocks for your -// CostFunction, and then call Probe(). Check that the return value is -// 'true'. See prober_test.cc for an example. +// (J_actual(i, j) - J_numeric(i, j)) +// ------------------------------------ < relative_precision +// max(J_actual(i, j), J_numeric(i, j)) +// +// where J_actual(i, j) is the jacobian as computed by the supplied cost +// function (by the user) multiplied by the local parameterization Jacobian +// and J_numeric is the jacobian as computed by finite differences, multiplied +// by the local parameterization Jacobian as well. // -// This is templated similarly to NumericDiffCostFunction, as it internally -// uses that. -template <typename CostFunctionToProbe, - int M = 0, int N0 = 0, int N1 = 0, int N2 = 0, int N3 = 0, int N4 = 0> +// How to use: Fill in an array of pointers to parameter blocks for your +// CostFunction, and then call Probe(). Check that the return value is 'true'. class GradientChecker { public: - // Here we stash some results from the probe, for later - // inspection. - struct GradientCheckResults { - // Computed cost. - Vector cost; - - // The sizes of these matrices are dictated by the cost function's - // parameter and residual block sizes. Each vector's length will - // term->parameter_block_sizes().size(), and each matrix is the - // Jacobian of the residual with respect to the corresponding parameter - // block. + // This will not take ownership of the cost function or local + // parameterizations. + // + // function: The cost function to probe. + // local_parameterization: A vector of local parameterizations for each + // parameter. May be NULL or contain NULL pointers to indicate that the + // respective parameter does not have a local parameterization. + // options: Options to use for numerical differentiation. + GradientChecker( + const CostFunction* function, + const std::vector<const LocalParameterization*>* local_parameterizations, + const NumericDiffOptions& options); + + // Contains results from a call to Probe for later inspection. + struct ProbeResults { + // The return value of the cost function. + bool return_value; + + // Computed residual vector. + Vector residuals; + + // The sizes of the Jacobians below are dictated by the cost function's + // parameter block size and residual block sizes. If a parameter block + // has a local parameterization associated with it, the size of the "local" + // Jacobian will be determined by the local parameterization dimension and + // residual block size, otherwise it will be identical to the regular + // Jacobian. // Derivatives as computed by the cost function. - std::vector<Matrix> term_jacobians; + std::vector<Matrix> jacobians; + + // Derivatives as computed by the cost function in local space. + std::vector<Matrix> local_jacobians; - // Derivatives as computed by finite differencing. - std::vector<Matrix> finite_difference_jacobians; + // Derivatives as computed by nuerical differentiation in local space. + std::vector<Matrix> numeric_jacobians; - // Infinity-norm of term_jacobians - finite_difference_jacobians. - double error_jacobians; + // Derivatives as computed by nuerical differentiation in local space. + std::vector<Matrix> local_numeric_jacobians; + + // Contains the maximum relative error found in the local Jacobians. + double maximum_relative_error; + + // If an error was detected, this will contain a detailed description of + // that error. + std::string error_log; }; - // Checks the Jacobian computed by a cost function. - // - // probe_point: The parameter values at which to probe. - // error_tolerance: A threshold for the infinity-norm difference - // between the Jacobians. If the Jacobians differ by more than - // this amount, then the probe fails. + // Call the cost function, compute alternative Jacobians using finite + // differencing and compare results. If local parameterizations are given, + // the Jacobians will be multiplied by the local parameterization Jacobians + // before performing the check, which effectively means that all errors along + // the null space of the local parameterization will be ignored. + // Returns false if the Jacobians don't match, the cost function return false, + // or if the cost function returns different residual when called with a + // Jacobian output argument vs. calling it without. Otherwise returns true. // - // term: The cost function to test. Not retained after this call returns. - // - // results: On return, the two Jacobians (and other information) - // will be stored here. May be NULL. + // parameters: The parameter values at which to probe. + // relative_precision: A threshold for the relative difference between the + // Jacobians. If the Jacobians differ by more than this amount, then the + // probe fails. + // results: On return, the Jacobians (and other information) will be stored + // here. May be NULL. // // Returns true if no problems are detected and the difference between the // Jacobians is less than error_tolerance. - static bool Probe(double const* const* probe_point, - double error_tolerance, - CostFunctionToProbe *term, - GradientCheckResults* results) { - CHECK_NOTNULL(probe_point); - CHECK_NOTNULL(term); - LOG(INFO) << "-------------------- Starting Probe() --------------------"; - - // We need a GradientCheckeresults, whether or not they supplied one. - internal::scoped_ptr<GradientCheckResults> owned_results; - if (results == NULL) { - owned_results.reset(new GradientCheckResults); - results = owned_results.get(); - } - - // Do a consistency check between the term and the template parameters. - CHECK_EQ(M, term->num_residuals()); - const int num_residuals = M; - const std::vector<int32>& block_sizes = term->parameter_block_sizes(); - const int num_blocks = block_sizes.size(); - - CHECK_LE(num_blocks, 5) << "Unable to test functions that take more " - << "than 5 parameter blocks"; - if (N0) { - CHECK_EQ(N0, block_sizes[0]); - CHECK_GE(num_blocks, 1); - } else { - CHECK_LT(num_blocks, 1); - } - if (N1) { - CHECK_EQ(N1, block_sizes[1]); - CHECK_GE(num_blocks, 2); - } else { - CHECK_LT(num_blocks, 2); - } - if (N2) { - CHECK_EQ(N2, block_sizes[2]); - CHECK_GE(num_blocks, 3); - } else { - CHECK_LT(num_blocks, 3); - } - if (N3) { - CHECK_EQ(N3, block_sizes[3]); - CHECK_GE(num_blocks, 4); - } else { - CHECK_LT(num_blocks, 4); - } - if (N4) { - CHECK_EQ(N4, block_sizes[4]); - CHECK_GE(num_blocks, 5); - } else { - CHECK_LT(num_blocks, 5); - } - - results->term_jacobians.clear(); - results->term_jacobians.resize(num_blocks); - results->finite_difference_jacobians.clear(); - results->finite_difference_jacobians.resize(num_blocks); - - internal::FixedArray<double*> term_jacobian_pointers(num_blocks); - internal::FixedArray<double*> - finite_difference_jacobian_pointers(num_blocks); - for (int i = 0; i < num_blocks; i++) { - results->term_jacobians[i].resize(num_residuals, block_sizes[i]); - term_jacobian_pointers[i] = results->term_jacobians[i].data(); - results->finite_difference_jacobians[i].resize( - num_residuals, block_sizes[i]); - finite_difference_jacobian_pointers[i] = - results->finite_difference_jacobians[i].data(); - } - results->cost.resize(num_residuals, 1); - - CHECK(term->Evaluate(probe_point, results->cost.data(), - term_jacobian_pointers.get())); - NumericDiffCostFunction<CostFunctionToProbe, CENTRAL, M, N0, N1, N2, N3, N4> - numeric_term(term, DO_NOT_TAKE_OWNERSHIP); - CHECK(numeric_term.Evaluate(probe_point, results->cost.data(), - finite_difference_jacobian_pointers.get())); - - results->error_jacobians = 0; - for (int i = 0; i < num_blocks; i++) { - Matrix jacobian_difference = results->term_jacobians[i] - - results->finite_difference_jacobians[i]; - results->error_jacobians = - std::max(results->error_jacobians, - jacobian_difference.lpNorm<Eigen::Infinity>()); - } - - LOG(INFO) << "========== term-computed derivatives =========="; - for (int i = 0; i < num_blocks; i++) { - LOG(INFO) << "term_computed block " << i; - LOG(INFO) << "\n" << results->term_jacobians[i]; - } - - LOG(INFO) << "========== finite-difference derivatives =========="; - for (int i = 0; i < num_blocks; i++) { - LOG(INFO) << "finite_difference block " << i; - LOG(INFO) << "\n" << results->finite_difference_jacobians[i]; - } - - LOG(INFO) << "========== difference =========="; - for (int i = 0; i < num_blocks; i++) { - LOG(INFO) << "difference block " << i; - LOG(INFO) << (results->term_jacobians[i] - - results->finite_difference_jacobians[i]); - } - - LOG(INFO) << "||difference|| = " << results->error_jacobians; - - return results->error_jacobians < error_tolerance; - } + bool Probe(double const* const* parameters, + double relative_precision, + ProbeResults* results) const; private: CERES_DISALLOW_IMPLICIT_CONSTRUCTORS(GradientChecker); + + std::vector<const LocalParameterization*> local_parameterizations_; + const CostFunction* function_; + internal::scoped_ptr<CostFunction> finite_diff_cost_function_; }; } // namespace ceres diff --git a/extern/ceres/include/ceres/internal/port.h b/extern/ceres/include/ceres/internal/port.h index e57049dde4b..f4dcaee7bd8 100644 --- a/extern/ceres/include/ceres/internal/port.h +++ b/extern/ceres/include/ceres/internal/port.h @@ -33,9 +33,8 @@ // This file needs to compile as c code. #ifdef __cplusplus - +#include <cstddef> #include "ceres/internal/config.h" - #if defined(CERES_TR1_MEMORY_HEADER) #include <tr1/memory> #else @@ -50,6 +49,25 @@ using std::tr1::shared_ptr; using std::shared_ptr; #endif +// We allocate some Eigen objects on the stack and other places they +// might not be aligned to 16-byte boundaries. If we have C++11, we +// can specify their alignment anyway, and thus can safely enable +// vectorization on those matrices; in C++99, we are out of luck. Figure out +// what case we're in and write macros that do the right thing. +#ifdef CERES_USE_CXX11 +namespace port_constants { +static constexpr size_t kMaxAlignBytes = + // Work around a GCC 4.8 bug + // (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56019) where + // std::max_align_t is misplaced. +#if defined (__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ == 8 + alignof(::max_align_t); +#else + alignof(std::max_align_t); +#endif +} // namespace port_constants +#endif + } // namespace ceres #endif // __cplusplus diff --git a/extern/ceres/include/ceres/iteration_callback.h b/extern/ceres/include/ceres/iteration_callback.h index 6bab00439c5..db5d0efe53a 100644 --- a/extern/ceres/include/ceres/iteration_callback.h +++ b/extern/ceres/include/ceres/iteration_callback.h @@ -69,7 +69,7 @@ struct CERES_EXPORT IterationSummary { // Step was numerically valid, i.e., all values are finite and the // step reduces the value of the linearized model. // - // Note: step_is_valid is false when iteration = 0. + // Note: step_is_valid is always true when iteration = 0. bool step_is_valid; // Step did not reduce the value of the objective function @@ -77,7 +77,7 @@ struct CERES_EXPORT IterationSummary { // acceptance criterion used by the non-monotonic trust region // algorithm. // - // Note: step_is_nonmonotonic is false when iteration = 0; + // Note: step_is_nonmonotonic is always false when iteration = 0; bool step_is_nonmonotonic; // Whether or not the minimizer accepted this step or not. If the @@ -89,7 +89,7 @@ struct CERES_EXPORT IterationSummary { // relative decrease is not sufficient, the algorithm may accept the // step and the step is declared successful. // - // Note: step_is_successful is false when iteration = 0. + // Note: step_is_successful is always true when iteration = 0. bool step_is_successful; // Value of the objective function. diff --git a/extern/ceres/include/ceres/jet.h b/extern/ceres/include/ceres/jet.h index a21fd7adb90..a104707298c 100644 --- a/extern/ceres/include/ceres/jet.h +++ b/extern/ceres/include/ceres/jet.h @@ -164,6 +164,7 @@ #include "Eigen/Core" #include "ceres/fpclassify.h" +#include "ceres/internal/port.h" namespace ceres { @@ -227,21 +228,23 @@ struct Jet { T a; // The infinitesimal part. - // - // Note the Eigen::DontAlign bit is needed here because this object - // gets allocated on the stack and as part of other arrays and - // structs. Forcing the right alignment there is the source of much - // pain and suffering. Even if that works, passing Jets around to - // functions by value has problems because the C++ ABI does not - // guarantee alignment for function arguments. - // - // Setting the DontAlign bit prevents Eigen from using SSE for the - // various operations on Jets. This is a small performance penalty - // since the AutoDiff code will still expose much of the code as - // statically sized loops to the compiler. But given the subtle - // issues that arise due to alignment, especially when dealing with - // multiple platforms, it seems to be a trade off worth making. + + // We allocate Jets on the stack and other places they + // might not be aligned to 16-byte boundaries. If we have C++11, we + // can specify their alignment anyway, and thus can safely enable + // vectorization on those matrices; in C++99, we are out of luck. Figure out + // what case we're in and do the right thing. +#ifndef CERES_USE_CXX11 + // fall back to safe version: Eigen::Matrix<T, N, 1, Eigen::DontAlign> v; +#else + static constexpr bool kShouldAlignMatrix = + 16 <= ::ceres::port_constants::kMaxAlignBytes; + static constexpr int kAlignHint = kShouldAlignMatrix ? + Eigen::AutoAlign : Eigen::DontAlign; + static constexpr size_t kAlignment = kShouldAlignMatrix ? 16 : 1; + alignas(kAlignment) Eigen::Matrix<T, N, 1, kAlignHint> v; +#endif }; // Unary + @@ -388,6 +391,8 @@ inline double atan (double x) { return std::atan(x); } inline double sinh (double x) { return std::sinh(x); } inline double cosh (double x) { return std::cosh(x); } inline double tanh (double x) { return std::tanh(x); } +inline double floor (double x) { return std::floor(x); } +inline double ceil (double x) { return std::ceil(x); } inline double pow (double x, double y) { return std::pow(x, y); } inline double atan2(double y, double x) { return std::atan2(y, x); } @@ -482,10 +487,51 @@ Jet<T, N> tanh(const Jet<T, N>& f) { return Jet<T, N>(tanh_a, tmp * f.v); } +// The floor function should be used with extreme care as this operation will +// result in a zero derivative which provides no information to the solver. +// +// floor(a + h) ~= floor(a) + 0 +template <typename T, int N> inline +Jet<T, N> floor(const Jet<T, N>& f) { + return Jet<T, N>(floor(f.a)); +} + +// The ceil function should be used with extreme care as this operation will +// result in a zero derivative which provides no information to the solver. +// +// ceil(a + h) ~= ceil(a) + 0 +template <typename T, int N> inline +Jet<T, N> ceil(const Jet<T, N>& f) { + return Jet<T, N>(ceil(f.a)); +} + // Bessel functions of the first kind with integer order equal to 0, 1, n. -inline double BesselJ0(double x) { return j0(x); } -inline double BesselJ1(double x) { return j1(x); } -inline double BesselJn(int n, double x) { return jn(n, x); } +// +// Microsoft has deprecated the j[0,1,n]() POSIX Bessel functions in favour of +// _j[0,1,n](). Where available on MSVC, use _j[0,1,n]() to avoid deprecated +// function errors in client code (the specific warning is suppressed when +// Ceres itself is built). +inline double BesselJ0(double x) { +#if defined(_MSC_VER) && defined(_j0) + return _j0(x); +#else + return j0(x); +#endif +} +inline double BesselJ1(double x) { +#if defined(_MSC_VER) && defined(_j1) + return _j1(x); +#else + return j1(x); +#endif +} +inline double BesselJn(int n, double x) { +#if defined(_MSC_VER) && defined(_jn) + return _jn(n, x); +#else + return jn(n, x); +#endif +} // For the formulae of the derivatives of the Bessel functions see the book: // Olver, Lozier, Boisvert, Clark, NIST Handbook of Mathematical Functions, @@ -743,7 +789,15 @@ template<typename T, int N> inline Jet<T, N> ei_pow (const Jet<T, N>& x, // strange compile errors. template <typename T, int N> inline std::ostream &operator<<(std::ostream &s, const Jet<T, N>& z) { - return s << "[" << z.a << " ; " << z.v.transpose() << "]"; + s << "[" << z.a << " ; "; + for (int i = 0; i < N; ++i) { + s << z.v[i]; + if (i != N - 1) { + s << ", "; + } + } + s << "]"; + return s; } } // namespace ceres @@ -757,6 +811,7 @@ struct NumTraits<ceres::Jet<T, N> > { typedef ceres::Jet<T, N> Real; typedef ceres::Jet<T, N> NonInteger; typedef ceres::Jet<T, N> Nested; + typedef ceres::Jet<T, N> Literal; static typename ceres::Jet<T, N> dummy_precision() { return ceres::Jet<T, N>(1e-12); @@ -777,6 +832,21 @@ struct NumTraits<ceres::Jet<T, N> > { HasFloatingPoint = 1, RequireInitialization = 1 }; + + template<bool Vectorized> + struct Div { + enum { +#if defined(EIGEN_VECTORIZE_AVX) + AVX = true, +#else + AVX = false, +#endif + + // Assuming that for Jets, division is as expensive as + // multiplication. + Cost = 3 + }; + }; }; } // namespace Eigen diff --git a/extern/ceres/include/ceres/local_parameterization.h b/extern/ceres/include/ceres/local_parameterization.h index 67633de309f..379fc684921 100644 --- a/extern/ceres/include/ceres/local_parameterization.h +++ b/extern/ceres/include/ceres/local_parameterization.h @@ -211,6 +211,28 @@ class CERES_EXPORT QuaternionParameterization : public LocalParameterization { virtual int LocalSize() const { return 3; } }; +// Implements the quaternion local parameterization for Eigen's representation +// of the quaternion. Eigen uses a different internal memory layout for the +// elements of the quaternion than what is commonly used. Specifically, Eigen +// stores the elements in memory as [x, y, z, w] where the real part is last +// whereas it is typically stored first. Note, when creating an Eigen quaternion +// through the constructor the elements are accepted in w, x, y, z order. Since +// Ceres operates on parameter blocks which are raw double pointers this +// difference is important and requires a different parameterization. +// +// Plus(x, delta) = [sin(|delta|) delta / |delta|, cos(|delta|)] * x +// with * being the quaternion multiplication operator. +class EigenQuaternionParameterization : public ceres::LocalParameterization { + public: + virtual ~EigenQuaternionParameterization() {} + virtual bool Plus(const double* x, + const double* delta, + double* x_plus_delta) const; + virtual bool ComputeJacobian(const double* x, + double* jacobian) const; + virtual int GlobalSize() const { return 4; } + virtual int LocalSize() const { return 3; } +}; // This provides a parameterization for homogeneous vectors which are commonly // used in Structure for Motion problems. One example where they are used is diff --git a/extern/ceres/include/ceres/numeric_diff_cost_function.h b/extern/ceres/include/ceres/numeric_diff_cost_function.h index fa96078df02..5dfaeab6241 100644 --- a/extern/ceres/include/ceres/numeric_diff_cost_function.h +++ b/extern/ceres/include/ceres/numeric_diff_cost_function.h @@ -206,29 +206,6 @@ class NumericDiffCostFunction } } - // Deprecated. New users should avoid using this constructor. Instead, use the - // constructor with NumericDiffOptions. - NumericDiffCostFunction(CostFunctor* functor, - Ownership ownership, - int num_residuals, - const double relative_step_size) - :functor_(functor), - ownership_(ownership), - options_() { - LOG(WARNING) << "This constructor is deprecated and will be removed in " - "a future version. Please use the NumericDiffOptions " - "constructor instead."; - - if (kNumResiduals == DYNAMIC) { - SizedCostFunction<kNumResiduals, - N0, N1, N2, N3, N4, - N5, N6, N7, N8, N9> - ::set_num_residuals(num_residuals); - } - - options_.relative_step_size = relative_step_size; - } - ~NumericDiffCostFunction() { if (ownership_ != TAKE_OWNERSHIP) { functor_.release(); diff --git a/extern/ceres/include/ceres/problem.h b/extern/ceres/include/ceres/problem.h index 409274c62c2..27ed4ef15da 100644 --- a/extern/ceres/include/ceres/problem.h +++ b/extern/ceres/include/ceres/problem.h @@ -309,6 +309,9 @@ class CERES_EXPORT Problem { // Allow the indicated parameter block to vary during optimization. void SetParameterBlockVariable(double* values); + // Returns true if a parameter block is set constant, and false otherwise. + bool IsParameterBlockConstant(double* values) const; + // Set the local parameterization for one of the parameter blocks. // The local_parameterization is owned by the Problem by default. It // is acceptable to set the same parameterization for multiple @@ -461,6 +464,10 @@ class CERES_EXPORT Problem { // parameter block has a local parameterization, then it contributes // "LocalSize" entries to the gradient vector (and the number of // columns in the jacobian). + // + // Note 3: This function cannot be called while the problem is being + // solved, for example it cannot be called from an IterationCallback + // at the end of an iteration during a solve. bool Evaluate(const EvaluateOptions& options, double* cost, std::vector<double>* residuals, diff --git a/extern/ceres/include/ceres/rotation.h b/extern/ceres/include/ceres/rotation.h index e9496d772e4..b6a06f772c4 100644 --- a/extern/ceres/include/ceres/rotation.h +++ b/extern/ceres/include/ceres/rotation.h @@ -48,7 +48,6 @@ #include <algorithm> #include <cmath> #include <limits> -#include "glog/logging.h" namespace ceres { @@ -418,7 +417,6 @@ template <typename T> inline void EulerAnglesToRotationMatrix(const T* euler, const int row_stride_parameter, T* R) { - CHECK_EQ(row_stride_parameter, 3); EulerAnglesToRotationMatrix(euler, RowMajorAdapter3x3(R)); } @@ -496,7 +494,6 @@ void QuaternionToRotation(const T q[4], QuaternionToScaledRotation(q, R); T normalizer = q[0]*q[0] + q[1]*q[1] + q[2]*q[2] + q[3]*q[3]; - CHECK_NE(normalizer, T(0)); normalizer = T(1) / normalizer; for (int i = 0; i < 3; ++i) { diff --git a/extern/ceres/include/ceres/solver.h b/extern/ceres/include/ceres/solver.h index 318cf48cb83..0d77d242dfe 100644 --- a/extern/ceres/include/ceres/solver.h +++ b/extern/ceres/include/ceres/solver.h @@ -134,7 +134,7 @@ class CERES_EXPORT Solver { trust_region_problem_dump_format_type = TEXTFILE; check_gradients = false; gradient_check_relative_precision = 1e-8; - numeric_derivative_relative_step_size = 1e-6; + gradient_check_numeric_derivative_relative_step_size = 1e-6; update_state_every_iteration = false; } @@ -701,12 +701,22 @@ class CERES_EXPORT Solver { // this number, then the jacobian for that cost term is dumped. double gradient_check_relative_precision; - // Relative shift used for taking numeric derivatives. For finite - // differencing, each dimension is evaluated at slightly shifted - // values; for the case of central difference, this is what gets - // evaluated: + // WARNING: This option only applies to the to the numeric + // differentiation used for checking the user provided derivatives + // when when Solver::Options::check_gradients is true. If you are + // using NumericDiffCostFunction and are interested in changing + // the step size for numeric differentiation in your cost + // function, please have a look at + // include/ceres/numeric_diff_options.h. // - // delta = numeric_derivative_relative_step_size; + // Relative shift used for taking numeric derivatives when + // Solver::Options::check_gradients is true. + // + // For finite differencing, each dimension is evaluated at + // slightly shifted values; for the case of central difference, + // this is what gets evaluated: + // + // delta = gradient_check_numeric_derivative_relative_step_size; // f_initial = f(x) // f_forward = f((1 + delta) * x) // f_backward = f((1 - delta) * x) @@ -723,7 +733,7 @@ class CERES_EXPORT Solver { // theory a good choice is sqrt(eps) * x, which for doubles means // about 1e-8 * x. However, I have found this number too // optimistic. This number should be exposed for users to change. - double numeric_derivative_relative_step_size; + double gradient_check_numeric_derivative_relative_step_size; // If true, the user's parameter blocks are updated at the end of // every Minimizer iteration, otherwise they are updated when the @@ -801,6 +811,13 @@ class CERES_EXPORT Solver { // Number of times inner iterations were performed. int num_inner_iteration_steps; + // Total number of iterations inside the line search algorithm + // across all invocations. We call these iterations "steps" to + // distinguish them from the outer iterations of the line search + // and trust region minimizer algorithms which call the line + // search algorithm as a subroutine. + int num_line_search_steps; + // All times reported below are wall times. // When the user calls Solve, before the actual optimization diff --git a/extern/ceres/include/ceres/version.h b/extern/ceres/include/ceres/version.h index 66505a515c9..2f1cc297a38 100644 --- a/extern/ceres/include/ceres/version.h +++ b/extern/ceres/include/ceres/version.h @@ -32,7 +32,7 @@ #define CERES_PUBLIC_VERSION_H_ #define CERES_VERSION_MAJOR 1 -#define CERES_VERSION_MINOR 11 +#define CERES_VERSION_MINOR 12 #define CERES_VERSION_REVISION 0 // Classic CPP stringifcation; the extra level of indirection allows the diff --git a/extern/ceres/internal/ceres/compressed_row_jacobian_writer.cc b/extern/ceres/internal/ceres/compressed_row_jacobian_writer.cc index 64b6ac00447..40977b74c67 100644 --- a/extern/ceres/internal/ceres/compressed_row_jacobian_writer.cc +++ b/extern/ceres/internal/ceres/compressed_row_jacobian_writer.cc @@ -46,6 +46,7 @@ namespace internal { using std::make_pair; using std::pair; using std::vector; +using std::adjacent_find; void CompressedRowJacobianWriter::PopulateJacobianRowAndColumnBlockVectors( const Program* program, CompressedRowSparseMatrix* jacobian) { @@ -140,12 +141,21 @@ SparseMatrix* CompressedRowJacobianWriter::CreateJacobian() const { // Sort the parameters by their position in the state vector. sort(parameter_indices.begin(), parameter_indices.end()); - CHECK(unique(parameter_indices.begin(), parameter_indices.end()) == - parameter_indices.end()) - << "Ceres internal error: " - << "Duplicate parameter blocks detected in a cost function. " - << "This should never happen. Please report this to " - << "the Ceres developers."; + if (adjacent_find(parameter_indices.begin(), parameter_indices.end()) != + parameter_indices.end()) { + std::string parameter_block_description; + for (int j = 0; j < num_parameter_blocks; ++j) { + ParameterBlock* parameter_block = residual_block->parameter_blocks()[j]; + parameter_block_description += + parameter_block->ToString() + "\n"; + } + LOG(FATAL) << "Ceres internal error: " + << "Duplicate parameter blocks detected in a cost function. " + << "This should never happen. Please report this to " + << "the Ceres developers.\n" + << "Residual Block: " << residual_block->ToString() << "\n" + << "Parameter Blocks: " << parameter_block_description; + } // Update the row indices. const int num_residuals = residual_block->NumResiduals(); diff --git a/extern/ceres/internal/ceres/covariance.cc b/extern/ceres/internal/ceres/covariance.cc index 690847945a9..cb280a36847 100644 --- a/extern/ceres/internal/ceres/covariance.cc +++ b/extern/ceres/internal/ceres/covariance.cc @@ -38,6 +38,7 @@ namespace ceres { +using std::make_pair; using std::pair; using std::vector; @@ -54,6 +55,12 @@ bool Covariance::Compute( return impl_->Compute(covariance_blocks, problem->problem_impl_.get()); } +bool Covariance::Compute( + const vector<const double*>& parameter_blocks, + Problem* problem) { + return impl_->Compute(parameter_blocks, problem->problem_impl_.get()); +} + bool Covariance::GetCovarianceBlock(const double* parameter_block1, const double* parameter_block2, double* covariance_block) const { @@ -73,4 +80,20 @@ bool Covariance::GetCovarianceBlockInTangentSpace( covariance_block); } +bool Covariance::GetCovarianceMatrix( + const vector<const double*>& parameter_blocks, + double* covariance_matrix) { + return impl_->GetCovarianceMatrixInTangentOrAmbientSpace(parameter_blocks, + true, // ambient + covariance_matrix); +} + +bool Covariance::GetCovarianceMatrixInTangentSpace( + const std::vector<const double *>& parameter_blocks, + double *covariance_matrix) { + return impl_->GetCovarianceMatrixInTangentOrAmbientSpace(parameter_blocks, + false, // tangent + covariance_matrix); +} + } // namespace ceres diff --git a/extern/ceres/internal/ceres/covariance_impl.cc b/extern/ceres/internal/ceres/covariance_impl.cc index 3e8302bed55..d698f88fa9b 100644 --- a/extern/ceres/internal/ceres/covariance_impl.cc +++ b/extern/ceres/internal/ceres/covariance_impl.cc @@ -36,6 +36,8 @@ #include <algorithm> #include <cstdlib> +#include <numeric> +#include <sstream> #include <utility> #include <vector> @@ -43,6 +45,7 @@ #include "Eigen/SparseQR" #include "Eigen/SVD" +#include "ceres/collections_port.h" #include "ceres/compressed_col_sparse_matrix_utils.h" #include "ceres/compressed_row_sparse_matrix.h" #include "ceres/covariance.h" @@ -51,6 +54,7 @@ #include "ceres/map_util.h" #include "ceres/parameter_block.h" #include "ceres/problem_impl.h" +#include "ceres/residual_block.h" #include "ceres/suitesparse.h" #include "ceres/wall_time.h" #include "glog/logging.h" @@ -61,6 +65,7 @@ namespace internal { using std::make_pair; using std::map; using std::pair; +using std::sort; using std::swap; using std::vector; @@ -86,8 +91,38 @@ CovarianceImpl::CovarianceImpl(const Covariance::Options& options) CovarianceImpl::~CovarianceImpl() { } +template <typename T> void CheckForDuplicates(vector<T> blocks) { + sort(blocks.begin(), blocks.end()); + typename vector<T>::iterator it = + std::adjacent_find(blocks.begin(), blocks.end()); + if (it != blocks.end()) { + // In case there are duplicates, we search for their location. + map<T, vector<int> > blocks_map; + for (int i = 0; i < blocks.size(); ++i) { + blocks_map[blocks[i]].push_back(i); + } + + std::ostringstream duplicates; + while (it != blocks.end()) { + duplicates << "("; + for (int i = 0; i < blocks_map[*it].size() - 1; ++i) { + duplicates << blocks_map[*it][i] << ", "; + } + duplicates << blocks_map[*it].back() << ")"; + it = std::adjacent_find(it + 1, blocks.end()); + if (it < blocks.end()) { + duplicates << " and "; + } + } + + LOG(FATAL) << "Covariance::Compute called with duplicate blocks at " + << "indices " << duplicates.str(); + } +} + bool CovarianceImpl::Compute(const CovarianceBlocks& covariance_blocks, ProblemImpl* problem) { + CheckForDuplicates<pair<const double*, const double*> >(covariance_blocks); problem_ = problem; parameter_block_to_row_index_.clear(); covariance_matrix_.reset(NULL); @@ -97,6 +132,20 @@ bool CovarianceImpl::Compute(const CovarianceBlocks& covariance_blocks, return is_valid_; } +bool CovarianceImpl::Compute(const vector<const double*>& parameter_blocks, + ProblemImpl* problem) { + CheckForDuplicates<const double*>(parameter_blocks); + CovarianceBlocks covariance_blocks; + for (int i = 0; i < parameter_blocks.size(); ++i) { + for (int j = i; j < parameter_blocks.size(); ++j) { + covariance_blocks.push_back(make_pair(parameter_blocks[i], + parameter_blocks[j])); + } + } + + return Compute(covariance_blocks, problem); +} + bool CovarianceImpl::GetCovarianceBlockInTangentOrAmbientSpace( const double* original_parameter_block1, const double* original_parameter_block2, @@ -120,9 +169,17 @@ bool CovarianceImpl::GetCovarianceBlockInTangentOrAmbientSpace( ParameterBlock* block2 = FindOrDie(parameter_map, const_cast<double*>(original_parameter_block2)); + const int block1_size = block1->Size(); const int block2_size = block2->Size(); - MatrixRef(covariance_block, block1_size, block2_size).setZero(); + const int block1_local_size = block1->LocalSize(); + const int block2_local_size = block2->LocalSize(); + if (!lift_covariance_to_ambient_space) { + MatrixRef(covariance_block, block1_local_size, block2_local_size) + .setZero(); + } else { + MatrixRef(covariance_block, block1_size, block2_size).setZero(); + } return true; } @@ -240,6 +297,94 @@ bool CovarianceImpl::GetCovarianceBlockInTangentOrAmbientSpace( return true; } +bool CovarianceImpl::GetCovarianceMatrixInTangentOrAmbientSpace( + const vector<const double*>& parameters, + bool lift_covariance_to_ambient_space, + double* covariance_matrix) const { + CHECK(is_computed_) + << "Covariance::GetCovarianceMatrix called before Covariance::Compute"; + CHECK(is_valid_) + << "Covariance::GetCovarianceMatrix called when Covariance::Compute " + << "returned false."; + + const ProblemImpl::ParameterMap& parameter_map = problem_->parameter_map(); + // For OpenMP compatibility we need to define these vectors in advance + const int num_parameters = parameters.size(); + vector<int> parameter_sizes; + vector<int> cum_parameter_size; + parameter_sizes.reserve(num_parameters); + cum_parameter_size.resize(num_parameters + 1); + cum_parameter_size[0] = 0; + for (int i = 0; i < num_parameters; ++i) { + ParameterBlock* block = + FindOrDie(parameter_map, const_cast<double*>(parameters[i])); + if (lift_covariance_to_ambient_space) { + parameter_sizes.push_back(block->Size()); + } else { + parameter_sizes.push_back(block->LocalSize()); + } + } + std::partial_sum(parameter_sizes.begin(), parameter_sizes.end(), + cum_parameter_size.begin() + 1); + const int max_covariance_block_size = + *std::max_element(parameter_sizes.begin(), parameter_sizes.end()); + const int covariance_size = cum_parameter_size.back(); + + // Assemble the blocks in the covariance matrix. + MatrixRef covariance(covariance_matrix, covariance_size, covariance_size); + const int num_threads = options_.num_threads; + scoped_array<double> workspace( + new double[num_threads * max_covariance_block_size * + max_covariance_block_size]); + + bool success = true; + +// The collapse() directive is only supported in OpenMP 3.0 and higher. OpenMP +// 3.0 was released in May 2008 (hence the version number). +#if _OPENMP >= 200805 +# pragma omp parallel for num_threads(num_threads) schedule(dynamic) collapse(2) +#else +# pragma omp parallel for num_threads(num_threads) schedule(dynamic) +#endif + for (int i = 0; i < num_parameters; ++i) { + for (int j = 0; j < num_parameters; ++j) { + // The second loop can't start from j = i for compatibility with OpenMP + // collapse command. The conditional serves as a workaround + if (j >= i) { + int covariance_row_idx = cum_parameter_size[i]; + int covariance_col_idx = cum_parameter_size[j]; + int size_i = parameter_sizes[i]; + int size_j = parameter_sizes[j]; +#ifdef CERES_USE_OPENMP + int thread_id = omp_get_thread_num(); +#else + int thread_id = 0; +#endif + double* covariance_block = + workspace.get() + + thread_id * max_covariance_block_size * max_covariance_block_size; + if (!GetCovarianceBlockInTangentOrAmbientSpace( + parameters[i], parameters[j], lift_covariance_to_ambient_space, + covariance_block)) { + success = false; + } + + covariance.block(covariance_row_idx, covariance_col_idx, + size_i, size_j) = + MatrixRef(covariance_block, size_i, size_j); + + if (i != j) { + covariance.block(covariance_col_idx, covariance_row_idx, + size_j, size_i) = + MatrixRef(covariance_block, size_i, size_j).transpose(); + + } + } + } + } + return success; +} + // Determine the sparsity pattern of the covariance matrix based on // the block pairs requested by the user. bool CovarianceImpl::ComputeCovarianceSparsity( @@ -252,18 +397,28 @@ bool CovarianceImpl::ComputeCovarianceSparsity( vector<double*> all_parameter_blocks; problem->GetParameterBlocks(&all_parameter_blocks); const ProblemImpl::ParameterMap& parameter_map = problem->parameter_map(); + HashSet<ParameterBlock*> parameter_blocks_in_use; + vector<ResidualBlock*> residual_blocks; + problem->GetResidualBlocks(&residual_blocks); + + for (int i = 0; i < residual_blocks.size(); ++i) { + ResidualBlock* residual_block = residual_blocks[i]; + parameter_blocks_in_use.insert(residual_block->parameter_blocks(), + residual_block->parameter_blocks() + + residual_block->NumParameterBlocks()); + } + constant_parameter_blocks_.clear(); vector<double*>& active_parameter_blocks = evaluate_options_.parameter_blocks; active_parameter_blocks.clear(); for (int i = 0; i < all_parameter_blocks.size(); ++i) { double* parameter_block = all_parameter_blocks[i]; - ParameterBlock* block = FindOrDie(parameter_map, parameter_block); - if (block->IsConstant()) { - constant_parameter_blocks_.insert(parameter_block); - } else { + if (!block->IsConstant() && (parameter_blocks_in_use.count(block) > 0)) { active_parameter_blocks.push_back(parameter_block); + } else { + constant_parameter_blocks_.insert(parameter_block); } } @@ -386,8 +541,8 @@ bool CovarianceImpl::ComputeCovarianceValues() { switch (options_.algorithm_type) { case DENSE_SVD: return ComputeCovarianceValuesUsingDenseSVD(); -#ifndef CERES_NO_SUITESPARSE case SUITE_SPARSE_QR: +#ifndef CERES_NO_SUITESPARSE return ComputeCovarianceValuesUsingSuiteSparseQR(); #else LOG(ERROR) << "SuiteSparse is required to use the " @@ -624,7 +779,10 @@ bool CovarianceImpl::ComputeCovarianceValuesUsingDenseSVD() { if (automatic_truncation) { break; } else { - LOG(ERROR) << "Cholesky factorization of J'J is not reliable. " + LOG(ERROR) << "Error: Covariance matrix is near rank deficient " + << "and the user did not specify a non-zero" + << "Covariance::Options::null_space_rank " + << "to enable the computation of a Pseudo-Inverse. " << "Reciprocal condition number: " << singular_value_ratio * singular_value_ratio << " " << "min_reciprocal_condition_number: " diff --git a/extern/ceres/internal/ceres/covariance_impl.h b/extern/ceres/internal/ceres/covariance_impl.h index eb0cd040666..a3f0761f57c 100644 --- a/extern/ceres/internal/ceres/covariance_impl.h +++ b/extern/ceres/internal/ceres/covariance_impl.h @@ -55,12 +55,21 @@ class CovarianceImpl { const double*> >& covariance_blocks, ProblemImpl* problem); + bool Compute( + const std::vector<const double*>& parameter_blocks, + ProblemImpl* problem); + bool GetCovarianceBlockInTangentOrAmbientSpace( const double* parameter_block1, const double* parameter_block2, bool lift_covariance_to_ambient_space, double* covariance_block) const; + bool GetCovarianceMatrixInTangentOrAmbientSpace( + const std::vector<const double*>& parameters, + bool lift_covariance_to_ambient_space, + double *covariance_matrix) const; + bool ComputeCovarianceSparsity( const std::vector<std::pair<const double*, const double*> >& covariance_blocks, diff --git a/extern/ceres/internal/ceres/gradient_checker.cc b/extern/ceres/internal/ceres/gradient_checker.cc new file mode 100644 index 00000000000..c16c141db09 --- /dev/null +++ b/extern/ceres/internal/ceres/gradient_checker.cc @@ -0,0 +1,276 @@ +// Ceres Solver - A fast non-linear least squares minimizer +// Copyright 2016 Google Inc. All rights reserved. +// http://ceres-solver.org/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors may be +// used to endorse or promote products derived from this software without +// specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Authors: wjr@google.com (William Rucklidge), +// keir@google.com (Keir Mierle), +// dgossow@google.com (David Gossow) + +#include "ceres/gradient_checker.h" + +#include <algorithm> +#include <cmath> +#include <numeric> +#include <string> +#include <vector> + +#include "ceres/is_close.h" +#include "ceres/stringprintf.h" +#include "ceres/types.h" + +namespace ceres { + +using internal::IsClose; +using internal::StringAppendF; +using internal::StringPrintf; +using std::string; +using std::vector; + +namespace { +// Evaluate the cost function and transform the returned Jacobians to +// the local space of the respective local parameterizations. +bool EvaluateCostFunction( + const ceres::CostFunction* function, + double const* const * parameters, + const std::vector<const ceres::LocalParameterization*>& + local_parameterizations, + Vector* residuals, + std::vector<Matrix>* jacobians, + std::vector<Matrix>* local_jacobians) { + CHECK_NOTNULL(residuals); + CHECK_NOTNULL(jacobians); + CHECK_NOTNULL(local_jacobians); + + const vector<int32>& block_sizes = function->parameter_block_sizes(); + const int num_parameter_blocks = block_sizes.size(); + + // Allocate Jacobian matrices in local space. + local_jacobians->resize(num_parameter_blocks); + vector<double*> local_jacobian_data(num_parameter_blocks); + for (int i = 0; i < num_parameter_blocks; ++i) { + int block_size = block_sizes.at(i); + if (local_parameterizations.at(i) != NULL) { + block_size = local_parameterizations.at(i)->LocalSize(); + } + local_jacobians->at(i).resize(function->num_residuals(), block_size); + local_jacobians->at(i).setZero(); + local_jacobian_data.at(i) = local_jacobians->at(i).data(); + } + + // Allocate Jacobian matrices in global space. + jacobians->resize(num_parameter_blocks); + vector<double*> jacobian_data(num_parameter_blocks); + for (int i = 0; i < num_parameter_blocks; ++i) { + jacobians->at(i).resize(function->num_residuals(), block_sizes.at(i)); + jacobians->at(i).setZero(); + jacobian_data.at(i) = jacobians->at(i).data(); + } + + // Compute residuals & jacobians. + CHECK_NE(0, function->num_residuals()); + residuals->resize(function->num_residuals()); + residuals->setZero(); + if (!function->Evaluate(parameters, residuals->data(), + jacobian_data.data())) { + return false; + } + + // Convert Jacobians from global to local space. + for (size_t i = 0; i < local_jacobians->size(); ++i) { + if (local_parameterizations.at(i) == NULL) { + local_jacobians->at(i) = jacobians->at(i); + } else { + int global_size = local_parameterizations.at(i)->GlobalSize(); + int local_size = local_parameterizations.at(i)->LocalSize(); + CHECK_EQ(jacobians->at(i).cols(), global_size); + Matrix global_J_local(global_size, local_size); + local_parameterizations.at(i)->ComputeJacobian( + parameters[i], global_J_local.data()); + local_jacobians->at(i) = jacobians->at(i) * global_J_local; + } + } + return true; +} +} // namespace + +GradientChecker::GradientChecker( + const CostFunction* function, + const vector<const LocalParameterization*>* local_parameterizations, + const NumericDiffOptions& options) : + function_(function) { + CHECK_NOTNULL(function); + if (local_parameterizations != NULL) { + local_parameterizations_ = *local_parameterizations; + } else { + local_parameterizations_.resize(function->parameter_block_sizes().size(), + NULL); + } + DynamicNumericDiffCostFunction<CostFunction, CENTRAL>* + finite_diff_cost_function = + new DynamicNumericDiffCostFunction<CostFunction, CENTRAL>( + function, DO_NOT_TAKE_OWNERSHIP, options); + finite_diff_cost_function_.reset(finite_diff_cost_function); + + const vector<int32>& parameter_block_sizes = + function->parameter_block_sizes(); + const int num_parameter_blocks = parameter_block_sizes.size(); + for (int i = 0; i < num_parameter_blocks; ++i) { + finite_diff_cost_function->AddParameterBlock(parameter_block_sizes[i]); + } + finite_diff_cost_function->SetNumResiduals(function->num_residuals()); +} + +bool GradientChecker::Probe(double const* const * parameters, + double relative_precision, + ProbeResults* results_param) const { + int num_residuals = function_->num_residuals(); + + // Make sure that we have a place to store results, no matter if the user has + // provided an output argument. + ProbeResults* results; + ProbeResults results_local; + if (results_param != NULL) { + results = results_param; + results->residuals.resize(0); + results->jacobians.clear(); + results->numeric_jacobians.clear(); + results->local_jacobians.clear(); + results->local_numeric_jacobians.clear(); + results->error_log.clear(); + } else { + results = &results_local; + } + results->maximum_relative_error = 0.0; + results->return_value = true; + + // Evaluate the derivative using the user supplied code. + vector<Matrix>& jacobians = results->jacobians; + vector<Matrix>& local_jacobians = results->local_jacobians; + if (!EvaluateCostFunction(function_, parameters, local_parameterizations_, + &results->residuals, &jacobians, &local_jacobians)) { + results->error_log = "Function evaluation with Jacobians failed."; + results->return_value = false; + } + + // Evaluate the derivative using numeric derivatives. + vector<Matrix>& numeric_jacobians = results->numeric_jacobians; + vector<Matrix>& local_numeric_jacobians = results->local_numeric_jacobians; + Vector finite_diff_residuals; + if (!EvaluateCostFunction(finite_diff_cost_function_.get(), parameters, + local_parameterizations_, &finite_diff_residuals, + &numeric_jacobians, &local_numeric_jacobians)) { + results->error_log += "\nFunction evaluation with numerical " + "differentiation failed."; + results->return_value = false; + } + + if (!results->return_value) { + return false; + } + + for (int i = 0; i < num_residuals; ++i) { + if (!IsClose( + results->residuals[i], + finite_diff_residuals[i], + relative_precision, + NULL, + NULL)) { + results->error_log = "Function evaluation with and without Jacobians " + "resulted in different residuals."; + LOG(INFO) << results->residuals.transpose(); + LOG(INFO) << finite_diff_residuals.transpose(); + return false; + } + } + + // See if any elements have relative error larger than the threshold. + int num_bad_jacobian_components = 0; + double& worst_relative_error = results->maximum_relative_error; + worst_relative_error = 0; + + // Accumulate the error message for all the jacobians, since it won't get + // output if there are no bad jacobian components. + string error_log; + for (int k = 0; k < function_->parameter_block_sizes().size(); k++) { + StringAppendF(&error_log, + "========== " + "Jacobian for " "block %d: (%ld by %ld)) " + "==========\n", + k, + static_cast<long>(local_jacobians[k].rows()), + static_cast<long>(local_jacobians[k].cols())); + // The funny spacing creates appropriately aligned column headers. + error_log += + " block row col user dx/dy num diff dx/dy " + "abs error relative error parameter residual\n"; + + for (int i = 0; i < local_jacobians[k].rows(); i++) { + for (int j = 0; j < local_jacobians[k].cols(); j++) { + double term_jacobian = local_jacobians[k](i, j); + double finite_jacobian = local_numeric_jacobians[k](i, j); + double relative_error, absolute_error; + bool bad_jacobian_entry = + !IsClose(term_jacobian, + finite_jacobian, + relative_precision, + &relative_error, + &absolute_error); + worst_relative_error = std::max(worst_relative_error, relative_error); + + StringAppendF(&error_log, + "%6d %4d %4d %17g %17g %17g %17g %17g %17g", + k, i, j, + term_jacobian, finite_jacobian, + absolute_error, relative_error, + parameters[k][j], + results->residuals[i]); + + if (bad_jacobian_entry) { + num_bad_jacobian_components++; + StringAppendF( + &error_log, + " ------ (%d,%d,%d) Relative error worse than %g", + k, i, j, relative_precision); + } + error_log += "\n"; + } + } + } + + // Since there were some bad errors, dump comprehensive debug info. + if (num_bad_jacobian_components) { + string header = StringPrintf("\nDetected %d bad Jacobian component(s). " + "Worst relative error was %g.\n", + num_bad_jacobian_components, + worst_relative_error); + results->error_log = header + "\n" + error_log; + return false; + } + return true; +} + +} // namespace ceres diff --git a/extern/ceres/internal/ceres/gradient_checking_cost_function.cc b/extern/ceres/internal/ceres/gradient_checking_cost_function.cc index 580fd260e15..f2c73367891 100644 --- a/extern/ceres/internal/ceres/gradient_checking_cost_function.cc +++ b/extern/ceres/internal/ceres/gradient_checking_cost_function.cc @@ -26,7 +26,8 @@ // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. // -// Author: keir@google.com (Keir Mierle) +// Authors: keir@google.com (Keir Mierle), +// dgossow@google.com (David Gossow) #include "ceres/gradient_checking_cost_function.h" @@ -36,7 +37,7 @@ #include <string> #include <vector> -#include "ceres/cost_function.h" +#include "ceres/gradient_checker.h" #include "ceres/internal/eigen.h" #include "ceres/internal/scoped_ptr.h" #include "ceres/parameter_block.h" @@ -59,55 +60,25 @@ using std::vector; namespace { -// True if x and y have an absolute relative difference less than -// relative_precision and false otherwise. Stores the relative and absolute -// difference in relative/absolute_error if non-NULL. -bool IsClose(double x, double y, double relative_precision, - double *relative_error, - double *absolute_error) { - double local_absolute_error; - double local_relative_error; - if (!absolute_error) { - absolute_error = &local_absolute_error; - } - if (!relative_error) { - relative_error = &local_relative_error; - } - *absolute_error = abs(x - y); - *relative_error = *absolute_error / max(abs(x), abs(y)); - if (x == 0 || y == 0) { - // If x or y is exactly zero, then relative difference doesn't have any - // meaning. Take the absolute difference instead. - *relative_error = *absolute_error; - } - return abs(*relative_error) < abs(relative_precision); -} - class GradientCheckingCostFunction : public CostFunction { public: - GradientCheckingCostFunction(const CostFunction* function, - const NumericDiffOptions& options, - double relative_precision, - const string& extra_info) + GradientCheckingCostFunction( + const CostFunction* function, + const std::vector<const LocalParameterization*>* local_parameterizations, + const NumericDiffOptions& options, + double relative_precision, + const string& extra_info, + GradientCheckingIterationCallback* callback) : function_(function), + gradient_checker_(function, local_parameterizations, options), relative_precision_(relative_precision), - extra_info_(extra_info) { - DynamicNumericDiffCostFunction<CostFunction, CENTRAL>* - finite_diff_cost_function = - new DynamicNumericDiffCostFunction<CostFunction, CENTRAL>( - function, - DO_NOT_TAKE_OWNERSHIP, - options); - + extra_info_(extra_info), + callback_(callback) { + CHECK_NOTNULL(callback_); const vector<int32>& parameter_block_sizes = function->parameter_block_sizes(); - for (int i = 0; i < parameter_block_sizes.size(); ++i) { - finite_diff_cost_function->AddParameterBlock(parameter_block_sizes[i]); - } *mutable_parameter_block_sizes() = parameter_block_sizes; set_num_residuals(function->num_residuals()); - finite_diff_cost_function->SetNumResiduals(num_residuals()); - finite_diff_cost_function_.reset(finite_diff_cost_function); } virtual ~GradientCheckingCostFunction() { } @@ -120,133 +91,92 @@ class GradientCheckingCostFunction : public CostFunction { return function_->Evaluate(parameters, residuals, NULL); } - int num_residuals = function_->num_residuals(); + GradientChecker::ProbeResults results; + bool okay = gradient_checker_.Probe(parameters, + relative_precision_, + &results); - // Make space for the jacobians of the two methods. - const vector<int32>& block_sizes = function_->parameter_block_sizes(); - vector<Matrix> term_jacobians(block_sizes.size()); - vector<Matrix> finite_difference_jacobians(block_sizes.size()); - vector<double*> term_jacobian_pointers(block_sizes.size()); - vector<double*> finite_difference_jacobian_pointers(block_sizes.size()); - for (int i = 0; i < block_sizes.size(); i++) { - term_jacobians[i].resize(num_residuals, block_sizes[i]); - term_jacobian_pointers[i] = term_jacobians[i].data(); - finite_difference_jacobians[i].resize(num_residuals, block_sizes[i]); - finite_difference_jacobian_pointers[i] = - finite_difference_jacobians[i].data(); - } - - // Evaluate the derivative using the user supplied code. - if (!function_->Evaluate(parameters, - residuals, - &term_jacobian_pointers[0])) { - LOG(WARNING) << "Function evaluation failed."; + // If the cost function returned false, there's nothing we can say about + // the gradients. + if (results.return_value == false) { return false; } - // Evaluate the derivative using numeric derivatives. - finite_diff_cost_function_->Evaluate( - parameters, - residuals, - &finite_difference_jacobian_pointers[0]); + // Copy the residuals. + const int num_residuals = function_->num_residuals(); + MatrixRef(residuals, num_residuals, 1) = results.residuals; - // See if any elements have relative error larger than the threshold. - int num_bad_jacobian_components = 0; - double worst_relative_error = 0; - - // Accumulate the error message for all the jacobians, since it won't get - // output if there are no bad jacobian components. - string m; + // Copy the original jacobian blocks into the jacobians array. + const vector<int32>& block_sizes = function_->parameter_block_sizes(); for (int k = 0; k < block_sizes.size(); k++) { - // Copy the original jacobian blocks into the jacobians array. if (jacobians[k] != NULL) { MatrixRef(jacobians[k], - term_jacobians[k].rows(), - term_jacobians[k].cols()) = term_jacobians[k]; - } - - StringAppendF(&m, - "========== " - "Jacobian for " "block %d: (%ld by %ld)) " - "==========\n", - k, - static_cast<long>(term_jacobians[k].rows()), - static_cast<long>(term_jacobians[k].cols())); - // The funny spacing creates appropriately aligned column headers. - m += " block row col user dx/dy num diff dx/dy " - "abs error relative error parameter residual\n"; - - for (int i = 0; i < term_jacobians[k].rows(); i++) { - for (int j = 0; j < term_jacobians[k].cols(); j++) { - double term_jacobian = term_jacobians[k](i, j); - double finite_jacobian = finite_difference_jacobians[k](i, j); - double relative_error, absolute_error; - bool bad_jacobian_entry = - !IsClose(term_jacobian, - finite_jacobian, - relative_precision_, - &relative_error, - &absolute_error); - worst_relative_error = max(worst_relative_error, relative_error); - - StringAppendF(&m, "%6d %4d %4d %17g %17g %17g %17g %17g %17g", - k, i, j, - term_jacobian, finite_jacobian, - absolute_error, relative_error, - parameters[k][j], - residuals[i]); - - if (bad_jacobian_entry) { - num_bad_jacobian_components++; - StringAppendF( - &m, " ------ (%d,%d,%d) Relative error worse than %g", - k, i, j, relative_precision_); - } - m += "\n"; - } + results.jacobians[k].rows(), + results.jacobians[k].cols()) = results.jacobians[k]; } } - // Since there were some bad errors, dump comprehensive debug info. - if (num_bad_jacobian_components) { - string header = StringPrintf("Detected %d bad jacobian component(s). " - "Worst relative error was %g.\n", - num_bad_jacobian_components, - worst_relative_error); - if (!extra_info_.empty()) { - header += "Extra info for this residual: " + extra_info_ + "\n"; - } - LOG(WARNING) << "\n" << header << m; + if (!okay) { + std::string error_log = "Gradient Error detected!\nExtra info for " + "this residual: " + extra_info_ + "\n" + results.error_log; + callback_->SetGradientErrorDetected(error_log); } return true; } private: const CostFunction* function_; - internal::scoped_ptr<CostFunction> finite_diff_cost_function_; + GradientChecker gradient_checker_; double relative_precision_; string extra_info_; + GradientCheckingIterationCallback* callback_; }; } // namespace -CostFunction *CreateGradientCheckingCostFunction( - const CostFunction *cost_function, +GradientCheckingIterationCallback::GradientCheckingIterationCallback() + : gradient_error_detected_(false) { +} + +CallbackReturnType GradientCheckingIterationCallback::operator()( + const IterationSummary& summary) { + if (gradient_error_detected_) { + LOG(ERROR)<< "Gradient error detected. Terminating solver."; + return SOLVER_ABORT; + } + return SOLVER_CONTINUE; +} +void GradientCheckingIterationCallback::SetGradientErrorDetected( + std::string& error_log) { + mutex_.Lock(); + gradient_error_detected_ = true; + error_log_ += "\n" + error_log; + mutex_.Unlock(); +} + +CostFunction* CreateGradientCheckingCostFunction( + const CostFunction* cost_function, + const std::vector<const LocalParameterization*>* local_parameterizations, double relative_step_size, double relative_precision, - const string& extra_info) { + const std::string& extra_info, + GradientCheckingIterationCallback* callback) { NumericDiffOptions numeric_diff_options; numeric_diff_options.relative_step_size = relative_step_size; return new GradientCheckingCostFunction(cost_function, + local_parameterizations, numeric_diff_options, - relative_precision, - extra_info); + relative_precision, extra_info, + callback); } -ProblemImpl* CreateGradientCheckingProblemImpl(ProblemImpl* problem_impl, - double relative_step_size, - double relative_precision) { +ProblemImpl* CreateGradientCheckingProblemImpl( + ProblemImpl* problem_impl, + double relative_step_size, + double relative_precision, + GradientCheckingIterationCallback* callback) { + CHECK_NOTNULL(callback); // We create new CostFunctions by wrapping the original CostFunction // in a gradient checking CostFunction. So its okay for the // ProblemImpl to take ownership of it and destroy it. The @@ -260,6 +190,9 @@ ProblemImpl* CreateGradientCheckingProblemImpl(ProblemImpl* problem_impl, gradient_checking_problem_options.local_parameterization_ownership = DO_NOT_TAKE_OWNERSHIP; + NumericDiffOptions numeric_diff_options; + numeric_diff_options.relative_step_size = relative_step_size; + ProblemImpl* gradient_checking_problem_impl = new ProblemImpl( gradient_checking_problem_options); @@ -294,19 +227,26 @@ ProblemImpl* CreateGradientCheckingProblemImpl(ProblemImpl* problem_impl, string extra_info = StringPrintf( "Residual block id %d; depends on parameters [", i); vector<double*> parameter_blocks; + vector<const LocalParameterization*> local_parameterizations; + parameter_blocks.reserve(residual_block->NumParameterBlocks()); + local_parameterizations.reserve(residual_block->NumParameterBlocks()); for (int j = 0; j < residual_block->NumParameterBlocks(); ++j) { ParameterBlock* parameter_block = residual_block->parameter_blocks()[j]; parameter_blocks.push_back(parameter_block->mutable_user_state()); StringAppendF(&extra_info, "%p", parameter_block->mutable_user_state()); extra_info += (j < residual_block->NumParameterBlocks() - 1) ? ", " : "]"; + local_parameterizations.push_back(problem_impl->GetParameterization( + parameter_block->mutable_user_state())); } // Wrap the original CostFunction in a GradientCheckingCostFunction. CostFunction* gradient_checking_cost_function = - CreateGradientCheckingCostFunction(residual_block->cost_function(), - relative_step_size, - relative_precision, - extra_info); + new GradientCheckingCostFunction(residual_block->cost_function(), + &local_parameterizations, + numeric_diff_options, + relative_precision, + extra_info, + callback); // The const_cast is necessary because // ProblemImpl::AddResidualBlock can potentially take ownership of diff --git a/extern/ceres/internal/ceres/gradient_checking_cost_function.h b/extern/ceres/internal/ceres/gradient_checking_cost_function.h index cf92cb72bc5..497f8e2a594 100644 --- a/extern/ceres/internal/ceres/gradient_checking_cost_function.h +++ b/extern/ceres/internal/ceres/gradient_checking_cost_function.h @@ -26,7 +26,8 @@ // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. // -// Author: keir@google.com (Keir Mierle) +// Authors: keir@google.com (Keir Mierle), +// dgossow@google.com (David Gossow) #ifndef CERES_INTERNAL_GRADIENT_CHECKING_COST_FUNCTION_H_ #define CERES_INTERNAL_GRADIENT_CHECKING_COST_FUNCTION_H_ @@ -34,50 +35,76 @@ #include <string> #include "ceres/cost_function.h" +#include "ceres/iteration_callback.h" +#include "ceres/local_parameterization.h" +#include "ceres/mutex.h" namespace ceres { namespace internal { class ProblemImpl; -// Creates a CostFunction that checks the jacobians that cost_function computes -// with finite differences. Bad results are logged; required precision is -// controlled by relative_precision and the numeric differentiation step size is -// controlled with relative_step_size. See solver.h for a better explanation of -// relative_step_size. Caller owns result. -// -// The condition enforced is that -// -// (J_actual(i, j) - J_numeric(i, j)) -// ------------------------------------ < relative_precision -// max(J_actual(i, j), J_numeric(i, j)) -// -// where J_actual(i, j) is the jacobian as computed by the supplied cost -// function (by the user) and J_numeric is the jacobian as computed by finite -// differences. -// -// Note: This is quite inefficient and is intended only for debugging. +// Callback that collects information about gradient checking errors, and +// will abort the solve as soon as an error occurs. +class GradientCheckingIterationCallback : public IterationCallback { + public: + GradientCheckingIterationCallback(); + + // Will return SOLVER_CONTINUE until a gradient error has been detected, + // then return SOLVER_ABORT. + virtual CallbackReturnType operator()(const IterationSummary& summary); + + // Notify this that a gradient error has occurred (thread safe). + void SetGradientErrorDetected(std::string& error_log); + + // Retrieve error status (not thread safe). + bool gradient_error_detected() const { return gradient_error_detected_; } + const std::string& error_log() const { return error_log_; } + private: + bool gradient_error_detected_; + std::string error_log_; + // Mutex protecting member variables. + ceres::internal::Mutex mutex_; +}; + +// Creates a CostFunction that checks the Jacobians that cost_function computes +// with finite differences. This API is only intended for unit tests that intend +// to check the functionality of the GradientCheckingCostFunction +// implementation directly. CostFunction* CreateGradientCheckingCostFunction( const CostFunction* cost_function, + const std::vector<const LocalParameterization*>* local_parameterizations, double relative_step_size, double relative_precision, - const std::string& extra_info); + const std::string& extra_info, + GradientCheckingIterationCallback* callback); -// Create a new ProblemImpl object from the input problem_impl, where -// each CostFunctions in problem_impl are wrapped inside a -// GradientCheckingCostFunctions. This gives us a ProblemImpl object -// which checks its derivatives against estimates from numeric -// differentiation everytime a ResidualBlock is evaluated. +// Create a new ProblemImpl object from the input problem_impl, where all +// cost functions are wrapped so that each time their Evaluate method is called, +// an additional check is performed that compares the Jacobians computed by +// the original cost function with alternative Jacobians computed using +// numerical differentiation. If local parameterizations are given for any +// parameters, the Jacobians will be compared in the local space instead of the +// ambient space. For details on the gradient checking procedure, see the +// documentation of the GradientChecker class. If an error is detected in any +// iteration, the respective cost function will notify the +// GradientCheckingIterationCallback. +// +// The caller owns the returned ProblemImpl object. +// +// Note: This is quite inefficient and is intended only for debugging. // // relative_step_size and relative_precision are parameters to control // the numeric differentiation and the relative tolerance between the // jacobian computed by the CostFunctions in problem_impl and -// jacobians obtained by numerically differentiating them. For more -// details see the documentation for -// CreateGradientCheckingCostFunction above. -ProblemImpl* CreateGradientCheckingProblemImpl(ProblemImpl* problem_impl, - double relative_step_size, - double relative_precision); +// jacobians obtained by numerically differentiating them. See the +// documentation of 'numeric_derivative_relative_step_size' in solver.h for a +// better explanation. +ProblemImpl* CreateGradientCheckingProblemImpl( + ProblemImpl* problem_impl, + double relative_step_size, + double relative_precision, + GradientCheckingIterationCallback* callback); } // namespace internal } // namespace ceres diff --git a/extern/ceres/internal/ceres/gradient_problem_solver.cc b/extern/ceres/internal/ceres/gradient_problem_solver.cc index 9a549c23dac..8709f8f3fbd 100644 --- a/extern/ceres/internal/ceres/gradient_problem_solver.cc +++ b/extern/ceres/internal/ceres/gradient_problem_solver.cc @@ -84,6 +84,12 @@ Solver::Options GradientProblemSolverOptionsToSolverOptions( } // namespace +bool GradientProblemSolver::Options::IsValid(std::string* error) const { + const Solver::Options solver_options = + GradientProblemSolverOptionsToSolverOptions(*this); + return solver_options.IsValid(error); +} + GradientProblemSolver::~GradientProblemSolver() { } @@ -99,8 +105,6 @@ void GradientProblemSolver::Solve(const GradientProblemSolver::Options& options, using internal::SetSummaryFinalCost; double start_time = WallTimeInSeconds(); - Solver::Options solver_options = - GradientProblemSolverOptionsToSolverOptions(options); *CHECK_NOTNULL(summary) = Summary(); summary->num_parameters = problem.NumParameters(); @@ -112,14 +116,16 @@ void GradientProblemSolver::Solve(const GradientProblemSolver::Options& options, summary->nonlinear_conjugate_gradient_type = options.nonlinear_conjugate_gradient_type; // NOLINT // Check validity - if (!solver_options.IsValid(&summary->message)) { + if (!options.IsValid(&summary->message)) { LOG(ERROR) << "Terminating: " << summary->message; return; } - // Assuming that the parameter blocks in the program have been - Minimizer::Options minimizer_options; - minimizer_options = Minimizer::Options(solver_options); + // TODO(sameeragarwal): This is a bit convoluted, we should be able + // to convert to minimizer options directly, but this will do for + // now. + Minimizer::Options minimizer_options = + Minimizer::Options(GradientProblemSolverOptionsToSolverOptions(options)); minimizer_options.evaluator.reset(new GradientProblemEvaluator(problem)); scoped_ptr<IterationCallback> logging_callback; diff --git a/extern/ceres/internal/ceres/is_close.cc b/extern/ceres/internal/ceres/is_close.cc new file mode 100644 index 00000000000..a91a17454d9 --- /dev/null +++ b/extern/ceres/internal/ceres/is_close.cc @@ -0,0 +1,59 @@ +// Ceres Solver - A fast non-linear least squares minimizer +// Copyright 2016 Google Inc. All rights reserved. +// http://ceres-solver.org/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors may be +// used to endorse or promote products derived from this software without +// specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Authors: keir@google.com (Keir Mierle), dgossow@google.com (David Gossow) + +#include "ceres/is_close.h" + +#include <algorithm> +#include <cmath> + +namespace ceres { +namespace internal { +bool IsClose(double x, double y, double relative_precision, + double *relative_error, + double *absolute_error) { + double local_absolute_error; + double local_relative_error; + if (!absolute_error) { + absolute_error = &local_absolute_error; + } + if (!relative_error) { + relative_error = &local_relative_error; + } + *absolute_error = std::fabs(x - y); + *relative_error = *absolute_error / std::max(std::fabs(x), std::fabs(y)); + if (x == 0 || y == 0) { + // If x or y is exactly zero, then relative difference doesn't have any + // meaning. Take the absolute difference instead. + *relative_error = *absolute_error; + } + return *relative_error < std::fabs(relative_precision); +} +} // namespace internal +} // namespace ceres diff --git a/extern/ceres/internal/ceres/is_close.h b/extern/ceres/internal/ceres/is_close.h new file mode 100644 index 00000000000..7789448c8e8 --- /dev/null +++ b/extern/ceres/internal/ceres/is_close.h @@ -0,0 +1,51 @@ +// Ceres Solver - A fast non-linear least squares minimizer +// Copyright 2016 Google Inc. All rights reserved. +// http://ceres-solver.org/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors may be +// used to endorse or promote products derived from this software without +// specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Authors: keir@google.com (Keir Mierle), dgossow@google.com (David Gossow) +// +// Utility routine for comparing two values. + +#ifndef CERES_INTERNAL_IS_CLOSE_H_ +#define CERES_INTERNAL_IS_CLOSE_H_ + +namespace ceres { +namespace internal { +// Returns true if x and y have a relative (unsigned) difference less than +// relative_precision and false otherwise. Stores the relative and absolute +// difference in relative/absolute_error if non-NULL. If one of the two values +// is exactly zero, the absolute difference will be compared, and relative_error +// will be set to the absolute difference. +bool IsClose(double x, + double y, + double relative_precision, + double *relative_error, + double *absolute_error); +} // namespace internal +} // namespace ceres + +#endif // CERES_INTERNAL_IS_CLOSE_H_ diff --git a/extern/ceres/internal/ceres/line_search_minimizer.cc b/extern/ceres/internal/ceres/line_search_minimizer.cc index 62264fb0b64..fdde1ca9c86 100644 --- a/extern/ceres/internal/ceres/line_search_minimizer.cc +++ b/extern/ceres/internal/ceres/line_search_minimizer.cc @@ -191,6 +191,7 @@ void LineSearchMinimizer::Minimize(const Minimizer::Options& options, options.line_search_sufficient_curvature_decrease; line_search_options.max_step_expansion = options.max_line_search_step_expansion; + line_search_options.is_silent = options.is_silent; line_search_options.function = &line_search_function; scoped_ptr<LineSearch> @@ -341,10 +342,12 @@ void LineSearchMinimizer::Minimize(const Minimizer::Options& options, "as the step was valid when it was selected by the line search."; LOG_IF(WARNING, is_not_silent) << "Terminating: " << summary->message; break; - } else if (!Evaluate(evaluator, - x_plus_delta, - ¤t_state, - &summary->message)) { + } + + if (!Evaluate(evaluator, + x_plus_delta, + ¤t_state, + &summary->message)) { summary->termination_type = FAILURE; summary->message = "Step failed to evaluate. This should not happen as the step was " @@ -352,15 +355,17 @@ void LineSearchMinimizer::Minimize(const Minimizer::Options& options, summary->message; LOG_IF(WARNING, is_not_silent) << "Terminating: " << summary->message; break; - } else { - x = x_plus_delta; } + // Compute the norm of the step in the ambient space. + iteration_summary.step_norm = (x_plus_delta - x).norm(); + x = x_plus_delta; + iteration_summary.gradient_max_norm = current_state.gradient_max_norm; iteration_summary.gradient_norm = sqrt(current_state.gradient_squared_norm); iteration_summary.cost_change = previous_state.cost - current_state.cost; iteration_summary.cost = current_state.cost + summary->fixed_cost; - iteration_summary.step_norm = delta.norm(); + iteration_summary.step_is_valid = true; iteration_summary.step_is_successful = true; iteration_summary.step_size = current_state.step_size; @@ -376,6 +381,13 @@ void LineSearchMinimizer::Minimize(const Minimizer::Options& options, WallTimeInSeconds() - start_time + summary->preprocessor_time_in_seconds; + // Iterations inside the line search algorithm are considered + // 'steps' in the broader context, to distinguish these inner + // iterations from from the outer iterations of the line search + // minimizer. The number of line search steps is the total number + // of inner line search iterations (or steps) across the entire + // minimization. + summary->num_line_search_steps += line_search_summary.num_iterations; summary->line_search_cost_evaluation_time_in_seconds += line_search_summary.cost_evaluation_time_in_seconds; summary->line_search_gradient_evaluation_time_in_seconds += diff --git a/extern/ceres/internal/ceres/local_parameterization.cc b/extern/ceres/internal/ceres/local_parameterization.cc index 82004761ec0..a6bf1f6ddcc 100644 --- a/extern/ceres/internal/ceres/local_parameterization.cc +++ b/extern/ceres/internal/ceres/local_parameterization.cc @@ -30,6 +30,8 @@ #include "ceres/local_parameterization.h" +#include <algorithm> +#include "Eigen/Geometry" #include "ceres/householder_vector.h" #include "ceres/internal/eigen.h" #include "ceres/internal/fixed_array.h" @@ -87,28 +89,17 @@ bool IdentityParameterization::MultiplyByJacobian(const double* x, } SubsetParameterization::SubsetParameterization( - int size, - const vector<int>& constant_parameters) - : local_size_(size - constant_parameters.size()), - constancy_mask_(size, 0) { - CHECK_GT(constant_parameters.size(), 0) - << "The set of constant parameters should contain at least " - << "one element. If you do not wish to hold any parameters " - << "constant, then do not use a SubsetParameterization"; - + int size, const vector<int>& constant_parameters) + : local_size_(size - constant_parameters.size()), constancy_mask_(size, 0) { vector<int> constant = constant_parameters; - sort(constant.begin(), constant.end()); - CHECK(unique(constant.begin(), constant.end()) == constant.end()) + std::sort(constant.begin(), constant.end()); + CHECK_GE(constant.front(), 0) + << "Indices indicating constant parameter must be greater than zero."; + CHECK_LT(constant.back(), size) + << "Indices indicating constant parameter must be less than the size " + << "of the parameter block."; + CHECK(std::adjacent_find(constant.begin(), constant.end()) == constant.end()) << "The set of constant parameters cannot contain duplicates"; - CHECK_LT(constant_parameters.size(), size) - << "Number of parameters held constant should be less " - << "than the size of the parameter block. If you wish " - << "to hold the entire parameter block constant, then a " - << "efficient way is to directly mark it as constant " - << "instead of using a LocalParameterization to do so."; - CHECK_GE(*min_element(constant.begin(), constant.end()), 0); - CHECK_LT(*max_element(constant.begin(), constant.end()), size); - for (int i = 0; i < constant_parameters.size(); ++i) { constancy_mask_[constant_parameters[i]] = 1; } @@ -129,6 +120,10 @@ bool SubsetParameterization::Plus(const double* x, bool SubsetParameterization::ComputeJacobian(const double* x, double* jacobian) const { + if (local_size_ == 0) { + return true; + } + MatrixRef m(jacobian, constancy_mask_.size(), local_size_); m.setZero(); for (int i = 0, j = 0; i < constancy_mask_.size(); ++i) { @@ -143,6 +138,10 @@ bool SubsetParameterization::MultiplyByJacobian(const double* x, const int num_rows, const double* global_matrix, double* local_matrix) const { + if (local_size_ == 0) { + return true; + } + for (int row = 0; row < num_rows; ++row) { for (int col = 0, j = 0; col < constancy_mask_.size(); ++col) { if (!constancy_mask_[col]) { @@ -184,6 +183,39 @@ bool QuaternionParameterization::ComputeJacobian(const double* x, return true; } +bool EigenQuaternionParameterization::Plus(const double* x_ptr, + const double* delta, + double* x_plus_delta_ptr) const { + Eigen::Map<Eigen::Quaterniond> x_plus_delta(x_plus_delta_ptr); + Eigen::Map<const Eigen::Quaterniond> x(x_ptr); + + const double norm_delta = + sqrt(delta[0] * delta[0] + delta[1] * delta[1] + delta[2] * delta[2]); + if (norm_delta > 0.0) { + const double sin_delta_by_delta = sin(norm_delta) / norm_delta; + + // Note, in the constructor w is first. + Eigen::Quaterniond delta_q(cos(norm_delta), + sin_delta_by_delta * delta[0], + sin_delta_by_delta * delta[1], + sin_delta_by_delta * delta[2]); + x_plus_delta = delta_q * x; + } else { + x_plus_delta = x; + } + + return true; +} + +bool EigenQuaternionParameterization::ComputeJacobian(const double* x, + double* jacobian) const { + jacobian[0] = x[3]; jacobian[1] = x[2]; jacobian[2] = -x[1]; // NOLINT + jacobian[3] = -x[2]; jacobian[4] = x[3]; jacobian[5] = x[0]; // NOLINT + jacobian[6] = x[1]; jacobian[7] = -x[0]; jacobian[8] = x[3]; // NOLINT + jacobian[9] = -x[0]; jacobian[10] = -x[1]; jacobian[11] = -x[2]; // NOLINT + return true; +} + HomogeneousVectorParameterization::HomogeneousVectorParameterization(int size) : size_(size) { CHECK_GT(size_, 1) << "The size of the homogeneous vector needs to be " @@ -332,9 +364,9 @@ bool ProductParameterization::ComputeJacobian(const double* x, if (!param->ComputeJacobian(x + x_cursor, buffer.get())) { return false; } - jacobian.block(x_cursor, delta_cursor, global_size, local_size) = MatrixRef(buffer.get(), global_size, local_size); + delta_cursor += local_size; x_cursor += global_size; } diff --git a/extern/ceres/internal/ceres/map_util.h b/extern/ceres/internal/ceres/map_util.h index 61c531f297c..f55aee37689 100644 --- a/extern/ceres/internal/ceres/map_util.h +++ b/extern/ceres/internal/ceres/map_util.h @@ -67,7 +67,7 @@ FindOrDie(const Collection& collection, // If the key is present in the map then the value associated with that // key is returned, otherwise the value passed as a default is returned. template <class Collection> -const typename Collection::value_type::second_type& +const typename Collection::value_type::second_type FindWithDefault(const Collection& collection, const typename Collection::value_type::first_type& key, const typename Collection::value_type::second_type& value) { diff --git a/extern/ceres/internal/ceres/parameter_block.h b/extern/ceres/internal/ceres/parameter_block.h index cb7140d9582..8e21553c668 100644 --- a/extern/ceres/internal/ceres/parameter_block.h +++ b/extern/ceres/internal/ceres/parameter_block.h @@ -161,25 +161,34 @@ class ParameterBlock { // does not take ownership of the parameterization. void SetParameterization(LocalParameterization* new_parameterization) { CHECK(new_parameterization != NULL) << "NULL parameterization invalid."; + // Nothing to do if the new parameterization is the same as the + // old parameterization. + if (new_parameterization == local_parameterization_) { + return; + } + + CHECK(local_parameterization_ == NULL) + << "Can't re-set the local parameterization; it leads to " + << "ambiguous ownership. Current local parameterization is: " + << local_parameterization_; + CHECK(new_parameterization->GlobalSize() == size_) << "Invalid parameterization for parameter block. The parameter block " << "has size " << size_ << " while the parameterization has a global " << "size of " << new_parameterization->GlobalSize() << ". Did you " << "accidentally use the wrong parameter block or parameterization?"; - if (new_parameterization != local_parameterization_) { - CHECK(local_parameterization_ == NULL) - << "Can't re-set the local parameterization; it leads to " - << "ambiguous ownership."; - local_parameterization_ = new_parameterization; - local_parameterization_jacobian_.reset( - new double[local_parameterization_->GlobalSize() * - local_parameterization_->LocalSize()]); - CHECK(UpdateLocalParameterizationJacobian()) - << "Local parameterization Jacobian computation failed for x: " - << ConstVectorRef(state_, Size()).transpose(); - } else { - // Ignore the case that the parameterizations match. - } + + CHECK_GT(new_parameterization->LocalSize(), 0) + << "Invalid parameterization. Parameterizations must have a positive " + << "dimensional tangent space."; + + local_parameterization_ = new_parameterization; + local_parameterization_jacobian_.reset( + new double[local_parameterization_->GlobalSize() * + local_parameterization_->LocalSize()]); + CHECK(UpdateLocalParameterizationJacobian()) + << "Local parameterization Jacobian computation failed for x: " + << ConstVectorRef(state_, Size()).transpose(); } void SetUpperBound(int index, double upper_bound) { diff --git a/extern/ceres/internal/ceres/problem.cc b/extern/ceres/internal/ceres/problem.cc index 03b7d6afa48..730ce642036 100644 --- a/extern/ceres/internal/ceres/problem.cc +++ b/extern/ceres/internal/ceres/problem.cc @@ -174,6 +174,10 @@ void Problem::SetParameterBlockVariable(double* values) { problem_impl_->SetParameterBlockVariable(values); } +bool Problem::IsParameterBlockConstant(double* values) const { + return problem_impl_->IsParameterBlockConstant(values); +} + void Problem::SetParameterization( double* values, LocalParameterization* local_parameterization) { diff --git a/extern/ceres/internal/ceres/problem_impl.cc b/extern/ceres/internal/ceres/problem_impl.cc index 8547d5d3f77..4abea8b33ee 100644 --- a/extern/ceres/internal/ceres/problem_impl.cc +++ b/extern/ceres/internal/ceres/problem_impl.cc @@ -249,10 +249,11 @@ ResidualBlock* ProblemImpl::AddResidualBlock( // Check for duplicate parameter blocks. vector<double*> sorted_parameter_blocks(parameter_blocks); sort(sorted_parameter_blocks.begin(), sorted_parameter_blocks.end()); - vector<double*>::const_iterator duplicate_items = - unique(sorted_parameter_blocks.begin(), - sorted_parameter_blocks.end()); - if (duplicate_items != sorted_parameter_blocks.end()) { + const bool has_duplicate_items = + (std::adjacent_find(sorted_parameter_blocks.begin(), + sorted_parameter_blocks.end()) + != sorted_parameter_blocks.end()); + if (has_duplicate_items) { string blocks; for (int i = 0; i < parameter_blocks.size(); ++i) { blocks += StringPrintf(" %p ", parameter_blocks[i]); @@ -572,6 +573,16 @@ void ProblemImpl::SetParameterBlockConstant(double* values) { parameter_block->SetConstant(); } +bool ProblemImpl::IsParameterBlockConstant(double* values) const { + const ParameterBlock* parameter_block = + FindWithDefault(parameter_block_map_, values, NULL); + CHECK(parameter_block != NULL) + << "Parameter block not found: " << values << ". You must add the " + << "parameter block to the problem before it can be queried."; + + return parameter_block->IsConstant(); +} + void ProblemImpl::SetParameterBlockVariable(double* values) { ParameterBlock* parameter_block = FindWithDefault(parameter_block_map_, values, NULL); diff --git a/extern/ceres/internal/ceres/problem_impl.h b/extern/ceres/internal/ceres/problem_impl.h index f42bde6c793..a4689c362f6 100644 --- a/extern/ceres/internal/ceres/problem_impl.h +++ b/extern/ceres/internal/ceres/problem_impl.h @@ -128,6 +128,8 @@ class ProblemImpl { void SetParameterBlockConstant(double* values); void SetParameterBlockVariable(double* values); + bool IsParameterBlockConstant(double* values) const; + void SetParameterization(double* values, LocalParameterization* local_parameterization); const LocalParameterization* GetParameterization(double* values) const; diff --git a/extern/ceres/internal/ceres/reorder_program.cc b/extern/ceres/internal/ceres/reorder_program.cc index d0e8f32b3b7..a7c37107591 100644 --- a/extern/ceres/internal/ceres/reorder_program.cc +++ b/extern/ceres/internal/ceres/reorder_program.cc @@ -142,6 +142,11 @@ void OrderingForSparseNormalCholeskyUsingSuiteSparse( ordering); } + VLOG(2) << "Block ordering stats: " + << " flops: " << ss.mutable_cc()->fl + << " lnz : " << ss.mutable_cc()->lnz + << " anz : " << ss.mutable_cc()->anz; + ss.Free(block_jacobian_transpose); #endif // CERES_NO_SUITESPARSE } diff --git a/extern/ceres/internal/ceres/residual_block.h b/extern/ceres/internal/ceres/residual_block.h index 05e6d1f81e5..a32f1c36cd3 100644 --- a/extern/ceres/internal/ceres/residual_block.h +++ b/extern/ceres/internal/ceres/residual_block.h @@ -127,7 +127,7 @@ class ResidualBlock { int index() const { return index_; } void set_index(int index) { index_ = index; } - std::string ToString() { + std::string ToString() const { return StringPrintf("{residual block; index=%d}", index_); } diff --git a/extern/ceres/internal/ceres/schur_complement_solver.cc b/extern/ceres/internal/ceres/schur_complement_solver.cc index 2491060dcdc..65449832c4c 100644 --- a/extern/ceres/internal/ceres/schur_complement_solver.cc +++ b/extern/ceres/internal/ceres/schur_complement_solver.cc @@ -33,6 +33,7 @@ #include <algorithm> #include <ctime> #include <set> +#include <sstream> #include <vector> #include "ceres/block_random_access_dense_matrix.h" @@ -563,6 +564,12 @@ SparseSchurComplementSolver::SolveReducedLinearSystemUsingEigen( // worse than the one computed using the block version of the // algorithm. simplicial_ldlt_->analyzePattern(eigen_lhs); + if (VLOG_IS_ON(2)) { + std::stringstream ss; + simplicial_ldlt_->dumpMemory(ss); + VLOG(2) << "Symbolic Analysis\n" + << ss.str(); + } event_logger.AddEvent("Analysis"); if (simplicial_ldlt_->info() != Eigen::Success) { summary.termination_type = LINEAR_SOLVER_FATAL_ERROR; diff --git a/extern/ceres/internal/ceres/solver.cc b/extern/ceres/internal/ceres/solver.cc index 9f3228bb0be..8411350986a 100644 --- a/extern/ceres/internal/ceres/solver.cc +++ b/extern/ceres/internal/ceres/solver.cc @@ -94,7 +94,7 @@ bool CommonOptionsAreValid(const Solver::Options& options, string* error) { OPTION_GT(num_linear_solver_threads, 0); if (options.check_gradients) { OPTION_GT(gradient_check_relative_precision, 0.0); - OPTION_GT(numeric_derivative_relative_step_size, 0.0); + OPTION_GT(gradient_check_numeric_derivative_relative_step_size, 0.0); } return true; } @@ -351,6 +351,7 @@ void PreSolveSummarize(const Solver::Options& options, summary->dense_linear_algebra_library_type = options.dense_linear_algebra_library_type; // NOLINT summary->dogleg_type = options.dogleg_type; summary->inner_iteration_time_in_seconds = 0.0; + summary->num_line_search_steps = 0; summary->line_search_cost_evaluation_time_in_seconds = 0.0; summary->line_search_gradient_evaluation_time_in_seconds = 0.0; summary->line_search_polynomial_minimization_time_in_seconds = 0.0; @@ -495,21 +496,28 @@ void Solver::Solve(const Solver::Options& options, // values provided by the user. program->SetParameterBlockStatePtrsToUserStatePtrs(); + // If gradient_checking is enabled, wrap all cost functions in a + // gradient checker and install a callback that terminates if any gradient + // error is detected. scoped_ptr<internal::ProblemImpl> gradient_checking_problem; + internal::GradientCheckingIterationCallback gradient_checking_callback; + Solver::Options modified_options = options; if (options.check_gradients) { + modified_options.callbacks.push_back(&gradient_checking_callback); gradient_checking_problem.reset( CreateGradientCheckingProblemImpl( problem_impl, - options.numeric_derivative_relative_step_size, - options.gradient_check_relative_precision)); + options.gradient_check_numeric_derivative_relative_step_size, + options.gradient_check_relative_precision, + &gradient_checking_callback)); problem_impl = gradient_checking_problem.get(); program = problem_impl->mutable_program(); } scoped_ptr<Preprocessor> preprocessor( - Preprocessor::Create(options.minimizer_type)); + Preprocessor::Create(modified_options.minimizer_type)); PreprocessedProblem pp; - const bool status = preprocessor->Preprocess(options, problem_impl, &pp); + const bool status = preprocessor->Preprocess(modified_options, problem_impl, &pp); summary->fixed_cost = pp.fixed_cost; summary->preprocessor_time_in_seconds = WallTimeInSeconds() - start_time; @@ -534,6 +542,13 @@ void Solver::Solve(const Solver::Options& options, summary->postprocessor_time_in_seconds = WallTimeInSeconds() - postprocessor_start_time; + // If the gradient checker reported an error, we want to report FAILURE + // instead of USER_FAILURE and provide the error log. + if (gradient_checking_callback.gradient_error_detected()) { + summary->termination_type = FAILURE; + summary->message = gradient_checking_callback.error_log(); + } + summary->total_time_in_seconds = WallTimeInSeconds() - start_time; } @@ -556,6 +571,7 @@ Solver::Summary::Summary() num_successful_steps(-1), num_unsuccessful_steps(-1), num_inner_iteration_steps(-1), + num_line_search_steps(-1), preprocessor_time_in_seconds(-1.0), minimizer_time_in_seconds(-1.0), postprocessor_time_in_seconds(-1.0), @@ -696,16 +712,14 @@ string Solver::Summary::FullReport() const { num_linear_solver_threads_given, num_linear_solver_threads_used); - if (IsSchurType(linear_solver_type_used)) { - string given; - StringifyOrdering(linear_solver_ordering_given, &given); - string used; - StringifyOrdering(linear_solver_ordering_used, &used); - StringAppendF(&report, - "Linear solver ordering %22s %24s\n", - given.c_str(), - used.c_str()); - } + string given; + StringifyOrdering(linear_solver_ordering_given, &given); + string used; + StringifyOrdering(linear_solver_ordering_used, &used); + StringAppendF(&report, + "Linear solver ordering %22s %24s\n", + given.c_str(), + used.c_str()); if (inner_iterations_given) { StringAppendF(&report, @@ -784,9 +798,14 @@ string Solver::Summary::FullReport() const { num_inner_iteration_steps); } - const bool print_line_search_timing_information = - minimizer_type == LINE_SEARCH || - (minimizer_type == TRUST_REGION && is_constrained); + const bool line_search_used = + (minimizer_type == LINE_SEARCH || + (minimizer_type == TRUST_REGION && is_constrained)); + + if (line_search_used) { + StringAppendF(&report, "Line search steps % 14d\n", + num_line_search_steps); + } StringAppendF(&report, "\nTime (in seconds):\n"); StringAppendF(&report, "Preprocessor %25.4f\n", @@ -794,13 +813,13 @@ string Solver::Summary::FullReport() const { StringAppendF(&report, "\n Residual evaluation %23.4f\n", residual_evaluation_time_in_seconds); - if (print_line_search_timing_information) { + if (line_search_used) { StringAppendF(&report, " Line search cost evaluation %10.4f\n", line_search_cost_evaluation_time_in_seconds); } StringAppendF(&report, " Jacobian evaluation %23.4f\n", jacobian_evaluation_time_in_seconds); - if (print_line_search_timing_information) { + if (line_search_used) { StringAppendF(&report, " Line search gradient evaluation %6.4f\n", line_search_gradient_evaluation_time_in_seconds); } @@ -815,7 +834,7 @@ string Solver::Summary::FullReport() const { inner_iteration_time_in_seconds); } - if (print_line_search_timing_information) { + if (line_search_used) { StringAppendF(&report, " Line search polynomial minimization %.4f\n", line_search_polynomial_minimization_time_in_seconds); } diff --git a/extern/ceres/internal/ceres/sparse_normal_cholesky_solver.cc b/extern/ceres/internal/ceres/sparse_normal_cholesky_solver.cc index ed00879b47a..a4c2c766ddc 100644 --- a/extern/ceres/internal/ceres/sparse_normal_cholesky_solver.cc +++ b/extern/ceres/internal/ceres/sparse_normal_cholesky_solver.cc @@ -33,6 +33,7 @@ #include <algorithm> #include <cstring> #include <ctime> +#include <sstream> #include "ceres/compressed_row_sparse_matrix.h" #include "ceres/cxsparse.h" @@ -71,6 +72,12 @@ LinearSolver::Summary SimplicialLDLTSolve( if (do_symbolic_analysis) { solver->analyzePattern(lhs); + if (VLOG_IS_ON(2)) { + std::stringstream ss; + solver->dumpMemory(ss); + VLOG(2) << "Symbolic Analysis\n" + << ss.str(); + } event_logger->AddEvent("Analyze"); if (solver->info() != Eigen::Success) { summary.termination_type = LINEAR_SOLVER_FATAL_ERROR; diff --git a/extern/ceres/internal/ceres/stringprintf.cc b/extern/ceres/internal/ceres/stringprintf.cc index d1d8b5fe8ab..b3b7474d8f8 100644 --- a/extern/ceres/internal/ceres/stringprintf.cc +++ b/extern/ceres/internal/ceres/stringprintf.cc @@ -43,14 +43,27 @@ namespace internal { using std::string; -#ifdef _MSC_VER -enum { IS_COMPILER_MSVC = 1 }; -#if _MSC_VER < 1800 -#define va_copy(d, s) ((d) = (s)) -#endif +// va_copy() was defined in the C99 standard. However, it did not appear in the +// C++ standard until C++11. This means that if Ceres is being compiled with a +// strict pre-C++11 standard (e.g. -std=c++03), va_copy() will NOT be defined, +// as we are using the C++ compiler (it would however be defined if we were +// using the C compiler). Note however that both GCC & Clang will in fact +// define va_copy() when compiling for C++ if the C++ standard is not explicitly +// specified (i.e. no -std=c++<XX> arg), even though it should not strictly be +// defined unless -std=c++11 (or greater) was passed. +#if !defined(va_copy) +#if defined (__GNUC__) +// On GCC/Clang, if va_copy() is not defined (C++ standard < C++11 explicitly +// specified), use the internal __va_copy() version, which should be present +// in even very old GCC versions. +#define va_copy(d, s) __va_copy(d, s) #else -enum { IS_COMPILER_MSVC = 0 }; -#endif +// Some older versions of MSVC do not have va_copy(), in which case define it. +// Although this is required for older MSVC versions, it should also work for +// other non-GCC/Clang compilers which also do not defined va_copy(). +#define va_copy(d, s) ((d) = (s)) +#endif // defined (__GNUC__) +#endif // !defined(va_copy) void StringAppendV(string* dst, const char* format, va_list ap) { // First try with a small fixed size buffer @@ -71,13 +84,13 @@ void StringAppendV(string* dst, const char* format, va_list ap) { return; } - if (IS_COMPILER_MSVC) { - // Error or MSVC running out of space. MSVC 8.0 and higher - // can be asked about space needed with the special idiom below: - va_copy(backup_ap, ap); - result = vsnprintf(NULL, 0, format, backup_ap); - va_end(backup_ap); - } +#if defined (_MSC_VER) + // Error or MSVC running out of space. MSVC 8.0 and higher + // can be asked about space needed with the special idiom below: + va_copy(backup_ap, ap); + result = vsnprintf(NULL, 0, format, backup_ap); + va_end(backup_ap); +#endif if (result < 0) { // Just an error. diff --git a/extern/ceres/internal/ceres/trust_region_minimizer.cc b/extern/ceres/internal/ceres/trust_region_minimizer.cc index d654d0867f1..d809906ab54 100644 --- a/extern/ceres/internal/ceres/trust_region_minimizer.cc +++ b/extern/ceres/internal/ceres/trust_region_minimizer.cc @@ -1,5 +1,5 @@ // Ceres Solver - A fast non-linear least squares minimizer -// Copyright 2015 Google Inc. All rights reserved. +// Copyright 2016 Google Inc. All rights reserved. // http://ceres-solver.org/ // // Redistribution and use in source and binary forms, with or without @@ -43,674 +43,747 @@ #include "ceres/coordinate_descent_minimizer.h" #include "ceres/evaluator.h" #include "ceres/file.h" -#include "ceres/internal/eigen.h" -#include "ceres/internal/scoped_ptr.h" #include "ceres/line_search.h" -#include "ceres/linear_least_squares_problems.h" -#include "ceres/sparse_matrix.h" #include "ceres/stringprintf.h" -#include "ceres/trust_region_strategy.h" #include "ceres/types.h" #include "ceres/wall_time.h" #include "glog/logging.h" +// Helper macro to simplify some of the control flow. +#define RETURN_IF_ERROR_AND_LOG(expr) \ + do { \ + if (!(expr)) { \ + LOG(ERROR) << "Terminating: " << solver_summary_->message; \ + return; \ + } \ + } while (0) + namespace ceres { namespace internal { -namespace { -LineSearch::Summary DoLineSearch(const Minimizer::Options& options, - const Vector& x, - const Vector& gradient, - const double cost, - const Vector& delta, - Evaluator* evaluator) { - LineSearchFunction line_search_function(evaluator); +TrustRegionMinimizer::~TrustRegionMinimizer() {} - LineSearch::Options line_search_options; - line_search_options.is_silent = true; - line_search_options.interpolation_type = - options.line_search_interpolation_type; - line_search_options.min_step_size = options.min_line_search_step_size; - line_search_options.sufficient_decrease = - options.line_search_sufficient_function_decrease; - line_search_options.max_step_contraction = - options.max_line_search_step_contraction; - line_search_options.min_step_contraction = - options.min_line_search_step_contraction; - line_search_options.max_num_iterations = - options.max_num_line_search_step_size_iterations; - line_search_options.sufficient_curvature_decrease = - options.line_search_sufficient_curvature_decrease; - line_search_options.max_step_expansion = - options.max_line_search_step_expansion; - line_search_options.function = &line_search_function; +void TrustRegionMinimizer::Minimize(const Minimizer::Options& options, + double* parameters, + Solver::Summary* solver_summary) { + start_time_in_secs_ = WallTimeInSeconds(); + iteration_start_time_in_secs_ = start_time_in_secs_; + Init(options, parameters, solver_summary); + RETURN_IF_ERROR_AND_LOG(IterationZero()); + + // Create the TrustRegionStepEvaluator. The construction needs to be + // delayed to this point because we need the cost for the starting + // point to initialize the step evaluator. + step_evaluator_.reset(new TrustRegionStepEvaluator( + x_cost_, + options_.use_nonmonotonic_steps + ? options_.max_consecutive_nonmonotonic_steps + : 0)); + + while (FinalizeIterationAndCheckIfMinimizerCanContinue()) { + iteration_start_time_in_secs_ = WallTimeInSeconds(); + iteration_summary_ = IterationSummary(); + iteration_summary_.iteration = + solver_summary->iterations.back().iteration + 1; + + RETURN_IF_ERROR_AND_LOG(ComputeTrustRegionStep()); + if (!iteration_summary_.step_is_valid) { + RETURN_IF_ERROR_AND_LOG(HandleInvalidStep()); + continue; + } - std::string message; - scoped_ptr<LineSearch> line_search( - CHECK_NOTNULL(LineSearch::Create(ceres::ARMIJO, - line_search_options, - &message))); - LineSearch::Summary summary; - line_search_function.Init(x, delta); - line_search->Search(1.0, cost, gradient.dot(delta), &summary); - return summary; -} + if (options_.is_constrained) { + // Use a projected line search to enforce the bounds constraints + // and improve the quality of the step. + DoLineSearch(x_, gradient_, x_cost_, &delta_); + } + + ComputeCandidatePointAndEvaluateCost(); + DoInnerIterationsIfNeeded(); -} // namespace + if (ParameterToleranceReached()) { + return; + } + + if (FunctionToleranceReached()) { + return; + } -// Compute a scaling vector that is used to improve the conditioning -// of the Jacobian. -void TrustRegionMinimizer::EstimateScale(const SparseMatrix& jacobian, - double* scale) const { - jacobian.SquaredColumnNorm(scale); - for (int i = 0; i < jacobian.num_cols(); ++i) { - scale[i] = 1.0 / (1.0 + sqrt(scale[i])); + if (IsStepSuccessful()) { + RETURN_IF_ERROR_AND_LOG(HandleSuccessfulStep()); + continue; + } + + HandleUnsuccessfulStep(); } } -void TrustRegionMinimizer::Init(const Minimizer::Options& options) { +// Initialize the minimizer, allocate working space and set some of +// the fields in the solver_summary. +void TrustRegionMinimizer::Init(const Minimizer::Options& options, + double* parameters, + Solver::Summary* solver_summary) { options_ = options; sort(options_.trust_region_minimizer_iterations_to_dump.begin(), options_.trust_region_minimizer_iterations_to_dump.end()); + + parameters_ = parameters; + + solver_summary_ = solver_summary; + solver_summary_->termination_type = NO_CONVERGENCE; + solver_summary_->num_successful_steps = 0; + solver_summary_->num_unsuccessful_steps = 0; + solver_summary_->is_constrained = options.is_constrained; + + evaluator_ = CHECK_NOTNULL(options_.evaluator.get()); + jacobian_ = CHECK_NOTNULL(options_.jacobian.get()); + strategy_ = CHECK_NOTNULL(options_.trust_region_strategy.get()); + + is_not_silent_ = !options.is_silent; + inner_iterations_are_enabled_ = + options.inner_iteration_minimizer.get() != NULL; + inner_iterations_were_useful_ = false; + + num_parameters_ = evaluator_->NumParameters(); + num_effective_parameters_ = evaluator_->NumEffectiveParameters(); + num_residuals_ = evaluator_->NumResiduals(); + num_consecutive_invalid_steps_ = 0; + + x_ = ConstVectorRef(parameters_, num_parameters_); + x_norm_ = x_.norm(); + residuals_.resize(num_residuals_); + trust_region_step_.resize(num_effective_parameters_); + delta_.resize(num_effective_parameters_); + candidate_x_.resize(num_parameters_); + gradient_.resize(num_effective_parameters_); + model_residuals_.resize(num_residuals_); + negative_gradient_.resize(num_effective_parameters_); + projected_gradient_step_.resize(num_parameters_); + + // By default scaling is one, if the user requests Jacobi scaling of + // the Jacobian, we will compute and overwrite this vector. + jacobian_scaling_ = Vector::Ones(num_effective_parameters_); + + x_norm_ = -1; // Invalid value + x_cost_ = std::numeric_limits<double>::max(); + minimum_cost_ = x_cost_; + model_cost_change_ = 0.0; } -void TrustRegionMinimizer::Minimize(const Minimizer::Options& options, - double* parameters, - Solver::Summary* summary) { - double start_time = WallTimeInSeconds(); - double iteration_start_time = start_time; - Init(options); - - Evaluator* evaluator = CHECK_NOTNULL(options_.evaluator.get()); - SparseMatrix* jacobian = CHECK_NOTNULL(options_.jacobian.get()); - TrustRegionStrategy* strategy = - CHECK_NOTNULL(options_.trust_region_strategy.get()); - - const bool is_not_silent = !options.is_silent; - - // If the problem is bounds constrained, then enable the use of a - // line search after the trust region step has been computed. This - // line search will automatically use a projected test point onto - // the feasible set, there by guaranteeing the feasibility of the - // final output. - // - // TODO(sameeragarwal): Make line search available more generally. - const bool use_line_search = options.is_constrained; - - summary->termination_type = NO_CONVERGENCE; - summary->num_successful_steps = 0; - summary->num_unsuccessful_steps = 0; - summary->is_constrained = options.is_constrained; - - const int num_parameters = evaluator->NumParameters(); - const int num_effective_parameters = evaluator->NumEffectiveParameters(); - const int num_residuals = evaluator->NumResiduals(); - - Vector residuals(num_residuals); - Vector trust_region_step(num_effective_parameters); - Vector delta(num_effective_parameters); - Vector x_plus_delta(num_parameters); - Vector gradient(num_effective_parameters); - Vector model_residuals(num_residuals); - Vector scale(num_effective_parameters); - Vector negative_gradient(num_effective_parameters); - Vector projected_gradient_step(num_parameters); - - IterationSummary iteration_summary; - iteration_summary.iteration = 0; - iteration_summary.step_is_valid = false; - iteration_summary.step_is_successful = false; - iteration_summary.cost_change = 0.0; - iteration_summary.gradient_max_norm = 0.0; - iteration_summary.gradient_norm = 0.0; - iteration_summary.step_norm = 0.0; - iteration_summary.relative_decrease = 0.0; - iteration_summary.trust_region_radius = strategy->Radius(); - iteration_summary.eta = options_.eta; - iteration_summary.linear_solver_iterations = 0; - iteration_summary.step_solver_time_in_seconds = 0; - - VectorRef x_min(parameters, num_parameters); - Vector x = x_min; - // Project onto the feasible set. - if (options.is_constrained) { - delta.setZero(); - if (!evaluator->Plus(x.data(), delta.data(), x_plus_delta.data())) { - summary->message = +// 1. Project the initial solution onto the feasible set if needed. +// 2. Compute the initial cost, jacobian & gradient. +// +// Return true if all computations can be performed successfully. +bool TrustRegionMinimizer::IterationZero() { + iteration_summary_ = IterationSummary(); + iteration_summary_.iteration = 0; + iteration_summary_.step_is_valid = false; + iteration_summary_.step_is_successful = false; + iteration_summary_.cost_change = 0.0; + iteration_summary_.gradient_max_norm = 0.0; + iteration_summary_.gradient_norm = 0.0; + iteration_summary_.step_norm = 0.0; + iteration_summary_.relative_decrease = 0.0; + iteration_summary_.eta = options_.eta; + iteration_summary_.linear_solver_iterations = 0; + iteration_summary_.step_solver_time_in_seconds = 0; + + if (options_.is_constrained) { + delta_.setZero(); + if (!evaluator_->Plus(x_.data(), delta_.data(), candidate_x_.data())) { + solver_summary_->message = "Unable to project initial point onto the feasible set."; - summary->termination_type = FAILURE; - LOG_IF(WARNING, is_not_silent) << "Terminating: " << summary->message; - return; + solver_summary_->termination_type = FAILURE; + return false; } - x_min = x_plus_delta; - x = x_plus_delta; - } - double x_norm = x.norm(); - - // Do initial cost and Jacobian evaluation. - double cost = 0.0; - if (!evaluator->Evaluate(x.data(), - &cost, - residuals.data(), - gradient.data(), - jacobian)) { - summary->message = "Residual and Jacobian evaluation failed."; - summary->termination_type = FAILURE; - LOG_IF(WARNING, is_not_silent) << "Terminating: " << summary->message; - return; + x_ = candidate_x_; + x_norm_ = x_.norm(); } - negative_gradient = -gradient; - if (!evaluator->Plus(x.data(), - negative_gradient.data(), - projected_gradient_step.data())) { - summary->message = "Unable to compute gradient step."; - summary->termination_type = FAILURE; - LOG(ERROR) << "Terminating: " << summary->message; - return; + if (!EvaluateGradientAndJacobian()) { + return false; } - summary->initial_cost = cost + summary->fixed_cost; - iteration_summary.cost = cost + summary->fixed_cost; - iteration_summary.gradient_max_norm = - (x - projected_gradient_step).lpNorm<Eigen::Infinity>(); - iteration_summary.gradient_norm = (x - projected_gradient_step).norm(); - - if (iteration_summary.gradient_max_norm <= options.gradient_tolerance) { - summary->message = StringPrintf("Gradient tolerance reached. " - "Gradient max norm: %e <= %e", - iteration_summary.gradient_max_norm, - options_.gradient_tolerance); - summary->termination_type = CONVERGENCE; - VLOG_IF(1, is_not_silent) << "Terminating: " << summary->message; - - // Ensure that there is an iteration summary object for iteration - // 0 in Summary::iterations. - iteration_summary.iteration_time_in_seconds = - WallTimeInSeconds() - iteration_start_time; - iteration_summary.cumulative_time_in_seconds = - WallTimeInSeconds() - start_time + - summary->preprocessor_time_in_seconds; - summary->iterations.push_back(iteration_summary); - return; - } + solver_summary_->initial_cost = x_cost_ + solver_summary_->fixed_cost; + iteration_summary_.step_is_valid = true; + iteration_summary_.step_is_successful = true; + return true; +} - if (options_.jacobi_scaling) { - EstimateScale(*jacobian, scale.data()); - jacobian->ScaleColumns(scale.data()); - } else { - scale.setOnes(); +// For the current x_, compute +// +// 1. Cost +// 2. Jacobian +// 3. Gradient +// 4. Scale the Jacobian if needed (and compute the scaling if we are +// in iteration zero). +// 5. Compute the 2 and max norm of the gradient. +// +// Returns true if all computations could be performed +// successfully. Any failures are considered fatal and the +// Solver::Summary is updated to indicate this. +bool TrustRegionMinimizer::EvaluateGradientAndJacobian() { + if (!evaluator_->Evaluate(x_.data(), + &x_cost_, + residuals_.data(), + gradient_.data(), + jacobian_)) { + solver_summary_->message = "Residual and Jacobian evaluation failed."; + solver_summary_->termination_type = FAILURE; + return false; } - iteration_summary.iteration_time_in_seconds = - WallTimeInSeconds() - iteration_start_time; - iteration_summary.cumulative_time_in_seconds = - WallTimeInSeconds() - start_time - + summary->preprocessor_time_in_seconds; - summary->iterations.push_back(iteration_summary); - - int num_consecutive_nonmonotonic_steps = 0; - double minimum_cost = cost; - double reference_cost = cost; - double accumulated_reference_model_cost_change = 0.0; - double candidate_cost = cost; - double accumulated_candidate_model_cost_change = 0.0; - int num_consecutive_invalid_steps = 0; - bool inner_iterations_are_enabled = - options.inner_iteration_minimizer.get() != NULL; - while (true) { - bool inner_iterations_were_useful = false; - if (!RunCallbacks(options, iteration_summary, summary)) { - return; - } + iteration_summary_.cost = x_cost_ + solver_summary_->fixed_cost; - iteration_start_time = WallTimeInSeconds(); - if (iteration_summary.iteration >= options_.max_num_iterations) { - summary->message = "Maximum number of iterations reached."; - summary->termination_type = NO_CONVERGENCE; - VLOG_IF(1, is_not_silent) << "Terminating: " << summary->message; - return; + if (options_.jacobi_scaling) { + if (iteration_summary_.iteration == 0) { + // Compute a scaling vector that is used to improve the + // conditioning of the Jacobian. + // + // jacobian_scaling_ = diag(J'J)^{-1} + jacobian_->SquaredColumnNorm(jacobian_scaling_.data()); + for (int i = 0; i < jacobian_->num_cols(); ++i) { + // Add one to the denominator to prevent division by zero. + jacobian_scaling_[i] = 1.0 / (1.0 + sqrt(jacobian_scaling_[i])); + } } - const double total_solver_time = iteration_start_time - start_time + - summary->preprocessor_time_in_seconds; - if (total_solver_time >= options_.max_solver_time_in_seconds) { - summary->message = "Maximum solver time reached."; - summary->termination_type = NO_CONVERGENCE; - VLOG_IF(1, is_not_silent) << "Terminating: " << summary->message; - return; - } + // jacobian = jacobian * diag(J'J) ^{-1} + jacobian_->ScaleColumns(jacobian_scaling_.data()); + } + + // The gradient exists in the local tangent space. To account for + // the bounds constraints correctly, instead of just computing the + // norm of the gradient vector, we compute + // + // |Plus(x, -gradient) - x| + // + // Where the Plus operator lifts the negative gradient to the + // ambient space, adds it to x and projects it on the hypercube + // defined by the bounds. + negative_gradient_ = -gradient_; + if (!evaluator_->Plus(x_.data(), + negative_gradient_.data(), + projected_gradient_step_.data())) { + solver_summary_->message = + "projected_gradient_step = Plus(x, -gradient) failed."; + solver_summary_->termination_type = FAILURE; + return false; + } - const double strategy_start_time = WallTimeInSeconds(); - TrustRegionStrategy::PerSolveOptions per_solve_options; - per_solve_options.eta = options_.eta; - if (find(options_.trust_region_minimizer_iterations_to_dump.begin(), - options_.trust_region_minimizer_iterations_to_dump.end(), - iteration_summary.iteration) != - options_.trust_region_minimizer_iterations_to_dump.end()) { - per_solve_options.dump_format_type = - options_.trust_region_problem_dump_format_type; - per_solve_options.dump_filename_base = - JoinPath(options_.trust_region_problem_dump_directory, - StringPrintf("ceres_solver_iteration_%03d", - iteration_summary.iteration)); + iteration_summary_.gradient_max_norm = + (x_ - projected_gradient_step_).lpNorm<Eigen::Infinity>(); + iteration_summary_.gradient_norm = (x_ - projected_gradient_step_).norm(); + return true; +} + +// 1. Add the final timing information to the iteration summary. +// 2. Run the callbacks +// 3. Check for termination based on +// a. Run time +// b. Iteration count +// c. Max norm of the gradient +// d. Size of the trust region radius. +// +// Returns true if user did not terminate the solver and none of these +// termination criterion are met. +bool TrustRegionMinimizer::FinalizeIterationAndCheckIfMinimizerCanContinue() { + if (iteration_summary_.step_is_successful) { + ++solver_summary_->num_successful_steps; + if (x_cost_ < minimum_cost_) { + minimum_cost_ = x_cost_; + VectorRef(parameters_, num_parameters_) = x_; + iteration_summary_.step_is_nonmonotonic = false; } else { - per_solve_options.dump_format_type = TEXTFILE; - per_solve_options.dump_filename_base.clear(); + iteration_summary_.step_is_nonmonotonic = true; } + } else { + ++solver_summary_->num_unsuccessful_steps; + } - TrustRegionStrategy::Summary strategy_summary = - strategy->ComputeStep(per_solve_options, - jacobian, - residuals.data(), - trust_region_step.data()); - - if (strategy_summary.termination_type == LINEAR_SOLVER_FATAL_ERROR) { - summary->message = - "Linear solver failed due to unrecoverable " - "non-numeric causes. Please see the error log for clues. "; - summary->termination_type = FAILURE; - LOG_IF(WARNING, is_not_silent) << "Terminating: " << summary->message; - return; - } + iteration_summary_.trust_region_radius = strategy_->Radius(); + iteration_summary_.iteration_time_in_seconds = + WallTimeInSeconds() - iteration_start_time_in_secs_; + iteration_summary_.cumulative_time_in_seconds = + WallTimeInSeconds() - start_time_in_secs_ + + solver_summary_->preprocessor_time_in_seconds; - iteration_summary = IterationSummary(); - iteration_summary.iteration = summary->iterations.back().iteration + 1; - iteration_summary.step_solver_time_in_seconds = - WallTimeInSeconds() - strategy_start_time; - iteration_summary.linear_solver_iterations = - strategy_summary.num_iterations; - iteration_summary.step_is_valid = false; - iteration_summary.step_is_successful = false; - - double model_cost_change = 0.0; - if (strategy_summary.termination_type != LINEAR_SOLVER_FAILURE) { - // new_model_cost - // = 1/2 [f + J * step]^2 - // = 1/2 [ f'f + 2f'J * step + step' * J' * J * step ] - // model_cost_change - // = cost - new_model_cost - // = f'f/2 - 1/2 [ f'f + 2f'J * step + step' * J' * J * step] - // = -f'J * step - step' * J' * J * step / 2 - model_residuals.setZero(); - jacobian->RightMultiply(trust_region_step.data(), model_residuals.data()); - model_cost_change = - - model_residuals.dot(residuals + model_residuals / 2.0); - - if (model_cost_change < 0.0) { - VLOG_IF(1, is_not_silent) - << "Invalid step: current_cost: " << cost - << " absolute difference " << model_cost_change - << " relative difference " << (model_cost_change / cost); - } else { - iteration_summary.step_is_valid = true; - } - } + solver_summary_->iterations.push_back(iteration_summary_); - if (!iteration_summary.step_is_valid) { - // Invalid steps can happen due to a number of reasons, and we - // allow a limited number of successive failures, and return with - // FAILURE if this limit is exceeded. - if (++num_consecutive_invalid_steps >= - options_.max_num_consecutive_invalid_steps) { - summary->message = StringPrintf( - "Number of successive invalid steps more " - "than Solver::Options::max_num_consecutive_invalid_steps: %d", - options_.max_num_consecutive_invalid_steps); - summary->termination_type = FAILURE; - LOG_IF(WARNING, is_not_silent) << "Terminating: " << summary->message; - return; - } + if (!RunCallbacks(options_, iteration_summary_, solver_summary_)) { + return false; + } - // We are going to try and reduce the trust region radius and - // solve again. To do this, we are going to treat this iteration - // as an unsuccessful iteration. Since the various callbacks are - // still executed, we are going to fill the iteration summary - // with data that assumes a step of length zero and no progress. - iteration_summary.cost = cost + summary->fixed_cost; - iteration_summary.cost_change = 0.0; - iteration_summary.gradient_max_norm = - summary->iterations.back().gradient_max_norm; - iteration_summary.gradient_norm = - summary->iterations.back().gradient_norm; - iteration_summary.step_norm = 0.0; - iteration_summary.relative_decrease = 0.0; - iteration_summary.eta = options_.eta; - } else { - // The step is numerically valid, so now we can judge its quality. - num_consecutive_invalid_steps = 0; + if (MaxSolverTimeReached()) { + return false; + } - // Undo the Jacobian column scaling. - delta = (trust_region_step.array() * scale.array()).matrix(); + if (MaxSolverIterationsReached()) { + return false; + } - // Try improving the step further by using an ARMIJO line - // search. - // - // TODO(sameeragarwal): What happens to trust region sizing as - // it interacts with the line search ? - if (use_line_search) { - const LineSearch::Summary line_search_summary = - DoLineSearch(options, x, gradient, cost, delta, evaluator); - - summary->line_search_cost_evaluation_time_in_seconds += - line_search_summary.cost_evaluation_time_in_seconds; - summary->line_search_gradient_evaluation_time_in_seconds += - line_search_summary.gradient_evaluation_time_in_seconds; - summary->line_search_polynomial_minimization_time_in_seconds += - line_search_summary.polynomial_minimization_time_in_seconds; - summary->line_search_total_time_in_seconds += - line_search_summary.total_time_in_seconds; - - if (line_search_summary.success) { - delta *= line_search_summary.optimal_step_size; - } - } + if (GradientToleranceReached()) { + return false; + } - double new_cost = std::numeric_limits<double>::max(); - if (evaluator->Plus(x.data(), delta.data(), x_plus_delta.data())) { - if (!evaluator->Evaluate(x_plus_delta.data(), - &new_cost, - NULL, - NULL, - NULL)) { - LOG_IF(WARNING, is_not_silent) - << "Step failed to evaluate. " - << "Treating it as a step with infinite cost"; - new_cost = std::numeric_limits<double>::max(); - } - } else { - LOG_IF(WARNING, is_not_silent) - << "x_plus_delta = Plus(x, delta) failed. " - << "Treating it as a step with infinite cost"; - } + if (MinTrustRegionRadiusReached()) { + return false; + } - if (new_cost < std::numeric_limits<double>::max()) { - // Check if performing an inner iteration will make it better. - if (inner_iterations_are_enabled) { - ++summary->num_inner_iteration_steps; - double inner_iteration_start_time = WallTimeInSeconds(); - const double x_plus_delta_cost = new_cost; - Vector inner_iteration_x = x_plus_delta; - Solver::Summary inner_iteration_summary; - options.inner_iteration_minimizer->Minimize(options, - inner_iteration_x.data(), - &inner_iteration_summary); - if (!evaluator->Evaluate(inner_iteration_x.data(), - &new_cost, - NULL, NULL, NULL)) { - VLOG_IF(2, is_not_silent) << "Inner iteration failed."; - new_cost = x_plus_delta_cost; - } else { - x_plus_delta = inner_iteration_x; - // Boost the model_cost_change, since the inner iteration - // improvements are not accounted for by the trust region. - model_cost_change += x_plus_delta_cost - new_cost; - VLOG_IF(2, is_not_silent) - << "Inner iteration succeeded; Current cost: " << cost - << " Trust region step cost: " << x_plus_delta_cost - << " Inner iteration cost: " << new_cost; - - inner_iterations_were_useful = new_cost < cost; - - const double inner_iteration_relative_progress = - 1.0 - new_cost / x_plus_delta_cost; - // Disable inner iterations once the relative improvement - // drops below tolerance. - inner_iterations_are_enabled = - (inner_iteration_relative_progress > - options.inner_iteration_tolerance); - VLOG_IF(2, is_not_silent && !inner_iterations_are_enabled) - << "Disabling inner iterations. Progress : " - << inner_iteration_relative_progress; - } - summary->inner_iteration_time_in_seconds += - WallTimeInSeconds() - inner_iteration_start_time; - } - } + return true; +} - iteration_summary.step_norm = (x - x_plus_delta).norm(); - - // Convergence based on parameter_tolerance. - const double step_size_tolerance = options_.parameter_tolerance * - (x_norm + options_.parameter_tolerance); - if (iteration_summary.step_norm <= step_size_tolerance) { - summary->message = - StringPrintf("Parameter tolerance reached. " - "Relative step_norm: %e <= %e.", - (iteration_summary.step_norm / - (x_norm + options_.parameter_tolerance)), - options_.parameter_tolerance); - summary->termination_type = CONVERGENCE; - VLOG_IF(1, is_not_silent) << "Terminating: " << summary->message; - return; - } +// Compute the trust region step using the TrustRegionStrategy chosen +// by the user. +// +// If the strategy returns with LINEAR_SOLVER_FATAL_ERROR, which +// indicates an unrecoverable error, return false. This is the only +// condition that returns false. +// +// If the strategy returns with LINEAR_SOLVER_FAILURE, which indicates +// a numerical failure that could be recovered from by retrying +// (e.g. by increasing the strength of the regularization), we set +// iteration_summary_.step_is_valid to false and return true. +// +// In all other cases, we compute the decrease in the trust region +// model problem. In exact arithmetic, this should always be +// positive, but due to numerical problems in the TrustRegionStrategy +// or round off error when computing the decrease it may be +// negative. In which case again, we set +// iteration_summary_.step_is_valid to false. +bool TrustRegionMinimizer::ComputeTrustRegionStep() { + const double strategy_start_time = WallTimeInSeconds(); + iteration_summary_.step_is_valid = false; + TrustRegionStrategy::PerSolveOptions per_solve_options; + per_solve_options.eta = options_.eta; + if (find(options_.trust_region_minimizer_iterations_to_dump.begin(), + options_.trust_region_minimizer_iterations_to_dump.end(), + iteration_summary_.iteration) != + options_.trust_region_minimizer_iterations_to_dump.end()) { + per_solve_options.dump_format_type = + options_.trust_region_problem_dump_format_type; + per_solve_options.dump_filename_base = + JoinPath(options_.trust_region_problem_dump_directory, + StringPrintf("ceres_solver_iteration_%03d", + iteration_summary_.iteration)); + } - iteration_summary.cost_change = cost - new_cost; - const double absolute_function_tolerance = - options_.function_tolerance * cost; - if (fabs(iteration_summary.cost_change) <= absolute_function_tolerance) { - summary->message = - StringPrintf("Function tolerance reached. " - "|cost_change|/cost: %e <= %e", - fabs(iteration_summary.cost_change) / cost, - options_.function_tolerance); - summary->termination_type = CONVERGENCE; - VLOG_IF(1, is_not_silent) << "Terminating: " << summary->message; - return; - } + TrustRegionStrategy::Summary strategy_summary = + strategy_->ComputeStep(per_solve_options, + jacobian_, + residuals_.data(), + trust_region_step_.data()); + + if (strategy_summary.termination_type == LINEAR_SOLVER_FATAL_ERROR) { + solver_summary_->message = + "Linear solver failed due to unrecoverable " + "non-numeric causes. Please see the error log for clues. "; + solver_summary_->termination_type = FAILURE; + return false; + } - const double relative_decrease = - iteration_summary.cost_change / model_cost_change; + iteration_summary_.step_solver_time_in_seconds = + WallTimeInSeconds() - strategy_start_time; + iteration_summary_.linear_solver_iterations = strategy_summary.num_iterations; - const double historical_relative_decrease = - (reference_cost - new_cost) / - (accumulated_reference_model_cost_change + model_cost_change); + if (strategy_summary.termination_type == LINEAR_SOLVER_FAILURE) { + return true; + } - // If monotonic steps are being used, then the relative_decrease - // is the usual ratio of the change in objective function value - // divided by the change in model cost. - // - // If non-monotonic steps are allowed, then we take the maximum - // of the relative_decrease and the - // historical_relative_decrease, which measures the increase - // from a reference iteration. The model cost change is - // estimated by accumulating the model cost changes since the - // reference iteration. The historical relative_decrease offers - // a boost to a step which is not too bad compared to the - // reference iteration, allowing for non-monotonic steps. - iteration_summary.relative_decrease = - options.use_nonmonotonic_steps - ? std::max(relative_decrease, historical_relative_decrease) - : relative_decrease; - - // Normally, the quality of a trust region step is measured by - // the ratio - // - // cost_change - // r = ----------------- - // model_cost_change - // - // All the change in the nonlinear objective is due to the trust - // region step so this ratio is a good measure of the quality of - // the trust region radius. However, when inner iterations are - // being used, cost_change includes the contribution of the - // inner iterations and its not fair to credit it all to the - // trust region algorithm. So we change the ratio to be - // - // cost_change - // r = ------------------------------------------------ - // (model_cost_change + inner_iteration_cost_change) - // - // In most cases this is fine, but it can be the case that the - // change in solution quality due to inner iterations is so large - // and the trust region step is so bad, that this ratio can become - // quite small. - // - // This can cause the trust region loop to reject this step. To - // get around this, we expicitly check if the inner iterations - // led to a net decrease in the objective function value. If - // they did, we accept the step even if the trust region ratio - // is small. - // - // Notice that we do not just check that cost_change is positive - // which is a weaker condition and would render the - // min_relative_decrease threshold useless. Instead, we keep - // track of inner_iterations_were_useful, which is true only - // when inner iterations lead to a net decrease in the cost. - iteration_summary.step_is_successful = - (inner_iterations_were_useful || - iteration_summary.relative_decrease > - options_.min_relative_decrease); - - if (iteration_summary.step_is_successful) { - accumulated_candidate_model_cost_change += model_cost_change; - accumulated_reference_model_cost_change += model_cost_change; - - if (!inner_iterations_were_useful && - relative_decrease <= options_.min_relative_decrease) { - iteration_summary.step_is_nonmonotonic = true; - VLOG_IF(2, is_not_silent) - << "Non-monotonic step! " - << " relative_decrease: " - << relative_decrease - << " historical_relative_decrease: " - << historical_relative_decrease; - } - } - } + // new_model_cost + // = 1/2 [f + J * step]^2 + // = 1/2 [ f'f + 2f'J * step + step' * J' * J * step ] + // model_cost_change + // = cost - new_model_cost + // = f'f/2 - 1/2 [ f'f + 2f'J * step + step' * J' * J * step] + // = -f'J * step - step' * J' * J * step / 2 + // = -(J * step)'(f + J * step / 2) + model_residuals_.setZero(); + jacobian_->RightMultiply(trust_region_step_.data(), model_residuals_.data()); + model_cost_change_ = + -model_residuals_.dot(residuals_ + model_residuals_ / 2.0); + + // TODO(sameeragarwal) + // + // 1. What happens if model_cost_change_ = 0 + // 2. What happens if -epsilon <= model_cost_change_ < 0 for some + // small epsilon due to round off error. + iteration_summary_.step_is_valid = (model_cost_change_ > 0.0); + if (iteration_summary_.step_is_valid) { + // Undo the Jacobian column scaling. + delta_ = (trust_region_step_.array() * jacobian_scaling_.array()).matrix(); + num_consecutive_invalid_steps_ = 0; + } - if (iteration_summary.step_is_successful) { - ++summary->num_successful_steps; - strategy->StepAccepted(iteration_summary.relative_decrease); - - x = x_plus_delta; - x_norm = x.norm(); - - // Step looks good, evaluate the residuals and Jacobian at this - // point. - if (!evaluator->Evaluate(x.data(), - &cost, - residuals.data(), - gradient.data(), - jacobian)) { - summary->message = "Residual and Jacobian evaluation failed."; - summary->termination_type = FAILURE; - LOG_IF(WARNING, is_not_silent) << "Terminating: " << summary->message; - return; - } + VLOG_IF(1, is_not_silent_ && !iteration_summary_.step_is_valid) + << "Invalid step: current_cost: " << x_cost_ + << " absolute model cost change: " << model_cost_change_ + << " relative model cost change: " << (model_cost_change_ / x_cost_); + return true; +} - negative_gradient = -gradient; - if (!evaluator->Plus(x.data(), - negative_gradient.data(), - projected_gradient_step.data())) { - summary->message = - "projected_gradient_step = Plus(x, -gradient) failed."; - summary->termination_type = FAILURE; - LOG(ERROR) << "Terminating: " << summary->message; - return; - } +// Invalid steps can happen due to a number of reasons, and we allow a +// limited number of consecutive failures, and return false if this +// limit is exceeded. +bool TrustRegionMinimizer::HandleInvalidStep() { + // TODO(sameeragarwal): Should we be returning FAILURE or + // NO_CONVERGENCE? The solution value is still usable in many cases, + // it is not clear if we should declare the solver a failure + // entirely. For example the case where model_cost_change ~ 0.0, but + // just slightly negative. + if (++num_consecutive_invalid_steps_ >= + options_.max_num_consecutive_invalid_steps) { + solver_summary_->message = StringPrintf( + "Number of consecutive invalid steps more " + "than Solver::Options::max_num_consecutive_invalid_steps: %d", + options_.max_num_consecutive_invalid_steps); + solver_summary_->termination_type = FAILURE; + return false; + } - iteration_summary.gradient_max_norm = - (x - projected_gradient_step).lpNorm<Eigen::Infinity>(); - iteration_summary.gradient_norm = (x - projected_gradient_step).norm(); + strategy_->StepIsInvalid(); + + // We are going to try and reduce the trust region radius and + // solve again. To do this, we are going to treat this iteration + // as an unsuccessful iteration. Since the various callbacks are + // still executed, we are going to fill the iteration summary + // with data that assumes a step of length zero and no progress. + iteration_summary_.cost = x_cost_ + solver_summary_->fixed_cost; + iteration_summary_.cost_change = 0.0; + iteration_summary_.gradient_max_norm = + solver_summary_->iterations.back().gradient_max_norm; + iteration_summary_.gradient_norm = + solver_summary_->iterations.back().gradient_norm; + iteration_summary_.step_norm = 0.0; + iteration_summary_.relative_decrease = 0.0; + iteration_summary_.eta = options_.eta; + return true; +} - if (options_.jacobi_scaling) { - jacobian->ScaleColumns(scale.data()); - } +// Use the supplied coordinate descent minimizer to perform inner +// iterations and compute the improvement due to it. Returns the cost +// after performing the inner iterations. +// +// The optimization is performed with candidate_x_ as the starting +// point, and if the optimization is successful, candidate_x_ will be +// updated with the optimized parameters. +void TrustRegionMinimizer::DoInnerIterationsIfNeeded() { + inner_iterations_were_useful_ = false; + if (!inner_iterations_are_enabled_ || + candidate_cost_ >= std::numeric_limits<double>::max()) { + return; + } - // Update the best, reference and candidate iterates. - // - // Based on algorithm 10.1.2 (page 357) of "Trust Region - // Methods" by Conn Gould & Toint, or equations 33-40 of - // "Non-monotone trust-region algorithms for nonlinear - // optimization subject to convex constraints" by Phil Toint, - // Mathematical Programming, 77, 1997. - if (cost < minimum_cost) { - // A step that improves solution quality was found. - x_min = x; - minimum_cost = cost; - // Set the candidate iterate to the current point. - candidate_cost = cost; - num_consecutive_nonmonotonic_steps = 0; - accumulated_candidate_model_cost_change = 0.0; - } else { - ++num_consecutive_nonmonotonic_steps; - if (cost > candidate_cost) { - // The current iterate is has a higher cost than the - // candidate iterate. Set the candidate to this point. - VLOG_IF(2, is_not_silent) - << "Updating the candidate iterate to the current point."; - candidate_cost = cost; - accumulated_candidate_model_cost_change = 0.0; - } - - // At this point we have made too many non-monotonic steps and - // we are going to reset the value of the reference iterate so - // as to force the algorithm to descend. - // - // This is the case because the candidate iterate has a value - // greater than minimum_cost but smaller than the reference - // iterate. - if (num_consecutive_nonmonotonic_steps == - options.max_consecutive_nonmonotonic_steps) { - VLOG_IF(2, is_not_silent) - << "Resetting the reference point to the candidate point"; - reference_cost = candidate_cost; - accumulated_reference_model_cost_change = - accumulated_candidate_model_cost_change; - } - } - } else { - ++summary->num_unsuccessful_steps; - if (iteration_summary.step_is_valid) { - strategy->StepRejected(iteration_summary.relative_decrease); - } else { - strategy->StepIsInvalid(); - } - } + double inner_iteration_start_time = WallTimeInSeconds(); + ++solver_summary_->num_inner_iteration_steps; + inner_iteration_x_ = candidate_x_; + Solver::Summary inner_iteration_summary; + options_.inner_iteration_minimizer->Minimize( + options_, inner_iteration_x_.data(), &inner_iteration_summary); + double inner_iteration_cost; + if (!evaluator_->Evaluate( + inner_iteration_x_.data(), &inner_iteration_cost, NULL, NULL, NULL)) { + VLOG_IF(2, is_not_silent_) << "Inner iteration failed."; + return; + } - iteration_summary.cost = cost + summary->fixed_cost; - iteration_summary.trust_region_radius = strategy->Radius(); - iteration_summary.iteration_time_in_seconds = - WallTimeInSeconds() - iteration_start_time; - iteration_summary.cumulative_time_in_seconds = - WallTimeInSeconds() - start_time - + summary->preprocessor_time_in_seconds; - summary->iterations.push_back(iteration_summary); - - // If the step was successful, check for the gradient norm - // collapsing to zero, and if the step is unsuccessful then check - // if the trust region radius has collapsed to zero. - // - // For correctness (Number of IterationSummary objects, correct - // final cost, and state update) these convergence tests need to - // be performed at the end of the iteration. - if (iteration_summary.step_is_successful) { - // Gradient norm can only go down in successful steps. - if (iteration_summary.gradient_max_norm <= options.gradient_tolerance) { - summary->message = StringPrintf("Gradient tolerance reached. " - "Gradient max norm: %e <= %e", - iteration_summary.gradient_max_norm, - options_.gradient_tolerance); - summary->termination_type = CONVERGENCE; - VLOG_IF(1, is_not_silent) << "Terminating: " << summary->message; - return; - } - } else { - // Trust region radius can only go down if the step if - // unsuccessful. - if (iteration_summary.trust_region_radius < - options_.min_trust_region_radius) { - summary->message = "Termination. Minimum trust region radius reached."; - summary->termination_type = CONVERGENCE; - VLOG_IF(1, is_not_silent) << summary->message; - return; - } - } + VLOG_IF(2, is_not_silent_) + << "Inner iteration succeeded; Current cost: " << x_cost_ + << " Trust region step cost: " << candidate_cost_ + << " Inner iteration cost: " << inner_iteration_cost; + + candidate_x_ = inner_iteration_x_; + + // Normally, the quality of a trust region step is measured by + // the ratio + // + // cost_change + // r = ----------------- + // model_cost_change + // + // All the change in the nonlinear objective is due to the trust + // region step so this ratio is a good measure of the quality of + // the trust region radius. However, when inner iterations are + // being used, cost_change includes the contribution of the + // inner iterations and its not fair to credit it all to the + // trust region algorithm. So we change the ratio to be + // + // cost_change + // r = ------------------------------------------------ + // (model_cost_change + inner_iteration_cost_change) + // + // Practically we do this by increasing model_cost_change by + // inner_iteration_cost_change. + + const double inner_iteration_cost_change = + candidate_cost_ - inner_iteration_cost; + model_cost_change_ += inner_iteration_cost_change; + inner_iterations_were_useful_ = inner_iteration_cost < x_cost_; + const double inner_iteration_relative_progress = + 1.0 - inner_iteration_cost / candidate_cost_; + + // Disable inner iterations once the relative improvement + // drops below tolerance. + inner_iterations_are_enabled_ = + (inner_iteration_relative_progress > options_.inner_iteration_tolerance); + VLOG_IF(2, is_not_silent_ && !inner_iterations_are_enabled_) + << "Disabling inner iterations. Progress : " + << inner_iteration_relative_progress; + candidate_cost_ = inner_iteration_cost; + + solver_summary_->inner_iteration_time_in_seconds += + WallTimeInSeconds() - inner_iteration_start_time; +} + +// Perform a projected line search to improve the objective function +// value along delta. +// +// TODO(sameeragarwal): The current implementation does not do +// anything illegal but is incorrect and not terribly effective. +// +// https://github.com/ceres-solver/ceres-solver/issues/187 +void TrustRegionMinimizer::DoLineSearch(const Vector& x, + const Vector& gradient, + const double cost, + Vector* delta) { + LineSearchFunction line_search_function(evaluator_); + + LineSearch::Options line_search_options; + line_search_options.is_silent = true; + line_search_options.interpolation_type = + options_.line_search_interpolation_type; + line_search_options.min_step_size = options_.min_line_search_step_size; + line_search_options.sufficient_decrease = + options_.line_search_sufficient_function_decrease; + line_search_options.max_step_contraction = + options_.max_line_search_step_contraction; + line_search_options.min_step_contraction = + options_.min_line_search_step_contraction; + line_search_options.max_num_iterations = + options_.max_num_line_search_step_size_iterations; + line_search_options.sufficient_curvature_decrease = + options_.line_search_sufficient_curvature_decrease; + line_search_options.max_step_expansion = + options_.max_line_search_step_expansion; + line_search_options.function = &line_search_function; + + std::string message; + scoped_ptr<LineSearch> line_search(CHECK_NOTNULL( + LineSearch::Create(ceres::ARMIJO, line_search_options, &message))); + LineSearch::Summary line_search_summary; + line_search_function.Init(x, *delta); + line_search->Search(1.0, cost, gradient.dot(*delta), &line_search_summary); + + solver_summary_->num_line_search_steps += line_search_summary.num_iterations; + solver_summary_->line_search_cost_evaluation_time_in_seconds += + line_search_summary.cost_evaluation_time_in_seconds; + solver_summary_->line_search_gradient_evaluation_time_in_seconds += + line_search_summary.gradient_evaluation_time_in_seconds; + solver_summary_->line_search_polynomial_minimization_time_in_seconds += + line_search_summary.polynomial_minimization_time_in_seconds; + solver_summary_->line_search_total_time_in_seconds += + line_search_summary.total_time_in_seconds; + + if (line_search_summary.success) { + *delta *= line_search_summary.optimal_step_size; + } +} + +// Check if the maximum amount of time allowed by the user for the +// solver has been exceeded, and if so return false after updating +// Solver::Summary::message. +bool TrustRegionMinimizer::MaxSolverTimeReached() { + const double total_solver_time = + WallTimeInSeconds() - start_time_in_secs_ + + solver_summary_->preprocessor_time_in_seconds; + if (total_solver_time < options_.max_solver_time_in_seconds) { + return false; + } + + solver_summary_->message = StringPrintf("Maximum solver time reached. " + "Total solver time: %e >= %e.", + total_solver_time, + options_.max_solver_time_in_seconds); + solver_summary_->termination_type = NO_CONVERGENCE; + VLOG_IF(1, is_not_silent_) << "Terminating: " << solver_summary_->message; + return true; +} + +// Check if the maximum number of iterations allowed by the user for +// the solver has been exceeded, and if so return false after updating +// Solver::Summary::message. +bool TrustRegionMinimizer::MaxSolverIterationsReached() { + if (iteration_summary_.iteration < options_.max_num_iterations) { + return false; + } + + solver_summary_->message = + StringPrintf("Maximum number of iterations reached. " + "Number of iterations: %d.", + iteration_summary_.iteration); + + solver_summary_->termination_type = NO_CONVERGENCE; + VLOG_IF(1, is_not_silent_) << "Terminating: " << solver_summary_->message; + return true; +} + +// Check convergence based on the max norm of the gradient (only for +// iterations where the step was declared successful). +bool TrustRegionMinimizer::GradientToleranceReached() { + if (!iteration_summary_.step_is_successful || + iteration_summary_.gradient_max_norm > options_.gradient_tolerance) { + return false; + } + + solver_summary_->message = StringPrintf( + "Gradient tolerance reached. " + "Gradient max norm: %e <= %e", + iteration_summary_.gradient_max_norm, + options_.gradient_tolerance); + solver_summary_->termination_type = CONVERGENCE; + VLOG_IF(1, is_not_silent_) << "Terminating: " << solver_summary_->message; + return true; +} + +// Check convergence based the size of the trust region radius. +bool TrustRegionMinimizer::MinTrustRegionRadiusReached() { + if (iteration_summary_.trust_region_radius > + options_.min_trust_region_radius) { + return false; + } + + solver_summary_->message = + StringPrintf("Minimum trust region radius reached. " + "Trust region radius: %e <= %e", + iteration_summary_.trust_region_radius, + options_.min_trust_region_radius); + solver_summary_->termination_type = CONVERGENCE; + VLOG_IF(1, is_not_silent_) << "Terminating: " << solver_summary_->message; + return true; +} + +// Solver::Options::parameter_tolerance based convergence check. +bool TrustRegionMinimizer::ParameterToleranceReached() { + // Compute the norm of the step in the ambient space. + iteration_summary_.step_norm = (x_ - candidate_x_).norm(); + const double step_size_tolerance = + options_.parameter_tolerance * (x_norm_ + options_.parameter_tolerance); + + if (iteration_summary_.step_norm > step_size_tolerance) { + return false; } + + solver_summary_->message = StringPrintf( + "Parameter tolerance reached. " + "Relative step_norm: %e <= %e.", + (iteration_summary_.step_norm / (x_norm_ + options_.parameter_tolerance)), + options_.parameter_tolerance); + solver_summary_->termination_type = CONVERGENCE; + VLOG_IF(1, is_not_silent_) << "Terminating: " << solver_summary_->message; + return true; +} + +// Solver::Options::function_tolerance based convergence check. +bool TrustRegionMinimizer::FunctionToleranceReached() { + iteration_summary_.cost_change = x_cost_ - candidate_cost_; + const double absolute_function_tolerance = + options_.function_tolerance * x_cost_; + + if (fabs(iteration_summary_.cost_change) > absolute_function_tolerance) { + return false; + } + + solver_summary_->message = StringPrintf( + "Function tolerance reached. " + "|cost_change|/cost: %e <= %e", + fabs(iteration_summary_.cost_change) / x_cost_, + options_.function_tolerance); + solver_summary_->termination_type = CONVERGENCE; + VLOG_IF(1, is_not_silent_) << "Terminating: " << solver_summary_->message; + return true; } +// Compute candidate_x_ = Plus(x_, delta_) +// Evaluate the cost of candidate_x_ as candidate_cost_. +// +// Failure to compute the step or the cost mean that candidate_cost_ +// is set to std::numeric_limits<double>::max(). Unlike +// EvaluateGradientAndJacobian, failure in this function is not fatal +// as we are only computing and evaluating a candidate point, and if +// for some reason we are unable to evaluate it, we consider it to be +// a point with very high cost. This allows the user to deal with edge +// cases/constraints as part of the LocalParameterization and +// CostFunction objects. +void TrustRegionMinimizer::ComputeCandidatePointAndEvaluateCost() { + if (!evaluator_->Plus(x_.data(), delta_.data(), candidate_x_.data())) { + LOG_IF(WARNING, is_not_silent_) + << "x_plus_delta = Plus(x, delta) failed. " + << "Treating it as a step with infinite cost"; + candidate_cost_ = std::numeric_limits<double>::max(); + return; + } + + if (!evaluator_->Evaluate( + candidate_x_.data(), &candidate_cost_, NULL, NULL, NULL)) { + LOG_IF(WARNING, is_not_silent_) + << "Step failed to evaluate. " + << "Treating it as a step with infinite cost"; + candidate_cost_ = std::numeric_limits<double>::max(); + } +} + +bool TrustRegionMinimizer::IsStepSuccessful() { + iteration_summary_.relative_decrease = + step_evaluator_->StepQuality(candidate_cost_, model_cost_change_); + + // In most cases, boosting the model_cost_change by the + // improvement caused by the inner iterations is fine, but it can + // be the case that the original trust region step was so bad that + // the resulting improvement in the cost was negative, and the + // change caused by the inner iterations was large enough to + // improve the step, but also to make relative decrease quite + // small. + // + // This can cause the trust region loop to reject this step. To + // get around this, we expicitly check if the inner iterations + // led to a net decrease in the objective function value. If + // they did, we accept the step even if the trust region ratio + // is small. + // + // Notice that we do not just check that cost_change is positive + // which is a weaker condition and would render the + // min_relative_decrease threshold useless. Instead, we keep + // track of inner_iterations_were_useful, which is true only + // when inner iterations lead to a net decrease in the cost. + return (inner_iterations_were_useful_ || + iteration_summary_.relative_decrease > + options_.min_relative_decrease); +} + +// Declare the step successful, move to candidate_x, update the +// derivatives and let the trust region strategy and the step +// evaluator know that the step has been accepted. +bool TrustRegionMinimizer::HandleSuccessfulStep() { + x_ = candidate_x_; + x_norm_ = x_.norm(); + + if (!EvaluateGradientAndJacobian()) { + return false; + } + + iteration_summary_.step_is_successful = true; + strategy_->StepAccepted(iteration_summary_.relative_decrease); + step_evaluator_->StepAccepted(candidate_cost_, model_cost_change_); + return true; +} + +// Declare the step unsuccessful and inform the trust region strategy. +void TrustRegionMinimizer::HandleUnsuccessfulStep() { + iteration_summary_.step_is_successful = false; + strategy_->StepRejected(iteration_summary_.relative_decrease); + iteration_summary_.cost = candidate_cost_ + solver_summary_->fixed_cost; +} } // namespace internal } // namespace ceres diff --git a/extern/ceres/internal/ceres/trust_region_minimizer.h b/extern/ceres/internal/ceres/trust_region_minimizer.h index ed52c2642d1..43141da58a1 100644 --- a/extern/ceres/internal/ceres/trust_region_minimizer.h +++ b/extern/ceres/internal/ceres/trust_region_minimizer.h @@ -1,5 +1,5 @@ // Ceres Solver - A fast non-linear least squares minimizer -// Copyright 2015 Google Inc. All rights reserved. +// Copyright 2016 Google Inc. All rights reserved. // http://ceres-solver.org/ // // Redistribution and use in source and binary forms, with or without @@ -31,35 +31,136 @@ #ifndef CERES_INTERNAL_TRUST_REGION_MINIMIZER_H_ #define CERES_INTERNAL_TRUST_REGION_MINIMIZER_H_ +#include "ceres/internal/eigen.h" +#include "ceres/internal/scoped_ptr.h" #include "ceres/minimizer.h" #include "ceres/solver.h" +#include "ceres/sparse_matrix.h" +#include "ceres/trust_region_step_evaluator.h" +#include "ceres/trust_region_strategy.h" #include "ceres/types.h" namespace ceres { namespace internal { -// Generic trust region minimization algorithm. The heavy lifting is -// done by a TrustRegionStrategy object passed in as part of options. +// Generic trust region minimization algorithm. // // For example usage, see SolverImpl::Minimize. class TrustRegionMinimizer : public Minimizer { public: - ~TrustRegionMinimizer() {} + ~TrustRegionMinimizer(); + + // This method is not thread safe. virtual void Minimize(const Minimizer::Options& options, double* parameters, - Solver::Summary* summary); + Solver::Summary* solver_summary); private: - void Init(const Minimizer::Options& options); - void EstimateScale(const SparseMatrix& jacobian, double* scale) const; - bool MaybeDumpLinearLeastSquaresProblem(const int iteration, - const SparseMatrix* jacobian, - const double* residuals, - const double* step) const; + void Init(const Minimizer::Options& options, + double* parameters, + Solver::Summary* solver_summary); + bool IterationZero(); + bool FinalizeIterationAndCheckIfMinimizerCanContinue(); + bool ComputeTrustRegionStep(); + + bool EvaluateGradientAndJacobian(); + void ComputeCandidatePointAndEvaluateCost(); + + void DoLineSearch(const Vector& x, + const Vector& gradient, + const double cost, + Vector* delta); + void DoInnerIterationsIfNeeded(); + + bool ParameterToleranceReached(); + bool FunctionToleranceReached(); + bool GradientToleranceReached(); + bool MaxSolverTimeReached(); + bool MaxSolverIterationsReached(); + bool MinTrustRegionRadiusReached(); + + bool IsStepSuccessful(); + void HandleUnsuccessfulStep(); + bool HandleSuccessfulStep(); + bool HandleInvalidStep(); Minimizer::Options options_; + + // These pointers are shortcuts to objects passed to the + // TrustRegionMinimizer. The TrustRegionMinimizer does not own them. + double* parameters_; + Solver::Summary* solver_summary_; + Evaluator* evaluator_; + SparseMatrix* jacobian_; + TrustRegionStrategy* strategy_; + + scoped_ptr<TrustRegionStepEvaluator> step_evaluator_; + + bool is_not_silent_; + bool inner_iterations_are_enabled_; + bool inner_iterations_were_useful_; + + // Summary of the current iteration. + IterationSummary iteration_summary_; + + // Dimensionality of the problem in the ambient space. + int num_parameters_; + // Dimensionality of the problem in the tangent space. This is the + // number of columns in the Jacobian. + int num_effective_parameters_; + // Length of the residual vector, also the number of rows in the Jacobian. + int num_residuals_; + + // Current point. + Vector x_; + // Residuals at x_; + Vector residuals_; + // Gradient at x_. + Vector gradient_; + // Solution computed by the inner iterations. + Vector inner_iteration_x_; + // model_residuals = J * trust_region_step + Vector model_residuals_; + Vector negative_gradient_; + // projected_gradient_step = Plus(x, -gradient), an intermediate + // quantity used to compute the projected gradient norm. + Vector projected_gradient_step_; + // The step computed by the trust region strategy. If Jacobi scaling + // is enabled, this is a vector in the scaled space. + Vector trust_region_step_; + // The current proposal for how far the trust region algorithm + // thinks we should move. In the most basic case, it is just the + // trust_region_step_ with the Jacobi scaling undone. If bounds + // constraints are present, then it is the result of the projected + // line search. + Vector delta_; + // candidate_x = Plus(x, delta) + Vector candidate_x_; + // Scaling vector to scale the columns of the Jacobian. + Vector jacobian_scaling_; + + // Euclidean norm of x_. + double x_norm_; + // Cost at x_. + double x_cost_; + // Minimum cost encountered up till now. + double minimum_cost_; + // How much did the trust region strategy reduce the cost of the + // linearized Gauss-Newton model. + double model_cost_change_; + // Cost at candidate_x_. + double candidate_cost_; + + // Time at which the minimizer was started. + double start_time_in_secs_; + // Time at which the current iteration was started. + double iteration_start_time_in_secs_; + // Number of consecutive steps where the minimizer loop computed a + // numerically invalid step. + int num_consecutive_invalid_steps_; }; } // namespace internal } // namespace ceres + #endif // CERES_INTERNAL_TRUST_REGION_MINIMIZER_H_ diff --git a/extern/ceres/internal/ceres/trust_region_step_evaluator.cc b/extern/ceres/internal/ceres/trust_region_step_evaluator.cc new file mode 100644 index 00000000000..c9167e623ef --- /dev/null +++ b/extern/ceres/internal/ceres/trust_region_step_evaluator.cc @@ -0,0 +1,107 @@ +// Ceres Solver - A fast non-linear least squares minimizer +// Copyright 2016 Google Inc. All rights reserved. +// http://ceres-solver.org/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors may be +// used to endorse or promote products derived from this software without +// specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Author: sameeragarwal@google.com (Sameer Agarwal) + +#include <algorithm> +#include "ceres/trust_region_step_evaluator.h" +#include "glog/logging.h" + +namespace ceres { +namespace internal { + +TrustRegionStepEvaluator::TrustRegionStepEvaluator( + const double initial_cost, + const int max_consecutive_nonmonotonic_steps) + : max_consecutive_nonmonotonic_steps_(max_consecutive_nonmonotonic_steps), + minimum_cost_(initial_cost), + current_cost_(initial_cost), + reference_cost_(initial_cost), + candidate_cost_(initial_cost), + accumulated_reference_model_cost_change_(0.0), + accumulated_candidate_model_cost_change_(0.0), + num_consecutive_nonmonotonic_steps_(0){ +} + +double TrustRegionStepEvaluator::StepQuality( + const double cost, + const double model_cost_change) const { + const double relative_decrease = (current_cost_ - cost) / model_cost_change; + const double historical_relative_decrease = + (reference_cost_ - cost) / + (accumulated_reference_model_cost_change_ + model_cost_change); + return std::max(relative_decrease, historical_relative_decrease); +} + +void TrustRegionStepEvaluator::StepAccepted( + const double cost, + const double model_cost_change) { + // Algorithm 10.1.2 from Trust Region Methods by Conn, Gould & + // Toint. + // + // Step 3a + current_cost_ = cost; + accumulated_candidate_model_cost_change_ += model_cost_change; + accumulated_reference_model_cost_change_ += model_cost_change; + + // Step 3b. + if (current_cost_ < minimum_cost_) { + minimum_cost_ = current_cost_; + num_consecutive_nonmonotonic_steps_ = 0; + candidate_cost_ = current_cost_; + accumulated_candidate_model_cost_change_ = 0.0; + } else { + // Step 3c. + ++num_consecutive_nonmonotonic_steps_; + if (current_cost_ > candidate_cost_) { + candidate_cost_ = current_cost_; + accumulated_candidate_model_cost_change_ = 0.0; + } + } + + // Step 3d. + // + // At this point we have made too many non-monotonic steps and + // we are going to reset the value of the reference iterate so + // as to force the algorithm to descend. + // + // Note: In the original algorithm by Toint, this step was only + // executed if the step was non-monotonic, but that would not handle + // the case of max_consecutive_nonmonotonic_steps = 0. The small + // modification of doing this always handles that corner case + // correctly. + if (num_consecutive_nonmonotonic_steps_ == + max_consecutive_nonmonotonic_steps_) { + reference_cost_ = candidate_cost_; + accumulated_reference_model_cost_change_ = + accumulated_candidate_model_cost_change_; + } +} + +} // namespace internal +} // namespace ceres diff --git a/extern/ceres/internal/ceres/trust_region_step_evaluator.h b/extern/ceres/internal/ceres/trust_region_step_evaluator.h new file mode 100644 index 00000000000..06df102a308 --- /dev/null +++ b/extern/ceres/internal/ceres/trust_region_step_evaluator.h @@ -0,0 +1,122 @@ +// Ceres Solver - A fast non-linear least squares minimizer +// Copyright 2016 Google Inc. All rights reserved. +// http://ceres-solver.org/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors may be +// used to endorse or promote products derived from this software without +// specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Author: sameeragarwal@google.com (Sameer Agarwal) + +#ifndef CERES_INTERNAL_TRUST_REGION_STEP_EVALUATOR_H_ +#define CERES_INTERNAL_TRUST_REGION_STEP_EVALUATOR_H_ + +namespace ceres { +namespace internal { + +// The job of the TrustRegionStepEvaluator is to evaluate the quality +// of a step, i.e., how the cost of a step compares with the reduction +// in the objective of the trust region problem. +// +// Classic trust region methods are descent methods, in that they only +// accept a point if it strictly reduces the value of the objective +// function. They do this by measuring the quality of a step as +// +// cost_change / model_cost_change. +// +// Relaxing the monotonic descent requirement allows the algorithm to +// be more efficient in the long term at the cost of some local +// increase in the value of the objective function. +// +// This is because allowing for non-decreasing objective function +// values in a principled manner allows the algorithm to "jump over +// boulders" as the method is not restricted to move into narrow +// valleys while preserving its convergence properties. +// +// The parameter max_consecutive_nonmonotonic_steps controls the +// window size used by the step selection algorithm to accept +// non-monotonic steps. Setting this parameter to zero, recovers the +// classic montonic descent algorithm. +// +// Based on algorithm 10.1.2 (page 357) of "Trust Region +// Methods" by Conn Gould & Toint, or equations 33-40 of +// "Non-monotone trust-region algorithms for nonlinear +// optimization subject to convex constraints" by Phil Toint, +// Mathematical Programming, 77, 1997. +// +// Example usage: +// +// TrustRegionStepEvaluator* step_evaluator = ... +// +// cost = ... // Compute the non-linear objective function value. +// model_cost_change = ... // Change in the value of the trust region objective. +// if (step_evaluator->StepQuality(cost, model_cost_change) > threshold) { +// x = x + delta; +// step_evaluator->StepAccepted(cost, model_cost_change); +// } +class TrustRegionStepEvaluator { + public: + // initial_cost is as the name implies the cost of the starting + // state of the trust region minimizer. + // + // max_consecutive_nonmonotonic_steps controls the window size used + // by the step selection algorithm to accept non-monotonic + // steps. Setting this parameter to zero, recovers the classic + // montonic descent algorithm. + TrustRegionStepEvaluator(double initial_cost, + int max_consecutive_nonmonotonic_steps); + + // Return the quality of the step given its cost and the decrease in + // the cost of the model. model_cost_change has to be positive. + double StepQuality(double cost, double model_cost_change) const; + + // Inform the step evaluator that a step with the given cost and + // model_cost_change has been accepted by the trust region + // minimizer. + void StepAccepted(double cost, double model_cost_change); + + private: + const int max_consecutive_nonmonotonic_steps_; + // The minimum cost encountered up till now. + double minimum_cost_; + // The current cost of the trust region minimizer as informed by the + // last call to StepAccepted. + double current_cost_; + double reference_cost_; + double candidate_cost_; + // Accumulated model cost since the last time the reference model + // cost was updated, i.e., when a step with cost less than the + // current known minimum cost is accepted. + double accumulated_reference_model_cost_change_; + // Accumulated model cost since the last time the candidate model + // cost was updated, i.e., a non-monotonic step was taken with a + // cost that was greater than the current candidate cost. + double accumulated_candidate_model_cost_change_; + // Number of steps taken since the last time minimum_cost was updated. + int num_consecutive_nonmonotonic_steps_; +}; + +} // namespace internal +} // namespace ceres + +#endif // CERES_INTERNAL_TRUST_REGION_STEP_EVALUATOR_H_ diff --git a/extern/ceres/internal/ceres/trust_region_strategy.h b/extern/ceres/internal/ceres/trust_region_strategy.h index 9560e67459a..36e8e981cc0 100644 --- a/extern/ceres/internal/ceres/trust_region_strategy.h +++ b/extern/ceres/internal/ceres/trust_region_strategy.h @@ -86,20 +86,20 @@ class TrustRegionStrategy { struct PerSolveOptions { PerSolveOptions() : eta(0), - dump_filename_base(""), dump_format_type(TEXTFILE) { } // Forcing sequence for inexact solves. double eta; + DumpFormatType dump_format_type; + // If non-empty and dump_format_type is not CONSOLE, the trust // regions strategy will write the linear system to file(s) with // name starting with dump_filename_base. If dump_format_type is // CONSOLE then dump_filename_base will be ignored and the linear // system will be written to the standard error. std::string dump_filename_base; - DumpFormatType dump_format_type; }; struct Summary { diff --git a/intern/atomic/atomic_ops.h b/intern/atomic/atomic_ops.h index 0bc7905aa07..1107deddf94 100644 --- a/intern/atomic/atomic_ops.h +++ b/intern/atomic/atomic_ops.h @@ -77,32 +77,40 @@ /* Function prototypes. */ #if (LG_SIZEOF_PTR == 8 || LG_SIZEOF_INT == 8) -ATOMIC_INLINE uint64_t atomic_add_uint64(uint64_t *p, uint64_t x); -ATOMIC_INLINE uint64_t atomic_sub_uint64(uint64_t *p, uint64_t x); +ATOMIC_INLINE uint64_t atomic_add_and_fetch_uint64(uint64_t *p, uint64_t x); +ATOMIC_INLINE uint64_t atomic_sub_and_fetch_uint64(uint64_t *p, uint64_t x); +ATOMIC_INLINE uint64_t atomic_fetch_and_add_uint64(uint64_t *p, uint64_t x); +ATOMIC_INLINE uint64_t atomic_fetch_and_sub_uint64(uint64_t *p, uint64_t x); ATOMIC_INLINE uint64_t atomic_cas_uint64(uint64_t *v, uint64_t old, uint64_t _new); #endif -ATOMIC_INLINE uint32_t atomic_add_uint32(uint32_t *p, uint32_t x); -ATOMIC_INLINE uint32_t atomic_sub_uint32(uint32_t *p, uint32_t x); +ATOMIC_INLINE uint32_t atomic_add_and_fetch_uint32(uint32_t *p, uint32_t x); +ATOMIC_INLINE uint32_t atomic_sub_and_fetch_uint32(uint32_t *p, uint32_t x); ATOMIC_INLINE uint32_t atomic_cas_uint32(uint32_t *v, uint32_t old, uint32_t _new); ATOMIC_INLINE uint32_t atomic_fetch_and_add_uint32(uint32_t *p, uint32_t x); +ATOMIC_INLINE uint32_t atomic_fetch_and_or_uint32(uint32_t *p, uint32_t x); +ATOMIC_INLINE uint32_t atomic_fetch_and_and_uint32(uint32_t *p, uint32_t x); ATOMIC_INLINE uint8_t atomic_fetch_and_or_uint8(uint8_t *p, uint8_t b); ATOMIC_INLINE uint8_t atomic_fetch_and_and_uint8(uint8_t *p, uint8_t b); -ATOMIC_INLINE size_t atomic_add_z(size_t *p, size_t x); -ATOMIC_INLINE size_t atomic_sub_z(size_t *p, size_t x); +ATOMIC_INLINE size_t atomic_add_and_fetch_z(size_t *p, size_t x); +ATOMIC_INLINE size_t atomic_sub_and_fetch_z(size_t *p, size_t x); +ATOMIC_INLINE size_t atomic_fetch_and_add_z(size_t *p, size_t x); +ATOMIC_INLINE size_t atomic_fetch_and_sub_z(size_t *p, size_t x); ATOMIC_INLINE size_t atomic_cas_z(size_t *v, size_t old, size_t _new); -ATOMIC_INLINE unsigned atomic_add_u(unsigned *p, unsigned x); -ATOMIC_INLINE unsigned atomic_sub_u(unsigned *p, unsigned x); +ATOMIC_INLINE unsigned atomic_add_and_fetch_u(unsigned *p, unsigned x); +ATOMIC_INLINE unsigned atomic_sub_and_fetch_u(unsigned *p, unsigned x); +ATOMIC_INLINE unsigned atomic_fetch_and_add_u(unsigned *p, unsigned x); +ATOMIC_INLINE unsigned atomic_fetch_and_sub_u(unsigned *p, unsigned x); ATOMIC_INLINE unsigned atomic_cas_u(unsigned *v, unsigned old, unsigned _new); /* WARNING! Float 'atomics' are really faked ones, those are actually closer to some kind of spinlock-sync'ed operation, * which means they are only efficient if collisions are highly unlikely (i.e. if probability of two threads * working on the same pointer at the same time is very low). */ -ATOMIC_INLINE float atomic_add_fl(float *p, const float x); +ATOMIC_INLINE float atomic_add_and_fetch_fl(float *p, const float x); /******************************************************************************/ /* Include system-dependent implementations. */ diff --git a/intern/atomic/intern/atomic_ops_ext.h b/intern/atomic/intern/atomic_ops_ext.h index 4065299d2ea..8421aa72192 100644 --- a/intern/atomic/intern/atomic_ops_ext.h +++ b/intern/atomic/intern/atomic_ops_ext.h @@ -56,25 +56,47 @@ /******************************************************************************/ /* size_t operations. */ -ATOMIC_INLINE size_t atomic_add_z(size_t *p, size_t x) +ATOMIC_INLINE size_t atomic_add_and_fetch_z(size_t *p, size_t x) { assert(sizeof(size_t) == LG_SIZEOF_PTR); #if (LG_SIZEOF_PTR == 8) - return (size_t)atomic_add_uint64((uint64_t *)p, (uint64_t)x); + return (size_t)atomic_add_and_fetch_uint64((uint64_t *)p, (uint64_t)x); #elif (LG_SIZEOF_PTR == 4) - return (size_t)atomic_add_uint32((uint32_t *)p, (uint32_t)x); + return (size_t)atomic_add_and_fetch_uint32((uint32_t *)p, (uint32_t)x); #endif } -ATOMIC_INLINE size_t atomic_sub_z(size_t *p, size_t x) +ATOMIC_INLINE size_t atomic_sub_and_fetch_z(size_t *p, size_t x) { assert(sizeof(size_t) == LG_SIZEOF_PTR); #if (LG_SIZEOF_PTR == 8) - return (size_t)atomic_add_uint64((uint64_t *)p, (uint64_t)-((int64_t)x)); + return (size_t)atomic_add_and_fetch_uint64((uint64_t *)p, (uint64_t)-((int64_t)x)); #elif (LG_SIZEOF_PTR == 4) - return (size_t)atomic_add_uint32((uint32_t *)p, (uint32_t)-((int32_t)x)); + return (size_t)atomic_add_and_fetch_uint32((uint32_t *)p, (uint32_t)-((int32_t)x)); +#endif +} + +ATOMIC_INLINE size_t atomic_fetch_and_add_z(size_t *p, size_t x) +{ + assert(sizeof(size_t) == LG_SIZEOF_PTR); + +#if (LG_SIZEOF_PTR == 8) + return (size_t)atomic_fetch_and_add_uint64((uint64_t *)p, (uint64_t)x); +#elif (LG_SIZEOF_PTR == 4) + return (size_t)atomic_fetch_and_add_uint32((uint32_t *)p, (uint32_t)x); +#endif +} + +ATOMIC_INLINE size_t atomic_fetch_and_sub_z(size_t *p, size_t x) +{ + assert(sizeof(size_t) == LG_SIZEOF_PTR); + +#if (LG_SIZEOF_PTR == 8) + return (size_t)atomic_fetch_and_add_uint64((uint64_t *)p, (uint64_t)-((int64_t)x)); +#elif (LG_SIZEOF_PTR == 4) + return (size_t)atomic_fetch_and_add_uint32((uint32_t *)p, (uint32_t)-((int32_t)x)); #endif } @@ -91,25 +113,47 @@ ATOMIC_INLINE size_t atomic_cas_z(size_t *v, size_t old, size_t _new) /******************************************************************************/ /* unsigned operations. */ -ATOMIC_INLINE unsigned atomic_add_u(unsigned *p, unsigned x) +ATOMIC_INLINE unsigned atomic_add_and_fetch_u(unsigned *p, unsigned x) +{ + assert(sizeof(unsigned) == LG_SIZEOF_INT); + +#if (LG_SIZEOF_INT == 8) + return (unsigned)atomic_add_and_fetch_uint64((uint64_t *)p, (uint64_t)x); +#elif (LG_SIZEOF_INT == 4) + return (unsigned)atomic_add_and_fetch_uint32((uint32_t *)p, (uint32_t)x); +#endif +} + +ATOMIC_INLINE unsigned atomic_sub_and_fetch_u(unsigned *p, unsigned x) +{ + assert(sizeof(unsigned) == LG_SIZEOF_INT); + +#if (LG_SIZEOF_INT == 8) + return (unsigned)atomic_add_and_fetch_uint64((uint64_t *)p, (uint64_t)-((int64_t)x)); +#elif (LG_SIZEOF_INT == 4) + return (unsigned)atomic_add_and_fetch_uint32((uint32_t *)p, (uint32_t)-((int32_t)x)); +#endif +} + +ATOMIC_INLINE unsigned atomic_fetch_and_add_u(unsigned *p, unsigned x) { assert(sizeof(unsigned) == LG_SIZEOF_INT); #if (LG_SIZEOF_INT == 8) - return (unsigned)atomic_add_uint64((uint64_t *)p, (uint64_t)x); + return (unsigned)atomic_fetch_and_add_uint64((uint64_t *)p, (uint64_t)x); #elif (LG_SIZEOF_INT == 4) - return (unsigned)atomic_add_uint32((uint32_t *)p, (uint32_t)x); + return (unsigned)atomic_fetch_and_add_uint32((uint32_t *)p, (uint32_t)x); #endif } -ATOMIC_INLINE unsigned atomic_sub_u(unsigned *p, unsigned x) +ATOMIC_INLINE unsigned atomic_fetch_and_sub_u(unsigned *p, unsigned x) { assert(sizeof(unsigned) == LG_SIZEOF_INT); #if (LG_SIZEOF_INT == 8) - return (unsigned)atomic_add_uint64((uint64_t *)p, (uint64_t)-((int64_t)x)); + return (unsigned)atomic_fetch_and_add_uint64((uint64_t *)p, (uint64_t)-((int64_t)x)); #elif (LG_SIZEOF_INT == 4) - return (unsigned)atomic_add_uint32((uint32_t *)p, (uint32_t)-((int32_t)x)); + return (unsigned)atomic_fetch_and_add_uint32((uint32_t *)p, (uint32_t)-((int32_t)x)); #endif } @@ -127,7 +171,7 @@ ATOMIC_INLINE unsigned atomic_cas_u(unsigned *v, unsigned old, unsigned _new) /******************************************************************************/ /* float operations. */ -ATOMIC_INLINE float atomic_add_fl(float *p, const float x) +ATOMIC_INLINE float atomic_add_and_fetch_fl(float *p, const float x) { assert(sizeof(float) == sizeof(uint32_t)); diff --git a/intern/atomic/intern/atomic_ops_msvc.h b/intern/atomic/intern/atomic_ops_msvc.h index 15ddda246d9..034ac1e3e53 100644 --- a/intern/atomic/intern/atomic_ops_msvc.h +++ b/intern/atomic/intern/atomic_ops_msvc.h @@ -43,12 +43,12 @@ /******************************************************************************/ /* 64-bit operations. */ #if (LG_SIZEOF_PTR == 8 || LG_SIZEOF_INT == 8) -ATOMIC_INLINE uint64_t atomic_add_uint64(uint64_t *p, uint64_t x) +ATOMIC_INLINE uint64_t atomic_add_and_fetch_uint64(uint64_t *p, uint64_t x) { return InterlockedExchangeAdd64((int64_t *)p, (int64_t)x) + x; } -ATOMIC_INLINE uint64_t atomic_sub_uint64(uint64_t *p, uint64_t x) +ATOMIC_INLINE uint64_t atomic_sub_and_fetch_uint64(uint64_t *p, uint64_t x) { return InterlockedExchangeAdd64((int64_t *)p, -((int64_t)x)) - x; } @@ -57,16 +57,26 @@ ATOMIC_INLINE uint64_t atomic_cas_uint64(uint64_t *v, uint64_t old, uint64_t _ne { return InterlockedCompareExchange64((int64_t *)v, _new, old); } + +ATOMIC_INLINE uint64_t atomic_fetch_and_add_uint64(uint64_t *p, uint64_t x) +{ + return InterlockedExchangeAdd64((int64_t *)p, (int64_t)x); +} + +ATOMIC_INLINE uint64_t atomic_fetch_and_sub_uint64(uint64_t *p, uint64_t x) +{ + return InterlockedExchangeAdd64((int64_t *)p, -((int64_t)x)); +} #endif /******************************************************************************/ /* 32-bit operations. */ -ATOMIC_INLINE uint32_t atomic_add_uint32(uint32_t *p, uint32_t x) +ATOMIC_INLINE uint32_t atomic_add_and_fetch_uint32(uint32_t *p, uint32_t x) { return InterlockedExchangeAdd(p, x) + x; } -ATOMIC_INLINE uint32_t atomic_sub_uint32(uint32_t *p, uint32_t x) +ATOMIC_INLINE uint32_t atomic_sub_and_fetch_uint32(uint32_t *p, uint32_t x) { return InterlockedExchangeAdd(p, -((int32_t)x)) - x; } @@ -81,6 +91,16 @@ ATOMIC_INLINE uint32_t atomic_fetch_and_add_uint32(uint32_t *p, uint32_t x) return InterlockedExchangeAdd(p, x); } +ATOMIC_INLINE uint32_t atomic_fetch_and_or_uint32(uint32_t *p, uint32_t x) +{ + return InterlockedOr((long *)p, x); +} + +ATOMIC_INLINE uint32_t atomic_fetch_and_and_uint32(uint32_t *p, uint32_t x) +{ + return InterlockedAnd((long *)p, x); +} + /******************************************************************************/ /* 8-bit operations. */ diff --git a/intern/atomic/intern/atomic_ops_unix.h b/intern/atomic/intern/atomic_ops_unix.h index 55c00024244..0a3322ad2b1 100644 --- a/intern/atomic/intern/atomic_ops_unix.h +++ b/intern/atomic/intern/atomic_ops_unix.h @@ -58,22 +58,32 @@ /* 64-bit operations. */ #if (LG_SIZEOF_PTR == 8 || LG_SIZEOF_INT == 8) # if (defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_8) || defined(JE_FORCE_SYNC_COMPARE_AND_SWAP_8)) -ATOMIC_INLINE uint64_t atomic_add_uint64(uint64_t *p, uint64_t x) +ATOMIC_INLINE uint64_t atomic_add_and_fetch_uint64(uint64_t *p, uint64_t x) { return __sync_add_and_fetch(p, x); } -ATOMIC_INLINE uint64_t atomic_sub_uint64(uint64_t *p, uint64_t x) +ATOMIC_INLINE uint64_t atomic_sub_and_fetch_uint64(uint64_t *p, uint64_t x) { return __sync_sub_and_fetch(p, x); } +ATOMIC_INLINE uint64_t atomic_fetch_and_add_uint64(uint64_t *p, uint64_t x) +{ + return __sync_fetch_and_add(p, x); +} + +ATOMIC_INLINE uint64_t atomic_fetch_and_sub_uint64(uint64_t *p, uint64_t x) +{ + return __sync_fetch_and_sub(p, x); +} + ATOMIC_INLINE uint64_t atomic_cas_uint64(uint64_t *v, uint64_t old, uint64_t _new) { return __sync_val_compare_and_swap(v, old, _new); } # elif (defined(__amd64__) || defined(__x86_64__)) -ATOMIC_INLINE uint64_t atomic_add_uint64(uint64_t *p, uint64_t x) +ATOMIC_INLINE uint64_t atomic_fetch_and_add_uint64(uint64_t *p, uint64_t x) { asm volatile ( "lock; xaddq %0, %1;" @@ -83,7 +93,7 @@ ATOMIC_INLINE uint64_t atomic_add_uint64(uint64_t *p, uint64_t x) return x; } -ATOMIC_INLINE uint64_t atomic_sub_uint64(uint64_t *p, uint64_t x) +ATOMIC_INLINE uint64_t atomic_fetch_and_sub_uint64(uint64_t *p, uint64_t x) { x = (uint64_t)(-(int64_t)x); asm volatile ( @@ -94,6 +104,16 @@ ATOMIC_INLINE uint64_t atomic_sub_uint64(uint64_t *p, uint64_t x) return x; } +ATOMIC_INLINE uint64_t atomic_add_and_fetch_uint64(uint64_t *p, uint64_t x) +{ + return atomic_fetch_and_add_uint64(p, x) + x; +} + +ATOMIC_INLINE uint64_t atomic_sub_and_fetch_uint64(uint64_t *p, uint64_t x) +{ + return atomic_fetch_and_sub_uint64(p, x) - x; +} + ATOMIC_INLINE uint64_t atomic_cas_uint64(uint64_t *v, uint64_t old, uint64_t _new) { uint64_t ret; @@ -112,12 +132,12 @@ ATOMIC_INLINE uint64_t atomic_cas_uint64(uint64_t *v, uint64_t old, uint64_t _ne /******************************************************************************/ /* 32-bit operations. */ #if (defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4) || defined(JE_FORCE_SYNC_COMPARE_AND_SWAP_4)) -ATOMIC_INLINE uint32_t atomic_add_uint32(uint32_t *p, uint32_t x) +ATOMIC_INLINE uint32_t atomic_add_and_fetch_uint32(uint32_t *p, uint32_t x) { return __sync_add_and_fetch(p, x); } -ATOMIC_INLINE uint32_t atomic_sub_uint32(uint32_t *p, uint32_t x) +ATOMIC_INLINE uint32_t atomic_sub_and_fetch_uint32(uint32_t *p, uint32_t x) { return __sync_sub_and_fetch(p, x); } @@ -127,7 +147,7 @@ ATOMIC_INLINE uint32_t atomic_cas_uint32(uint32_t *v, uint32_t old, uint32_t _ne return __sync_val_compare_and_swap(v, old, _new); } #elif (defined(__i386__) || defined(__amd64__) || defined(__x86_64__)) -ATOMIC_INLINE uint32_t atomic_add_uint32(uint32_t *p, uint32_t x) +ATOMIC_INLINE uint32_t atomic_add_and_fetch_uint32(uint32_t *p, uint32_t x) { uint32_t ret = x; asm volatile ( @@ -138,7 +158,7 @@ ATOMIC_INLINE uint32_t atomic_add_uint32(uint32_t *p, uint32_t x) return ret+x; } -ATOMIC_INLINE uint32_t atomic_sub_uint32(uint32_t *p, uint32_t x) +ATOMIC_INLINE uint32_t atomic_sub_and_fetch_uint32(uint32_t *p, uint32_t x) { ret = (uint32_t)(-(int32_t)x); asm volatile ( @@ -169,6 +189,16 @@ ATOMIC_INLINE uint32_t atomic_fetch_and_add_uint32(uint32_t *p, uint32_t x) return __sync_fetch_and_add(p, x); } +ATOMIC_INLINE uint32_t atomic_fetch_and_or_uint32(uint32_t *p, uint32_t x) +{ + return __sync_fetch_and_or(p, x); +} + +ATOMIC_INLINE uint32_t atomic_fetch_and_and_uint32(uint32_t *p, uint32_t x) +{ + return __sync_fetch_and_and(p, x); +} + #else # error "Missing implementation for 32-bit atomic operations" #endif diff --git a/intern/cycles/app/cycles_standalone.cpp b/intern/cycles/app/cycles_standalone.cpp index e8168bc15ff..b21e8630cdb 100644 --- a/intern/cycles/app/cycles_standalone.cpp +++ b/intern/cycles/app/cycles_standalone.cpp @@ -337,7 +337,7 @@ static void options_parse(int argc, const char **argv) /* device names */ string device_names = ""; - string devicename = "cpu"; + string devicename = "CPU"; bool list = false; vector<DeviceType>& types = Device::available_types(); diff --git a/intern/cycles/app/cycles_xml.cpp b/intern/cycles/app/cycles_xml.cpp index f118815099a..a0c81806350 100644 --- a/intern/cycles/app/cycles_xml.cpp +++ b/intern/cycles/app/cycles_xml.cpp @@ -210,17 +210,6 @@ static void xml_read_camera(XMLReadState& state, pugi::xml_node node) /* Shader */ -static string xml_socket_name(const char *name) -{ - string sname = name; - size_t i; - - while((i = sname.find(" ")) != string::npos) - sname.replace(i, 1, ""); - - return sname; -} - static void xml_read_shader_graph(XMLReadState& state, Shader *shader, pugi::xml_node graph_node) { xml_read_node(state, shader, graph_node); @@ -254,7 +243,7 @@ static void xml_read_shader_graph(XMLReadState& state, Shader *shader, pugi::xml ShaderNode *fromnode = (ShaderNode*)graph_reader.node_map[from_node_name]; foreach(ShaderOutput *out, fromnode->outputs) - if(string_iequals(xml_socket_name(out->name().c_str()), from_socket_name.c_str())) + if(string_iequals(out->socket_type.name.string(), from_socket_name.string())) output = out; if(!output) @@ -267,7 +256,7 @@ static void xml_read_shader_graph(XMLReadState& state, Shader *shader, pugi::xml ShaderNode *tonode = (ShaderNode*)graph_reader.node_map[to_node_name]; foreach(ShaderInput *in, tonode->inputs) - if(string_iequals(xml_socket_name(in->name().c_str()), to_socket_name.c_str())) + if(string_iequals(in->socket_type.name.string(), to_socket_name.string())) input = in; if(!input) @@ -405,7 +394,7 @@ static void xml_read_mesh(const XMLReadState& state, pugi::xml_node node) int shader = 0; bool smooth = state.smooth; - /* read vertices and polygons, RIB style */ + /* read vertices and polygons */ vector<float3> P; vector<float> UV; vector<int> verts, nverts; @@ -531,8 +520,12 @@ static void xml_read_mesh(const XMLReadState& state, pugi::xml_node node) sdparams.objecttoworld = state.tfm; } - /* temporary for test compatibility */ - mesh->attributes.remove(ATTR_STD_VERTEX_NORMAL); + /* we don't yet support arbitrary attributes, for now add vertex + * coordinates as generated coordinates if requested */ + if (mesh->need_attribute(state.scene, ATTR_STD_GENERATED)) { + Attribute *attr = mesh->attributes.add(ATTR_STD_GENERATED); + memcpy(attr->data_float3(), mesh->verts.data(), sizeof(float3)*mesh->verts.size()); + } } /* Light */ diff --git a/intern/cycles/blender/CCL_api.h b/intern/cycles/blender/CCL_api.h index d3a68c4db4f..233ffc8802c 100644 --- a/intern/cycles/blender/CCL_api.h +++ b/intern/cycles/blender/CCL_api.h @@ -21,17 +21,6 @@ extern "C" { #endif -/* returns a list of devices for selection, array is empty identifier - * terminated and must not be freed */ - -typedef struct CCLDeviceInfo { - char identifier[128]; - char name[512]; - int value; -} CCLDeviceInfo; - -CCLDeviceInfo *CCL_compute_device_list(int device_type); - /* create python module _cycles used by addon */ void *CCL_python_module_init(void); diff --git a/intern/cycles/blender/addon/properties.py b/intern/cycles/blender/addon/properties.py index 977d7f75bb7..27c9b922042 100644 --- a/intern/cycles/blender/addon/properties.py +++ b/intern/cycles/blender/addon/properties.py @@ -21,7 +21,8 @@ from bpy.props import (BoolProperty, EnumProperty, FloatProperty, IntProperty, - PointerProperty) + PointerProperty, + StringProperty) # enums @@ -122,6 +123,12 @@ enum_volume_interpolation = ( ('CUBIC', "Cubic", "Smoothed high quality interpolation, but slower") ) +enum_device_type = ( + ('CPU', "CPU", "CPU", 0), + ('CUDA', "CUDA", "CUDA", 1), + ('OPENCL', "OpenCL", "OpenCL", 2) + ) + class CyclesRenderSettings(bpy.types.PropertyGroup): @classmethod @@ -266,6 +273,13 @@ class CyclesRenderSettings(bpy.types.PropertyGroup): description="Sample all lights (for indirect samples), rather than randomly picking one", default=True, ) + cls.light_sampling_threshold = FloatProperty( + name="Light Sampling Threshold", + description="Probabilistically terminate light samples when the light contribution is below this threshold (more noise but faster rendering). " + "Zero disables the test and never ignores lights", + min=0.0, max=1.0, + default=0.05, + ) cls.caustics_reflective = BoolProperty( name="Reflective Caustics", @@ -1123,6 +1137,103 @@ class CyclesCurveSettings(bpy.types.PropertyGroup): del bpy.types.ParticleSettings.cycles +class CyclesDeviceSettings(bpy.types.PropertyGroup): + @classmethod + def register(cls): + cls.id = StringProperty(name="ID") + cls.name = StringProperty(name="Name") + cls.use = BoolProperty(name="Use", default=True) + cls.type = EnumProperty(name="Type", items=enum_device_type, default='CUDA') + + +class CyclesPreferences(bpy.types.AddonPreferences): + bl_idname = __package__ + + def get_device_types(self, context): + import _cycles + has_cuda, has_opencl = _cycles.get_device_types() + list = [('NONE', "None", "Don't use compute device", 0)] + if has_cuda: + list.append(('CUDA', "CUDA", "Use CUDA for GPU acceleration", 1)) + if has_opencl: + list.append(('OPENCL', "OpenCL", "Use OpenCL for GPU acceleration", 2)) + return list + + compute_device_type = EnumProperty( + name="Compute Device Type", + description="Device to use for computation (rendering with Cycles)", + items=get_device_types, + ) + + devices = bpy.props.CollectionProperty(type=CyclesDeviceSettings) + + def get_devices(self): + import _cycles + # Layout of the device tuples: (Name, Type, Internal ID, Persistent ID) + device_list = _cycles.available_devices() + + cuda_devices = [] + opencl_devices = [] + for device in device_list: + if not device[1] in {'CUDA', 'OPENCL'}: + continue + + entry = None + # Try to find existing Device entry + for dev in self.devices: + if dev.id == device[2] and dev.type == device[1]: + entry = dev + break + # Create new entry if no existing one was found + if not entry: + entry = self.devices.add() + entry.id = device[2] + entry.name = device[0] + entry.type = device[1] + + # Sort entries into lists + if entry.type == 'CUDA': + cuda_devices.append(entry) + elif entry.type == 'OPENCL': + opencl_devices.append(entry) + return cuda_devices, opencl_devices + + + def has_active_device(self): + import _cycles + device_list = _cycles.available_devices() + for device in device_list: + if device[1] != self.compute_device_type: + continue + if any(dev.use and dev.id == device[2] for dev in self.devices): + return True + return False + + + def draw_impl(self, layout, context): + layout.label(text="Compute Device:") + layout.row().prop(self, "compute_device_type", expand=True) + + cuda_devices, opencl_devices = self.get_devices() + row = layout.row() + + if cuda_devices: + col = row.column(align=True) + col.label(text="CUDA devices:") + for device in cuda_devices: + col.prop(device, "use", text=device.name, toggle=True) + + if opencl_devices: + col = row.column(align=True) + col.label(text="OpenCL devices:") + for device in opencl_devices: + col.prop(device, "use", text=device.name, toggle=True) + + + def draw(self, context): + self.draw_impl(self.layout, context) + + def register(): bpy.utils.register_class(CyclesRenderSettings) bpy.utils.register_class(CyclesCameraSettings) @@ -1134,6 +1245,8 @@ def register(): bpy.utils.register_class(CyclesObjectSettings) bpy.utils.register_class(CyclesCurveRenderSettings) bpy.utils.register_class(CyclesCurveSettings) + bpy.utils.register_class(CyclesDeviceSettings) + bpy.utils.register_class(CyclesPreferences) def unregister(): @@ -1147,3 +1260,5 @@ def unregister(): bpy.utils.unregister_class(CyclesVisibilitySettings) bpy.utils.unregister_class(CyclesCurveRenderSettings) bpy.utils.unregister_class(CyclesCurveSettings) + bpy.utils.unregister_class(CyclesDeviceSettings) + bpy.utils.unregister_class(CyclesPreferences) diff --git a/intern/cycles/blender/addon/ui.py b/intern/cycles/blender/addon/ui.py index 52872d2b83f..f28fa0d52ba 100644 --- a/intern/cycles/blender/addon/ui.py +++ b/intern/cycles/blender/addon/ui.py @@ -53,25 +53,26 @@ class CyclesButtonsPanel: return rd.engine in cls.COMPAT_ENGINES +def get_device_type(context): + return context.user_preferences.addons[__package__].preferences.compute_device_type + + def use_cpu(context): cscene = context.scene.cycles - device_type = context.user_preferences.system.compute_device_type - return (device_type == 'NONE' or cscene.device == 'CPU') + return (get_device_type(context) == 'NONE' or cscene.device == 'CPU') def use_opencl(context): cscene = context.scene.cycles - device_type = context.user_preferences.system.compute_device_type - return (device_type == 'OPENCL' and cscene.device == 'GPU') + return (get_device_type(context) == 'OPENCL' and cscene.device == 'GPU') def use_cuda(context): cscene = context.scene.cycles - device_type = context.user_preferences.system.compute_device_type - return (device_type == 'CUDA' and cscene.device == 'GPU') + return (get_device_type(context) == 'CUDA' and cscene.device == 'GPU') def use_branched_path(context): @@ -85,6 +86,14 @@ def use_sample_all_lights(context): return cscene.sample_all_lights_direct or cscene.sample_all_lights_indirect +def show_device_selection(context): + type = get_device_type(context) + if type == 'NETWORK': + return True + if not type in {'CUDA', 'OPENCL'}: + return False + return context.user_preferences.addons[__package__].preferences.has_active_device() + def draw_samples_info(layout, context): cscene = context.scene.cycles @@ -141,7 +150,6 @@ class CyclesRender_PT_sampling(CyclesButtonsPanel, Panel): scene = context.scene cscene = scene.cycles - device_type = context.user_preferences.system.compute_device_type row = layout.row(align=True) row.menu("CYCLES_MT_sampling_presets", text=bpy.types.CYCLES_MT_sampling_presets.bl_label) @@ -150,7 +158,7 @@ class CyclesRender_PT_sampling(CyclesButtonsPanel, Panel): row = layout.row() sub = row.row() - sub.active = device_type != 'OPENCL' or use_cpu(context) + sub.active = get_device_type(context) != 'OPENCL' or use_cpu(context) sub.prop(cscene, "progressive", text="") row.prop(cscene, "use_square_samples") @@ -166,6 +174,7 @@ class CyclesRender_PT_sampling(CyclesButtonsPanel, Panel): sub.prop(cscene, "sample_clamp_direct") sub.prop(cscene, "sample_clamp_indirect") + sub.prop(cscene, "light_sampling_threshold") if cscene.progressive == 'PATH' or use_branched_path(context) is False: col = split.column() @@ -1605,9 +1614,13 @@ def draw_device(self, context): layout.prop(cscene, "feature_set") - device_type = context.user_preferences.system.compute_device_type - if device_type in {'CUDA', 'OPENCL', 'NETWORK'}: - layout.prop(cscene, "device") + split = layout.split(percentage=1/3) + split.label("Device:") + row = split.row(align=True) + sub = row.split(align=True) + sub.active = show_device_selection(context) + sub.prop(cscene, "device", text="") + row.operator("wm.addon_userpref_show", text="Preferences", icon='PREFERENCES').module = __package__ if engine.with_osl() and use_cpu(context): layout.prop(cscene, "shading_system") diff --git a/intern/cycles/blender/addon/version_update.py b/intern/cycles/blender/addon/version_update.py index 830723d6149..951afd37a92 100644 --- a/intern/cycles/blender/addon/version_update.py +++ b/intern/cycles/blender/addon/version_update.py @@ -278,3 +278,9 @@ def do_versions(self): cscene.pixel_filter_type = cscene.filter_type if cscene.filter_type == 'BLACKMAN_HARRIS': cscene.filter_type = 'GAUSSIAN' + + if bpy.data.version <= (2, 78, 2): + for scene in bpy.data.scenes: + cscene = scene.cycles + if not cscene.is_property_set("light_sampling_threshold"): + cscene.light_sampling_threshold = 0.0 diff --git a/intern/cycles/blender/blender_python.cpp b/intern/cycles/blender/blender_python.cpp index a50f5edb1df..438abc49f88 100644 --- a/intern/cycles/blender/blender_python.cpp +++ b/intern/cycles/blender/blender_python.cpp @@ -40,10 +40,6 @@ CCL_NAMESPACE_BEGIN namespace { -/* Device list stored static (used by compute_device_list()). */ -static ccl::vector<CCLDeviceInfo> device_list; -static ccl::DeviceType device_type = DEVICE_NONE; - /* Flag describing whether debug flags were synchronized from scene. */ bool debug_flags_set = false; @@ -195,7 +191,6 @@ static PyObject *exit_func(PyObject * /*self*/, PyObject * /*args*/) ShaderManager::free_memory(); TaskScheduler::free_memory(); Device::free_memory(); - device_list.free_memory(); Py_RETURN_NONE; } @@ -389,7 +384,12 @@ static PyObject *available_devices_func(PyObject * /*self*/, PyObject * /*args*/ for(size_t i = 0; i < devices.size(); i++) { DeviceInfo& device = devices[i]; - PyTuple_SET_ITEM(ret, i, PyUnicode_FromString(device.description.c_str())); + string type_name = Device::string_from_type(device.type); + PyObject *device_tuple = PyTuple_New(3); + PyTuple_SET_ITEM(device_tuple, 0, PyUnicode_FromString(device.description.c_str())); + PyTuple_SET_ITEM(device_tuple, 1, PyUnicode_FromString(type_name.c_str())); + PyTuple_SET_ITEM(device_tuple, 2, PyUnicode_FromString(device.id.c_str())); + PyTuple_SET_ITEM(ret, i, device_tuple); } return ret; @@ -676,6 +676,20 @@ static PyObject *set_resumable_chunks_func(PyObject * /*self*/, PyObject *args) Py_RETURN_NONE; } +static PyObject *get_device_types_func(PyObject * /*self*/, PyObject * /*args*/) +{ + vector<DeviceInfo>& devices = Device::available_devices(); + bool has_cuda = false, has_opencl = false; + for(int i = 0; i < devices.size(); i++) { + has_cuda |= (devices[i].type == DEVICE_CUDA); + has_opencl |= (devices[i].type == DEVICE_OPENCL); + } + PyObject *list = PyTuple_New(2); + PyTuple_SET_ITEM(list, 0, PyBool_FromLong(has_cuda)); + PyTuple_SET_ITEM(list, 1, PyBool_FromLong(has_opencl)); + return list; +} + static PyMethodDef methods[] = { {"init", init_func, METH_VARARGS, ""}, {"exit", exit_func, METH_VARARGS, ""}, @@ -703,6 +717,9 @@ static PyMethodDef methods[] = { /* Resumable render */ {"set_resumable_chunks", set_resumable_chunks_func, METH_VARARGS, ""}, + /* Compute Device selection */ + {"get_device_types", get_device_types_func, METH_VARARGS, ""}, + {NULL, NULL, 0, NULL}, }; @@ -715,47 +732,6 @@ static struct PyModuleDef module = { NULL, NULL, NULL, NULL }; -static CCLDeviceInfo *compute_device_list(DeviceType type) -{ - /* create device list if it's not already done */ - if(type != device_type) { - ccl::vector<DeviceInfo>& devices = ccl::Device::available_devices(); - - device_type = type; - device_list.clear(); - - /* add devices */ - int i = 0; - - foreach(DeviceInfo& info, devices) { - if(info.type == type || - (info.type == DEVICE_MULTI && info.multi_devices[0].type == type)) - { - CCLDeviceInfo cinfo; - - strncpy(cinfo.identifier, info.id.c_str(), sizeof(cinfo.identifier)); - cinfo.identifier[info.id.length()] = '\0'; - - strncpy(cinfo.name, info.description.c_str(), sizeof(cinfo.name)); - cinfo.name[info.description.length()] = '\0'; - - cinfo.value = i++; - - device_list.push_back(cinfo); - } - } - - /* null terminate */ - if(!device_list.empty()) { - CCLDeviceInfo cinfo = {"", "", 0}; - device_list.push_back(cinfo); - } - } - - return (device_list.empty())? NULL: &device_list[0]; -} - - CCL_NAMESPACE_END void *CCL_python_module_init() @@ -794,24 +770,3 @@ void *CCL_python_module_init() return (void*)mod; } - -CCLDeviceInfo *CCL_compute_device_list(int device_type) -{ - ccl::DeviceType type; - switch(device_type) { - case 0: - type = ccl::DEVICE_CUDA; - break; - case 1: - type = ccl::DEVICE_OPENCL; - break; - case 2: - type = ccl::DEVICE_NETWORK; - break; - default: - type = ccl::DEVICE_NONE; - break; - } - return ccl::compute_device_list(type); -} - diff --git a/intern/cycles/blender/blender_sync.cpp b/intern/cycles/blender/blender_sync.cpp index f99a4889d34..ed5c681c952 100644 --- a/intern/cycles/blender/blender_sync.cpp +++ b/intern/cycles/blender/blender_sync.cpp @@ -261,8 +261,17 @@ void BlenderSync::sync_integrator() integrator->filter_glossy = get_float(cscene, "blur_glossy"); integrator->seed = get_int(cscene, "seed"); - if(get_boolean(cscene, "use_animated_seed")) - integrator->seed = hash_int_2d(b_scene.frame_current(), get_int(cscene, "seed")); + if(get_boolean(cscene, "use_animated_seed")) { + integrator->seed = hash_int_2d(b_scene.frame_current(), + get_int(cscene, "seed")); + if(b_scene.frame_subframe() != 0.0f) { + /* TODO(sergey): Ideally should be some sort of hash_merge, + * but this is good enough for now. + */ + integrator->seed += hash_int_2d((int)(b_scene.frame_subframe() * (float)INT_MAX), + get_int(cscene, "seed")); + } + } integrator->sampling_pattern = (SamplingPattern)get_enum( cscene, @@ -290,6 +299,7 @@ void BlenderSync::sync_integrator() integrator->sample_all_lights_direct = get_boolean(cscene, "sample_all_lights_direct"); integrator->sample_all_lights_indirect = get_boolean(cscene, "sample_all_lights_indirect"); + integrator->light_sampling_threshold = get_float(cscene, "light_sampling_threshold"); int diffuse_samples = get_int(cscene, "diffuse_samples"); int glossy_samples = get_int(cscene, "glossy_samples"); @@ -536,7 +546,12 @@ SessionParams BlenderSync::get_session_params(BL::RenderEngine& b_engine, vector<DeviceInfo>& devices = Device::available_devices(); /* device default CPU */ - params.device = devices[0]; + foreach(DeviceInfo& device, devices) { + if(device.type == DEVICE_CPU) { + params.device = device; + break; + } + } if(get_enum(cscene, "device") == 2) { /* find network device */ @@ -545,17 +560,39 @@ SessionParams BlenderSync::get_session_params(BL::RenderEngine& b_engine, params.device = info; } else if(get_enum(cscene, "device") == 1) { - /* find GPU device with given id */ - PointerRNA systemptr = b_userpref.system().ptr; - PropertyRNA *deviceprop = RNA_struct_find_property(&systemptr, "compute_device"); - int device_id = b_userpref.system().compute_device(); + PointerRNA b_preferences; - const char *id; + BL::UserPreferences::addons_iterator b_addon_iter; + for(b_userpref.addons.begin(b_addon_iter); b_addon_iter != b_userpref.addons.end(); ++b_addon_iter) { + if(b_addon_iter->module() == "cycles") { + b_preferences = b_addon_iter->preferences().ptr; + break; + } + } + + int compute_device = get_enum(b_preferences, "compute_device_type"); + + if(compute_device != 0) { + vector<DeviceInfo> used_devices; + RNA_BEGIN(&b_preferences, device, "devices") { + if(get_enum(device, "type") == compute_device && get_boolean(device, "use")) { + string id = get_string(device, "id"); + foreach(DeviceInfo& info, devices) { + if(info.id == id) { + used_devices.push_back(info); + break; + } + } + } + } RNA_END - if(RNA_property_enum_identifier(NULL, &systemptr, deviceprop, device_id, &id)) { - foreach(DeviceInfo& info, devices) - if(info.id == id) - params.device = info; + if(used_devices.size() == 1) { + params.device = used_devices[0]; + } + else if(used_devices.size() > 1) { + params.device = Device::get_multi_device(used_devices); + } + /* Else keep using the CPU device that was set before. */ } } diff --git a/intern/cycles/cmake/external_libs.cmake b/intern/cycles/cmake/external_libs.cmake index 616dd940801..403a0540963 100644 --- a/intern/cycles/cmake/external_libs.cmake +++ b/intern/cycles/cmake/external_libs.cmake @@ -44,6 +44,10 @@ if(WITH_CYCLES_CUDA_BINARIES OR NOT WITH_CUDA_DYNLOAD) else() message(STATUS "CUDA compiler not found, disabling WITH_CYCLES_CUDA_BINARIES") set(WITH_CYCLES_CUDA_BINARIES OFF) + if(NOT WITH_CUDA_DYNLOAD) + message(STATUS "Additionally falling back to dynamic CUDA load") + set(WITH_CUDA_DYNLOAD ON) + endif() endif() endif() diff --git a/intern/cycles/device/device.cpp b/intern/cycles/device/device.cpp index 909ec7a6d60..ff9387b0a8a 100644 --- a/intern/cycles/device/device.cpp +++ b/intern/cycles/device/device.cpp @@ -258,33 +258,33 @@ Device *Device::create(DeviceInfo& info, Stats &stats, bool background) DeviceType Device::type_from_string(const char *name) { - if(strcmp(name, "cpu") == 0) + if(strcmp(name, "CPU") == 0) return DEVICE_CPU; - else if(strcmp(name, "cuda") == 0) + else if(strcmp(name, "CUDA") == 0) return DEVICE_CUDA; - else if(strcmp(name, "opencl") == 0) + else if(strcmp(name, "OPENCL") == 0) return DEVICE_OPENCL; - else if(strcmp(name, "network") == 0) + else if(strcmp(name, "NETWORK") == 0) return DEVICE_NETWORK; - else if(strcmp(name, "multi") == 0) + else if(strcmp(name, "MULTI") == 0) return DEVICE_MULTI; - + return DEVICE_NONE; } string Device::string_from_type(DeviceType type) { if(type == DEVICE_CPU) - return "cpu"; + return "CPU"; else if(type == DEVICE_CUDA) - return "cuda"; + return "CUDA"; else if(type == DEVICE_OPENCL) - return "opencl"; + return "OPENCL"; else if(type == DEVICE_NETWORK) - return "network"; + return "NETWORK"; else if(type == DEVICE_MULTI) - return "multi"; - + return "MULTI"; + return ""; } @@ -307,9 +307,6 @@ vector<DeviceType>& Device::available_types() #ifdef WITH_NETWORK types.push_back(DEVICE_NETWORK); #endif -#ifdef WITH_MULTI - types.push_back(DEVICE_MULTI); -#endif need_types_update = false; } @@ -331,10 +328,6 @@ vector<DeviceInfo>& Device::available_devices() device_opencl_info(devices); #endif -#ifdef WITH_MULTI - device_multi_info(devices); -#endif - device_cpu_info(devices); #ifdef WITH_NETWORK @@ -368,6 +361,29 @@ string Device::device_capabilities() return capabilities; } +DeviceInfo Device::get_multi_device(vector<DeviceInfo> subdevices) +{ + assert(subdevices.size() > 1); + + DeviceInfo info; + info.type = DEVICE_MULTI; + info.id = "MULTI"; + info.description = "Multi Device"; + info.multi_devices = subdevices; + info.num = 0; + + info.has_bindless_textures = true; + info.pack_images = false; + foreach(DeviceInfo &device, subdevices) { + assert(device.type == info.multi_devices[0].type); + + info.pack_images |= device.pack_images; + info.has_bindless_textures &= device.has_bindless_textures; + } + + return info; +} + void Device::tag_update() { need_types_update = true; diff --git a/intern/cycles/device/device.h b/intern/cycles/device/device.h index d990218a931..467b8dca354 100644 --- a/intern/cycles/device/device.h +++ b/intern/cycles/device/device.h @@ -51,7 +51,7 @@ class DeviceInfo { public: DeviceType type; string description; - string id; + string id; /* used for user preferences, should stay fixed with changing hardware config */ int num; bool display_device; bool advanced_shading; @@ -71,6 +71,12 @@ public: has_bindless_textures = false; use_split_kernel = false; } + + bool operator==(const DeviceInfo &info) { + /* Multiple Devices with the same ID would be very bad. */ + assert(id != info.id || (type == info.type && num == info.num && description == info.description)); + return id == info.id; + } }; class DeviceRequestedFeatures { @@ -287,6 +293,7 @@ public: static vector<DeviceType>& available_types(); static vector<DeviceInfo>& available_devices(); static string device_capabilities(); + static DeviceInfo get_multi_device(vector<DeviceInfo> subdevices); /* Tag devices lists for update. */ static void tag_update(); diff --git a/intern/cycles/device/device_cuda.cpp b/intern/cycles/device/device_cuda.cpp index 7f8a0bf2f43..a4818aa3b8d 100644 --- a/intern/cycles/device/device_cuda.cpp +++ b/intern/cycles/device/device_cuda.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include <climits> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -213,7 +214,8 @@ public: return; int major, minor; - cuDeviceComputeCapability(&major, &minor, cuDevId); + cuDeviceGetAttribute(&major, CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MAJOR, cuDevId); + cuDeviceGetAttribute(&minor, CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MINOR, cuDevId); cuDevArchitecture = major*100 + minor*10; cuda_pop_context(); @@ -233,7 +235,8 @@ public: bool support_device(const DeviceRequestedFeatures& /*requested_features*/) { int major, minor; - cuDeviceComputeCapability(&major, &minor, cuDevId); + cuDeviceGetAttribute(&major, CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MAJOR, cuDevId); + cuDeviceGetAttribute(&minor, CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MINOR, cuDevId); /* We only support sm_20 and above */ if(major < 2) { @@ -315,7 +318,8 @@ public: { /* Compute cubin name. */ int major, minor; - cuDeviceComputeCapability(&major, &minor, cuDevId); + cuDeviceGetAttribute(&major, CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MAJOR, cuDevId); + cuDeviceGetAttribute(&minor, CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MINOR, cuDevId); /* Attempt to use kernel provided with Blender. */ if(!use_adaptive_compilation()) { @@ -1394,8 +1398,8 @@ void device_cuda_info(vector<DeviceInfo>& devices) if(cuDeviceGetName(name, 256, num) != CUDA_SUCCESS) continue; - int major, minor; - cuDeviceComputeCapability(&major, &minor, num); + int major; + cuDeviceGetAttribute(&major, CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MAJOR, num); if(major < 2) { continue; } @@ -1404,13 +1408,18 @@ void device_cuda_info(vector<DeviceInfo>& devices) info.type = DEVICE_CUDA; info.description = string(name); - info.id = string_printf("CUDA_%d", num); info.num = num; info.advanced_shading = (major >= 2); info.has_bindless_textures = (major >= 3); info.pack_images = false; + int pci_location[3] = {0, 0, 0}; + cuDeviceGetAttribute(&pci_location[0], CU_DEVICE_ATTRIBUTE_PCI_DOMAIN_ID, num); + cuDeviceGetAttribute(&pci_location[1], CU_DEVICE_ATTRIBUTE_PCI_BUS_ID, num); + cuDeviceGetAttribute(&pci_location[2], CU_DEVICE_ATTRIBUTE_PCI_DEVICE_ID, num); + info.id = string_printf("CUDA_%s_%04x:%02x:%02x", name, pci_location[0], pci_location[1], pci_location[2]); + /* if device has a kernel timeout, assume it is used for display */ if(cuDeviceGetAttribute(&attr, CU_DEVICE_ATTRIBUTE_KERNEL_EXEC_TIMEOUT, num) == CUDA_SUCCESS && attr == 1) { info.description += " (Display)"; diff --git a/intern/cycles/device/device_intern.h b/intern/cycles/device/device_intern.h index 47584ae6d22..de487649045 100644 --- a/intern/cycles/device/device_intern.h +++ b/intern/cycles/device/device_intern.h @@ -33,7 +33,6 @@ void device_cpu_info(vector<DeviceInfo>& devices); void device_opencl_info(vector<DeviceInfo>& devices); void device_cuda_info(vector<DeviceInfo>& devices); void device_network_info(vector<DeviceInfo>& devices); -void device_multi_info(vector<DeviceInfo>& devices); string device_cpu_capabilities(void); string device_opencl_capabilities(void); diff --git a/intern/cycles/device/device_multi.cpp b/intern/cycles/device/device_multi.cpp index ef257358b22..48fd159d508 100644 --- a/intern/cycles/device/device_multi.cpp +++ b/intern/cycles/device/device_multi.cpp @@ -350,120 +350,5 @@ Device *device_multi_create(DeviceInfo& info, Stats &stats, bool background) return new MultiDevice(info, stats, background); } -static bool device_multi_add(vector<DeviceInfo>& devices, DeviceType type, bool with_display, bool with_advanced_shading, const char *id_fmt, int num) -{ - DeviceInfo info; - - /* create map to find duplicate descriptions */ - map<string, int> dupli_map; - map<string, int>::iterator dt; - int num_added = 0, num_display = 0; - - info.advanced_shading = with_advanced_shading; - info.pack_images = false; - info.has_bindless_textures = true; - - foreach(DeviceInfo& subinfo, devices) { - if(subinfo.type == type) { - if(subinfo.advanced_shading != info.advanced_shading) - continue; - if(subinfo.display_device) { - if(with_display) - num_display++; - else - continue; - } - - string key = subinfo.description; - - if(dupli_map.find(key) == dupli_map.end()) - dupli_map[key] = 1; - else - dupli_map[key]++; - - info.multi_devices.push_back(subinfo); - if(subinfo.display_device) - info.display_device = true; - info.pack_images = info.pack_images || subinfo.pack_images; - info.has_bindless_textures = info.has_bindless_textures && subinfo.has_bindless_textures; - num_added++; - } - } - - if(num_added <= 1 || (with_display && num_display == 0)) - return false; - - /* generate string */ - stringstream desc; - vector<string> last_tokens; - bool first = true; - - for(dt = dupli_map.begin(); dt != dupli_map.end(); dt++) { - if(!first) desc << " + "; - first = false; - - /* get name and count */ - string name = dt->first; - int count = dt->second; - - /* strip common prefixes */ - vector<string> tokens; - string_split(tokens, dt->first); - - if(tokens.size() > 1) { - int i; - - for(i = 0; i < tokens.size() && i < last_tokens.size(); i++) - if(tokens[i] != last_tokens[i]) - break; - - name = ""; - for(; i < tokens.size(); i++) { - name += tokens[i]; - if(i != tokens.size() - 1) - name += " "; - } - } - - last_tokens = tokens; - - /* add */ - if(count > 1) - desc << name << " (" << count << "x)"; - else - desc << name; - } - - /* add info */ - info.type = DEVICE_MULTI; - info.description = desc.str(); - info.id = string_printf(id_fmt, num); - info.display_device = with_display; - info.num = 0; - - if(with_display) - devices.push_back(info); - else - devices.insert(devices.begin(), info); - - return true; -} - -void device_multi_info(vector<DeviceInfo>& devices) -{ - int num = 0; - - if(!device_multi_add(devices, DEVICE_CUDA, false, true, "CUDA_MULTI_%d", num++)) - device_multi_add(devices, DEVICE_CUDA, false, false, "CUDA_MULTI_%d", num++); - if(!device_multi_add(devices, DEVICE_CUDA, true, true, "CUDA_MULTI_%d", num++)) - device_multi_add(devices, DEVICE_CUDA, true, false, "CUDA_MULTI_%d", num++); - - num = 0; - if(!device_multi_add(devices, DEVICE_OPENCL, false, true, "OPENCL_MULTI_%d", num++)) - device_multi_add(devices, DEVICE_OPENCL, false, false, "OPENCL_MULTI_%d", num++); - if(!device_multi_add(devices, DEVICE_OPENCL, true, true, "OPENCL_MULTI_%d", num++)) - device_multi_add(devices, DEVICE_OPENCL, true, false, "OPENCL_MULTI_%d", num++); -} - CCL_NAMESPACE_END diff --git a/intern/cycles/device/device_opencl.cpp b/intern/cycles/device/device_opencl.cpp index 45cf6b074e9..ba94c592a5f 100644 --- a/intern/cycles/device/device_opencl.cpp +++ b/intern/cycles/device/device_opencl.cpp @@ -83,17 +83,22 @@ void device_opencl_info(vector<DeviceInfo>& devices) const string& platform_name = platform_device.platform_name; const cl_device_type device_type = platform_device.device_type; const string& device_name = platform_device.device_name; + string hardware_id = platform_device.hardware_id; + if(hardware_id == "") { + hardware_id = string_printf("ID_%d", num_devices); + } + DeviceInfo info; info.type = DEVICE_OPENCL; info.description = string_remove_trademark(string(device_name)); info.num = num_devices; - info.id = string_printf("OPENCL_%d", info.num); /* We don't know if it's used for display, but assume it is. */ info.display_device = true; info.advanced_shading = OpenCLInfo::kernel_use_advanced_shading(platform_name); info.pack_images = true; info.use_split_kernel = OpenCLInfo::kernel_use_split(platform_name, device_type); + info.id = string("OPENCL_") + platform_name + "_" + device_name + "_" + hardware_id; devices.push_back(info); num_devices++; } diff --git a/intern/cycles/device/opencl/opencl.h b/intern/cycles/device/opencl/opencl.h index 30a35acbb2a..054ac9014f0 100644 --- a/intern/cycles/device/opencl/opencl.h +++ b/intern/cycles/device/opencl/opencl.h @@ -55,17 +55,20 @@ struct OpenCLPlatformDevice { const string& platform_name, cl_device_id device_id, cl_device_type device_type, - const string& device_name) + const string& device_name, + const string& hardware_id) : platform_id(platform_id), platform_name(platform_name), device_id(device_id), device_type(device_type), - device_name(device_name) {} + device_name(device_name), + hardware_id(hardware_id) {} cl_platform_id platform_id; string platform_name; cl_device_id device_id; cl_device_type device_type; string device_name; + string hardware_id; }; /* Contains all static OpenCL helper functions. */ @@ -83,6 +86,8 @@ public: string *error = NULL); static bool device_version_check(cl_device_id device, string *error = NULL); + static string get_hardware_id(string platform_name, + cl_device_id device_id); static void get_usable_devices(vector<OpenCLPlatformDevice> *usable_devices, bool force_all = false); }; diff --git a/intern/cycles/device/opencl/opencl_util.cpp b/intern/cycles/device/opencl/opencl_util.cpp index e425ae8e2e8..36eb70b8c85 100644 --- a/intern/cycles/device/opencl/opencl_util.cpp +++ b/intern/cycles/device/opencl/opencl_util.cpp @@ -661,6 +661,27 @@ bool OpenCLInfo::device_version_check(cl_device_id device, return true; } +string OpenCLInfo::get_hardware_id(string platform_name, cl_device_id device_id) +{ + if(platform_name == "AMD Accelerated Parallel Processing" || platform_name == "Apple") { + /* Use cl_amd_device_topology extension. */ + cl_char topology[24]; + if(clGetDeviceInfo(device_id, 0x4037, sizeof(topology), topology, NULL) == CL_SUCCESS && topology[0] == 1) { + return string_printf("%02x:%02x.%01x", topology[21], topology[22], topology[23]); + } + } + else if(platform_name == "NVIDIA CUDA") { + /* Use two undocumented options of the cl_nv_device_attribute_query extension. */ + cl_int bus_id, slot_id; + if(clGetDeviceInfo(device_id, 0x4008, sizeof(cl_int), &bus_id, NULL) == CL_SUCCESS && + clGetDeviceInfo(device_id, 0x4009, sizeof(cl_int), &slot_id, NULL) == CL_SUCCESS) { + return string_printf("%02x:%02x.%01x", bus_id, slot_id>>3, slot_id & 0x7); + } + } + /* No general way to get a hardware ID from OpenCL => give up. */ + return ""; +} + void OpenCLInfo::get_usable_devices(vector<OpenCLPlatformDevice> *usable_devices, bool force_all) { @@ -773,11 +794,13 @@ void OpenCLInfo::get_usable_devices(vector<OpenCLPlatformDevice> *usable_devices continue; } FIRST_VLOG(2) << "Adding new device " << device_name << "."; + string hardware_id = get_hardware_id(platform_name, device_id); usable_devices->push_back(OpenCLPlatformDevice(platform_id, platform_name, device_id, device_type, - device_name)); + device_name, + hardware_id)); } else { FIRST_VLOG(2) << "Ignoring device " << device_name diff --git a/intern/cycles/kernel/CMakeLists.txt b/intern/cycles/kernel/CMakeLists.txt index 2c95825435d..105207311c9 100644 --- a/intern/cycles/kernel/CMakeLists.txt +++ b/intern/cycles/kernel/CMakeLists.txt @@ -178,6 +178,7 @@ set(SRC_UTIL_HEADERS ../util/util_atomic.h ../util/util_color.h ../util/util_half.h + ../util/util_hash.h ../util/util_math.h ../util/util_math_fast.h ../util/util_static_assert.h diff --git a/intern/cycles/kernel/geom/geom_object.h b/intern/cycles/kernel/geom/geom_object.h index 6b42f66b0d5..9f0fe032ba4 100644 --- a/intern/cycles/kernel/geom/geom_object.h +++ b/intern/cycles/kernel/geom/geom_object.h @@ -162,10 +162,14 @@ ccl_device_inline void object_inverse_position_transform(KernelGlobals *kg, cons ccl_device_inline void object_inverse_normal_transform(KernelGlobals *kg, const ShaderData *sd, float3 *N) { #ifdef __OBJECT_MOTION__ - *N = normalize(transform_direction_transposed_auto(&ccl_fetch(sd, ob_tfm), *N)); + if((ccl_fetch(sd, object) != OBJECT_NONE) || (ccl_fetch(sd, type) == PRIMITIVE_LAMP)) { + *N = normalize(transform_direction_transposed_auto(&ccl_fetch(sd, ob_tfm), *N)); + } #else - Transform tfm = object_fetch_transform(kg, ccl_fetch(sd, object), OBJECT_TRANSFORM); - *N = normalize(transform_direction_transposed(&tfm, *N)); + if(ccl_fetch(sd, object) != OBJECT_NONE) { + Transform tfm = object_fetch_transform(kg, ccl_fetch(sd, object), OBJECT_TRANSFORM); + *N = normalize(transform_direction_transposed(&tfm, *N)); + } #endif } diff --git a/intern/cycles/kernel/kernel_accumulate.h b/intern/cycles/kernel/kernel_accumulate.h index 623c1dcaaa1..6c3ee6b8098 100644 --- a/intern/cycles/kernel/kernel_accumulate.h +++ b/intern/cycles/kernel/kernel_accumulate.h @@ -96,7 +96,7 @@ ccl_device_inline bool bsdf_eval_is_zero(BsdfEval *eval) } } -ccl_device_inline void bsdf_eval_mul(BsdfEval *eval, float3 value) +ccl_device_inline void bsdf_eval_mul(BsdfEval *eval, float value) { #ifdef __PASSES__ if(eval->use_light_pass) { @@ -115,6 +115,36 @@ ccl_device_inline void bsdf_eval_mul(BsdfEval *eval, float3 value) } } +ccl_device_inline void bsdf_eval_mul3(BsdfEval *eval, float3 value) +{ +#ifdef __PASSES__ + if(eval->use_light_pass) { + eval->diffuse *= value; + eval->glossy *= value; + eval->transmission *= value; + eval->subsurface *= value; + eval->scatter *= value; + + /* skipping transparent, this function is used by for eval(), will be zero then */ + } + else + eval->diffuse *= value; +#else + eval->diffuse *= value; +#endif +} + +ccl_device_inline float3 bsdf_eval_sum(BsdfEval *eval) +{ +#ifdef __PASSES__ + if(eval->use_light_pass) { + return eval->diffuse + eval->glossy + eval->transmission + eval->subsurface + eval->scatter; + } + else +#endif + return eval->diffuse; +} + /* Path Radiance * * We accumulate different render passes separately. After summing at the end @@ -193,8 +223,7 @@ ccl_device_inline void path_radiance_bsdf_bounce(PathRadiance *L, ccl_addr_space } else { /* transparent bounce before first hit, or indirectly visible through BSDF */ - float3 sum = (bsdf_eval->diffuse + bsdf_eval->glossy + bsdf_eval->transmission + bsdf_eval->transparent + - bsdf_eval->subsurface + bsdf_eval->scatter) * inverse_pdf; + float3 sum = (bsdf_eval_sum(bsdf_eval) + bsdf_eval->transparent) * inverse_pdf; *throughput *= sum; } } @@ -264,8 +293,7 @@ ccl_device_inline void path_radiance_accum_light(PathRadiance *L, float3 through } else { /* indirectly visible lighting after BSDF bounce */ - float3 sum = bsdf_eval->diffuse + bsdf_eval->glossy + bsdf_eval->transmission + bsdf_eval->subsurface + bsdf_eval->scatter; - L->indirect += throughput*sum*shadow; + L->indirect += throughput*bsdf_eval_sum(bsdf_eval)*shadow; } } else diff --git a/intern/cycles/kernel/kernel_bake.h b/intern/cycles/kernel/kernel_bake.h index 84575d35d7f..c32ac6ccf41 100644 --- a/intern/cycles/kernel/kernel_bake.h +++ b/intern/cycles/kernel/kernel_bake.h @@ -63,7 +63,7 @@ ccl_device_inline void compute_light_pass(KernelGlobals *kg, /* sample ambient occlusion */ if(pass_filter & BAKE_FILTER_AO) { - kernel_path_ao(kg, sd, &emission_sd, &L_sample, &state, &rng, throughput); + kernel_path_ao(kg, sd, &emission_sd, &L_sample, &state, &rng, throughput, shader_bsdf_alpha(kg, sd)); } /* sample emission */ diff --git a/intern/cycles/kernel/kernel_emission.h b/intern/cycles/kernel/kernel_emission.h index 9e4a631b998..8c7c651a053 100644 --- a/intern/cycles/kernel/kernel_emission.h +++ b/intern/cycles/kernel/kernel_emission.h @@ -94,7 +94,8 @@ ccl_device_noinline bool direct_emission(KernelGlobals *kg, ccl_addr_space PathState *state, Ray *ray, BsdfEval *eval, - bool *is_lamp) + bool *is_lamp, + float rand_terminate) { if(ls->pdf == 0.0f) return false; @@ -134,7 +135,7 @@ ccl_device_noinline bool direct_emission(KernelGlobals *kg, shader_bsdf_eval(kg, sd, ls->D, eval, ls->pdf, ls->shader & SHADER_USE_MIS); #endif - bsdf_eval_mul(eval, light_eval/ls->pdf); + bsdf_eval_mul3(eval, light_eval/ls->pdf); #ifdef __PASSES__ /* use visibility flag to skip lights */ @@ -155,6 +156,16 @@ ccl_device_noinline bool direct_emission(KernelGlobals *kg, if(bsdf_eval_is_zero(eval)) return false; + if(kernel_data.integrator.light_inv_rr_threshold > 0.0f) { + float probability = max3(bsdf_eval_sum(eval)) * kernel_data.integrator.light_inv_rr_threshold; + if(probability < 1.0f) { + if(rand_terminate >= probability) { + return false; + } + bsdf_eval_mul(eval, 1.0f / probability); + } + } + if(ls->shader & SHADER_CAST_SHADOW) { /* setup ray */ bool transmit = (dot(ccl_fetch(sd, Ng), ls->D) < 0.0f); diff --git a/intern/cycles/kernel/kernel_passes.h b/intern/cycles/kernel/kernel_passes.h index 20cf3fa931b..7aec47e4957 100644 --- a/intern/cycles/kernel/kernel_passes.h +++ b/intern/cycles/kernel/kernel_passes.h @@ -20,7 +20,7 @@ ccl_device_inline void kernel_write_pass_float(ccl_global float *buffer, int sam { ccl_global float *buf = buffer; #if defined(__SPLIT_KERNEL__) && defined(__WORK_STEALING__) - atomic_add_float(buf, value); + atomic_add_and_fetch_float(buf, value); #else *buf = (sample == 0)? value: *buf + value; #endif // __SPLIT_KERNEL__ && __WORK_STEALING__ @@ -33,9 +33,9 @@ ccl_device_inline void kernel_write_pass_float3(ccl_global float *buffer, int sa ccl_global float *buf_y = buffer + 1; ccl_global float *buf_z = buffer + 2; - atomic_add_float(buf_x, value.x); - atomic_add_float(buf_y, value.y); - atomic_add_float(buf_z, value.z); + atomic_add_and_fetch_float(buf_x, value.x); + atomic_add_and_fetch_float(buf_y, value.y); + atomic_add_and_fetch_float(buf_z, value.z); #else ccl_global float3 *buf = (ccl_global float3*)buffer; *buf = (sample == 0)? value: *buf + value; @@ -50,10 +50,10 @@ ccl_device_inline void kernel_write_pass_float4(ccl_global float *buffer, int sa ccl_global float *buf_z = buffer + 2; ccl_global float *buf_w = buffer + 3; - atomic_add_float(buf_x, value.x); - atomic_add_float(buf_y, value.y); - atomic_add_float(buf_z, value.z); - atomic_add_float(buf_w, value.w); + atomic_add_and_fetch_float(buf_x, value.x); + atomic_add_and_fetch_float(buf_y, value.y); + atomic_add_and_fetch_float(buf_z, value.z); + atomic_add_and_fetch_float(buf_w, value.w); #else ccl_global float4 *buf = (ccl_global float4*)buffer; *buf = (sample == 0)? value: *buf + value; diff --git a/intern/cycles/kernel/kernel_path.h b/intern/cycles/kernel/kernel_path.h index 7558fb94478..6d89a89ed5b 100644 --- a/intern/cycles/kernel/kernel_path.h +++ b/intern/cycles/kernel/kernel_path.h @@ -53,6 +53,47 @@ CCL_NAMESPACE_BEGIN +ccl_device_noinline void kernel_path_ao(KernelGlobals *kg, + ShaderData *sd, + ShaderData *emission_sd, + PathRadiance *L, + PathState *state, + RNG *rng, + float3 throughput, + float3 ao_alpha) +{ + /* todo: solve correlation */ + float bsdf_u, bsdf_v; + + path_state_rng_2D(kg, rng, state, PRNG_BSDF_U, &bsdf_u, &bsdf_v); + + float ao_factor = kernel_data.background.ao_factor; + float3 ao_N; + float3 ao_bsdf = shader_bsdf_ao(kg, sd, ao_factor, &ao_N); + float3 ao_D; + float ao_pdf; + + sample_cos_hemisphere(ao_N, bsdf_u, bsdf_v, &ao_D, &ao_pdf); + + if(dot(ccl_fetch(sd, Ng), ao_D) > 0.0f && ao_pdf != 0.0f) { + Ray light_ray; + float3 ao_shadow; + + light_ray.P = ray_offset(ccl_fetch(sd, P), ccl_fetch(sd, Ng)); + light_ray.D = ao_D; + light_ray.t = kernel_data.background.ao_distance; +#ifdef __OBJECT_MOTION__ + light_ray.time = ccl_fetch(sd, time); +#endif /* __OBJECT_MOTION__ */ + light_ray.dP = ccl_fetch(sd, dP); + light_ray.dD = differential3_zero(); + + if(!shadow_blocked(kg, emission_sd, state, &light_ray, &ao_shadow)) { + path_radiance_accum_ao(L, throughput, ao_alpha, ao_bsdf, ao_shadow, state->bounce); + } + } +} + ccl_device void kernel_path_indirect(KernelGlobals *kg, ShaderData *sd, ShaderData *emission_sd, @@ -97,7 +138,7 @@ ccl_device void kernel_path_indirect(KernelGlobals *kg, state->bounce); } } -#endif +#endif /* __LAMP_MIS__ */ #ifdef __VOLUME__ /* volume attenuation, emission, scatter */ @@ -198,7 +239,7 @@ ccl_device void kernel_path_indirect(KernelGlobals *kg, } } else -# endif +# endif /* __VOLUME_DECOUPLED__ */ { /* integrate along volume segment with distance sampling */ VolumeIntegrateResult result = kernel_volume_integrate( @@ -230,10 +271,10 @@ ccl_device void kernel_path_indirect(KernelGlobals *kg, break; } } -# endif +# endif /* __VOLUME_SCATTER__ */ } } -#endif +#endif /* __VOLUME__ */ if(!hit) { #ifdef __BACKGROUND__ @@ -243,7 +284,7 @@ ccl_device void kernel_path_indirect(KernelGlobals *kg, throughput, L_background, state->bounce); -#endif +#endif /* __BACKGROUND__ */ break; } @@ -257,7 +298,7 @@ ccl_device void kernel_path_indirect(KernelGlobals *kg, shader_eval_surface(kg, sd, rng, state, rbsdf, state->flag, SHADER_CONTEXT_INDIRECT); #ifdef __BRANCHED_PATH__ shader_merge_closures(sd); -#endif +#endif /* __BRANCHED_PATH__ */ /* blurring of bsdf after bounces, for rays that have a small likelihood * of following this particular path (diffuse, rough glossy) */ @@ -280,7 +321,7 @@ ccl_device void kernel_path_indirect(KernelGlobals *kg, state->ray_pdf); path_radiance_accum_emission(L, throughput, emission, state->bounce); } -#endif +#endif /* __EMISSION__ */ /* path termination. this is a strange place to put the termination, it's * mainly due to the mixed in MIS that we use. gives too many unneeded @@ -305,42 +346,9 @@ ccl_device void kernel_path_indirect(KernelGlobals *kg, #ifdef __AO__ /* ambient occlusion */ if(kernel_data.integrator.use_ambient_occlusion || (sd->flag & SD_AO)) { - float bsdf_u, bsdf_v; - path_state_rng_2D(kg, rng, state, PRNG_BSDF_U, &bsdf_u, &bsdf_v); - - float ao_factor = kernel_data.background.ao_factor; - float3 ao_N; - float3 ao_bsdf = shader_bsdf_ao(kg, sd, ao_factor, &ao_N); - float3 ao_D; - float ao_pdf; - float3 ao_alpha = make_float3(0.0f, 0.0f, 0.0f); - - sample_cos_hemisphere(ao_N, bsdf_u, bsdf_v, &ao_D, &ao_pdf); - - if(dot(sd->Ng, ao_D) > 0.0f && ao_pdf != 0.0f) { - Ray light_ray; - float3 ao_shadow; - - light_ray.P = ray_offset(sd->P, sd->Ng); - light_ray.D = ao_D; - light_ray.t = kernel_data.background.ao_distance; -# ifdef __OBJECT_MOTION__ - light_ray.time = sd->time; -# endif - light_ray.dP = sd->dP; - light_ray.dD = differential3_zero(); - - if(!shadow_blocked(kg, emission_sd, state, &light_ray, &ao_shadow)) { - path_radiance_accum_ao(L, - throughput, - ao_alpha, - ao_bsdf, - ao_shadow, - state->bounce); - } - } + kernel_path_ao(kg, sd, emission_sd, L, state, rng, throughput, make_float3(0.0f, 0.0f, 0.0f)); } -#endif +#endif /* __AO__ */ #ifdef __SUBSURFACE__ /* bssrdf scatter to a different location on the same object, replacing @@ -372,7 +380,7 @@ ccl_device void kernel_path_indirect(KernelGlobals *kg, false); } } -#endif +#endif /* __SUBSURFACE__ */ #if defined(__EMISSION__) && defined(__BRANCHED_PATH__) if(kernel_data.integrator.use_direct_light) { @@ -387,53 +395,13 @@ ccl_device void kernel_path_indirect(KernelGlobals *kg, L, all); } -#endif +#endif /* defined(__EMISSION__) && defined(__BRANCHED_PATH__) */ if(!kernel_path_surface_bounce(kg, rng, sd, &throughput, state, L, ray)) break; } } -ccl_device_noinline void kernel_path_ao(KernelGlobals *kg, - ShaderData *sd, - ShaderData *emission_sd, - PathRadiance *L, - PathState *state, - RNG *rng, - float3 throughput) -{ - /* todo: solve correlation */ - float bsdf_u, bsdf_v; - - path_state_rng_2D(kg, rng, state, PRNG_BSDF_U, &bsdf_u, &bsdf_v); - - float ao_factor = kernel_data.background.ao_factor; - float3 ao_N; - float3 ao_bsdf = shader_bsdf_ao(kg, sd, ao_factor, &ao_N); - float3 ao_D; - float ao_pdf; - float3 ao_alpha = shader_bsdf_alpha(kg, sd); - - sample_cos_hemisphere(ao_N, bsdf_u, bsdf_v, &ao_D, &ao_pdf); - - if(dot(ccl_fetch(sd, Ng), ao_D) > 0.0f && ao_pdf != 0.0f) { - Ray light_ray; - float3 ao_shadow; - - light_ray.P = ray_offset(ccl_fetch(sd, P), ccl_fetch(sd, Ng)); - light_ray.D = ao_D; - light_ray.t = kernel_data.background.ao_distance; -#ifdef __OBJECT_MOTION__ - light_ray.time = ccl_fetch(sd, time); -#endif - light_ray.dP = ccl_fetch(sd, dP); - light_ray.dD = differential3_zero(); - - if(!shadow_blocked(kg, emission_sd, state, &light_ray, &ao_shadow)) - path_radiance_accum_ao(L, throughput, ao_alpha, ao_bsdf, ao_shadow, state->bounce); - } -} - #ifdef __SUBSURFACE__ # ifndef __KERNEL_CUDA__ ccl_device @@ -481,7 +449,7 @@ bool kernel_path_subsurface_scatter( ss_indirect->need_update_volume_stack = kernel_data.integrator.use_volumes && ccl_fetch(sd, flag) & SD_OBJECT_INTERSECTS_VOLUME; -# endif +# endif /* __VOLUME__ */ /* compute lighting with the BSDF closure */ for(int hit = 0; hit < num_hits; hit++) { @@ -524,7 +492,7 @@ bool kernel_path_subsurface_scatter( { # ifdef __LAMP_MIS__ hit_state->ray_t = 0.0f; -# endif +# endif /* __LAMP_MIS__ */ # ifdef __VOLUME__ if(ss_indirect->need_update_volume_stack) { @@ -539,7 +507,7 @@ bool kernel_path_subsurface_scatter( &volume_ray, hit_state->volume_stack); } -# endif +# endif /* __VOLUME__ */ path_radiance_reset_indirect(L); ss_indirect->num_rays++; } @@ -625,14 +593,14 @@ ccl_device_inline float4 kernel_path_integrate(KernelGlobals *kg, #ifdef __KERNEL_DEBUG__ DebugData debug_data; debug_data_init(&debug_data); -#endif +#endif /* __KERNEL_DEBUG__ */ #ifdef __SUBSURFACE__ SubsurfaceIndirectRays ss_indirect; kernel_path_subsurface_init_indirect(&ss_indirect); for(;;) { -#endif +#endif /* __SUBSURFACE__ */ /* path iteration */ for(;;) { @@ -658,7 +626,7 @@ ccl_device_inline float4 kernel_path_integrate(KernelGlobals *kg, bool hit = scene_intersect(kg, ray, visibility, &isect, &lcg_state, difl, extmax); #else bool hit = scene_intersect(kg, ray, visibility, &isect, NULL, 0.0f, 0.0f); -#endif +#endif /* __HAIR__ */ #ifdef __KERNEL_DEBUG__ if(state.flag & PATH_RAY_CAMERA) { @@ -666,7 +634,7 @@ ccl_device_inline float4 kernel_path_integrate(KernelGlobals *kg, debug_data.num_bvh_traversed_instances += isect.num_traversed_instances; } debug_data.num_ray_bounces++; -#endif +#endif /* __KERNEL_DEBUG__ */ #ifdef __LAMP_MIS__ if(kernel_data.integrator.use_lamp_mis && !(state.flag & PATH_RAY_CAMERA)) { @@ -687,7 +655,7 @@ ccl_device_inline float4 kernel_path_integrate(KernelGlobals *kg, if(indirect_lamp_emission(kg, &emission_sd, &state, &light_ray, &emission)) path_radiance_accum_emission(&L, throughput, emission, state.bounce); } -#endif +#endif /* __LAMP_MIS__ */ #ifdef __VOLUME__ /* volume attenuation, emission, scatter */ @@ -751,7 +719,7 @@ ccl_device_inline float4 kernel_path_integrate(KernelGlobals *kg, } } else -# endif +# endif /* __VOLUME_DECOUPLED__ */ { /* integrate along volume segment with distance sampling */ VolumeIntegrateResult result = kernel_volume_integrate( @@ -768,10 +736,10 @@ ccl_device_inline float4 kernel_path_integrate(KernelGlobals *kg, else break; } -# endif +# endif /* __VOLUME_SCATTER__ */ } } -#endif +#endif /* __VOLUME__ */ if(!hit) { /* eval background shader if nothing hit */ @@ -780,7 +748,7 @@ ccl_device_inline float4 kernel_path_integrate(KernelGlobals *kg, #ifdef __PASSES__ if(!(kernel_data.film.pass_flag & PASS_BACKGROUND)) -#endif +#endif /* __PASSES__ */ break; } @@ -788,7 +756,7 @@ ccl_device_inline float4 kernel_path_integrate(KernelGlobals *kg, /* sample background shader */ float3 L_background = indirect_background(kg, &emission_sd, &state, &ray); path_radiance_accum_background(&L, throughput, L_background, state.bounce); -#endif +#endif /* __BACKGROUND__ */ break; } @@ -816,7 +784,7 @@ ccl_device_inline float4 kernel_path_integrate(KernelGlobals *kg, if(sd.flag & SD_HOLDOUT_MASK) break; } -#endif +#endif /* __HOLDOUT__ */ /* holdout mask objects do not write data passes */ kernel_write_data_passes(kg, buffer, &L, &sd, sample, &state, throughput); @@ -839,7 +807,7 @@ ccl_device_inline float4 kernel_path_integrate(KernelGlobals *kg, float3 emission = indirect_primitive_emission(kg, &sd, isect.t, state.flag, state.ray_pdf); path_radiance_accum_emission(&L, throughput, emission, state.bounce); } -#endif +#endif /* __EMISSION__ */ /* path termination. this is a strange place to put the termination, it's * mainly due to the mixed in MIS that we use. gives too many unneeded @@ -851,7 +819,6 @@ ccl_device_inline float4 kernel_path_integrate(KernelGlobals *kg, } else if(probability != 1.0f) { float terminate = path_state_rng_1D_for_decision(kg, rng, &state, PRNG_TERMINATE); - if(terminate >= probability) break; @@ -861,9 +828,9 @@ ccl_device_inline float4 kernel_path_integrate(KernelGlobals *kg, #ifdef __AO__ /* ambient occlusion */ if(kernel_data.integrator.use_ambient_occlusion || (sd.flag & SD_AO)) { - kernel_path_ao(kg, &sd, &emission_sd, &L, &state, rng, throughput); + kernel_path_ao(kg, &sd, &emission_sd, &L, &state, rng, throughput, shader_bsdf_alpha(kg, &sd)); } -#endif +#endif /* __AO__ */ #ifdef __SUBSURFACE__ /* bssrdf scatter to a different location on the same object, replacing @@ -918,7 +885,7 @@ ccl_device_inline float4 kernel_path_integrate(KernelGlobals *kg, #ifdef __KERNEL_DEBUG__ kernel_write_debug_passes(kg, buffer, &state, &debug_data, sample); -#endif +#endif /* __KERNEL_DEBUG__ */ return make_float4(L_sum.x, L_sum.y, L_sum.z, 1.0f - L_transparent); } diff --git a/intern/cycles/kernel/kernel_path_branched.h b/intern/cycles/kernel/kernel_path_branched.h index cdb07db587a..c84727ace99 100644 --- a/intern/cycles/kernel/kernel_path_branched.h +++ b/intern/cycles/kernel/kernel_path_branched.h @@ -51,7 +51,7 @@ ccl_device_inline void kernel_branched_path_ao(KernelGlobals *kg, light_ray.t = kernel_data.background.ao_distance; #ifdef __OBJECT_MOTION__ light_ray.time = ccl_fetch(sd, time); -#endif +#endif /* __OBJECT_MOTION__ */ light_ray.dP = ccl_fetch(sd, dP); light_ray.dD = differential3_zero(); @@ -169,7 +169,7 @@ ccl_device void kernel_branched_path_subsurface_scatter(KernelGlobals *kg, Ray volume_ray = *ray; bool need_update_volume_stack = kernel_data.integrator.use_volumes && ccl_fetch(sd, flag) & SD_OBJECT_INTERSECTS_VOLUME; -#endif +#endif /* __VOLUME__ */ /* compute lighting with the BSDF closure */ for(int hit = 0; hit < num_hits; hit++) { @@ -200,7 +200,7 @@ ccl_device void kernel_branched_path_subsurface_scatter(KernelGlobals *kg, &volume_ray, hit_state.volume_stack); } -#endif +#endif /* __VOLUME__ */ #ifdef __EMISSION__ /* direct light */ @@ -217,7 +217,7 @@ ccl_device void kernel_branched_path_subsurface_scatter(KernelGlobals *kg, L, all); } -#endif +#endif /* __EMISSION__ */ /* indirect light */ kernel_branched_path_surface_indirect_light( @@ -234,7 +234,7 @@ ccl_device void kernel_branched_path_subsurface_scatter(KernelGlobals *kg, } } } -#endif +#endif /* __SUBSURFACE__ */ ccl_device float4 kernel_branched_path_integrate(KernelGlobals *kg, RNG *rng, int sample, Ray ray, ccl_global float *buffer) { @@ -256,7 +256,7 @@ ccl_device float4 kernel_branched_path_integrate(KernelGlobals *kg, RNG *rng, in #ifdef __KERNEL_DEBUG__ DebugData debug_data; debug_data_init(&debug_data); -#endif +#endif /* __KERNEL_DEBUG__ */ /* Main Loop * Here we only handle transparency intersections from the camera ray. @@ -285,13 +285,13 @@ ccl_device float4 kernel_branched_path_integrate(KernelGlobals *kg, RNG *rng, in bool hit = scene_intersect(kg, ray, visibility, &isect, &lcg_state, difl, extmax); #else bool hit = scene_intersect(kg, ray, visibility, &isect, NULL, 0.0f, 0.0f); -#endif +#endif /* __HAIR__ */ #ifdef __KERNEL_DEBUG__ debug_data.num_bvh_traversal_steps += isect.num_traversal_steps; debug_data.num_bvh_traversed_instances += isect.num_traversed_instances; debug_data.num_ray_bounces++; -#endif +#endif /* __KERNEL_DEBUG__ */ #ifdef __VOLUME__ /* volume attenuation, emission, scatter */ @@ -432,14 +432,14 @@ ccl_device float4 kernel_branched_path_integrate(KernelGlobals *kg, RNG *rng, in path_radiance_reset_indirect(&L); } } -#endif +#endif /* __VOLUME_SCATTER__ */ } /* todo: avoid this calculation using decoupled ray marching */ kernel_volume_shadow(kg, &emission_sd, &state, &volume_ray, &throughput); -#endif +#endif /* __VOLUME_DECOUPLED__ */ } -#endif +#endif /* __VOLUME__ */ if(!hit) { /* eval background shader if nothing hit */ @@ -448,7 +448,7 @@ ccl_device float4 kernel_branched_path_integrate(KernelGlobals *kg, RNG *rng, in #ifdef __PASSES__ if(!(kernel_data.film.pass_flag & PASS_BACKGROUND)) -#endif +#endif /* __PASSES__ */ break; } @@ -456,7 +456,7 @@ ccl_device float4 kernel_branched_path_integrate(KernelGlobals *kg, RNG *rng, in /* sample background shader */ float3 L_background = indirect_background(kg, &emission_sd, &state, &ray); path_radiance_accum_background(&L, throughput, L_background, state.bounce); -#endif +#endif /* __BACKGROUND__ */ break; } @@ -484,7 +484,7 @@ ccl_device float4 kernel_branched_path_integrate(KernelGlobals *kg, RNG *rng, in if(sd.flag & SD_HOLDOUT_MASK) break; } -#endif +#endif /* __HOLDOUT__ */ /* holdout mask objects do not write data passes */ kernel_write_data_passes(kg, buffer, &L, &sd, sample, &state, throughput); @@ -495,7 +495,7 @@ ccl_device float4 kernel_branched_path_integrate(KernelGlobals *kg, RNG *rng, in float3 emission = indirect_primitive_emission(kg, &sd, isect.t, state.flag, state.ray_pdf); path_radiance_accum_emission(&L, throughput, emission, state.bounce); } -#endif +#endif /* __EMISSION__ */ /* transparency termination */ if(state.flag & PATH_RAY_TRANSPARENT) { @@ -522,7 +522,7 @@ ccl_device float4 kernel_branched_path_integrate(KernelGlobals *kg, RNG *rng, in if(kernel_data.integrator.use_ambient_occlusion || (sd.flag & SD_AO)) { kernel_branched_path_ao(kg, &sd, &emission_sd, &L, &state, rng, throughput); } -#endif +#endif /* __AO__ */ #ifdef __SUBSURFACE__ /* bssrdf scatter to a different location on the same object */ @@ -530,7 +530,7 @@ ccl_device float4 kernel_branched_path_integrate(KernelGlobals *kg, RNG *rng, in kernel_branched_path_subsurface_scatter(kg, &sd, &indirect_sd, &emission_sd, &L, &state, rng, &ray, throughput); } -#endif +#endif /* __SUBSURFACE__ */ if(!(sd.flag & SD_HAS_ONLY_VOLUME)) { PathState hit_state = state; @@ -542,7 +542,7 @@ ccl_device float4 kernel_branched_path_integrate(KernelGlobals *kg, RNG *rng, in kernel_branched_path_surface_connect_light(kg, rng, &sd, &emission_sd, &hit_state, throughput, 1.0f, &L, all); } -#endif +#endif /* __EMISSION__ */ /* indirect light */ kernel_branched_path_surface_indirect_light(kg, rng, @@ -567,12 +567,12 @@ ccl_device float4 kernel_branched_path_integrate(KernelGlobals *kg, RNG *rng, in ray.dP = sd.dP; ray.dD.dx = -sd.dI.dx; ray.dD.dy = -sd.dI.dy; -#endif +#endif /* __RAY_DIFFERENTIALS__ */ #ifdef __VOLUME__ /* enter/exit volume */ kernel_volume_stack_enter_exit(kg, &sd, state.volume_stack); -#endif +#endif /* __VOLUME__ */ } float3 L_sum = path_radiance_clamp_and_sum(kg, &L); @@ -581,7 +581,7 @@ ccl_device float4 kernel_branched_path_integrate(KernelGlobals *kg, RNG *rng, in #ifdef __KERNEL_DEBUG__ kernel_write_debug_passes(kg, buffer, &state, &debug_data, sample); -#endif +#endif /* __KERNEL_DEBUG__ */ return make_float4(L_sum.x, L_sum.y, L_sum.z, 1.0f - L_transparent); } diff --git a/intern/cycles/kernel/kernel_path_common.h b/intern/cycles/kernel/kernel_path_common.h index 1912dfa16ed..13597eab287 100644 --- a/intern/cycles/kernel/kernel_path_common.h +++ b/intern/cycles/kernel/kernel_path_common.h @@ -14,6 +14,8 @@ * limitations under the License. */ +#include "util_hash.h" + CCL_NAMESPACE_BEGIN ccl_device_inline void kernel_path_trace_setup(KernelGlobals *kg, @@ -28,6 +30,10 @@ ccl_device_inline void kernel_path_trace_setup(KernelGlobals *kg, int num_samples = kernel_data.integrator.aa_samples; + if(sample == 0) { + *rng_state = hash_int_2d(x, y); + } + path_rng_init(kg, rng_state, sample, num_samples, rng, x, y, &filter_u, &filter_v); /* sample camera ray */ diff --git a/intern/cycles/kernel/kernel_path_surface.h b/intern/cycles/kernel/kernel_path_surface.h index 45f0f1cbfaa..fea503d06e5 100644 --- a/intern/cycles/kernel/kernel_path_surface.h +++ b/intern/cycles/kernel/kernel_path_surface.h @@ -49,6 +49,7 @@ ccl_device_noinline void kernel_branched_path_surface_connect_light(KernelGlobal for(int j = 0; j < num_samples; j++) { float light_u, light_v; path_branched_rng_2D(kg, &lamp_rng, state, j, num_samples, PRNG_LIGHT_U, &light_u, &light_v); + float terminate = path_branched_rng_light_termination(kg, &lamp_rng, state, j, num_samples); LightSample ls; if(lamp_light_sample(kg, i, light_u, light_v, ccl_fetch(sd, P), &ls)) { @@ -57,7 +58,7 @@ ccl_device_noinline void kernel_branched_path_surface_connect_light(KernelGlobal if(kernel_data.integrator.pdf_triangles != 0.0f) ls.pdf *= 2.0f; - if(direct_emission(kg, sd, emission_sd, &ls, state, &light_ray, &L_light, &is_lamp)) { + if(direct_emission(kg, sd, emission_sd, &ls, state, &light_ray, &L_light, &is_lamp, terminate)) { /* trace shadow ray */ float3 shadow; @@ -79,6 +80,7 @@ ccl_device_noinline void kernel_branched_path_surface_connect_light(KernelGlobal float light_t = path_branched_rng_1D(kg, rng, state, j, num_samples, PRNG_LIGHT); float light_u, light_v; path_branched_rng_2D(kg, rng, state, j, num_samples, PRNG_LIGHT_U, &light_u, &light_v); + float terminate = path_branched_rng_light_termination(kg, rng, state, j, num_samples); /* only sample triangle lights */ if(kernel_data.integrator.num_all_lights) @@ -90,7 +92,7 @@ ccl_device_noinline void kernel_branched_path_surface_connect_light(KernelGlobal if(kernel_data.integrator.num_all_lights) ls.pdf *= 2.0f; - if(direct_emission(kg, sd, emission_sd, &ls, state, &light_ray, &L_light, &is_lamp)) { + if(direct_emission(kg, sd, emission_sd, &ls, state, &light_ray, &L_light, &is_lamp, terminate)) { /* trace shadow ray */ float3 shadow; @@ -108,11 +110,12 @@ ccl_device_noinline void kernel_branched_path_surface_connect_light(KernelGlobal float light_t = path_state_rng_1D(kg, rng, state, PRNG_LIGHT); float light_u, light_v; path_state_rng_2D(kg, rng, state, PRNG_LIGHT_U, &light_u, &light_v); + float terminate = path_state_rng_light_termination(kg, rng, state); LightSample ls; if(light_sample(kg, light_t, light_u, light_v, ccl_fetch(sd, time), ccl_fetch(sd, P), state->bounce, &ls)) { /* sample random light */ - if(direct_emission(kg, sd, emission_sd, &ls, state, &light_ray, &L_light, &is_lamp)) { + if(direct_emission(kg, sd, emission_sd, &ls, state, &light_ray, &L_light, &is_lamp, terminate)) { /* trace shadow ray */ float3 shadow; @@ -210,7 +213,8 @@ ccl_device_inline void kernel_path_surface_connect_light(KernelGlobals *kg, ccl_ LightSample ls; if(light_sample(kg, light_t, light_u, light_v, ccl_fetch(sd, time), ccl_fetch(sd, P), state->bounce, &ls)) { - if(direct_emission(kg, sd, emission_sd, &ls, state, &light_ray, &L_light, &is_lamp)) { + float terminate = path_state_rng_light_termination(kg, rng, state); + if(direct_emission(kg, sd, emission_sd, &ls, state, &light_ray, &L_light, &is_lamp, terminate)) { /* trace shadow ray */ float3 shadow; diff --git a/intern/cycles/kernel/kernel_path_volume.h b/intern/cycles/kernel/kernel_path_volume.h index 5ee1912c913..3d3b7385d8b 100644 --- a/intern/cycles/kernel/kernel_path_volume.h +++ b/intern/cycles/kernel/kernel_path_volume.h @@ -48,7 +48,8 @@ ccl_device_inline void kernel_path_volume_connect_light( if(light_sample(kg, light_t, light_u, light_v, sd->time, sd->P, state->bounce, &ls)) { - if(direct_emission(kg, sd, emission_sd, &ls, state, &light_ray, &L_light, &is_lamp)) { + float terminate = path_state_rng_light_termination(kg, rng, state); + if(direct_emission(kg, sd, emission_sd, &ls, state, &light_ray, &L_light, &is_lamp, terminate)) { /* trace shadow ray */ float3 shadow; @@ -161,7 +162,8 @@ ccl_device void kernel_branched_path_volume_connect_light(KernelGlobals *kg, RNG if(kernel_data.integrator.pdf_triangles != 0.0f) ls.pdf *= 2.0f; - if(direct_emission(kg, sd, emission_sd, &ls, state, &light_ray, &L_light, &is_lamp)) { + float terminate = path_branched_rng_light_termination(kg, rng, state, j, num_samples); + if(direct_emission(kg, sd, emission_sd, &ls, state, &light_ray, &L_light, &is_lamp, terminate)) { /* trace shadow ray */ float3 shadow; @@ -209,7 +211,8 @@ ccl_device void kernel_branched_path_volume_connect_light(KernelGlobals *kg, RNG if(kernel_data.integrator.num_all_lights) ls.pdf *= 2.0f; - if(direct_emission(kg, sd, emission_sd, &ls, state, &light_ray, &L_light, &is_lamp)) { + float terminate = path_branched_rng_light_termination(kg, rng, state, j, num_samples); + if(direct_emission(kg, sd, emission_sd, &ls, state, &light_ray, &L_light, &is_lamp, terminate)) { /* trace shadow ray */ float3 shadow; @@ -246,7 +249,8 @@ ccl_device void kernel_branched_path_volume_connect_light(KernelGlobals *kg, RNG /* todo: split up light_sample so we don't have to call it again with new position */ if(light_sample(kg, light_t, light_u, light_v, sd->time, sd->P, state->bounce, &ls)) { /* sample random light */ - if(direct_emission(kg, sd, emission_sd, &ls, state, &light_ray, &L_light, &is_lamp)) { + float terminate = path_state_rng_light_termination(kg, rng, state); + if(direct_emission(kg, sd, emission_sd, &ls, state, &light_ray, &L_light, &is_lamp, terminate)) { /* trace shadow ray */ float3 shadow; diff --git a/intern/cycles/kernel/kernel_random.h b/intern/cycles/kernel/kernel_random.h index 4a76ffddbe7..2b767da5041 100644 --- a/intern/cycles/kernel/kernel_random.h +++ b/intern/cycles/kernel/kernel_random.h @@ -300,6 +300,23 @@ ccl_device_inline void path_branched_rng_2D(KernelGlobals *kg, ccl_addr_space RN path_rng_2D(kg, rng, state->sample*num_branches + branch, state->num_samples*num_branches, state->rng_offset + dimension, fx, fy); } +/* Utitility functions to get light termination value, since it might not be needed in many cases. */ +ccl_device_inline float path_state_rng_light_termination(KernelGlobals *kg, ccl_addr_space RNG *rng, const ccl_addr_space PathState *state) +{ + if(kernel_data.integrator.light_inv_rr_threshold > 0.0f) { + return path_state_rng_1D_for_decision(kg, rng, state, PRNG_LIGHT_TERMINATE); + } + return 0.0f; +} + +ccl_device_inline float path_branched_rng_light_termination(KernelGlobals *kg, ccl_addr_space RNG *rng, const PathState *state, int branch, int num_branches) +{ + if(kernel_data.integrator.light_inv_rr_threshold > 0.0f) { + return path_branched_rng_1D_for_decision(kg, rng, state, branch, num_branches, PRNG_LIGHT_TERMINATE); + } + return 0.0f; +} + ccl_device_inline void path_state_branch(PathState *state, int branch, int num_branches) { /* path is splitting into a branch, adjust so that each branch diff --git a/intern/cycles/kernel/kernel_shader.h b/intern/cycles/kernel/kernel_shader.h index 652612c19db..9d5ea53d5d8 100644 --- a/intern/cycles/kernel/kernel_shader.h +++ b/intern/cycles/kernel/kernel_shader.h @@ -285,16 +285,13 @@ ccl_device_inline void shader_setup_from_sample(KernelGlobals *kg, #ifdef __OBJECT_MOTION__ shader_setup_object_transforms(kg, sd, time); -#endif + ccl_fetch(sd, time) = time; } else if(lamp != LAMP_NONE) { ccl_fetch(sd, ob_tfm) = lamp_fetch_transform(kg, lamp, false); ccl_fetch(sd, ob_itfm) = lamp_fetch_transform(kg, lamp, true); - } - -#ifdef __OBJECT_MOTION__ - ccl_fetch(sd, time) = time; #endif + } /* transform into world space */ if(object_space) { @@ -581,7 +578,7 @@ void shader_bsdf_eval(KernelGlobals *kg, _shader_bsdf_multi_eval(kg, sd, omega_in, &pdf, -1, eval, 0.0f, 0.0f); if(use_mis) { float weight = power_heuristic(light_pdf, pdf); - bsdf_eval_mul(eval, make_float3(weight, weight, weight)); + bsdf_eval_mul(eval, weight); } } } diff --git a/intern/cycles/kernel/kernel_types.h b/intern/cycles/kernel/kernel_types.h index 96f2dd1be2c..15960dba40d 100644 --- a/intern/cycles/kernel/kernel_types.h +++ b/intern/cycles/kernel/kernel_types.h @@ -253,7 +253,7 @@ enum PathTraceDimension { PRNG_LIGHT = 3, PRNG_LIGHT_U = 4, PRNG_LIGHT_V = 5, - PRNG_UNUSED_3 = 6, + PRNG_LIGHT_TERMINATE = 6, PRNG_TERMINATE = 7, #ifdef __VOLUME__ @@ -1129,8 +1129,9 @@ typedef struct KernelIntegrator { float volume_step_size; int volume_samples; + float light_inv_rr_threshold; + int pad1; - int pad2; } KernelIntegrator; static_assert_align(KernelIntegrator, 16); diff --git a/intern/cycles/kernel/split/kernel_direct_lighting.h b/intern/cycles/kernel/split/kernel_direct_lighting.h index 6ad736fc2c1..82ca18829d3 100644 --- a/intern/cycles/kernel/split/kernel_direct_lighting.h +++ b/intern/cycles/kernel/split/kernel_direct_lighting.h @@ -72,6 +72,7 @@ ccl_device char kernel_direct_lighting( float light_t = path_state_rng_1D(kg, rng, state, PRNG_LIGHT); float light_u, light_v; path_state_rng_2D(kg, rng, state, PRNG_LIGHT_U, &light_u, &light_v); + float terminate = path_state_rng_light_termination(kg, rng, state); LightSample ls; if(light_sample(kg, @@ -88,7 +89,7 @@ ccl_device char kernel_direct_lighting( BsdfEval L_light; bool is_lamp; - if(direct_emission(kg, sd, kg->sd_input, &ls, state, &light_ray, &L_light, &is_lamp)) { + if(direct_emission(kg, sd, kg->sd_input, &ls, state, &light_ray, &L_light, &is_lamp, terminate)) { /* Write intermediate data to global memory to access from * the next kernel. */ diff --git a/intern/cycles/kernel/svm/svm_brick.h b/intern/cycles/kernel/svm/svm_brick.h index 47e1ba2ba6b..14245cf0522 100644 --- a/intern/cycles/kernel/svm/svm_brick.h +++ b/intern/cycles/kernel/svm/svm_brick.h @@ -112,7 +112,7 @@ ccl_device void svm_node_tex_brick(KernelGlobals *kg, ShaderData *sd, float *sta } if(stack_valid(color_offset)) - stack_store_float3(stack, color_offset, lerp(color1, mortar, f)); + stack_store_float3(stack, color_offset, color1*(1.0f-f) + mortar*f); if(stack_valid(fac_offset)) stack_store_float(stack, fac_offset, f); } diff --git a/intern/cycles/kernel/svm/svm_image.h b/intern/cycles/kernel/svm/svm_image.h index 9606064492e..2afdf61b476 100644 --- a/intern/cycles/kernel/svm/svm_image.h +++ b/intern/cycles/kernel/svm/svm_image.h @@ -241,8 +241,7 @@ ccl_device void svm_node_tex_image_box(KernelGlobals *kg, ShaderData *sd, float float3 N = ccl_fetch(sd, N); N = ccl_fetch(sd, N); - if(ccl_fetch(sd, object) != OBJECT_NONE) - object_inverse_normal_transform(kg, sd, &N); + object_inverse_normal_transform(kg, sd, &N); /* project from direction vector to barycentric coordinates in triangles */ N.x = fabsf(N.x); diff --git a/intern/cycles/kernel/svm/svm_tex_coord.h b/intern/cycles/kernel/svm/svm_tex_coord.h index 6c3394adbce..c0b01262212 100644 --- a/intern/cycles/kernel/svm/svm_tex_coord.h +++ b/intern/cycles/kernel/svm/svm_tex_coord.h @@ -49,8 +49,7 @@ ccl_device void svm_node_tex_coord(KernelGlobals *kg, } case NODE_TEXCO_NORMAL: { data = ccl_fetch(sd, N); - if((ccl_fetch(sd, object) != OBJECT_NONE) || (ccl_fetch(sd, type) == PRIMITIVE_LAMP)) - object_inverse_normal_transform(kg, sd, &data); + object_inverse_normal_transform(kg, sd, &data); break; } case NODE_TEXCO_CAMERA: { @@ -131,8 +130,7 @@ ccl_device void svm_node_tex_coord_bump_dx(KernelGlobals *kg, } case NODE_TEXCO_NORMAL: { data = ccl_fetch(sd, N); - if((ccl_fetch(sd, object) != OBJECT_NONE) || (ccl_fetch(sd, type) == PRIMITIVE_LAMP)) - object_inverse_normal_transform(kg, sd, &data); + object_inverse_normal_transform(kg, sd, &data); break; } case NODE_TEXCO_CAMERA: { @@ -216,8 +214,7 @@ ccl_device void svm_node_tex_coord_bump_dy(KernelGlobals *kg, } case NODE_TEXCO_NORMAL: { data = ccl_fetch(sd, N); - if((ccl_fetch(sd, object) != OBJECT_NONE) || (ccl_fetch(sd, type) == PRIMITIVE_LAMP)) - object_inverse_normal_transform(kg, sd, &data); + object_inverse_normal_transform(kg, sd, &data); break; } case NODE_TEXCO_CAMERA: { diff --git a/intern/cycles/render/buffers.cpp b/intern/cycles/render/buffers.cpp index 1e170d3a96e..cb20e811708 100644 --- a/intern/cycles/render/buffers.cpp +++ b/intern/cycles/render/buffers.cpp @@ -135,15 +135,7 @@ void RenderBuffers::reset(Device *device, BufferParams& params_) /* allocate rng state */ rng_state.resize(params.width, params.height); - uint *init_state = rng_state.resize(params.width, params.height); - int x, y, width = params.width, height = params.height; - - for(y = 0; y < height; y++) - for(x = 0; x < width; x++) - init_state[y*width + x] = hash_int_2d(params.full_x+x, params.full_y+y); - device->mem_alloc(rng_state, MEM_READ_WRITE); - device->mem_copy_to(rng_state); } bool RenderBuffers::copy_from_device() diff --git a/intern/cycles/render/image.cpp b/intern/cycles/render/image.cpp index 073a0aa2ac9..7465fbd43a7 100644 --- a/intern/cycles/render/image.cpp +++ b/intern/cycles/render/image.cpp @@ -471,133 +471,43 @@ bool ImageManager::file_load_image_generic(Image *img, ImageInput **in, int &wid return true; } -template<typename T> -bool ImageManager::file_load_byte_image(Image *img, ImageDataType type, device_vector<T>& tex_img) +template<TypeDesc::BASETYPE FileFormat, + typename StorageType, + typename DeviceType> +bool ImageManager::file_load_image(Image *img, + ImageDataType type, + device_vector<DeviceType>& tex_img) { + const StorageType alpha_one = (FileFormat == TypeDesc::UINT8)? 255 : 1; ImageInput *in = NULL; int width, height, depth, components; - - if(!file_load_image_generic(img, &in, width, height, depth, components)) - return false; - - /* read RGBA pixels */ - uchar *pixels = (uchar*)tex_img.resize(width, height, depth); - if(pixels == NULL) { + if(!file_load_image_generic(img, &in, width, height, depth, components)) { return false; } - bool cmyk = false; - - if(in) { - if(depth <= 1) { - int scanlinesize = width*components*sizeof(uchar); - - in->read_image(TypeDesc::UINT8, - (uchar*)pixels + (((size_t)height)-1)*scanlinesize, - AutoStride, - -scanlinesize, - AutoStride); - } - else { - in->read_image(TypeDesc::UINT8, (uchar*)pixels); - } - - cmyk = strcmp(in->format_name(), "jpeg") == 0 && components == 4; - - in->close(); - delete in; - } - else { - builtin_image_pixels_cb(img->filename, img->builtin_data, pixels); - } - - /* Check if we actually have a byte4 slot, in case components == 1, but device - * doesn't support single channel textures. */ - if(type == IMAGE_DATA_TYPE_BYTE4) { - size_t num_pixels = ((size_t)width) * height * depth; - if(cmyk) { - /* CMYK */ - for(size_t i = num_pixels-1, pixel = 0; pixel < num_pixels; pixel++, i--) { - pixels[i*4+2] = (pixels[i*4+2]*pixels[i*4+3])/255; - pixels[i*4+1] = (pixels[i*4+1]*pixels[i*4+3])/255; - pixels[i*4+0] = (pixels[i*4+0]*pixels[i*4+3])/255; - pixels[i*4+3] = 255; - } - } - else if(components == 2) { - /* grayscale + alpha */ - for(size_t i = num_pixels-1, pixel = 0; pixel < num_pixels; pixel++, i--) { - pixels[i*4+3] = pixels[i*2+1]; - pixels[i*4+2] = pixels[i*2+0]; - pixels[i*4+1] = pixels[i*2+0]; - pixels[i*4+0] = pixels[i*2+0]; - } - } - else if(components == 3) { - /* RGB */ - for(size_t i = num_pixels-1, pixel = 0; pixel < num_pixels; pixel++, i--) { - pixels[i*4+3] = 255; - pixels[i*4+2] = pixels[i*3+2]; - pixels[i*4+1] = pixels[i*3+1]; - pixels[i*4+0] = pixels[i*3+0]; - } - } - else if(components == 1) { - /* grayscale */ - for(size_t i = num_pixels-1, pixel = 0; pixel < num_pixels; pixel++, i--) { - pixels[i*4+3] = 255; - pixels[i*4+2] = pixels[i]; - pixels[i*4+1] = pixels[i]; - pixels[i*4+0] = pixels[i]; - } - } - - if(img->use_alpha == false) { - for(size_t i = num_pixels-1, pixel = 0; pixel < num_pixels; pixel++, i--) { - pixels[i*4+3] = 255; - } - } - } - - return true; -} - -template<typename T> -bool ImageManager::file_load_float_image(Image *img, ImageDataType type, device_vector<T>& tex_img) -{ - ImageInput *in = NULL; - int width, height, depth, components; - - if(!file_load_image_generic(img, &in, width, height, depth, components)) - return false; - - /* read RGBA pixels */ - float *pixels = (float*)tex_img.resize(width, height, depth); + /* Read RGBA pixels. */ + StorageType *pixels = (StorageType*)tex_img.resize(width, height, depth); if(pixels == NULL) { return false; } bool cmyk = false; - if(in) { - float *readpixels = pixels; - vector<float> tmppixels; - + StorageType *readpixels = pixels; + vector<StorageType> tmppixels; if(components > 4) { tmppixels.resize(((size_t)width)*height*components); readpixels = &tmppixels[0]; } - if(depth <= 1) { - size_t scanlinesize = ((size_t)width)*components*sizeof(float); - in->read_image(TypeDesc::FLOAT, + size_t scanlinesize = ((size_t)width)*components*sizeof(StorageType); + in->read_image(FileFormat, (uchar*)readpixels + (height-1)*scanlinesize, AutoStride, -scanlinesize, AutoStride); } else { - in->read_image(TypeDesc::FLOAT, (uchar*)readpixels); + in->read_image(FileFormat, (uchar*)readpixels); } - if(components > 4) { size_t dimensions = ((size_t)width)*height; for(size_t i = dimensions-1, pixel = 0; pixel < dimensions; pixel++, i--) { @@ -606,30 +516,42 @@ bool ImageManager::file_load_float_image(Image *img, ImageDataType type, device_ pixels[i*4+1] = tmppixels[i*components+1]; pixels[i*4+0] = tmppixels[i*components+0]; } - tmppixels.clear(); } - cmyk = strcmp(in->format_name(), "jpeg") == 0 && components == 4; - in->close(); delete in; } else { - builtin_image_float_pixels_cb(img->filename, img->builtin_data, pixels); + if(FileFormat == TypeDesc::FLOAT) { + builtin_image_float_pixels_cb(img->filename, + img->builtin_data, + (float*)pixels); + } + else if(FileFormat == TypeDesc::UINT8) { + builtin_image_pixels_cb(img->filename, + img->builtin_data, + (uchar*)pixels); + } + else { + /* TODO(dingto): Support half for ImBuf. */ + } } - - /* Check if we actually have a float4 slot, in case components == 1, but device - * doesn't support single channel textures. */ - if(type == IMAGE_DATA_TYPE_FLOAT4) { + /* Check if we actually have a float4 slot, in case components == 1, + * but device doesn't support single channel textures. + */ + if(type == IMAGE_DATA_TYPE_FLOAT4 || + type == IMAGE_DATA_TYPE_HALF4 || + type == IMAGE_DATA_TYPE_BYTE4) + { size_t num_pixels = ((size_t)width) * height * depth; if(cmyk) { /* CMYK */ for(size_t i = num_pixels-1, pixel = 0; pixel < num_pixels; pixel++, i--) { - pixels[i*4+3] = 255; pixels[i*4+2] = (pixels[i*4+2]*pixels[i*4+3])/255; pixels[i*4+1] = (pixels[i*4+1]*pixels[i*4+3])/255; pixels[i*4+0] = (pixels[i*4+0]*pixels[i*4+3])/255; + pixels[i*4+3] = alpha_one; } } else if(components == 2) { @@ -644,7 +566,7 @@ bool ImageManager::file_load_float_image(Image *img, ImageDataType type, device_ else if(components == 3) { /* RGB */ for(size_t i = num_pixels-1, pixel = 0; pixel < num_pixels; pixel++, i--) { - pixels[i*4+3] = 1.0f; + pixels[i*4+3] = alpha_one; pixels[i*4+2] = pixels[i*3+2]; pixels[i*4+1] = pixels[i*3+1]; pixels[i*4+0] = pixels[i*3+0]; @@ -653,120 +575,18 @@ bool ImageManager::file_load_float_image(Image *img, ImageDataType type, device_ else if(components == 1) { /* grayscale */ for(size_t i = num_pixels-1, pixel = 0; pixel < num_pixels; pixel++, i--) { - pixels[i*4+3] = 1.0f; + pixels[i*4+3] = alpha_one; pixels[i*4+2] = pixels[i]; pixels[i*4+1] = pixels[i]; pixels[i*4+0] = pixels[i]; } } - if(img->use_alpha == false) { for(size_t i = num_pixels-1, pixel = 0; pixel < num_pixels; pixel++, i--) { - pixels[i*4+3] = 1.0f; + pixels[i*4+3] = alpha_one; } } } - - return true; -} - -template<typename T> -bool ImageManager::file_load_half_image(Image *img, ImageDataType type, device_vector<T>& tex_img) -{ - ImageInput *in = NULL; - int width, height, depth, components; - - if(!file_load_image_generic(img, &in, width, height, depth, components)) - return false; - - /* read RGBA pixels */ - half *pixels = (half*)tex_img.resize(width, height, depth); - if(pixels == NULL) { - return false; - } - - if(in) { - half *readpixels = pixels; - vector<half> tmppixels; - - if(components > 4) { - tmppixels.resize(((size_t)width)*height*components); - readpixels = &tmppixels[0]; - } - - if(depth <= 1) { - size_t scanlinesize = ((size_t)width)*components*sizeof(half); - in->read_image(TypeDesc::HALF, - (uchar*)readpixels + (height-1)*scanlinesize, - AutoStride, - -scanlinesize, - AutoStride); - } - else { - in->read_image(TypeDesc::HALF, (uchar*)readpixels); - } - - if(components > 4) { - size_t dimensions = ((size_t)width)*height; - for(size_t i = dimensions-1, pixel = 0; pixel < dimensions; pixel++, i--) { - pixels[i*4+3] = tmppixels[i*components+3]; - pixels[i*4+2] = tmppixels[i*components+2]; - pixels[i*4+1] = tmppixels[i*components+1]; - pixels[i*4+0] = tmppixels[i*components+0]; - } - - tmppixels.clear(); - } - - in->close(); - delete in; - } -#if 0 - /* TODO(dingto): Support half for ImBuf. */ - else { - builtin_image_float_pixels_cb(img->filename, img->builtin_data, pixels); - } -#endif - - /* Check if we actually have a half4 slot, in case components == 1, but device - * doesn't support single channel textures. */ - if(type == IMAGE_DATA_TYPE_HALF4) { - size_t num_pixels = ((size_t)width) * height * depth; - if(components == 2) { - /* grayscale + alpha */ - for(size_t i = num_pixels-1, pixel = 0; pixel < num_pixels; pixel++, i--) { - pixels[i*4+3] = pixels[i*2+1]; - pixels[i*4+2] = pixels[i*2+0]; - pixels[i*4+1] = pixels[i*2+0]; - pixels[i*4+0] = pixels[i*2+0]; - } - } - else if(components == 3) { - /* RGB */ - for(size_t i = num_pixels-1, pixel = 0; pixel < num_pixels; pixel++, i--) { - pixels[i*4+3] = 1.0f; - pixels[i*4+2] = pixels[i*3+2]; - pixels[i*4+1] = pixels[i*3+1]; - pixels[i*4+0] = pixels[i*3+0]; - } - } - else if(components == 1) { - /* grayscale */ - for(size_t i = num_pixels-1, pixel = 0; pixel < num_pixels; pixel++, i--) { - pixels[i*4+3] = 1.0f; - pixels[i*4+2] = pixels[i]; - pixels[i*4+1] = pixels[i]; - pixels[i*4+0] = pixels[i]; - } - } - - if(img->use_alpha == false) { - for(size_t i = num_pixels-1, pixel = 0; pixel < num_pixels; pixel++, i--) { - pixels[i*4+3] = 1.0f; - } - } - } - return true; } @@ -802,7 +622,7 @@ void ImageManager::device_load_image(Device *device, DeviceScene *dscene, ImageD device->tex_free(tex_img); } - if(!file_load_float_image(img, type, tex_img)) { + if(!file_load_image<TypeDesc::FLOAT, float>(img, type, tex_img)) { /* on failure to load, we set a 1x1 pixels pink image */ float *pixels = (float*)tex_img.resize(1, 1); @@ -828,7 +648,7 @@ void ImageManager::device_load_image(Device *device, DeviceScene *dscene, ImageD device->tex_free(tex_img); } - if(!file_load_float_image(img, type, tex_img)) { + if(!file_load_image<TypeDesc::FLOAT, float>(img, type, tex_img)) { /* on failure to load, we set a 1x1 pixels pink image */ float *pixels = (float*)tex_img.resize(1, 1); @@ -851,7 +671,7 @@ void ImageManager::device_load_image(Device *device, DeviceScene *dscene, ImageD device->tex_free(tex_img); } - if(!file_load_byte_image(img, type, tex_img)) { + if(!file_load_image<TypeDesc::UINT8, uchar>(img, type, tex_img)) { /* on failure to load, we set a 1x1 pixels pink image */ uchar *pixels = (uchar*)tex_img.resize(1, 1); @@ -877,7 +697,7 @@ void ImageManager::device_load_image(Device *device, DeviceScene *dscene, ImageD device->tex_free(tex_img); } - if(!file_load_byte_image(img, type, tex_img)) { + if(!file_load_image<TypeDesc::UINT8, uchar>(img, type, tex_img)) { /* on failure to load, we set a 1x1 pixels pink image */ uchar *pixels = (uchar*)tex_img.resize(1, 1); @@ -900,7 +720,7 @@ void ImageManager::device_load_image(Device *device, DeviceScene *dscene, ImageD device->tex_free(tex_img); } - if(!file_load_half_image(img, type, tex_img)) { + if(!file_load_image<TypeDesc::HALF, half>(img, type, tex_img)) { /* on failure to load, we set a 1x1 pixels pink image */ half *pixels = (half*)tex_img.resize(1, 1); @@ -926,7 +746,7 @@ void ImageManager::device_load_image(Device *device, DeviceScene *dscene, ImageD device->tex_free(tex_img); } - if(!file_load_half_image(img, type, tex_img)) { + if(!file_load_image<TypeDesc::HALF, half>(img, type, tex_img)) { /* on failure to load, we set a 1x1 pixels pink image */ half *pixels = (half*)tex_img.resize(1, 1); diff --git a/intern/cycles/render/image.h b/intern/cycles/render/image.h index cca71a6bb93..1dc4bf180f8 100644 --- a/intern/cycles/render/image.h +++ b/intern/cycles/render/image.h @@ -109,14 +109,12 @@ private: bool file_load_image_generic(Image *img, ImageInput **in, int &width, int &height, int &depth, int &components); - template<typename T> - bool file_load_byte_image(Image *img, ImageDataType type, device_vector<T>& tex_img); - - template<typename T> - bool file_load_float_image(Image *img, ImageDataType type, device_vector<T>& tex_img); - - template<typename T> - bool file_load_half_image(Image *img, ImageDataType type, device_vector<T>& tex_img); + template<TypeDesc::BASETYPE FileFormat, + typename StorageType, + typename DeviceType> + bool file_load_image(Image *img, + ImageDataType type, + device_vector<DeviceType>& tex_img); int type_index_to_flattened_slot(int slot, ImageDataType type); int flattened_slot_to_type_index(int flat_slot, ImageDataType *type); diff --git a/intern/cycles/render/integrator.cpp b/intern/cycles/render/integrator.cpp index 63914e57319..a9a33d2e789 100644 --- a/intern/cycles/render/integrator.cpp +++ b/intern/cycles/render/integrator.cpp @@ -65,6 +65,7 @@ NODE_DEFINE(Integrator) SOCKET_BOOLEAN(sample_all_lights_direct, "Sample All Lights Direct", true); SOCKET_BOOLEAN(sample_all_lights_indirect, "Sample All Lights Indirect", true); + SOCKET_FLOAT(light_sampling_threshold, "Light Sampling Threshold", 0.05f); static NodeEnum method_enum; method_enum.insert("path", PATH); @@ -164,6 +165,13 @@ void Integrator::device_update(Device *device, DeviceScene *dscene, Scene *scene kintegrator->sampling_pattern = sampling_pattern; kintegrator->aa_samples = aa_samples; + if(light_sampling_threshold > 0.0f) { + kintegrator->light_inv_rr_threshold = 1.0f / light_sampling_threshold; + } + else { + kintegrator->light_inv_rr_threshold = 0.0f; + } + /* sobol directions table */ int max_samples = 1; diff --git a/intern/cycles/render/integrator.h b/intern/cycles/render/integrator.h index 39eaaf246d4..17fdd0ef1db 100644 --- a/intern/cycles/render/integrator.h +++ b/intern/cycles/render/integrator.h @@ -64,8 +64,10 @@ public: int mesh_light_samples; int subsurface_samples; int volume_samples; + bool sample_all_lights_direct; bool sample_all_lights_indirect; + float light_sampling_threshold; enum Method { BRANCHED_PATH = 0, diff --git a/intern/cycles/render/light.cpp b/intern/cycles/render/light.cpp index 777f3229ce6..2245c861d5a 100644 --- a/intern/cycles/render/light.cpp +++ b/intern/cycles/render/light.cpp @@ -43,8 +43,8 @@ static void shade_background_pixels(Device *device, DeviceScene *dscene, int res for(int y = 0; y < height; y++) { for(int x = 0; x < width; x++) { - float u = x/(float)width; - float v = y/(float)height; + float u = (x + 0.5f)/width; + float v = (y + 0.5f)/height; uint4 in = make_uint4(__float_as_int(u), __float_as_int(v), 0, 0); d_input_data[x + y*width] = in; @@ -106,6 +106,7 @@ NODE_DEFINE(Light) static NodeEnum type_enum; type_enum.insert("point", LIGHT_POINT); + type_enum.insert("distant", LIGHT_DISTANT); type_enum.insert("background", LIGHT_BACKGROUND); type_enum.insert("area", LIGHT_AREA); type_enum.insert("spot", LIGHT_SPOT); diff --git a/intern/cycles/render/session.h b/intern/cycles/render/session.h index 8bff0f9ed15..1db4692e171 100644 --- a/intern/cycles/render/session.h +++ b/intern/cycles/render/session.h @@ -89,8 +89,7 @@ public: } bool modified(const SessionParams& params) - { return !(device.type == params.device.type - && device.id == params.device.id + { return !(device == params.device && background == params.background && progressive_refine == params.progressive_refine && output_path == params.output_path diff --git a/intern/cycles/util/util_atomic.h b/intern/cycles/util/util_atomic.h index 1d1e2963348..433e41fbbb6 100644 --- a/intern/cycles/util/util_atomic.h +++ b/intern/cycles/util/util_atomic.h @@ -39,7 +39,7 @@ ATOMIC_INLINE void atomic_update_max_z(size_t *maximum_value, size_t value) /* Float atomics implementation credits: * http://suhorukov.blogspot.in/2011/12/opencl-11-atomic-operations-on-floating.html */ -ccl_device_inline void atomic_add_float(volatile ccl_global float *source, +ccl_device_inline void atomic_add_and_fetch_float(volatile ccl_global float *source, const float operand) { union { diff --git a/intern/cycles/util/util_hash.h b/intern/cycles/util/util_hash.h index 3ff2802b46d..98c3a681ff2 100644 --- a/intern/cycles/util/util_hash.h +++ b/intern/cycles/util/util_hash.h @@ -21,7 +21,7 @@ CCL_NAMESPACE_BEGIN -static inline uint hash_int_2d(uint kx, uint ky) +ccl_device_inline uint hash_int_2d(uint kx, uint ky) { #define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k)))) @@ -44,11 +44,12 @@ static inline uint hash_int_2d(uint kx, uint ky) #undef rot } -static inline uint hash_int(uint k) +ccl_device_inline uint hash_int(uint k) { return hash_int_2d(k, 0); } +#ifndef __KERNEL_GPU__ static inline uint hash_string(const char *str) { uint i = 0, c; @@ -58,6 +59,7 @@ static inline uint hash_string(const char *str) return i; } +#endif CCL_NAMESPACE_END diff --git a/intern/cycles/util/util_math.h b/intern/cycles/util/util_math.h index e2abfcde702..3f4d3e06c0b 100644 --- a/intern/cycles/util/util_math.h +++ b/intern/cycles/util/util_math.h @@ -162,6 +162,11 @@ ccl_device_inline float max4(float a, float b, float c, float d) return max(max(a, b), max(c, d)); } +ccl_device_inline float max3(float3 a) +{ + return max(max(a.x, a.y), a.z); +} + #ifndef __KERNEL_OPENCL__ ccl_device_inline int clamp(int a, int mn, int mx) diff --git a/intern/cycles/util/util_path.cpp b/intern/cycles/util/util_path.cpp index 62ef8fc0b48..5df262fcbbb 100644 --- a/intern/cycles/util/util_path.cpp +++ b/intern/cycles/util/util_path.cpp @@ -757,9 +757,9 @@ uint64_t path_modified_time(const string& path) { path_stat_t st; if(path_stat(path, &st) != 0) { - return st.st_mtime; + return 0; } - return 0; + return st.st_mtime; } bool path_remove(const string& path) diff --git a/intern/cycles/util/util_stats.h b/intern/cycles/util/util_stats.h index b970b017270..c21a8488c81 100644 --- a/intern/cycles/util/util_stats.h +++ b/intern/cycles/util/util_stats.h @@ -29,13 +29,13 @@ public: explicit Stats(static_init_t) {} void mem_alloc(size_t size) { - atomic_add_z(&mem_used, size); + atomic_add_and_fetch_z(&mem_used, size); atomic_update_max_z(&mem_peak, mem_used); } void mem_free(size_t size) { assert(mem_used >= size); - atomic_sub_z(&mem_used, size); + atomic_sub_and_fetch_z(&mem_used, size); } size_t mem_used; diff --git a/intern/guardedalloc/intern/mallocn_guarded_impl.c b/intern/guardedalloc/intern/mallocn_guarded_impl.c index 1933e9d3ee3..76b7e072321 100644 --- a/intern/guardedalloc/intern/mallocn_guarded_impl.c +++ b/intern/guardedalloc/intern/mallocn_guarded_impl.c @@ -505,8 +505,8 @@ static void make_memhead_header(MemHead *memh, size_t len, const char *str) memt = (MemTail *)(((char *) memh) + sizeof(MemHead) + len); memt->tag3 = MEMTAG3; - atomic_add_u(&totblock, 1); - atomic_add_z(&mem_in_use, len); + atomic_add_and_fetch_u(&totblock, 1); + atomic_add_and_fetch_z(&mem_in_use, len); mem_lock_thread(); addtail(membase, &memh->next); @@ -638,7 +638,7 @@ void *MEM_guarded_mapallocN(size_t len, const char *str) if (memh != (MemHead *)-1) { make_memhead_header(memh, len, str); memh->mmap = 1; - atomic_add_z(&mmap_in_use, len); + atomic_add_and_fetch_z(&mmap_in_use, len); mem_lock_thread(); peak_mem = mmap_in_use > peak_mem ? mmap_in_use : peak_mem; mem_unlock_thread(); @@ -1007,8 +1007,8 @@ static void rem_memblock(MemHead *memh) } mem_unlock_thread(); - atomic_sub_u(&totblock, 1); - atomic_sub_z(&mem_in_use, memh->len); + atomic_sub_and_fetch_u(&totblock, 1); + atomic_sub_and_fetch_z(&mem_in_use, memh->len); #ifdef DEBUG_MEMDUPLINAME if (memh->need_free_name) @@ -1016,7 +1016,7 @@ static void rem_memblock(MemHead *memh) #endif if (memh->mmap) { - atomic_sub_z(&mmap_in_use, memh->len); + atomic_sub_and_fetch_z(&mmap_in_use, memh->len); #if defined(WIN32) /* our windows mmap implementation is not thread safe */ mem_lock_thread(); diff --git a/intern/guardedalloc/intern/mallocn_lockfree_impl.c b/intern/guardedalloc/intern/mallocn_lockfree_impl.c index a80d67c3e80..ce8a5b29ece 100644 --- a/intern/guardedalloc/intern/mallocn_lockfree_impl.c +++ b/intern/guardedalloc/intern/mallocn_lockfree_impl.c @@ -142,11 +142,11 @@ void MEM_lockfree_freeN(void *vmemh) return; } - atomic_sub_u(&totblock, 1); - atomic_sub_z(&mem_in_use, len); + atomic_sub_and_fetch_u(&totblock, 1); + atomic_sub_and_fetch_z(&mem_in_use, len); if (MEMHEAD_IS_MMAP(memh)) { - atomic_sub_z(&mmap_in_use, len); + atomic_sub_and_fetch_z(&mmap_in_use, len); #if defined(WIN32) /* our windows mmap implementation is not thread safe */ mem_lock_thread(); @@ -287,8 +287,8 @@ void *MEM_lockfree_callocN(size_t len, const char *str) if (LIKELY(memh)) { memh->len = len; - atomic_add_u(&totblock, 1); - atomic_add_z(&mem_in_use, len); + atomic_add_and_fetch_u(&totblock, 1); + atomic_add_and_fetch_z(&mem_in_use, len); update_maximum(&peak_mem, mem_in_use); return PTR_FROM_MEMHEAD(memh); @@ -312,8 +312,8 @@ void *MEM_lockfree_mallocN(size_t len, const char *str) } memh->len = len; - atomic_add_u(&totblock, 1); - atomic_add_z(&mem_in_use, len); + atomic_add_and_fetch_u(&totblock, 1); + atomic_add_and_fetch_z(&mem_in_use, len); update_maximum(&peak_mem, mem_in_use); return PTR_FROM_MEMHEAD(memh); @@ -361,8 +361,8 @@ void *MEM_lockfree_mallocN_aligned(size_t len, size_t alignment, const char *str memh->len = len | (size_t) MEMHEAD_ALIGN_FLAG; memh->alignment = (short) alignment; - atomic_add_u(&totblock, 1); - atomic_add_z(&mem_in_use, len); + atomic_add_and_fetch_u(&totblock, 1); + atomic_add_and_fetch_z(&mem_in_use, len); update_maximum(&peak_mem, mem_in_use); return PTR_FROM_MEMHEAD(memh); @@ -396,9 +396,9 @@ void *MEM_lockfree_mapallocN(size_t len, const char *str) if (memh != (MemHead *)-1) { memh->len = len | (size_t) MEMHEAD_MMAP_FLAG; - atomic_add_u(&totblock, 1); - atomic_add_z(&mem_in_use, len); - atomic_add_z(&mmap_in_use, len); + atomic_add_and_fetch_u(&totblock, 1); + atomic_add_and_fetch_z(&mem_in_use, len); + atomic_add_and_fetch_z(&mmap_in_use, len); update_maximum(&peak_mem, mem_in_use); update_maximum(&peak_mem, mmap_in_use); diff --git a/intern/iksolver/intern/IK_QSegment.h b/intern/iksolver/intern/IK_QSegment.h index 74f157aa763..247807dc5e0 100644 --- a/intern/iksolver/intern/IK_QSegment.h +++ b/intern/iksolver/intern/IK_QSegment.h @@ -60,6 +60,7 @@ class IK_QSegment { public: + EIGEN_MAKE_ALIGNED_OPERATOR_NEW virtual ~IK_QSegment(); // start: a user defined translation diff --git a/intern/iksolver/intern/IK_Solver.cpp b/intern/iksolver/intern/IK_Solver.cpp index cefb8c7ed7b..a00db4fa2f5 100644 --- a/intern/iksolver/intern/IK_Solver.cpp +++ b/intern/iksolver/intern/IK_Solver.cpp @@ -42,6 +42,7 @@ using namespace std; class IK_QSolver { public: + EIGEN_MAKE_ALIGNED_OPERATOR_NEW IK_QSolver() : root(NULL) { } diff --git a/intern/libmv/libmv/simple_pipeline/modal_solver_test.cc b/intern/libmv/libmv/simple_pipeline/modal_solver_test.cc index 8b87acd95bb..b4cae8defb2 100644 --- a/intern/libmv/libmv/simple_pipeline/modal_solver_test.cc +++ b/intern/libmv/libmv/simple_pipeline/modal_solver_test.cc @@ -65,9 +65,9 @@ TEST(ModalSolver, SyntheticCubeSceneMotion) { NULL); Mat3 expected_rotation; - expected_rotation << 0.98215101299251, 0.17798357184544, 0.06083778292258, - -0.16875286001759, 0.97665299913606, -0.13293378620359, - -0.08307743323957, 0.12029450291547, 0.98925596922871; + expected_rotation << 0.98215101743472, 0.17798354937546, 0.06083777694542, + -0.16875283983360, 0.97665300495333, -0.13293376908719, + -0.08307742172243, 0.12029448893171, 0.98925597189636; Mat3 &first_camera_R = reconstruction.CameraForImage(1)->R; Mat3 &second_camera_R = reconstruction.CameraForImage(2)->R; @@ -4,6 +4,11 @@ REM This is for users who like to configure & build Blender with a single comman setlocal ENABLEEXTENSIONS set BLENDER_DIR=%~dp0 +set BLENDER_DIR_NOSPACES=%BLENDER_DIR: =% +if not "%BLENDER_DIR%"=="%BLENDER_DIR_NOSPACES%" ( + echo There are spaces detected in the build path "%BLENDER_DIR%", this is currently not supported, exiting.... + goto EOF +) set BUILD_DIR=%BLENDER_DIR%..\build_windows set BUILD_TYPE=Release rem reset all variables so they do not get accidentally get carried over from previous builds diff --git a/release/scripts/startup/bl_operators/mesh.py b/release/scripts/startup/bl_operators/mesh.py index be74f8dbc0e..58eab5436e6 100644 --- a/release/scripts/startup/bl_operators/mesh.py +++ b/release/scripts/startup/bl_operators/mesh.py @@ -198,3 +198,53 @@ class MeshSelectPrev(Operator): bmesh.update_edit_mesh(me, False) return {'FINISHED'} + + +# XXX This is hackish (going forth and back from Object mode...), to be redone once we have proper support of +# custom normals in BMesh/edit mode. +class MehsSetNormalsFromFaces(Operator): + """Set the custom vertex normals from the selected faces ones""" + bl_idname = "mesh.set_normals_from_faces" + bl_label = "Set Normals From Faces" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + return (context.mode == 'EDIT_MESH' and context.edit_object.data.polygons) + + def execute(self, context): + import mathutils + + bpy.ops.object.mode_set(mode='OBJECT') + obj = context.active_object + me = obj.data + + v2nors = {} + for p in me.polygons: + if not p.select: + continue + for lidx, vidx in zip(p.loop_indices, p.vertices): + assert(me.loops[lidx].vertex_index == vidx) + v2nors.setdefault(vidx, []).append(p.normal) + + for nors in v2nors.values(): + nors[:] = [sum(nors, mathutils.Vector((0, 0, 0))).normalized()] + + if not me.has_custom_normals: + me.create_normals_split() + me.calc_normals_split() + + normals = [] + for l in me.loops: + nor = v2nors.get(l.vertex_index, [None])[0] + if nor is None: + nor = l.normal + normals.append(nor.to_tuple()) + + me.normals_split_custom_set(normals) + + me.free_normals_split() + bpy.ops.object.mode_set(mode='EDIT') + + return {'FINISHED'} + diff --git a/release/scripts/startup/bl_operators/wm.py b/release/scripts/startup/bl_operators/wm.py index 1c97d213e05..68a25acc2db 100644 --- a/release/scripts/startup/bl_operators/wm.py +++ b/release/scripts/startup/bl_operators/wm.py @@ -935,16 +935,23 @@ def _wm_doc_get_id(doc_id, do_url=True, url_prefix=""): # detect if this is a inherited member and use that name instead rna_parent = rna_class.bl_rna - rna_prop = rna_parent.properties[class_prop] - rna_parent = rna_parent.base - while rna_parent and rna_prop == rna_parent.properties.get(class_prop): - class_name = rna_parent.identifier + rna_prop = rna_parent.properties.get(class_prop) + if rna_prop: rna_parent = rna_parent.base + while rna_parent and rna_prop == rna_parent.properties.get(class_prop): + class_name = rna_parent.identifier + rna_parent = rna_parent.base - if do_url: - url = ("%s/bpy.types.%s.html#bpy.types.%s.%s" % (url_prefix, class_name, class_name, class_prop)) + if do_url: + url = ("%s/bpy.types.%s.html#bpy.types.%s.%s" % (url_prefix, class_name, class_name, class_prop)) + else: + rna = ("bpy.types.%s.%s" % (class_name, class_prop)) else: - rna = ("bpy.types.%s.%s" % (class_name, class_prop)) + # We assume this is custom property, only try to generate generic url/rna_id... + if do_url: + url = ("%s/bpy.types.bpy_struct.html#bpy.types.bpy_struct.items" % (url_prefix,)) + else: + rna = "bpy.types.bpy_struct" return url if do_url else rna @@ -2163,3 +2170,32 @@ class WM_OT_addon_expand(Operator): info["show_expanded"] = not info["show_expanded"] return {'FINISHED'} + +class WM_OT_addon_userpref_show(Operator): + "Show add-on user preferences" + bl_idname = "wm.addon_userpref_show" + bl_label = "" + bl_options = {'INTERNAL'} + + module = StringProperty( + name="Module", + description="Module name of the add-on to expand", + ) + + def execute(self, context): + import addon_utils + + module_name = self.module + + modules = addon_utils.modules(refresh=False) + mod = addon_utils.addons_fake_modules.get(module_name) + if mod is not None: + info = addon_utils.module_bl_info(mod) + info["show_expanded"] = True + + bpy.context.user_preferences.active_section = 'ADDONS' + context.window_manager.addon_filter = 'All' + context.window_manager.addon_search = info["name"] + bpy.ops.screen.userpref_show('INVOKE_DEFAULT') + + return {'FINISHED'} diff --git a/release/scripts/startup/bl_ui/properties_data_mesh.py b/release/scripts/startup/bl_ui/properties_data_mesh.py index 5416735494b..59907692fe0 100644 --- a/release/scripts/startup/bl_ui/properties_data_mesh.py +++ b/release/scripts/startup/bl_ui/properties_data_mesh.py @@ -38,6 +38,7 @@ class MESH_MT_vertex_group_specials(Menu): layout.operator("object.vertex_group_mirror", text="Mirror Vertex Group (Topology)", icon='ARROW_LEFTRIGHT').use_topology = True layout.operator("object.vertex_group_remove_from", icon='X', text="Remove from All Groups").use_all_groups = True layout.operator("object.vertex_group_remove_from", icon='X', text="Clear Active Group").use_all_verts = True + layout.operator("object.vertex_group_remove", icon='X', text="Delete All Unlocked Groups").all_unlocked = True layout.operator("object.vertex_group_remove", icon='X', text="Delete All Groups").all = True layout.separator() layout.operator("object.vertex_group_lock", icon='LOCKED', text="Lock All").action = 'LOCK' diff --git a/release/scripts/startup/bl_ui/properties_grease_pencil_common.py b/release/scripts/startup/bl_ui/properties_grease_pencil_common.py index bc40932018d..08e07b8ed93 100644 --- a/release/scripts/startup/bl_ui/properties_grease_pencil_common.py +++ b/release/scripts/startup/bl_ui/properties_grease_pencil_common.py @@ -61,6 +61,9 @@ def gpencil_stroke_placement_settings(context, layout): def gpencil_active_brush_settings_simple(context, layout): brush = context.active_gpencil_brush + if brush is None: + layout.label("No Active Brush") + return col = layout.column() col.label("Active Brush: ") diff --git a/release/scripts/startup/bl_ui/properties_physics_rigidbody_constraint.py b/release/scripts/startup/bl_ui/properties_physics_rigidbody_constraint.py index 38c97746f4a..9d4f51b256b 100644 --- a/release/scripts/startup/bl_ui/properties_physics_rigidbody_constraint.py +++ b/release/scripts/startup/bl_ui/properties_physics_rigidbody_constraint.py @@ -205,30 +205,60 @@ class PHYSICS_PT_rigid_body_constraint(PHYSICS_PT_rigidbody_constraint_panel, Pa row = col.row(align=True) sub = row.row(align=True) - sub.scale_x = 0.1 - sub.prop(rbc, "use_spring_x", toggle=True, text="X") + sub.scale_x = 0.5 + sub.prop(rbc, "use_spring_x", toggle=True, text="X Axis") sub = row.row(align=True) sub.active = rbc.use_spring_x sub.prop(rbc, "spring_stiffness_x", text="Stiffness") - sub.prop(rbc, "spring_damping_x") + sub.prop(rbc, "spring_damping_x", text="Damping") row = col.row(align=True) sub = row.row(align=True) - sub.scale_x = 0.1 - sub.prop(rbc, "use_spring_y", toggle=True, text="Y") + sub.scale_x = 0.5 + sub.prop(rbc, "use_spring_y", toggle=True, text="Y Axis") sub = row.row(align=True) sub.active = rbc.use_spring_y sub.prop(rbc, "spring_stiffness_y", text="Stiffness") - sub.prop(rbc, "spring_damping_y") + sub.prop(rbc, "spring_damping_y", text="Damping") row = col.row(align=True) sub = row.row(align=True) - sub.scale_x = 0.1 - sub.prop(rbc, "use_spring_z", toggle=True, text="Z") + sub.scale_x = 0.5 + sub.prop(rbc, "use_spring_z", toggle=True, text="Z Axis") sub = row.row(align=True) sub.active = rbc.use_spring_z sub.prop(rbc, "spring_stiffness_z", text="Stiffness") - sub.prop(rbc, "spring_damping_z") + sub.prop(rbc, "spring_damping_z", text="Damping") + + col = layout.column(align=True) + + row = col.row(align=True) + sub = row.row(align=True) + sub.scale_x = 0.5 + sub.prop(rbc, "use_spring_ang_x", toggle=True, text="X Angle") + sub = row.row(align=True) + sub.active = rbc.use_spring_ang_x + sub.prop(rbc, "spring_stiffness_ang_x", text="Stiffness") + sub.prop(rbc, "spring_damping_ang_x", text="Damping") + + row = col.row(align=True) + sub = row.row(align=True) + sub.scale_x = 0.5 + sub.prop(rbc, "use_spring_ang_y", toggle=True, text="Y Angle") + sub = row.row(align=True) + sub.active = rbc.use_spring_ang_y + sub.prop(rbc, "spring_stiffness_ang_y", text="Stiffness") + sub.prop(rbc, "spring_damping_ang_y", text="Damping") + + row = col.row(align=True) + sub = row.row(align=True) + sub.scale_x = 0.5 + sub.prop(rbc, "use_spring_ang_z", toggle=True, text="Z Angle") + sub = row.row(align=True) + sub.active = rbc.use_spring_ang_z + sub.prop(rbc, "spring_stiffness_ang_z", text="Stiffness") + sub.prop(rbc, "spring_damping_ang_z", text="Damping") + if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) diff --git a/release/scripts/startup/bl_ui/properties_physics_smoke.py b/release/scripts/startup/bl_ui/properties_physics_smoke.py index df03f23a4b5..0374d032141 100644 --- a/release/scripts/startup/bl_ui/properties_physics_smoke.py +++ b/release/scripts/startup/bl_ui/properties_physics_smoke.py @@ -397,6 +397,14 @@ class PHYSICS_PT_smoke_display_settings(PhysicButtonsPanel, Panel): col.prop(domain, "vector_draw_type") col.prop(domain, "vector_scale") + layout.separator() + layout.label(text="Color Mapping:") + layout.prop(domain, "use_color_ramp") + col = layout.column(); + col.enabled = domain.use_color_ramp + col.prop(domain, "coba_field") + col.template_color_ramp(domain, "color_ramp", expand=True) + if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) diff --git a/release/scripts/startup/bl_ui/space_image.py b/release/scripts/startup/bl_ui/space_image.py index b6087184518..04b4cef9512 100644 --- a/release/scripts/startup/bl_ui/space_image.py +++ b/release/scripts/startup/bl_ui/space_image.py @@ -259,6 +259,20 @@ class IMAGE_MT_uvs_showhide(Menu): layout.operator("uv.hide", text="Hide Unselected").unselected = True +class IMAGE_MT_uvs_proportional(Menu): + bl_label = "Proportional Editing" + + def draw(self, context): + layout = self.layout + + layout.props_enum(context.tool_settings, "proportional_edit") + + layout.separator() + + layout.label("Falloff:") + layout.props_enum(context.tool_settings, "proportional_edit_falloff") + + class IMAGE_MT_uvs_transform(Menu): bl_label = "Transform" @@ -360,8 +374,7 @@ class IMAGE_MT_uvs(Menu): layout.separator() - layout.prop_menu_enum(toolsettings, "proportional_edit") - layout.prop_menu_enum(toolsettings, "proportional_edit_falloff") + layout.menu("IMAGE_MT_uvs_proportional") layout.separator() diff --git a/release/scripts/startup/bl_ui/space_userpref.py b/release/scripts/startup/bl_ui/space_userpref.py index dcafac66fca..ab3ec3559e5 100644 --- a/release/scripts/startup/bl_ui/space_userpref.py +++ b/release/scripts/startup/bl_ui/space_userpref.py @@ -429,13 +429,6 @@ class USERPREF_PT_system(Panel): col.separator() - if hasattr(system, "compute_device_type"): - col.label(text="Compute Device:") - col.row().prop(system, "compute_device_type", expand=True) - sub = col.row() - sub.active = system.compute_device_type != 'CPU' - sub.prop(system, "compute_device", text="") - if hasattr(system, "opensubdiv_compute_type"): col.label(text="OpenSubdiv compute:") col.row().prop(system, "opensubdiv_compute_type", text="") diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py index 8a3a5d3b7e8..9de9376312c 100644 --- a/release/scripts/startup/bl_ui/space_view3d.py +++ b/release/scripts/startup/bl_ui/space_view3d.py @@ -710,6 +710,7 @@ class VIEW3D_MT_select_particle(Menu): layout = self.layout layout.operator("view3d.select_border") + layout.operator("view3d.select_circle") layout.separator() @@ -2477,6 +2478,10 @@ class VIEW3D_MT_edit_mesh_vertices(Menu): layout.menu("VIEW3D_MT_vertex_group") layout.menu("VIEW3D_MT_hook") + layout.separator() + + layout.operator("object.vertex_parent_set") + class VIEW3D_MT_edit_mesh_edges(Menu): bl_label = "Edges" @@ -2731,6 +2736,10 @@ class VIEW3D_MT_edit_curve_ctrlpoints(Menu): layout.menu("VIEW3D_MT_hook") + layout.separator() + + layout.operator("object.vertex_parent_set") + class VIEW3D_MT_edit_curve_segments(Menu): bl_label = "Segments" @@ -2893,6 +2902,10 @@ class VIEW3D_MT_edit_lattice(Menu): layout.separator() + layout.operator("object.vertex_parent_set") + + layout.separator() + layout.menu("VIEW3D_MT_edit_proportional") diff --git a/release/scripts/startup/bl_ui/space_view3d_toolbar.py b/release/scripts/startup/bl_ui/space_view3d_toolbar.py index 8019c8d2f34..3eb76a3b0f9 100644 --- a/release/scripts/startup/bl_ui/space_view3d_toolbar.py +++ b/release/scripts/startup/bl_ui/space_view3d_toolbar.py @@ -431,6 +431,7 @@ class VIEW3D_PT_tools_shading(View3DPanel, Panel): col.label(text="Normals:") col.operator("mesh.normals_make_consistent", text="Recalculate") col.operator("mesh.flip_normals", text="Flip Direction") + col.operator("mesh.set_normals_from_faces", text="Set From Faces") class VIEW3D_PT_tools_uvs(View3DPanel, Panel): @@ -1559,7 +1560,7 @@ class VIEW3D_PT_sculpt_dyntopo(Panel, View3DPaintPanel): sub.active = (brush and brush.sculpt_tool != 'MASK') if (sculpt.detail_type_method == 'CONSTANT'): row = sub.row(align=True) - row.prop(sculpt, "constant_detail") + row.prop(sculpt, "constant_detail_resolution") row.operator("sculpt.sample_detail_size", text="", icon='EYEDROPPER') elif (sculpt.detail_type_method == 'BRUSH'): sub.prop(sculpt, "detail_percent") diff --git a/source/blender/blenkernel/BKE_armature.h b/source/blender/blenkernel/BKE_armature.h index c2323100205..78d6f6c7cb9 100644 --- a/source/blender/blenkernel/BKE_armature.h +++ b/source/blender/blenkernel/BKE_armature.h @@ -97,6 +97,7 @@ void BKE_armature_where_is(struct bArmature *arm); void BKE_armature_where_is_bone(struct Bone *bone, struct Bone *prevbone, const bool use_recursion); void BKE_pose_clear_pointers(struct bPose *pose); void BKE_pose_rebuild(struct Object *ob, struct bArmature *arm); +void BKE_pose_rebuild_ex(struct Object *ob, struct bArmature *arm, const bool sort_bones); void BKE_pose_where_is(struct Scene *scene, struct Object *ob); void BKE_pose_where_is_bone(struct Scene *scene, struct Object *ob, struct bPoseChannel *pchan, float ctime, bool do_extra); void BKE_pose_where_is_bone_tail(struct bPoseChannel *pchan); diff --git a/source/blender/blenkernel/BKE_blender_undo.h b/source/blender/blenkernel/BKE_blender_undo.h index 9547eeb9838..84a6d07be7d 100644 --- a/source/blender/blenkernel/BKE_blender_undo.h +++ b/source/blender/blenkernel/BKE_blender_undo.h @@ -42,6 +42,7 @@ extern bool BKE_undo_is_valid(const char *name); extern void BKE_undo_reset(void); extern void BKE_undo_number(struct bContext *C, int nr); extern const char *BKE_undo_get_name(int nr, bool *r_active); +extern const char *BKE_undo_get_name_last(void); extern bool BKE_undo_save_file(const char *filename); extern struct Main *BKE_undo_get_main(struct Scene **r_scene); diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index 55142510f9e..baf8510dd0d 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -28,7 +28,7 @@ * and keep comment above the defines. * Use STRINGIFY() rather than defining with quotes */ #define BLENDER_VERSION 278 -#define BLENDER_SUBVERSION 1 +#define BLENDER_SUBVERSION 3 /* Several breakages with 270, e.g. constraint deg vs rad */ #define BLENDER_MINVERSION 270 #define BLENDER_MINSUBVERSION 6 diff --git a/source/blender/blenkernel/BKE_library.h b/source/blender/blenkernel/BKE_library.h index 0d82de09165..855eb10976c 100644 --- a/source/blender/blenkernel/BKE_library.h +++ b/source/blender/blenkernel/BKE_library.h @@ -39,6 +39,7 @@ extern "C" { #include "BLI_compiler_attrs.h" struct BlendThumbnail; +struct GHash; struct ListBase; struct ID; struct ImBuf; @@ -64,7 +65,7 @@ struct ID *BKE_libblock_find_name(const short type, const char *name) ATTR_WARN_ /* library_remap.c (keep here since they're general functions) */ void BKE_libblock_free(struct Main *bmain, void *idv) ATTR_NONNULL(); -void BKE_libblock_free_ex(struct Main *bmain, void *idv, const bool do_id_user) ATTR_NONNULL(); +void BKE_libblock_free_ex(struct Main *bmain, void *idv, const bool do_id_user, const bool do_ui_user) ATTR_NONNULL(); void BKE_libblock_free_us(struct Main *bmain, void *idv) ATTR_NONNULL(); void BKE_libblock_free_data(struct Main *bmain, struct ID *id) ATTR_NONNULL(); void BKE_libblock_delete(struct Main *bmain, void *idv) ATTR_NONNULL(); @@ -125,8 +126,11 @@ void BKE_id_ui_prefix(char name[66 + 1], const struct ID *id); void BKE_library_free(struct Library *lib); void BKE_library_make_local( - struct Main *bmain, const struct Library *lib, const bool untagged_only, const bool set_fake); + struct Main *bmain, const struct Library *lib, struct GHash *old_to_new_ids, + const bool untagged_only, const bool set_fake); +void BKE_id_tag_set_atomic(struct ID *id, int tag); +void BKE_id_tag_clear_atomic(struct ID *id, int tag); /* use when "" is given to new_id() */ #define ID_FALLBACK_NAME N_("Untitled") diff --git a/source/blender/blenkernel/BKE_library_query.h b/source/blender/blenkernel/BKE_library_query.h index c6b63754b57..a7470107c24 100644 --- a/source/blender/blenkernel/BKE_library_query.h +++ b/source/blender/blenkernel/BKE_library_query.h @@ -88,6 +88,7 @@ bool BKE_library_ID_is_locally_used(struct Main *bmain, void *idv); bool BKE_library_ID_is_indirectly_used(struct Main *bmain, void *idv); void BKE_library_ID_test_usages(struct Main *bmain, void *idv, bool *is_used_local, bool *is_used_linked); -void BKE_library_tag_unused_linked_data(struct Main *bmain, const bool do_init_tag); +void BKE_library_unused_linked_data_set_tag(struct Main *bmain, const bool do_init_tag); +void BKE_library_indirectly_used_data_tag_clear(struct Main *bmain); #endif /* __BKE_LIBRARY_QUERY_H__ */ diff --git a/source/blender/blenkernel/BKE_object_deform.h b/source/blender/blenkernel/BKE_object_deform.h index a0a885c2a04..19a2220006a 100644 --- a/source/blender/blenkernel/BKE_object_deform.h +++ b/source/blender/blenkernel/BKE_object_deform.h @@ -51,9 +51,11 @@ bool BKE_object_defgroup_clear(struct Object *ob, struct bDeformGroup *dg, const bool BKE_object_defgroup_clear_all(struct Object *ob, const bool use_selection); void BKE_object_defgroup_remove(struct Object *ob, struct bDeformGroup *defgroup); +void BKE_object_defgroup_remove_all_ex(struct Object *ob, bool only_unlocked); void BKE_object_defgroup_remove_all(struct Object *ob); + /* Select helpers */ enum eVGroupSelect; bool *BKE_object_defgroup_subset_from_select_type( diff --git a/source/blender/blenkernel/intern/action.c b/source/blender/blenkernel/intern/action.c index 470098f8c7c..dcbb667adca 100644 --- a/source/blender/blenkernel/intern/action.c +++ b/source/blender/blenkernel/intern/action.c @@ -433,8 +433,8 @@ bPoseChannel *BKE_pose_channel_verify(bPose *pose, const char *name) chan->scaleIn = chan->scaleOut = 1.0f; - chan->limitmin[0] = chan->limitmin[1] = chan->limitmin[2] = -180.0f; - chan->limitmax[0] = chan->limitmax[1] = chan->limitmax[2] = 180.0f; + chan->limitmin[0] = chan->limitmin[1] = chan->limitmin[2] = -M_PI; + chan->limitmax[0] = chan->limitmax[1] = chan->limitmax[2] = M_PI; chan->stiffness[0] = chan->stiffness[1] = chan->stiffness[2] = 0.0f; chan->ikrotweight = chan->iklinweight = 0.0f; unit_m4(chan->constinv); diff --git a/source/blender/blenkernel/intern/anim_sys.c b/source/blender/blenkernel/intern/anim_sys.c index e3764adb969..a5abc6beff8 100644 --- a/source/blender/blenkernel/intern/anim_sys.c +++ b/source/blender/blenkernel/intern/anim_sys.c @@ -1657,7 +1657,7 @@ static bool animsys_write_rna_setting(PathResolvedRNA *anim_rna, const float val /* for cases like duplifarmes it's only a temporary so don't * notify anyone of updates */ if (!(id->tag & LIB_TAG_ANIM_NO_RECALC)) { - id->tag |= LIB_TAG_ID_RECALC; + BKE_id_tag_set_atomic(id, LIB_TAG_ID_RECALC); DAG_id_type_tag(G.main, GS(id->name)); } } diff --git a/source/blender/blenkernel/intern/armature.c b/source/blender/blenkernel/intern/armature.c index c644fe09364..2b333941c6e 100644 --- a/source/blender/blenkernel/intern/armature.c +++ b/source/blender/blenkernel/intern/armature.c @@ -1794,6 +1794,7 @@ static void pose_proxy_synchronize(Object *ob, Object *from, int layer_protected /* copy posechannel to temp, but restore important pointers */ pchanw = *pchanp; + pchanw.bone = pchan->bone; pchanw.prev = pchan->prev; pchanw.next = pchan->next; pchanw.parent = pchan->parent; @@ -1916,7 +1917,7 @@ void BKE_pose_clear_pointers(bPose *pose) /* only after leave editmode, duplicating, validating older files, library syncing */ /* NOTE: pose->flag is set for it */ -void BKE_pose_rebuild(Object *ob, bArmature *arm) +void BKE_pose_rebuild_ex(Object *ob, bArmature *arm, const bool sort_bones) { Bone *bone; bPose *pose; @@ -1963,8 +1964,9 @@ void BKE_pose_rebuild(Object *ob, bArmature *arm) #ifdef WITH_LEGACY_DEPSGRAPH /* the sorting */ /* Sorting for new dependnecy graph is done on the scene graph level. */ - if (counter > 1) + if (counter > 1 && sort_bones) { DAG_pose_sort(ob); + } #endif ob->pose->flag &= ~POSE_RECALC; @@ -1973,6 +1975,11 @@ void BKE_pose_rebuild(Object *ob, bArmature *arm) BKE_pose_channels_hash_make(ob->pose); } +void BKE_pose_rebuild(Object *ob, bArmature *arm) +{ + BKE_pose_rebuild_ex(ob, arm, true); +} + /* ********************** THE POSE SOLVER ******************* */ /* loc/rot/size to given mat4 */ diff --git a/source/blender/blenkernel/intern/blender_copybuffer.c b/source/blender/blenkernel/intern/blender_copybuffer.c index a4c28121040..e57524af546 100644 --- a/source/blender/blenkernel/intern/blender_copybuffer.c +++ b/source/blender/blenkernel/intern/blender_copybuffer.c @@ -101,7 +101,7 @@ bool BKE_copybuffer_read(Main *bmain_dst, const char *libname, ReportList *repor IMB_colormanagement_check_file_config(bmain_dst); /* Append, rather than linking. */ Library *lib = BLI_findstring(&bmain_dst->library, libname, offsetof(Library, filepath)); - BKE_library_make_local(bmain_dst, lib, true, false); + BKE_library_make_local(bmain_dst, lib, NULL, true, false); /* Important we unset, otherwise these object wont * link into other scenes from this blend file. */ @@ -150,7 +150,7 @@ bool BKE_copybuffer_paste(bContext *C, const char *libname, const short flag, Re /* append, rather than linking */ lib = BLI_findstring(&bmain->library, libname, offsetof(Library, filepath)); - BKE_library_make_local(bmain, lib, true, false); + BKE_library_make_local(bmain, lib, NULL, true, false); /* important we unset, otherwise these object wont * link into other scenes from this blend file */ diff --git a/source/blender/blenkernel/intern/blender_undo.c b/source/blender/blenkernel/intern/blender_undo.c index d64bf7ecf43..ce6d29bbfee 100644 --- a/source/blender/blenkernel/intern/blender_undo.c +++ b/source/blender/blenkernel/intern/blender_undo.c @@ -319,6 +319,13 @@ const char *BKE_undo_get_name(int nr, bool *r_active) return NULL; } +/* return the name of the last item */ +const char *BKE_undo_get_name_last() +{ + UndoElem *uel = undobase.last; + return (uel ? uel->name : NULL); +} + /** * Saves .blend using undo buffer. * diff --git a/source/blender/blenkernel/intern/blendfile.c b/source/blender/blenkernel/intern/blendfile.c index 6da68470ecc..54f709a1e5b 100644 --- a/source/blender/blenkernel/intern/blendfile.c +++ b/source/blender/blenkernel/intern/blendfile.c @@ -407,9 +407,9 @@ bool BKE_blendfile_read_from_memfile( if (bfd) { /* remove the unused screens and wm */ while (bfd->main->wm.first) - BKE_libblock_free_ex(bfd->main, bfd->main->wm.first, true); + BKE_libblock_free_ex(bfd->main, bfd->main->wm.first, true, true); while (bfd->main->screen.first) - BKE_libblock_free_ex(bfd->main, bfd->main->screen.first, true); + BKE_libblock_free_ex(bfd->main, bfd->main->screen.first, true, true); setup_app_data(C, bfd, "<memory1>", reports); } diff --git a/source/blender/blenkernel/intern/cachefile.c b/source/blender/blenkernel/intern/cachefile.c index 6a08673144e..deeb35bd880 100644 --- a/source/blender/blenkernel/intern/cachefile.c +++ b/source/blender/blenkernel/intern/cachefile.c @@ -93,7 +93,9 @@ void BKE_cachefile_free(CacheFile *cache_file) ABC_free_handle(cache_file->handle); #endif - BLI_mutex_free(cache_file->handle_mutex); + if (cache_file->handle_mutex) { + BLI_mutex_free(cache_file->handle_mutex); + } BLI_freelistN(&cache_file->object_paths); } diff --git a/source/blender/blenkernel/intern/depsgraph.c b/source/blender/blenkernel/intern/depsgraph.c index 02ae123a71e..50f8423bbff 100644 --- a/source/blender/blenkernel/intern/depsgraph.c +++ b/source/blender/blenkernel/intern/depsgraph.c @@ -3284,7 +3284,7 @@ void DAG_threaded_update_handle_node_updated(void *node_v, for (itA = node->child; itA; itA = itA->next) { DagNode *child_node = itA->node; if (child_node != node) { - atomic_sub_uint32(&child_node->num_pending_parents, 1); + atomic_sub_and_fetch_uint32(&child_node->num_pending_parents, 1); if (child_node->num_pending_parents == 0) { bool need_schedule; diff --git a/source/blender/blenkernel/intern/dynamicpaint.c b/source/blender/blenkernel/intern/dynamicpaint.c index c7399047ed5..66070923153 100644 --- a/source/blender/blenkernel/intern/dynamicpaint.c +++ b/source/blender/blenkernel/intern/dynamicpaint.c @@ -2264,7 +2264,7 @@ static void dynamic_paint_create_uv_surface_neighbor_cb(void *userdata, const in * to non--1 *before* its tri_index is set (i.e. that it cannot be used a neighbour). */ tPoint->neighbour_pixel = ind - 1; - atomic_add_uint32(&tPoint->neighbour_pixel, 1); + atomic_add_and_fetch_uint32(&tPoint->neighbour_pixel, 1); tPoint->tri_index = i; /* Now calculate pixel data for this pixel as it was on polygon surface */ @@ -2289,7 +2289,7 @@ static void dynamic_paint_create_uv_surface_neighbor_cb(void *userdata, const in /* Increase the final number of active surface points if relevant. */ if (tPoint->tri_index != -1) - atomic_add_uint32(active_points, 1); + atomic_add_and_fetch_uint32(active_points, 1); } } diff --git a/source/blender/blenkernel/intern/library.c b/source/blender/blenkernel/intern/library.c index 14612151a8e..3411eae22e1 100644 --- a/source/blender/blenkernel/intern/library.c +++ b/source/blender/blenkernel/intern/library.c @@ -73,6 +73,7 @@ #include "BLI_blenlib.h" #include "BLI_utildefines.h" +#include "BLI_ghash.h" #include "BLI_linklist.h" #include "BLI_memarena.h" @@ -128,6 +129,8 @@ #include "IMB_imbuf.h" #include "IMB_imbuf_types.h" +#include "atomic_ops.h" + /* GS reads the memory pointed at in a specific ordering. * only use this definition, makes little and big endian systems * work fine, in conjunction with MAKE_ID */ @@ -261,9 +264,12 @@ void id_fake_user_clear(ID *id) } static int id_expand_local_callback( - void *UNUSED(user_data), struct ID *UNUSED(id_self), struct ID **id_pointer, int UNUSED(cd_flag)) + void *UNUSED(user_data), struct ID *id_self, struct ID **id_pointer, int UNUSED(cd_flag)) { - if (*id_pointer) { + /* Can hapen that we get unlinkable ID here, e.g. with shapekey referring to itself (through drivers)... + * Just skip it, shape key can only be either indirectly linked, or fully local, period. + * And let's curse one more time that stupid useless shapekey ID type! */ + if (*id_pointer && *id_pointer != id_self && BKE_idcode_is_linkable(GS((*id_pointer)->name))) { id_lib_extern(*id_pointer); } @@ -1205,46 +1211,46 @@ void BKE_main_free(Main *mainvar) while ( (id = lb->first) ) { #if 1 - BKE_libblock_free_ex(mainvar, id, false); + BKE_libblock_free_ex(mainvar, id, false, false); #else /* errors freeing ID's can be hard to track down, * enable this so valgrind will give the line number in its error log */ switch (a) { - case 0: BKE_libblock_free_ex(mainvar, id, false); break; - case 1: BKE_libblock_free_ex(mainvar, id, false); break; - case 2: BKE_libblock_free_ex(mainvar, id, false); break; - case 3: BKE_libblock_free_ex(mainvar, id, false); break; - case 4: BKE_libblock_free_ex(mainvar, id, false); break; - case 5: BKE_libblock_free_ex(mainvar, id, false); break; - case 6: BKE_libblock_free_ex(mainvar, id, false); break; - case 7: BKE_libblock_free_ex(mainvar, id, false); break; - case 8: BKE_libblock_free_ex(mainvar, id, false); break; - case 9: BKE_libblock_free_ex(mainvar, id, false); break; - case 10: BKE_libblock_free_ex(mainvar, id, false); break; - case 11: BKE_libblock_free_ex(mainvar, id, false); break; - case 12: BKE_libblock_free_ex(mainvar, id, false); break; - case 13: BKE_libblock_free_ex(mainvar, id, false); break; - case 14: BKE_libblock_free_ex(mainvar, id, false); break; - case 15: BKE_libblock_free_ex(mainvar, id, false); break; - case 16: BKE_libblock_free_ex(mainvar, id, false); break; - case 17: BKE_libblock_free_ex(mainvar, id, false); break; - case 18: BKE_libblock_free_ex(mainvar, id, false); break; - case 19: BKE_libblock_free_ex(mainvar, id, false); break; - case 20: BKE_libblock_free_ex(mainvar, id, false); break; - case 21: BKE_libblock_free_ex(mainvar, id, false); break; - case 22: BKE_libblock_free_ex(mainvar, id, false); break; - case 23: BKE_libblock_free_ex(mainvar, id, false); break; - case 24: BKE_libblock_free_ex(mainvar, id, false); break; - case 25: BKE_libblock_free_ex(mainvar, id, false); break; - case 26: BKE_libblock_free_ex(mainvar, id, false); break; - case 27: BKE_libblock_free_ex(mainvar, id, false); break; - case 28: BKE_libblock_free_ex(mainvar, id, false); break; - case 29: BKE_libblock_free_ex(mainvar, id, false); break; - case 30: BKE_libblock_free_ex(mainvar, id, false); break; - case 31: BKE_libblock_free_ex(mainvar, id, false); break; - case 32: BKE_libblock_free_ex(mainvar, id, false); break; - case 33: BKE_libblock_free_ex(mainvar, id, false); break; - case 34: BKE_libblock_free_ex(mainvar, id, false); break; + case 0: BKE_libblock_free_ex(mainvar, id, false, false); break; + case 1: BKE_libblock_free_ex(mainvar, id, false, false); break; + case 2: BKE_libblock_free_ex(mainvar, id, false, false); break; + case 3: BKE_libblock_free_ex(mainvar, id, false, false); break; + case 4: BKE_libblock_free_ex(mainvar, id, false, false); break; + case 5: BKE_libblock_free_ex(mainvar, id, false, false); break; + case 6: BKE_libblock_free_ex(mainvar, id, false, false); break; + case 7: BKE_libblock_free_ex(mainvar, id, false, false); break; + case 8: BKE_libblock_free_ex(mainvar, id, false, false); break; + case 9: BKE_libblock_free_ex(mainvar, id, false, false); break; + case 10: BKE_libblock_free_ex(mainvar, id, false, false); break; + case 11: BKE_libblock_free_ex(mainvar, id, false, false); break; + case 12: BKE_libblock_free_ex(mainvar, id, false, false); break; + case 13: BKE_libblock_free_ex(mainvar, id, false, false); break; + case 14: BKE_libblock_free_ex(mainvar, id, false, false); break; + case 15: BKE_libblock_free_ex(mainvar, id, false, false); break; + case 16: BKE_libblock_free_ex(mainvar, id, false, false); break; + case 17: BKE_libblock_free_ex(mainvar, id, false, false); break; + case 18: BKE_libblock_free_ex(mainvar, id, false, false); break; + case 19: BKE_libblock_free_ex(mainvar, id, false, false); break; + case 20: BKE_libblock_free_ex(mainvar, id, false, false); break; + case 21: BKE_libblock_free_ex(mainvar, id, false, false); break; + case 22: BKE_libblock_free_ex(mainvar, id, false, false); break; + case 23: BKE_libblock_free_ex(mainvar, id, false, false); break; + case 24: BKE_libblock_free_ex(mainvar, id, false, false); break; + case 25: BKE_libblock_free_ex(mainvar, id, false, false); break; + case 26: BKE_libblock_free_ex(mainvar, id, false, false); break; + case 27: BKE_libblock_free_ex(mainvar, id, false, false); break; + case 28: BKE_libblock_free_ex(mainvar, id, false, false); break; + case 29: BKE_libblock_free_ex(mainvar, id, false, false); break; + case 30: BKE_libblock_free_ex(mainvar, id, false, false); break; + case 31: BKE_libblock_free_ex(mainvar, id, false, false); break; + case 32: BKE_libblock_free_ex(mainvar, id, false, false); break; + case 33: BKE_libblock_free_ex(mainvar, id, false, false); break; + case 34: BKE_libblock_free_ex(mainvar, id, false, false); break; default: BLI_assert(0); break; @@ -1626,30 +1632,28 @@ void BKE_main_id_clear_newpoins(Main *bmain) * \param untagged_only If true, only make local datablocks not tagged with LIB_TAG_PRE_EXISTING. * \param set_fake If true, set fake user on all localized datablocks (except group and objects ones). */ -/* XXX TODO This function should probably be reworked. - * - * Old (2.77) version was simply making (tagging) datablocks as local, without actually making any check whether +/* Note: Old (2.77) version was simply making (tagging) datablocks as local, without actually making any check whether * they were also indirectly used or not... * - * Current version uses regular id_make_local callback, but this is not super-efficient since this ends up + * Current version uses regular id_make_local callback, which is not super-efficient since this ends up * duplicating some IDs and then removing original ones (due to missing knowledge of which ID uses some other ID). * - * We could first check all IDs and detect those to be made local that are only used by other local or future-local - * datablocks, and directly tag those as local (instead of going through id_make_local) maybe... - * - * We'll probably need at some point a true dependency graph between datablocks, but for now this should work - * good enough (performances is not a critical point here anyway). + * However, we now have a first check that allows us to use 'direct localization' of a lot of IDs, so performances + * are now *reasonably* OK. */ -void BKE_library_make_local(Main *bmain, const Library *lib, const bool untagged_only, const bool set_fake) +void BKE_library_make_local( + Main *bmain, const Library *lib, GHash *old_to_new_ids, const bool untagged_only, const bool set_fake) { ListBase *lbarray[MAX_LIBARRAY]; - ID *id, *id_next; + ID *id; int a; + LinkNode *todo_ids = NULL; LinkNode *copied_ids = NULL; LinkNode *linked_loop_candidates = NULL; - MemArena *linklist_mem = BLI_memarena_new(256 * sizeof(copied_ids), __func__); + MemArena *linklist_mem = BLI_memarena_new(512 * sizeof(*todo_ids), __func__); + /* Step 1: Detect datablocks to make local. */ for (a = set_listbasepointers(bmain, lbarray); a--; ) { id = lbarray[a]->first; @@ -1657,54 +1661,80 @@ void BKE_library_make_local(Main *bmain, const Library *lib, const bool untagged * by real datablocks responsible of them. */ const bool do_skip = (id && !BKE_idcode_is_linkable(GS(id->name))); - for (; id; id = id_next) { + for (; id; id = id->next) { id->newid = NULL; id->tag &= ~LIB_TAG_DOIT; - id_next = id->next; /* id is possibly being inserted again */ - /* The check on the second line (LIB_TAG_PRE_EXISTING) is done so its + if (id->lib == NULL) { + id->tag &= ~(LIB_TAG_EXTERN | LIB_TAG_INDIRECT | LIB_TAG_NEW); + } + /* The check on the fourth line (LIB_TAG_PRE_EXISTING) is done so its * possible to tag data you don't want to be made local, used for * appending data, so any libdata already linked wont become local - * (very nasty to discover all your links are lost after appending) - * */ - if (!do_skip && id->tag & (LIB_TAG_EXTERN | LIB_TAG_INDIRECT | LIB_TAG_NEW) && - ((untagged_only == false) || !(id->tag & LIB_TAG_PRE_EXISTING))) + * (very nasty to discover all your links are lost after appending). + * Also, never ever make proxified objects local, would not make any sense. */ + else if (!do_skip && id->tag & (LIB_TAG_EXTERN | LIB_TAG_INDIRECT | LIB_TAG_NEW) && + ELEM(lib, NULL, id->lib) && + !(GS(id->name) == ID_OB && ((Object *)id)->proxy_from != NULL) && + ((untagged_only == false) || !(id->tag & LIB_TAG_PRE_EXISTING))) { - if (lib == NULL || id->lib == lib) { - if (id->lib) { - /* In this specific case, we do want to make ID local even if it has no local usage yet... */ - if (GS(id->name) == ID_OB) { - /* Special case for objects because we don't want proxy pointers to be - * cleared yet. This will happen down the road in this function. - */ - BKE_object_make_local_ex(bmain, (Object*)id, true, false); - } - else { - id_make_local(bmain, id, false, true); - } - - if (id->newid) { - BLI_linklist_prepend_arena(&copied_ids, id, linklist_mem); - } - } - else { - id->tag &= ~(LIB_TAG_EXTERN | LIB_TAG_INDIRECT | LIB_TAG_NEW); - } - } + BLI_linklist_prepend_arena(&todo_ids, id, linklist_mem); + id->tag |= LIB_TAG_DOIT; + } + } + } - if (set_fake) { - if (!ELEM(GS(id->name), ID_OB, ID_GR)) { - /* do not set fake user on objects, groups (instancing) */ - id_fake_user_set(id); - } - } + /* Step 2: Check which datablocks we can directly make local (because they are only used by already, or future, + * local data), others will need to be duplicated and further processed later. */ + BKE_library_indirectly_used_data_tag_clear(bmain); + + /* Step 3: Make IDs local, either directly (quick and simple), or using generic process, + * which involves more complex checks and might instead create a local copy of original linked ID. */ + for (LinkNode *it = todo_ids, *it_next; it; it = it_next) { + it_next = it->next; + id = it->link; + + if (id->tag & LIB_TAG_DOIT) { + /* We know all users of this object are local or will be made fully local, even if currently there are + * some indirect usages. So instead of making a copy that se'll likely get rid of later, directly make + * that data block local. Saves a tremendous amount of time with complex scenes... */ + id_clear_lib_data_ex(bmain, id, true); + BKE_id_expand_local(id); + id->tag &= ~LIB_TAG_DOIT; + } + else { + /* In this specific case, we do want to make ID local even if it has no local usage yet... */ + if (GS(id->name) == ID_OB) { + /* Special case for objects because we don't want proxy pointers to be + * cleared yet. This will happen down the road in this function. + */ + BKE_object_make_local_ex(bmain, (Object*)id, true, false); + } + else { + id_make_local(bmain, id, false, true); + } + + if (id->newid) { + /* Reuse already allocated LinkNode (transferring it from todo_ids to copied_ids). */ + BLI_linklist_prepend_nlink(&copied_ids, id, it); + } + } + + if (set_fake) { + if (!ELEM(GS(id->name), ID_OB, ID_GR)) { + /* do not set fake user on objects, groups (instancing) */ + id_fake_user_set(id); } } } - /* We have to remap local usages of old (linked) ID to new (local) id in a second loop, as lbarray ordering is not - * enough to ensure us we did catch all dependencies (e.g. if making local a parent object before its child...). - * See T48907. */ + /* At this point, we are done with directly made local IDs. Now we have to handle duplicated ones, since their + * remaining linked original counterpart may not be needed anymore... */ + todo_ids = NULL; + + /* Step 4: We have to remap local usages of old (linked) ID to new (local) id in a separated loop, + * as lbarray ordering is not enough to ensure us we did catch all dependencies + * (e.g. if making local a parent object before its child...). See T48907. */ for (LinkNode *it = copied_ids; it; it = it->next) { id = it->link; @@ -1712,9 +1742,18 @@ void BKE_library_make_local(Main *bmain, const Library *lib, const bool untagged BLI_assert(id->lib != NULL); BKE_libblock_remap(bmain, id, id->newid, ID_REMAP_SKIP_INDIRECT_USAGE); + if (old_to_new_ids) { + BLI_ghash_insert(old_to_new_ids, id, id->newid); + } + + /* Special hack for groups... Thing is, since we can't instantiate them here, we need to ensure + * they remain 'alive' (only instantiation is a real group 'user'... *sigh* See T49722. */ + if (GS(id->name) == ID_GR && (id->tag & LIB_TAG_INDIRECT) != 0) { + id_us_ensure_real(id->newid); + } } - /* Third step: remove datablocks that have been copied to be localized and are no more used in the end... + /* Step 5: remove datablocks that have been copied to be localized and are no more used in the end... * Note that we may have to loop more than once here, to tackle dependencies between linked objects... */ bool do_loop = true; while (do_loop) { @@ -1761,11 +1800,6 @@ void BKE_library_make_local(Main *bmain, const Library *lib, const bool untagged ob->proxy = ob->proxy_from = ob->proxy_group = NULL; } } - /* Special hack for groups... Thing is, since we can't instantiate them here, we need to ensure - * they remain 'alive' (only instantiation is a real group 'user'... *sigh* See T49722. */ - else if (GS(id->name) == ID_GR && (id->tag & LIB_TAG_INDIRECT) != 0) { - id_us_ensure_real(id->newid); - } if (!is_local) { if (!is_lib) { /* Not used at all, we can free it! */ @@ -1773,7 +1807,8 @@ void BKE_library_make_local(Main *bmain, const Library *lib, const bool untagged it->link = NULL; do_loop = true; } - else { /* Only used by linked data, potential candidate to ugly lib-only dependency cycles... */ + /* Only used by linked data, potential candidate to ugly lib-only dependency cycles... */ + else if ((id->tag & LIB_TAG_DOIT) == 0) { /* Check TAG_DOIT to avoid adding same ID several times... */ /* Note that we store the node, not directly ID pointer, that way if it->link is set to NULL * later we can skip it in lib-dependency cycles search later. */ BLI_linklist_prepend_arena(&linked_loop_candidates, it, linklist_mem); @@ -1791,9 +1826,9 @@ void BKE_library_make_local(Main *bmain, const Library *lib, const bool untagged } } - /* Fourth step: Try to find circle dependencies between indirectly-linked-only datablocks. + /* Step 6: Try to find circle dependencies between indirectly-linked-only datablocks. * Those are fake 'usages' that prevent their deletion. See T49775 for nice ugly case. */ - BKE_library_tag_unused_linked_data(bmain, false); + BKE_library_unused_linked_data_set_tag(bmain, false); for (LinkNode *it = linked_loop_candidates; it; it = it->next) { if (it->link == NULL) { continue; @@ -1806,16 +1841,23 @@ void BKE_library_make_local(Main *bmain, const Library *lib, const bool untagged /* Note: in theory here we are only handling datablocks forming exclusive linked dependency-cycles-based * archipelagos, so no need to check again after we have deleted one, as done in previous step. */ if (id->tag & LIB_TAG_DOIT) { + /* Object's deletion rely on valid ob->data, but ob->data may have already been freed here... + * Setting it to NULL may not be 100% correct, but should be safe and do the work. */ + if (GS(id->name) == ID_OB) { + ((Object *)id)->data = NULL; + } + /* Note: *in theory* IDs tagged here are fully *outside* of file scope, totally unused, so we can * directly wipe them out without caring about clearing their usages. * However, this is a highly-risky presumption, and nice crasher in case something goes wrong here. * So for 2.78a will keep the safe option, and switch to more efficient one in master later. */ -#if 0 - BKE_libblock_free_ex(bmain, id, false); +#if 1 + BKE_libblock_free_ex(bmain, id, false, true); #else BKE_libblock_unlink(bmain, id, false, false); BKE_libblock_free(bmain, id); #endif + ((LinkNode *)it->link)->link = NULL; /* Not strictly necessary, but safer (see T49903)... */ it->link = NULL; } } @@ -1888,3 +1930,13 @@ void BKE_library_filepath_set(Library *lib, const char *filepath) BLI_path_abs(lib->filepath, basepath); } } + +void BKE_id_tag_set_atomic(ID *id, int tag) +{ + atomic_fetch_and_or_uint32((uint32_t *)&id->tag, tag); +} + +void BKE_id_tag_clear_atomic(ID *id, int tag) +{ + atomic_fetch_and_and_uint32((uint32_t *)&id->tag, ~tag); +} diff --git a/source/blender/blenkernel/intern/library_query.c b/source/blender/blenkernel/intern/library_query.c index cec7fbacd80..fa75c906fb1 100644 --- a/source/blender/blenkernel/intern/library_query.c +++ b/source/blender/blenkernel/intern/library_query.c @@ -1170,8 +1170,9 @@ void BKE_library_ID_test_usages(Main *bmain, void *idv, bool *is_used_local, boo *is_used_linked = (iter.count_indirect != 0); } - -static int foreach_libblock_tag_unused_linked_data_callback(void *user_data, ID *self_id, ID **id_p, int UNUSED(cb_flag)) +/* ***** IDs usages.checking/tagging. ***** */ +static int foreach_libblock_used_linked_data_tag_clear_cb( + void *user_data, ID *self_id, ID **id_p, int UNUSED(cb_flag)) { bool *is_changed = user_data; @@ -1206,7 +1207,7 @@ static int foreach_libblock_tag_unused_linked_data_callback(void *user_data, ID * \param do_init_tag if \a true, all linked data are checked, if \a false, only linked datablocks already tagged with * LIB_TAG_DOIT are checked. */ -void BKE_library_tag_unused_linked_data(Main *bmain, const bool do_init_tag) +void BKE_library_unused_linked_data_set_tag(Main *bmain, const bool do_init_tag) { ListBase *lb_array[MAX_LIBARRAY]; @@ -1232,7 +1233,38 @@ void BKE_library_tag_unused_linked_data(Main *bmain, const bool do_init_tag) while (i--) { for (ID *id = lb_array[i]->first; id; id = id->next) { - BKE_library_foreach_ID_link(id, foreach_libblock_tag_unused_linked_data_callback, &do_loop, IDWALK_NOP); + if (id->tag & LIB_TAG_DOIT) { + /* Unused ID (so far), no need to check it further. */ + continue; + } + BKE_library_foreach_ID_link(id, foreach_libblock_used_linked_data_tag_clear_cb, &do_loop, IDWALK_NOP); + } + } + } +} + +/** + * Untag linked data blocks used by other untagged linked datablocks. + * Used to detect datablocks that we can forcefully make local (instead of copying them to later get rid of original): + * All datablocks we want to make local are tagged by caller, after this function has ran caller knows datablocks still + * tagged can directly be made local, since they are only used by other datablocks that will also be made fully local. + */ +void BKE_library_indirectly_used_data_tag_clear(Main *bmain) +{ + ListBase *lb_array[MAX_LIBARRAY]; + + bool do_loop = true; + while (do_loop) { + int i = set_listbasepointers(bmain, lb_array); + do_loop = false; + + while (i--) { + for (ID *id = lb_array[i]->first; id; id = id->next) { + if (id->lib == NULL || id->tag & LIB_TAG_DOIT) { + /* Local or non-indirectly-used ID (so far), no need to check it further. */ + continue; + } + BKE_library_foreach_ID_link(id, foreach_libblock_used_linked_data_tag_clear_cb, &do_loop, IDWALK_NOP); } } } diff --git a/source/blender/blenkernel/intern/library_remap.c b/source/blender/blenkernel/intern/library_remap.c index b468e6436c8..40441034171 100644 --- a/source/blender/blenkernel/intern/library_remap.c +++ b/source/blender/blenkernel/intern/library_remap.c @@ -666,46 +666,23 @@ void BKE_libblock_relink_ex( } } -static void animdata_dtar_clear_cb(ID *UNUSED(id), AnimData *adt, void *userdata) -{ - ChannelDriver *driver; - FCurve *fcu; - - /* find the driver this belongs to and update it */ - for (fcu = adt->drivers.first; fcu; fcu = fcu->next) { - driver = fcu->driver; - - if (driver) { - DriverVar *dvar; - for (dvar = driver->variables.first; dvar; dvar = dvar->next) { - DRIVER_TARGETS_USED_LOOPER(dvar) - { - if (dtar->id == userdata) - dtar->id = NULL; - } - DRIVER_TARGETS_LOOPER_END - } - } - } -} - -void BKE_libblock_free_data(Main *bmain, ID *id) +void BKE_libblock_free_data(Main *UNUSED(bmain), ID *id) { if (id->properties) { IDP_FreeProperty(id->properties); MEM_freeN(id->properties); } - - /* this ID may be a driver target! */ - BKE_animdata_main_cb(bmain, animdata_dtar_clear_cb, (void *)id); } /** * used in headerbuttons.c image.c mesh.c screen.c sound.c and library.c * * \param do_id_user: if \a true, try to release other ID's 'references' hold by \a idv. + * (only applies to main database) + * \param do_ui_user: similar to do_id_user but makes sure UI does not hold references to + * \a id. */ -void BKE_libblock_free_ex(Main *bmain, void *idv, const bool do_id_user) +void BKE_libblock_free_ex(Main *bmain, void *idv, const bool do_id_user, const bool do_ui_user) { ID *id = idv; short type = GS(id->name); @@ -830,12 +807,14 @@ void BKE_libblock_free_ex(Main *bmain, void *idv, const bool do_id_user) /* avoid notifying on removed data */ BKE_main_lock(bmain); - if (free_notifier_reference_cb) { - free_notifier_reference_cb(id); - } + if (do_ui_user) { + if (free_notifier_reference_cb) { + free_notifier_reference_cb(id); + } - if (remap_editor_id_reference_cb) { - remap_editor_id_reference_cb(id, NULL); + if (remap_editor_id_reference_cb) { + remap_editor_id_reference_cb(id, NULL); + } } BLI_remlink(lb, id); @@ -848,7 +827,7 @@ void BKE_libblock_free_ex(Main *bmain, void *idv, const bool do_id_user) void BKE_libblock_free(Main *bmain, void *idv) { - BKE_libblock_free_ex(bmain, idv, true); + BKE_libblock_free_ex(bmain, idv, true, true); } void BKE_libblock_free_us(Main *bmain, void *idv) /* test users */ diff --git a/source/blender/blenkernel/intern/mesh_evaluate.c b/source/blender/blenkernel/intern/mesh_evaluate.c index fa113ef5eef..a3fe73e4b11 100644 --- a/source/blender/blenkernel/intern/mesh_evaluate.c +++ b/source/blender/blenkernel/intern/mesh_evaluate.c @@ -58,6 +58,7 @@ #include "BLI_strict_flags.h" +#include "atomic_ops.h" #include "mikktspace.h" // #define DEBUG_TIME @@ -236,7 +237,9 @@ static void mesh_calc_normals_poly_accum_task_cb(void *userdata, const int pidx) const float fac = saacos(-dot_v3v3(cur_edge, prev_edge)); /* accumulate */ - madd_v3_v3fl(vnors[ml[i].v], pnor, fac); + for (int k = 3; k--; ) { + atomic_add_and_fetch_fl(&vnors[ml[i].v][k], pnor[k] * fac); + } prev_edge = cur_edge; } } diff --git a/source/blender/blenkernel/intern/object_deform.c b/source/blender/blenkernel/intern/object_deform.c index b5f63588423..b5e1ded35bb 100644 --- a/source/blender/blenkernel/intern/object_deform.c +++ b/source/blender/blenkernel/intern/object_deform.c @@ -408,8 +408,9 @@ void BKE_object_defgroup_remove(Object *ob, bDeformGroup *defgroup) /** * Remove all vgroups from object. Work in Object and Edit modes. + * When only_unlocked=true, locked vertex groups are not removed. */ -void BKE_object_defgroup_remove_all(Object *ob) +void BKE_object_defgroup_remove_all_ex(struct Object *ob, bool only_unlocked) { bDeformGroup *dg = (bDeformGroup *)ob->defbase.first; const bool edit_mode = BKE_object_is_in_editmode_vgroup(ob); @@ -418,10 +419,12 @@ void BKE_object_defgroup_remove_all(Object *ob) while (dg) { bDeformGroup *next_dg = dg->next; - if (edit_mode) - object_defgroup_remove_edit_mode(ob, dg); - else - object_defgroup_remove_object_mode(ob, dg); + if (!only_unlocked || (dg->flag & DG_LOCK_WEIGHT) == 0) { + if (edit_mode) + object_defgroup_remove_edit_mode(ob, dg); + else + object_defgroup_remove_object_mode(ob, dg); + } dg = next_dg; } @@ -446,6 +449,15 @@ void BKE_object_defgroup_remove_all(Object *ob) } /** + * Remove all vgroups from object. Work in Object and Edit modes. + */ +void BKE_object_defgroup_remove_all(struct Object *ob) +{ + BKE_object_defgroup_remove_all_ex(ob, false); +} + + +/** * Get MDeformVert vgroup data from given object. Should only be used in Object mode. * * \return True if the id type supports weights. diff --git a/source/blender/blenkernel/intern/pbvh.c b/source/blender/blenkernel/intern/pbvh.c index ff69f381b06..4fe4d6e75a6 100644 --- a/source/blender/blenkernel/intern/pbvh.c +++ b/source/blender/blenkernel/intern/pbvh.c @@ -977,7 +977,7 @@ static void pbvh_update_normals_accum_task_cb(void *userdata, const int n) * Not exact equivalent though, since atomicity is only ensured for one component * of the vector at a time, but here it shall not make any sensible difference. */ for (int k = 3; k--; ) { - atomic_add_fl(&vnors[v][k], fn[k]); + atomic_add_and_fetch_fl(&vnors[v][k], fn[k]); } } } diff --git a/source/blender/blenkernel/intern/pbvh_bmesh.c b/source/blender/blenkernel/intern/pbvh_bmesh.c index 55f9f384081..a821578db1a 100644 --- a/source/blender/blenkernel/intern/pbvh_bmesh.c +++ b/source/blender/blenkernel/intern/pbvh_bmesh.c @@ -148,8 +148,7 @@ BLI_INLINE void bm_face_as_array_index_tri(BMFace *f, int r_index[3]) * * Its assumed that \a l_radial_first is never forming the target face. */ -static bool bm_face_exists_tri_from_loop_vert( - BMLoop *l_radial_first, BMVert *v_opposite, BMFace **r_face_existing) +static BMFace *bm_face_exists_tri_from_loop_vert(BMLoop *l_radial_first, BMVert *v_opposite) { BLI_assert(!ELEM(v_opposite, l_radial_first->v, l_radial_first->next->v, l_radial_first->prev->v)); if (l_radial_first->radial_next != l_radial_first) { @@ -157,12 +156,11 @@ static bool bm_face_exists_tri_from_loop_vert( do { BLI_assert(l_radial_iter->f->len == 3); if (l_radial_iter->prev->v == v_opposite) { - *r_face_existing = l_radial_iter->f; - return true; + return l_radial_iter->f; } } while ((l_radial_iter = l_radial_iter->radial_next) != l_radial_first); } - return false; + return NULL; } /** @@ -519,7 +517,7 @@ static BMFace *pbvh_bmesh_face_create( PBVHNode *node = &bvh->nodes[node_index]; /* ensure we never add existing face */ - BLI_assert(BM_face_exists(v_tri, 3, NULL) == false); + BLI_assert(!BM_face_exists(v_tri, 3)); BMFace *f = BM_face_create(bvh->bm, v_tri, e_tri, 3, f_example, BM_CREATE_NOP); f->head.hflag = f_example->head.hflag; @@ -1313,18 +1311,17 @@ static void pbvh_bmesh_collapse_edge( * deletion as well. Prevents extraneous "flaps" from being * created. */ #if 0 - if (UNLIKELY(BM_face_exists(v_tri, 3, &existing_face))) + if (UNLIKELY(existing_face = BM_face_exists(v_tri, 3))) #else - if (UNLIKELY(bm_face_exists_tri_from_loop_vert(l->next, v_conn, &existing_face))) + if (UNLIKELY(existing_face = bm_face_exists_tri_from_loop_vert(l->next, v_conn))) #endif { - BLI_assert(existing_face); BLI_buffer_append(deleted_faces, BMFace *, existing_face); } else { BMVert *v_tri[3] = {v_conn, l->next->v, l->prev->v}; - BLI_assert(BM_face_exists(v_tri, 3, NULL) == false); + BLI_assert(!BM_face_exists(v_tri, 3)); BMEdge *e_tri[3]; PBVHNode *n = pbvh_bmesh_node_from_face(bvh, f); int ni = n - bvh->nodes; diff --git a/source/blender/blenkernel/intern/rigidbody.c b/source/blender/blenkernel/intern/rigidbody.c index c3ae5736aa9..ebf9f017731 100644 --- a/source/blender/blenkernel/intern/rigidbody.c +++ b/source/blender/blenkernel/intern/rigidbody.c @@ -65,13 +65,22 @@ #include "BKE_rigidbody.h" #include "BKE_scene.h" -#ifdef WITH_BULLET - /* ************************************** */ /* Memory Management */ /* Freeing Methods --------------------- */ +#ifndef WITH_BULLET + +static void RB_dworld_remove_constraint(void *UNUSED(world), void *UNUSED(con)) {} +static void RB_dworld_remove_body(void *UNUSED(world), void *UNUSED(body)) {} +static void RB_dworld_delete(void *UNUSED(world)) {} +static void RB_body_delete(void *UNUSED(body)) {} +static void RB_shape_delete(void *UNUSED(shape)) {} +static void RB_constraint_delete(void *UNUSED(con)) {} + +#endif + /* Free rigidbody world */ void BKE_rigidbody_free_world(RigidBodyWorld *rbw) { @@ -165,6 +174,8 @@ void BKE_rigidbody_free_constraint(Object *ob) ob->rigidbody_constraint = NULL; } +#ifdef WITH_BULLET + /* Copying Methods --------------------- */ /* These just copy the data, clearing out references to physics objects. @@ -804,6 +815,18 @@ static void rigidbody_validate_sim_constraint(RigidBodyWorld *rbw, Object *ob, b RB_constraint_set_stiffness_6dof_spring(rbc->physics_constraint, RB_LIMIT_LIN_Z, rbc->spring_stiffness_z); RB_constraint_set_damping_6dof_spring(rbc->physics_constraint, RB_LIMIT_LIN_Z, rbc->spring_damping_z); + RB_constraint_set_spring_6dof_spring(rbc->physics_constraint, RB_LIMIT_ANG_X, rbc->flag & RBC_FLAG_USE_SPRING_ANG_X); + RB_constraint_set_stiffness_6dof_spring(rbc->physics_constraint, RB_LIMIT_ANG_X, rbc->spring_stiffness_ang_x); + RB_constraint_set_damping_6dof_spring(rbc->physics_constraint, RB_LIMIT_ANG_X, rbc->spring_damping_ang_x); + + RB_constraint_set_spring_6dof_spring(rbc->physics_constraint, RB_LIMIT_ANG_Y, rbc->flag & RBC_FLAG_USE_SPRING_ANG_Y); + RB_constraint_set_stiffness_6dof_spring(rbc->physics_constraint, RB_LIMIT_ANG_Y, rbc->spring_stiffness_ang_y); + RB_constraint_set_damping_6dof_spring(rbc->physics_constraint, RB_LIMIT_ANG_Y, rbc->spring_damping_ang_y); + + RB_constraint_set_spring_6dof_spring(rbc->physics_constraint, RB_LIMIT_ANG_Z, rbc->flag & RBC_FLAG_USE_SPRING_ANG_Z); + RB_constraint_set_stiffness_6dof_spring(rbc->physics_constraint, RB_LIMIT_ANG_Z, rbc->spring_stiffness_ang_z); + RB_constraint_set_damping_6dof_spring(rbc->physics_constraint, RB_LIMIT_ANG_Z, rbc->spring_damping_ang_z); + RB_constraint_set_equilibrium_6dof_spring(rbc->physics_constraint); /* fall-through */ case RBC_TYPE_6DOF: @@ -1072,9 +1095,15 @@ RigidBodyCon *BKE_rigidbody_create_constraint(Scene *scene, Object *ob, short ty rbc->spring_damping_x = 0.5f; rbc->spring_damping_y = 0.5f; rbc->spring_damping_z = 0.5f; + rbc->spring_damping_ang_x = 0.5f; + rbc->spring_damping_ang_y = 0.5f; + rbc->spring_damping_ang_z = 0.5f; rbc->spring_stiffness_x = 10.0f; rbc->spring_stiffness_y = 10.0f; rbc->spring_stiffness_z = 10.0f; + rbc->spring_stiffness_ang_x = 10.0f; + rbc->spring_stiffness_ang_y = 10.0f; + rbc->spring_stiffness_ang_z = 10.0f; rbc->motor_lin_max_impulse = 1.0f; rbc->motor_lin_target_velocity = 1.0f; @@ -1602,9 +1631,6 @@ void BKE_rigidbody_do_simulation(Scene *scene, float ctime) # pragma GCC diagnostic ignored "-Wunused-parameter" #endif -void BKE_rigidbody_free_world(RigidBodyWorld *rbw) {} -void BKE_rigidbody_free_object(Object *ob) {} -void BKE_rigidbody_free_constraint(Object *ob) {} struct RigidBodyOb *BKE_rigidbody_copy_object(Object *ob) { return NULL; } struct RigidBodyCon *BKE_rigidbody_copy_constraint(Object *ob) { return NULL; } void BKE_rigidbody_relink_constraint(RigidBodyCon *rbc) {} diff --git a/source/blender/blenkernel/intern/smoke.c b/source/blender/blenkernel/intern/smoke.c index 05540f51588..e8970d416e9 100644 --- a/source/blender/blenkernel/intern/smoke.c +++ b/source/blender/blenkernel/intern/smoke.c @@ -360,6 +360,10 @@ static void smokeModifier_freeDomain(SmokeModifierData *smd) BKE_ptcache_free_list(&(smd->domain->ptcaches[0])); smd->domain->point_cache[0] = NULL; + if (smd->domain->coba) { + MEM_freeN(smd->domain->coba); + } + MEM_freeN(smd->domain); smd->domain = NULL; } @@ -544,6 +548,9 @@ void smokeModifier_createType(struct SmokeModifierData *smd) smd->domain->slice_depth = 0.5f; smd->domain->slice_axis = 0; smd->domain->vector_scale = 1.0f; + + smd->domain->coba = NULL; + smd->domain->coba_field = FLUID_FIELD_DENSITY; } else if (smd->type & MOD_SMOKE_TYPE_FLOW) { @@ -646,6 +653,10 @@ void smokeModifier_copy(struct SmokeModifierData *smd, struct SmokeModifierData tsmd->domain->draw_velocity = smd->domain->draw_velocity; tsmd->domain->vector_draw_type = smd->domain->vector_draw_type; tsmd->domain->vector_scale = smd->domain->vector_scale; + + if (smd->domain->coba) { + tsmd->domain->coba = MEM_dupallocN(smd->domain->coba); + } } else if (tsmd->flow) { tsmd->flow->psys = smd->flow->psys; diff --git a/source/blender/blenkernel/intern/sound.c b/source/blender/blenkernel/intern/sound.c index 3132a8e27e7..f20885b1e8f 100644 --- a/source/blender/blenkernel/intern/sound.c +++ b/source/blender/blenkernel/intern/sound.c @@ -251,7 +251,8 @@ void BKE_sound_init(struct Main *bmain) void BKE_sound_init_main(struct Main *bmain) { #ifdef WITH_JACK - AUD_setSynchronizerCallback(sound_sync_callback, bmain); + if (sound_device) + AUD_setSynchronizerCallback(sound_sync_callback, bmain); #else (void)bmain; /* unused */ #endif diff --git a/source/blender/blenlib/intern/task.c b/source/blender/blenlib/intern/task.c index 436cd2b8fde..fc2d9674c2f 100644 --- a/source/blender/blenlib/intern/task.c +++ b/source/blender/blenlib/intern/task.c @@ -237,7 +237,7 @@ static void task_pool_num_decrease(TaskPool *pool, size_t done) BLI_assert(pool->num >= done); pool->num -= done; - atomic_sub_z(&pool->currently_running_tasks, done); + atomic_sub_and_fetch_z(&pool->currently_running_tasks, done); pool->done += done; if (pool->num == 0) @@ -292,7 +292,7 @@ static bool task_scheduler_thread_wait_pop(TaskScheduler *scheduler, Task **task continue; } - if (atomic_add_z(&pool->currently_running_tasks, 1) <= pool->num_threads || + if (atomic_add_and_fetch_z(&pool->currently_running_tasks, 1) <= pool->num_threads || pool->num_threads == 0) { *task = current_task; @@ -301,7 +301,7 @@ static bool task_scheduler_thread_wait_pop(TaskScheduler *scheduler, Task **task break; } else { - atomic_sub_z(&pool->currently_running_tasks, 1); + atomic_sub_and_fetch_z(&pool->currently_running_tasks, 1); } } if (!found_task) @@ -669,7 +669,7 @@ void BLI_task_pool_work_and_wait(TaskPool *pool) /* if found task, do it, otherwise wait until other tasks are done */ if (found_task) { /* run task */ - atomic_add_z(&pool->currently_running_tasks, 1); + atomic_add_and_fetch_z(&pool->currently_running_tasks, 1); work_task->run(pool, work_task->taskdata, 0); /* delete task */ diff --git a/source/blender/blenloader/intern/readfile.c b/source/blender/blenloader/intern/readfile.c index 0de883fe7bf..5c460c7b176 100644 --- a/source/blender/blenloader/intern/readfile.c +++ b/source/blender/blenloader/intern/readfile.c @@ -5095,6 +5095,7 @@ static void direct_link_modifiers(FileData *fd, ListBase *lb) smd->domain->tex = NULL; smd->domain->tex_shadow = NULL; smd->domain->tex_wt = NULL; + smd->domain->coba = newdataadr(fd, smd->domain->coba); smd->domain->effector_weights = newdataadr(fd, smd->domain->effector_weights); if (!smd->domain->effector_weights) diff --git a/source/blender/blenloader/intern/versioning_270.c b/source/blender/blenloader/intern/versioning_270.c index 14e02c9ffb6..8133d0496fa 100644 --- a/source/blender/blenloader/intern/versioning_270.c +++ b/source/blender/blenloader/intern/versioning_270.c @@ -53,6 +53,7 @@ #include "DNA_actuator_types.h" #include "DNA_view3d_types.h" #include "DNA_smoke_types.h" +#include "DNA_rigidbody_types.h" #include "DNA_genfile.h" @@ -1426,7 +1427,7 @@ void blo_do_versions_270(FileData *fd, Library *UNUSED(lib), Main *main) } } - { + if (!MAIN_VERSION_ATLEAST(main, 278, 3)) { for (Scene *scene = main->scene.first; scene != NULL; scene = scene->id.next) { if (scene->toolsettings != NULL) { ToolSettings *ts = scene->toolsettings; @@ -1438,5 +1439,31 @@ void blo_do_versions_270(FileData *fd, Library *UNUSED(lib), Main *main) } } } + + if (!DNA_struct_elem_find(fd->filesdna, "RigidBodyCon", "float", "spring_stiffness_ang_x")) { + Object *ob; + for (ob = main->object.first; ob; ob = ob->id.next) { + RigidBodyCon *rbc = ob->rigidbody_constraint; + if (rbc) { + rbc->spring_stiffness_ang_x = 10.0; + rbc->spring_stiffness_ang_y = 10.0; + rbc->spring_stiffness_ang_z = 10.0; + rbc->spring_damping_ang_x = 0.5; + rbc->spring_damping_ang_y = 0.5; + rbc->spring_damping_ang_z = 0.5; + } + } + } + + /* constant detail for sculpting is now a resolution value instead of + * a percentage, we reuse old DNA struct member but convert it */ + for (Scene *scene = main->scene.first; scene != NULL; scene = scene->id.next) { + if (scene->toolsettings != NULL) { + ToolSettings *ts = scene->toolsettings; + if (ts->sculpt && ts->sculpt->constant_detail != 0.0f) { + ts->sculpt->constant_detail = 100.0f / ts->sculpt->constant_detail; + } + } + } } } diff --git a/source/blender/blenloader/intern/writefile.c b/source/blender/blenloader/intern/writefile.c index b02b76118a2..f2fef5d476b 100644 --- a/source/blender/blenloader/intern/writefile.c +++ b/source/blender/blenloader/intern/writefile.c @@ -1729,6 +1729,10 @@ static void write_modifiers(WriteData *wd, ListBase *modbase) smd->domain->point_cache[1]->step = 1; write_pointcaches(wd, &(smd->domain->ptcaches[1])); + + if (smd->domain->coba) { + writestruct(wd, DATA, ColorBand, 1, smd->domain->coba); + } } writestruct(wd, DATA, SmokeDomainSettings, 1, smd->domain); diff --git a/source/blender/bmesh/bmesh_class.h b/source/blender/bmesh/bmesh_class.h index 72ea7bd7f5d..104df625ee6 100644 --- a/source/blender/bmesh/bmesh_class.h +++ b/source/blender/bmesh/bmesh_class.h @@ -245,7 +245,7 @@ typedef struct BMesh { /* ID of the shape key this bmesh came from */ int shapenr; - int walkers, totflags; + int totflags; ListBase selected; BMFace *act_face; diff --git a/source/blender/bmesh/intern/bmesh_construct.c b/source/blender/bmesh/intern/bmesh_construct.c index 4d92baab6eb..132a7ccd4fa 100644 --- a/source/blender/bmesh/intern/bmesh_construct.c +++ b/source/blender/bmesh/intern/bmesh_construct.c @@ -626,7 +626,7 @@ void BM_elem_attrs_copy(BMesh *bm_src, BMesh *bm_dst, const void *ele_src, void BM_elem_attrs_copy_ex(bm_src, bm_dst, ele_src, ele_dst, BM_ELEM_SELECT); } -void BM_elem_select_copy(BMesh *bm_dst, BMesh *UNUSED(bm_src), void *ele_dst_v, const void *ele_src_v) +void BM_elem_select_copy(BMesh *bm_dst, void *ele_dst_v, const void *ele_src_v) { BMHeader *ele_dst = ele_dst_v; const BMHeader *ele_src = ele_src_v; diff --git a/source/blender/bmesh/intern/bmesh_construct.h b/source/blender/bmesh/intern/bmesh_construct.h index 06bc5465a19..9c6483de42b 100644 --- a/source/blender/bmesh/intern/bmesh_construct.h +++ b/source/blender/bmesh/intern/bmesh_construct.h @@ -58,7 +58,7 @@ void BM_elem_attrs_copy_ex( BMesh *bm_src, BMesh *bm_dst, const void *ele_src_v, void *ele_dst_v, const char hflag_mask); void BM_elem_attrs_copy(BMesh *bm_src, BMesh *bm_dst, const void *ele_src_v, void *ele_dst_v); -void BM_elem_select_copy(BMesh *bm_dst, BMesh *bm_src, void *ele_dst_v, const void *ele_src_v); +void BM_elem_select_copy(BMesh *bm_dst, void *ele_dst_v, const void *ele_src_v); void BM_mesh_copy_init_customdata(BMesh *bm_dst, BMesh *bm_src, const struct BMAllocTemplate *allocsize); BMesh *BM_mesh_copy(BMesh *bm_old); diff --git a/source/blender/bmesh/intern/bmesh_core.c b/source/blender/bmesh/intern/bmesh_core.c index e83b752947c..0cd91107171 100644 --- a/source/blender/bmesh/intern/bmesh_core.c +++ b/source/blender/bmesh/intern/bmesh_core.c @@ -287,7 +287,7 @@ static BMLoop *bm_face_boundary_add( #endif BMLoop *l = bm_loop_create(bm, startv, starte, f, NULL /* starte->l */, create_flag); - bmesh_radial_append(starte, l); + bmesh_radial_loop_append(starte, l); #ifdef USE_BMESH_HOLES lst->first = lst->last = l; @@ -295,8 +295,6 @@ static BMLoop *bm_face_boundary_add( #else f->l_first = l; #endif - - l->f = f; return l; } @@ -446,26 +444,20 @@ BMFace *BM_face_create( if (create_flag & BM_CREATE_NO_DOUBLE) { /* Check if face already exists */ - const bool is_overlap = BM_face_exists(verts, len, &f); - if (is_overlap) { + f = BM_face_exists(verts, len); + if (f != NULL) { return f; } - else { - BLI_assert(f == NULL); - } } f = bm_face_create__internal(bm); startl = lastl = bm_face_boundary_add(bm, f, verts[0], edges[0], create_flag); - - startl->v = verts[0]; - startl->e = edges[0]; + for (i = 1; i < len; i++) { l = bm_loop_create(bm, verts[i], edges[i], f, NULL /* edges[i]->l */, create_flag); - - l->f = f; - bmesh_radial_append(edges[i], l); + + bmesh_radial_loop_append(edges[i], l); l->prev = lastl; lastl->next = l; @@ -904,7 +896,7 @@ void BM_face_kill(BMesh *bm, BMFace *f) do { l_next = l_iter->next; - bmesh_radial_loop_remove(l_iter, l_iter->e); + bmesh_radial_loop_remove(l_iter->e, l_iter); bm_kill_only_loop(bm, l_iter); } while ((l_iter = l_next) != l_first); @@ -949,7 +941,7 @@ void BM_face_kill_loose(BMesh *bm, BMFace *f) l_next = l_iter->next; e = l_iter->e; - bmesh_radial_loop_remove(l_iter, e); + bmesh_radial_loop_remove(e, l_iter); bm_kill_only_loop(bm, l_iter); if (e->l == NULL) { @@ -981,23 +973,8 @@ void BM_face_kill_loose(BMesh *bm, BMFace *f) */ void BM_edge_kill(BMesh *bm, BMEdge *e) { - - if (e->l) { - BMLoop *l = e->l, *lnext, *startl = e->l; - - do { - lnext = l->radial_next; - if (lnext->f == l->f) { - BM_face_kill(bm, l->f); - break; - } - - BM_face_kill(bm, l->f); - - if (l == lnext) - break; - l = lnext; - } while (l != startl); + while (e->l) { + BM_face_kill(bm, e->l->f); } bmesh_disk_edge_remove(e, e->v1); @@ -1011,15 +988,8 @@ void BM_edge_kill(BMesh *bm, BMEdge *e) */ void BM_vert_kill(BMesh *bm, BMVert *v) { - if (v->e) { - BMEdge *e, *e_next; - - e = v->e; - while (v->e) { - e_next = bmesh_disk_edge_next(e, v); - BM_edge_kill(bm, e); - e = e_next; - } + while (v->e) { + BM_edge_kill(bm, v->e); } bm_kill_only_vert(bm, v); @@ -1046,78 +1016,73 @@ static int UNUSED_FUNCTION(bm_loop_length)(BMLoop *l) * \brief Loop Reverse * * Changes the winding order of a face from CW to CCW or vice versa. - * This euler is a bit peculiar in comparison to others as it is its - * own inverse. - * - * BMESH_TODO: reinsert validation code. * * \param cd_loop_mdisp_offset: Cached result of `CustomData_get_offset(&bm->ldata, CD_MDISPS)`. * \param use_loop_mdisp_flip: When set, flip the Z-depth of the mdisp, * (use when flipping normals, disable when mirroring, eg: symmetrize). - * - * \return Success */ -static bool bm_loop_reverse_loop( +void bmesh_loop_reverse( BMesh *bm, BMFace *f, -#ifdef USE_BMESH_HOLES - BMLoopList *lst, -#endif const int cd_loop_mdisp_offset, const bool use_loop_mdisp_flip) { + BMLoop *l_first = f->l_first; -#ifdef USE_BMESH_HOLES - BMLoop *l_first = lst->first; + /* track previous cycles radial state */ + BMEdge *e_prev = l_first->prev->e; + BMLoop *l_prev_radial_next = l_first->prev->radial_next; + BMLoop *l_prev_radial_prev = l_first->prev->radial_prev; + bool is_prev_boundary = l_prev_radial_next == l_prev_radial_next->radial_next; + + BMLoop *l_iter = l_first; + do { + BMEdge *e_iter = l_iter->e; + BMLoop *l_iter_radial_next = l_iter->radial_next; + BMLoop *l_iter_radial_prev = l_iter->radial_prev; + bool is_iter_boundary = l_iter_radial_next == l_iter_radial_next->radial_next; + +#if 0 + bmesh_radial_loop_remove(e_iter, l_iter); + bmesh_radial_loop_append(e_prev, l_iter); #else - BMLoop *l_first = f->l_first; -#endif + /* inline loop reversal */ + if (is_prev_boundary) { + /* boundary */ + l_iter->radial_next = l_iter; + l_iter->radial_prev = l_iter; + } + else { + /* non-boundary, replace radial links */ + l_iter->radial_next = l_prev_radial_next; + l_iter->radial_prev = l_prev_radial_prev; + l_prev_radial_next->radial_prev = l_iter; + l_prev_radial_prev->radial_next = l_iter; + } - const int len = f->len; - BMLoop *l_iter, *oldprev, *oldnext; - BMEdge **edar = BLI_array_alloca(edar, len); - int i, j, edok; + if (e_iter->l == l_iter) { + e_iter->l = l_iter->next; + } + l_iter->e = e_prev; +#endif - for (i = 0, l_iter = l_first; i < len; i++, l_iter = l_iter->next) { - bmesh_radial_loop_remove(l_iter, (edar[i] = l_iter->e)); - } + SWAP(BMLoop *, l_iter->next, l_iter->prev); - /* actually reverse the loop */ - for (i = 0, l_iter = l_first; i < len; i++) { - oldnext = l_iter->next; - oldprev = l_iter->prev; - l_iter->next = oldprev; - l_iter->prev = oldnext; - l_iter = oldnext; - if (cd_loop_mdisp_offset != -1) { MDisps *md = BM_ELEM_CD_GET_VOID_P(l_iter, cd_loop_mdisp_offset); BKE_mesh_mdisp_flip(md, use_loop_mdisp_flip); } - } - if (len == 2) { /* two edged face */ - /* do some verification here! */ - l_first->e = edar[1]; - l_first->next->e = edar[0]; - } - else { - for (i = 0, l_iter = l_first; i < len; i++, l_iter = l_iter->next) { - edok = 0; - for (j = 0; j < len; j++) { - edok = BM_verts_in_edge(l_iter->v, l_iter->next->v, edar[j]); - if (edok) { - l_iter->e = edar[j]; - break; - } - } - } - } - /* rebuild radial */ - for (i = 0, l_iter = l_first; i < len; i++, l_iter = l_iter->next) - bmesh_radial_append(l_iter->e, l_iter); + e_prev = e_iter; + l_prev_radial_next = l_iter_radial_next; + l_prev_radial_prev = l_iter_radial_prev; + is_prev_boundary = is_iter_boundary; + + /* step to next (now swapped) */ + } while ((l_iter = l_iter->prev) != l_first); #ifndef NDEBUG /* validate radial */ - for (i = 0, l_iter = l_first; i < len; i++, l_iter = l_iter->next) { + int i; + for (i = 0, l_iter = l_first; i < f->len; i++, l_iter = l_iter->next) { BM_CHECK_ELEMENT(l_iter); BM_CHECK_ELEMENT(l_iter->e); BM_CHECK_ELEMENT(l_iter->v); @@ -1129,22 +1094,6 @@ static bool bm_loop_reverse_loop( /* Loop indices are no more valid! */ bm->elem_index_dirty |= BM_LOOP; - - return true; -} - -/** - * \brief Flip the faces direction - */ -bool bmesh_loop_reverse( - BMesh *bm, BMFace *f, - const int cd_loop_mdisp_offset, const bool use_loop_mdisp_flip) -{ -#ifdef USE_BMESH_HOLES - return bm_loop_reverse_loop(bm, f, f->loops.first, cd_loop_mdisp_offset, use_loop_mdisp_flip); -#else - return bm_loop_reverse_loop(bm, f, cd_loop_mdisp_offset, use_loop_mdisp_flip); -#endif } static void bm_elements_systag_enable(void *veles, int tot, const char api_flag) @@ -1193,7 +1142,11 @@ static int UNUSED_FUNCTION(bm_vert_systag_count_disk)(BMVert *v, const char api_ return i; } -static bool disk_is_flagged(BMVert *v, const char api_flag) +/** + * Return true when the vertex is manifold, + * attached to faces which are all flagged. + */ +static bool bm_vert_is_manifold_flagged(BMVert *v, const char api_flag) { BMEdge *e = v->e; @@ -1252,7 +1205,7 @@ BMFace *BM_faces_join(BMesh *bm, BMFace **faces, int totface, const bool do_del) BLI_array_staticdeclare(deledges, BM_DEFAULT_NGON_STACK_SIZE); BLI_array_staticdeclare(delverts, BM_DEFAULT_NGON_STACK_SIZE); BMVert *v1 = NULL, *v2 = NULL; - int i, tote = 0; + int i; const int cd_loop_mdisp_offset = CustomData_get_offset(&bm->ldata, CD_MDISPS); if (UNLIKELY(!totface)) { @@ -1282,13 +1235,10 @@ BMFace *BM_faces_join(BMesh *bm, BMFace **faces, int totface, const bool do_del) v1 = l_iter->v; v2 = BM_edge_other_vert(l_iter->e, l_iter->v); } - tote++; } else if (rlen == 2) { - int d1, d2; - - d1 = disk_is_flagged(l_iter->e->v1, _FLAG_JF); - d2 = disk_is_flagged(l_iter->e->v2, _FLAG_JF); + const bool d1 = bm_vert_is_manifold_flagged(l_iter->e->v1, _FLAG_JF); + const bool d2 = bm_vert_is_manifold_flagged(l_iter->e->v2, _FLAG_JF); if (!d1 && !d2 && !BM_ELEM_API_FLAG_TEST(l_iter->e, _FLAG_JF)) { /* don't remove an edge it makes up the side of another face @@ -1332,7 +1282,8 @@ BMFace *BM_faces_join(BMesh *bm, BMFace **faces, int totface, const bool do_del) } /* create region face */ - f_new = tote ? BM_face_create_ngon(bm, v1, v2, edges, tote, faces[0], BM_CREATE_NOP) : NULL; + f_new = BLI_array_count(edges) ? + BM_face_create_ngon(bm, v1, v2, edges, BLI_array_count(edges), faces[0], BM_CREATE_NOP) : NULL; if (UNLIKELY(f_new == NULL)) { /* Invalid boundary region to join faces */ goto error; @@ -1350,10 +1301,11 @@ BMFace *BM_faces_join(BMesh *bm, BMFace **faces, int totface, const bool do_del) } while (l2 != l_iter); if (l2 != l_iter) { - /* I think this is correct? */ + /* loops share an edge, shared vert depends on winding */ if (l2->v != l_iter->v) { l2 = l2->next; } + BLI_assert(l_iter->v == l2->v); BM_elem_attrs_copy(bm, bm, l2, l_iter); } @@ -1362,22 +1314,15 @@ BMFace *BM_faces_join(BMesh *bm, BMFace **faces, int totface, const bool do_del) #ifdef USE_BMESH_HOLES /* add holes */ BLI_movelisttolist(&f_new->loops, &holes); -#endif /* update loop face pointer */ -#ifdef USE_BMESH_HOLES - for (lst = f_new->loops.first; lst; lst = lst->next) -#endif - { -#ifdef USE_BMESH_HOLES + for (lst = f_new->loops.first; lst; lst = lst->next) { l_iter = l_first = lst->first; -#else - l_iter = l_first = BM_FACE_FIRST_LOOP(f_new); -#endif do { l_iter->f = f_new; } while ((l_iter = l_iter->next) != l_first); } +#endif bm_elements_systag_disable(faces, totface, _FLAG_JF); BM_ELEM_API_FLAG_DISABLE(f_new, _FLAG_JF); @@ -1585,8 +1530,8 @@ BMFace *bmesh_sfme( } while ((l_iter = l_iter->next) != l_first); /* link up the new loops into the new edges radial */ - bmesh_radial_append(e, l_f1); - bmesh_radial_append(e, l_f2); + bmesh_radial_loop_append(e, l_f1); + bmesh_radial_loop_append(e, l_f2); f2->len = f2len; @@ -1693,18 +1638,18 @@ BMVert *bmesh_semv(BMesh *bm, BMVert *tv, BMEdge *e, BMEdge **r_e) #ifndef NDEBUG int radlen = bmesh_radial_length(l_next); #endif - int first1 = 0, first2 = 0; + bool is_first = true; /* Take the next loop. Remove it from radial. Split it. Append to appropriate radials */ while (l_next) { l = l_next; l->f->len++; l_next = l_next != l_next->radial_next ? l_next->radial_next : NULL; - bmesh_radial_loop_remove(l, NULL); + bmesh_radial_loop_unlink(l); l_new = bm_loop_create(bm, NULL, NULL, l->f, l, 0); l_new->prev = l; - l_new->next = (l->next); + l_new->next = l->next; l_new->prev->next = l_new; l_new->next->prev = l_new; l_new->v = v_new; @@ -1715,36 +1660,26 @@ BMVert *bmesh_semv(BMesh *bm, BMVert *tv, BMEdge *e, BMEdge **r_e) l->e = e_new; /* append l into e_new's rad cycle */ - if (!first1) { - first1 = 1; - l->radial_next = l->radial_prev = NULL; - } - - if (!first2) { - first2 = 1; + if (is_first) { + is_first = false; l->radial_next = l->radial_prev = NULL; } - bmesh_radial_append(l_new->e, l_new); - bmesh_radial_append(l->e, l); + bmesh_radial_loop_append(l_new->e, l_new); + bmesh_radial_loop_append(l->e, l); } else if (BM_verts_in_edge(l_new->v, l_new->next->v, e_new)) { l_new->e = e_new; l->e = e; /* append l into e_new's rad cycle */ - if (!first1) { - first1 = 1; + if (is_first) { + is_first = false; l->radial_next = l->radial_prev = NULL; } - if (!first2) { - first2 = 1; - l->radial_next = l->radial_prev = NULL; - } - - bmesh_radial_append(l_new->e, l_new); - bmesh_radial_append(l->e, l); + bmesh_radial_loop_append(l_new->e, l_new); + bmesh_radial_loop_append(l->e, l); } } @@ -1839,7 +1774,6 @@ BMEdge *bmesh_jekv( BMEdge *e_old; BMVert *v_old, *v_target; BMLoop *l_kill; - bool halt = false; #ifndef NDEBUG int radlen, i; bool edok; @@ -1860,9 +1794,9 @@ BMEdge *bmesh_jekv( e_old = bmesh_disk_edge_next(e_kill, v_kill); v_target = BM_edge_other_vert(e_kill, v_kill); v_old = BM_edge_other_vert(e_old, v_kill); - halt = BM_verts_in_edge(v_kill, v_target, e_old); /* check for double edges */ - - if (halt) { + + /* check for double edges */ + if (BM_verts_in_edge(v_kill, v_target, e_old)) { return NULL; } else { @@ -2413,7 +2347,7 @@ void bmesh_vert_separate( v_new = BM_vert_create(bm, v->co, v, BM_CREATE_NOP); if (copy_select) { - BM_elem_select_copy(bm, bm, v_new, v); + BM_elem_select_copy(bm, v_new, v); } while ((e = BLI_SMALLSTACK_POP(edges))) { @@ -2471,18 +2405,13 @@ static void bmesh_vert_separate__cleanup(BMesh *bm, LinkNode *edges_separate) do { BMEdge *e_orig = n_orig->link; LinkNode *n_step = n_orig->next; - LinkNode *n_prev = n_orig; do { BMEdge *e = n_step->link; BLI_assert(e != e_orig); if ((e->v1 == e_orig->v1) && (e->v2 == e_orig->v2)) { BM_edge_splice(bm, e_orig, e); - n_prev->next = n_step->next; - n_step = n_prev; } - } while ((void) - (n_prev = n_step), - (n_step = n_step->next)); + } while ((n_step = n_step->next)); } while ((n_orig = n_orig->next) && n_orig->next); } while ((edges_separate = edges_separate->next)); @@ -2619,8 +2548,8 @@ bool BM_edge_splice(BMesh *bm, BMEdge *e_dst, BMEdge *e_src) l = e_src->l; BLI_assert(BM_vert_in_edge(e_dst, l->v)); BLI_assert(BM_vert_in_edge(e_dst, l->next->v)); - bmesh_radial_loop_remove(l, e_src); - bmesh_radial_append(e_dst, l); + bmesh_radial_loop_remove(e_src, l); + bmesh_radial_loop_append(e_dst, l); } BLI_assert(bmesh_radial_length(e_src->l) == 0); @@ -2667,12 +2596,12 @@ void bmesh_edge_separate( } e_new = BM_edge_create(bm, e->v1, e->v2, e, BM_CREATE_NOP); - bmesh_radial_loop_remove(l_sep, e); - bmesh_radial_append(e_new, l_sep); + bmesh_radial_loop_remove(e, l_sep); + bmesh_radial_loop_append(e_new, l_sep); l_sep->e = e_new; if (copy_select) { - BM_elem_select_copy(bm, bm, e_new, e); + BM_elem_select_copy(bm, e_new, e); } BLI_assert(bmesh_radial_length(e->l) == radlen - 1); @@ -2855,8 +2784,8 @@ BMVert *bmesh_urmv_loop_multi( do { l_next = l_iter->radial_next; if (BM_ELEM_API_FLAG_TEST(l_iter, LOOP_VISIT)) { - bmesh_radial_loop_remove(l_iter, e); - bmesh_radial_append(e_new, l_iter); + bmesh_radial_loop_remove(e, l_iter); + bmesh_radial_loop_append(e_new, l_iter); l_iter->e = e_new; } } while ((l_iter = l_next) != l_first); diff --git a/source/blender/bmesh/intern/bmesh_core.h b/source/blender/bmesh/intern/bmesh_core.h index fb5702bc574..f72e9d7b198 100644 --- a/source/blender/bmesh/intern/bmesh_core.h +++ b/source/blender/bmesh/intern/bmesh_core.h @@ -75,7 +75,7 @@ void bmesh_vert_separate( BMesh *bm, BMVert *v, BMVert ***r_vout, int *r_vout_len, const bool copy_select); -bool bmesh_loop_reverse( +void bmesh_loop_reverse( BMesh *bm, BMFace *f, const int cd_loop_mdisp_offset, const bool use_loop_mdisp_flip); diff --git a/source/blender/bmesh/intern/bmesh_iterators.c b/source/blender/bmesh/intern/bmesh_iterators.c index 961b10d848a..96154f051f9 100644 --- a/source/blender/bmesh/intern/bmesh_iterators.c +++ b/source/blender/bmesh/intern/bmesh_iterators.c @@ -485,9 +485,9 @@ void bmiter__face_of_vert_begin(struct BMIter__face_of_vert *iter) { ((BMIter *)iter)->count = bmesh_disk_facevert_count(iter->vdata); if (((BMIter *)iter)->count) { - iter->e_first = bmesh_disk_faceedge_find_first(iter->vdata->e, iter->vdata); + iter->l_first = bmesh_disk_faceloop_find_first(iter->vdata->e, iter->vdata); + iter->e_first = iter->l_first->e; iter->e_next = iter->e_first; - iter->l_first = bmesh_radial_faceloop_find_first(iter->e_first->l, iter->vdata); iter->l_next = iter->l_first; } else { @@ -526,9 +526,9 @@ void bmiter__loop_of_vert_begin(struct BMIter__loop_of_vert *iter) { ((BMIter *)iter)->count = bmesh_disk_facevert_count(iter->vdata); if (((BMIter *)iter)->count) { - iter->e_first = bmesh_disk_faceedge_find_first(iter->vdata->e, iter->vdata); + iter->l_first = bmesh_disk_faceloop_find_first(iter->vdata->e, iter->vdata); + iter->e_first = iter->l_first->e; iter->e_next = iter->e_first; - iter->l_first = bmesh_radial_faceloop_find_first(iter->e_first->l, iter->vdata); iter->l_next = iter->l_first; } else { diff --git a/source/blender/bmesh/intern/bmesh_mods.c b/source/blender/bmesh/intern/bmesh_mods.c index d3c847de64e..03165beb329 100644 --- a/source/blender/bmesh/intern/bmesh_mods.c +++ b/source/blender/bmesh/intern/bmesh_mods.c @@ -104,7 +104,6 @@ bool BM_vert_dissolve(BMesh *bm, BMVert *v) */ bool BM_disk_dissolve(BMesh *bm, BMVert *v) { - BMFace *f, *f2; BMEdge *e, *keepedge = NULL, *baseedge = NULL; int len = 0; @@ -132,16 +131,17 @@ bool BM_disk_dissolve(BMesh *bm, BMVert *v) #if 0 /* handle specific case for three-valence. solve it by * increasing valence to four. this may be hackish. . */ - BMLoop *loop = e->l; - if (loop->v == v) loop = loop->next; - if (!BM_face_split(bm, loop->f, v, loop->v, NULL, NULL, false)) + BMLoop *l_a = BM_face_vert_share_loop(e->l->f, v); + BMLoop *l_b = (e->l->v == v) ? e->l->next : e->l; + + if (!BM_face_split(bm, e->l->f, l_a, l_b, NULL, NULL, false)) return false; if (!BM_disk_dissolve(bm, v)) { return false; } #else - if (UNLIKELY(!BM_faces_join_pair(bm, e->l->f, e->l->radial_next->f, e, true))) { + if (UNLIKELY(!BM_faces_join_pair(bm, e->l, e->l->radial_next, true))) { return false; } else if (UNLIKELY(!BM_vert_collapse_faces(bm, v->e, v, 1.0, true, false, true))) { @@ -159,11 +159,10 @@ bool BM_disk_dissolve(BMesh *bm, BMVert *v) } /* handle two-valence */ - f = e->l->f; - f2 = e->l->radial_next->f; - - if (f != f2 && !BM_faces_join_pair(bm, f, f2, e, true)) { - return false; + if (e->l != e->l->radial_next) { + if (!BM_faces_join_pair(bm, e->l, e->l->radial_next, true)) { + return false; + } } return true; @@ -176,9 +175,9 @@ bool BM_disk_dissolve(BMesh *bm, BMVert *v) done = true; e = v->e; do { - f = NULL; + BMFace *f = NULL; if (BM_edge_is_manifold(e) && (e != baseedge) && (e != keepedge)) { - f = BM_faces_join_pair(bm, e->l->f, e->l->radial_next->f, e, true); + f = BM_faces_join_pair(bm, e->l, e->l->radial_next, true); /* return if couldn't join faces in manifold * conditions */ /* !disabled for testing why bad things happen */ @@ -204,12 +203,9 @@ bool BM_disk_dissolve(BMesh *bm, BMVert *v) if (e->l) { /* get remaining two faces */ - f = e->l->f; - f2 = e->l->radial_next->f; - - if (f != f2) { + if (e->l != e->l->radial_next) { /* join two remaining faces */ - if (!BM_faces_join_pair(bm, f, f2, e, true)) { + if (!BM_faces_join_pair(bm, e->l, e->l->radial_next, true)) { return false; } } @@ -224,30 +220,24 @@ bool BM_disk_dissolve(BMesh *bm, BMVert *v) * * Joins two adjacent faces together. * - * Because this method calls to #BM_faces_join to do its work, if a pair - * of faces share multiple edges, the pair of faces will be joined at - * every edge (not just edge \a e). This part of the functionality might need - * to be reconsidered. + * \note This method calls to #BM_faces_join to do its work. + * This means connected edges which also share the two faces will be joined. * * If the windings do not match the winding of the new face will follow - * \a f_a's winding (i.e. \a f_b will be reversed before the join). + * \a l_a's winding (i.e. \a l_b will be reversed before the join). * - * \return pointer to the combined face + * \return The combined face or NULL on failure. */ -BMFace *BM_faces_join_pair(BMesh *bm, BMFace *f_a, BMFace *f_b, BMEdge *e, const bool do_del) +BMFace *BM_faces_join_pair(BMesh *bm, BMLoop *l_a, BMLoop *l_b, const bool do_del) { - BMFace *faces[2] = {f_a, f_b}; - - BMLoop *l_a = BM_face_edge_share_loop(f_a, e); - BMLoop *l_b = BM_face_edge_share_loop(f_b, e); - - BLI_assert(l_a && l_b); + BLI_assert((l_a != l_b) && (l_a->e == l_b->e)); if (l_a->v == l_b->v) { const int cd_loop_mdisp_offset = CustomData_get_offset(&bm->ldata, CD_MDISPS); - bmesh_loop_reverse(bm, f_b, cd_loop_mdisp_offset, true); + bmesh_loop_reverse(bm, l_b->f, cd_loop_mdisp_offset, true); } - + + BMFace *faces[2] = {l_a->f, l_b->f}; return BM_faces_join(bm, faces, 2, do_del); } @@ -551,7 +541,7 @@ BMEdge *BM_vert_collapse_edge( BMVert *tv2 = BM_edge_other_vert(e2, v_kill); if (tv2) { /* only action, other calls here only get the edge to return */ - e_new = bmesh_jekv(bm, e_kill, v_kill, do_del); + e_new = bmesh_jekv(bm, e_kill, v_kill, do_del, true, kill_degenerate_faces); } } } @@ -598,18 +588,13 @@ BMVert *BM_edge_collapse( BMVert *BM_edge_split(BMesh *bm, BMEdge *e, BMVert *v, BMEdge **r_e, float fac) { BMVert *v_new, *v_other; + BMEdge *e_new; BMFace **oldfaces = NULL; - BMEdge *e_dummy; BLI_array_staticdeclare(oldfaces, 32); const int cd_loop_mdisp_offset = BM_edge_is_wire(e) ? -1 : CustomData_get_offset(&bm->ldata, CD_MDISPS); BLI_assert(BM_vert_in_edge(e, v) == true); - /* we need this for handling multi-res */ - if (!r_e) { - r_e = &e_dummy; - } - /* do we have a multi-res layer? */ if (cd_loop_mdisp_offset != -1) { BMLoop *l; @@ -630,17 +615,20 @@ BMVert *BM_edge_split(BMesh *bm, BMEdge *e, BMVert *v, BMEdge **r_e, float fac) } v_other = BM_edge_other_vert(e, v); - v_new = bmesh_semv(bm, v, e, r_e); + v_new = bmesh_semv(bm, v, e, &e_new); + if (r_e != NULL) { + *r_e = e_new; + } BLI_assert(v_new != NULL); - BLI_assert(BM_vert_in_edge(*r_e, v) && BM_vert_in_edge(*r_e, v_new)); + BLI_assert(BM_vert_in_edge(e_new, v) && BM_vert_in_edge(e_new, v_new)); BLI_assert(BM_vert_in_edge(e, v_new) && BM_vert_in_edge(e, v_other)); sub_v3_v3v3(v_new->co, v_other->co, v->co); madd_v3_v3v3fl(v_new->co, v->co, v_new->co, fac); - (*r_e)->head.hflag = e->head.hflag; - BM_elem_attrs_copy(bm, bm, e, *r_e); + e_new->head.hflag = e->head.hflag; + BM_elem_attrs_copy(bm, bm, e, e_new); /* v->v_new->v2 */ BM_data_interp_face_vert_edge(bm, v_other, v, v_new, e, fac); @@ -656,7 +644,7 @@ BMVert *BM_edge_split(BMesh *bm, BMEdge *e, BMVert *v, BMEdge **r_e, float fac) BM_face_calc_center_mean(oldfaces[i], f_center_old); for (j = 0; j < 2; j++) { - BMEdge *e1 = j ? *r_e : e; + BMEdge *e1 = j ? e_new : e; BMLoop *l; l = e1->l; @@ -689,7 +677,7 @@ BMVert *BM_edge_split(BMesh *bm, BMEdge *e, BMVert *v, BMEdge **r_e, float fac) /* fix boundaries a bit, doesn't work too well quite yet */ #if 0 for (j = 0; j < 2; j++) { - BMEdge *e1 = j ? *r_e : e; + BMEdge *e1 = j ? e_new : e; BMLoop *l, *l2; l = e1->l; @@ -991,6 +979,7 @@ BMEdge *BM_edge_rotate(BMesh *bm, BMEdge *e, const bool ccw, const short check_f BMLoop *l1, *l2; BMFace *f; BMEdge *e_new = NULL; + char f_active_prev = 0; char f_hflag_prev_1; char f_hflag_prev_2; @@ -1041,8 +1030,18 @@ BMEdge *BM_edge_rotate(BMesh *bm, BMEdge *e, const bool ccw, const short check_f f_hflag_prev_1 = l1->f->head.hflag; f_hflag_prev_2 = l2->f->head.hflag; + /* maintain active face */ + if (bm->act_face == l1->f) { + f_active_prev = 1; + } + else if (bm->act_face == l2->f) { + f_active_prev = 2; + } + + const bool is_flipped = !BM_edge_is_contiguous(e); + /* don't delete the edge, manually remove the edge after so we can copy its attributes */ - f = BM_faces_join_pair(bm, l1->f, l2->f, e, true); + f = BM_faces_join_pair(bm, BM_face_edge_share_loop(l1->f, e), BM_face_edge_share_loop(l2->f, e), true); if (f == NULL) { return NULL; @@ -1062,6 +1061,22 @@ BMEdge *BM_edge_rotate(BMesh *bm, BMEdge *e, const bool ccw, const short check_f if (BM_edge_face_pair(e_new, &fa, &fb)) { fa->head.hflag = f_hflag_prev_1; fb->head.hflag = f_hflag_prev_2; + + if (f_active_prev == 1) { + bm->act_face = fa; + } + else if (f_active_prev == 2) { + bm->act_face = fb; + } + + if (is_flipped) { + BM_face_normal_flip(bm, fb); + + if (ccw) { + /* needed otherwise ccw toggles direction */ + e_new->l = e_new->l->radial_next; + } + } } } else { diff --git a/source/blender/bmesh/intern/bmesh_mods.h b/source/blender/bmesh/intern/bmesh_mods.h index 2e557e3b606..5e95e9a2cc7 100644 --- a/source/blender/bmesh/intern/bmesh_mods.h +++ b/source/blender/bmesh/intern/bmesh_mods.h @@ -31,7 +31,7 @@ bool BM_vert_dissolve(BMesh *bm, BMVert *v); bool BM_disk_dissolve(BMesh *bm, BMVert *v); -BMFace *BM_faces_join_pair(BMesh *bm, BMFace *f1, BMFace *f2, BMEdge *e, const bool do_del); +BMFace *BM_faces_join_pair(BMesh *bm, BMLoop *l_a, BMLoop *l_b, const bool do_del); /** see: bmesh_polygon_edgenet.h for #BM_face_split_edgenet */ diff --git a/source/blender/bmesh/intern/bmesh_queries.c b/source/blender/bmesh/intern/bmesh_queries.c index b5f9575aff5..7ca5640578a 100644 --- a/source/blender/bmesh/intern/bmesh_queries.c +++ b/source/blender/bmesh/intern/bmesh_queries.c @@ -102,17 +102,10 @@ BMLoop *BM_loop_other_edge_loop(BMLoop *l, BMVert *v) */ BMLoop *BM_face_other_vert_loop(BMFace *f, BMVert *v_prev, BMVert *v) { - BMIter liter; - BMLoop *l_iter; + BMLoop *l_iter = BM_face_vert_share_loop(f, v); BLI_assert(BM_edge_exists(v_prev, v) != NULL); - BM_ITER_ELEM (l_iter, &liter, v, BM_LOOPS_OF_VERT) { - if (l_iter->f == f) { - break; - } - } - if (l_iter) { if (l_iter->prev->v == v_prev) { return l_iter->next; @@ -149,7 +142,6 @@ BMLoop *BM_face_other_vert_loop(BMFace *f, BMVert *v_prev, BMVert *v) * The faces loop direction is ignored. * </pre> */ - BMLoop *BM_loop_other_vert_loop(BMLoop *l, BMVert *v) { #if 0 /* works but slow */ @@ -178,9 +170,6 @@ BMLoop *BM_loop_other_vert_loop(BMLoop *l, BMVert *v) return l->next->next; } } - - - #endif } @@ -392,17 +381,7 @@ BMFace *BM_vert_pair_share_face_by_angle( */ BMLoop *BM_vert_find_first_loop(BMVert *v) { - BMEdge *e; - - if (!v->e) - return NULL; - - e = bmesh_disk_faceedge_find_first(v->e, v); - - if (!e) - return NULL; - - return bmesh_radial_faceloop_find_first(e->l, v); + return v->e ? bmesh_disk_faceloop_find_first(v->e, v) : NULL; } /** @@ -878,9 +857,18 @@ int BM_vert_face_count_ex(const BMVert *v, int count_max) * * same as ``BM_vert_face_count(v) != 0`` or ``BM_vert_find_first_loop(v) == NULL`` */ -bool BM_vert_face_check(BMVert *v) +bool BM_vert_face_check(const BMVert *v) { - return v->e && (bmesh_disk_faceedge_find_first(v->e, v) != NULL); + if (v->e != NULL) { + const BMEdge *e_iter, *e_first; + e_first = e_iter = v->e; + do { + if (e_iter->l != NULL) { + return true; + } + } while ((e_iter = bmesh_disk_edge_next(e_iter, v)) != e_first); + } + return false; } /** @@ -910,7 +898,7 @@ bool BM_vert_is_wire(const BMVert *v) * A vertex is non-manifold if it meets the following conditions: * 1: Loose - (has no edges/faces incident upon it). * 2: Joins two distinct regions - (two pyramids joined at the tip). - * 3: Is part of a an edge with more than 2 faces. + * 3: Is part of an edge with more than 2 faces. * 4: Is part of a wire edge. */ bool BM_vert_is_manifold(const BMVert *v) @@ -926,7 +914,8 @@ bool BM_vert_is_manifold(const BMVert *v) /* count edges while looking for non-manifold edges */ e_first = e_iter = v->e; - l_first = e_iter->l ? e_iter->l : NULL; + /* may be null */ + l_first = e_iter->l; do { /* loose edge or edge shared by more than two faces, * edges with 1 face user are OK, otherwise we could @@ -1924,7 +1913,7 @@ BMEdge *BM_edge_find_double(BMEdge *e) * * \note there used to be a BM_face_exists_overlap function that checks for partial overlap. */ -bool BM_face_exists(BMVert **varr, int len, BMFace **r_existface) +BMFace *BM_face_exists(BMVert **varr, int len) { if (varr[0]->e) { BMEdge *e_iter, *e_first; @@ -1963,10 +1952,7 @@ bool BM_face_exists(BMVert **varr, int len, BMFace **r_existface) } if (i_walk == len) { - if (r_existface) { - *r_existface = l_iter_radial->f; - } - return true; + return l_iter_radial->f; } } } while ((l_iter_radial = l_iter_radial->radial_next) != l_first_radial); @@ -1975,10 +1961,7 @@ bool BM_face_exists(BMVert **varr, int len, BMFace **r_existface) } while ((e_iter = BM_DISK_EDGE_NEXT(e_iter, varr[0])) != e_first); } - if (r_existface) { - *r_existface = NULL; - } - return false; + return NULL; } @@ -2121,26 +2104,21 @@ bool BM_face_exists_multi_edge(BMEdge **earr, int len) * \note The face may contain other verts \b not in \a varr. * * \note Its possible there are more than one overlapping faces, - * in this case the first one found will be assigned to \a r_f_overlap. + * in this case the first one found will be returned. * * \param varr Array of unordered verts. * \param len \a varr array length. - * \param r_f_overlap The overlapping face to return. - * \return Success + * \return The face or NULL. */ -bool BM_face_exists_overlap(BMVert **varr, const int len, BMFace **r_f_overlap) +BMFace *BM_face_exists_overlap(BMVert **varr, const int len) { BMIter viter; BMFace *f; int i; - bool is_overlap = false; + BMFace *f_overlap = NULL; LinkNode *f_lnk = NULL; - if (r_f_overlap) { - *r_f_overlap = NULL; - } - #ifdef DEBUG /* check flag isn't already set */ for (i = 0; i < len; i++) { @@ -2154,10 +2132,7 @@ bool BM_face_exists_overlap(BMVert **varr, const int len, BMFace **r_f_overlap) BM_ITER_ELEM (f, &viter, varr[i], BM_FACES_OF_VERT) { if (BM_ELEM_API_FLAG_TEST(f, _FLAG_OVERLAP) == 0) { if (len <= BM_verts_in_face_count(varr, len, f)) { - if (r_f_overlap) - *r_f_overlap = f; - - is_overlap = true; + f_overlap = f; break; } @@ -2171,7 +2146,7 @@ bool BM_face_exists_overlap(BMVert **varr, const int len, BMFace **r_f_overlap) BM_ELEM_API_FLAG_DISABLE((BMFace *)f_lnk->link, _FLAG_OVERLAP); } - return is_overlap; + return f_overlap; } /** diff --git a/source/blender/bmesh/intern/bmesh_queries.h b/source/blender/bmesh/intern/bmesh_queries.h index 10e4b9a15aa..a6a37767ee9 100644 --- a/source/blender/bmesh/intern/bmesh_queries.h +++ b/source/blender/bmesh/intern/bmesh_queries.h @@ -86,7 +86,7 @@ BMEdge *BM_vert_other_disk_edge(BMVert *v, BMEdge *e) ATTR_WARN_UNUSED_RESULT AT bool BM_vert_is_edge_pair(const BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); bool BM_vert_edge_pair(BMVert *v, BMEdge **r_e_a, BMEdge **r_e_b); -bool BM_vert_face_check(BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); +bool BM_vert_face_check(const BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); bool BM_vert_is_wire(const BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); BLI_INLINE bool BM_edge_is_wire(const BMEdge *e) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); @@ -135,12 +135,12 @@ BMLoop *BM_face_find_longest_loop(BMFace *f) ATTR_WARN_UNUSED_RESULT ATTR_NONNUL BMEdge *BM_edge_exists(BMVert *v1, BMVert *v2) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); BMEdge *BM_edge_find_double(BMEdge *e) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); -bool BM_face_exists(BMVert **varr, int len, BMFace **r_existface) ATTR_NONNULL(1); +BMFace* BM_face_exists(BMVert **varr, int len) ATTR_NONNULL(1); bool BM_face_exists_multi(BMVert **varr, BMEdge **earr, int len) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); bool BM_face_exists_multi_edge(BMEdge **earr, int len) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); -bool BM_face_exists_overlap(BMVert **varr, const int len, BMFace **r_f_overlap) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1); +BMFace *BM_face_exists_overlap(BMVert **varr, const int len) ATTR_WARN_UNUSED_RESULT; bool BM_face_exists_overlap_subset(BMVert **varr, const int len) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); int BM_face_share_face_count(BMFace *f_a, BMFace *f_b) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); diff --git a/source/blender/bmesh/intern/bmesh_structure.c b/source/blender/bmesh/intern/bmesh_structure.c index cb302139a4c..8e484841568 100644 --- a/source/blender/bmesh/intern/bmesh_structure.c +++ b/source/blender/bmesh/intern/bmesh_structure.c @@ -143,7 +143,7 @@ void bmesh_disk_vert_replace(BMEdge *e, BMVert *v_dst, BMVert *v_src) * to store non-manifold conditions since BM does not keep track of region/shell information. * * Functions relating to this cycle: - * - #bmesh_radial_append + * - #bmesh_radial_loop_append * - #bmesh_radial_loop_remove * - #bmesh_radial_facevert_count * - #bmesh_radial_facevert_check @@ -264,10 +264,12 @@ bool bmesh_disk_validate(int len, BMEdge *e, BMVert *v) { BMEdge *e_iter; - if (!BM_vert_in_edge(e, v)) + if (!BM_vert_in_edge(e, v)) { return false; - if (bmesh_disk_count_ex(v, len + 1) != len || len == 0) + } + if (len == 0 || bmesh_disk_count_ex(v, len + 1) != len) { return false; + } e_iter = e; do { @@ -336,13 +338,28 @@ int bmesh_disk_facevert_count_ex(const BMVert *v, const int count_max) */ BMEdge *bmesh_disk_faceedge_find_first(const BMEdge *e, const BMVert *v) { - const BMEdge *e_find = e; + const BMEdge *e_iter = e; do { - if (e_find->l && bmesh_radial_facevert_check(e_find->l, v)) { - return (BMEdge *)e_find; + if (e_iter->l != NULL) { + return (BMEdge *)((e_iter->l->v == v) ? e_iter : e_iter->l->next->e); } - } while ((e_find = bmesh_disk_edge_next(e_find, v)) != e); + } while ((e_iter = bmesh_disk_edge_next(e_iter, v)) != e); + return NULL; +} +/** + * Special case for BM_LOOPS_OF_VERT & BM_FACES_OF_VERT, avoids 2x calls. + * + * The returned BMLoop.e matches the result of #bmesh_disk_faceedge_find_first + */ +BMLoop *bmesh_disk_faceloop_find_first(const BMEdge *e, const BMVert *v) +{ + const BMEdge *e_iter = e; + do { + if (e_iter->l != NULL) { + return (e_iter->l->v == v) ? e_iter->l : e_iter->l->next; + } + } while ((e_iter = bmesh_disk_edge_next(e_iter, v)) != e); return NULL; } @@ -389,6 +406,30 @@ bool bmesh_radial_validate(int radlen, BMLoop *l) return true; } +void bmesh_radial_loop_append(BMEdge *e, BMLoop *l) +{ + if (e->l == NULL) { + e->l = l; + l->radial_next = l->radial_prev = l; + } + else { + l->radial_prev = e->l; + l->radial_next = e->l->radial_next; + + e->l->radial_next->radial_prev = l; + e->l->radial_next = l; + + e->l = l; + } + + if (UNLIKELY(l->e && l->e != e)) { + /* l is already in a radial cycle for a different edge */ + BMESH_ASSERT(0); + } + + l->e = e; +} + /** * \brief BMESH RADIAL REMOVE LOOP * @@ -397,28 +438,27 @@ bool bmesh_radial_validate(int radlen, BMLoop *l) * updated (in the case that the edge's link into the radial * cycle was the loop which is being removed from the cycle). */ -void bmesh_radial_loop_remove(BMLoop *l, BMEdge *e) +void bmesh_radial_loop_remove(BMEdge *e, BMLoop *l) { /* if e is non-NULL, l must be in the radial cycle of e */ - if (UNLIKELY(e && e != l->e)) { + if (UNLIKELY(e != l->e)) { BMESH_ASSERT(0); } if (l->radial_next != l) { - if (e && l == e->l) + if (l == e->l) { e->l = l->radial_next; + } l->radial_next->radial_prev = l->radial_prev; l->radial_prev->radial_next = l->radial_next; } else { - if (e) { - if (l == e->l) { - e->l = NULL; - } - else { - BMESH_ASSERT(0); - } + if (l == e->l) { + e->l = NULL; + } + else { + BMESH_ASSERT(0); } } @@ -428,6 +468,22 @@ void bmesh_radial_loop_remove(BMLoop *l, BMEdge *e) l->e = NULL; } +/** + * A version of #bmesh_radial_loop_remove which only performs the radial unlink, + * leaving the edge untouched. + */ +void bmesh_radial_loop_unlink(BMLoop *l) +{ + if (l->radial_next != l) { + l->radial_next->radial_prev = l->radial_prev; + l->radial_prev->radial_next = l->radial_next; + } + + /* l is no longer in a radial cycle; empty the links + * to the cycle and the link back to an edge */ + l->radial_next = l->radial_prev = NULL; + l->e = NULL; +} /** * \brief BME RADIAL FIND FIRST FACE VERT @@ -484,30 +540,6 @@ int bmesh_radial_length(const BMLoop *l) return i; } -void bmesh_radial_append(BMEdge *e, BMLoop *l) -{ - if (e->l == NULL) { - e->l = l; - l->radial_next = l->radial_prev = l; - } - else { - l->radial_prev = e->l; - l->radial_next = e->l->radial_next; - - e->l->radial_next->radial_prev = l; - e->l->radial_next = l; - - e->l = l; - } - - if (UNLIKELY(l->e && l->e != e)) { - /* l is already in a radial cycle for a different edge */ - BMESH_ASSERT(0); - } - - l->e = e; -} - /** * \brief RADIAL COUNT FACE VERT * diff --git a/source/blender/bmesh/intern/bmesh_structure.h b/source/blender/bmesh/intern/bmesh_structure.h index 07f94796bb2..0efb25da37c 100644 --- a/source/blender/bmesh/intern/bmesh_structure.h +++ b/source/blender/bmesh/intern/bmesh_structure.h @@ -52,11 +52,13 @@ BLI_INLINE BMEdge *bmesh_disk_edge_prev(const BMEdge *e, const BMVert *v) ATTR_W int bmesh_disk_facevert_count_ex(const BMVert *v, const int count_max) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); int bmesh_disk_facevert_count(const BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); BMEdge *bmesh_disk_faceedge_find_first(const BMEdge *e, const BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); +BMLoop *bmesh_disk_faceloop_find_first(const BMEdge *e, const BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); BMEdge *bmesh_disk_faceedge_find_next(const BMEdge *e, const BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); /* RADIAL CYCLE MANAGMENT */ -void bmesh_radial_append(BMEdge *e, BMLoop *l) ATTR_NONNULL(); -void bmesh_radial_loop_remove(BMLoop *l, BMEdge *e) ATTR_NONNULL(1); +void bmesh_radial_loop_append(BMEdge *e, BMLoop *l) ATTR_NONNULL(); +void bmesh_radial_loop_remove(BMEdge *e, BMLoop *l) ATTR_NONNULL(); +void bmesh_radial_loop_unlink(BMLoop *l) ATTR_NONNULL(); /* note: * bmesh_radial_loop_next(BMLoop *l) / prev. * just use member access l->radial_next, l->radial_prev now */ diff --git a/source/blender/bmesh/operators/bmo_bridge.c b/source/blender/bmesh/operators/bmo_bridge.c index 6ef0fd6b084..61179d7be70 100644 --- a/source/blender/bmesh/operators/bmo_bridge.c +++ b/source/blender/bmesh/operators/bmo_bridge.c @@ -398,7 +398,8 @@ static void bridge_loop_pair( if (v_b != v_b_next) { BMVert *v_arr[4] = {v_a, v_b, v_b_next, v_a_next}; - if (BM_face_exists(v_arr, 4, &f) == false) { + f = BM_face_exists(v_arr, 4); + if (f == NULL) { /* copy if loop data if its is missing on one ring */ f = BM_face_create_verts(bm, v_arr, 4, NULL, BM_CREATE_NOP, true); @@ -411,7 +412,8 @@ static void bridge_loop_pair( } else { BMVert *v_arr[3] = {v_a, v_b, v_a_next}; - if (BM_face_exists(v_arr, 3, &f) == false) { + f = BM_face_exists(v_arr, 3); + if (f == NULL) { /* fan-fill a triangle */ f = BM_face_create_verts(bm, v_arr, 3, NULL, BM_CREATE_NOP, true); diff --git a/source/blender/bmesh/operators/bmo_dissolve.c b/source/blender/bmesh/operators/bmo_dissolve.c index 05efb14a699..6e3a8a1473d 100644 --- a/source/blender/bmesh/operators/bmo_dissolve.c +++ b/source/blender/bmesh/operators/bmo_dissolve.c @@ -322,12 +322,12 @@ void bmo_dissolve_edges_exec(BMesh *bm, BMOperator *op) } BMO_ITER (e, &eiter, op->slots_in, "edges", BM_EDGE) { - BMFace *fa, *fb; - if (BM_edge_face_pair(e, &fa, &fb)) { + BMLoop *l_a, *l_b; + if (BM_edge_loop_pair(e, &l_a, &l_b)) { BMFace *f_new; /* join faces */ - f_new = BM_faces_join_pair(bm, fa, fb, e, false); + f_new = BM_faces_join_pair(bm, l_a, l_b, false); if (f_new) { /* maintain active face */ @@ -437,12 +437,12 @@ void bmo_dissolve_verts_exec(BMesh *bm, BMOperator *op) if (!BMO_vert_flag_test(bm, v, VERT_MARK_PAIR)) { BM_ITER_ELEM (e, &itersub, v, BM_EDGES_OF_VERT) { - BMFace *fa, *fb; - if (BM_edge_face_pair(e, &fa, &fb)) { + BMLoop *l_a, *l_b; + if (BM_edge_loop_pair(e, &l_a, &l_b)) { BMFace *f_new; /* join faces */ - f_new = BM_faces_join_pair(bm, fa, fb, e, false); + f_new = BM_faces_join_pair(bm, l_a, l_b, false); /* maintain active face */ if (act_face && bm->act_face == NULL) { diff --git a/source/blender/bmesh/operators/bmo_fill_edgeloop.c b/source/blender/bmesh/operators/bmo_fill_edgeloop.c index c68130bc11d..f33a60ccc5c 100644 --- a/source/blender/bmesh/operators/bmo_fill_edgeloop.c +++ b/source/blender/bmesh/operators/bmo_fill_edgeloop.c @@ -136,7 +136,7 @@ void bmo_edgeloop_fill_exec(BMesh *bm, BMOperator *op) i++; } while ((v != f_verts[0])); - if (BM_face_exists(f_verts, i, NULL) == false) { + if (!BM_face_exists(f_verts, i)) { BMFace *f; /* don't use calc_edges option because we already have the edges */ diff --git a/source/blender/bmesh/operators/bmo_hull.c b/source/blender/bmesh/operators/bmo_hull.c index 9c41e4f2115..81ec2860cf7 100644 --- a/source/blender/bmesh/operators/bmo_hull.c +++ b/source/blender/bmesh/operators/bmo_hull.c @@ -119,7 +119,8 @@ static void hull_output_triangles(BMesh *bm, GSet *hull_triangles) }; BMFace *f, *example = NULL; - if (BM_face_exists(t->v, 3, &f)) { + f = BM_face_exists(t->v, 3); + if (f != NULL) { /* If the operator is run with "use_existing_faces" * disabled, but an output face in the hull is the * same as a face in the existing mesh, it should not diff --git a/source/blender/bmesh/operators/bmo_join_triangles.c b/source/blender/bmesh/operators/bmo_join_triangles.c index bc620e4a020..655fb346976 100644 --- a/source/blender/bmesh/operators/bmo_join_triangles.c +++ b/source/blender/bmesh/operators/bmo_join_triangles.c @@ -361,16 +361,16 @@ void bmo_join_triangles_exec(BMesh *bm, BMOperator *op) qsort(jedges, totedge, sizeof(*jedges), BLI_sortutil_cmp_float); for (i = 0; i < totedge; i++) { - BMFace *f_a, *f_b; + BMLoop *l_a, *l_b; e = jedges[i].data; - f_a = e->l->f; - f_b = e->l->radial_next->f; + l_a = e->l; + l_b = e->l->radial_next; /* check if another edge already claimed this face */ - if ((f_a->len == 3) && (f_b->len == 3)) { + if ((l_a->f->len == 3) && (l_b->f->len == 3)) { BMFace *f_new; - f_new = BM_faces_join_pair(bm, f_a, f_b, e, true); + f_new = BM_faces_join_pair(bm, l_a, l_b, true); if (f_new) { BMO_face_flag_enable(bm, f_new, FACE_OUT); } diff --git a/source/blender/bmesh/operators/bmo_removedoubles.c b/source/blender/bmesh/operators/bmo_removedoubles.c index 6da591b23a0..0ad8247e539 100644 --- a/source/blender/bmesh/operators/bmo_removedoubles.c +++ b/source/blender/bmesh/operators/bmo_removedoubles.c @@ -160,7 +160,7 @@ finally: } if (STACK_SIZE(edges) >= 3) { - if (!BM_face_exists(verts, STACK_SIZE(edges), NULL)) { + if (!BM_face_exists(verts, STACK_SIZE(edges))) { BMFace *f_new = BM_face_create(bm, verts, edges, STACK_SIZE(edges), f, BM_CREATE_NOP); BLI_assert(f_new != f); diff --git a/source/blender/bmesh/operators/bmo_triangulate.c b/source/blender/bmesh/operators/bmo_triangulate.c index 8938d086c1a..6bd3174d27a 100644 --- a/source/blender/bmesh/operators/bmo_triangulate.c +++ b/source/blender/bmesh/operators/bmo_triangulate.c @@ -253,10 +253,7 @@ void bmo_triangle_fill_exec(BMesh *bm, BMOperator *op) if (BMO_edge_flag_test(bm, e, ELE_NEW)) { /* in rare cases the edges face will have already been removed from the edge */ if (LIKELY(e->l)) { - BMFace *f_new = BM_faces_join_pair( - bm, e->l->f, - e->l->radial_next->f, e, - false); /* join faces */ + BMFace *f_new = BM_faces_join_pair(bm, e->l, e->l->radial_next, false); if (f_new) { BMO_face_flag_enable(bm, f_new, ELE_NEW); BM_edge_kill(bm, e); diff --git a/source/blender/bmesh/tools/bmesh_decimate_dissolve.c b/source/blender/bmesh/tools/bmesh_decimate_dissolve.c index 978cceee37c..e2c36299ddf 100644 --- a/source/blender/bmesh/tools/bmesh_decimate_dissolve.c +++ b/source/blender/bmesh/tools/bmesh_decimate_dissolve.c @@ -322,9 +322,7 @@ void BM_mesh_decimate_dissolve_ex( i = BM_elem_index_get(e); if (BM_edge_is_manifold(e)) { - f_new = BM_faces_join_pair(bm, e->l->f, - e->l->radial_next->f, e, - false); /* join faces */ + f_new = BM_faces_join_pair(bm, e->l, e->l->radial_next, false); if (f_new) { BMLoop *l_first, *l_iter; diff --git a/source/blender/bmesh/tools/bmesh_decimate_unsubdivide.c b/source/blender/bmesh/tools/bmesh_decimate_unsubdivide.c index 0fc571bc0a8..92300ae66a2 100644 --- a/source/blender/bmesh/tools/bmesh_decimate_unsubdivide.c +++ b/source/blender/bmesh/tools/bmesh_decimate_unsubdivide.c @@ -74,7 +74,7 @@ static bool bm_vert_dissolve_fan_test(BMVert *v) ((tot_edge == 3) && (tot_edge_boundary == 0) && (tot_edge_manifold == 3)) || ((tot_edge == 3) && (tot_edge_boundary == 2) && (tot_edge_manifold == 1))) { - if (!BM_face_exists(varr, tot_edge, NULL)) { + if (!BM_face_exists(varr, tot_edge)) { return true; } } diff --git a/source/blender/collada/MeshImporter.cpp b/source/blender/collada/MeshImporter.cpp index 6ff6de33d56..8f3bf88af65 100644 --- a/source/blender/collada/MeshImporter.cpp +++ b/source/blender/collada/MeshImporter.cpp @@ -317,11 +317,6 @@ bool MeshImporter::is_nice_mesh(COLLADAFW::Mesh *mesh) // checks if mesh has su } } - if (mesh->getPositions().empty()) { - fprintf(stderr, "ERROR: Mesh %s has no vertices.\n", name.c_str()); - return false; - } - return true; } @@ -329,11 +324,15 @@ void MeshImporter::read_vertices(COLLADAFW::Mesh *mesh, Mesh *me) { // vertices COLLADAFW::MeshVertexData& pos = mesh->getPositions(); + if (pos.empty()) { + return; + } + int stride = pos.getStride(0); if (stride == 0) stride = 3; - - me->totvert = mesh->getPositions().getFloatValues()->getCount() / stride; - me->mvert = (MVert *)CustomData_add_layer(&me->vdata, CD_MVERT, CD_CALLOC, NULL, me->totvert); + + me->totvert = pos.getFloatValues()->getCount() / stride; + me->mvert = (MVert *)CustomData_add_layer(&me->vdata, CD_MVERT, CD_CALLOC, NULL, me->totvert); MVert *mvert; int i; diff --git a/source/blender/collada/TransformWriter.cpp b/source/blender/collada/TransformWriter.cpp index b16e2e2b0d3..908111ebae6 100644 --- a/source/blender/collada/TransformWriter.cpp +++ b/source/blender/collada/TransformWriter.cpp @@ -113,11 +113,6 @@ void TransformWriter::add_node_transform_ob(COLLADASW::Node& node, Object *ob, B node.addMatrix("transform",d_obmat); break; } - case BC_TRANSFORMATION_TYPE_BOTH: - { - node.addMatrix("transform",d_obmat); - /* fall-through */ - } case BC_TRANSFORMATION_TYPE_TRANSROTLOC: { float loc[3], rot[3], scale[3]; diff --git a/source/blender/collada/collada.h b/source/blender/collada/collada.h index 0017c66836a..a4416608584 100644 --- a/source/blender/collada/collada.h +++ b/source/blender/collada/collada.h @@ -43,8 +43,7 @@ typedef enum BC_export_mesh_type { typedef enum BC_export_transformation_type { BC_TRANSFORMATION_TYPE_MATRIX, - BC_TRANSFORMATION_TYPE_TRANSROTLOC, - BC_TRANSFORMATION_TYPE_BOTH + BC_TRANSFORMATION_TYPE_TRANSROTLOC } BC_export_transformation_type; struct bContext; diff --git a/source/blender/compositor/intern/COM_ExecutionGroup.cpp b/source/blender/compositor/intern/COM_ExecutionGroup.cpp index e5c2b8ace4e..9a47c6b2438 100644 --- a/source/blender/compositor/intern/COM_ExecutionGroup.cpp +++ b/source/blender/compositor/intern/COM_ExecutionGroup.cpp @@ -383,7 +383,7 @@ void ExecutionGroup::finalizeChunkExecution(int chunkNumber, MemoryBuffer **memo if (this->m_chunkExecutionStates[chunkNumber] == COM_ES_SCHEDULED) this->m_chunkExecutionStates[chunkNumber] = COM_ES_EXECUTED; - atomic_add_u(&this->m_chunksFinished, 1); + atomic_add_and_fetch_u(&this->m_chunksFinished, 1); if (memoryBuffers) { for (unsigned int index = 0; index < this->m_cachedMaxReadBufferOffset; index++) { MemoryBuffer *buffer = memoryBuffers[index]; diff --git a/source/blender/compositor/operations/COM_TextureOperation.cpp b/source/blender/compositor/operations/COM_TextureOperation.cpp index bba5c8702b8..6bfd8ae3888 100644 --- a/source/blender/compositor/operations/COM_TextureOperation.cpp +++ b/source/blender/compositor/operations/COM_TextureOperation.cpp @@ -118,7 +118,7 @@ void TextureBaseOperation::executePixelSampled(float output[4], float x, float y * interpolaiton and (b) in such configuration multitex() sinply floor's the value * which often produces artifacts. */ - if ((m_texture->imaflag & TEX_INTERPOL) == 0) { + if (m_texture != NULL && (m_texture->imaflag & TEX_INTERPOL) == 0) { u += 0.5f / cx; v += 0.5f / cy; } diff --git a/source/blender/depsgraph/CMakeLists.txt b/source/blender/depsgraph/CMakeLists.txt index fd2a521bec5..ab12a8d5b3e 100644 --- a/source/blender/depsgraph/CMakeLists.txt +++ b/source/blender/depsgraph/CMakeLists.txt @@ -45,6 +45,7 @@ set(SRC intern/builder/deg_builder_nodes.cc intern/builder/deg_builder_pchanmap.cc intern/builder/deg_builder_relations.cc + intern/builder/deg_builder_relations_keys.cc intern/builder/deg_builder_transitive.cc intern/debug/deg_debug_graphviz.cc intern/eval/deg_eval.cc diff --git a/source/blender/depsgraph/DEG_depsgraph_build.h b/source/blender/depsgraph/DEG_depsgraph_build.h index 0945da439ef..fdc86540171 100644 --- a/source/blender/depsgraph/DEG_depsgraph_build.h +++ b/source/blender/depsgraph/DEG_depsgraph_build.h @@ -51,8 +51,12 @@ extern "C" { /* Graph Building -------------------------------- */ -/* Build depsgraph for the given scene, and dump results in given graph container */ -void DEG_graph_build_from_scene(struct Depsgraph *graph, struct Main *bmain, struct Scene *scene); +/* Build depsgraph for the given scene, and dump results in given + * graph container. + */ +void DEG_graph_build_from_scene(struct Depsgraph *graph, + struct Main *bmain, + struct Scene *scene); /* Tag relations from the given graph for update. */ void DEG_graph_tag_relations_update(struct Depsgraph *graph); @@ -85,31 +89,69 @@ struct CacheFile; struct Object; typedef enum eDepsSceneComponentType { - DEG_SCENE_COMP_PARAMETERS, /* Parameters Component - Default when nothing else fits (i.e. just SDNA property setting) */ - DEG_SCENE_COMP_ANIMATION, /* Animation Component */ // XXX: merge in with parameters? - DEG_SCENE_COMP_SEQUENCER, /* Sequencer Component (Scene Only) */ + /* Parameters Component - Default when nothing else fits + * (i.e. just SDNA property setting). + */ + DEG_SCENE_COMP_PARAMETERS, + /* Animation Component + * TODO(sergey): merge in with parameters? + */ + DEG_SCENE_COMP_ANIMATION, + /* Sequencer Component (Scene Only). */ + DEG_SCENE_COMP_SEQUENCER, } eDepsSceneComponentType; typedef enum eDepsObjectComponentType { - DEG_OB_COMP_PARAMETERS, /* Parameters Component - Default when nothing else fits (i.e. just SDNA property setting) */ - DEG_OB_COMP_PROXY, /* Generic "Proxy-Inherit" Component */ // XXX: Also for instancing of subgraphs? - DEG_OB_COMP_ANIMATION, /* Animation Component */ // XXX: merge in with parameters? - DEG_OB_COMP_TRANSFORM, /* Transform Component (Parenting/Constraints) */ - DEG_OB_COMP_GEOMETRY, /* Geometry Component (DerivedMesh/Displist) */ - + /* Parameters Component - Default when nothing else fits + * (i.e. just SDNA property setting). + */ + DEG_OB_COMP_PARAMETERS, + /* Generic "Proxy-Inherit" Component. + * TODO(sergey): Also for instancing of subgraphs? + */ + DEG_OB_COMP_PROXY, + /* Animation Component. + * + * TODO(sergey): merge in with parameters? + */ + DEG_OB_COMP_ANIMATION, + /* Transform Component (Parenting/Constraints) */ + DEG_OB_COMP_TRANSFORM, + /* Geometry Component (DerivedMesh/Displist) */ + DEG_OB_COMP_GEOMETRY, + /* Evaluation-Related Outer Types (with Subdata) */ - DEG_OB_COMP_EVAL_POSE, /* Pose Component - Owner/Container of Bones Eval */ - DEG_OB_COMP_BONE, /* Bone Component - Child/Subcomponent of Pose */ - - DEG_OB_COMP_EVAL_PARTICLES, /* Particle Systems Component */ - DEG_OB_COMP_SHADING, /* Material Shading Component */ - DEG_OB_COMP_CACHE, /* Cache Component */ + + /* Pose Component - Owner/Container of Bones Eval */ + DEG_OB_COMP_EVAL_POSE, + /* Bone Component - Child/Subcomponent of Pose */ + DEG_OB_COMP_BONE, + + /* Particle Systems Component */ + DEG_OB_COMP_EVAL_PARTICLES, + /* Material Shading Component */ + DEG_OB_COMP_SHADING, + /* Cache Component */ + DEG_OB_COMP_CACHE, } eDepsObjectComponentType; -void DEG_add_scene_relation(struct DepsNodeHandle *node, struct Scene *scene, eDepsSceneComponentType component, const char *description); -void DEG_add_object_relation(struct DepsNodeHandle *node, struct Object *ob, eDepsObjectComponentType component, const char *description); -void DEG_add_bone_relation(struct DepsNodeHandle *handle, struct Object *ob, const char *bone_name, eDepsObjectComponentType component, const char *description); -void DEG_add_object_cache_relation(struct DepsNodeHandle *handle, struct CacheFile *cache_file, eDepsObjectComponentType component, const char *description); +void DEG_add_scene_relation(struct DepsNodeHandle *node, + struct Scene *scene, + eDepsSceneComponentType component, + const char *description); +void DEG_add_object_relation(struct DepsNodeHandle *node, struct + Object *ob, + eDepsObjectComponentType component, + const char *description); +void DEG_add_bone_relation(struct DepsNodeHandle *handle, + struct Object *ob, + const char *bone_name, + eDepsObjectComponentType component, + const char *description); +void DEG_add_object_cache_relation(struct DepsNodeHandle *handle, + struct CacheFile *cache_file, + eDepsObjectComponentType component, + const char *description); /* TODO(sergey): Remove once all geometry update is granular. */ void DEG_add_special_eval_flag(struct Depsgraph *graph, struct ID *id, short flag); @@ -117,8 +159,22 @@ void DEG_add_special_eval_flag(struct Depsgraph *graph, struct ID *id, short fla /* Utility functions for physics modifiers */ typedef bool (*DEG_CollobjFilterFunction)(struct Object *obj, struct ModifierData *md); -void DEG_add_collision_relations(struct DepsNodeHandle *handle, struct Scene *scene, Object *ob, struct Group *group, int layer, unsigned int modifier_type, DEG_CollobjFilterFunction fn, bool dupli, const char *name); -void DEG_add_forcefield_relations(struct DepsNodeHandle *handle, struct Scene *scene, Object *ob, struct EffectorWeights *eff, bool add_absorption, int skip_forcefield, const char *name); +void DEG_add_collision_relations(struct DepsNodeHandle *handle, + struct Scene *scene, + Object *ob, + struct Group *group, + int layer, + unsigned int modifier_type, + DEG_CollobjFilterFunction fn, + bool dupli, + const char *name); +void DEG_add_forcefield_relations(struct DepsNodeHandle *handle, + struct Scene *scene, + Object *ob, + struct EffectorWeights *eff, + bool add_absorption, + int skip_forcefield, + const char *name); /* ************************************************ */ diff --git a/source/blender/depsgraph/intern/builder/deg_builder.cc b/source/blender/depsgraph/intern/builder/deg_builder.cc index 6169100d574..8939e4cc93a 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder.cc @@ -34,6 +34,8 @@ #include <stack> #include "DNA_anim_types.h" +#include "DNA_object_types.h" +#include "DNA_ID.h" #include "BLI_utildefines.h" #include "BLI_ghash.h" @@ -56,10 +58,46 @@ string deg_fcurve_id_name(const FCurve *fcu) return string(fcu->rna_path) + index_buf; } +static bool check_object_needs_evaluation(Object *object) +{ + if (object->recalc & OB_RECALC_ALL) { + /* Object is tagged for update anyway, no need to re-tag it. */ + return false; + } + if (object->type == OB_MESH) { + return object->derivedFinal == NULL; + } + else if (ELEM(object->type, + OB_CURVE, OB_SURF, OB_FONT, OB_MBALL, OB_LATTICE)) + { + return object->curve_cache == NULL; + } + return false; +} + void deg_graph_build_finalize(Depsgraph *graph) { + /* STEP 1: Make sure new invisible dependencies are ready for use. + * + * TODO(sergey): This might do a bit of extra tagging, but it's kinda nice + * to do it ahead of a time and don't spend time on flushing updates on + * every frame change. + */ + GHASH_FOREACH_BEGIN(IDDepsNode *, id_node, graph->id_hash) + { + if (id_node->layers == 0 || 1) { + ID *id = id_node->id; + if (GS(id->name) == ID_OB) { + Object *object = (Object *)id; + if (check_object_needs_evaluation(object)) { + id_node->tag_update(graph); + } + } + } + } + GHASH_FOREACH_END(); + /* STEP 2: Flush visibility layers from children to parent. */ std::stack<OperationDepsNode *> stack; - foreach (OperationDepsNode *node, graph->operations) { IDDepsNode *id_node = node->owner->owner; node->done = 0; @@ -78,7 +116,6 @@ void deg_graph_build_finalize(Depsgraph *graph) node->owner->layers = id_node->layers; id_node->id->tag |= LIB_TAG_DOIT; } - while (!stack.empty()) { OperationDepsNode *node = stack.top(); stack.pop(); @@ -104,8 +141,9 @@ void deg_graph_build_finalize(Depsgraph *graph) } } } - - /* Re-tag IDs for update if it was tagged before the relations update tag. */ + /* STEP 3: Re-tag IDs for update if it was tagged before the relations + * update tag. + */ GHASH_FOREACH_BEGIN(IDDepsNode *, id_node, graph->id_hash) { GHASH_FOREACH_BEGIN(ComponentDepsNode *, comp, id_node->components) @@ -121,6 +159,13 @@ void deg_graph_build_finalize(Depsgraph *graph) id_node->tag_update(graph); id->tag &= ~LIB_TAG_DOIT; } + else if (GS(id->name) == ID_OB) { + Object *object = (Object *)id; + if (object->recalc & OB_RECALC_ALL) { + id_node->tag_update(graph); + id->tag &= ~LIB_TAG_DOIT; + } + } id_node->finalize_build(); } GHASH_FOREACH_END(); diff --git a/source/blender/depsgraph/intern/builder/deg_builder_cycle.cc b/source/blender/depsgraph/intern/builder/deg_builder_cycle.cc index 225cc64ae4d..9b37aaa12ff 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_cycle.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder_cycle.cc @@ -56,12 +56,14 @@ struct StackEntry { void deg_graph_detect_cycles(Depsgraph *graph) { - /* Not is not visited at all during traversal. */ - const int NODE_NOT_VISITED = 0; - /* Node has been visited during traversal and not in current stack. */ - const int NODE_VISITED = 1; - /* Node has been visited during traversal and is in current stack. */ - const int NODE_IN_STACK = 2; + enum { + /* Not is not visited at all during traversal. */ + NODE_NOT_VISITED = 0, + /* Node has been visited during traversal and not in current stack. */ + NODE_VISITED = 1, + /* Node has been visited during traversal and is in current stack. */ + NODE_IN_STACK = 2, + }; std::stack<StackEntry> traversal_stack; foreach (OperationDepsNode *node, graph->operations) { @@ -77,21 +79,23 @@ void deg_graph_detect_cycles(Depsgraph *graph) entry.from = NULL; entry.via_relation = NULL; traversal_stack.push(entry); - node->done = NODE_IN_STACK; + node->tag = NODE_IN_STACK; } else { - node->done = NODE_NOT_VISITED; + node->tag = NODE_NOT_VISITED; } + node->done = 0; } while (!traversal_stack.empty()) { - StackEntry &entry = traversal_stack.top(); + StackEntry& entry = traversal_stack.top(); OperationDepsNode *node = entry.node; bool all_child_traversed = true; - foreach (DepsRelation *rel, node->outlinks) { + for (int i = node->done; i < node->outlinks.size(); ++i) { + DepsRelation *rel = node->outlinks[i]; if (rel->to->type == DEPSNODE_TYPE_OPERATION) { OperationDepsNode *to = (OperationDepsNode *)rel->to; - if (to->done == NODE_IN_STACK) { + if (to->tag == NODE_IN_STACK) { printf("Dependency cycle detected:\n"); printf(" '%s' depends on '%s' through '%s'\n", to->full_identifier().c_str(), @@ -107,23 +111,24 @@ void deg_graph_detect_cycles(Depsgraph *graph) current->via_relation->name); current = current->from; } - /* TODO(sergey): So called roussian rlette cycle solver. */ + /* TODO(sergey): So called russian roulette cycle solver. */ rel->flag |= DEPSREL_FLAG_CYCLIC; } - else if (to->done == NODE_NOT_VISITED) { + else if (to->tag == NODE_NOT_VISITED) { StackEntry new_entry; new_entry.node = to; new_entry.from = &entry; new_entry.via_relation = rel; traversal_stack.push(new_entry); - to->done = NODE_IN_STACK; + to->tag = NODE_IN_STACK; all_child_traversed = false; + node->done = i; break; } } } if (all_child_traversed) { - node->done = NODE_VISITED; + node->tag = NODE_VISITED; traversal_stack.pop(); } } diff --git a/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc b/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc index 1812384440f..12050e3e003 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc @@ -34,7 +34,6 @@ #include <stdio.h> #include <stdlib.h> -#include <string.h> #include "MEM_guardedalloc.h" @@ -109,6 +108,40 @@ extern "C" { namespace DEG { +namespace { + +struct BuilderWalkUserData { + DepsgraphNodeBuilder *builder; + Scene *scene; +}; + +static void modifier_walk(void *user_data, + struct Object * /*ob*/, + struct Object **obpoin, + int /*cd_flag*/) +{ + BuilderWalkUserData *data = (BuilderWalkUserData *)user_data; + if (*obpoin) { + data->builder->build_object(data->scene, NULL, *obpoin); + } +} + +void constraint_walk(bConstraint * /*con*/, + ID **idpoin, + bool /*is_reference*/, + void *user_data) +{ + BuilderWalkUserData *data = (BuilderWalkUserData *)user_data; + if (*idpoin) { + ID *id = *idpoin; + if (GS(id->name) == ID_OB) { + data->builder->build_object(data->scene, NULL, (Object *)id); + } + } +} + +} /* namespace */ + /* ************ */ /* Node Builder */ @@ -131,8 +164,7 @@ RootDepsNode *DepsgraphNodeBuilder::add_root_node() IDDepsNode *DepsgraphNodeBuilder::add_id_node(ID *id) { - const char *idtype_name = BKE_idcode_to_name(GS(id->name)); - return m_graph->add_id_node(id, string(id->name + 2) + "[" + idtype_name + "]"); + return m_graph->add_id_node(id, id->name); } TimeSourceDepsNode *DepsgraphNodeBuilder::add_time_source(ID *id) @@ -179,7 +211,7 @@ TimeSourceDepsNode *DepsgraphNodeBuilder::add_time_source(ID *id) ComponentDepsNode *DepsgraphNodeBuilder::add_component_node( ID *id, eDepsNode_Type comp_type, - const string &comp_name) + const char *comp_name) { IDDepsNode *id_node = add_id_node(id); ComponentDepsNode *comp_node = id_node->add_component(comp_type, comp_name); @@ -192,15 +224,19 @@ OperationDepsNode *DepsgraphNodeBuilder::add_operation_node( eDepsOperation_Type optype, DepsEvalOperationCb op, eDepsOperation_Code opcode, - const string &description) + const char *name, + int name_tag) { - OperationDepsNode *op_node = comp_node->has_operation(opcode, description); + OperationDepsNode *op_node = comp_node->has_operation(opcode, + name, + name_tag); if (op_node == NULL) { - op_node = comp_node->add_operation(optype, op, opcode, description); + op_node = comp_node->add_operation(optype, op, opcode, name, name_tag); m_graph->operations.push_back(op_node); } else { - fprintf(stderr, "add_operation: Operation already exists - %s has %s at %p\n", + fprintf(stderr, + "add_operation: Operation already exists - %s has %s at %p\n", comp_node->identifier().c_str(), op_node->identifier().c_str(), op_node); @@ -212,14 +248,15 @@ OperationDepsNode *DepsgraphNodeBuilder::add_operation_node( OperationDepsNode *DepsgraphNodeBuilder::add_operation_node( ID *id, eDepsNode_Type comp_type, - const string &comp_name, + const char *comp_name, eDepsOperation_Type optype, DepsEvalOperationCb op, eDepsOperation_Code opcode, - const string &description) + const char *name, + int name_tag) { ComponentDepsNode *comp_node = add_component_node(id, comp_type, comp_name); - return add_operation_node(comp_node, optype, op, opcode, description); + return add_operation_node(comp_node, optype, op, opcode, name, name_tag); } OperationDepsNode *DepsgraphNodeBuilder::add_operation_node( @@ -228,38 +265,54 @@ OperationDepsNode *DepsgraphNodeBuilder::add_operation_node( eDepsOperation_Type optype, DepsEvalOperationCb op, eDepsOperation_Code opcode, - const string& description) + const char *name, + int name_tag) { - return add_operation_node(id, comp_type, "", optype, op, opcode, description); + return add_operation_node(id, + comp_type, + "", + optype, + op, + opcode, + name, + name_tag); } bool DepsgraphNodeBuilder::has_operation_node(ID *id, eDepsNode_Type comp_type, - const string &comp_name, + const char *comp_name, eDepsOperation_Code opcode, - const string &description) + const char *name, + int name_tag) { - return find_operation_node(id, comp_type, comp_name, opcode, description) != NULL; + return find_operation_node(id, + comp_type, + comp_name, + opcode, + name, + name_tag) != NULL; } OperationDepsNode *DepsgraphNodeBuilder::find_operation_node( ID *id, eDepsNode_Type comp_type, - const string &comp_name, + const char *comp_name, eDepsOperation_Code opcode, - const string &description) + const char *name, + int name_tag) { ComponentDepsNode *comp_node = add_component_node(id, comp_type, comp_name); - return comp_node->has_operation(opcode, description); + return comp_node->has_operation(opcode, name, name_tag); } OperationDepsNode *DepsgraphNodeBuilder::find_operation_node( ID *id, eDepsNode_Type comp_type, eDepsOperation_Code opcode, - const string& description) + const char *name, + int name_tag) { - return find_operation_node(id, comp_type, "", opcode, description); + return find_operation_node(id, comp_type, "", opcode, name, name_tag); } /* **** Build functions for entity nodes **** */ @@ -386,19 +439,22 @@ SubgraphDepsNode *DepsgraphNodeBuilder::build_subgraph(Group *group) { /*Object *ob = go->ob;*/ - /* Each "group object" is effectively a separate instance of the underlying - * object data. When the group is evaluated, the transform results and/or - * some other attributes end up getting overridden by the group + /* Each "group object" is effectively a separate instance of the + * underlying object data. When the group is evaluated, the transform + * results and/or some other attributes end up getting overridden by + * the group. */ } - /* create a node for representing subgraph */ + /* Create a node for representing subgraph. */ SubgraphDepsNode *subgraph_node = m_graph->add_subgraph_node(&group->id); subgraph_node->graph = subgraph; - /* make a copy of the data this node will need? */ - // XXX: do we do this now, or later? - // TODO: need API function which queries graph's ID's hash, and duplicates those blocks thoroughly with all outside links removed... + /* Make a copy of the data this node will need? */ + /* XXX: do we do this now, or later? */ + /* TODO: need API function which queries graph's ID's hash, and duplicates + * those blocks thoroughly with all outside links removed. + */ return subgraph_node; } @@ -407,18 +463,40 @@ void DepsgraphNodeBuilder::build_object(Scene *scene, Base *base, Object *ob) { if (ob->id.tag & LIB_TAG_DOIT) { IDDepsNode *id_node = m_graph->find_id_node(&ob->id); - id_node->layers |= base->lay; + if (base != NULL) { + id_node->layers |= base->lay; + } return; } + ob->id.tag |= LIB_TAG_DOIT; IDDepsNode *id_node = add_id_node(&ob->id); - id_node->layers |= base->lay; + if (base != NULL) { + id_node->layers |= base->lay; + } ob->customdata_mask = 0; - /* standard components */ + /* Standard components. */ build_object_transform(scene, ob); - /* object data */ + if (ob->parent != NULL) { + build_object(scene, NULL, ob->parent); + } + if (ob->modifiers.first != NULL) { + BuilderWalkUserData data; + data.builder = this; + data.scene = scene; + modifiers_foreachObjectLink(ob, modifier_walk, &data); + } + if (ob->constraints.first != NULL) { + BuilderWalkUserData data; + data.builder = this; + data.scene = scene; + modifiers_foreachObjectLink(ob, modifier_walk, &data); + BKE_constraints_id_loop(&ob->constraints, constraint_walk, &data); + } + + /* Object data. */ if (ob->data) { /* type-specific data... */ switch (ob->type) { @@ -428,15 +506,6 @@ void DepsgraphNodeBuilder::build_object(Scene *scene, Base *base, Object *ob) case OB_SURF: case OB_MBALL: case OB_LATTICE: - { - /* TODO(sergey): This way using this object's - * properties as driver target works fine. - * - * Does this depend on other nodes? - */ - add_operation_node(&ob->id, DEPSNODE_TYPE_PARAMETERS, DEPSOP_TYPE_POST, NULL, - DEG_OPCODE_PLACEHOLDER, "Parameters Eval"); - build_obdata_geom(scene, ob); /* TODO(sergey): Only for until we support granular * update of curves. @@ -448,7 +517,6 @@ void DepsgraphNodeBuilder::build_object(Scene *scene, Base *base, Object *ob) } } break; - } case OB_ARMATURE: /* Pose */ if (ID_IS_LINKED_DATABLOCK(ob) && ob->proxy_from != NULL) { @@ -617,12 +685,17 @@ OperationDepsNode *DepsgraphNodeBuilder::build_driver(ID *id, FCurve *fcu) OperationDepsNode *driver_op = find_operation_node(id, DEPSNODE_TYPE_PARAMETERS, DEG_OPCODE_DRIVER, - deg_fcurve_id_name(fcu)); + fcu->rna_path, + fcu->array_index); if (driver_op == NULL) { - driver_op = add_operation_node(id, DEPSNODE_TYPE_PARAMETERS, - DEPSOP_TYPE_EXEC, function_bind(BKE_animsys_eval_driver, _1, id, fcu), - DEG_OPCODE_DRIVER, deg_fcurve_id_name(fcu)); + driver_op = add_operation_node(id, + DEPSNODE_TYPE_PARAMETERS, + DEPSOP_TYPE_EXEC, + function_bind(BKE_animsys_eval_driver, _1, id, fcu), + DEG_OPCODE_DRIVER, + fcu->rna_path, + fcu->array_index); } /* tag "scripted expression" drivers as needing Python (due to GIL issues, etc.) */ @@ -807,7 +880,7 @@ void DepsgraphNodeBuilder::build_rig(Scene *scene, Object *ob) /* Rebuild pose if not up to date. */ if (ob->pose == NULL || (ob->pose->flag & POSE_RECALC)) { - BKE_pose_rebuild(ob, arm); + BKE_pose_rebuild_ex(ob, arm, false); /* XXX: Without this animation gets lost in certain circumstances * after loading file. Need to investigate further since it does * not happen with simple scenes.. @@ -972,6 +1045,18 @@ void DepsgraphNodeBuilder::build_obdata_geom(Scene *scene, Object *ob) { ID *obdata = (ID *)ob->data; + /* TODO(sergey): This way using this object's properties as driver target + * works fine. + * + * Does this depend on other nodes? + */ + add_operation_node(&ob->id, + DEPSNODE_TYPE_PARAMETERS, + DEPSOP_TYPE_POST, + NULL, + DEG_OPCODE_PLACEHOLDER, + "Parameters Eval"); + /* Temporary uber-update node, which does everything. * It is for the being we're porting old dependencies into the new system. * We'll get rid of this node as soon as all the granular update functions @@ -979,35 +1064,45 @@ void DepsgraphNodeBuilder::build_obdata_geom(Scene *scene, Object *ob) * * TODO(sergey): Get rid of this node. */ - add_operation_node(&ob->id, DEPSNODE_TYPE_GEOMETRY, - DEPSOP_TYPE_POST, function_bind(BKE_object_eval_uber_data, _1, scene, ob), + add_operation_node(&ob->id, + DEPSNODE_TYPE_GEOMETRY, + DEPSOP_TYPE_POST, + function_bind(BKE_object_eval_uber_data, _1, scene, ob), DEG_OPCODE_GEOMETRY_UBEREVAL); - add_operation_node(&ob->id, DEPSNODE_TYPE_GEOMETRY, - DEPSOP_TYPE_INIT, NULL, - DEG_OPCODE_PLACEHOLDER, "Eval Init"); + add_operation_node(&ob->id, + DEPSNODE_TYPE_GEOMETRY, + DEPSOP_TYPE_INIT, + NULL, + DEG_OPCODE_PLACEHOLDER, + "Eval Init"); // TODO: "Done" operation /* Modifiers */ if (ob->modifiers.first) { - ModifierData *md; - - for (md = (ModifierData *)ob->modifiers.first; md; md = md->next) { - add_operation_node(&ob->id, DEPSNODE_TYPE_GEOMETRY, - DEPSOP_TYPE_EXEC, function_bind(BKE_object_eval_modifier, _1, scene, ob, md), - DEG_OPCODE_GEOMETRY_MODIFIER, md->name); + for (ModifierData *md = (ModifierData *)ob->modifiers.first; + md != NULL; + md = md->next) + { + add_operation_node(&ob->id, + DEPSNODE_TYPE_GEOMETRY, + DEPSOP_TYPE_EXEC, + function_bind(BKE_object_eval_modifier, + _1, + scene, + ob, + md), + DEG_OPCODE_GEOMETRY_MODIFIER, + md->name); } } /* materials */ if (ob->totcol) { - int a; - - for (a = 1; a <= ob->totcol; a++) { + for (int a = 1; a <= ob->totcol; a++) { Material *ma = give_current_material(ob, a); - - if (ma) { + if (ma != NULL) { // XXX?! ComponentDepsNode *geom_node = add_component_node(&ob->id, DEPSNODE_TYPE_GEOMETRY); build_material(geom_node, ma); @@ -1032,16 +1127,23 @@ void DepsgraphNodeBuilder::build_obdata_geom(Scene *scene, Object *ob) build_animdata(obdata); - /* nodes for result of obdata's evaluation, and geometry evaluation on object */ + /* Nodes for result of obdata's evaluation, and geometry + * evaluation on object. + */ switch (ob->type) { case OB_MESH: { //Mesh *me = (Mesh *)ob->data; /* evaluation operations */ - add_operation_node(obdata, DEPSNODE_TYPE_GEOMETRY, - DEPSOP_TYPE_INIT, function_bind(BKE_mesh_eval_geometry, _1, (Mesh *)obdata), - DEG_OPCODE_PLACEHOLDER, "Geometry Eval"); + add_operation_node(obdata, + DEPSNODE_TYPE_GEOMETRY, + DEPSOP_TYPE_INIT, + function_bind(BKE_mesh_eval_geometry, + _1, + (Mesh *)obdata), + DEG_OPCODE_PLACEHOLDER, + "Geometry Eval"); break; } @@ -1049,48 +1151,76 @@ void DepsgraphNodeBuilder::build_obdata_geom(Scene *scene, Object *ob) { Object *mom = BKE_mball_basis_find(scene, ob); - /* motherball - mom depends on children! */ + /* Motherball - mom depends on children! */ if (mom == ob) { /* metaball evaluation operations */ /* NOTE: only the motherball gets evaluated! */ - add_operation_node(obdata, DEPSNODE_TYPE_GEOMETRY, - DEPSOP_TYPE_INIT, function_bind(BKE_mball_eval_geometry, _1, (MetaBall *)obdata), - DEG_OPCODE_PLACEHOLDER, "Geometry Eval"); + add_operation_node(obdata, + DEPSNODE_TYPE_GEOMETRY, + DEPSOP_TYPE_INIT, + function_bind(BKE_mball_eval_geometry, + _1, + (MetaBall *)obdata), + DEG_OPCODE_PLACEHOLDER, + "Geometry Eval"); } break; } case OB_CURVE: + case OB_SURF: case OB_FONT: { - /* curve evaluation operations */ + /* Curve/nurms evaluation operations. */ /* - calculate curve geometry (including path) */ - add_operation_node(obdata, DEPSNODE_TYPE_GEOMETRY, - DEPSOP_TYPE_INIT, function_bind(BKE_curve_eval_geometry, _1, (Curve *)obdata), - DEG_OPCODE_PLACEHOLDER, "Geometry Eval"); - - /* - calculate curve path - this is used by constraints, etc. */ - add_operation_node(obdata, DEPSNODE_TYPE_GEOMETRY, - DEPSOP_TYPE_EXEC, function_bind(BKE_curve_eval_path, _1, (Curve *)obdata), - DEG_OPCODE_GEOMETRY_PATH, "Path"); - break; - } + add_operation_node(obdata, + DEPSNODE_TYPE_GEOMETRY, + DEPSOP_TYPE_INIT, + function_bind(BKE_curve_eval_geometry, + _1, + (Curve *)obdata), + DEG_OPCODE_PLACEHOLDER, + "Geometry Eval"); + + /* Calculate curve path - this is used by constraints, etc. */ + if (ELEM(ob->type, OB_CURVE, OB_FONT)) { + add_operation_node(obdata, + DEPSNODE_TYPE_GEOMETRY, + DEPSOP_TYPE_EXEC, + function_bind(BKE_curve_eval_path, + _1, + (Curve *)obdata), + DEG_OPCODE_GEOMETRY_PATH, + "Path"); + } - case OB_SURF: /* Nurbs Surface */ - { - /* nurbs evaluation operations */ - add_operation_node(obdata, DEPSNODE_TYPE_GEOMETRY, - DEPSOP_TYPE_INIT, function_bind(BKE_curve_eval_geometry, _1, (Curve *)obdata), - DEG_OPCODE_PLACEHOLDER, "Geometry Eval"); + /* Make sure objects used for bevel.taper are in the graph. + * NOTE: This objects might be not linked to the scene. + */ + Curve *cu = (Curve *)obdata; + if (cu->bevobj != NULL) { + build_object(scene, NULL, cu->bevobj); + } + if (cu->taperobj != NULL) { + build_object(scene, NULL, cu->bevobj); + } + if (ob->type == OB_FONT && cu->textoncurve != NULL) { + build_object(scene, NULL, cu->textoncurve); + } break; } - case OB_LATTICE: /* Lattice */ + case OB_LATTICE: { - /* lattice evaluation operations */ - add_operation_node(obdata, DEPSNODE_TYPE_GEOMETRY, - DEPSOP_TYPE_INIT, function_bind(BKE_lattice_eval_geometry, _1, (Lattice *)obdata), - DEG_OPCODE_PLACEHOLDER, "Geometry Eval"); + /* Lattice evaluation operations. */ + add_operation_node(obdata, + DEPSNODE_TYPE_GEOMETRY, + DEPSOP_TYPE_INIT, + function_bind(BKE_lattice_eval_geometry, + _1, + (Lattice *)obdata), + DEG_OPCODE_PLACEHOLDER, + "Geometry Eval"); break; } } @@ -1171,15 +1301,20 @@ void DepsgraphNodeBuilder::build_nodetree(DepsNode *owner_node, bNodeTree *ntree /* nodetree's nodes... */ for (bNode *bnode = (bNode *)ntree->nodes.first; bnode; bnode = bnode->next) { - if (bnode->id) { - if (GS(bnode->id->name) == ID_MA) { - build_material(owner_node, (Material *)bnode->id); + ID *id = bnode->id; + if (id != NULL) { + short id_type = GS(id->name); + if (id_type == ID_MA) { + build_material(owner_node, (Material *)id); } - else if (bnode->type == ID_TE) { - build_texture(owner_node, (Tex *)bnode->id); + else if (id_type == ID_TE) { + build_texture(owner_node, (Tex *)id); + } + else if (id_type == ID_IM) { + build_image((Image *)id); } else if (bnode->type == NODE_GROUP) { - bNodeTree *group_ntree = (bNodeTree *)bnode->id; + bNodeTree *group_ntree = (bNodeTree *)id; if ((group_ntree->id.tag & LIB_TAG_DOIT) == 0) { build_nodetree(owner_node, group_ntree); } @@ -1236,10 +1371,33 @@ void DepsgraphNodeBuilder::build_texture(DepsNode *owner_node, Tex *tex) return; } tex_id->tag |= LIB_TAG_DOIT; - /* texture itself */ + /* Texture itself. */ build_animdata(tex_id); - /* texture's nodetree */ + /* Texture's nodetree. */ build_nodetree(owner_node, tex->nodetree); + /* Special cases for different IDs which texture uses. */ + if (tex->type == TEX_IMAGE) { + if (tex->ima != NULL) { + build_image(tex->ima); + } + } +} + +void DepsgraphNodeBuilder::build_image(Image *image) { + ID *image_id = &image->id; + if (image_id->tag & LIB_TAG_DOIT) { + return; + } + image_id->tag |= LIB_TAG_DOIT; + /* Image ID node itself. */ + add_id_node(image_id); + /* Placeholder so we can add relations and tag ID node for update. */ + add_operation_node(image_id, + DEPSNODE_TYPE_PARAMETERS, + DEPSOP_TYPE_EXEC, + NULL, + DEG_OPCODE_PLACEHOLDER, + "Image Eval"); } void DepsgraphNodeBuilder::build_compositor(Scene *scene) diff --git a/source/blender/depsgraph/intern/builder/deg_builder_nodes.h b/source/blender/depsgraph/intern/builder/deg_builder_nodes.h index f378f076804..72dc73357bf 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_nodes.h +++ b/source/blender/depsgraph/intern/builder/deg_builder_nodes.h @@ -38,6 +38,7 @@ struct bGPdata; struct ListBase; struct GHash; struct ID; +struct Image; struct FCurve; struct Group; struct Key; @@ -75,43 +76,49 @@ struct DepsgraphNodeBuilder { ComponentDepsNode *add_component_node(ID *id, eDepsNode_Type comp_type, - const string& comp_name = ""); + const char *comp_name = ""); OperationDepsNode *add_operation_node(ComponentDepsNode *comp_node, eDepsOperation_Type optype, DepsEvalOperationCb op, eDepsOperation_Code opcode, - const string& description = ""); + const char *name = "", + int name_tag = -1); OperationDepsNode *add_operation_node(ID *id, eDepsNode_Type comp_type, - const string& comp_name, + const char *comp_name, eDepsOperation_Type optype, DepsEvalOperationCb op, eDepsOperation_Code opcode, - const string& description = ""); + const char *name = "", + int name_tag = -1); OperationDepsNode *add_operation_node(ID *id, eDepsNode_Type comp_type, eDepsOperation_Type optype, DepsEvalOperationCb op, eDepsOperation_Code opcode, - const string& description = ""); + const char *name = "", + int name_tag = -1); bool has_operation_node(ID *id, eDepsNode_Type comp_type, - const string& comp_name, + const char *comp_name, eDepsOperation_Code opcode, - const string& description = ""); + const char *name = "", + int name_tag = -1); OperationDepsNode *find_operation_node(ID *id, eDepsNode_Type comp_type, - const string &comp_name, + const char *comp_name, eDepsOperation_Code opcode, - const string &description = ""); + const char *name = "", + int name_tag = -1); OperationDepsNode *find_operation_node(ID *id, eDepsNode_Type comp_type, eDepsOperation_Code opcode, - const string &description = ""); + const char *name = "", + int name_tag = -1); void build_scene(Main *bmain, Scene *scene); SubgraphDepsNode *build_subgraph(Group *group); @@ -142,6 +149,7 @@ struct DepsgraphNodeBuilder { void build_material(DepsNode *owner_node, Material *ma); void build_texture(DepsNode *owner_node, Tex *tex); void build_texture_stack(DepsNode *owner_node, MTex **texture_stack); + void build_image(Image *image); void build_world(World *world); void build_compositor(Scene *scene); void build_gpencil(bGPdata *gpd); diff --git a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc index 09c7d9ab9de..fe75de5e350 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc @@ -34,13 +34,12 @@ #include <stdio.h> #include <stdlib.h> -#include <string.h> +#include <cstring> /* required for STREQ later on. */ #include "MEM_guardedalloc.h" extern "C" { #include "BLI_blenlib.h" -#include "BLI_string.h" #include "BLI_utildefines.h" #include "DNA_action_types.h" @@ -211,10 +210,12 @@ OperationDepsNode *DepsgraphRelationBuilder::find_node( return NULL; } - OperationDepsNode *op_node = comp_node->find_operation(key.opcode, key.name); + OperationDepsNode *op_node = comp_node->find_operation(key.opcode, + key.name, + key.name_tag); if (!op_node) { fprintf(stderr, "find_node_operation: Failed for (%s, '%s')\n", - DEG_OPNAMES[key.opcode], key.name.c_str()); + DEG_OPNAMES[key.opcode], key.name); } return op_node; } @@ -236,7 +237,7 @@ OperationDepsNode *DepsgraphRelationBuilder::has_node( if (!comp_node) { return NULL; } - return comp_node->has_operation(key.opcode, key.name); + return comp_node->has_operation(key.opcode, key.name, key.name_tag); } void DepsgraphRelationBuilder::add_time_relation(TimeSourceDepsNode *timesrc, @@ -449,6 +450,7 @@ void DepsgraphRelationBuilder::build_object(Main *bmain, Scene *scene, Object *o if (ob->id.tag & LIB_TAG_DOIT) { return; } + ob->id.tag |= LIB_TAG_DOIT; /* Object Transforms */ eDepsOperation_Code base_op = (ob->parent) ? DEG_OPCODE_TRANSFORM_PARENT : DEG_OPCODE_TRANSFORM_LOCAL; @@ -500,7 +502,7 @@ void DepsgraphRelationBuilder::build_object(Main *bmain, Scene *scene, Object *o build_animdata(&ob->id); // XXX: This should be hooked up by the build_animdata code - if (ob->adt && (ob->adt->action || ob->adt->nla_tracks.first)) { + if (needs_animdata_node(&ob->id)) { ComponentKey adt_key(&ob->id, DEPSNODE_TYPE_ANIMATION); add_relation(adt_key, local_transform_key, DEPSREL_TYPE_OPERATION, "Object Animation"); } @@ -837,11 +839,66 @@ void DepsgraphRelationBuilder::build_animdata(ID *id) /* drivers */ for (FCurve *fcu = (FCurve *)adt->drivers.first; fcu; fcu = fcu->next) { - OperationKey driver_key(id, DEPSNODE_TYPE_PARAMETERS, DEG_OPCODE_DRIVER, deg_fcurve_id_name(fcu)); + OperationKey driver_key(id, + DEPSNODE_TYPE_PARAMETERS, + DEG_OPCODE_DRIVER, + fcu->rna_path, + fcu->array_index); /* create the driver's relations to targets */ build_driver(id, fcu); + /* Special case for array drivers: we can not multithread them because + * of the way how they work internally: animation system will write the + * whole array back to RNA even when changing individual array value. + * + * Some tricky things here: + * - array_index is -1 for single channel drivers, meaning we only have + * to do some magic when array_index is not -1. + * - We do relation from next array index to a previous one, so we don't + * have to deal with array index 0. + * + * TODO(sergey): Avoid liner lookup somehow. + */ + if (fcu->array_index > 0) { + FCurve *fcu_prev = NULL; + for (FCurve *fcu_candidate = (FCurve *)adt->drivers.first; + fcu_candidate != NULL; + fcu_candidate = fcu_candidate->next) + { + /* Writing to different RNA paths is */ + if (!STREQ(fcu_candidate->rna_path, fcu->rna_path)) { + continue; + } + /* We only do relation from previous fcurve to previous one. */ + if (fcu_candidate->array_index >= fcu->array_index) { + continue; + } + /* Choose fcurve with highest possible array index. */ + if (fcu_prev == NULL || + fcu_candidate->array_index > fcu_prev->array_index) + { + fcu_prev = fcu_candidate; + } + } + if (fcu_prev != NULL) { + OperationKey prev_driver_key(id, + DEPSNODE_TYPE_PARAMETERS, + DEG_OPCODE_DRIVER, + fcu_prev->rna_path, + fcu_prev->array_index); + OperationKey driver_key(id, + DEPSNODE_TYPE_PARAMETERS, + DEG_OPCODE_DRIVER, + fcu->rna_path, + fcu->array_index); + add_relation(prev_driver_key, + driver_key, + DEPSREL_TYPE_OPERATION, + "[Driver Order]"); + } + } + /* prevent driver from occurring before own animation... */ if (adt->action || adt->nla_tracks.first) { add_relation(adt_key, driver_key, DEPSREL_TYPE_OPERATION, @@ -853,7 +910,11 @@ void DepsgraphRelationBuilder::build_animdata(ID *id) void DepsgraphRelationBuilder::build_driver(ID *id, FCurve *fcu) { ChannelDriver *driver = fcu->driver; - OperationKey driver_key(id, DEPSNODE_TYPE_PARAMETERS, DEG_OPCODE_DRIVER, deg_fcurve_id_name(fcu)); + OperationKey driver_key(id, + DEPSNODE_TYPE_PARAMETERS, + DEG_OPCODE_DRIVER, + fcu->rna_path, + fcu->array_index); bPoseChannel *pchan = NULL; /* create dependency between driver and data affected by it */ @@ -1356,10 +1417,10 @@ void DepsgraphRelationBuilder::build_ik_pose(Object *ob, if (data->poletar != NULL) { if ((data->poletar->type == OB_ARMATURE) && (data->polesubtarget[0])) { // XXX: same armature issues - ready vs done? - ComponentKey target_key(&data->poletar->id, DEPSNODE_TYPE_BONE, data->subtarget); + ComponentKey target_key(&data->poletar->id, DEPSNODE_TYPE_BONE, data->polesubtarget); add_relation(target_key, solver_key, DEPSREL_TYPE_TRANSFORM, con->name); } - else if (ELEM(data->poletar->type, OB_MESH, OB_LATTICE) && (data->subtarget[0])) { + else if (ELEM(data->poletar->type, OB_MESH, OB_LATTICE) && (data->polesubtarget[0])) { /* vertex group target */ /* NOTE: for now, we don't need to represent vertex groups separately... */ ComponentKey target_key(&data->poletar->id, DEPSNODE_TYPE_GEOMETRY); @@ -1527,7 +1588,7 @@ void DepsgraphRelationBuilder::build_rig(Scene *scene, Object *ob) "Armature Eval"); add_relation(armature_key, init_key, DEPSREL_TYPE_COMPONENT_ORDER, "Data dependency"); - if (ob->adt && (ob->adt->action || ob->adt->nla_tracks.first)) { + if (needs_animdata_node(&ob->id)) { ComponentKey animation_key(&ob->id, DEPSNODE_TYPE_ANIMATION); add_relation(animation_key, init_key, DEPSREL_TYPE_OPERATION, "Rig Animation"); } @@ -1765,7 +1826,7 @@ void DepsgraphRelationBuilder::build_obdata_geom(Main *bmain, Scene *scene, Obje * for either the modifier needing time, or that it is animated. */ /* XXX: Remove this hack when these links are added as part of build_animdata() instead */ - if (modifier_dependsOnTime(md) == false) { + if (modifier_dependsOnTime(md) == false && needs_animdata_node(&ob->id)) { ComponentKey animation_key(&ob->id, DEPSNODE_TYPE_ANIMATION); add_relation(animation_key, mod_key, DEPSREL_TYPE_OPERATION, "Modifier Animation"); } @@ -1848,15 +1909,18 @@ void DepsgraphRelationBuilder::build_obdata_geom(Main *bmain, Scene *scene, Obje // XXX: these needs geom data, but where is geom stored? if (cu->bevobj) { ComponentKey bevob_key(&cu->bevobj->id, DEPSNODE_TYPE_GEOMETRY); + build_object(bmain, scene, cu->bevobj); add_relation(bevob_key, geom_key, DEPSREL_TYPE_GEOMETRY_EVAL, "Curve Bevel"); } if (cu->taperobj) { ComponentKey taperob_key(&cu->taperobj->id, DEPSNODE_TYPE_GEOMETRY); + build_object(bmain, scene, cu->taperobj); add_relation(taperob_key, geom_key, DEPSREL_TYPE_GEOMETRY_EVAL, "Curve Taper"); } if (ob->type == OB_FONT) { if (cu->textoncurve) { - ComponentKey textoncurve_key(&cu->taperobj->id, DEPSNODE_TYPE_GEOMETRY); + ComponentKey textoncurve_key(&cu->textoncurve->id, DEPSNODE_TYPE_GEOMETRY); + build_object(bmain, scene, cu->textoncurve); add_relation(textoncurve_key, geom_key, DEPSREL_TYPE_GEOMETRY_EVAL, "Text on Curve"); } } @@ -2063,7 +2127,7 @@ bool DepsgraphRelationBuilder::needs_animdata_node(ID *id) { AnimData *adt = BKE_animdata_from_id(id); if (adt != NULL) { - return adt->action != NULL; + return (adt->action != NULL) || (adt->nla_tracks.first != NULL); } return false; } diff --git a/source/blender/depsgraph/intern/builder/deg_builder_relations.h b/source/blender/depsgraph/intern/builder/deg_builder_relations.h index 5240aa24b54..8d8ad6772b8 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_relations.h +++ b/source/blender/depsgraph/intern/builder/deg_builder_relations.h @@ -81,108 +81,83 @@ struct ComponentDepsNode; struct OperationDepsNode; struct RootPChanMap; -struct RootKey -{ - RootKey() {} +struct RootKey { + RootKey(); }; struct TimeSourceKey { - TimeSourceKey() : id(NULL) {} - TimeSourceKey(ID *id) : id(id) {} + TimeSourceKey(); + TimeSourceKey(ID *id); - string identifier() const - { - return string("TimeSourceKey"); - } + string identifier() const; ID *id; }; struct ComponentKey { - ComponentKey() : - id(NULL), type(DEPSNODE_TYPE_UNDEFINED), name("") - {} - ComponentKey(ID *id, eDepsNode_Type type, const string &name = "") : - id(id), type(type), name(name) - {} - - string identifier() const - { - const char *idname = (id) ? id->name : "<None>"; + ComponentKey(); + ComponentKey(ID *id, eDepsNode_Type type, const char *name = ""); - char typebuf[5]; - BLI_snprintf(typebuf, sizeof(typebuf), "%d", type); - - return string("ComponentKey(") + idname + ", " + typebuf + ", '" + name + "')"; - } + string identifier() const; ID *id; eDepsNode_Type type; - string name; + const char *name; }; struct OperationKey { - OperationKey() : - id(NULL), component_type(DEPSNODE_TYPE_UNDEFINED), component_name(""), opcode(DEG_OPCODE_OPERATION), name("") - {} - - OperationKey(ID *id, eDepsNode_Type component_type, const string &name) : - id(id), component_type(component_type), component_name(""), opcode(DEG_OPCODE_OPERATION), name(name) - {} - OperationKey(ID *id, eDepsNode_Type component_type, const string &component_name, const string &name) : - id(id), component_type(component_type), component_name(component_name), opcode(DEG_OPCODE_OPERATION), name(name) - {} - - OperationKey(ID *id, eDepsNode_Type component_type, eDepsOperation_Code opcode) : - id(id), component_type(component_type), component_name(""), opcode(opcode), name("") - {} - OperationKey(ID *id, eDepsNode_Type component_type, const string &component_name, eDepsOperation_Code opcode) : - id(id), component_type(component_type), component_name(component_name), opcode(opcode), name("") - {} - - OperationKey(ID *id, eDepsNode_Type component_type, eDepsOperation_Code opcode, const string &name) : - id(id), component_type(component_type), component_name(""), opcode(opcode), name(name) - {} - OperationKey(ID *id, eDepsNode_Type component_type, const string &component_name, eDepsOperation_Code opcode, const string &name) : - id(id), component_type(component_type), component_name(component_name), opcode(opcode), name(name) - {} - - string identifier() const - { - char typebuf[5]; - BLI_snprintf(typebuf, sizeof(typebuf), "%d", component_type); - - return string("OperationKey(") + "t: " + typebuf + ", cn: '" + component_name + "', c: " + DEG_OPNAMES[opcode] + ", n: '" + name + "')"; - } - + OperationKey(); + OperationKey(ID *id, + eDepsNode_Type component_type, + const char *name, + int name_tag = -1); + OperationKey(ID *id, + eDepsNode_Type component_type, + const char *component_name, + const char *name, + int name_tag); + + OperationKey(ID *id, + eDepsNode_Type component_type, + eDepsOperation_Code opcode); + OperationKey(ID *id, + eDepsNode_Type component_type, + const char *component_name, + eDepsOperation_Code opcode); + + OperationKey(ID *id, + eDepsNode_Type component_type, + eDepsOperation_Code opcode, + const char *name, + int name_tag = -1); + OperationKey(ID *id, + eDepsNode_Type component_type, + const char *component_name, + eDepsOperation_Code opcode, + const char *name, + int name_tag = -1); + + string identifier() const; ID *id; eDepsNode_Type component_type; - string component_name; + const char *component_name; eDepsOperation_Code opcode; - string name; + const char *name; + int name_tag; }; struct RNAPathKey { - // Note: see depsgraph_build.cpp for implementation + /* NOTE: see depsgraph_build.cpp for implementation */ RNAPathKey(ID *id, const char *path); - RNAPathKey(ID *id, const PointerRNA &ptr, PropertyRNA *prop) : - id(id), ptr(ptr), prop(prop) - {} - - string identifier() const - { - const char *id_name = (id) ? id->name : "<No ID>"; - const char *prop_name = (prop) ? RNA_property_identifier(prop) : "<No Prop>"; - - return string("RnaPathKey(") + "id: " + id_name + ", prop: " + prop_name + "')"; - } + RNAPathKey(ID *id, const PointerRNA &ptr, PropertyRNA *prop); + string identifier() const; ID *id; PointerRNA ptr; @@ -270,7 +245,7 @@ protected: template <typename KeyType> DepsNodeHandle create_node_handle(const KeyType& key, - const string& default_name = ""); + const char *default_name = ""); bool needs_animdata_node(ID *id); @@ -280,7 +255,7 @@ private: struct DepsNodeHandle { - DepsNodeHandle(DepsgraphRelationBuilder *builder, OperationDepsNode *node, const string &default_name = "") : + DepsNodeHandle(DepsgraphRelationBuilder *builder, OperationDepsNode *node, const char *default_name = "") : builder(builder), node(node), default_name(default_name) @@ -290,7 +265,7 @@ struct DepsNodeHandle DepsgraphRelationBuilder *builder; OperationDepsNode *node; - const string &default_name; + const char *default_name; }; /* Utilities for Builders ----------------------------------------------------- */ @@ -318,6 +293,7 @@ void DepsgraphRelationBuilder::add_relation(const KeyFrom &key_from, else { if (!op_from) { /* XXX TODO handle as error or report if needed */ + node_from = find_node(key_from); fprintf(stderr, "add_relation(%d, %s) - Could not find op_from (%s)\n", type, description, key_from.identifier().c_str()); } @@ -383,7 +359,7 @@ void DepsgraphRelationBuilder::add_node_handle_relation( template <typename KeyType> DepsNodeHandle DepsgraphRelationBuilder::create_node_handle( const KeyType &key, - const string &default_name) + const char *default_name) { return DepsNodeHandle(this, find_node(key), default_name); } diff --git a/source/blender/depsgraph/intern/builder/deg_builder_relations_keys.cc b/source/blender/depsgraph/intern/builder/deg_builder_relations_keys.cc new file mode 100644 index 00000000000..7ada04e8f74 --- /dev/null +++ b/source/blender/depsgraph/intern/builder/deg_builder_relations_keys.cc @@ -0,0 +1,211 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2013 Blender Foundation. + * All rights reserved. + * + * Original Author: Joshua Leung + * Contributor(s): Based on original depsgraph.c code - Blender Foundation (2005-2013) + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file blender/depsgraph/intern/builder/deg_builder_relations.cc + * \ingroup depsgraph + * + * Methods for constructing depsgraph + */ + +#include "intern/builder/deg_builder_relations.h" + +namespace DEG { + +///////////////////////////////////////// +// Root. + +RootKey::RootKey() +{ +} + +///////////////////////////////////////// +// Time source. + +TimeSourceKey::TimeSourceKey() + : id(NULL) +{ +} + +TimeSourceKey::TimeSourceKey(ID *id) + : id(id) +{ +} + +string TimeSourceKey::identifier() const +{ + return string("TimeSourceKey"); +} + +///////////////////////////////////////// +// Component. + +ComponentKey::ComponentKey() + : id(NULL), + type(DEPSNODE_TYPE_UNDEFINED), + name("") +{ +} + +ComponentKey::ComponentKey(ID *id, eDepsNode_Type type, const char *name) + : id(id), + type(type), + name(name) +{ +} + +string ComponentKey::identifier() const +{ + const char *idname = (id) ? id->name : "<None>"; + char typebuf[5]; + BLI_snprintf(typebuf, sizeof(typebuf), "%d", type); + return string("ComponentKey(") + + idname + ", " + typebuf + ", '" + name + "')"; +} + +///////////////////////////////////////// +// Operation. + +OperationKey::OperationKey() + : id(NULL), + component_type(DEPSNODE_TYPE_UNDEFINED), + component_name(""), + opcode(DEG_OPCODE_OPERATION), + name(""), + name_tag(-1) +{ +} + +OperationKey::OperationKey(ID *id, + eDepsNode_Type component_type, + const char *name, + int name_tag) + : id(id), + component_type(component_type), + component_name(""), + opcode(DEG_OPCODE_OPERATION), + name(name), + name_tag(name_tag) +{ +} + +OperationKey::OperationKey(ID *id, + eDepsNode_Type component_type, + const char *component_name, + const char *name, + int name_tag) + : id(id), + component_type(component_type), + component_name(component_name), + opcode(DEG_OPCODE_OPERATION), + name(name), + name_tag(name_tag) +{ +} + +OperationKey::OperationKey(ID *id, + eDepsNode_Type component_type, + eDepsOperation_Code opcode) + : id(id), + component_type(component_type), + component_name(""), + opcode(opcode), + name(""), + name_tag(-1) +{ +} + +OperationKey::OperationKey(ID *id, + eDepsNode_Type component_type, + const char *component_name, + eDepsOperation_Code opcode) + : id(id), + component_type(component_type), + component_name(component_name), + opcode(opcode), + name(""), + name_tag(-1) +{ +} + +OperationKey::OperationKey(ID *id, + eDepsNode_Type component_type, + eDepsOperation_Code opcode, + const char *name, + int name_tag) + : id(id), + component_type(component_type), + component_name(""), + opcode(opcode), + name(name), + name_tag(name_tag) +{ +} + +OperationKey::OperationKey(ID *id, + eDepsNode_Type component_type, + const char *component_name, + eDepsOperation_Code opcode, + const char *name, + int name_tag) + : id(id), + component_type(component_type), + component_name(component_name), + opcode(opcode), + name(name), + name_tag(name_tag) +{ +} + +string OperationKey::identifier() const +{ + char typebuf[5]; + BLI_snprintf(typebuf, sizeof(typebuf), "%d", component_type); + return string("OperationKey(") + + "t: " + typebuf + + ", cn: '" + component_name + + "', c: " + DEG_OPNAMES[opcode] + + ", n: '" + name + "')"; +} + +///////////////////////////////////////// +// RNA path. + +RNAPathKey::RNAPathKey(ID *id, const PointerRNA &ptr, PropertyRNA *prop) + : id(id), + ptr(ptr), + prop(prop) +{ +} + +string RNAPathKey::identifier() const +{ + const char *id_name = (id) ? id->name : "<No ID>"; + const char *prop_name = (prop) ? RNA_property_identifier(prop) : "<No Prop>"; + return string("RnaPathKey(") + "id: " + id_name + + ", prop: " + prop_name + "')"; +} + +} // namespace DEG diff --git a/source/blender/depsgraph/intern/debug/deg_debug_graphviz.cc b/source/blender/depsgraph/intern/debug/deg_debug_graphviz.cc index 70cd5f11a47..0d56ce71c7d 100644 --- a/source/blender/depsgraph/intern/debug/deg_debug_graphviz.cc +++ b/source/blender/depsgraph/intern/debug/deg_debug_graphviz.cc @@ -321,7 +321,7 @@ static void deg_debug_graphviz_node_single(const DebugContext &ctx, static void deg_debug_graphviz_node_cluster_begin(const DebugContext &ctx, const DepsNode *node) { - string name = node->identifier().c_str(); + string name = node->identifier(); if (node->type == DEPSNODE_TYPE_ID_REF) { IDDepsNode *id_node = (IDDepsNode *)node; char buf[256]; diff --git a/source/blender/depsgraph/intern/depsgraph.cc b/source/blender/depsgraph/intern/depsgraph.cc index 2b7c63767ab..3502267d9ca 100644 --- a/source/blender/depsgraph/intern/depsgraph.cc +++ b/source/blender/depsgraph/intern/depsgraph.cc @@ -32,8 +32,6 @@ #include "intern/depsgraph.h" /* own include */ -#include <string.h> - #include "MEM_guardedalloc.h" #include "BLI_utildefines.h" @@ -53,6 +51,8 @@ extern "C" { #include "RNA_access.h" } +#include <cstring> + #include "DEG_depsgraph.h" #include "intern/nodes/deg_node.h" @@ -116,7 +116,7 @@ static bool pointer_to_component_node_criteria(const PointerRNA *ptr, const PropertyRNA *prop, ID **id, eDepsNode_Type *type, - string *subdata) + const char **subdata) { if (!ptr->type) return false; @@ -232,7 +232,7 @@ DepsNode *Depsgraph::find_node_from_pointer(const PointerRNA *ptr, { ID *id; eDepsNode_Type type; - string name; + const char *name; /* Get querying conditions. */ if (pointer_to_id_node_criteria(ptr, prop, &id)) { @@ -240,8 +240,9 @@ DepsNode *Depsgraph::find_node_from_pointer(const PointerRNA *ptr, } else if (pointer_to_component_node_criteria(ptr, prop, &id, &type, &name)) { IDDepsNode *id_node = find_id_node(id); - if (id_node) + if (id_node != NULL) { return id_node->find_component(type, name); + } } return NULL; @@ -328,7 +329,7 @@ IDDepsNode *Depsgraph::find_id_node(const ID *id) const return reinterpret_cast<IDDepsNode *>(BLI_ghash_lookup(id_hash, id)); } -IDDepsNode *Depsgraph::add_id_node(ID *id, const string &name) +IDDepsNode *Depsgraph::add_id_node(ID *id, const char *name) { IDDepsNode *id_node = find_id_node(id); if (!id_node) { diff --git a/source/blender/depsgraph/intern/depsgraph.h b/source/blender/depsgraph/intern/depsgraph.h index 08b264f8497..e668facd645 100644 --- a/source/blender/depsgraph/intern/depsgraph.h +++ b/source/blender/depsgraph/intern/depsgraph.h @@ -101,22 +101,6 @@ struct Depsgraph { ~Depsgraph(); /** - * Find node which matches the specified description. - * - * \param id: ID block that is associated with this - * \param subdata: identifier used for sub-ID data (e.g. bone) - * \param type: type of node we're dealing with - * \param name: custom identifier assigned to node - * - * \return A node matching the required characteristics if it exists - * or NULL if no such node exists in the graph. - */ - DepsNode *find_node(const ID *id, - eDepsNode_Type type, - const string &subdata, - const string &name); - - /** * Convenience wrapper to find node given just pointer + property. * * \param ptr: pointer to the data that node will represent @@ -136,7 +120,7 @@ struct Depsgraph { void clear_subgraph_nodes(); IDDepsNode *find_id_node(const ID *id) const; - IDDepsNode *add_id_node(ID *id, const string &name = ""); + IDDepsNode *add_id_node(ID *id, const char *name = ""); void remove_id_node(const ID *id); void clear_id_nodes(); diff --git a/source/blender/depsgraph/intern/depsgraph_build.cc b/source/blender/depsgraph/intern/depsgraph_build.cc index 7a3b19e82c6..9952f714145 100644 --- a/source/blender/depsgraph/intern/depsgraph_build.cc +++ b/source/blender/depsgraph/intern/depsgraph_build.cc @@ -32,6 +32,8 @@ #include "MEM_guardedalloc.h" +// #define DEBUG_TIME + extern "C" { #include "DNA_cachefile_types.h" #include "DNA_object_types.h" @@ -41,6 +43,11 @@ extern "C" { #include "BLI_utildefines.h" #include "BLI_ghash.h" +#ifdef DEBUG_TIME +# include "PIL_time.h" +# include "PIL_time_utildefines.h" +#endif + #include "BKE_main.h" #include "BKE_collision.h" #include "BKE_effect.h" @@ -190,6 +197,10 @@ void DEG_add_special_eval_flag(Depsgraph *graph, ID *id, short flag) */ void DEG_graph_build_from_scene(Depsgraph *graph, Main *bmain, Scene *scene) { +#ifdef DEBUG_TIME + TIMEIT_START(DEG_graph_build_from_scene); +#endif + DEG::Depsgraph *deg_graph = reinterpret_cast<DEG::Depsgraph *>(graph); /* 1) Generate all the nodes in the graph first */ @@ -239,6 +250,10 @@ void DEG_graph_build_from_scene(Depsgraph *graph, Main *bmain, Scene *scene) abort(); } #endif + +#ifdef DEBUG_TIME + TIMEIT_END(DEG_graph_build_from_scene); +#endif } /* Tag graph relations for update. */ @@ -309,7 +324,15 @@ void DEG_scene_graph_free(Scene *scene) } } -void DEG_add_collision_relations(DepsNodeHandle *handle, Scene *scene, Object *ob, Group *group, int layer, unsigned int modifier_type, DEG_CollobjFilterFunction fn, bool dupli, const char *name) +void DEG_add_collision_relations(DepsNodeHandle *handle, + Scene *scene, + Object *ob, + Group *group, + int layer, + unsigned int modifier_type, + DEG_CollobjFilterFunction fn, + bool dupli, + const char *name) { unsigned int numcollobj; Object **collobjs = get_collisionobjects_ext(scene, ob, group, layer, &numcollobj, modifier_type, dupli); @@ -327,7 +350,13 @@ void DEG_add_collision_relations(DepsNodeHandle *handle, Scene *scene, Object *o MEM_freeN(collobjs); } -void DEG_add_forcefield_relations(DepsNodeHandle *handle, Scene *scene, Object *ob, EffectorWeights *effector_weights, bool add_absorption, int skip_forcefield, const char *name) +void DEG_add_forcefield_relations(DepsNodeHandle *handle, + Scene *scene, + Object *ob, + EffectorWeights *effector_weights, + bool add_absorption, + int skip_forcefield, + const char *name) { ListBase *effectors = pdInitEffectors(scene, ob, NULL, effector_weights, false); @@ -339,17 +368,33 @@ void DEG_add_forcefield_relations(DepsNodeHandle *handle, Scene *scene, Object * if (eff->psys) { DEG_add_object_relation(handle, eff->ob, DEG_OB_COMP_EVAL_PARTICLES, name); - /* TODO: remove this when/if EVAL_PARTICLES is sufficient for up to date particles */ + /* TODO: remove this when/if EVAL_PARTICLES is sufficient + * for up to date particles. + */ DEG_add_object_relation(handle, eff->ob, DEG_OB_COMP_GEOMETRY, name); } if (eff->pd->forcefield == PFIELD_SMOKEFLOW && eff->pd->f_source) { - DEG_add_object_relation(handle, eff->pd->f_source, DEG_OB_COMP_TRANSFORM, "Smoke Force Domain"); - DEG_add_object_relation(handle, eff->pd->f_source, DEG_OB_COMP_GEOMETRY, "Smoke Force Domain"); + DEG_add_object_relation(handle, + eff->pd->f_source, + DEG_OB_COMP_TRANSFORM, + "Smoke Force Domain"); + DEG_add_object_relation(handle, + eff->pd->f_source, + DEG_OB_COMP_GEOMETRY, + "Smoke Force Domain"); } if (add_absorption && (eff->pd->flag & PFIELD_VISIBILITY)) { - DEG_add_collision_relations(handle, scene, ob, NULL, eff->ob->lay, eModifierType_Collision, NULL, true, "Force Absorption"); + DEG_add_collision_relations(handle, + scene, + ob, + NULL, + eff->ob->lay, + eModifierType_Collision, + NULL, + true, + "Force Absorption"); } } } diff --git a/source/blender/depsgraph/intern/depsgraph_intern.h b/source/blender/depsgraph/intern/depsgraph_intern.h index e5d3d1f5861..2d8e7dc841c 100644 --- a/source/blender/depsgraph/intern/depsgraph_intern.h +++ b/source/blender/depsgraph/intern/depsgraph_intern.h @@ -63,8 +63,8 @@ struct DepsNodeFactory { virtual const char *tname() const = 0; virtual DepsNode *create_node(const ID *id, - const string &subdata, - const string &name) const = 0; + const char *subdata, + const char *name) const = 0; }; template <class NodeType> @@ -73,7 +73,7 @@ struct DepsNodeFactoryImpl : public DepsNodeFactory { eDepsNode_Class tclass() const { return NodeType::typeinfo.tclass; } const char *tname() const { return NodeType::typeinfo.tname; } - DepsNode *create_node(const ID *id, const string &subdata, const string &name) const + DepsNode *create_node(const ID *id, const char *subdata, const char *name) const { DepsNode *node = OBJECT_GUARDED_NEW(NodeType); @@ -81,12 +81,14 @@ struct DepsNodeFactoryImpl : public DepsNodeFactory { node->type = type(); node->tclass = tclass(); - if (!name.empty()) + if (name[0] != '\0') { /* set name if provided ... */ node->name = name; - else + } + else { /* ... otherwise use default type name */ node->name = tname(); + } node->init(id, subdata); diff --git a/source/blender/depsgraph/intern/depsgraph_tag.cc b/source/blender/depsgraph/intern/depsgraph_tag.cc index 4f27dab258d..e8ed03666a6 100644 --- a/source/blender/depsgraph/intern/depsgraph_tag.cc +++ b/source/blender/depsgraph/intern/depsgraph_tag.cc @@ -31,7 +31,7 @@ */ #include <stdio.h> -#include <cstring> +#include <cstring> /* required for memset */ #include <queue> extern "C" { diff --git a/source/blender/depsgraph/intern/eval/deg_eval.cc b/source/blender/depsgraph/intern/eval/deg_eval.cc index c3fd202d832..e926f83bcbe 100644 --- a/source/blender/depsgraph/intern/eval/deg_eval.cc +++ b/source/blender/depsgraph/intern/eval/deg_eval.cc @@ -152,7 +152,7 @@ static void deg_task_run_func(TaskPool *pool, } if ((rel->flag & DEPSREL_FLAG_CYCLIC) == 0) { BLI_assert(child->num_links_pending > 0); - atomic_sub_uint32(&child->num_links_pending, 1); + atomic_sub_and_fetch_uint32(&child->num_links_pending, 1); } if (child->num_links_pending == 0) { bool is_scheduled = atomic_fetch_and_or_uint8( @@ -287,7 +287,7 @@ static void schedule_node(TaskPool *pool, Depsgraph *graph, unsigned int layers, { if (dec_parents) { BLI_assert(node->num_links_pending > 0); - atomic_sub_uint32(&node->num_links_pending, 1); + atomic_sub_and_fetch_uint32(&node->num_links_pending, 1); } if (node->num_links_pending == 0) { diff --git a/source/blender/depsgraph/intern/eval/deg_eval_debug.cc b/source/blender/depsgraph/intern/eval/deg_eval_debug.cc index 67d64aae8bf..060544a4407 100644 --- a/source/blender/depsgraph/intern/eval/deg_eval_debug.cc +++ b/source/blender/depsgraph/intern/eval/deg_eval_debug.cc @@ -30,10 +30,10 @@ * Implementation of tools for debugging the depsgraph */ -#include <cstring> - #include "intern/eval/deg_eval_debug.h" +#include <cstring> /* required for STREQ later on. */ + extern "C" { #include "BLI_listbase.h" #include "BLI_ghash.h" @@ -53,10 +53,10 @@ namespace DEG { DepsgraphStats *DepsgraphDebug::stats = NULL; -static string get_component_name(eDepsNode_Type type, const string &name = "") +static string get_component_name(eDepsNode_Type type, const char *name = "") { DepsNodeFactory *factory = deg_get_node_factory(type); - if (name.empty()) { + if (name[0] != '\0') { return string(factory->tname()); } else { @@ -116,7 +116,7 @@ void DepsgraphDebug::task_started(Depsgraph *graph, */ DepsgraphStatsComponent *comp_stats = get_component_stats(id, get_component_name(comp->type, - comp->name), + comp->name).c_str(), true); times_clear(comp_stats->times); } @@ -146,7 +146,7 @@ void DepsgraphDebug::task_completed(Depsgraph *graph, DepsgraphStatsComponent *comp_stats = get_component_stats(id, get_component_name(comp->type, - comp->name), + comp->name).c_str(), true); times_add(comp_stats->times, time); } @@ -226,7 +226,7 @@ DepsgraphStatsID *DepsgraphDebug::get_id_stats(ID *id, bool create) DepsgraphStatsComponent *DepsgraphDebug::get_component_stats( DepsgraphStatsID *id_stats, - const string &name, + const char *name, bool create) { DepsgraphStatsComponent *comp_stats; @@ -234,13 +234,14 @@ DepsgraphStatsComponent *DepsgraphDebug::get_component_stats( comp_stats != NULL; comp_stats = comp_stats->next) { - if (STREQ(comp_stats->name, name.c_str())) + if (STREQ(comp_stats->name, name)) { break; + } } if (!comp_stats && create) { comp_stats = (DepsgraphStatsComponent *)MEM_callocN(sizeof(DepsgraphStatsComponent), "Depsgraph Component Stats"); - BLI_strncpy(comp_stats->name, name.c_str(), sizeof(comp_stats->name)); + BLI_strncpy(comp_stats->name, name, sizeof(comp_stats->name)); BLI_addtail(&id_stats->components, comp_stats); } return comp_stats; diff --git a/source/blender/depsgraph/intern/eval/deg_eval_debug.h b/source/blender/depsgraph/intern/eval/deg_eval_debug.h index 9109019eb2d..0bbe88cc9ca 100644 --- a/source/blender/depsgraph/intern/eval/deg_eval_debug.h +++ b/source/blender/depsgraph/intern/eval/deg_eval_debug.h @@ -66,10 +66,10 @@ struct DepsgraphDebug { static DepsgraphStatsID *get_id_stats(ID *id, bool create); static DepsgraphStatsComponent *get_component_stats(DepsgraphStatsID *id_stats, - const string &name, + const char *name, bool create); static DepsgraphStatsComponent *get_component_stats(ID *id, - const string &name, + const char *name, bool create) { return get_component_stats(get_id_stats(id, create), name, create); diff --git a/source/blender/depsgraph/intern/nodes/deg_node.cc b/source/blender/depsgraph/intern/nodes/deg_node.cc index eb408f293de..57b25c10670 100644 --- a/source/blender/depsgraph/intern/nodes/deg_node.cc +++ b/source/blender/depsgraph/intern/nodes/deg_node.cc @@ -31,7 +31,7 @@ #include "intern/nodes/deg_node.h" #include <stdio.h> -#include <string.h> +#include <cstring> /* required for STREQ later on. */ #include "BLI_utildefines.h" #include "BLI_ghash.h" @@ -72,7 +72,7 @@ DepsNode::TypeInfo::TypeInfo(eDepsNode_Type type, const char *tname) DepsNode::DepsNode() { - name[0] = '\0'; + name = ""; } DepsNode::~DepsNode() @@ -122,7 +122,7 @@ RootDepsNode::~RootDepsNode() OBJECT_GUARDED_DELETE(time_source, TimeSourceDepsNode); } -TimeSourceDepsNode *RootDepsNode::add_time_source(const string &name) +TimeSourceDepsNode *RootDepsNode::add_time_source(const char *name) { if (!time_source) { DepsNodeFactory *factory = deg_get_node_factory(DEPSNODE_TYPE_TIMESOURCE); @@ -142,12 +142,24 @@ static DepsNodeFactoryImpl<TimeSourceDepsNode> DNTI_TIMESOURCE; /* ID Node ================================================ */ +IDDepsNode::ComponentIDKey::ComponentIDKey(eDepsNode_Type type, + const char *name) + : type(type), name(name) +{ +} + +bool IDDepsNode::ComponentIDKey::operator== (const ComponentIDKey &other) const +{ + return type == other.type && + STREQ(name, other.name); +} + static unsigned int id_deps_node_hash_key(const void *key_v) { const IDDepsNode::ComponentIDKey *key = reinterpret_cast<const IDDepsNode::ComponentIDKey *>(key_v); return hash_combine(BLI_ghashutil_uinthash(key->type), - BLI_ghashutil_strhash_p(key->name.c_str())); + BLI_ghashutil_strhash_p(key->name)); } static bool id_deps_node_hash_key_cmp(const void *a, const void *b) @@ -173,7 +185,7 @@ static void id_deps_node_hash_value_free(void *value_v) } /* Initialize 'id' node - from pointer data given. */ -void IDDepsNode::init(const ID *id, const string &UNUSED(subdata)) +void IDDepsNode::init(const ID *id, const char *UNUSED(subdata)) { /* Store ID-pointer. */ BLI_assert(id != NULL); @@ -204,14 +216,14 @@ IDDepsNode::~IDDepsNode() } ComponentDepsNode *IDDepsNode::find_component(eDepsNode_Type type, - const string &name) const + const char *name) const { ComponentIDKey key(type, name); return reinterpret_cast<ComponentDepsNode *>(BLI_ghash_lookup(components, &key)); } ComponentDepsNode *IDDepsNode::add_component(eDepsNode_Type type, - const string &name) + const char *name) { ComponentDepsNode *comp_node = find_component(type, name); if (!comp_node) { @@ -226,7 +238,7 @@ ComponentDepsNode *IDDepsNode::add_component(eDepsNode_Type type, return comp_node; } -void IDDepsNode::remove_component(eDepsNode_Type type, const string &name) +void IDDepsNode::remove_component(eDepsNode_Type type, const char *name) { ComponentDepsNode *comp_node = find_component(type, name); if (comp_node) { @@ -281,7 +293,7 @@ static DepsNodeFactoryImpl<IDDepsNode> DNTI_ID_REF; /* Subgraph Node ========================================== */ /* Initialize 'subgraph' node - from pointer data given. */ -void SubgraphDepsNode::init(const ID *id, const string &UNUSED(subdata)) +void SubgraphDepsNode::init(const ID *id, const char *UNUSED(subdata)) { /* Store ID-ref if provided. */ this->root_id = (ID *)id; diff --git a/source/blender/depsgraph/intern/nodes/deg_node.h b/source/blender/depsgraph/intern/nodes/deg_node.h index b2262c4bd12..7c2f53840b6 100644 --- a/source/blender/depsgraph/intern/nodes/deg_node.h +++ b/source/blender/depsgraph/intern/nodes/deg_node.h @@ -32,6 +32,8 @@ #include "intern/depsgraph_types.h" +#include "BLI_utildefines.h" + struct ID; struct GHash; struct Scene; @@ -57,7 +59,7 @@ struct DepsNode { }; /* Identifier - mainly for debugging purposes. */ - string name; + const char *name; /* Structural type of node. */ eDepsNode_Type type; @@ -78,8 +80,9 @@ struct DepsNode { /* Nodes which depend on this one. */ Relations outlinks; - /* Generic tag for traversal algorithms */ + /* Generic tags for traversal algorithms. */ int done; + int tag; /* Methods. */ @@ -90,7 +93,7 @@ struct DepsNode { string full_identifier() const; virtual void init(const ID * /*id*/, - const string &/*subdata*/) {} + const char * /*subdata*/) {} virtual void tag_update(Depsgraph * /*graph*/) {} @@ -129,7 +132,7 @@ struct RootDepsNode : public DepsNode { RootDepsNode(); ~RootDepsNode(); - TimeSourceDepsNode *add_time_source(const string &name = ""); + TimeSourceDepsNode *add_time_source(const char *name = ""); /* scene that this corresponds to */ Scene *scene; @@ -143,26 +146,21 @@ struct RootDepsNode : public DepsNode { /* ID-Block Reference */ struct IDDepsNode : public DepsNode { struct ComponentIDKey { - ComponentIDKey(eDepsNode_Type type, const string &name = "") - : type(type), name(name) {} - - bool operator== (const ComponentIDKey &other) const - { - return type == other.type && name == other.name; - } + ComponentIDKey(eDepsNode_Type type, const char *name = ""); + bool operator==(const ComponentIDKey &other) const; eDepsNode_Type type; - string name; + const char *name; }; - void init(const ID *id, const string &subdata); + void init(const ID *id, const char *subdata); ~IDDepsNode(); ComponentDepsNode *find_component(eDepsNode_Type type, - const string &name = "") const; + const char *name = "") const; ComponentDepsNode *add_component(eDepsNode_Type type, - const string &name = ""); - void remove_component(eDepsNode_Type type, const string &name = ""); + const char *name = ""); + void remove_component(eDepsNode_Type type, const char *name = ""); void clear_components(); void tag_update(Depsgraph *graph); @@ -189,7 +187,7 @@ struct IDDepsNode : public DepsNode { /* Subgraph Reference. */ struct SubgraphDepsNode : public DepsNode { - void init(const ID *id, const string &subdata); + void init(const ID *id, const char *subdata); ~SubgraphDepsNode(); /* Instanced graph. */ diff --git a/source/blender/depsgraph/intern/nodes/deg_node_component.cc b/source/blender/depsgraph/intern/nodes/deg_node_component.cc index 01f33b6368b..06f91ac7fdc 100644 --- a/source/blender/depsgraph/intern/nodes/deg_node_component.cc +++ b/source/blender/depsgraph/intern/nodes/deg_node_component.cc @@ -31,7 +31,7 @@ #include "intern/nodes/deg_node_component.h" #include <stdio.h> -#include <string.h> +#include <cstring> /* required for STREQ later on. */ extern "C" { #include "BLI_utildefines.h" @@ -53,12 +53,50 @@ namespace DEG { /* Standard Component Methods ============================= */ +ComponentDepsNode::OperationIDKey::OperationIDKey() + : opcode(DEG_OPCODE_OPERATION), + name(""), + name_tag(-1) +{ +} + +ComponentDepsNode::OperationIDKey::OperationIDKey(eDepsOperation_Code opcode) + : opcode(opcode), + name(""), + name_tag(-1) +{ +} + +ComponentDepsNode::OperationIDKey::OperationIDKey(eDepsOperation_Code opcode, + const char *name, + int name_tag) + : opcode(opcode), + name(name), + name_tag(name_tag) +{ +} + +string ComponentDepsNode::OperationIDKey::identifier() const +{ + char codebuf[5]; + BLI_snprintf(codebuf, sizeof(codebuf), "%d", opcode); + return string("OperationIDKey(") + codebuf + ", " + name + ")"; +} + +bool ComponentDepsNode::OperationIDKey::operator==( + const OperationIDKey &other) const +{ + return (opcode == other.opcode) && + (STREQ(name, other.name)) && + (name_tag == other.name_tag); +} + static unsigned int comp_node_hash_key(const void *key_v) { const ComponentDepsNode::OperationIDKey *key = reinterpret_cast<const ComponentDepsNode::OperationIDKey *>(key_v); return hash_combine(BLI_ghashutil_uinthash(key->opcode), - BLI_ghashutil_strhash_p(key->name.c_str())); + BLI_ghashutil_strhash_p(key->name)); } static bool comp_node_hash_key_cmp(const void *a, const void *b) @@ -95,7 +133,7 @@ ComponentDepsNode::ComponentDepsNode() : /* Initialize 'component' node - from pointer data given */ void ComponentDepsNode::init(const ID * /*id*/, - const string & /*subdata*/) + const char * /*subdata*/) { /* hook up eval context? */ // XXX: maybe this needs a special API? @@ -114,7 +152,7 @@ ComponentDepsNode::~ComponentDepsNode() string ComponentDepsNode::identifier() const { - string &idname = this->owner->name; + string idname = this->owner->name; char typebuf[16]; sprintf(typebuf, "(%d)", type); @@ -139,9 +177,11 @@ OperationDepsNode *ComponentDepsNode::find_operation(OperationIDKey key) const } } -OperationDepsNode *ComponentDepsNode::find_operation(eDepsOperation_Code opcode, const string &name) const +OperationDepsNode *ComponentDepsNode::find_operation(eDepsOperation_Code opcode, + const char *name, + int name_tag) const { - OperationIDKey key(opcode, name); + OperationIDKey key(opcode, name, name_tag); return find_operation(key); } @@ -151,21 +191,26 @@ OperationDepsNode *ComponentDepsNode::has_operation(OperationIDKey key) const } OperationDepsNode *ComponentDepsNode::has_operation(eDepsOperation_Code opcode, - const string &name) const + const char *name, + int name_tag) const { - OperationIDKey key(opcode, name); + OperationIDKey key(opcode, name, name_tag); return has_operation(key); } -OperationDepsNode *ComponentDepsNode::add_operation(eDepsOperation_Type optype, DepsEvalOperationCb op, eDepsOperation_Code opcode, const string &name) +OperationDepsNode *ComponentDepsNode::add_operation(eDepsOperation_Type optype, + DepsEvalOperationCb op, + eDepsOperation_Code opcode, + const char *name, + int name_tag) { - OperationDepsNode *op_node = has_operation(opcode, name); + OperationDepsNode *op_node = has_operation(opcode, name, name_tag); if (!op_node) { DepsNodeFactory *factory = deg_get_node_factory(DEPSNODE_TYPE_OPERATION); op_node = (OperationDepsNode *)factory->create_node(this->owner->id, "", name); /* register opnode in this component's operation set */ - OperationIDKey *key = OBJECT_GUARDED_NEW(OperationIDKey, opcode, name); + OperationIDKey *key = OBJECT_GUARDED_NEW(OperationIDKey, opcode, name, name_tag); BLI_ghash_insert(operations_map, key, op_node); /* set as entry/exit node of component (if appropriate) */ @@ -197,16 +242,6 @@ OperationDepsNode *ComponentDepsNode::add_operation(eDepsOperation_Type optype, return op_node; } -void ComponentDepsNode::remove_operation(eDepsOperation_Code opcode, const string &name) -{ - /* unregister */ - OperationIDKey key(opcode, name); - BLI_ghash_remove(operations_map, - &key, - comp_node_hash_key_free, - comp_node_hash_key_free); -} - void ComponentDepsNode::clear_operations() { if (operations_map != NULL) { @@ -337,7 +372,7 @@ static DepsNodeFactoryImpl<PoseComponentDepsNode> DNTI_EVAL_POSE; /* Bone Component ========================================= */ /* Initialize 'bone component' node - from pointer data given */ -void BoneComponentDepsNode::init(const ID *id, const string &subdata) +void BoneComponentDepsNode::init(const ID *id, const char *subdata) { /* generic component-node... */ ComponentDepsNode::init(id, subdata); @@ -350,7 +385,7 @@ void BoneComponentDepsNode::init(const ID *id, const string &subdata) /* bone-specific node data */ Object *ob = (Object *)id; - this->pchan = BKE_pose_channel_find_name(ob->pose, subdata.c_str()); + this->pchan = BKE_pose_channel_find_name(ob->pose, subdata); } DEG_DEPSNODE_DEFINE(BoneComponentDepsNode, DEPSNODE_TYPE_BONE, "Bone Component"); diff --git a/source/blender/depsgraph/intern/nodes/deg_node_component.h b/source/blender/depsgraph/intern/nodes/deg_node_component.h index 7dec8eaaa90..969771a29c9 100644 --- a/source/blender/depsgraph/intern/nodes/deg_node_component.h +++ b/source/blender/depsgraph/intern/nodes/deg_node_component.h @@ -53,50 +53,38 @@ struct ComponentDepsNode : public DepsNode { struct OperationIDKey { eDepsOperation_Code opcode; - string name; - - - OperationIDKey() : - opcode(DEG_OPCODE_OPERATION), name("") - {} - OperationIDKey(eDepsOperation_Code opcode) : - opcode(opcode), name("") - {} - OperationIDKey(eDepsOperation_Code opcode, const string &name) : - opcode(opcode), name(name) - {} - - string identifier() const - { - char codebuf[5]; - BLI_snprintf(codebuf, sizeof(codebuf), "%d", opcode); - - return string("OperationIDKey(") + codebuf + ", " + name + ")"; - } - - bool operator==(const OperationIDKey &other) const - { - return (opcode == other.opcode) && (name == other.name); - } + const char *name; + int name_tag; + + OperationIDKey(); + OperationIDKey(eDepsOperation_Code opcode); + OperationIDKey(eDepsOperation_Code opcode, + const char *name, + int name_tag); + + string identifier() const; + bool operator==(const OperationIDKey &other) const; }; /* Typedef for container of operations */ ComponentDepsNode(); ~ComponentDepsNode(); - void init(const ID *id, const string &subdata); + void init(const ID *id, const char *subdata); string identifier() const; /* Find an existing operation, will throw an assert() if it does not exist. */ OperationDepsNode *find_operation(OperationIDKey key) const; OperationDepsNode *find_operation(eDepsOperation_Code opcode, - const string &name) const; + const char *name, + int name_tag) const; /* Check operation exists and return it. */ OperationDepsNode *has_operation(OperationIDKey key) const; OperationDepsNode *has_operation(eDepsOperation_Code opcode, - const string &name) const; + const char *name, + int name_tag) const; /** * Create a new node for representing an operation and add this to graph @@ -114,9 +102,9 @@ struct ComponentDepsNode : public DepsNode { OperationDepsNode *add_operation(eDepsOperation_Type optype, DepsEvalOperationCb op, eDepsOperation_Code opcode, - const string &name); + const char *name, + int name_tag); - void remove_operation(eDepsOperation_Code opcode, const string &name); void clear_operations(); void tag_update(Depsgraph *graph); @@ -194,7 +182,7 @@ struct PoseComponentDepsNode : public ComponentDepsNode { /* Bone Component */ struct BoneComponentDepsNode : public ComponentDepsNode { - void init(const ID *id, const string &subdata); + void init(const ID *id, const char *subdata); struct bPoseChannel *pchan; /* the bone that this component represents */ diff --git a/source/blender/depsgraph/intern/nodes/deg_node_operation.cc b/source/blender/depsgraph/intern/nodes/deg_node_operation.cc index 5847af29ac2..9eed4dfe8d8 100644 --- a/source/blender/depsgraph/intern/nodes/deg_node_operation.cc +++ b/source/blender/depsgraph/intern/nodes/deg_node_operation.cc @@ -68,7 +68,7 @@ string OperationDepsNode::full_identifier() const { string owner_str = ""; if (owner->type == DEPSNODE_TYPE_BONE) { - owner_str = owner->owner->name + "." + owner->name; + owner_str = string(owner->owner->name) + "." + owner->name; } else { owner_str = owner->owner->name; diff --git a/source/blender/editors/animation/anim_ops.c b/source/blender/editors/animation/anim_ops.c index 262ce0b9e23..c0d6963acbb 100644 --- a/source/blender/editors/animation/anim_ops.c +++ b/source/blender/editors/animation/anim_ops.c @@ -57,6 +57,7 @@ #include "ED_anim_api.h" #include "ED_screen.h" #include "ED_sequencer.h" +#include "ED_util.h" #include "anim_intern.h" @@ -263,7 +264,8 @@ static void ANIM_OT_change_frame(wmOperatorType *ot) ot->poll = change_frame_poll; /* flags */ - ot->flag = OPTYPE_BLOCKING | OPTYPE_UNDO | OPTYPE_GRAB_CURSOR; + ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR | OPTYPE_UNDO_GROUPED; + ot->undo_group = "FRAME_CHANGE"; /* rna */ ot->prop = RNA_def_int(ot->srna, "frame", 0, MINAFRAME, MAXFRAME, "Frame", "", MINAFRAME, MAXFRAME); diff --git a/source/blender/editors/armature/armature_edit.c b/source/blender/editors/armature/armature_edit.c index ece0f18e96e..47e73f9b777 100644 --- a/source/blender/editors/armature/armature_edit.c +++ b/source/blender/editors/armature/armature_edit.c @@ -1328,6 +1328,7 @@ static int armature_delete_selected_exec(bContext *C, wmOperator *UNUSED(op)) return OPERATOR_CANCELLED; ED_armature_sync_selection(arm->edbo); + BKE_pose_tag_recalc(CTX_data_main(C), obedit->pose); WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, obedit); diff --git a/source/blender/editors/armature/pose_edit.c b/source/blender/editors/armature/pose_edit.c index 5015829f868..322476dcca0 100644 --- a/source/blender/editors/armature/pose_edit.c +++ b/source/blender/editors/armature/pose_edit.c @@ -626,7 +626,7 @@ void POSE_OT_flip_names(wmOperatorType *ot) /* api callbacks */ ot->exec = pose_flip_names_exec; - ot->poll = ED_operator_posemode; + ot->poll = ED_operator_posemode_local; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; diff --git a/source/blender/editors/armature/pose_slide.c b/source/blender/editors/armature/pose_slide.c index cd0ea23e2d3..8e8345d34c9 100644 --- a/source/blender/editors/armature/pose_slide.c +++ b/source/blender/editors/armature/pose_slide.c @@ -685,6 +685,7 @@ static int pose_slide_modal(bContext *C, wmOperator *op, const wmEvent *event) switch (event->type) { case LEFTMOUSE: /* confirm */ case RETKEY: + case PADENTER: { /* return to normal cursor and header status */ ED_area_headerprint(pso->sa, NULL); diff --git a/source/blender/editors/gpencil/gpencil_data.c b/source/blender/editors/gpencil/gpencil_data.c index 7dcbe2cc24c..ae83e899649 100644 --- a/source/blender/editors/gpencil/gpencil_data.c +++ b/source/blender/editors/gpencil/gpencil_data.c @@ -84,7 +84,8 @@ static int gp_data_add_exec(bContext *C, wmOperator *op) { bGPdata **gpd_ptr = ED_gpencil_data_get_pointers(C, NULL); - + ToolSettings *ts = CTX_data_tool_settings(C); + if (gpd_ptr == NULL) { BKE_report(op->reports, RPT_ERROR, "Nowhere for grease pencil data to go"); return OPERATOR_CANCELLED; @@ -95,6 +96,15 @@ static int gp_data_add_exec(bContext *C, wmOperator *op) id_us_min(&gpd->id); *gpd_ptr = BKE_gpencil_data_addnew(DATA_("GPencil")); + + /* if not exist brushes, create a new set */ + if (ts) { + if (BLI_listbase_is_empty(&ts->gp_brushes)) { + /* create new brushes */ + BKE_gpencil_brush_init_presets(ts); + } + } + } /* notifiers */ @@ -174,7 +184,8 @@ void GPENCIL_OT_data_unlink(wmOperatorType *ot) static int gp_layer_add_exec(bContext *C, wmOperator *op) { bGPdata **gpd_ptr = ED_gpencil_data_get_pointers(C, NULL); - + ToolSettings *ts = CTX_data_tool_settings(C); + /* if there's no existing Grease-Pencil data there, add some */ if (gpd_ptr == NULL) { BKE_report(op->reports, RPT_ERROR, "Nowhere for grease pencil data to go"); @@ -183,6 +194,14 @@ static int gp_layer_add_exec(bContext *C, wmOperator *op) if (*gpd_ptr == NULL) *gpd_ptr = BKE_gpencil_data_addnew(DATA_("GPencil")); + /* if not exist brushes, create a new set */ + if (ts) { + if (BLI_listbase_is_empty(&ts->gp_brushes)) { + /* create new brushes */ + BKE_gpencil_brush_init_presets(ts); + } + } + /* add new layer now */ BKE_gpencil_layer_addnew(*gpd_ptr, DATA_("GP_Layer"), true); diff --git a/source/blender/editors/include/ED_screen.h b/source/blender/editors/include/ED_screen.h index 6a558d1c185..ec09add56b8 100644 --- a/source/blender/editors/include/ED_screen.h +++ b/source/blender/editors/include/ED_screen.h @@ -183,6 +183,7 @@ int ED_operator_uvmap(struct bContext *C); int ED_operator_posemode_exclusive(struct bContext *C); int ED_operator_posemode_context(struct bContext *C); int ED_operator_posemode(struct bContext *C); +int ED_operator_posemode_local(struct bContext *C); int ED_operator_mask(struct bContext *C); diff --git a/source/blender/editors/include/ED_util.h b/source/blender/editors/include/ED_util.h index f5968397f65..a4afa958450 100644 --- a/source/blender/editors/include/ED_util.h +++ b/source/blender/editors/include/ED_util.h @@ -52,6 +52,8 @@ void ED_OT_flush_edits(struct wmOperatorType *ot); /* undo.c */ void ED_undo_push(struct bContext *C, const char *str); void ED_undo_push_op(struct bContext *C, struct wmOperator *op); +void ED_undo_grouped_push(struct bContext *C, const char *str); +void ED_undo_grouped_push_op(struct bContext *C, struct wmOperator *op); void ED_undo_pop_op(struct bContext *C, struct wmOperator *op); void ED_undo_pop(struct bContext *C); void ED_undo_redo(struct bContext *C); diff --git a/source/blender/editors/interface/interface_eyedropper.c b/source/blender/editors/interface/interface_eyedropper.c index 31598a44b09..d7f06b7db13 100644 --- a/source/blender/editors/interface/interface_eyedropper.c +++ b/source/blender/editors/interface/interface_eyedropper.c @@ -1083,6 +1083,15 @@ static int depthdropper_poll(bContext *C) return 1; } } + else { + RegionView3D *rv3d = CTX_wm_region_view3d(C); + if (rv3d && rv3d->persp == RV3D_CAMOB) { + View3D *v3d = CTX_wm_view3d(C); + if (v3d->camera && v3d->camera->data && !ID_IS_LINKED_DATABLOCK(v3d->camera->data)) { + return 1; + } + } + } return 0; } diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c index 863f5e3852c..f3eeadb6604 100644 --- a/source/blender/editors/interface/interface_handlers.c +++ b/source/blender/editors/interface/interface_handlers.c @@ -2219,7 +2219,7 @@ static void ui_but_drop(bContext *C, const wmEvent *event, uiBut *but, uiHandleB /* ******************* copy and paste ******************** */ /* c = copy, v = paste */ -static void ui_but_copy_paste(bContext *C, uiBut *but, uiHandleButtonData *data, char mode) +static void ui_but_copy_paste(bContext *C, uiBut *but, uiHandleButtonData *data, const char mode, const bool copy_array) { int buf_paste_len = 0; const char *buf_paste = ""; @@ -2255,6 +2255,46 @@ static void ui_but_copy_paste(bContext *C, uiBut *but, uiHandleButtonData *data, if (but->poin == NULL && but->rnapoin.data == NULL) { /* pass */ } + else if (copy_array && but->rnapoin.data && but->rnaprop && + ELEM(RNA_property_subtype(but->rnaprop), PROP_COLOR, PROP_TRANSLATION, PROP_DIRECTION, + PROP_VELOCITY, PROP_ACCELERATION, PROP_MATRIX, PROP_EULER, PROP_QUATERNION, PROP_AXISANGLE, + PROP_XYZ, PROP_XYZ_LENGTH, PROP_COLOR_GAMMA, PROP_COORDS)) + { + float values[4]; + int array_length = RNA_property_array_length(&but->rnapoin, but->rnaprop); + + if (mode == 'c') { + char buf_copy[UI_MAX_DRAW_STR]; + + if (array_length == 4) { + values[3] = RNA_property_float_get_index(&but->rnapoin, but->rnaprop, 3); + } + else { + values[3] = 0.0f; + } + ui_but_v3_get(but, values); + + BLI_snprintf(buf_copy, sizeof(buf_copy), "[%f, %f, %f, %f]", values[0], values[1], values[2], values[3]); + WM_clipboard_text_set(buf_copy, 0); + } + else { + if (sscanf(buf_paste, "[%f, %f, %f, %f]", &values[0], &values[1], &values[2], &values[3]) >= array_length) { + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); + + ui_but_v3_set(but, values); + if (but->rnaprop && array_length == 4) { + RNA_property_float_set_index(&but->rnapoin, but->rnaprop, 3, values[3]); + } + data->value = values[but->rnaindex]; + + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + else { + WM_report(RPT_ERROR, "Paste expected 4 numbers, formatted: '[n, n, n, n]'"); + show_report = true; + } + } + } else if (mode == 'c') { /* Get many decimal places, then strip trailing zeros. * note: too high values start to give strange results */ @@ -6617,15 +6657,22 @@ static void popup_add_shortcut_func(bContext *C, void *arg1, void *UNUSED(arg2)) void ui_panel_menu(bContext *C, ARegion *ar, Panel *pa) { bScreen *sc = CTX_wm_screen(C); + const bool has_panel_category = UI_panel_category_is_visible(ar); + const bool any_item_visible = has_panel_category; PointerRNA ptr; uiPopupMenu *pup; uiLayout *layout; + if (!any_item_visible) { + return; + } + RNA_pointer_create(&sc->id, &RNA_Panel, pa, &ptr); pup = UI_popup_menu_begin(C, IFACE_("Panel"), ICON_NONE); layout = UI_popup_menu_layout(pup); - if (UI_panel_category_is_visible(ar)) { + + if (has_panel_category) { char tmpstr[80]; BLI_snprintf(tmpstr, sizeof(tmpstr), "%s" UI_SEP_CHAR_S "%s", IFACE_("Pin"), IFACE_("Shift+Left Mouse")); uiItemR(layout, &ptr, "use_pin", 0, tmpstr, ICON_NONE); @@ -6636,7 +6683,6 @@ void ui_panel_menu(bContext *C, ARegion *ar, Panel *pa) uiBut *but = block->buttons.last; but->flag |= UI_BUT_HAS_SEP_CHAR; } - } UI_popup_menu_end(C, pup); } @@ -6959,7 +7005,7 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent * if ((data->state == BUTTON_STATE_HIGHLIGHT) || (event->type == EVT_DROP)) { /* handle copy-paste */ if (ELEM(event->type, CKEY, VKEY) && event->val == KM_PRESS && - IS_EVENT_MOD(event, ctrl, oskey) && !event->shift && !event->alt) + IS_EVENT_MOD(event, ctrl, oskey) && !event->shift) { /* Specific handling for listrows, we try to find their overlapping tex button. */ if (but->type == UI_BTYPE_LISTROW) { @@ -6969,7 +7015,7 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent * data = but->active; } } - ui_but_copy_paste(C, but, data, (event->type == CKEY) ? 'c' : 'v'); + ui_but_copy_paste(C, but, data, (event->type == CKEY) ? 'c' : 'v', event->alt); return WM_UI_HANDLER_BREAK; } /* handle drop */ diff --git a/source/blender/editors/interface/interface_icons.c b/source/blender/editors/interface/interface_icons.c index ff9d2840e9c..65b12fcd64e 100644 --- a/source/blender/editors/interface/interface_icons.c +++ b/source/blender/editors/interface/interface_icons.c @@ -213,173 +213,6 @@ static void viconutil_set_point(GLint pt[2], int x, int y) pt[1] = y; } -static void viconutil_draw_tri(GLint(*pts)[2]) -{ - glBegin(GL_TRIANGLES); - glVertex2iv(pts[0]); - glVertex2iv(pts[1]); - glVertex2iv(pts[2]); - glEnd(); -} - -static void viconutil_draw_lineloop(GLint(*pts)[2], int numPoints) -{ - int i; - - glBegin(GL_LINE_LOOP); - for (i = 0; i < numPoints; i++) { - glVertex2iv(pts[i]); - } - glEnd(); -} - -static void viconutil_draw_lineloop_smooth(GLint(*pts)[2], int numPoints) -{ - glEnable(GL_LINE_SMOOTH); - viconutil_draw_lineloop(pts, numPoints); - glDisable(GL_LINE_SMOOTH); -} - -static void viconutil_draw_points(GLint(*pts)[2], int numPoints, int pointSize) -{ - int i; - - glBegin(GL_QUADS); - for (i = 0; i < numPoints; i++) { - int x = pts[i][0], y = pts[i][1]; - - glVertex2i(x - pointSize, y - pointSize); - glVertex2i(x + pointSize, y - pointSize); - glVertex2i(x + pointSize, y + pointSize); - glVertex2i(x - pointSize, y + pointSize); - } - glEnd(); -} - -/* Drawing functions */ - -static void vicon_x_draw(int x, int y, int w, int h, float alpha) -{ - x += 3; - y += 3; - w -= 6; - h -= 6; - - glEnable(GL_LINE_SMOOTH); - - glLineWidth(2.5); - - glColor4f(0.0, 0.0, 0.0, alpha); - glBegin(GL_LINES); - glVertex2i(x, y); - glVertex2i(x + w, y + h); - glVertex2i(x + w, y); - glVertex2i(x, y + h); - glEnd(); - - glDisable(GL_LINE_SMOOTH); -} - -static void vicon_view3d_draw(int x, int y, int w, int h, float alpha) -{ - int cx = x + w / 2; - int cy = y + h / 2; - int d = MAX2(2, h / 3); - - glColor4f(0.5, 0.5, 0.5, alpha); - glBegin(GL_LINES); - glVertex2i(x, cy - d); - glVertex2i(x + w, cy - d); - glVertex2i(x, cy + d); - glVertex2i(x + w, cy + d); - - glVertex2i(cx - d, y); - glVertex2i(cx - d, y + h); - glVertex2i(cx + d, y); - glVertex2i(cx + d, y + h); - glEnd(); - - glColor4f(0.0, 0.0, 0.0, alpha); - glBegin(GL_LINES); - glVertex2i(x, cy); - glVertex2i(x + w, cy); - glVertex2i(cx, y); - glVertex2i(cx, y + h); - glEnd(); -} - -static void vicon_edit_draw(int x, int y, int w, int h, float alpha) -{ - GLint pts[4][2]; - - viconutil_set_point(pts[0], x + 3, y + 3); - viconutil_set_point(pts[1], x + w - 3, y + 3); - viconutil_set_point(pts[2], x + w - 3, y + h - 3); - viconutil_set_point(pts[3], x + 3, y + h - 3); - - glColor4f(0.0, 0.0, 0.0, alpha); - viconutil_draw_lineloop(pts, 4); - - glColor3f(1, 1, 0.0); - viconutil_draw_points(pts, 4, 1); -} - -static void vicon_editmode_hlt_draw(int x, int y, int w, int h, float alpha) -{ - GLint pts[3][2]; - - viconutil_set_point(pts[0], x + w / 2, y + h - 2); - viconutil_set_point(pts[1], x + 3, y + 4); - viconutil_set_point(pts[2], x + w - 3, y + 4); - - glColor4f(0.5, 0.5, 0.5, alpha); - viconutil_draw_tri(pts); - - glColor4f(0.0, 0.0, 0.0, 1); - viconutil_draw_lineloop_smooth(pts, 3); - - glColor3f(1, 1, 0.0); - viconutil_draw_points(pts, 3, 1); -} - -static void vicon_editmode_dehlt_draw(int x, int y, int w, int h, float UNUSED(alpha)) -{ - GLint pts[3][2]; - - viconutil_set_point(pts[0], x + w / 2, y + h - 2); - viconutil_set_point(pts[1], x + 3, y + 4); - viconutil_set_point(pts[2], x + w - 3, y + 4); - - glColor4f(0.0f, 0.0f, 0.0f, 1); - viconutil_draw_lineloop_smooth(pts, 3); - - glColor3f(0.9f, 0.9f, 0.9f); - viconutil_draw_points(pts, 3, 1); -} - -static void vicon_disclosure_tri_right_draw(int x, int y, int w, int UNUSED(h), float alpha) -{ - GLint pts[3][2]; - int cx = x + w / 2; - int cy = y + w / 2; - int d = w / 3, d2 = w / 5; - - viconutil_set_point(pts[0], cx - d2, cy + d); - viconutil_set_point(pts[1], cx - d2, cy - d); - viconutil_set_point(pts[2], cx + d2, cy); - - glBegin(GL_TRIANGLES); - glColor4f(0.8f, 0.8f, 0.8f, alpha); - glVertex2iv(pts[0]); - glVertex2iv(pts[1]); - glColor4f(0.3f, 0.3f, 0.3f, alpha); - glVertex2iv(pts[2]); - glEnd(); - - glColor4f(0.0f, 0.0f, 0.0f, 1); - viconutil_draw_lineloop_smooth(pts, 3); -} - static void vicon_small_tri_right_draw(int x, int y, int w, int UNUSED(h), float alpha) { GLint pts[3][2]; @@ -400,63 +233,6 @@ static void vicon_small_tri_right_draw(int x, int y, int w, int UNUSED(h), float glEnd(); } -static void vicon_disclosure_tri_down_draw(int x, int y, int w, int UNUSED(h), float alpha) -{ - GLint pts[3][2]; - int cx = x + w / 2; - int cy = y + w / 2; - int d = w / 3, d2 = w / 5; - - viconutil_set_point(pts[0], cx + d, cy + d2); - viconutil_set_point(pts[1], cx - d, cy + d2); - viconutil_set_point(pts[2], cx, cy - d2); - - glBegin(GL_TRIANGLES); - glColor4f(0.8f, 0.8f, 0.8f, alpha); - glVertex2iv(pts[0]); - glVertex2iv(pts[1]); - glColor4f(0.3f, 0.3f, 0.3f, alpha); - glVertex2iv(pts[2]); - glEnd(); - - glColor4f(0.0f, 0.0f, 0.0f, 1); - viconutil_draw_lineloop_smooth(pts, 3); -} - -static void vicon_move_up_draw(int x, int y, int w, int h, float UNUSED(alpha)) -{ - int d = -2; - - glEnable(GL_LINE_SMOOTH); - glLineWidth(1); - glColor3f(0.0, 0.0, 0.0); - - glBegin(GL_LINE_STRIP); - glVertex2i(x + w / 2 - d * 2, y + h / 2 + d); - glVertex2i(x + w / 2, y + h / 2 - d + 1); - glVertex2i(x + w / 2 + d * 2, y + h / 2 + d); - glEnd(); - - glDisable(GL_LINE_SMOOTH); -} - -static void vicon_move_down_draw(int x, int y, int w, int h, float UNUSED(alpha)) -{ - int d = 2; - - glEnable(GL_LINE_SMOOTH); - glLineWidth(1); - glColor3f(0.0, 0.0, 0.0); - - glBegin(GL_LINE_STRIP); - glVertex2i(x + w / 2 - d * 2, y + h / 2 + d); - glVertex2i(x + w / 2, y + h / 2 - d - 1); - glVertex2i(x + w / 2 + d * 2, y + h / 2 + d); - glEnd(); - - glDisable(GL_LINE_SMOOTH); -} - static void vicon_keytype_draw_wrapper(int x, int y, int w, int h, float alpha, short key_type) { /* init dummy theme state for Action Editor - where these colors are defined @@ -782,15 +558,6 @@ static void init_internal_icons(void) } } - def_internal_vicon(VICO_VIEW3D_VEC, vicon_view3d_draw); - def_internal_vicon(VICO_EDIT_VEC, vicon_edit_draw); - def_internal_vicon(VICO_EDITMODE_VEC_DEHLT, vicon_editmode_dehlt_draw); - def_internal_vicon(VICO_EDITMODE_VEC_HLT, vicon_editmode_hlt_draw); - def_internal_vicon(VICO_DISCLOSURE_TRI_RIGHT_VEC, vicon_disclosure_tri_right_draw); - def_internal_vicon(VICO_DISCLOSURE_TRI_DOWN_VEC, vicon_disclosure_tri_down_draw); - def_internal_vicon(VICO_MOVE_UP_VEC, vicon_move_up_draw); - def_internal_vicon(VICO_MOVE_DOWN_VEC, vicon_move_down_draw); - def_internal_vicon(VICO_X_VEC, vicon_x_draw); def_internal_vicon(VICO_SMALL_TRI_RIGHT_VEC, vicon_small_tri_right_draw); def_internal_vicon(VICO_KEYTYPE_KEYFRAME_VEC, vicon_keytype_keyframe_draw); diff --git a/source/blender/editors/interface/resources.c b/source/blender/editors/interface/resources.c index 5e24dc96255..539284030c2 100644 --- a/source/blender/editors/interface/resources.c +++ b/source/blender/editors/interface/resources.c @@ -2750,17 +2750,21 @@ void init_userdef_do_versions(void) } } + if (!USER_VERSION_ATLEAST(278, 3)) { + for (bTheme *btheme = U.themes.first; btheme; btheme = btheme->next) { + /* Keyframe Indicators (were using wrong alpha) */ + btheme->tv3d.time_keyframe[3] = btheme->tv3d.time_gp_keyframe[3] = 255; + btheme->ttime.time_keyframe[3] = btheme->ttime.time_gp_keyframe[3] = 255; + } + } + /** * Include next version bump. * * (keep this block even if it becomes empty). */ { - for (bTheme *btheme = U.themes.first; btheme; btheme = btheme->next) { - /* Keyframe Indicators (were using wrong alpha) */ - btheme->tv3d.time_keyframe[3] = btheme->tv3d.time_gp_keyframe[3] = 255; - btheme->ttime.time_keyframe[3] = btheme->ttime.time_gp_keyframe[3] = 255; - } + } if (U.pixelsize == 0.0f) diff --git a/source/blender/editors/io/io_collada.c b/source/blender/editors/io/io_collada.c index 8659100df87..baae92f962e 100644 --- a/source/blender/editors/io/io_collada.c +++ b/source/blender/editors/io/io_collada.c @@ -305,7 +305,6 @@ void WM_OT_collada_export(wmOperatorType *ot) static EnumPropertyItem prop_bc_export_transformation_type[] = { {BC_TRANSFORMATION_TYPE_MATRIX, "matrix", 0, "Matrix", "Use <matrix> to specify transformations"}, {BC_TRANSFORMATION_TYPE_TRANSROTLOC, "transrotloc", 0, "TransRotLoc", "Use <translate>, <rotate>, <scale> to specify transformations"}, - {BC_TRANSFORMATION_TYPE_BOTH, "both", 0, "Both", "Use <matrix> AND <translate>, <rotate>, <scale> to specify transformations"}, {0, NULL, 0, NULL, NULL} }; diff --git a/source/blender/editors/mesh/editmesh_rip.c b/source/blender/editors/mesh/editmesh_rip.c index e31e4096ded..e05ce727e22 100644 --- a/source/blender/editors/mesh/editmesh_rip.c +++ b/source/blender/editors/mesh/editmesh_rip.c @@ -496,7 +496,7 @@ static void edbm_tagged_loop_pairs_do_fill_faces(BMesh *bm, UnorderedLoopPair *u } /* face should never exist */ - BLI_assert(BM_face_exists(f_verts, f_verts[3] ? 4 : 3, &f) == false); + BLI_assert(!BM_face_exists(f_verts, f_verts[3] ? 4 : 3)); f = BM_face_create_verts(bm, f_verts, f_verts[3] ? 4 : 3, f_example, BM_CREATE_NOP, true); diff --git a/source/blender/editors/mesh/editmesh_tools.c b/source/blender/editors/mesh/editmesh_tools.c index 7e31deba2c7..c57b0215d46 100644 --- a/source/blender/editors/mesh/editmesh_tools.c +++ b/source/blender/editors/mesh/editmesh_tools.c @@ -1561,6 +1561,18 @@ static int edbm_edge_rotate_selected_exec(bContext *C, wmOperator *op) /* edges may rotate into hidden vertices, if this does _not_ run we get an ilogical state */ BMO_slot_buffer_hflag_disable(em->bm, bmop.slots_out, "edges.out", BM_EDGE, BM_ELEM_HIDDEN, true); BMO_slot_buffer_hflag_enable(em->bm, bmop.slots_out, "edges.out", BM_EDGE, BM_ELEM_SELECT, true); + + const int tot_rotate = BMO_slot_buffer_count(bmop.slots_out, "edges.out"); + const int tot_failed = tot - tot_rotate; + if (tot_failed != 0) { + /* If some edges fail to rotate, we need to re-select them, + * otherwise we can end up with invalid selection + * (unselected edge between 2 selected faces). */ + BM_mesh_elem_hflag_enable_test(em->bm, BM_EDGE, BM_ELEM_SELECT, true, false, BM_ELEM_TAG); + + BKE_reportf(op->reports, RPT_WARNING, "Unable to rotate %d edge(s)", tot_failed); + } + EDBM_selectmode_flush(em); if (!EDBM_op_finish(em, &bmop, op, true)) { diff --git a/source/blender/editors/mesh/editmesh_utils.c b/source/blender/editors/mesh/editmesh_utils.c index 4fc61e0912e..438c3acdb11 100644 --- a/source/blender/editors/mesh/editmesh_utils.c +++ b/source/blender/editors/mesh/editmesh_utils.c @@ -1139,7 +1139,6 @@ BMEdge *EDBM_verts_mirror_get_edge(BMEditMesh *em, BMEdge *e) BMFace *EDBM_verts_mirror_get_face(BMEditMesh *em, BMFace *f) { - BMFace *f_mirr = NULL; BMVert **v_mirr_arr = BLI_array_alloca(v_mirr_arr, f->len); BMLoop *l_iter, *l_first; @@ -1152,8 +1151,7 @@ BMFace *EDBM_verts_mirror_get_face(BMEditMesh *em, BMFace *f) } } while ((l_iter = l_iter->next) != l_first); - BM_face_exists(v_mirr_arr, f->len, &f_mirr); - return f_mirr; + return BM_face_exists(v_mirr_arr, f->len); } void EDBM_verts_mirror_cache_clear(BMEditMesh *em, BMVert *v) diff --git a/source/blender/editors/object/object_add.c b/source/blender/editors/object/object_add.c index 9f91feee4c6..8e64cdc9751 100644 --- a/source/blender/editors/object/object_add.c +++ b/source/blender/editors/object/object_add.c @@ -1307,7 +1307,9 @@ static void make_object_duplilist_real(bContext *C, Scene *scene, Base *base, if (use_hierarchy || use_base_parent) { dupli_gh = BLI_ghash_ptr_new(__func__); - parent_gh = BLI_ghash_new(dupliobject_hash, dupliobject_cmp, __func__); + if (use_hierarchy) { + parent_gh = BLI_ghash_new(dupliobject_hash, dupliobject_cmp, __func__); + } } for (dob = lb->first; dob; dob = dob->next) { @@ -1344,10 +1346,17 @@ static void make_object_duplilist_real(bContext *C, Scene *scene, Base *base, copy_m4_m4(ob->obmat, dob->mat); BKE_object_apply_mat4(ob, ob->obmat, false, false); - if (dupli_gh) + if (dupli_gh) { BLI_ghash_insert(dupli_gh, dob, ob); - if (parent_gh) - BLI_ghash_insert(parent_gh, dob, ob); + } + if (parent_gh) { + void **val; + /* Due to nature of hash/comparison of this ghash, a lot of duplis may be considered as 'the same', + * this avoids trying to insert same key several time and raise asserts in debug builds... */ + if (!BLI_ghash_ensure_p(parent_gh, dob, &val)) { + *val = ob; + } + } DAG_id_tag_update(&ob->id, OB_RECALC_DATA); } diff --git a/source/blender/editors/object/object_relations.c b/source/blender/editors/object/object_relations.c index d1232fd2aab..f448e925dd9 100644 --- a/source/blender/editors/object/object_relations.c +++ b/source/blender/editors/object/object_relations.c @@ -1740,10 +1740,16 @@ static void single_object_users(Main *bmain, Scene *scene, View3D *v3d, const in clear_sca_new_poins(); /* sensor/contr/act */ - /* newid may still have some trash from Outliner tree building, - * so clear that first to avoid errors [#26002] */ - for (ob = bmain->object.first; ob; ob = ob->id.next) - ob->id.newid = NULL; + /* newid may still have some trash from Outliner tree building, so clear that first to avoid errors, see T26002. + * We have to clear whole datablocks, not only Object one may be accessed here, see T49905. */ + ListBase *lbarray[MAX_LIBARRAY]; + int a = set_listbasepointers(bmain, lbarray); + while (a--) { + ListBase *lb = lbarray[a]; + for (ID *id = lb->first; id; id = id->next) { + id->newid = NULL; + } + } /* duplicate (must set newid) */ for (base = FIRSTBASE; base; base = base->next) { @@ -2235,7 +2241,7 @@ static int make_local_exec(bContext *C, wmOperator *op) "Orphan library objects added to the current scene to avoid loss"); } - BKE_library_make_local(bmain, NULL, false, false); /* NULL is all libs */ + BKE_library_make_local(bmain, NULL, NULL, false, false); /* NULL is all libs */ WM_event_add_notifier(C, NC_WINDOW, NULL); return OPERATOR_FINISHED; } diff --git a/source/blender/editors/object/object_vgroup.c b/source/blender/editors/object/object_vgroup.c index bd016b7fcfb..56f59dca9a1 100644 --- a/source/blender/editors/object/object_vgroup.c +++ b/source/blender/editors/object/object_vgroup.c @@ -886,7 +886,7 @@ static float get_vert_def_nr(Object *ob, const int def_nr, const int vertnum) const int cd_dvert_offset = CustomData_get_offset(&em->bm->vdata, CD_MDEFORMVERT); /* warning, this lookup is _not_ fast */ - if (cd_dvert_offset != -1) { + if (cd_dvert_offset != -1 && vertnum < em->bm->totvert) { BMVert *eve; BM_mesh_elem_table_ensure(em->bm, BM_VERT); eve = BM_vert_at_index(em->bm, vertnum); @@ -2604,6 +2604,8 @@ static int vertex_group_remove_exec(bContext *C, wmOperator *op) if (RNA_boolean_get(op->ptr, "all")) BKE_object_defgroup_remove_all(ob); + else if (RNA_boolean_get(op->ptr, "all_unlocked")) + BKE_object_defgroup_remove_all_ex(ob, true); else vgroup_delete_active(ob); @@ -2633,6 +2635,7 @@ void OBJECT_OT_vertex_group_remove(wmOperatorType *ot) /* properties */ RNA_def_boolean(ot->srna, "all", 0, "All", "Remove all vertex groups"); + RNA_def_boolean(ot->srna, "all_unlocked", 0, "All Unlocked", "Remove all unlocked vertex groups"); } static int vertex_group_assign_exec(bContext *C, wmOperator *UNUSED(op)) diff --git a/source/blender/editors/render/render_opengl.c b/source/blender/editors/render/render_opengl.c index 9d9ccf2f3ba..16842efb436 100644 --- a/source/blender/editors/render/render_opengl.c +++ b/source/blender/editors/render/render_opengl.c @@ -315,6 +315,12 @@ static void screen_opengl_render_doit(OGLRender *oglrender, RenderResult *rr) RE_render_result_rect_from_ibuf(rr, &scene->r, out, oglrender->view_id); IMB_freeImBuf(out); } + else if (gpd){ + /* If there are no strips, Grease Pencil still needs a buffer to draw on */ + ImBuf *out = IMB_allocImBuf(oglrender->sizex, oglrender->sizey, 32, IB_rect); + RE_render_result_rect_from_ibuf(rr, &scene->r, out, oglrender->view_id); + IMB_freeImBuf(out); + } if (gpd) { int i; @@ -479,23 +485,24 @@ static void add_gpencil_renderpass(OGLRender *oglrender, RenderResult *rr, Rende /* copy image data from rectf */ // XXX: Needs conversion. unsigned char *src = (unsigned char *)RE_RenderViewGetById(rr, oglrender->view_id)->rect32; - float *dest = rp->rect; - - int x, y, rectx, recty; - rectx = rr->rectx; - recty = rr->recty; - for (y = 0; y < recty; y++) { - for (x = 0; x < rectx; x++) { - unsigned char *pixSrc = src + 4 * (rectx * y + x); - if (pixSrc[3] > 0) { - float *pixDest = dest + 4 * (rectx * y + x); - float float_src[4]; - srgb_to_linearrgb_uchar4(float_src, pixSrc); - addAlphaOverFloat(pixDest, float_src); + if (src != NULL) { + float *dest = rp->rect; + + int x, y, rectx, recty; + rectx = rr->rectx; + recty = rr->recty; + for (y = 0; y < recty; y++) { + for (x = 0; x < rectx; x++) { + unsigned char *pixSrc = src + 4 * (rectx * y + x); + if (pixSrc[3] > 0) { + float *pixDest = dest + 4 * (rectx * y + x); + float float_src[4]; + srgb_to_linearrgb_uchar4(float_src, pixSrc); + addAlphaOverFloat(pixDest, float_src); + } } } } - /* back layer status */ i = 0; for (bGPDlayer *gph = gpd->layers.first; gph; gph = gph->next) { diff --git a/source/blender/editors/screen/screen_ops.c b/source/blender/editors/screen/screen_ops.c index 860a865466a..c69e01422e0 100644 --- a/source/blender/editors/screen/screen_ops.c +++ b/source/blender/editors/screen/screen_ops.c @@ -442,6 +442,17 @@ int ED_operator_posemode(bContext *C) return 0; } +int ED_operator_posemode_local(bContext *C) +{ + if (ED_operator_posemode(C)) { + Object *ob = BKE_object_pose_armature_get(CTX_data_active_object(C)); + bArmature *arm = ob->data; + return !(ID_IS_LINKED_DATABLOCK(&ob->id) || + ID_IS_LINKED_DATABLOCK(&arm->id)); + } + return false; +} + /* wrapper for ED_space_image_show_uvedit */ int ED_operator_uvedit(bContext *C) { @@ -2136,7 +2147,8 @@ static void SCREEN_OT_frame_offset(wmOperatorType *ot) ot->exec = frame_offset_exec; ot->poll = ED_operator_screenactive_norender; - ot->flag = 0; + ot->flag = OPTYPE_UNDO_GROUPED; + ot->undo_group = "FRAME_CHANGE"; /* rna */ RNA_def_int(ot->srna, "delta", 0, INT_MIN, INT_MAX, "Delta", "", INT_MIN, INT_MAX); @@ -2189,7 +2201,8 @@ static void SCREEN_OT_frame_jump(wmOperatorType *ot) ot->exec = frame_jump_exec; ot->poll = ED_operator_screenactive_norender; - ot->flag = OPTYPE_UNDO; + ot->flag = OPTYPE_UNDO_GROUPED; + ot->undo_group = "FRAME_CHANGE"; /* rna */ RNA_def_boolean(ot->srna, "end", 0, "Last Frame", "Jump to the last frame of the frame range"); @@ -2295,7 +2308,8 @@ static void SCREEN_OT_keyframe_jump(wmOperatorType *ot) ot->exec = keyframe_jump_exec; ot->poll = ED_operator_screenactive_norender; - ot->flag = OPTYPE_UNDO; + ot->flag = OPTYPE_UNDO_GROUPED; + ot->undo_group = "FRAME_CHANGE"; /* properties */ RNA_def_boolean(ot->srna, "next", true, "Next Keyframe", ""); @@ -2357,7 +2371,8 @@ static void SCREEN_OT_marker_jump(wmOperatorType *ot) ot->exec = marker_jump_exec; ot->poll = ED_operator_screenactive_norender; - ot->flag = OPTYPE_UNDO; + ot->flag = OPTYPE_UNDO_GROUPED; + ot->undo_group = "FRAME_CHANGE"; /* properties */ RNA_def_boolean(ot->srna, "next", true, "Next Marker", ""); diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c index fe0fb3f5035..53434b18d06 100644 --- a/source/blender/editors/sculpt_paint/sculpt.c +++ b/source/blender/editors/sculpt_paint/sculpt.c @@ -4688,7 +4688,7 @@ static void sculpt_stroke_update_step(bContext *C, struct PaintStroke *UNUSED(st sculpt_restore_mesh(sd, ob); if (sd->flags & SCULPT_DYNTOPO_DETAIL_CONSTANT) { - BKE_pbvh_bmesh_detail_size_set(ss->pbvh, sd->constant_detail / 100.0f); + BKE_pbvh_bmesh_detail_size_set(ss->pbvh, 1.0f / sd->constant_detail); } else if (sd->flags & SCULPT_DYNTOPO_DETAIL_BRUSH) { BKE_pbvh_bmesh_detail_size_set(ss->pbvh, ss->cache->radius * sd->detail_percent / 100.0f); @@ -5406,7 +5406,7 @@ static int sculpt_mode_toggle_exec(bContext *C, wmOperator *op) if (!ts->sculpt->detail_percent) ts->sculpt->detail_percent = 25; if (ts->sculpt->constant_detail == 0.0f) - ts->sculpt->constant_detail = 30.0f; + ts->sculpt->constant_detail = 3.0f; /* Set sane default tiling offsets */ if (!ts->sculpt->paint.tile_offset[0]) ts->sculpt->paint.tile_offset[0] = 1.0f; @@ -5543,7 +5543,7 @@ static int sculpt_detail_flood_fill_exec(bContext *C, wmOperator *UNUSED(op)) size = max_fff(bb_max[0], bb_max[1], bb_max[2]); /* update topology size */ - BKE_pbvh_bmesh_detail_size_set(ss->pbvh, sd->constant_detail / 100.0f); + BKE_pbvh_bmesh_detail_size_set(ss->pbvh, 1.0f / sd->constant_detail); sculpt_undo_push_begin("Dynamic topology flood fill"); sculpt_undo_push_node(ob, NULL, SCULPT_UNDO_COORDS); @@ -5608,7 +5608,8 @@ static void sample_detail(bContext *C, int ss_co[2]) ray_start, ray_normal, false); if (srd.hit) { - sd->constant_detail = srd.detail * 100.0f; + /* convert edge length to detail resolution */ + sd->constant_detail = 1.0f / srd.detail; } } diff --git a/source/blender/editors/space_buttons/buttons_texture.c b/source/blender/editors/space_buttons/buttons_texture.c index 58c538c4ee5..72de7e5c81c 100644 --- a/source/blender/editors/space_buttons/buttons_texture.c +++ b/source/blender/editors/space_buttons/buttons_texture.c @@ -587,6 +587,8 @@ static void template_texture_user_menu(bContext *C, uiLayout *layout, void *UNUS last_category = user->category; } + + UI_block_flag_enable(block, UI_BLOCK_NO_FLIP); } void uiTemplateTextureUser(uiLayout *layout, bContext *C) diff --git a/source/blender/editors/space_file/filelist.c b/source/blender/editors/space_file/filelist.c index 6af36ea6778..83469a48165 100644 --- a/source/blender/editors/space_file/filelist.c +++ b/source/blender/editors/space_file/filelist.c @@ -2510,7 +2510,7 @@ static void filelist_readjob_do( * Using an atomic operation to avoid having to lock thread... * Note that we do not really need this here currently, since there is a single listing thread, but better * remain consistent about threading! */ - *((uint32_t *)entry->uuid) = atomic_add_uint32((uint32_t *)filelist->filelist_intern.curr_uuid, 1); + *((uint32_t *)entry->uuid) = atomic_add_and_fetch_uint32((uint32_t *)filelist->filelist_intern.curr_uuid, 1); /* Only thing we change in direntry here, so we need to free it first. */ MEM_freeN(entry->relpath); diff --git a/source/blender/editors/space_view3d/drawobject.c b/source/blender/editors/space_view3d/drawobject.c index ea40d4eb5e1..dd282c427f6 100644 --- a/source/blender/editors/space_view3d/drawobject.c +++ b/source/blender/editors/space_view3d/drawobject.c @@ -7915,10 +7915,6 @@ void draw_object(Scene *scene, ARegion *ar, View3D *v3d, Base *base, const short if (!render_override && sds->draw_velocity) { draw_smoke_velocity(sds, viewnormal); } - -#ifdef SMOKE_DEBUG_HEAT - draw_smoke_heat(smd->domain, ob); -#endif } } diff --git a/source/blender/editors/space_view3d/drawvolume.c b/source/blender/editors/space_view3d/drawvolume.c index b0e21601b9c..27ecbf83db5 100644 --- a/source/blender/editors/space_view3d/drawvolume.c +++ b/source/blender/editors/space_view3d/drawvolume.c @@ -1,4 +1,4 @@ -/* +/* * ***** BEGIN GPL LICENSE BLOCK ***** * * This program is free software; you can redistribute it and/or @@ -41,6 +41,7 @@ #include "BLI_math.h" #include "BKE_DerivedMesh.h" +#include "BKE_texture.h" #include "BKE_particle.h" #include "smoke_API.h" @@ -62,28 +63,33 @@ struct GPUTexture; # include "PIL_time_utildefines.h" #endif -static GPUTexture *create_flame_spectrum_texture(void) +/* *************************** Transfer functions *************************** */ + +enum { + TFUNC_FLAME_SPECTRUM = 0, + TFUNC_COLOR_RAMP = 1, +}; + +#define TFUNC_WIDTH 256 + +static void create_flame_spectrum_texture(float *data) { -#define SPEC_WIDTH 256 #define FIRE_THRESH 7 #define MAX_FIRE_ALPHA 0.06f #define FULL_ON_FIRE 100 - GPUTexture *tex; - int i, j, k; - float *spec_data = MEM_mallocN(SPEC_WIDTH * 4 * sizeof(float), "spec_data"); - float *spec_pixels = MEM_mallocN(SPEC_WIDTH * 4 * 16 * 16 * sizeof(float), "spec_pixels"); + float *spec_pixels = MEM_mallocN(TFUNC_WIDTH * 4 * 16 * 16 * sizeof(float), "spec_pixels"); - blackbody_temperature_to_rgb_table(spec_data, SPEC_WIDTH, 1500, 3000); + blackbody_temperature_to_rgb_table(data, TFUNC_WIDTH, 1500, 3000); - for (i = 0; i < 16; i++) { - for (j = 0; j < 16; j++) { - for (k = 0; k < SPEC_WIDTH; k++) { - int index = (j * SPEC_WIDTH * 16 + i * SPEC_WIDTH + k) * 4; + for (int i = 0; i < 16; i++) { + for (int j = 0; j < 16; j++) { + for (int k = 0; k < TFUNC_WIDTH; k++) { + int index = (j * TFUNC_WIDTH * 16 + i * TFUNC_WIDTH + k) * 4; if (k >= FIRE_THRESH) { - spec_pixels[index] = (spec_data[k * 4]); - spec_pixels[index + 1] = (spec_data[k * 4 + 1]); - spec_pixels[index + 2] = (spec_data[k * 4 + 2]); + spec_pixels[index] = (data[k * 4]); + spec_pixels[index + 1] = (data[k * 4 + 1]); + spec_pixels[index + 2] = (data[k * 4 + 2]); spec_pixels[index + 3] = MAX_FIRE_ALPHA * ( (k > FULL_ON_FIRE) ? 1.0f : (k - FIRE_THRESH) / ((float)FULL_ON_FIRE - FIRE_THRESH)); } @@ -94,19 +100,69 @@ static GPUTexture *create_flame_spectrum_texture(void) } } - tex = GPU_texture_create_1D(SPEC_WIDTH, spec_pixels, NULL); + memcpy(data, spec_pixels, sizeof(float) * 4 * TFUNC_WIDTH); - MEM_freeN(spec_data); MEM_freeN(spec_pixels); -#undef SPEC_WIDTH #undef FIRE_THRESH #undef MAX_FIRE_ALPHA #undef FULL_ON_FIRE +} + +static void create_color_ramp(const ColorBand *coba, float *data) +{ + for (int i = 0; i < TFUNC_WIDTH; i++) { + do_colorband(coba, (float)i / TFUNC_WIDTH, &data[i * 4]); + } +} + +static GPUTexture *create_transfer_function(int type, const ColorBand *coba) +{ + float *data = MEM_mallocN(sizeof(float) * 4 * TFUNC_WIDTH, __func__); + + switch (type) { + case TFUNC_FLAME_SPECTRUM: + create_flame_spectrum_texture(data); + break; + case TFUNC_COLOR_RAMP: + create_color_ramp(coba, data); + break; + } + + GPUTexture *tex = GPU_texture_create_1D(TFUNC_WIDTH, data, NULL); + + MEM_freeN(data); return tex; } +static GPUTexture *create_field_texture(SmokeDomainSettings *sds) +{ + float *field = NULL; + + switch (sds->coba_field) { +#ifdef WITH_SMOKE + case FLUID_FIELD_DENSITY: field = smoke_get_density(sds->fluid); break; + case FLUID_FIELD_HEAT: field = smoke_get_heat(sds->fluid); break; + case FLUID_FIELD_FUEL: field = smoke_get_fuel(sds->fluid); break; + case FLUID_FIELD_REACT: field = smoke_get_react(sds->fluid); break; + case FLUID_FIELD_FLAME: field = smoke_get_flame(sds->fluid); break; + case FLUID_FIELD_VELOCITY_X: field = smoke_get_velocity_x(sds->fluid); break; + case FLUID_FIELD_VELOCITY_Y: field = smoke_get_velocity_y(sds->fluid); break; + case FLUID_FIELD_VELOCITY_Z: field = smoke_get_velocity_z(sds->fluid); break; + case FLUID_FIELD_COLOR_R: field = smoke_get_color_r(sds->fluid); break; + case FLUID_FIELD_COLOR_G: field = smoke_get_color_g(sds->fluid); break; + case FLUID_FIELD_COLOR_B: field = smoke_get_color_b(sds->fluid); break; + case FLUID_FIELD_FORCE_X: field = smoke_get_force_x(sds->fluid); break; + case FLUID_FIELD_FORCE_Y: field = smoke_get_force_y(sds->fluid); break; + case FLUID_FIELD_FORCE_Z: field = smoke_get_force_z(sds->fluid); break; +#endif + default: return NULL; + } + + return GPU_texture_create_3D(sds->res[0], sds->res[1], sds->res[2], 1, field); +} + typedef struct VolumeSlicer { float size[3]; float min[3]; @@ -347,6 +403,7 @@ static int create_view_aligned_slices(VolumeSlicer *slicer, } static void bind_shader(SmokeDomainSettings *sds, GPUShader *shader, GPUTexture *tex_spec, + GPUTexture *tex_tfunc, GPUTexture *tex_coba, bool use_fire, const float min[3], const float ob_sizei[3], const float invsize[3]) { @@ -359,6 +416,8 @@ static void bind_shader(SmokeDomainSettings *sds, GPUShader *shader, GPUTexture int densityscale_location; int spec_location, flame_location; int shadow_location, actcol_location; + int tfunc_location = 0; + int coba_location = 0; if (use_fire) { spec_location = GPU_shader_get_uniform(shader, "spectrum_texture"); @@ -370,6 +429,11 @@ static void bind_shader(SmokeDomainSettings *sds, GPUShader *shader, GPUTexture soot_location = GPU_shader_get_uniform(shader, "soot_texture"); stepsize_location = GPU_shader_get_uniform(shader, "step_size"); densityscale_location = GPU_shader_get_uniform(shader, "density_scale"); + + if (sds->use_coba) { + tfunc_location = GPU_shader_get_uniform(shader, "transfer_texture"); + coba_location = GPU_shader_get_uniform(shader, "color_band_texture"); + } } GPU_shader_bind(shader); @@ -397,6 +461,14 @@ static void bind_shader(SmokeDomainSettings *sds, GPUShader *shader, GPUTexture if ((sds->active_fields & SM_ACTIVE_COLORS) == 0) mul_v3_v3(active_color, sds->active_color); GPU_shader_uniform_vector(shader, actcol_location, 3, 1, active_color); + + if (sds->use_coba) { + GPU_texture_bind(tex_tfunc, 4); + GPU_shader_uniform_texture(shader, tfunc_location, tex_tfunc); + + GPU_texture_bind(tex_coba, 5); + GPU_shader_uniform_texture(shader, coba_location, tex_coba); + } } GPU_shader_uniform_vector(shader, min_location, 3, 1, min); @@ -404,7 +476,8 @@ static void bind_shader(SmokeDomainSettings *sds, GPUShader *shader, GPUTexture GPU_shader_uniform_vector(shader, invsize_location, 3, 1, invsize); } -static void unbind_shader(SmokeDomainSettings *sds, GPUTexture *tex_spec, bool use_fire) +static void unbind_shader(SmokeDomainSettings *sds, GPUTexture *tex_spec, + GPUTexture *tex_tfunc, GPUTexture *tex_coba, bool use_fire) { GPU_shader_unbind(); @@ -417,20 +490,30 @@ static void unbind_shader(SmokeDomainSettings *sds, GPUTexture *tex_spec, bool u } else { GPU_texture_unbind(sds->tex_shadow); + + if (sds->use_coba) { + GPU_texture_unbind(tex_tfunc); + GPU_texture_free(tex_tfunc); + + GPU_texture_unbind(tex_coba); + GPU_texture_free(tex_coba); + } } } static void draw_buffer(SmokeDomainSettings *sds, GPUShader *shader, const VolumeSlicer *slicer, const float ob_sizei[3], const float invsize[3], const int num_points, const bool do_fire) { - GPUTexture *tex_spec = (do_fire) ? create_flame_spectrum_texture() : NULL; + GPUTexture *tex_spec = (do_fire) ? create_transfer_function(TFUNC_FLAME_SPECTRUM, NULL) : NULL; + GPUTexture *tex_tfunc = (sds->use_coba) ? create_transfer_function(TFUNC_COLOR_RAMP, sds->coba) : NULL; + GPUTexture *tex_coba = (sds->use_coba) ? create_field_texture(sds) : NULL; GLuint vertex_buffer; glGenBuffers(1, &vertex_buffer); glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer); glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 3 * num_points, &slicer->verts[0][0], GL_STATIC_DRAW); - bind_shader(sds, shader, tex_spec, do_fire, slicer->min, ob_sizei, invsize); + bind_shader(sds, shader, tex_spec, tex_tfunc, tex_coba, do_fire, slicer->min, ob_sizei, invsize); glEnableClientState(GL_VERTEX_ARRAY); glVertexPointer(3, GL_FLOAT, 0, NULL); @@ -439,7 +522,7 @@ static void draw_buffer(SmokeDomainSettings *sds, GPUShader *shader, const Volum glDisableClientState(GL_VERTEX_ARRAY); - unbind_shader(sds, tex_spec, do_fire); + unbind_shader(sds, tex_spec, tex_tfunc, tex_coba, do_fire); /* cleanup */ @@ -459,7 +542,16 @@ void draw_smoke_volume(SmokeDomainSettings *sds, Object *ob, const bool use_fire = (sds->active_fields & SM_ACTIVE_FIRE) && sds->tex_flame; - GPUShader *shader = GPU_shader_get_builtin_shader(GPU_SHADER_SMOKE); + GPUBuiltinShader builtin_shader; + + if (sds->use_coba) { + builtin_shader = GPU_SHADER_SMOKE_COBA; + } + else { + builtin_shader = GPU_SHADER_SMOKE; + } + + GPUShader *shader = GPU_shader_get_builtin_shader(builtin_shader); if (!shader) { fprintf(stderr, "Unable to create GLSL smoke shader.\n"); @@ -549,7 +641,7 @@ void draw_smoke_volume(SmokeDomainSettings *sds, Object *ob, draw_buffer(sds, shader, &slicer, ob_sizei, invsize, num_points, false); /* Draw fire separately (T47639). */ - if (use_fire) { + if (use_fire && !sds->use_coba) { glBlendFunc(GL_ONE, GL_ONE); draw_buffer(sds, fire_shader, &slicer, ob_sizei, invsize, num_points, true); } @@ -759,50 +851,3 @@ void draw_smoke_velocity(SmokeDomainSettings *domain, float viewnormal[3]) UNUSED_VARS(domain, viewnormal); #endif } - -#ifdef SMOKE_DEBUG_HEAT -void draw_smoke_heat(SmokeDomainSettings *domain, Object *ob) -{ - float x, y, z; - float x0, y0, z0; - int *base_res = domain->base_res; - int *res = domain->res; - int *res_min = domain->res_min; - int *res_max = domain->res_max; - float *heat = smoke_get_heat(domain->fluid); - - float min[3]; - float *cell_size = domain->cell_size; - float step_size = ((float)max_iii(base_res[0], base_res[1], base_res[2])) / 16.f; - float vf = domain->scale / 16.f * 2.f; /* velocity factor */ - - /* set first position so that it doesn't jump when domain moves */ - x0 = res_min[0] + fmod(-(float)domain->shift[0] + res_min[0], step_size); - y0 = res_min[1] + fmod(-(float)domain->shift[1] + res_min[1], step_size); - z0 = res_min[2] + fmod(-(float)domain->shift[2] + res_min[2], step_size); - if (x0 < res_min[0]) x0 += step_size; - if (y0 < res_min[1]) y0 += step_size; - if (z0 < res_min[2]) z0 += step_size; - add_v3_v3v3(min, domain->p0, domain->obj_shift_f); - - for (x = floor(x0); x < res_max[0]; x += step_size) - for (y = floor(y0); y < res_max[1]; y += step_size) - for (z = floor(z0); z < res_max[2]; z += step_size) { - int index = (floor(x) - res_min[0]) + (floor(y) - res_min[1]) * res[0] + (floor(z) - res_min[2]) * res[0] * res[1]; - - float pos[3] = {min[0] + ((float)x + 0.5f) * cell_size[0], min[1] + ((float)y + 0.5f) * cell_size[1], min[2] + ((float)z + 0.5f) * cell_size[2]}; - - /* draw heat as different sized points */ - if (heat[index] >= 0.01f) { - float col_gb = 1.0f - heat[index]; - CLAMP(col_gb, 0.0f, 1.0f); - glColor3f(1.0f, col_gb, col_gb); - glPointSize(24.0f * heat[index]); - - glBegin(GL_POINTS); - glVertex3f(pos[0], pos[1], pos[2]); - glEnd(); - } - } -} -#endif diff --git a/source/blender/editors/space_view3d/view3d_intern.h b/source/blender/editors/space_view3d/view3d_intern.h index 0e2cb95dd89..b11f42bcfef 100644 --- a/source/blender/editors/space_view3d/view3d_intern.h +++ b/source/blender/editors/space_view3d/view3d_intern.h @@ -296,14 +296,8 @@ void draw_smoke_volume(struct SmokeDomainSettings *sds, struct Object *ob, const float min[3], const float max[3], const float viewnormal[3]); -//#define SMOKE_DEBUG_HEAT - void draw_smoke_velocity(struct SmokeDomainSettings *domain, float viewnormal[3]); -#ifdef SMOKE_DEBUG_HEAT -void draw_smoke_heat(struct SmokeDomainSettings *domain, struct Object *ob); -#endif - /* workaround for trivial but noticeable camera bug caused by imprecision * between view border calculation in 2D/3D space, workaround for bug [#28037]. * without this define we get the old behavior which is to try and align them diff --git a/source/blender/editors/transform/transform.c b/source/blender/editors/transform/transform.c index ef6cff19181..daf0aed59e7 100644 --- a/source/blender/editors/transform/transform.c +++ b/source/blender/editors/transform/transform.c @@ -3392,7 +3392,9 @@ static void ElementResize(TransInfo *t, TransData *td, float mat[3][3]) } protectedTransBits(td->protectflag, vec); - add_v3_v3v3(td->loc, td->iloc, vec); + if (td->loc) { + add_v3_v3v3(td->loc, td->iloc, vec); + } constraintTransLim(t, td); } diff --git a/source/blender/editors/transform/transform_conversions.c b/source/blender/editors/transform/transform_conversions.c index 9c266890d6d..ce3d903b8f6 100644 --- a/source/blender/editors/transform/transform_conversions.c +++ b/source/blender/editors/transform/transform_conversions.c @@ -5583,7 +5583,7 @@ void autokeyframe_ob_cb_func(bContext *C, Scene *scene, View3D *v3d, Object *ob, if (tmode == TFM_TRANSLATION) { do_loc = true; } - else if (tmode == TFM_ROTATION) { + else if (ELEM(tmode, TFM_ROTATION, TFM_TRACKBALL)) { if (v3d->around == V3D_AROUND_ACTIVE) { if (ob != OBACT) do_loc = true; @@ -5728,7 +5728,7 @@ void autokeyframe_pose_cb_func(bContext *C, Scene *scene, View3D *v3d, Object *o else do_loc = true; } - else if (tmode == TFM_ROTATION) { + else if (ELEM(tmode, TFM_ROTATION, TFM_TRACKBALL)) { if (ELEM(v3d->around, V3D_AROUND_CURSOR, V3D_AROUND_ACTIVE)) do_loc = true; diff --git a/source/blender/editors/util/undo.c b/source/blender/editors/util/undo.c index ee6700666c0..4a9311416b3 100644 --- a/source/blender/editors/util/undo.c +++ b/source/blender/editors/util/undo.c @@ -217,6 +217,19 @@ static int ed_undo_step(bContext *C, int step, const char *undoname) return OPERATOR_FINISHED; } +void ED_undo_grouped_push(bContext *C, const char *str) +{ + /* do nothing if previous undo task is the same as this one (or from the same undo group) */ + const char *last_undo = BKE_undo_get_name_last(); + + if (last_undo && STREQ(str, last_undo)) { + return; + } + + /* push as usual */ + ED_undo_push(C, str); +} + void ED_undo_pop(bContext *C) { ed_undo_step(C, 1, NULL); @@ -232,6 +245,16 @@ void ED_undo_push_op(bContext *C, wmOperator *op) ED_undo_push(C, op->type->name); } +void ED_undo_grouped_push_op(bContext *C, wmOperator *op) +{ + if (op->type->undo_group[0] != '\0') { + ED_undo_grouped_push(C, op->type->undo_group); + } + else { + ED_undo_grouped_push(C, op->type->name); + } +} + void ED_undo_pop_op(bContext *C, wmOperator *op) { /* search back a couple of undo's, in case something else added pushes */ diff --git a/source/blender/gpu/GPU_shader.h b/source/blender/gpu/GPU_shader.h index 762329ee077..5b94db6e120 100644 --- a/source/blender/gpu/GPU_shader.h +++ b/source/blender/gpu/GPU_shader.h @@ -89,6 +89,7 @@ typedef enum GPUBuiltinShader { GPU_SHADER_SEP_GAUSSIAN_BLUR = 1, GPU_SHADER_SMOKE = 2, GPU_SHADER_SMOKE_FIRE = 3, + GPU_SHADER_SMOKE_COBA = 4, } GPUBuiltinShader; GPUShader *GPU_shader_get_builtin_shader(GPUBuiltinShader shader); diff --git a/source/blender/gpu/intern/gpu_shader.c b/source/blender/gpu/intern/gpu_shader.c index 5cfb323bc4b..14f2764b009 100644 --- a/source/blender/gpu/intern/gpu_shader.c +++ b/source/blender/gpu/intern/gpu_shader.c @@ -68,6 +68,7 @@ static struct GPUShadersGlobal { GPUShader *sep_gaussian_blur; GPUShader *smoke; GPUShader *smoke_fire; + GPUShader *smoke_coba; /* cache for shader fx. Those can exist in combinations so store them here */ GPUShader *fx_shaders[MAX_FX_SHADERS * 2]; } shaders; @@ -623,6 +624,13 @@ GPUShader *GPU_shader_get_builtin_shader(GPUBuiltinShader shader) NULL, NULL, NULL, 0, 0, 0); retval = GG.shaders.smoke_fire; break; + case GPU_SHADER_SMOKE_COBA: + if (!GG.shaders.smoke_coba) + GG.shaders.smoke_coba = GPU_shader_create( + datatoc_gpu_shader_smoke_vert_glsl, datatoc_gpu_shader_smoke_frag_glsl, + NULL, NULL, "#define USE_COBA;\n", 0, 0, 0); + retval = GG.shaders.smoke_coba; + break; } if (retval == NULL) @@ -734,6 +742,11 @@ void GPU_shader_free_builtin_shaders(void) GG.shaders.smoke_fire = NULL; } + if (GG.shaders.smoke_coba) { + GPU_shader_free(GG.shaders.smoke_coba); + GG.shaders.smoke_coba = NULL; + } + for (i = 0; i < 2 * MAX_FX_SHADERS; ++i) { if (GG.shaders.fx_shaders[i]) { GPU_shader_free(GG.shaders.fx_shaders[i]); diff --git a/source/blender/gpu/shaders/gpu_shader_material.glsl b/source/blender/gpu/shaders/gpu_shader_material.glsl index 67da8201f66..4416b6494f9 100644 --- a/source/blender/gpu/shaders/gpu_shader_material.glsl +++ b/source/blender/gpu/shaders/gpu_shader_material.glsl @@ -2822,7 +2822,7 @@ void node_tex_checker(vec3 co, vec4 color1, vec4 color2, float scale, out vec4 c } #ifdef BIT_OPERATIONS -vec2 calc_brick_texture(vec3 p, float mortar_size, float bias, +vec2 calc_brick_texture(vec3 p, float mortar_size, float mortar_smooth, float bias, float brick_width, float row_height, float offset_amount, int offset_frequency, float squash_amount, int squash_frequency) @@ -2843,17 +2843,26 @@ vec2 calc_brick_texture(vec3 p, float mortar_size, float bias, x = (p.x + offset) - brick_width * bricknum; y = p.y - row_height * rownum; - return vec2(clamp((integer_noise((rownum << 16) + (bricknum & 0xFFFF)) + bias), 0.0, 1.0), - (x < mortar_size || y < mortar_size || - x > (brick_width - mortar_size) || - y > (row_height - mortar_size)) ? 1.0 : 0.0); + float tint = clamp((integer_noise((rownum << 16) + (bricknum & 0xFFFF)) + bias), 0.0, 1.0); + + float min_dist = min(min(x, y), min(brick_width - x, row_height - y)); + if(min_dist >= mortar_size) { + return vec2(tint, 0.0); + } + else if(mortar_smooth == 0.0) { + return vec2(tint, 1.0); + } + else { + min_dist = 1.0 - min_dist/mortar_size; + return vec2(tint, smoothstep(0.0, mortar_smooth, min_dist)); + } } #endif void node_tex_brick(vec3 co, vec4 color1, vec4 color2, vec4 mortar, float scale, - float mortar_size, float bias, + float mortar_size, float mortar_smooth, float bias, float brick_width, float row_height, float offset_amount, float offset_frequency, float squash_amount, float squash_frequency, @@ -2861,7 +2870,7 @@ void node_tex_brick(vec3 co, { #ifdef BIT_OPERATIONS vec2 f2 = calc_brick_texture(co * scale, - mortar_size, bias, + mortar_size, mortar_smooth, bias, brick_width, row_height, offset_amount, int(offset_frequency), squash_amount, int(squash_frequency)); @@ -2871,7 +2880,7 @@ void node_tex_brick(vec3 co, float facm = 1.0 - tint; color1 = facm * color1 + tint * color2; } - color = (f == 1.0) ? mortar : color1; + color = mix(color1, mortar, f); fac = f; #else color = vec4(1.0); diff --git a/source/blender/gpu/shaders/gpu_shader_smoke_frag.glsl b/source/blender/gpu/shaders/gpu_shader_smoke_frag.glsl index fd790009e02..6ded453225e 100644 --- a/source/blender/gpu/shaders/gpu_shader_smoke_frag.glsl +++ b/source/blender/gpu/shaders/gpu_shader_smoke_frag.glsl @@ -8,10 +8,17 @@ uniform float density_scale; uniform sampler3D soot_texture; uniform sampler3D shadow_texture; +#ifdef USE_COBA +uniform sampler1D transfer_texture; +uniform sampler3D color_band_texture; +#endif + void main() { /* compute color and density from volume texture */ vec4 soot = texture3D(soot_texture, coords); + +#ifndef USE_COBA vec3 soot_color; if (soot.a != 0) { soot_color = active_color * soot.rgb / soot.a; @@ -31,6 +38,11 @@ void main() /* premultiply alpha */ vec4 color = vec4(soot_alpha * soot_color, soot_alpha); +#else + float color_band = texture3D(color_band_texture, coords).r; + vec4 transfer_function = texture1D(transfer_texture, color_band); + vec4 color = transfer_function * density_scale; +#endif gl_FragColor = color; } diff --git a/source/blender/ikplugin/intern/itasc_plugin.cpp b/source/blender/ikplugin/intern/itasc_plugin.cpp index b8ed780397f..d58340965a7 100644 --- a/source/blender/ikplugin/intern/itasc_plugin.cpp +++ b/source/blender/ikplugin/intern/itasc_plugin.cpp @@ -1763,20 +1763,15 @@ void itasc_initialize_tree(struct Scene *scene, Object *ob, float ctime) } // if at least one tree, create the scenes from the PoseTree stored in the channels // postpone until execute_tree: this way the pose constraint are included - //if (count) - // create_scene(scene, ob, ctime); - //itasc_update_param(ob->pose); + if (count) + create_scene(scene, ob, ctime); + itasc_update_param(ob->pose); // make sure we don't rebuilt until the user changes something important ob->pose->flag &= ~POSE_WAS_REBUILT; } void itasc_execute_tree(struct Scene *scene, Object *ob, bPoseChannel *pchan_root, float ctime) { - if (!ob->pose->ikdata) { - // IK tree not yet created, no it now - create_scene(scene, ob, ctime); - itasc_update_param(ob->pose); - } if (ob->pose->ikdata) { IK_Data *ikdata = (IK_Data *)ob->pose->ikdata; bItasc *ikparam = (bItasc *) ob->pose->ikparam; diff --git a/source/blender/makesdna/DNA_rigidbody_types.h b/source/blender/makesdna/DNA_rigidbody_types.h index 5d76ffe57b5..381ee5d40e5 100644 --- a/source/blender/makesdna/DNA_rigidbody_types.h +++ b/source/blender/makesdna/DNA_rigidbody_types.h @@ -226,10 +226,16 @@ typedef struct RigidBodyCon { float spring_stiffness_x; float spring_stiffness_y; float spring_stiffness_z; + float spring_stiffness_ang_x; + float spring_stiffness_ang_y; + float spring_stiffness_ang_z; /* amount of velocity lost over time */ float spring_damping_x; float spring_damping_y; float spring_damping_z; + float spring_damping_ang_x; + float spring_damping_ang_y; + float spring_damping_ang_z; /* motor settings */ float motor_lin_target_velocity; /* linear velocity the motor tries to hold */ @@ -295,7 +301,11 @@ typedef enum eRigidBodyCon_Flag { RBC_FLAG_USE_SPRING_Z = (1 << 13), /* motors */ RBC_FLAG_USE_MOTOR_LIN = (1 << 14), - RBC_FLAG_USE_MOTOR_ANG = (1 << 15) + RBC_FLAG_USE_MOTOR_ANG = (1 << 15), + /* angular springs */ + RBC_FLAG_USE_SPRING_ANG_X = (1 << 16), + RBC_FLAG_USE_SPRING_ANG_Y = (1 << 17), + RBC_FLAG_USE_SPRING_ANG_Z = (1 << 18) } eRigidBodyCon_Flag; /* ******************************** */ diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index 9b0781ebe70..f5e71ae59a9 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -1103,7 +1103,7 @@ typedef struct Sculpt { float gravity_factor; /* scale for constant detail size */ - float constant_detail; + float constant_detail; /* Constant detail resolution (Blender unit / constant_detail) */ float detail_percent; float pad; diff --git a/source/blender/makesdna/DNA_smoke_types.h b/source/blender/makesdna/DNA_smoke_types.h index 8f197c8c42c..fb81e2b6402 100644 --- a/source/blender/makesdna/DNA_smoke_types.h +++ b/source/blender/makesdna/DNA_smoke_types.h @@ -77,6 +77,23 @@ enum { VECTOR_DRAW_STREAMLINE = 1, }; +enum { + FLUID_FIELD_DENSITY = 0, + FLUID_FIELD_HEAT = 1, + FLUID_FIELD_FUEL = 2, + FLUID_FIELD_REACT = 3, + FLUID_FIELD_FLAME = 4, + FLUID_FIELD_VELOCITY_X = 5, + FLUID_FIELD_VELOCITY_Y = 6, + FLUID_FIELD_VELOCITY_Z = 7, + FLUID_FIELD_COLOR_R = 8, + FLUID_FIELD_COLOR_G = 9, + FLUID_FIELD_COLOR_B = 10, + FLUID_FIELD_FORCE_X = 11, + FLUID_FIELD_FORCE_Y = 12, + FLUID_FIELD_FORCE_Z = 13, +}; + /* cache compression */ #define SM_CACHE_LIGHT 0 #define SM_CACHE_HEAVY 1 @@ -193,9 +210,13 @@ typedef struct SmokeDomainSettings { float slice_per_voxel; float slice_depth; float display_thickness; + + struct ColorBand *coba; float vector_scale; char vector_draw_type; - char pad2[3]; + char use_coba; + char coba_field; /* simulation field used for the color mapping */ + char pad2; char cache_filename[1024]; } SmokeDomainSettings; diff --git a/source/blender/makesrna/intern/rna_ID.c b/source/blender/makesrna/intern/rna_ID.c index 280ad4aa9b1..5174c957834 100644 --- a/source/blender/makesrna/intern/rna_ID.c +++ b/source/blender/makesrna/intern/rna_ID.c @@ -35,6 +35,7 @@ #include "BLI_utildefines.h" #include "BKE_icons.h" +#include "BKE_object.h" #include "RNA_access.h" #include "RNA_define.h" @@ -347,6 +348,20 @@ static void rna_ID_user_remap(ID *id, Main *bmain, ID *new_id) } } +static struct ID *rna_ID_make_local(struct ID *self, Main *bmain, int clear_proxy) +{ + /* Special case, as we can't rely on id_make_local(); it clears proxies. */ + if (!clear_proxy && GS(self->name) == ID_OB) { + BKE_object_make_local_ex(bmain, (Object *)self, false, clear_proxy); + } + else { + id_make_local(bmain, self, false, false); + } + + return self->newid ? self->newid : self; +} + + static AnimData * rna_ID_animation_data_create(ID *id, Main *bmain) { AnimData *adt = BKE_animdata_add_id(id); @@ -999,6 +1014,17 @@ static void rna_def_ID(BlenderRNA *brna) parm = RNA_def_pointer(func, "new_id", "ID", "", "New ID to use"); RNA_def_property_flag(parm, PROP_REQUIRED | PROP_NEVER_NULL); + func = RNA_def_function(srna, "make_local", "rna_ID_make_local"); + RNA_def_function_ui_description(func, "Make this datablock local, return local one " + "(may be a copy of the original, in case it is also indirectly used)"); + RNA_def_function_flag(func, FUNC_USE_MAIN); + RNA_def_boolean(func, "clear_proxy", true, "", + "Whether to clear proxies (the default behavior); can cause proxies to be duplicated" + " when still referred to from another library"); + RNA_def_property_flag(parm, PROP_PYFUNC_OPTIONAL); + parm = RNA_def_pointer(func, "id", "ID", "", "This ID, or the new ID if it was copied"); + RNA_def_function_return(func, parm); + func = RNA_def_function(srna, "user_of_id", "BKE_library_ID_use_ID"); RNA_def_function_ui_description(func, "Count the number of times that ID uses/references given one"); parm = RNA_def_pointer(func, "id", "ID", "", "ID to count usages"); diff --git a/source/blender/makesrna/intern/rna_main_api.c b/source/blender/makesrna/intern/rna_main_api.c index cc290eab59d..594c1752328 100644 --- a/source/blender/makesrna/intern/rna_main_api.c +++ b/source/blender/makesrna/intern/rna_main_api.c @@ -585,7 +585,7 @@ void RNA_def_main_cameras(BlenderRNA *brna, PropertyRNA *cprop) parm = RNA_def_pointer(func, "camera", "Camera", "", "Camera to remove"); RNA_def_property_flag(parm, PROP_REQUIRED | PROP_NEVER_NULL | PROP_RNAPTR); RNA_def_property_clear_flag(parm, PROP_THICK_WRAP); - RNA_def_boolean(func, "do_unlink", false, "", + RNA_def_boolean(func, "do_unlink", true, "", "Unlink all usages of this camera before deleting it " "(WARNING: will also delete objects instancing that camera data)"); @@ -624,7 +624,7 @@ void RNA_def_main_scenes(BlenderRNA *brna, PropertyRNA *cprop) parm = RNA_def_pointer(func, "scene", "Scene", "", "Scene to remove"); RNA_def_property_flag(parm, PROP_REQUIRED | PROP_NEVER_NULL | PROP_RNAPTR); RNA_def_property_clear_flag(parm, PROP_THICK_WRAP); - RNA_def_boolean(func, "do_unlink", false, "", "Unlink all usages of this scene before deleting it"); + RNA_def_boolean(func, "do_unlink", true, "", "Unlink all usages of this scene before deleting it"); func = RNA_def_function(srna, "tag", "rna_Main_scenes_tag"); parm = RNA_def_boolean(func, "value", 0, "Value", ""); @@ -665,7 +665,7 @@ void RNA_def_main_objects(BlenderRNA *brna, PropertyRNA *cprop) parm = RNA_def_pointer(func, "object", "Object", "", "Object to remove"); RNA_def_property_flag(parm, PROP_REQUIRED | PROP_NEVER_NULL | PROP_RNAPTR); RNA_def_property_clear_flag(parm, PROP_THICK_WRAP); - RNA_def_boolean(func, "do_unlink", false, "", "Unlink all usages of this object before deleting it"); + RNA_def_boolean(func, "do_unlink", true, "", "Unlink all usages of this object before deleting it"); func = RNA_def_function(srna, "tag", "rna_Main_objects_tag"); parm = RNA_def_boolean(func, "value", 0, "Value", ""); @@ -702,7 +702,7 @@ void RNA_def_main_materials(BlenderRNA *brna, PropertyRNA *cprop) parm = RNA_def_pointer(func, "material", "Material", "", "Material to remove"); RNA_def_property_flag(parm, PROP_REQUIRED | PROP_NEVER_NULL | PROP_RNAPTR); RNA_def_property_clear_flag(parm, PROP_THICK_WRAP); - RNA_def_boolean(func, "do_unlink", false, "", "Unlink all usages of this material before deleting it"); + RNA_def_boolean(func, "do_unlink", true, "", "Unlink all usages of this material before deleting it"); func = RNA_def_function(srna, "tag", "rna_Main_materials_tag"); parm = RNA_def_boolean(func, "value", 0, "Value", ""); @@ -746,7 +746,7 @@ void RNA_def_main_node_groups(BlenderRNA *brna, PropertyRNA *cprop) parm = RNA_def_pointer(func, "tree", "NodeTree", "", "Node tree to remove"); RNA_def_property_flag(parm, PROP_REQUIRED | PROP_NEVER_NULL | PROP_RNAPTR); RNA_def_property_clear_flag(parm, PROP_THICK_WRAP); - RNA_def_boolean(func, "do_unlink", false, "", "Unlink all usages of this node tree before deleting it"); + RNA_def_boolean(func, "do_unlink", true, "", "Unlink all usages of this node tree before deleting it"); func = RNA_def_function(srna, "tag", "rna_Main_node_groups_tag"); parm = RNA_def_boolean(func, "value", 0, "Value", ""); @@ -805,7 +805,7 @@ void RNA_def_main_meshes(BlenderRNA *brna, PropertyRNA *cprop) parm = RNA_def_pointer(func, "mesh", "Mesh", "", "Mesh to remove"); RNA_def_property_flag(parm, PROP_REQUIRED | PROP_NEVER_NULL | PROP_RNAPTR); RNA_def_property_clear_flag(parm, PROP_THICK_WRAP); - RNA_def_boolean(func, "do_unlink", false, "", + RNA_def_boolean(func, "do_unlink", true, "", "Unlink all usages of this mesh before deleting it " "(WARNING: will also delete objects instancing that mesh data)"); @@ -845,7 +845,7 @@ void RNA_def_main_lamps(BlenderRNA *brna, PropertyRNA *cprop) parm = RNA_def_pointer(func, "lamp", "Lamp", "", "Lamp to remove"); RNA_def_property_flag(parm, PROP_REQUIRED | PROP_NEVER_NULL | PROP_RNAPTR); RNA_def_property_clear_flag(parm, PROP_THICK_WRAP); - RNA_def_boolean(func, "do_unlink", false, "", + RNA_def_boolean(func, "do_unlink", true, "", "Unlink all usages of this lamp before deleting it " "(WARNING: will also delete objects instancing that lamp data)"); @@ -963,7 +963,7 @@ void RNA_def_main_images(BlenderRNA *brna, PropertyRNA *cprop) parm = RNA_def_pointer(func, "image", "Image", "", "Image to remove"); RNA_def_property_flag(parm, PROP_REQUIRED | PROP_NEVER_NULL | PROP_RNAPTR); RNA_def_property_clear_flag(parm, PROP_THICK_WRAP); - RNA_def_boolean(func, "do_unlink", false, "", "Unlink all usages of this image before deleting it"); + RNA_def_boolean(func, "do_unlink", true, "", "Unlink all usages of this image before deleting it"); func = RNA_def_function(srna, "tag", "rna_Main_images_tag"); parm = RNA_def_boolean(func, "value", 0, "Value", ""); @@ -1000,7 +1000,7 @@ void RNA_def_main_lattices(BlenderRNA *brna, PropertyRNA *cprop) parm = RNA_def_pointer(func, "lattice", "Lattice", "", "Lattice to remove"); RNA_def_property_flag(parm, PROP_REQUIRED | PROP_NEVER_NULL | PROP_RNAPTR); RNA_def_property_clear_flag(parm, PROP_THICK_WRAP); - RNA_def_boolean(func, "do_unlink", false, "", + RNA_def_boolean(func, "do_unlink", true, "", "Unlink all usages of this lattice before deleting it " "(WARNING: will also delete objects instancing that lattice data)"); @@ -1040,7 +1040,7 @@ void RNA_def_main_curves(BlenderRNA *brna, PropertyRNA *cprop) parm = RNA_def_pointer(func, "curve", "Curve", "", "Curve to remove"); RNA_def_property_flag(parm, PROP_REQUIRED | PROP_NEVER_NULL | PROP_RNAPTR); RNA_def_property_clear_flag(parm, PROP_THICK_WRAP); - RNA_def_boolean(func, "do_unlink", false, "", + RNA_def_boolean(func, "do_unlink", true, "", "Unlink all usages of this curve before deleting it " "(WARNING: will also delete objects instancing that curve data)"); @@ -1078,7 +1078,7 @@ void RNA_def_main_metaballs(BlenderRNA *brna, PropertyRNA *cprop) parm = RNA_def_pointer(func, "metaball", "MetaBall", "", "Metaball to remove"); RNA_def_property_flag(parm, PROP_REQUIRED | PROP_NEVER_NULL | PROP_RNAPTR); RNA_def_property_clear_flag(parm, PROP_THICK_WRAP); - RNA_def_boolean(func, "do_unlink", false, "", + RNA_def_boolean(func, "do_unlink", true, "", "Unlink all usages of this metaball before deleting it " "(WARNING: will also delete objects instancing that metaball data)"); @@ -1118,7 +1118,7 @@ void RNA_def_main_fonts(BlenderRNA *brna, PropertyRNA *cprop) parm = RNA_def_pointer(func, "vfont", "VectorFont", "", "Font to remove"); RNA_def_property_flag(parm, PROP_REQUIRED | PROP_NEVER_NULL | PROP_RNAPTR); RNA_def_property_clear_flag(parm, PROP_THICK_WRAP); - RNA_def_boolean(func, "do_unlink", false, "", "Unlink all usages of this font before deleting it"); + RNA_def_boolean(func, "do_unlink", true, "", "Unlink all usages of this font before deleting it"); func = RNA_def_function(srna, "tag", "rna_Main_fonts_tag"); parm = RNA_def_boolean(func, "value", 0, "Value", ""); @@ -1156,7 +1156,7 @@ void RNA_def_main_textures(BlenderRNA *brna, PropertyRNA *cprop) parm = RNA_def_pointer(func, "texture", "Texture", "", "Texture to remove"); RNA_def_property_flag(parm, PROP_REQUIRED | PROP_NEVER_NULL | PROP_RNAPTR); RNA_def_property_clear_flag(parm, PROP_THICK_WRAP); - RNA_def_boolean(func, "do_unlink", false, "", "Unlink all usages of this texture before deleting it"); + RNA_def_boolean(func, "do_unlink", true, "", "Unlink all usages of this texture before deleting it"); func = RNA_def_function(srna, "tag", "rna_Main_textures_tag"); parm = RNA_def_boolean(func, "value", 0, "Value", ""); @@ -1193,7 +1193,7 @@ void RNA_def_main_brushes(BlenderRNA *brna, PropertyRNA *cprop) parm = RNA_def_pointer(func, "brush", "Brush", "", "Brush to remove"); RNA_def_property_flag(parm, PROP_REQUIRED | PROP_NEVER_NULL | PROP_RNAPTR); RNA_def_property_clear_flag(parm, PROP_THICK_WRAP); - RNA_def_boolean(func, "do_unlink", false, "", "Unlink all usages of this brush before deleting it"); + RNA_def_boolean(func, "do_unlink", true, "", "Unlink all usages of this brush before deleting it"); func = RNA_def_function(srna, "tag", "rna_Main_brushes_tag"); parm = RNA_def_boolean(func, "value", 0, "Value", ""); @@ -1230,7 +1230,7 @@ void RNA_def_main_worlds(BlenderRNA *brna, PropertyRNA *cprop) parm = RNA_def_pointer(func, "world", "World", "", "World to remove"); RNA_def_property_flag(parm, PROP_REQUIRED | PROP_NEVER_NULL | PROP_RNAPTR); RNA_def_property_clear_flag(parm, PROP_THICK_WRAP); - RNA_def_boolean(func, "do_unlink", false, "", "Unlink all usages of this world before deleting it"); + RNA_def_boolean(func, "do_unlink", true, "", "Unlink all usages of this world before deleting it"); func = RNA_def_function(srna, "tag", "rna_Main_worlds_tag"); parm = RNA_def_boolean(func, "value", 0, "Value", ""); @@ -1267,7 +1267,7 @@ void RNA_def_main_groups(BlenderRNA *brna, PropertyRNA *cprop) parm = RNA_def_pointer(func, "group", "Group", "", "Group to remove"); RNA_def_property_flag(parm, PROP_REQUIRED | PROP_NEVER_NULL | PROP_RNAPTR); RNA_def_property_clear_flag(parm, PROP_THICK_WRAP); - RNA_def_boolean(func, "do_unlink", false, "", "Unlink all usages of this group before deleting it"); + RNA_def_boolean(func, "do_unlink", true, "", "Unlink all usages of this group before deleting it"); func = RNA_def_function(srna, "tag", "rna_Main_groups_tag"); parm = RNA_def_boolean(func, "value", 0, "Value", ""); @@ -1304,7 +1304,7 @@ void RNA_def_main_speakers(BlenderRNA *brna, PropertyRNA *cprop) parm = RNA_def_pointer(func, "speaker", "Speaker", "", "Speaker to remove"); RNA_def_property_flag(parm, PROP_REQUIRED | PROP_NEVER_NULL | PROP_RNAPTR); RNA_def_property_clear_flag(parm, PROP_THICK_WRAP); - RNA_def_boolean(func, "do_unlink", false, "", + RNA_def_boolean(func, "do_unlink", true, "", "Unlink all usages of this speaker before deleting it " "(WARNING: will also delete objects instancing that speaker data)"); @@ -1343,7 +1343,7 @@ void RNA_def_main_texts(BlenderRNA *brna, PropertyRNA *cprop) parm = RNA_def_pointer(func, "text", "Text", "", "Text to remove"); RNA_def_property_flag(parm, PROP_REQUIRED | PROP_NEVER_NULL | PROP_RNAPTR); RNA_def_property_clear_flag(parm, PROP_THICK_WRAP); - RNA_def_boolean(func, "do_unlink", false, "", "Unlink all usages of this text before deleting it"); + RNA_def_boolean(func, "do_unlink", true, "", "Unlink all usages of this text before deleting it"); /* load func */ func = RNA_def_function(srna, "load", "rna_Main_texts_load"); @@ -1393,7 +1393,7 @@ void RNA_def_main_sounds(BlenderRNA *brna, PropertyRNA *cprop) parm = RNA_def_pointer(func, "sound", "Sound", "", "Sound to remove"); RNA_def_property_flag(parm, PROP_REQUIRED | PROP_NEVER_NULL | PROP_RNAPTR); RNA_def_property_clear_flag(parm, PROP_THICK_WRAP); - RNA_def_boolean(func, "do_unlink", false, "", "Unlink all usages of this sound before deleting it"); + RNA_def_boolean(func, "do_unlink", true, "", "Unlink all usages of this sound before deleting it"); func = RNA_def_function(srna, "tag", "rna_Main_sounds_tag"); parm = RNA_def_boolean(func, "value", 0, "Value", ""); @@ -1430,7 +1430,7 @@ void RNA_def_main_armatures(BlenderRNA *brna, PropertyRNA *cprop) parm = RNA_def_pointer(func, "armature", "Armature", "", "Armature to remove"); RNA_def_property_flag(parm, PROP_REQUIRED | PROP_NEVER_NULL | PROP_RNAPTR); RNA_def_property_clear_flag(parm, PROP_THICK_WRAP); - RNA_def_boolean(func, "do_unlink", false, "", + RNA_def_boolean(func, "do_unlink", true, "", "Unlink all usages of this armature before deleting it " "(WARNING: will also delete objects instancing that armature data)"); @@ -1468,7 +1468,7 @@ void RNA_def_main_actions(BlenderRNA *brna, PropertyRNA *cprop) parm = RNA_def_pointer(func, "action", "Action", "", "Action to remove"); RNA_def_property_flag(parm, PROP_REQUIRED | PROP_NEVER_NULL | PROP_RNAPTR); RNA_def_property_clear_flag(parm, PROP_THICK_WRAP); - RNA_def_boolean(func, "do_unlink", false, "", "Unlink all usages of this action before deleting it"); + RNA_def_boolean(func, "do_unlink", true, "", "Unlink all usages of this action before deleting it"); func = RNA_def_function(srna, "tag", "rna_Main_actions_tag"); parm = RNA_def_boolean(func, "value", 0, "Value", ""); @@ -1504,7 +1504,7 @@ void RNA_def_main_particles(BlenderRNA *brna, PropertyRNA *cprop) parm = RNA_def_pointer(func, "particle", "ParticleSettings", "", "Particle Settings to remove"); RNA_def_property_flag(parm, PROP_REQUIRED | PROP_NEVER_NULL | PROP_RNAPTR); RNA_def_property_clear_flag(parm, PROP_THICK_WRAP); - RNA_def_boolean(func, "do_unlink", false, "", "Unlink all usages of those particle settings before deleting them"); + RNA_def_boolean(func, "do_unlink", true, "", "Unlink all usages of those particle settings before deleting them"); func = RNA_def_function(srna, "tag", "rna_Main_particles_tag"); parm = RNA_def_boolean(func, "value", 0, "Value", ""); @@ -1540,7 +1540,7 @@ void RNA_def_main_palettes(BlenderRNA *brna, PropertyRNA *cprop) parm = RNA_def_pointer(func, "palette", "Palette", "", "Palette to remove"); RNA_def_property_flag(parm, PROP_REQUIRED | PROP_NEVER_NULL | PROP_RNAPTR); RNA_def_property_clear_flag(parm, PROP_THICK_WRAP); - RNA_def_boolean(func, "do_unlink", false, "", "Unlink all usages of this palette before deleting it"); + RNA_def_boolean(func, "do_unlink", true, "", "Unlink all usages of this palette before deleting it"); func = RNA_def_function(srna, "tag", "rna_Main_palettes_tag"); parm = RNA_def_boolean(func, "value", 0, "Value", ""); @@ -1610,7 +1610,7 @@ void RNA_def_main_gpencil(BlenderRNA *brna, PropertyRNA *cprop) parm = RNA_def_pointer(func, "grease_pencil", "GreasePencil", "", "Grease Pencil to remove"); RNA_def_property_flag(parm, PROP_REQUIRED | PROP_NEVER_NULL | PROP_RNAPTR); RNA_def_property_clear_flag(parm, PROP_THICK_WRAP); - RNA_def_boolean(func, "do_unlink", false, "", "Unlink all usages of this grease pencil before deleting it"); + RNA_def_boolean(func, "do_unlink", true, "", "Unlink all usages of this grease pencil before deleting it"); prop = RNA_def_property(srna, "is_updated", PROP_BOOLEAN, PROP_NONE); RNA_def_property_clear_flag(prop, PROP_EDITABLE); @@ -1639,7 +1639,7 @@ void RNA_def_main_movieclips(BlenderRNA *brna, PropertyRNA *cprop) parm = RNA_def_pointer(func, "clip", "MovieClip", "", "Movie clip to remove"); RNA_def_property_flag(parm, PROP_REQUIRED | PROP_NEVER_NULL | PROP_RNAPTR); RNA_def_property_clear_flag(parm, PROP_THICK_WRAP); - RNA_def_boolean(func, "do_unlink", false, "", "Unlink all usages of this movie clip before deleting it"); + RNA_def_boolean(func, "do_unlink", true, "", "Unlink all usages of this movie clip before deleting it"); /* load func */ func = RNA_def_function(srna, "load", "rna_Main_movieclip_load"); @@ -1691,7 +1691,7 @@ void RNA_def_main_masks(BlenderRNA *brna, PropertyRNA *cprop) parm = RNA_def_pointer(func, "mask", "Mask", "", "Mask to remove"); RNA_def_property_flag(parm, PROP_REQUIRED | PROP_NEVER_NULL | PROP_RNAPTR); RNA_def_property_clear_flag(parm, PROP_THICK_WRAP); - RNA_def_boolean(func, "do_unlink", false, "", "Unlink all usages of this mask before deleting it"); + RNA_def_boolean(func, "do_unlink", true, "", "Unlink all usages of this mask before deleting it"); prop = RNA_def_property(srna, "is_updated", PROP_BOOLEAN, PROP_NONE); RNA_def_property_clear_flag(prop, PROP_EDITABLE); @@ -1728,7 +1728,7 @@ void RNA_def_main_linestyles(BlenderRNA *brna, PropertyRNA *cprop) parm = RNA_def_pointer(func, "linestyle", "FreestyleLineStyle", "", "Line style to remove"); RNA_def_property_flag(parm, PROP_REQUIRED | PROP_NEVER_NULL | PROP_RNAPTR); RNA_def_property_clear_flag(parm, PROP_THICK_WRAP); - RNA_def_boolean(func, "do_unlink", false, "", "Unlink all usages of this line style before deleting it"); + RNA_def_boolean(func, "do_unlink", true, "", "Unlink all usages of this line style before deleting it"); prop = RNA_def_property(srna, "is_updated", PROP_BOOLEAN, PROP_NONE); RNA_def_property_clear_flag(prop, PROP_EDITABLE); diff --git a/source/blender/makesrna/intern/rna_rigidbody.c b/source/blender/makesrna/intern/rna_rigidbody.c index bdf001ed0e1..85a34a94746 100644 --- a/source/blender/makesrna/intern/rna_rigidbody.c +++ b/source/blender/makesrna/intern/rna_rigidbody.c @@ -507,6 +507,45 @@ static void rna_RigidBodyCon_spring_stiffness_z_set(PointerRNA *ptr, float value #endif } +static void rna_RigidBodyCon_spring_stiffness_ang_x_set(PointerRNA *ptr, float value) +{ + RigidBodyCon *rbc = (RigidBodyCon *)ptr->data; + + rbc->spring_stiffness_ang_x = value; + +#ifdef WITH_BULLET + if (rbc->physics_constraint && rbc->type == RBC_TYPE_6DOF_SPRING && (rbc->flag & RBC_FLAG_USE_SPRING_ANG_X)) { + RB_constraint_set_stiffness_6dof_spring(rbc->physics_constraint, RB_LIMIT_ANG_X, value); + } +#endif +} + +static void rna_RigidBodyCon_spring_stiffness_ang_y_set(PointerRNA *ptr, float value) +{ + RigidBodyCon *rbc = (RigidBodyCon *)ptr->data; + + rbc->spring_stiffness_ang_y = value; + +#ifdef WITH_BULLET + if (rbc->physics_constraint && rbc->type == RBC_TYPE_6DOF_SPRING && (rbc->flag & RBC_FLAG_USE_SPRING_ANG_Y)) { + RB_constraint_set_stiffness_6dof_spring(rbc->physics_constraint, RB_LIMIT_ANG_Y, value); + } +#endif +} + +static void rna_RigidBodyCon_spring_stiffness_ang_z_set(PointerRNA *ptr, float value) +{ + RigidBodyCon *rbc = (RigidBodyCon *)ptr->data; + + rbc->spring_stiffness_ang_z = value; + +#ifdef WITH_BULLET + if (rbc->physics_constraint && rbc->type == RBC_TYPE_6DOF_SPRING && (rbc->flag & RBC_FLAG_USE_SPRING_ANG_Z)) { + RB_constraint_set_stiffness_6dof_spring(rbc->physics_constraint, RB_LIMIT_ANG_Z, value); + } +#endif +} + static void rna_RigidBodyCon_spring_damping_x_set(PointerRNA *ptr, float value) { RigidBodyCon *rbc = (RigidBodyCon *)ptr->data; @@ -544,6 +583,43 @@ static void rna_RigidBodyCon_spring_damping_z_set(PointerRNA *ptr, float value) #endif } +static void rna_RigidBodyCon_spring_damping_ang_x_set(PointerRNA *ptr, float value) +{ + RigidBodyCon *rbc = (RigidBodyCon *)ptr->data; + + rbc->spring_damping_ang_x = value; + +#ifdef WITH_BULLET + if (rbc->physics_constraint && rbc->type == RBC_TYPE_6DOF_SPRING && (rbc->flag & RBC_FLAG_USE_SPRING_ANG_X)) { + RB_constraint_set_damping_6dof_spring(rbc->physics_constraint, RB_LIMIT_ANG_X, value); + } +#endif +} + +static void rna_RigidBodyCon_spring_damping_ang_y_set(PointerRNA *ptr, float value) +{ + RigidBodyCon *rbc = (RigidBodyCon *)ptr->data; + + rbc->spring_damping_ang_y = value; +#ifdef WITH_BULLET + if (rbc->physics_constraint && rbc->type == RBC_TYPE_6DOF_SPRING && (rbc->flag & RBC_FLAG_USE_SPRING_ANG_Y)) { + RB_constraint_set_damping_6dof_spring(rbc->physics_constraint, RB_LIMIT_ANG_Y, value); + } +#endif +} + +static void rna_RigidBodyCon_spring_damping_ang_z_set(PointerRNA *ptr, float value) +{ + RigidBodyCon *rbc = (RigidBodyCon *)ptr->data; + + rbc->spring_damping_ang_z = value; +#ifdef WITH_BULLET + if (rbc->physics_constraint && rbc->type == RBC_TYPE_6DOF_SPRING && (rbc->flag & RBC_FLAG_USE_SPRING_ANG_Z)) { + RB_constraint_set_damping_6dof_spring(rbc->physics_constraint, RB_LIMIT_ANG_Z, value); + } +#endif +} + static void rna_RigidBodyCon_motor_lin_max_impulse_set(PointerRNA *ptr, float value) { RigidBodyCon *rbc = (RigidBodyCon *)ptr->data; @@ -1061,6 +1137,21 @@ static void rna_def_rigidbody_constraint(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Z Spring", "Enable spring on Z axis"); RNA_def_property_update(prop, NC_OBJECT, "rna_RigidBodyOb_reset"); + prop = RNA_def_property(srna, "use_spring_ang_x", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", RBC_FLAG_USE_SPRING_ANG_X); + RNA_def_property_ui_text(prop, "X Angle Spring", "Enable spring on X rotational axis"); + RNA_def_property_update(prop, NC_OBJECT, "rna_RigidBodyOb_reset"); + + prop = RNA_def_property(srna, "use_spring_ang_y", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", RBC_FLAG_USE_SPRING_ANG_Y); + RNA_def_property_ui_text(prop, "Y Angle Spring", "Enable spring on Y rotational axis"); + RNA_def_property_update(prop, NC_OBJECT, "rna_RigidBodyOb_reset"); + + prop = RNA_def_property(srna, "use_spring_ang_z", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", RBC_FLAG_USE_SPRING_ANG_Z); + RNA_def_property_ui_text(prop, "Z Angle Spring", "Enable spring on Z rotational axis"); + RNA_def_property_update(prop, NC_OBJECT, "rna_RigidBodyOb_reset"); + prop = RNA_def_property(srna, "use_motor_lin", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", RBC_FLAG_USE_MOTOR_LIN); RNA_def_property_boolean_funcs(prop, NULL, "rna_RigidBodyCon_use_motor_lin_set"); @@ -1178,6 +1269,33 @@ static void rna_def_rigidbody_constraint(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Z Axis Stiffness", "Stiffness on the Z axis"); RNA_def_property_update(prop, NC_OBJECT, "rna_RigidBodyOb_reset"); + prop = RNA_def_property(srna, "spring_stiffness_ang_x", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, NULL, "spring_stiffness_ang_x"); + RNA_def_property_range(prop, 0.0f, FLT_MAX); + RNA_def_property_ui_range(prop, 0.0f, 100.0f, 1, 3); + RNA_def_property_float_default(prop, 10.0f); + RNA_def_property_float_funcs(prop, NULL, "rna_RigidBodyCon_spring_stiffness_ang_x_set", NULL); + RNA_def_property_ui_text(prop, "X Angle Stiffness", "Stiffness on the X rotational axis"); + RNA_def_property_update(prop, NC_OBJECT, "rna_RigidBodyOb_reset"); + + prop = RNA_def_property(srna, "spring_stiffness_ang_y", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, NULL, "spring_stiffness_ang_y"); + RNA_def_property_range(prop, 0.0f, FLT_MAX); + RNA_def_property_ui_range(prop, 0.0f, 100.0f, 1, 3); + RNA_def_property_float_default(prop, 10.0f); + RNA_def_property_float_funcs(prop, NULL, "rna_RigidBodyCon_spring_stiffness_ang_y_set", NULL); + RNA_def_property_ui_text(prop, "Y Angle Stiffness", "Stiffness on the Y rotational axis"); + RNA_def_property_update(prop, NC_OBJECT, "rna_RigidBodyOb_reset"); + + prop = RNA_def_property(srna, "spring_stiffness_ang_z", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, NULL, "spring_stiffness_ang_z"); + RNA_def_property_range(prop, 0.0f, FLT_MAX); + RNA_def_property_ui_range(prop, 0.0f, 100.0f, 1, 3); + RNA_def_property_float_default(prop, 10.0f); + RNA_def_property_float_funcs(prop, NULL, "rna_RigidBodyCon_spring_stiffness_ang_z_set", NULL); + RNA_def_property_ui_text(prop, "Z Angle Stiffness", "Stiffness on the Z rotational axis"); + RNA_def_property_update(prop, NC_OBJECT, "rna_RigidBodyOb_reset"); + prop = RNA_def_property(srna, "spring_damping_x", PROP_FLOAT, PROP_FACTOR); RNA_def_property_float_sdna(prop, NULL, "spring_damping_x"); RNA_def_property_range(prop, 0.0f, 1.0f); @@ -1202,6 +1320,30 @@ static void rna_def_rigidbody_constraint(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Damping Z", "Damping on the Z axis"); RNA_def_property_update(prop, NC_OBJECT, "rna_RigidBodyOb_reset"); + prop = RNA_def_property(srna, "spring_damping_ang_x", PROP_FLOAT, PROP_FACTOR); + RNA_def_property_float_sdna(prop, NULL, "spring_damping_ang_x"); + RNA_def_property_range(prop, 0.0f, 1.0f); + RNA_def_property_float_default(prop, 0.5f); + RNA_def_property_float_funcs(prop, NULL, "rna_RigidBodyCon_spring_damping_ang_x_set", NULL); + RNA_def_property_ui_text(prop, "Damping X Angle", "Damping on the X rotational axis"); + RNA_def_property_update(prop, NC_OBJECT, "rna_RigidBodyOb_reset"); + + prop = RNA_def_property(srna, "spring_damping_ang_y", PROP_FLOAT, PROP_FACTOR); + RNA_def_property_float_sdna(prop, NULL, "spring_damping_ang_y"); + RNA_def_property_range(prop, 0.0f, 1.0f); + RNA_def_property_float_default(prop, 0.5f); + RNA_def_property_float_funcs(prop, NULL, "rna_RigidBodyCon_spring_damping_ang_y_set", NULL); + RNA_def_property_ui_text(prop, "Damping Y Angle", "Damping on the Y rotational axis"); + RNA_def_property_update(prop, NC_OBJECT, "rna_RigidBodyOb_reset"); + + prop = RNA_def_property(srna, "spring_damping_ang_z", PROP_FLOAT, PROP_FACTOR); + RNA_def_property_float_sdna(prop, NULL, "spring_damping_ang_z"); + RNA_def_property_range(prop, 0.0f, 1.0f); + RNA_def_property_float_default(prop, 0.5f); + RNA_def_property_float_funcs(prop, NULL, "rna_RigidBodyCon_spring_damping_ang_z_set", NULL); + RNA_def_property_ui_text(prop, "Damping Z Angle", "Damping on the Z rotational axis"); + RNA_def_property_update(prop, NC_OBJECT, "rna_RigidBodyOb_reset"); + prop = RNA_def_property(srna, "motor_lin_target_velocity", PROP_FLOAT, PROP_UNIT_VELOCITY); RNA_def_property_float_sdna(prop, NULL, "motor_lin_target_velocity"); RNA_def_property_range(prop, -FLT_MAX, FLT_MAX); diff --git a/source/blender/makesrna/intern/rna_sculpt_paint.c b/source/blender/makesrna/intern/rna_sculpt_paint.c index e169ef0d822..7f405f0fb1f 100644 --- a/source/blender/makesrna/intern/rna_sculpt_paint.c +++ b/source/blender/makesrna/intern/rna_sculpt_paint.c @@ -600,10 +600,12 @@ static void rna_def_sculpt(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Detail Percentage", "Maximum edge length for dynamic topology sculpting (in brush percenage)"); RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); - prop = RNA_def_property(srna, "constant_detail", PROP_FLOAT, PROP_PERCENTAGE); - RNA_def_property_range(prop, 0.001, 10000.0); - RNA_def_property_ui_range(prop, 0.1, 100.0, 10, 2); - RNA_def_property_ui_text(prop, "Detail Size", "Maximum edge length for dynamic topology sculpting (as percentage of blender unit)"); + prop = RNA_def_property(srna, "constant_detail_resolution", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, NULL, "constant_detail"); + RNA_def_property_range(prop, 0.0001, FLT_MAX); + RNA_def_property_ui_range(prop, 0.001, 1000.0, 10, 2); + RNA_def_property_ui_text(prop, "Resolution", "Maximum edge length for dynamic topology sculpting (as divisor " + "of blender unit - higher value means smaller edge length)"); RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); prop = RNA_def_property(srna, "use_smooth_shading", PROP_BOOLEAN, PROP_NONE); diff --git a/source/blender/makesrna/intern/rna_smoke.c b/source/blender/makesrna/intern/rna_smoke.c index ce18c86b8d0..15950e67671 100644 --- a/source/blender/makesrna/intern/rna_smoke.c +++ b/source/blender/makesrna/intern/rna_smoke.c @@ -30,6 +30,7 @@ #include <limits.h> #include "RNA_define.h" +#include "RNA_enum_types.h" #include "rna_internal.h" @@ -53,6 +54,7 @@ #include "BKE_context.h" #include "BKE_depsgraph.h" #include "BKE_particle.h" +#include "BKE_texture.h" #include "smoke_API.h" @@ -383,13 +385,24 @@ static void rna_SmokeFlow_uvlayer_set(PointerRNA *ptr, const char *value) rna_object_uvlayer_name_set(ptr, value, flow->uvlayer_name, sizeof(flow->uvlayer_name)); } +static void rna_Smoke_use_color_ramp_set(PointerRNA *ptr, int value) +{ + SmokeDomainSettings *sds = (SmokeDomainSettings *)ptr->data; + + sds->use_coba = value; + + if (value && sds->coba == NULL) { + sds->coba = add_colorband(false); + } +} + static void rna_SmokeModifier_cache_filename_get(PointerRNA *ptr, char *filename) { - SmokeDomainSettings *sds = (SmokeDomainSettings *)ptr->data; - PTCacheID pid; + SmokeDomainSettings *sds = (SmokeDomainSettings *)ptr->data; + PTCacheID pid; - BKE_ptcache_id_from_smoke(&pid, ptr->id.data, sds->smd); - ptcache_filename(&pid, filename, sds->smd->time, 1, 1); + BKE_ptcache_id_from_smoke(&pid, ptr->id.data, sds->smd); + ptcache_filename(&pid, filename, sds->smd->time, 1, 1); } #else @@ -815,6 +828,41 @@ static void rna_def_smoke_domain_settings(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Scale", "Multiplier for scaling the vectors"); RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, NULL); + /* --------- Color mapping. --------- */ + + prop = RNA_def_property(srna, "use_color_ramp", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "use_coba", 0); + RNA_def_property_boolean_funcs(prop, NULL, "rna_Smoke_use_color_ramp_set"); + RNA_def_property_ui_text(prop, "Use Color Ramp", + "Render a simulation field while mapping its voxels values to the colors of a ramp"); + RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, NULL); + + static EnumPropertyItem coba_field_items[] = { + {FLUID_FIELD_COLOR_R, "COLOR_R", 0, "Red", "Red component of the color field"}, + {FLUID_FIELD_COLOR_G, "COLOR_G", 0, "Green", "Green component of the color field"}, + {FLUID_FIELD_COLOR_B, "COLOR_B", 0, "Blue", "Blue component of the color field"}, + {FLUID_FIELD_DENSITY, "DENSITY", 0, "Density", "Quantity of soot in the fluid"}, + {FLUID_FIELD_FLAME, "FLAME", 0, "Flame", "Flame field"}, + {FLUID_FIELD_FUEL, "FUEL", 0, "Fuel", "Fuel field"}, + {FLUID_FIELD_HEAT, "HEAT", 0, "Heat", "Temperature of the fluid"}, + {FLUID_FIELD_VELOCITY_X, "VELOCITY_X", 0, "X Velocity", "X component of the velocity field"}, + {FLUID_FIELD_VELOCITY_Y, "VELOCITY_Y", 0, "Y Velocity", "Y component of the velocity field"}, + {FLUID_FIELD_VELOCITY_Z, "VELOCITY_Z", 0, "Z Velocity", "Z component of the velocity field"}, + {0, NULL, 0, NULL, NULL} + }; + + prop = RNA_def_property(srna, "coba_field", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "coba_field"); + RNA_def_property_enum_items(prop, coba_field_items); + RNA_def_property_ui_text(prop, "Field", "Simulation field to color map"); + RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, NULL); + + prop = RNA_def_property(srna, "color_ramp", PROP_POINTER, PROP_NEVER_NULL); + RNA_def_property_pointer_sdna(prop, NULL, "coba"); + RNA_def_property_struct_type(prop, "ColorRamp"); + RNA_def_property_ui_text(prop, "Color Ramp", ""); + RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, NULL); + prop = RNA_def_property(srna, "cache_filename", PROP_STRING, PROP_NONE); RNA_def_property_string_maxlength(prop, 1024); RNA_def_property_string_funcs(prop, "rna_SmokeModifier_cache_filename_get", NULL, NULL); diff --git a/source/blender/makesrna/intern/rna_space.c b/source/blender/makesrna/intern/rna_space.c index ffcf12edb2d..b262e6412e3 100644 --- a/source/blender/makesrna/intern/rna_space.c +++ b/source/blender/makesrna/intern/rna_space.c @@ -1980,7 +1980,7 @@ static void rna_def_space_image_uv(BlenderRNA *brna) static EnumPropertyItem other_uv_filter_items[] = { {SI_FILTER_ALL, "ALL", 0, "All", "No filter, show all islands from other objects"}, {SI_FILTER_SAME_IMAGE, "SAME_IMAGE", ICON_IMAGE_DATA, "Same Image", - "Only show others' UV islads who's active image matches image of the active face"}, + "Only show others' UV islands whose active image matches image of the active face"}, {0, NULL, 0, NULL, NULL} }; diff --git a/source/blender/makesrna/intern/rna_userdef.c b/source/blender/makesrna/intern/rna_userdef.c index 8ad016007f4..10807c32b91 100644 --- a/source/blender/makesrna/intern/rna_userdef.c +++ b/source/blender/makesrna/intern/rna_userdef.c @@ -52,15 +52,6 @@ #include "BLT_lang.h" #include "GPU_buffers.h" -#ifdef WITH_CYCLES -static EnumPropertyItem compute_device_type_items[] = { - {USER_COMPUTE_DEVICE_NONE, "NONE", 0, "None", "Don't use compute device"}, - {USER_COMPUTE_DEVICE_CUDA, "CUDA", 0, "CUDA", "Use CUDA for GPU acceleration"}, - {USER_COMPUTE_DEVICE_OPENCL, "OPENCL", 0, "OpenCL", "Use OpenCL for GPU acceleration"}, - { 0, NULL, 0, NULL, NULL} -}; -#endif - #ifdef WITH_OPENSUBDIV static EnumPropertyItem opensubdiv_compute_type_items[] = { {USER_OPENSUBDIV_COMPUTE_NONE, "NONE", 0, "None", ""}, @@ -124,8 +115,6 @@ static EnumPropertyItem rna_enum_language_default_items[] = { #include "UI_interface.h" -#include "CCL_api.h" - #ifdef WITH_OPENSUBDIV # include "opensubdiv_capi.h" #endif @@ -476,78 +465,6 @@ static PointerRNA rna_Theme_space_list_generic_get(PointerRNA *ptr) } -#ifdef WITH_CYCLES -static EnumPropertyItem *rna_userdef_compute_device_type_itemf(bContext *UNUSED(C), PointerRNA *UNUSED(ptr), - PropertyRNA *UNUSED(prop), bool *r_free) -{ - EnumPropertyItem *item = NULL; - int totitem = 0; - - /* add supported device types */ - RNA_enum_items_add_value(&item, &totitem, compute_device_type_items, USER_COMPUTE_DEVICE_NONE); - if (CCL_compute_device_list(0)) - RNA_enum_items_add_value(&item, &totitem, compute_device_type_items, USER_COMPUTE_DEVICE_CUDA); - if (CCL_compute_device_list(1)) - RNA_enum_items_add_value(&item, &totitem, compute_device_type_items, USER_COMPUTE_DEVICE_OPENCL); - - RNA_enum_item_end(&item, &totitem); - *r_free = true; - - return item; -} - -static int rna_userdef_compute_device_get(PointerRNA *UNUSED(ptr)) -{ - if (U.compute_device_type == USER_COMPUTE_DEVICE_NONE) - return 0; - - return U.compute_device_id; -} - -static EnumPropertyItem *rna_userdef_compute_device_itemf(bContext *UNUSED(C), PointerRNA *UNUSED(ptr), - PropertyRNA *UNUSED(prop), bool *r_free) -{ - EnumPropertyItem tmp = {0, "", 0, "", ""}; - EnumPropertyItem *item = NULL; - int totitem = 0; - - if (U.compute_device_type == USER_COMPUTE_DEVICE_NONE) { - /* only add a single CPU device */ - tmp.value = 0; - tmp.name = "CPU"; - tmp.identifier = "CPU"; - RNA_enum_item_add(&item, &totitem, &tmp); - } - else { - /* get device list from cycles. it would be good to make this generic - * once we have more subsystems using opencl, for now this is easiest */ - int opencl = (U.compute_device_type == USER_COMPUTE_DEVICE_OPENCL); - CCLDeviceInfo *devices = CCL_compute_device_list(opencl); - int a; - - if (devices) { - for (a = 0; devices[a].identifier[0]; a++) { - tmp.value = devices[a].value; - tmp.identifier = devices[a].identifier; - tmp.name = devices[a].name; - RNA_enum_item_add(&item, &totitem, &tmp); - } - } - else { - tmp.value = 0; - tmp.name = "CPU"; - tmp.identifier = "CPU"; - RNA_enum_item_add(&item, &totitem, &tmp); - } - } - - RNA_enum_item_end(&item, &totitem); - *r_free = true; - - return item; -} -#endif - #ifdef WITH_OPENSUBDIV static EnumPropertyItem *rna_userdef_opensubdiv_compute_type_itemf(bContext *UNUSED(C), PointerRNA *UNUSED(ptr), PropertyRNA *UNUSED(prop), bool *r_free) @@ -3977,13 +3894,6 @@ static void rna_def_userdef_system(BlenderRNA *brna) {0, NULL, 0, NULL, NULL} }; -#ifdef WITH_CYCLES - static EnumPropertyItem compute_device_items[] = { - {0, "CPU", 0, "CPU", ""}, - { 0, NULL, 0, NULL, NULL} - }; -#endif - static EnumPropertyItem image_draw_methods[] = { {IMAGE_DRAW_METHOD_2DTEXTURE, "2DTEXTURE", 0, "2D Texture", "Use CPU for display transform and draw image with 2D texture"}, {IMAGE_DRAW_METHOD_GLSL, "GLSL", 0, "GLSL", "Use GLSL shaders for display transform and draw image with 2D texture"}, @@ -4275,23 +4185,6 @@ static void rna_def_userdef_system(BlenderRNA *brna) "Draw tool/property regions over the main region, when using Triple Buffer"); RNA_def_property_update(prop, 0, "rna_userdef_dpi_update"); -#ifdef WITH_CYCLES - prop = RNA_def_property(srna, "compute_device_type", PROP_ENUM, PROP_NONE); - RNA_def_property_flag(prop, PROP_ENUM_NO_CONTEXT); - RNA_def_property_enum_sdna(prop, NULL, "compute_device_type"); - RNA_def_property_enum_items(prop, compute_device_type_items); - RNA_def_property_enum_funcs(prop, NULL, NULL, "rna_userdef_compute_device_type_itemf"); - RNA_def_property_ui_text(prop, "Compute Device Type", "Device to use for computation (rendering with Cycles)"); - RNA_def_property_update(prop, NC_SPACE | ND_SPACE_PROPERTIES, NULL); - - prop = RNA_def_property(srna, "compute_device", PROP_ENUM, PROP_NONE); - RNA_def_property_flag(prop, PROP_ENUM_NO_CONTEXT); - RNA_def_property_enum_sdna(prop, NULL, "compute_device_id"); - RNA_def_property_enum_items(prop, compute_device_items); - RNA_def_property_enum_funcs(prop, "rna_userdef_compute_device_get", NULL, "rna_userdef_compute_device_itemf"); - RNA_def_property_ui_text(prop, "Compute Device", "Device to use for computation"); -#endif - #ifdef WITH_OPENSUBDIV prop = RNA_def_property(srna, "opensubdiv_compute_type", PROP_ENUM, PROP_NONE); RNA_def_property_flag(prop, PROP_ENUM_NO_CONTEXT); diff --git a/source/blender/makesrna/intern/rna_wm.c b/source/blender/makesrna/intern/rna_wm.c index 90081a93188..35c9c9bcc89 100644 --- a/source/blender/makesrna/intern/rna_wm.c +++ b/source/blender/makesrna/intern/rna_wm.c @@ -419,6 +419,7 @@ static EnumPropertyItem keymap_modifiers_items[] = { static EnumPropertyItem operator_flag_items[] = { {OPTYPE_REGISTER, "REGISTER", 0, "Register", "Display in the info window and support the redo toolbar panel"}, {OPTYPE_UNDO, "UNDO", 0, "Undo", "Push an undo event (needed for operator redo)"}, + {OPTYPE_UNDO_GROUPED, "UNDO_GROUPED", 0, "Grouped Undo", "Push a single undo event for repetead instances of this operator"}, {OPTYPE_BLOCKING, "BLOCKING", 0, "Blocking", "Block anything else from using the cursor"}, {OPTYPE_MACRO, "MACRO", 0, "Macro", "Use to check if an operator is a macro"}, {OPTYPE_GRAB_CURSOR, "GRAB_CURSOR", 0, "Grab Pointer", @@ -1139,6 +1140,7 @@ static char _operator_idname[OP_MAX_TYPENAME]; static char _operator_name[OP_MAX_TYPENAME]; static char _operator_descr[RNA_DYN_DESCR_MAX]; static char _operator_ctxt[RNA_DYN_DESCR_MAX]; +static char _operator_undo_group[OP_MAX_TYPENAME]; static StructRNA *rna_Operator_register(Main *bmain, ReportList *reports, void *data, const char *identifier, StructValidateFunc validate, StructCallbackFunc call, StructFreeFunc free) { @@ -1153,10 +1155,11 @@ static StructRNA *rna_Operator_register(Main *bmain, ReportList *reports, void * dummyot.name = _operator_name; /* only assigne the pointer, string is NULL'd */ dummyot.description = _operator_descr; /* only assigne the pointer, string is NULL'd */ dummyot.translation_context = _operator_ctxt; /* only assigne the pointer, string is NULL'd */ + dummyot.undo_group = _operator_undo_group; /* only assigne the pointer, string is NULL'd */ RNA_pointer_create(NULL, &RNA_Operator, &dummyop, &dummyotr); /* clear in case they are left unset */ - _operator_idname[0] = _operator_name[0] = _operator_descr[0] = '\0'; + _operator_idname[0] = _operator_name[0] = _operator_descr[0] = _operator_undo_group[0] = '\0'; /* We have to set default op context! */ strcpy(_operator_ctxt, BLT_I18NCONTEXT_OPERATOR_DEFAULT); @@ -1210,9 +1213,10 @@ static StructRNA *rna_Operator_register(Main *bmain, ReportList *reports, void * int namelen = strlen(_operator_name) + 1; int desclen = strlen(_operator_descr) + 1; int ctxtlen = strlen(_operator_ctxt) + 1; + int ugrouplen = strlen(_operator_undo_group) + 1; char *ch; /* 2 terminators and 3 to convert a.b -> A_OT_b */ - ch = MEM_callocN(sizeof(char) * (idlen + namelen + desclen + ctxtlen), "_operator_idname"); + ch = MEM_callocN(sizeof(char) * (idlen + namelen + desclen + ctxtlen + ugrouplen), "_operator_idname"); WM_operator_bl_idname(ch, _operator_idname); /* convert the idname from python */ dummyot.idname = ch; ch += idlen; @@ -1224,6 +1228,9 @@ static StructRNA *rna_Operator_register(Main *bmain, ReportList *reports, void * ch += desclen; strcpy(ch, _operator_ctxt); dummyot.translation_context = ch; + ch += ctxtlen; + strcpy(ch, _operator_undo_group); + dummyot.undo_group = ch; } } @@ -1280,10 +1287,11 @@ static StructRNA *rna_MacroOperator_register(Main *bmain, ReportList *reports, v dummyot.name = _operator_name; /* only assigne the pointer, string is NULL'd */ dummyot.description = _operator_descr; /* only assigne the pointer, string is NULL'd */ dummyot.translation_context = _operator_ctxt; /* only assigne the pointer, string is NULL'd */ + dummyot.undo_group = _operator_undo_group; /* only assigne the pointer, string is NULL'd */ RNA_pointer_create(NULL, &RNA_Macro, &dummyop, &dummyotr); /* clear in case they are left unset */ - _operator_idname[0] = _operator_name[0] = _operator_descr[0] = '\0'; + _operator_idname[0] = _operator_name[0] = _operator_descr[0] = _operator_undo_group[0] = '\0'; /* We have to set default op context! */ strcpy(_operator_ctxt, BLT_I18NCONTEXT_OPERATOR_DEFAULT); @@ -1297,9 +1305,10 @@ static StructRNA *rna_MacroOperator_register(Main *bmain, ReportList *reports, v int namelen = strlen(_operator_name) + 1; int desclen = strlen(_operator_descr) + 1; int ctxtlen = strlen(_operator_ctxt) + 1; + int ugrouplen = strlen(_operator_undo_group) + 1; char *ch; /* 2 terminators and 3 to convert a.b -> A_OT_b */ - ch = MEM_callocN(sizeof(char) * (idlen + namelen + desclen + ctxtlen), "_operator_idname"); + ch = MEM_callocN(sizeof(char) * (idlen + namelen + desclen + ctxtlen + ugrouplen), "_operator_idname"); WM_operator_bl_idname(ch, _operator_idname); /* convert the idname from python */ dummyot.idname = ch; ch += idlen; @@ -1311,6 +1320,9 @@ static StructRNA *rna_MacroOperator_register(Main *bmain, ReportList *reports, v ch += desclen; strcpy(ch, _operator_ctxt); dummyot.translation_context = ch; + ch += ctxtlen; + strcpy(ch, _operator_undo_group); + dummyot.undo_group = ch; } if (strlen(identifier) >= sizeof(dummyop.idname)) { @@ -1401,6 +1413,16 @@ static void rna_Operator_bl_description_set(PointerRNA *ptr, const char *value) assert(!"setting the bl_description on a non-builtin operator"); } +static void rna_Operator_bl_undo_group_set(PointerRNA *ptr, const char *value) +{ + wmOperator *data = (wmOperator *)(ptr->data); + char *str = (char *)data->type->undo_group; + if (!str[0]) + BLI_strncpy(str, value, OP_MAX_TYPENAME); /* utf8 already ensured */ + else + assert(!"setting the bl_undo_group on a non-builtin operator"); +} + static void rna_KeyMapItem_update(Main *UNUSED(bmain), Scene *UNUSED(scene), PointerRNA *ptr) { wmKeyMapItem *kmi = ptr->data; @@ -1509,6 +1531,14 @@ static void rna_def_operator(BlenderRNA *brna) RNA_def_property_flag(prop, PROP_REGISTER_OPTIONAL); RNA_def_property_clear_flag(prop, PROP_NEVER_NULL); /* check for NULL */ + prop = RNA_def_property(srna, "bl_undo_group", PROP_STRING, PROP_NONE); + RNA_def_property_string_sdna(prop, NULL, "type->undo_group"); + RNA_def_property_string_maxlength(prop, OP_MAX_TYPENAME); /* else it uses the pointer size! */ + RNA_def_property_string_funcs(prop, NULL, NULL, "rna_Operator_bl_undo_group_set"); + /* RNA_def_property_clear_flag(prop, PROP_EDITABLE); */ + RNA_def_property_flag(prop, PROP_REGISTER_OPTIONAL); + RNA_def_property_clear_flag(prop, PROP_NEVER_NULL); /* check for NULL */ + prop = RNA_def_property(srna, "bl_options", PROP_ENUM, PROP_NONE); RNA_def_property_enum_sdna(prop, NULL, "type->flag"); RNA_def_property_enum_items(prop, operator_flag_items); @@ -1587,6 +1617,14 @@ static void rna_def_macro_operator(BlenderRNA *brna) RNA_def_property_flag(prop, PROP_REGISTER_OPTIONAL); RNA_def_property_clear_flag(prop, PROP_NEVER_NULL); /* check for NULL */ + prop = RNA_def_property(srna, "bl_undo_group", PROP_STRING, PROP_NONE); + RNA_def_property_string_sdna(prop, NULL, "type->undo_group"); + RNA_def_property_string_maxlength(prop, OP_MAX_TYPENAME); /* else it uses the pointer size! */ + RNA_def_property_string_funcs(prop, NULL, NULL, "rna_Operator_bl_undo_group_set"); + /* RNA_def_property_clear_flag(prop, PROP_EDITABLE); */ + RNA_def_property_flag(prop, PROP_REGISTER_OPTIONAL); + RNA_def_property_clear_flag(prop, PROP_NEVER_NULL); /* check for NULL */ + prop = RNA_def_property(srna, "bl_options", PROP_ENUM, PROP_NONE); RNA_def_property_enum_sdna(prop, NULL, "type->flag"); RNA_def_property_enum_items(prop, operator_flag_items); diff --git a/source/blender/modifiers/intern/MOD_array.c b/source/blender/modifiers/intern/MOD_array.c index ecbc3891e8c..b049457e640 100644 --- a/source/blender/modifiers/intern/MOD_array.c +++ b/source/blender/modifiers/intern/MOD_array.c @@ -143,17 +143,17 @@ static void updateDepsgraph(ModifierData *md, { ArrayModifierData *amd = (ArrayModifierData *)md; if (amd->start_cap != NULL) { - DEG_add_object_relation(node, amd->start_cap, DEG_OB_COMP_TRANSFORM, "Hook Modifier Start Cap"); + DEG_add_object_relation(node, amd->start_cap, DEG_OB_COMP_TRANSFORM, "Array Modifier Start Cap"); } if (amd->end_cap != NULL) { - DEG_add_object_relation(node, amd->end_cap, DEG_OB_COMP_TRANSFORM, "Hook Modifier End Cap"); + DEG_add_object_relation(node, amd->end_cap, DEG_OB_COMP_TRANSFORM, "Array Modifier End Cap"); } if (amd->curve_ob) { - DEG_add_object_relation(node, amd->end_cap, DEG_OB_COMP_GEOMETRY, "Hook Modifier Curve"); + DEG_add_object_relation(node, amd->curve_ob, DEG_OB_COMP_GEOMETRY, "Array Modifier Curve"); DEG_add_special_eval_flag(scene->depsgraph, &amd->curve_ob->id, DAG_EVAL_NEED_CURVE_PATH); } if (amd->offset_ob != NULL) { - DEG_add_object_relation(node, amd->offset_ob, DEG_OB_COMP_TRANSFORM, "Hook Modifier Offset"); + DEG_add_object_relation(node, amd->offset_ob, DEG_OB_COMP_TRANSFORM, "Array Modifier Offset"); } } diff --git a/source/blender/modifiers/intern/MOD_hook.c b/source/blender/modifiers/intern/MOD_hook.c index 83c4ca7984c..9186b10d8ca 100644 --- a/source/blender/modifiers/intern/MOD_hook.c +++ b/source/blender/modifiers/intern/MOD_hook.c @@ -145,12 +145,9 @@ static void updateDepsgraph(ModifierData *md, HookModifierData *hmd = (HookModifierData *)md; if (hmd->object != NULL) { if (hmd->subtarget[0]) { - DEG_add_bone_relation(node, hmd->object, hmd->subtarget, DEG_OB_COMP_TRANSFORM, "Hook Modifier"); DEG_add_bone_relation(node, hmd->object, hmd->subtarget, DEG_OB_COMP_BONE, "Hook Modifier"); } - else { - DEG_add_object_relation(node, hmd->object, DEG_OB_COMP_TRANSFORM, "Hook Modifier"); - } + DEG_add_object_relation(node, hmd->object, DEG_OB_COMP_TRANSFORM, "Hook Modifier"); } /* We need own transformation as well. */ DEG_add_object_relation(node, ob, DEG_OB_COMP_TRANSFORM, "Hook Modifier"); diff --git a/source/blender/python/bmesh/bmesh_py_types.c b/source/blender/python/bmesh/bmesh_py_types.c index 1d951bae48b..b20c03bee28 100644 --- a/source/blender/python/bmesh/bmesh_py_types.c +++ b/source/blender/python/bmesh/bmesh_py_types.c @@ -2233,7 +2233,7 @@ static PyObject *bpy_bmfaceseq_new(BPy_BMElemSeq *self, PyObject *args) } /* check if the face exists */ - if (BM_face_exists(vert_array, vert_seq_len, NULL)) { + if (BM_face_exists(vert_array, vert_seq_len) != NULL) { PyErr_SetString(PyExc_ValueError, "faces.new(verts): face already exists"); goto cleanup; @@ -2426,7 +2426,8 @@ static PyObject *bpy_bmfaceseq_get__method(BPy_BMElemSeq *self, PyObject *args) return NULL; } - if (BM_face_exists(vert_array, vert_seq_len, &f)) { + f = BM_face_exists(vert_array, vert_seq_len); + if (f != NULL) { ret = BPy_BMFace_CreatePyObject(bm, f); } else { diff --git a/source/blender/python/intern/bpy_library_load.c b/source/blender/python/intern/bpy_library_load.c index ec69abbb1df..15f3c665fcf 100644 --- a/source/blender/python/intern/bpy_library_load.c +++ b/source/blender/python/intern/bpy_library_load.c @@ -33,6 +33,7 @@ #include <stddef.h> #include "BLI_utildefines.h" +#include "BLI_ghash.h" #include "BLI_string.h" #include "BLI_linklist.h" #include "BLI_path_util.h" @@ -405,6 +406,8 @@ static PyObject *bpy_lib_exit(BPy_Library *self, PyObject *UNUSED(args)) BLO_blendhandle_close(self->blo_handle); self->blo_handle = NULL; + GHash *old_to_new_ids = BLI_ghash_ptr_new(__func__); + /* copied from wm_operator.c */ { /* mark all library linked objects to be updated */ @@ -412,7 +415,7 @@ static PyObject *bpy_lib_exit(BPy_Library *self, PyObject *UNUSED(args)) /* append, rather than linking */ if ((self->flag & FILE_LINK) == 0) { - BKE_library_make_local(bmain, lib, true, false); + BKE_library_make_local(bmain, lib, old_to_new_ids, true, false); } } @@ -439,6 +442,7 @@ static PyObject *bpy_lib_exit(BPy_Library *self, PyObject *UNUSED(args)) ID *id; id = PyCapsule_GetPointer(item, NULL); + id = BLI_ghash_lookup_default(old_to_new_ids, id, id); Py_DECREF(item); RNA_id_pointer_create(id, &id_ptr); @@ -452,6 +456,7 @@ static PyObject *bpy_lib_exit(BPy_Library *self, PyObject *UNUSED(args)) } #endif /* USE_RNA_DATABLOCKS */ + BLI_ghash_free(old_to_new_ids, NULL, NULL); Py_RETURN_NONE; } } diff --git a/source/blender/render/intern/source/shadeoutput.c b/source/blender/render/intern/source/shadeoutput.c index 9dec2698720..3d6462e09a0 100644 --- a/source/blender/render/intern/source/shadeoutput.c +++ b/source/blender/render/intern/source/shadeoutput.c @@ -2064,11 +2064,13 @@ static float lamp_get_data_internal(ShadeInput *shi, GroupObject *go, float col[ if (lar->mode & LA_SHAD_TEX) do_lamp_tex(lar, lv, shi, shadow, LA_SHAD_TEX); - lamp_get_shadow(lar, shi, inp, shadfac, shi->depth); + if (R.r.mode & R_SHADOW) { + lamp_get_shadow(lar, shi, inp, shadfac, shi->depth); - shadow[0] = 1.0f - ((1.0f - shadfac[0] * shadfac[3]) * (1.0f - shadow[0])); - shadow[1] = 1.0f - ((1.0f - shadfac[1] * shadfac[3]) * (1.0f - shadow[1])); - shadow[2] = 1.0f - ((1.0f - shadfac[2] * shadfac[3]) * (1.0f - shadow[2])); + shadow[0] = 1.0f - ((1.0f - shadfac[0] * shadfac[3]) * (1.0f - shadow[0])); + shadow[1] = 1.0f - ((1.0f - shadfac[1] * shadfac[3]) * (1.0f - shadow[1])); + shadow[2] = 1.0f - ((1.0f - shadfac[2] * shadfac[3]) * (1.0f - shadow[2])); + } } return visifac; diff --git a/source/blender/windowmanager/WM_types.h b/source/blender/windowmanager/WM_types.h index 0fe3e8a0fcf..cd46e24264d 100644 --- a/source/blender/windowmanager/WM_types.h +++ b/source/blender/windowmanager/WM_types.h @@ -138,6 +138,7 @@ enum { OPTYPE_INTERNAL = (1 << 6), OPTYPE_LOCK_BYPASS = (1 << 7), /* Allow operator to run when interface is locked */ + OPTYPE_UNDO_GROUPED = (1 << 8), /* Special type of undo which doesn't store itself multiple times */ }; /* context to call operator in for WM_operator_name_call */ @@ -522,6 +523,7 @@ typedef struct wmOperatorType { const char *idname; /* unique identifier */ const char *translation_context; const char *description; /* tooltips and python docs */ + const char *undo_group; /* identifier to group operators together */ /* this callback executes the operator without any interactive input, * parameters may be provided through operator properties. cannot use diff --git a/source/blender/windowmanager/intern/wm_event_system.c b/source/blender/windowmanager/intern/wm_event_system.c index dc34e8015c9..d2b0acd836b 100644 --- a/source/blender/windowmanager/intern/wm_event_system.c +++ b/source/blender/windowmanager/intern/wm_event_system.c @@ -727,10 +727,13 @@ static void wm_operator_finished(bContext *C, wmOperator *op, const bool repeat) /* we don't want to do undo pushes for operators that are being * called from operators that already do an undo push. usually * this will happen for python operators that call C operators */ - if (wm->op_undo_depth == 0) + if (wm->op_undo_depth == 0) { if (op->type->flag & OPTYPE_UNDO) ED_undo_push_op(C, op); - + else if (op->type->flag & OPTYPE_UNDO_GROUPED) + ED_undo_grouped_push_op(C, op); + } + if (repeat == 0) { if (G.debug & G_DEBUG_WM) { char *buf = WM_operator_pystring(C, op, false, true); @@ -1854,9 +1857,12 @@ static int wm_handler_fileselect_do(bContext *C, ListBase *handlers, wmEventHand wm->op_undo_depth--; /* XXX check this carefully, CTX_wm_manager(C) == wm is a bit hackish */ - if (CTX_wm_manager(C) == wm && wm->op_undo_depth == 0) + if (CTX_wm_manager(C) == wm && wm->op_undo_depth == 0) { if (handler->op->type->flag & OPTYPE_UNDO) ED_undo_push_op(C, handler->op); + else if (handler->op->type->flag & OPTYPE_UNDO_GROUPED) + ED_undo_grouped_push_op(C, handler->op); + } if (handler->op->reports->list.first) { diff --git a/source/blender/windowmanager/intern/wm_files_link.c b/source/blender/windowmanager/intern/wm_files_link.c index e872ec1a524..3b733f9558c 100644 --- a/source/blender/windowmanager/intern/wm_files_link.c +++ b/source/blender/windowmanager/intern/wm_files_link.c @@ -419,7 +419,7 @@ static int wm_link_append_exec(bContext *C, wmOperator *op) const bool use_recursive = RNA_boolean_get(op->ptr, "use_recursive"); if (use_recursive) { - BKE_library_make_local(bmain, NULL, true, set_fake); + BKE_library_make_local(bmain, NULL, NULL, true, set_fake); } else { LinkNode *itemlink; @@ -430,7 +430,7 @@ static int wm_link_append_exec(bContext *C, wmOperator *op) ID *new_id = ((WMLinkAppendDataItem *)(itemlink->link))->new_id; if (new_id && !BLI_gset_haskey(done_libraries, new_id->lib)) { - BKE_library_make_local(bmain, new_id->lib, true, set_fake); + BKE_library_make_local(bmain, new_id->lib, NULL, true, set_fake); BLI_gset_insert(done_libraries, new_id->lib); } } diff --git a/source/blenderplayer/bad_level_call_stubs/stubs.c b/source/blenderplayer/bad_level_call_stubs/stubs.c index d8a4ddc8d4f..6040dfff644 100644 --- a/source/blenderplayer/bad_level_call_stubs/stubs.c +++ b/source/blenderplayer/bad_level_call_stubs/stubs.c @@ -142,7 +142,6 @@ struct wmWindowManager; # pragma GCC diagnostic ignored "-Wunused-parameter" #endif -#include "../../intern/cycles/blender/CCL_api.h" #include "../../intern/dualcon/dualcon.h" #include "../../intern/elbeem/extern/elbeem.h" #include "../blender/blenkernel/BKE_modifier.h" @@ -770,10 +769,6 @@ void *dualcon(const DualConInput *input_mesh, float scale, int depth) RET_ZERO -/* intern/cycles */ -struct CCLDeviceInfo; -struct CCLDeviceInfo *CCL_compute_device_list(int opencl) RET_NULL - /* compositor */ void COM_execute(RenderData *rd, Scene *scene, bNodeTree *editingtree, int rendering, const ColorManagedViewSettings *viewSettings, const ColorManagedDisplaySettings *displaySettings, diff --git a/source/creator/CMakeLists.txt b/source/creator/CMakeLists.txt index f65688e1304..10af0d5489e 100644 --- a/source/creator/CMakeLists.txt +++ b/source/creator/CMakeLists.txt @@ -1153,13 +1153,12 @@ if(WIN32 AND NOT WITH_PYTHON_MODULE) COMPONENT Blender DESTINATION "." ) - + if(CMAKE_CL_64) + set(_WIN_PLATFORM x64) + else() + set(_WIN_PLATFORM x86) + endif() if(MSVC12_REDIST_DIR) - if(CMAKE_CL_64) - set(_WIN_PLATFORM x64) - else() - set(_WIN_PLATFORM x86) - endif() install( FILES ${MSVC12_REDIST_DIR}/${_WIN_PLATFORM}/Microsoft.VC120.CRT/msvcp120.dll @@ -1173,4 +1172,51 @@ if(WIN32 AND NOT WITH_PYTHON_MODULE) ) endif() endif() + + if(MSVC14_REDIST_DIR) + set(KITSDIRx86 "$ENV{${ProgramFilesX86_NAME}}/Windows Kits/10/") + set(KITSDIR "$ENV{ProgramFiles}/Windows Kits/10/") + if(IS_DIRECTORY ${KITSDIR}) + set(KITSPATH "${KITSDIR}/Redist/ucrt/DLLs/${_WIN_PLATFORM}") + else() + if(IS_DIRECTORY ${KITSDIRx86}) + set(KITSPATH "${KITSDIRx86}/Redist/ucrt/DLLs/${_WIN_PLATFORM}") + else() + message(FATAL_ERROR "Windows 10 SDK directory not found") + endif() + endif() + + install( + FILES + ${KITSPATH}/api-ms-win-core-file-l1-2-0.dll + ${KITSPATH}/api-ms-win-core-file-l2-1-0.dll + ${KITSPATH}/api-ms-win-core-localization-l1-2-0.dll + ${KITSPATH}/api-ms-win-core-processthreads-l1-1-0.dll + ${KITSPATH}/api-ms-win-core-processthreads-l1-1-1.dll + ${KITSPATH}/api-ms-win-core-synch-l1-1-0.dll + ${KITSPATH}/api-ms-win-core-synch-l1-2-0.dll + ${KITSPATH}/api-ms-win-core-timezone-l1-1-0.dll + ${KITSPATH}/api-ms-win-crt-conio-l1-1-0.dll + ${KITSPATH}/api-ms-win-crt-convert-l1-1-0.dll + ${KITSPATH}/api-ms-win-crt-environment-l1-1-0.dll + ${KITSPATH}/api-ms-win-crt-filesystem-l1-1-0.dll + ${KITSPATH}/api-ms-win-crt-heap-l1-1-0.dll + ${KITSPATH}/api-ms-win-crt-locale-l1-1-0.dll + ${KITSPATH}/api-ms-win-crt-math-l1-1-0.dll + ${KITSPATH}/api-ms-win-crt-process-l1-1-0.dll + ${KITSPATH}/api-ms-win-crt-runtime-l1-1-0.dll + ${KITSPATH}/api-ms-win-crt-stdio-l1-1-0.dll + ${KITSPATH}/api-ms-win-crt-string-l1-1-0.dll + ${KITSPATH}/api-ms-win-crt-time-l1-1-0.dll + ${KITSPATH}/ucrtbase.dll + ${MSVC14_REDIST_DIR}/${_WIN_PLATFORM}/Microsoft.VC140.CRT/vcruntime140.dll + DESTINATION "." + ) + if(WITH_OPENMP) + install( + FILES ${MSVC14_REDIST_DIR}/${_WIN_PLATFORM}/Microsoft.VC140.OpenMP/vcomp140.dll + DESTINATION "." + ) + endif() + endif() endif() diff --git a/source/gameengine/VideoTexture/VideoDeckLink.cpp b/source/gameengine/VideoTexture/VideoDeckLink.cpp index 4f5e34896fc..c588a4b33cf 100644 --- a/source/gameengine/VideoTexture/VideoDeckLink.cpp +++ b/source/gameengine/VideoTexture/VideoDeckLink.cpp @@ -544,12 +544,12 @@ HRESULT STDMETHODCALLTYPE PinnedMemoryAllocator::QueryInterface(REFIID /*iid*/, ULONG STDMETHODCALLTYPE PinnedMemoryAllocator::AddRef(void) { - return atomic_add_uint32(&mRefCount, 1U); + return atomic_add_and_fetch_uint32(&mRefCount, 1U); } ULONG STDMETHODCALLTYPE PinnedMemoryAllocator::Release(void) { - uint32_t newCount = atomic_sub_uint32(&mRefCount, 1U); + uint32_t newCount = atomic_sub_and_fetch_uint32(&mRefCount, 1U); if (newCount == 0) delete this; return (ULONG)newCount; diff --git a/tests/python/CMakeLists.txt b/tests/python/CMakeLists.txt index 8487c6a2094..a29b612e0ef 100644 --- a/tests/python/CMakeLists.txt +++ b/tests/python/CMakeLists.txt @@ -130,7 +130,7 @@ add_test(export_obj_cube ${TEST_BLENDER_EXE} --run={'FINISHED'}&bpy.ops.export_scene.obj\(filepath='${TEST_OUT_DIR}/export_obj_cube.obj',use_selection=False\) --md5_source=${TEST_OUT_DIR}/export_obj_cube.obj --md5_source=${TEST_OUT_DIR}/export_obj_cube.mtl - --md5=826f74e6b7a2128b0b61a52071ada36e --md5_method=FILE + --md5=e80660437ad9bfe082849641c361a233 --md5_method=FILE ) add_test(export_obj_nurbs ${TEST_BLENDER_EXE} |