From a9ee2afce048b57ee7704b44b95354b700b341c2 Mon Sep 17 00:00:00 2001 From: Benjamin Skinner Date: Mon, 17 Aug 2020 11:32:25 -0400 Subject: [PATCH] Initial open source release of HdCycles (v0.7.0) --- .clang-format | 127 ++ .clang-tidy | 11 + .gitignore | 1 + CMakeLists.txt | 124 ++ LICENSE-THIRDPARTY.txt | 121 ++ LICENSE.txt | 175 ++ README.md | 167 +- cmake/defaults/CXXDefaults.cmake | 94 + cmake/defaults/CXXHelpers.cmake | 36 + cmake/defaults/Options.cmake | 205 ++ cmake/defaults/Packages.cmake | 195 ++ cmake/defaults/ProjectDefaults.cmake | 51 + cmake/defaults/Version.cmake | 17 + cmake/defaults/clangdefaults.cmake | 32 + cmake/defaults/gccclangshareddefaults.cmake | 60 + cmake/defaults/gccdefaults.cmake | 27 + cmake/defaults/msvcdefaults.cmake | 110 ++ cmake/macros/Private.cmake | 1375 ++++++++++++++ cmake/macros/Public.cmake | 1108 +++++++++++ cmake/macros/compilePython.py | 66 + cmake/macros/copyHeaderForBuild.cmake | 29 + cmake/macros/generateDocs.py | 158 ++ cmake/macros/shebang.py | 49 + cmake/macros/testWrapper.py | 309 +++ cmake/modules/FindCycles.cmake | 95 + cmake/modules/FindDraco.cmake | 30 + cmake/modules/FindGLEW.cmake | 132 ++ cmake/modules/FindHoudini.cmake | 194 ++ cmake/modules/FindMaterialX.cmake | 125 ++ cmake/modules/FindOSL.cmake | 111 ++ cmake/modules/FindOpenColorIO.cmake | 110 ++ cmake/modules/FindOpenEXR.cmake | 100 + cmake/modules/FindOpenImageIO.cmake | 154 ++ cmake/modules/FindOpenJPEG.cmake | 75 + cmake/modules/FindOpenSubdiv.cmake | 132 ++ cmake/modules/FindOpenVDB.cmake | 56 + cmake/modules/FindPyOpenGL.cmake | 46 + cmake/modules/FindPySide.cmake | 79 + cmake/modules/FindTBB.cmake | 219 +++ cmake/modules/FindUSD.cmake | 264 +++ plugin/CMakeLists.txt | 16 + plugin/hdCycles/CMakeLists.txt | 84 + plugin/hdCycles/Mikktspace/mikktspace.c | 1890 +++++++++++++++++++ plugin/hdCycles/Mikktspace/mikktspace.h | 145 ++ plugin/hdCycles/api.h | 42 + plugin/hdCycles/basisCurves.cpp | 659 +++++++ plugin/hdCycles/basisCurves.h | 179 ++ plugin/hdCycles/camera.cpp | 425 +++++ plugin/hdCycles/camera.h | 226 +++ plugin/hdCycles/config.cpp | 227 +++ plugin/hdCycles/config.h | 318 ++++ plugin/hdCycles/hdcycles.h | 20 + plugin/hdCycles/instancer.cpp | 446 +++++ plugin/hdCycles/instancer.h | 62 + plugin/hdCycles/light.cpp | 430 +++++ plugin/hdCycles/light.h | 141 ++ plugin/hdCycles/material.cpp | 634 +++++++ plugin/hdCycles/material.h | 130 ++ plugin/hdCycles/mesh.cpp | 936 +++++++++ plugin/hdCycles/mesh.h | 283 +++ plugin/hdCycles/plugInfo.json | 22 + plugin/hdCycles/points.cpp | 382 ++++ plugin/hdCycles/points.h | 164 ++ plugin/hdCycles/renderBuffer.cpp | 236 +++ plugin/hdCycles/renderBuffer.h | 165 ++ plugin/hdCycles/renderDelegate.cpp | 591 ++++++ plugin/hdCycles/renderDelegate.h | 189 ++ plugin/hdCycles/renderParam.cpp | 858 +++++++++ plugin/hdCycles/renderParam.h | 531 ++++++ plugin/hdCycles/renderPass.cpp | 147 ++ plugin/hdCycles/renderPass.h | 99 + plugin/hdCycles/rendererPlugin.cpp | 46 + plugin/hdCycles/rendererPlugin.h | 78 + plugin/hdCycles/utils.cpp | 576 ++++++ plugin/hdCycles/utils.h | 296 +++ plugin/ndrCycles/CMakeLists.txt | 64 + plugin/ndrCycles/api.h | 42 + plugin/ndrCycles/discovery.cpp | 95 + plugin/ndrCycles/discovery.h | 66 + plugin/ndrCycles/parser.cpp | 101 + plugin/ndrCycles/parser.h | 71 + plugin/ndrCycles/plugInfo.json | 38 + tools/CMakeLists.txt | 17 + tools/houdini/CMakeLists.txt | 51 + tools/houdini/hda_generator.cpp | 519 +++++ 85 files changed, 19305 insertions(+), 1 deletion(-) create mode 100644 .clang-format create mode 100644 .clang-tidy create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 LICENSE-THIRDPARTY.txt create mode 100644 LICENSE.txt create mode 100644 cmake/defaults/CXXDefaults.cmake create mode 100644 cmake/defaults/CXXHelpers.cmake create mode 100644 cmake/defaults/Options.cmake create mode 100644 cmake/defaults/Packages.cmake create mode 100644 cmake/defaults/ProjectDefaults.cmake create mode 100644 cmake/defaults/Version.cmake create mode 100644 cmake/defaults/clangdefaults.cmake create mode 100644 cmake/defaults/gccclangshareddefaults.cmake create mode 100644 cmake/defaults/gccdefaults.cmake create mode 100644 cmake/defaults/msvcdefaults.cmake create mode 100644 cmake/macros/Private.cmake create mode 100644 cmake/macros/Public.cmake create mode 100644 cmake/macros/compilePython.py create mode 100644 cmake/macros/copyHeaderForBuild.cmake create mode 100644 cmake/macros/generateDocs.py create mode 100644 cmake/macros/shebang.py create mode 100644 cmake/macros/testWrapper.py create mode 100644 cmake/modules/FindCycles.cmake create mode 100644 cmake/modules/FindDraco.cmake create mode 100644 cmake/modules/FindGLEW.cmake create mode 100644 cmake/modules/FindHoudini.cmake create mode 100644 cmake/modules/FindMaterialX.cmake create mode 100644 cmake/modules/FindOSL.cmake create mode 100644 cmake/modules/FindOpenColorIO.cmake create mode 100644 cmake/modules/FindOpenEXR.cmake create mode 100644 cmake/modules/FindOpenImageIO.cmake create mode 100644 cmake/modules/FindOpenJPEG.cmake create mode 100644 cmake/modules/FindOpenSubdiv.cmake create mode 100644 cmake/modules/FindOpenVDB.cmake create mode 100644 cmake/modules/FindPyOpenGL.cmake create mode 100644 cmake/modules/FindPySide.cmake create mode 100644 cmake/modules/FindTBB.cmake create mode 100644 cmake/modules/FindUSD.cmake create mode 100644 plugin/CMakeLists.txt create mode 100644 plugin/hdCycles/CMakeLists.txt create mode 100644 plugin/hdCycles/Mikktspace/mikktspace.c create mode 100644 plugin/hdCycles/Mikktspace/mikktspace.h create mode 100644 plugin/hdCycles/api.h create mode 100644 plugin/hdCycles/basisCurves.cpp create mode 100644 plugin/hdCycles/basisCurves.h create mode 100644 plugin/hdCycles/camera.cpp create mode 100644 plugin/hdCycles/camera.h create mode 100644 plugin/hdCycles/config.cpp create mode 100644 plugin/hdCycles/config.h create mode 100644 plugin/hdCycles/hdcycles.h create mode 100644 plugin/hdCycles/instancer.cpp create mode 100644 plugin/hdCycles/instancer.h create mode 100644 plugin/hdCycles/light.cpp create mode 100644 plugin/hdCycles/light.h create mode 100644 plugin/hdCycles/material.cpp create mode 100644 plugin/hdCycles/material.h create mode 100644 plugin/hdCycles/mesh.cpp create mode 100644 plugin/hdCycles/mesh.h create mode 100644 plugin/hdCycles/plugInfo.json create mode 100644 plugin/hdCycles/points.cpp create mode 100644 plugin/hdCycles/points.h create mode 100644 plugin/hdCycles/renderBuffer.cpp create mode 100644 plugin/hdCycles/renderBuffer.h create mode 100644 plugin/hdCycles/renderDelegate.cpp create mode 100644 plugin/hdCycles/renderDelegate.h create mode 100644 plugin/hdCycles/renderParam.cpp create mode 100644 plugin/hdCycles/renderParam.h create mode 100644 plugin/hdCycles/renderPass.cpp create mode 100644 plugin/hdCycles/renderPass.h create mode 100644 plugin/hdCycles/rendererPlugin.cpp create mode 100644 plugin/hdCycles/rendererPlugin.h create mode 100644 plugin/hdCycles/utils.cpp create mode 100644 plugin/hdCycles/utils.h create mode 100644 plugin/ndrCycles/CMakeLists.txt create mode 100644 plugin/ndrCycles/api.h create mode 100644 plugin/ndrCycles/discovery.cpp create mode 100644 plugin/ndrCycles/discovery.h create mode 100644 plugin/ndrCycles/parser.cpp create mode 100644 plugin/ndrCycles/parser.h create mode 100644 plugin/ndrCycles/plugInfo.json create mode 100644 tools/CMakeLists.txt create mode 100644 tools/houdini/CMakeLists.txt create mode 100644 tools/houdini/hda_generator.cpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..c83d9227 --- /dev/null +++ b/.clang-format @@ -0,0 +1,127 @@ +Language: Cpp +BasedOnStyle: WebKit +SpaceBeforeParens: ControlStatements + +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: true +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Right +AlignOperands: true +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: true +AllowShortCaseLabelsOnASingleLine: true +AllowShortFunctionsOnASingleLine: All +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: TopLevel +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false +BreakBeforeBinaryOperators: All +BreakBeforeBraces: WebKit +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeComma +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: false +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + - Regex: '.*' + Priority: 1 +IncludeIsMainRegex: '(Test)?$' +IndentCaseLabels: false +IndentPPDirectives: AfterHash +IndentWidth: 4 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 3 +NamespaceIndentation: Inner +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 4 +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 40 +PenaltyBreakBeforeFirstCallParameter: 100 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 75 +PenaltyReturnTypeOnItsOwnLine: 50 +PointerAlignment: Left +ReflowComments: false +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 8 +UseTab: Never +#... diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 00000000..6d556add --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,11 @@ +Checks: '-*,clang-analyzer-security*' +WarningsAsErrors: '' +HeaderFilterRegex: '(OpenImageIO/[a-zA-Z0-9_]+\.h)|(imageio)|(oiio)|(iv/)|(_pvt\.h)' +AnalyzeTemporaryDtors: false +User: lg +CheckOptions: + - key: modernize-use-nullptr.NullMacros + value: 'NULL' + - key: modernize-use-emplace.SmartPointers + value: 'OIIO::intrusive_ptr' +... \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..2cdc6ab6 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +[Bb]uild/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..55aa12a3 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,124 @@ +# Copyright 2020 Tangent Animation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +project(hdCycles) + +cmake_minimum_required(VERSION 3.12) + +set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/defaults + ${CMAKE_SOURCE_DIR}/cmake/modules + ${CMAKE_SOURCE_DIR}/cmake/macros) + +include(Public) +include(Options) +include(ProjectDefaults) + +# CXXDefaults will set a variety of variables for the project. +# Consume them here. This is an effort to keep the most common +# build files readable. +include(CXXDefaults) + +add_definitions(${_PXR_CXX_DEFINITIONS}) +set(CMAKE_CXX_FLAGS "${_PXR_CXX_FLAGS} ${CMAKE_CXX_FLAGS}") + +if(WIN32) + set( CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /NODEFAULTLIB:libmmd.lib /NODEFAULTLIB:libirc.lib /NODEFAULTLIB:svml_dispmd.lib /NODEFAULTLIB:libdecimal.lib" ) + + # Added for release debug symbols + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Zi") + set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /DEBUG /OPT:REF /OPT:ICF") +endif() + +set(CMAKE_CXX_STANDARD 14) + +# -- HdCycles Definitions + +add_definitions(-DWITH_OPENSUBDIV) + +add_definitions( + ${BOOST_DEFINITIONS} + ${OPENIMAGEIO_DEFINITIONS} +) + +add_definitions( + -DCCL_NAMESPACE_BEGIN=namespace\ ccl\ { + -DCCL_NAMESPACE_END=} +) + +if($ENV{USD_HOUDINI}) + option(USE_USD_HOUDINI "Use Houdini" ON) + add_definitions(-DUSE_USD_HOUDINI) + + set(CMAKE_SHARED_LIBRARY_PREFIX "libpxr_") + find_package(Houdini) + + set(HBoost_NO_SYSTEM_PATHS true) + set(HBoost_USE_STATIC_LIBS OFF CACHE BOOL "use static libs") + + add_definitions(-DHBOOST_ALL_NO_LIB) + add_definitions(-DHBOOST_ALL_DYN_LINK) +endif() + +# -- External Packages + +find_package(Python COMPONENTS Development Interpreter REQUIRED) +find_package(PyOpenGL REQUIRED) +find_package(PySide REQUIRED) + + +# Boost python libraries in some cases do not have a trailing version. +# Try the versioned library first and fall back to a non-versioned library. + +set(boost_python_component python${Python_VERSION_MAJOR}${Python_VERSION_MINOR}) +string(TOUPPER ${boost_python_component} boost_python_component_upper) + +find_package(Boost COMPONENTS ${boost_python_component} thread program_options filesystem) # Not required + +if(NOT ${Boost_${boost_python_component_upper}_FOUND}) + find_package(Boost COMPONENTS python thread program_options filesystem REQUIRED) +endif() + +find_package(GLEW REQUIRED) +find_package(OpenEXR REQUIRED) +find_package(OpenGL REQUIRED) +find_package(OpenJPEG REQUIRED) +find_package(TBB REQUIRED) +find_package(ZLIB REQUIRED) + +find_package(JPEG REQUIRED) +find_package(PNG REQUIRED) +find_package(TIFF REQUIRED) +find_package(OpenImageIO REQUIRED) + +find_package(OpenSubdiv) +find_package(OpenVDB) + +find_package(USD REQUIRED) +find_package(Cycles REQUIRED) + +# -- Build Flags + +if("${USD_MINOR_VERSION}" STRGREATER_EQUAL "20") +add_definitions(-DUSD_HAS_NEW_SDR_NODE_CONSTRUCTOR) +endif() + +if("${USD_MINOR_VERSION}" STRGREATER_EQUAL "20" AND + "${USD_PATCH_VERSION}" STRGREATER_EQUAL "8") +add_definitions(-DUSD_HAS_UDIM_RESOLVE_FIX) +endif() + +# -- Source + +add_subdirectory(plugin) +add_subdirectory(tools) diff --git a/LICENSE-THIRDPARTY.txt b/LICENSE-THIRDPARTY.txt new file mode 100644 index 00000000..aad707ed --- /dev/null +++ b/LICENSE-THIRDPARTY.txt @@ -0,0 +1,121 @@ +For main licese check LICENSE.txt + +======================================================================================================================== + + OTHER LICENSES + +------------------------------------------------------------------------------------------------------------------------ + +BSD-3-Clause + +Copyright (c) . All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + + 2. 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. + + 3. Neither the name of the copyright holder 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 HOLDER 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. + +------------------------------------------------------------------------------------------------------------------------ + +MIT + +MIT License Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +------------------------------------------------------------------------------------------------------------------------ + +Zlib + +zlib License Copyright (c) + +This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source distribution. + +------------------------------------------------------------------------------------------------------------------------ + +======================================================================================================================== + + ACKNOWLEDGEMENTS + +------------------------------------------------------------------------------------------------------------------------ + +BSD-3-Clause + +Blender Foundation + +------------------------------------------------------------------------------------------------------------------------ + +BSD-3-Clause + +Alex Fuller + +------------------------------------------------------------------------------------------------------------------------ + +MIT + +Justus Calvin + +------------------------------------------------------------------------------------------------------------------------ + +Zlib + +Morten S. Mikkelsen + +------------------------------------------------------------------------------------------------------------------------ + +Apache-2.0 + +Tangent Animation + +------------------------------------------------------------------------------------------------------------------------ + +Apache-2.0 + +Pixar + +------------------------------------------------------------------------------------------------------------------------ + +Apache-2.0 + +Luma Pictures + +------------------------------------------------------------------------------------------------------------------------ + +Apache-2.0 + +Advanced Micro Devices, Inc + +------------------------------------------------------------------------------------------------------------------------ + +Copyright notices + +Copyright 2020 Tangent Animation +Copyright 2020 Autodesk +Copyright 2020 Advanced Micro Devices, Inc +Copyright 2019 Pixar +Copyright 2019 Luma Pictures +Copyright 2018 Pixar +Copyright 2018 Alex Fuller +Copyright 2017 Pixar +Copyright 2016 Pixar +Copyright 2015 Justus Calvin +Copyright 2013 Blender Foundation. +Copyright 2011 Morten S. Mikkelsen +Copyright 2011 Blender Foundation. diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 00000000..67db8588 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,175 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. diff --git a/README.md b/README.md index 74d39b1a..83da0473 100644 --- a/README.md +++ b/README.md @@ -1 +1,166 @@ -# hCycles \ No newline at end of file +# HdCycles + +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE.txt) + +A USD/Hydra RenderDelegate plugin that adds support for the +Cycles renderer (Blender's physically based path tracer) to any client. + +Its goal is to render a one-to-one representation of a USD scene with Cycles. + +This requires three components: +* hdCycles (Cycles Hydra Delegate) +* ndrCycles (Cycles Node Definition Registry) +* usdCycles (Cycles USD Schema) + +The first two of which are implemented in this repository. + +## Building + +### Requirements + +* Cycles standalone libraries and headers [Source](https://git.blender.org/gitweb/gitweb.cgi/cycles.git) +* USD 19.xx+ + * Most of the USD requirements need to be available (OpenSubdiv, PNG, OpenImageIO, OpenVDB ...) + +### Linux + +Make sure to build cycles with `-DCMAKE_POSITION_INDEPENDENT_CODE=ON` + +```shell +mkdir build +cd build + +cmake -DUSD_ROOT=/path/to/usd/root \ + -DCYCLES_ROOT=/path/to/cycles \ + -DCYCLES_LIBRARY_DIR=/path/to/cycles/build/lib \ + -DCYCLES_INCLUDE_DIRS=/path/to/cycles/src \ + .. +``` + +### Windows + +```batch +mkdir build +cd build + +cmake -DUSD_ROOT=C:/path/to/usd/root \ + -DCYCLES_ROOT=C:/path/to/cycles \ + -DCYCLES_LIBRARY_DIR=C:/path/to/cycles/build/lib \ + -DCYCLES_INCLUDE_DIRS=C:/path/to/cycles/src \ + .. +``` + +## Installation + +Both the hdCycles plugin and the ndrCycles plugin must be added to the +`PXR_PLUGINPATH_NAME` environment variable. + +For example: + +`PXR_PLUGINPATH_NAME = %HDCYCLES_INSTALL_DIR%/plugin/usd/ndrCycles/resources;%HDCYCLES_INSTALL_DIR%/plugin/usd/hdCycles/resources` + +## Notes + +### Stability & Performance + +The codebase is in __active__ development and should be deemed as __unstable__. + +The primary priority is feature-completness. +Stability and performance will be addressed in the future. + +Please file issues for any question or problem. + +### Materials + +Currently Cycles materials are exported through custom additions to the Blender +USD Exporter. + +Of note, hdCycles expects a flattened Cycles Material graph, with no groups or +reroute nodes. It also does not use the `Material Output` node. Instead it +favours the USD/Hydra material binding inputs. + +For now only BSDF nodes are registered in the ndr plugin. + +### Lights + +Currently light node networks are unsupported via USD Lux. +[A proposal from Pixar](https://graphics.pixar.com/usd/docs/Adapting-UsdLux-to-the-Needs-of-Renderers.html) +plans to fix these limitations. +It is planned to support proper world and light materials once the proposal +is accepted. + +## Feature Set + +Currently supported features: + +| hdCycles | Feature | Status | Notes | +|:---------------:|-----------------------|--------|---------------------------------------------------| +| **Meshes** | Basic Mesh | ✅ | | +| | Geom Subsets | ✅ | | +| | Subdivision Surface | ✅ | Ability to set at render time | +| | Subdivision Surface (Adaptive) | ❌ | | +| | Generic Primvars | ✅ | | +| | UVs | ✅ | | +| | Display Colors | ✅ | | +| | Generic Primitives | ✅ | (Cube, sphere, cylinder) | +| | Tangents | ✅ | | +| | Point Instances | ✅ | | +| | usdCycles Schema Support| ❌ | | +| | Motion Blur (Transform) | ✅ | Still buggy. Disabled by default. | +| | Motion Blur (Deforming) | ❌ | | +| | Motion Blur (Velocity) | ❌ | | +| | Motion Blur (Instances) | ❌ | | +| **Materials** | Cycles Material Graph | ✅ | Ongoing support | +| | Displacement | ✅ | | +| | Volumetric | ❌ | | +| | OSL | ❌ | | +| | USD Preview Surface | ✅ | | +| **Volumes** | VDB Support | ❌ | (Likely will go with foundations implementation) | +| **Cameras** | Basic Support | ✅ | | +| | Depth of Field | ✅ | | +| | Motion Blur | ❌ | | +| | usdCycles Schema Support| ❌ | | +| **Curves** | BasisCurves | ✅ | | +| | NURBs | ❌ | | +| | Point Instancing | ❌ | | +| | Motion Blur (Transform) | ✅ | Still buggy. Disabled by default. | +| | Motion Blur (Deforming) | ❌ | | +| | Motion Blur (Velocity) | ❌ | | +| | Motion Blur (Instances) | ❌ | | +| | usdCycles Schema Support| ❌ | | +| **Points** | Points | ✅ | | +| | usdCycles Schema Support| ✅ | | +| | Motion Blur (Transform) | ❌ | | +| | Motion Blur (Velocity) | ❌ | | +| **Lights** | Point | ✅ | | +| | Directional | ✅ | | +| | Spot | ✅ | | +| | Area | ✅ | | +| | Dome | ✅ | | +| | Temperature | ✅ | We manually create a blackbody shader for now... | +| | Light Materials | ❌ | Pending support for new USD Light network shaders | +| | usdCycles Schema Support| ❌ | | +| **Render Settings** | Basic Render Settings |✅ | | +| | usdCycles Schema Support | ❌| Render Settings, Render Products, etc. | +| **Rendering** | Combined AOV | ✅ | | +| | Tiled Rendering | ❌ | | +| | Full AOV Support | ❌ | | +| | Cryptomatte | ❌ | | +| | OCIO Support | ✅ | | +| | CUDA/GPU Support | ❌ | Should just require adjustments to build scripts | + +## License + +This project is licensed under the [Apache 2 license](LICENSE.txt). + +For a full list of third-party licenses see: [LICENSE-THIRDPARTY](LICENSE-THIRDPARTY.txt) + +## Attribution + +This could not have been made without the help and reference of the following open source projects: +* [USD - Copyright 2016 Pixar Animation Studios - Apache 2.0 License](https://github.com/PixarAnimationStudios/USD) +* [arnold-usd - Copyright 2020 Autodesk - Apache 2.0 License](https://github.com/Autodesk/arnold-usd) +* [arnold-usd - Copyright 2019 Luma Pictures - Apache 2.0 License](https://github.com/LumaPictures/usd-arnold) +* [RadeonProRenderUSD - Copyright 2020 Advanced Micro Devices, Inc - Apache 2.0 License](https://github.com/GPUOpen-LibrariesAndSDKs/RadeonProRenderUSD) +* [Mikktspace - Copyright 2011 Morten S. Mikkelsen - Zlib](http://www.mikktspace.com/) +* [GafferCycles - Copyright 2018 Alex Fuller - BSD 3-Clause License](https://github.com/boberfly/GafferCycles) diff --git a/cmake/defaults/CXXDefaults.cmake b/cmake/defaults/CXXDefaults.cmake new file mode 100644 index 00000000..3414a664 --- /dev/null +++ b/cmake/defaults/CXXDefaults.cmake @@ -0,0 +1,94 @@ +# +# Copyright 2016 Pixar +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# +include(CXXHelpers) +include(Version) +include(Options) + +if (CMAKE_COMPILER_IS_GNUCXX) + include(gccdefaults) +elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + include(clangdefaults) +elseif(MSVC) + include(msvcdefaults) +endif() + +_add_define(GL_GLEXT_PROTOTYPES) +_add_define(GLX_GLXEXT_PROTOTYPES) + +# Python bindings for tf require this define. +_add_define(BOOST_PYTHON_NO_PY_SIGNATURES) + +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + _add_define(BUILD_OPTLEVEL_DEV) +endif() + +# Set plugin path environment variable name +if (PXR_OVERRIDE_PLUGINPATH_NAME) + _add_define("PXR_PLUGINPATH_NAME=${PXR_OVERRIDE_PLUGINPATH_NAME}") +endif() + +set(_PXR_CXX_FLAGS ${_PXR_CXX_FLAGS} ${_PXR_CXX_WARNING_FLAGS}) + +# CMake list to string. +string(REPLACE ";" " " _PXR_CXX_FLAGS "${_PXR_CXX_FLAGS}") + +# Set namespace configuration. +if (PXR_ENABLE_NAMESPACES) + set(PXR_USE_NAMESPACES "1") + + if (PXR_SET_EXTERNAL_NAMESPACE) + set(PXR_EXTERNAL_NAMESPACE ${PXR_SET_EXTERNAL_NAMESPACE}) + else() + set(PXR_EXTERNAL_NAMESPACE "pxr") + endif() + + if (PXR_SET_INTERNAL_NAMESPACE) + set(PXR_INTERNAL_NAMESPACE ${PXR_SET_INTERNAL_NAMESPACE}) + else() + set(PXR_INTERNAL_NAMESPACE "pxrInternal_v${PXR_MAJOR_VERSION}_${PXR_MINOR_VERSION}") + endif() + + message(STATUS "C++ namespace configured to (external) ${PXR_EXTERNAL_NAMESPACE}, (internal) ${PXR_INTERNAL_NAMESPACE}") +else() + set(PXR_USE_NAMESPACES "0") + message(STATUS "C++ namespaces disabled.") +endif() + +# Set Python configuration +if (PXR_ENABLE_PYTHON_SUPPORT) + set(PXR_PYTHON_SUPPORT_ENABLED "1") +else() + set(PXR_PYTHON_SUPPORT_ENABLED "0") +endif() + +# XXX: This is a workaround for an issue in which Python headers unequivocally +# redefine macros defined in standard library headers. This behavior +# prevents users from running strict builds with PXR_STRICT_BUILD_MODE +# as the redefinition warnings would cause build failures. +# +# The python official docs call this out here: +# https://docs.python.org/2/c-api/intro.html#include-files +# +# The long term plan is to adhere to the required behavior. +include_directories(SYSTEM ${PYTHON_INCLUDE_DIR}) diff --git a/cmake/defaults/CXXHelpers.cmake b/cmake/defaults/CXXHelpers.cmake new file mode 100644 index 00000000..b5670659 --- /dev/null +++ b/cmake/defaults/CXXHelpers.cmake @@ -0,0 +1,36 @@ +# +# Copyright 2016 Pixar +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# +function(_add_define definition) + list(APPEND _PXR_CXX_DEFINITIONS "-D${definition}") + set(_PXR_CXX_DEFINITIONS ${_PXR_CXX_DEFINITIONS} PARENT_SCOPE) +endfunction() + +function(_disable_warning flag) + if(MSVC) + list(APPEND _PXR_CXX_WARNING_FLAGS "/wd${flag}") + else() + list(APPEND _PXR_CXX_WARNING_FLAGS "-Wno-${flag}") + endif() + set(_PXR_CXX_WARNING_FLAGS ${_PXR_CXX_WARNING_FLAGS} PARENT_SCOPE) +endfunction() diff --git a/cmake/defaults/Options.cmake b/cmake/defaults/Options.cmake new file mode 100644 index 00000000..72b190b5 --- /dev/null +++ b/cmake/defaults/Options.cmake @@ -0,0 +1,205 @@ +# +# Copyright 2016 Pixar +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# +option(PXR_STRICT_BUILD_MODE "Turn on additional warnings. Enforce all warnings as errors." OFF) +option(PXR_VALIDATE_GENERATED_CODE "Validate script generated code" OFF) +option(PXR_HEADLESS_TEST_MODE "Disallow GUI based tests, useful for running under headless CI systems." OFF) +option(PXR_BUILD_TESTS "Build tests" ON) +option(PXR_BUILD_USD_TOOLS "Build commandline tools" ON) +option(PXR_BUILD_IMAGING "Build imaging components" ON) +option(PXR_BUILD_EMBREE_PLUGIN "Build embree imaging plugin" OFF) +option(PXR_BUILD_OPENIMAGEIO_PLUGIN "Build OpenImageIO plugin" OFF) +option(PXR_BUILD_OPENCOLORIO_PLUGIN "Build OpenColorIO plugin" OFF) +option(PXR_BUILD_USD_IMAGING "Build USD imaging components" ON) +option(PXR_BUILD_USDVIEW "Build usdview" ON) +option(PXR_BUILD_KATANA_PLUGIN "Build usd katana plugin" OFF) +option(PXR_BUILD_ALEMBIC_PLUGIN "Build the Alembic plugin for USD" OFF) +option(PXR_BUILD_DRACO_PLUGIN "Build the Draco plugin for USD" OFF) +option(PXR_BUILD_HOUDINI_PLUGIN "Build the Houdini plugin for USD" OFF) +option(PXR_BUILD_PRMAN_PLUGIN "Build the PRMan imaging plugin" OFF) +option(PXR_BUILD_CYCLES_PLUGIN "Build the PRMan imaging plugin" ON) +option(PXR_BUILD_MATERIALX_PLUGIN "Build the MaterialX plugin for USD" OFF) +option(PXR_BUILD_DOCUMENTATION "Generate doxygen documentation" OFF) +option(PXR_ENABLE_GL_SUPPORT "Enable OpenGL based components" ON) +option(PXR_ENABLE_PYTHON_SUPPORT "Enable Python based components for USD" ON) +option(PXR_ENABLE_MULTIVERSE_SUPPORT "Enable Multiverse backend in the Alembic plugin for USD" OFF) +option(PXR_ENABLE_HDF5_SUPPORT "Enable HDF5 backend in the Alembic plugin for USD" ON) +option(PXR_ENABLE_OSL_SUPPORT "Enable OSL (OpenShadingLanguage) based components" OFF) +option(PXR_ENABLE_PTEX_SUPPORT "Enable Ptex support" ON) +option(PXR_ENABLE_OPENVDB_SUPPORT "Enable OpenVDB support" OFF) +option(PXR_ENABLE_NAMESPACES "Enable C++ namespaces." ON) + +# Precompiled headers are a win on Windows, not on gcc. +set(pxr_enable_pch "OFF") +if(MSVC) + set(pxr_enable_pch "ON") +endif() +option(PXR_ENABLE_PRECOMPILED_HEADERS "Enable precompiled headers." "${pxr_enable_pch}") +set(PXR_PRECOMPILED_HEADER_NAME "pch.h" + CACHE + STRING + "Default name of precompiled header files" +) + +set(PXR_INSTALL_LOCATION "" + CACHE + STRING + "Intended final location for plugin resource files." +) + +set(PXR_OVERRIDE_PLUGINPATH_NAME "" + CACHE + STRING + "Name of the environment variable that will be used to get plugin paths." +) + +set(PXR_ALL_LIBS "" + CACHE + INTERNAL + "Aggregation of all built libraries." +) +set(PXR_STATIC_LIBS "" + CACHE + INTERNAL + "Aggregation of all built explicitly static libraries." +) +set(PXR_CORE_LIBS "" + CACHE + INTERNAL + "Aggregation of all built core libraries." +) +set(PXR_OBJECT_LIBS "" + CACHE + INTERNAL + "Aggregation of all core libraries built as OBJECT libraries." +) + +set(PXR_LIB_PREFIX ${CMAKE_SHARED_LIBRARY_PREFIX} + CACHE + STRING + "Prefix for build library name" +) + +option(BUILD_SHARED_LIBS "Build shared libraries." ON) +option(PXR_BUILD_MONOLITHIC "Build a monolithic library." OFF) +set(PXR_MONOLITHIC_IMPORT "" + CACHE + STRING + "Path to cmake file that imports a usd_ms target" +) + +set(PXR_EXTRA_PLUGINS "" + CACHE + INTERNAL + "Aggregation of extra plugin directories containing a plugInfo.json.") + +# Resolve options that depend on one another so that subsequent .cmake scripts +# all have the final value for these options. +if (${PXR_BUILD_USD_IMAGING} AND NOT ${PXR_BUILD_IMAGING}) + message(STATUS + "Setting PXR_BUILD_USD_IMAGING=OFF because PXR_BUILD_IMAGING=OFF") + set(PXR_BUILD_USD_IMAGING "OFF" CACHE BOOL "" FORCE) +endif() + +if (${PXR_BUILD_USDVIEW}) + if (NOT ${PXR_BUILD_USD_IMAGING}) + message(STATUS + "Setting PXR_BUILD_USDVIEW=OFF because " + "PXR_BUILD_USD_IMAGING=OFF") + set(PXR_BUILD_USDVIEW "OFF" CACHE BOOL "" FORCE) + elseif (NOT ${PXR_ENABLE_PYTHON_SUPPORT}) + message(STATUS + "Setting PXR_BUILD_USDVIEW=OFF because " + "PXR_ENABLE_PYTHON_SUPPORT=OFF") + set(PXR_BUILD_USDVIEW "OFF" CACHE BOOL "" FORCE) + elseif (NOT ${PXR_ENABLE_GL_SUPPORT}) + message(STATUS + "Setting PXR_BUILD_USDVIEW=OFF because " + "PXR_ENABLE_GL_SUPPORT=OFF") + set(PXR_BUILD_USDVIEW "OFF" CACHE BOOL "" FORCE) + endif() +endif() + +if (${PXR_BUILD_EMBREE_PLUGIN}) + if (NOT ${PXR_BUILD_IMAGING}) + message(STATUS + "Setting PXR_BUILD_EMBREE_PLUGIN=OFF because PXR_BUILD_IMAGING=OFF") + set(PXR_BUILD_EMBREE_PLUGIN "OFF" CACHE BOOL "" FORCE) + elseif (NOT ${PXR_ENABLE_GL_SUPPORT}) + message(STATUS + "Setting PXR_BUILD_EMBREE_PLUGIN=OFF because " + "PXR_ENABLE_GL_SUPPORT=OFF") + set(PXR_BUILD_EMBREE_PLUGIN "OFF" CACHE BOOL "" FORCE) + endif() +endif() + +if (${PXR_BUILD_KATANA_PLUGIN}) + if (NOT ${PXR_ENABLE_PYTHON_SUPPORT}) + message(STATUS + "Setting PXR_BUILD_KATANA_PLUGIN=OFF because " + "PXR_ENABLE_PYTHON_SUPPORT=OFF") + set(PXR_BUILD_KATANA_PLUGIN "OFF" CACHE BOOL "" FORCE) + elseif (NOT ${PXR_ENABLE_GL_SUPPORT}) + message(STATUS + "Setting PXR_BUILD_KATANA_PLUGIN=OFF because " + "PXR_ENABLE_GL_SUPPORT=OFF") + set(PXR_BUILD_KATANA_PLUGIN "OFF" CACHE BOOL "" FORCE) + elseif (NOT ${PXR_BUILD_USD_IMAGING}) + message(STATUS + "Setting PXR_BUILD_KATANA_PLUGIN=OFF because " + "PXR_BUILD_USD_IMAGING=OFF") + set(PXR_BUILD_KATANA_PLUGIN "OFF" CACHE BOOL "" FORCE) + endif() +endif() + +if (${PXR_BUILD_HOUDINI_PLUGIN}) + if (NOT ${PXR_ENABLE_PYTHON_SUPPORT}) + message(STATUS + "Setting PXR_BUILD_HOUDINI_PLUGIN=OFF because " + "PXR_ENABLE_PYTHON_SUPPORT=OFF") + set(PXR_BUILD_HOUDINI_PLUGIN "OFF" CACHE BOOL "" FORCE) + endif() +endif() + +if (${PXR_BUILD_PRMAN_PLUGIN}) + if (NOT ${PXR_BUILD_IMAGING}) + message(STATUS + "Setting PXR_BUILD_PRMAN_PLUGIN=OFF because PXR_BUILD_IMAGING=OFF") + set(PXR_BUILD_PRMAN_PLUGIN "OFF" CACHE BOOL "" FORCE) + endif() +endif() + +if (${PXR_BUILD_CYCLES_PLUGIN}) + if (NOT ${PXR_BUILD_IMAGING}) + message(STATUS + "Setting PXR_BUILD_CYCLES_PLUGIN=OFF because PXR_BUILD_IMAGING=OFF") + set(PXR_BUILD_CYCLES_PLUGIN "OFF" CACHE BOOL "" FORCE) + endif() +endif() + +# Error out if user is building monolithic library on windows with draco plugin +# enabled. This currently results in missing symbols. +if (${PXR_BUILD_DRACO_PLUGIN} AND ${PXR_BUILD_MONOLITHIC} AND WIN32) + message(FATAL_ERROR + "Draco plugin can not be enabled for monolithic builds on Windows") +endif() diff --git a/cmake/defaults/Packages.cmake b/cmake/defaults/Packages.cmake new file mode 100644 index 00000000..505f8d86 --- /dev/null +++ b/cmake/defaults/Packages.cmake @@ -0,0 +1,195 @@ +# +# Copyright 2016 Pixar +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +# Save the current value of BUILD_SHARED_LIBS and restore it at +# the end of this file, since some of the Find* modules invoked +# below may wind up stomping over this value. +set(build_shared_libs "${BUILD_SHARED_LIBS}") + +# Core USD Package Requirements +# ---------------------------------------------- + +# Threads. Save the libraries needed in PXR_THREAD_LIBS; we may modify +# them later. We need the threads package because some platforms require +# it when using C++ functions from #include . +set(CMAKE_THREAD_PREFER_PTHREAD TRUE) +find_package(Threads REQUIRED) +set(PXR_THREAD_LIBS "${CMAKE_THREAD_LIBS_INIT}") + +if(PXR_ENABLE_PYTHON_SUPPORT) + # --Python. We are generally but not completely 2.6 compliant. + find_package(PythonInterp 2.7 REQUIRED) + find_package(PythonLibs 2.7 REQUIRED) + + # --Boost + find_package(Boost + COMPONENTS + program_options + python + REQUIRED + ) + + # --Jinja2 + find_package(Jinja2) +else() + find_package(PythonInterp 2.7 REQUIRED) + + # --Boost + find_package(Boost + COMPONENTS + program_options + REQUIRED + ) +endif() + +# --TBB +find_package(TBB REQUIRED COMPONENTS tbb) +add_definitions(${TBB_DEFINITIONS}) + +# --math +if(WIN32) + # Math functions are linked automatically by including math.h on Windows. + set(M_LIB "") +else() + find_library(M_LIB m) +endif() + +if (NOT PXR_MALLOC_LIBRARY) + if (NOT WIN32) + message(STATUS "Using default system allocator because PXR_MALLOC_LIBRARY is unspecified") + endif() +endif() + +# Developer Options Package Requirements +# ---------------------------------------------- +if (PXR_VALIDATE_GENERATED_CODE) + find_package(BISON 2.4.1 EXACT) + # Flex 2.5.39+ is required, generated API is generated incorrectly in + # 2.5.35, at least. scan_bytes generates with (..., int len, ...) instead of + # the correct (..., yy_size_t len, ...). Lower at your own peril. + find_package(FLEX 2.5.39 EXACT) +endif() + + +# Imaging Components Package Requirements +# ---------------------------------------------- + +if (PXR_BUILD_IMAGING) + # --OpenEXR + find_package(OpenEXR REQUIRED) + # --OpenImageIO + if (PXR_BUILD_OPENIMAGEIO_PLUGIN) + find_package(OpenImageIO REQUIRED) + add_definitions(-DPXR_OIIO_PLUGIN_ENABLED) + endif() + # --OpenColorIO + if (PXR_BUILD_OPENCOLORIO_PLUGIN) + find_package(OpenColorIO REQUIRED) + add_definitions(-DPXR_OCIO_PLUGIN_ENABLED) + endif() + # --OpenGL + if (PXR_ENABLE_GL_SUPPORT) + find_package(OpenGL REQUIRED) + find_package(GLEW REQUIRED) + endif() + # --Opensubdiv + set(OPENSUBDIV_USE_GPU ${PXR_ENABLE_GL_SUPPORT}) + find_package(OpenSubdiv 3 REQUIRED) + # --Ptex + if (PXR_ENABLE_PTEX_SUPPORT) + find_package(PTex REQUIRED) + add_definitions(-DPXR_PTEX_SUPPORT_ENABLED) + endif() + # --OpenVDB + if (PXR_ENABLE_OPENVDB_SUPPORT) + find_package(OpenVDB REQUIRED) + add_definitions(-DPXR_OPENVDB_SUPPORT_ENABLED) + endif() + # --X11 + if (CMAKE_SYSTEM_NAME STREQUAL "Linux") + find_package(X11) + endif() + # --Embree + if (PXR_BUILD_EMBREE_PLUGIN) + find_package(Embree REQUIRED) + endif() +endif() + +if (PXR_BUILD_USDVIEW) + # --PySide + find_package(PySide REQUIRED) + # --PyOpenGL + find_package(PyOpenGL REQUIRED) +endif() + +# Third Party Plugin Package Requirements +# ---------------------------------------------- +if (PXR_BUILD_KATANA_PLUGIN) + find_package(KatanaAPI REQUIRED) + find_package(Boost + COMPONENTS + thread + REQUIRED + ) +endif() + +if (PXR_BUILD_HOUDINI_PLUGIN) + find_package(Houdini REQUIRED) +endif() + +if (PXR_BUILD_PRMAN_PLUGIN) + find_package(Renderman REQUIRED) +endif() + +if (PXR_BUILD_CYCLES_PLUGIN) + find_package(Cycles REQUIRED) +endif() + +if (PXR_BUILD_ALEMBIC_PLUGIN) + find_package(Alembic REQUIRED) + find_package(OpenEXR REQUIRED) + if (PXR_ENABLE_HDF5_SUPPORT) + find_package(HDF5 REQUIRED + COMPONENTS + HL + REQUIRED + ) + endif() +endif() + +if (PXR_BUILD_DRACO_PLUGIN) + find_package(Draco REQUIRED) +endif() + +if (PXR_BUILD_MATERIALX_PLUGIN) + find_package(MaterialX REQUIRED) +endif() + +if(PXR_ENABLE_OSL_SUPPORT) + find_package(OSL REQUIRED) +endif() + +# ---------------------------------------------- + +set(BUILD_SHARED_LIBS "${build_shared_libs}") diff --git a/cmake/defaults/ProjectDefaults.cmake b/cmake/defaults/ProjectDefaults.cmake new file mode 100644 index 00000000..8ff64321 --- /dev/null +++ b/cmake/defaults/ProjectDefaults.cmake @@ -0,0 +1,51 @@ +# +# Copyright 2016 Pixar +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# +if(APPLE) + set(OSX_ARCHITECTURES "x86_64" CACHE STRING "Build architectures for OSX") + set(CMAKE_MACOSX_RPATH ON) + set(CMAKE_SKIP_BUILD_RPATH FALSE) + set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) + set(CMAKE_DYLIB_INSTALL_NAME_DIR "${CMAKE_INSTALL_PREFIX}/lib" CACHE STRING "install_name path for dylib.") + list(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_PREFIX}/lib" isSystemDir) + message(WARNING "Building USD on Mac OSX is currently experimental.") +elseif(WIN32) + # Windows specific set up + message(WARNING "Building USD on Windows is currently experimental.") +endif() + +# Allow local includes from source directory. +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +# Turn on folder usage +set_property(GLOBAL PROPERTY USE_FOLDERS ON) + +# Default build type +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Release") +endif() + +if (PXR_BUILD_TESTS) + # Enable CTest + enable_testing() +endif() diff --git a/cmake/defaults/Version.cmake b/cmake/defaults/Version.cmake new file mode 100644 index 00000000..381d0999 --- /dev/null +++ b/cmake/defaults/Version.cmake @@ -0,0 +1,17 @@ +# Copyright 2020 Tangent Animation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set(HD_CYCLES_MAJOR_VERSION "0") +set(HD_CYCLES_MINOR_VERSION "7") +set(HD_CYCLES_PATCH_VERSION "0") diff --git a/cmake/defaults/clangdefaults.cmake b/cmake/defaults/clangdefaults.cmake new file mode 100644 index 00000000..0cba9b99 --- /dev/null +++ b/cmake/defaults/clangdefaults.cmake @@ -0,0 +1,32 @@ +# +# Copyright 2016 Pixar +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +include(gccclangshareddefaults) + +set(_PXR_CXX_FLAGS "${_PXR_GCC_CLANG_SHARED_CXX_FLAGS}") + +# clang annoyingly warns about the -pthread option if it's only linking. +if(CMAKE_USE_PTHREADS_INIT) + _disable_warning("unused-command-line-argument") +endif() diff --git a/cmake/defaults/gccclangshareddefaults.cmake b/cmake/defaults/gccclangshareddefaults.cmake new file mode 100644 index 00000000..9dd3308e --- /dev/null +++ b/cmake/defaults/gccclangshareddefaults.cmake @@ -0,0 +1,60 @@ +# +# Copyright 2016 Pixar +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +# This file contains a set of flags/settings shared between our +# GCC and Clang configs. This allows clangdefaults and gccdefaults +# to remain minimal, marking the points where divergence is required. +include(Options) + +# Turn on C++11; pxr won't build without it. +set(_PXR_GCC_CLANG_SHARED_CXX_FLAGS "${_PXR_GCC_CLANG_SHARED_CXX_FLAGS} -std=c++11") + +# Enable all warnings. +set(_PXR_GCC_CLANG_SHARED_CXX_FLAGS "${_PXR_GCC_CLANG_SHARED_CXX_FLAGS} -Wall") + +# Errors are warnings in strict build mode. +if (${PXR_STRICT_BUILD_MODE}) + set(_PXR_GCC_CLANG_SHARED_CXX_FLAGS "${_PXR_GCC_CLANG_SHARED_CXX_FLAGS} -Werror") +endif() + +# We use hash_map, suppress deprecation warning. +_disable_warning("deprecated") +_disable_warning("deprecated-declarations") + +# Suppress unused typedef warnings emanating from boost. +if (NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR + NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 3.6) + if (NOT CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" OR + NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.1) + _disable_warning("unused-local-typedefs") + endif() +endif() + +# If using pthreads then tell the compiler. This should automatically cause +# the linker to pull in the pthread library if necessary so we also clear +# PXR_THREAD_LIBS. +if(CMAKE_USE_PTHREADS_INIT) + set(_PXR_GCC_CLANG_SHARED_CXX_FLAGS "${_PXR_GCC_CLANG_SHARED_CXX_FLAGS} -pthread") + set(PXR_THREAD_LIBS "") +endif() diff --git a/cmake/defaults/gccdefaults.cmake b/cmake/defaults/gccdefaults.cmake new file mode 100644 index 00000000..ce7789a2 --- /dev/null +++ b/cmake/defaults/gccdefaults.cmake @@ -0,0 +1,27 @@ +# +# Copyright 2016 Pixar +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +include(gccclangshareddefaults) + +set(_PXR_CXX_FLAGS "${_PXR_GCC_CLANG_SHARED_CXX_FLAGS}") diff --git a/cmake/defaults/msvcdefaults.cmake b/cmake/defaults/msvcdefaults.cmake new file mode 100644 index 00000000..9024c5d3 --- /dev/null +++ b/cmake/defaults/msvcdefaults.cmake @@ -0,0 +1,110 @@ +# +# Copyright 2016 Pixar +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +# Enable exception handling. +set(_PXR_CXX_FLAGS "${_PXR_CXX_FLAGS} /EHsc") + +# Standards compliant. +set(_PXR_CXX_FLAGS "${_PXR_CXX_FLAGS} /Zc:rvalueCast /Zc:strictStrings /Zc:inline") + +# Turn on all but informational warnings. +set(_PXR_CXX_FLAGS "${_PXR_CXX_FLAGS} /W3") + +# Warnings are errors in strict build mode. +if (${PXR_STRICT_BUILD_MODE}) + set(_PXR_CXX_FLAGS "${_PXR_CXX_FLAGS} /WX") +endif() + +# truncation from 'double' to 'float' due to matrix and vector classes in `Gf` +_disable_warning("4244") +_disable_warning("4305") + +# conversion from size_t to int. While we don't want this enabled +# it's in the Python headers. So all the Python wrap code is affected. +_disable_warning("4267") + +# no definition for inline function +# this affects Glf only +_disable_warning("4506") + +# 'typedef ': ignored on left of '' when no variable is declared +# XXX:figure out why we need this +_disable_warning("4091") + +# c:\python27\include\pymath.h(22): warning C4273: 'round': inconsistent dll linkage +# XXX:figure out real fix +_disable_warning("4273") + +# qualifier applied to function type has no meaning; ignored +# tbb/parallel_for_each.h +_disable_warning("4180") + +# '<<': result of 32-bit shift implicitly converted to 64 bits +# tbb/enumerable_thread_specific.h +_disable_warning("4334") + +# Disable warning C4996 regarding fopen(), strcpy(), etc. +_add_define("_CRT_SECURE_NO_WARNINGS") + +# Disable warning C4996 regarding unchecked iterators for std::transform, +# std::copy, std::equal, et al. +_add_define("_SCL_SECURE_NO_WARNINGS") + +# Make sure WinDef.h does not define min and max macros which +# will conflict with std::min() and std::max(). +_add_define("NOMINMAX") + +# Needed to prevent YY files trying to include unistd.h +# (which doesn't exist on Windows) +_add_define("YY_NO_UNISTD_H") + +# Forces all libraries that have separate source to be linked as +# DLL's rather than static libraries on Microsoft Windows, unless +# explicitly told otherwise. +if (NOT Boost_USE_STATIC_LIBS) + _add_define("BOOST_ALL_DYN_LINK") +endif() + +# Need half::_toFloat and half::_eLut. +_add_define("OPENEXR_DLL") + +# These files require /bigobj compiler flag +# Vt/arrayPyBuffer.cpp +# Usd/crateFile.cpp +# Usd/stage.cpp +# Until we can set the flag on a per file basis, we'll have to enable it +# for all translation units. +set(_PXR_CXX_FLAGS "${_PXR_CXX_FLAGS} /bigobj") + +# Enable PDB generation. +set(_PXR_CXX_FLAGS "${_PXR_CXX_FLAGS} /Zi") + +# Enable multiprocessor builds. +set(_PXR_CXX_FLAGS "${_PXR_CXX_FLAGS} /MP") +set(_PXR_CXX_FLAGS "${_PXR_CXX_FLAGS} /Gm-") + +# Ignore LNK4221. This happens when making an archive with a object file +# with no symbols in it. We do this a lot because of a pattern of having +# a C++ source file for many header-only facilities, e.g. tf/bitUtils.cpp. +set(CMAKE_STATIC_LINKER_FLAGS "${CMAKE_STATIC_LINKER_FLAGS} /IGNORE:4221") diff --git a/cmake/macros/Private.cmake b/cmake/macros/Private.cmake new file mode 100644 index 00000000..299e6648 --- /dev/null +++ b/cmake/macros/Private.cmake @@ -0,0 +1,1375 @@ +# +# Copyright 2016 Pixar +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# +include(Version) + +# Copy headers to the build tree. Under pxr/ the include paths match the +# source tree paths but elsewhere they do not. Instead we use include +# paths like rmanArgsParser/rmanArgsParser.h. So if /pxr/ is not in the +# source tree path then copy the headers (public and private) into the +# build tree under paths of the latter scheme. +function(_copy_headers LIBRARY_NAME) + set(options "") + set(oneValueArgs PREFIX) + set(multiValueArgs FILES) + cmake_parse_arguments(_args + "${options}" + "${oneValueArgs}" + "${multiValueArgs}" + ${ARGN} + ) + + set(files_copied "") + set(hpath "${_args_PREFIX}/${LIBRARY_NAME}") + if ("${CMAKE_CURRENT_SOURCE_DIR}" MATCHES ".*/pxr/.*") + # Include paths under pxr/ match the source path. + file(RELATIVE_PATH hpath "${CMAKE_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}") + endif() + set(header_dest_dir "${CMAKE_BINARY_DIR}/${PXR_INSTALL_SUBDIR}/include/${hpath}") + if( NOT "${_args_FILES}" STREQUAL "") + set(files_copied "") + foreach (f ${_args_FILES}) + set(infile "${CMAKE_CURRENT_SOURCE_DIR}/${f}") + set(outfile "${header_dest_dir}/${f}") + get_filename_component(dir_to_create "${outfile}" PATH) + add_custom_command( + OUTPUT ${outfile} + COMMAND ${CMAKE_COMMAND} -E make_directory "${dir_to_create}" + COMMAND ${CMAKE_COMMAND} -Dinfile="${infile}" -Doutfile="${outfile}" -P "${PROJECT_SOURCE_DIR}/cmake/macros/copyHeaderForBuild.cmake" + MAIN_DEPENDENCY "${infile}" + COMMENT "Copying ${f} ..." + VERBATIM + ) + list(APPEND files_copied ${outfile}) + endforeach() + endif() + + # Add a headers target. + add_custom_target(${LIBRARY_NAME}_headerfiles + DEPENDS ${files_copied} + ) + set_target_properties(${LIBRARY_NAME}_headerfiles + PROPERTIES + FOLDER "headerfiles" + ) + + # Make sure headers are installed before building the library. + add_dependencies(${LIBRARY_NAME} ${LIBRARY_NAME}_headerfiles) +endfunction() # _copy_headers + +# Converts a library name, such as _tf.so to the internal module name given +# our naming conventions, e.g. Tf +function(_get_python_module_name LIBRARY_FILENAME MODULE_NAME) + # Library names are either something like tf.so for shared libraries + # or _tf.so for Python module libraries. We want to strip the leading + # "_" off. + string(REPLACE "_" "" LIBNAME ${LIBRARY_FILENAME}) + string(SUBSTRING ${LIBNAME} 0 1 LIBNAME_FL) + string(TOUPPER ${LIBNAME_FL} LIBNAME_FL) + string(SUBSTRING ${LIBNAME} 1 -1 LIBNAME_SUFFIX) + set(${MODULE_NAME} + "${LIBNAME_FL}${LIBNAME_SUFFIX}" + PARENT_SCOPE + ) +endfunction() # _get_python_module_name + +function(_plugInfo_subst libTarget pluginToLibraryPath plugInfoPath) + _get_resources_dir_name(PLUG_INFO_RESOURCE_PATH) + set(PLUG_INFO_ROOT "..") + set(PLUG_INFO_PLUGIN_NAME "pxr.${libTarget}") + set(PLUG_INFO_LIBRARY_PATH "${pluginToLibraryPath}") + + configure_file( + ${plugInfoPath} + ${CMAKE_CURRENT_BINARY_DIR}/${plugInfoPath} + ) +endfunction() # _plugInfo_subst + +# Generate a doxygen config file +function(_pxrDoxyConfig_subst) + configure_file(${CMAKE_SOURCE_DIR}/pxr/usd/usd/Doxyfile.in + ${CMAKE_BINARY_DIR}/Doxyfile + ) +endfunction() + +# Install compiled python files alongside the python object, +# e.g. lib/python/pxr/Ar/__init__.pyc +function(_install_python LIBRARY_NAME) + set(options "") + set(oneValueArgs "") + set(multiValueArgs FILES) + cmake_parse_arguments(ip + "${options}" + "${oneValueArgs}" + "${multiValueArgs}" + ${ARGN} + ) + + set(libPythonPrefix lib/python) + _get_python_module_name(${LIBRARY_NAME} LIBRARY_INSTALLNAME) + + set(files_copied "") + foreach(file ${ip_FILES}) + set(filesToInstall "") + set(installDest + "${libPythonPrefix}/pxr/${LIBRARY_INSTALLNAME}") + + # Only attempt to compile .py files. Files like plugInfo.json may also + # be in this list + if (${file} MATCHES ".py$") + get_filename_component(file_we ${file} NAME_WE) + + # Preserve any directory prefix, just strip the extension. This + # directory needs to exist in the binary dir for the COMMAND below + # to work. + get_filename_component(dir ${file} PATH) + if (dir) + file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${dir}) + set(file_we ${dir}/${file_we}) + set(installDest ${installDest}/${dir}) + endif() + + set(outfile ${CMAKE_CURRENT_BINARY_DIR}/${file_we}.pyc) + list(APPEND files_copied ${outfile}) + add_custom_command(OUTPUT ${outfile} + COMMAND + ${PYTHON_EXECUTABLE} + ${PROJECT_SOURCE_DIR}/cmake/macros/compilePython.py + ${CMAKE_CURRENT_SOURCE_DIR}/${file} + ${CMAKE_CURRENT_SOURCE_DIR}/${file} + ${CMAKE_CURRENT_BINARY_DIR}/${file_we}.pyc + ) + list(APPEND filesToInstall ${CMAKE_CURRENT_SOURCE_DIR}/${file}) + list(APPEND filesToInstall ${CMAKE_CURRENT_BINARY_DIR}/${file_we}.pyc) + elseif (${file} MATCHES ".qss$") + # XXX -- Allow anything or allow nothing? + list(APPEND filesToInstall ${CMAKE_CURRENT_SOURCE_DIR}/${file}) + else() + message(FATAL_ERROR "Cannot have non-Python file ${file} in PYTHON_FILES.") + endif() + + # Note that we always install under lib/python/pxr, even if we are in + # the third_party project. This means the import will always look like + # 'from pxr import X'. We need to do this per-loop iteration because + # the installDest may be different due to the presence of subdirs. + install( + FILES + ${filesToInstall} + DESTINATION + "${installDest}" + ) + endforeach() + + # Add the target. + add_custom_target(${LIBRARY_NAME}_pythonfiles + DEPENDS ${files_copied} + ) + add_dependencies(python ${LIBRARY_NAME}_pythonfiles) + + _get_folder("_python" folder) + set_target_properties(${LIBRARY_NAME}_pythonfiles + PROPERTIES + FOLDER "${folder}" + ) +endfunction() #_install_python + +function(_install_resource_files NAME pluginInstallPrefix pluginToLibraryPath) + # Resource files install into a structure that looks like: + # lib/ + # usd/ + # ${NAME}/ + # resources/ + # resourceFileA + # subdir/ + # resourceFileB + # resourceFileC + # ... + # + _get_resources_dir(${pluginInstallPrefix} ${NAME} resourcesPath) + + foreach(resourceFile ${ARGN}) + # A resource file may be specified like : to + # indicate that it should be installed to a different location in + # the resources area. Check if this is the case. + string(REPLACE ":" ";" resourceFile "${resourceFile}") + list(LENGTH resourceFile n) + if (n EQUAL 1) + set(resourceDestFile ${resourceFile}) + elseif (n EQUAL 2) + list(GET resourceFile 1 resourceDestFile) + list(GET resourceFile 0 resourceFile) + else() + message(FATAL_ERROR + "Failed to parse resource path ${resourceFile}") + endif() + + get_filename_component(dirPath ${resourceDestFile} PATH) + get_filename_component(destFileName ${resourceDestFile} NAME) + + # plugInfo.json go through an initial template substitution step files + # install it from the binary (gen) directory specified by the full + # path. Otherwise, use the original relative path which is relative to + # the source directory. + if (${destFileName} STREQUAL "plugInfo.json") + _plugInfo_subst(${NAME} "${pluginToLibraryPath}" ${resourceFile}) + set(resourceFile "${CMAKE_CURRENT_BINARY_DIR}/${resourceFile}") + endif() + + install( + FILES ${resourceFile} + DESTINATION ${resourcesPath}/${dirPath} + RENAME ${destFileName} + ) + endforeach() +endfunction() # _install_resource_files + +function(_install_pyside_ui_files LIBRARY_NAME) + set(uiFiles "") + foreach(uiFile ${ARGN}) + get_filename_component(outFileName ${uiFile} NAME_WE) + get_filename_component(uiFilePath ${uiFile} ABSOLUTE) + set(outFilePath "${CMAKE_CURRENT_BINARY_DIR}/${outFileName}.py") + add_custom_command( + OUTPUT ${outFilePath} + COMMAND "${PYSIDEUICBINARY}" + ARGS -o ${outFilePath} ${uiFilePath} + MAIN_DEPENDENCY "${uiFilePath}" + COMMENT "Generating Python for ${uiFilePath} ..." + VERBATIM + ) + list(APPEND uiFiles ${outFilePath}) + endforeach() + + # Add the target. + add_custom_target(${LIBRARY_NAME}_pysideuifiles + DEPENDS ${uiFiles} + ) + add_dependencies(python ${LIBRARY_NAME}_pythonfiles) + + _get_folder("_pysideuifiles" folder) + set_target_properties( + ${LIBRARY_NAME}_pysideuifiles + PROPERTIES + FOLDER "${folder}" + ) + + set(libPythonPrefix lib/python) + _get_python_module_name(${LIBRARY_NAME} LIBRARY_INSTALLNAME) + + install( + FILES ${uiFiles} + DESTINATION "${libPythonPrefix}/pxr/${LIBRARY_INSTALLNAME}" + ) +endfunction() # _install_pyside_ui_files + +function(_classes LIBRARY_NAME) + # Install headers to build or install prefix + set(options PUBLIC PRIVATE) + cmake_parse_arguments(classes + "${options}" + "${oneValueArgs}" + "${multiValueArgs}" + ${ARGN} + ) + # If both get set, fall back to public. + if(${classes_PUBLIC}) + set(VISIBILITY "PUBLIC") + elseif(${classes_PRIVATE}) + set(VISIBILITY "PRIVATE") + else() + message(FATAL_ERROR + "Library ${LIBRARY_NAME} has implicit visibility. " + "Provide PUBLIC or PRIVATE to classes() call.") + endif() + + # Should the classes have an argument name? + foreach(cls ${classes_UNPARSED_ARGUMENTS}) + list(APPEND ${LIBRARY_NAME}_${VISIBILITY}_HEADERS ${cls}.h) + list(APPEND ${LIBRARY_NAME}_CPPFILES ${cls}.cpp) + endforeach() + set(${LIBRARY_NAME}_${VISIBILITY}_HEADERS + ${${LIBRARY_NAME}_${VISIBILITY}_HEADERS} + PARENT_SCOPE + ) + set(${LIBRARY_NAME}_CPPFILES ${${LIBRARY_NAME}_CPPFILES} PARENT_SCOPE) +endfunction() # _classes + +function(_get_install_dir path out) + if (PXR_INSTALL_SUBDIR) + set(${out} ${PXR_INSTALL_SUBDIR}/${path} PARENT_SCOPE) + else() + set(${out} ${path} PARENT_SCOPE) + endif() +endfunction() # get_install_dir + +function(_get_resources_dir_name output) + set(${output} + resources + PARENT_SCOPE) +endfunction() # _get_resources_dir_name + +function(_get_resources_dir pluginsPrefix pluginName output) + _get_resources_dir_name(resourcesDir) + set(${output} + ${pluginsPrefix}/${pluginName}/${resourcesDir} + PARENT_SCOPE) +endfunction() # _get_resources_dir + +function(_get_folder suffix result) + # XXX -- Shouldn't we set PXR_PREFIX everywhere? + if(PXR_PREFIX) + set(folder "${PXR_PREFIX}") + elseif(PXR_INSTALL_SUBDIR) + set(folder "${PXR_INSTALL_SUBDIR}") + else() + set(folder "misc") + endif() + if(suffix) + set(folder "${folder}/${suffix}") + endif() + set(${result} ${folder} PARENT_SCOPE) +endfunction() + +function(_pch_get_directory_property property separator output) + get_property(value DIRECTORY PROPERTY ${property}) + if(NOT value STREQUAL "value-NOTFOUND") + # XXX -- Need better list joining. + if(${output}) + set(${output} "${${output}}${separator}${value}" PARENT_SCOPE) + else() + set(${output} "${value}" PARENT_SCOPE) + endif() + endif() +endfunction() + +function(_pch_get_target_property target property separator output) + get_property(value TARGET ${target} PROPERTY ${property}) + if(NOT value STREQUAL "value-NOTFOUND") + # XXX -- Need better list joining. + if(${output}) + set(${output} "${${output}}${separator}${value}" PARENT_SCOPE) + else() + set(${output} "${value}" PARENT_SCOPE) + endif() + endif() +endfunction() + +function(_pch_get_property target property output) + set(sep ";") + if("${property}" STREQUAL "COMPILE_FLAGS") + set(sep " ") + set(accum "${accum}${sep}${CMAKE_CXX_FLAGS}") + if(CMAKE_BUILD_TYPE) + string(TOUPPER ${CMAKE_BUILD_TYPE} buildType) + set(accum "${accum}${sep}${CMAKE_CXX_FLAGS_${buildType}}") + endif() + endif() + _pch_get_directory_property(${property} "${sep}" accum) + _pch_get_target_property(${target} ${property} "${sep}" accum) + set(${output} "${accum}" PARENT_SCOPE) +endfunction() + +function(_pxr_enable_precompiled_header TARGET_NAME) + # Ignore if disabled. + if(NOT PXR_ENABLE_PRECOMPILED_HEADERS) + return() + endif() + + set(options + ) + set(oneValueArgs + SOURCE_NAME + OUTPUT_NAME_PREFIX + ) + set(multiValueArgs + EXCLUDE + ) + cmake_parse_arguments(pch + "${options}" + "${oneValueArgs}" + "${multiValueArgs}" + ${ARGN} + ) + + # Header to precompile. SOURCE_NAME falls back to + # ${PXR_PRECOMPILED_HEADER_NAME}. + if("${pch_SOURCE_NAME}" STREQUAL "") + set(pch_SOURCE_NAME "${PXR_PRECOMPILED_HEADER_NAME}") + endif() + if("${pch_SOURCE_NAME}" STREQUAL "") + # Emergency backup name is "pch.h". + set(pch_SOURCE_NAME "pch.h") + endif() + set(source_header_name ${pch_SOURCE_NAME}) + get_filename_component(source_header_name_we ${source_header_name} NAME_WE) + + # Name of file to precompile in the build directory. The client can + # specify a prefix for this file, allowing multiple binaries/libraries + # in a single subdirectory to use unique precompiled headers, meaning + # each can have different compile options. + set(output_header_name_we "${pch_OUTPUT_NAME_PREFIX}${source_header_name_we}") + set(output_header_name ${output_header_name_we}.h) + + # Precompiled header file name. We choose the name that matches the + # convention for the compiler. That isn't necessary since we give + # this name explicitly wherever it's needed. + if(MSVC) + set(precompiled_name ${output_header_name_we}.pch) + elseif(CMAKE_COMPILER_IS_GNUCXX) + set(precompiled_name ${output_header_name_we}.h.gch) + elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") + set(precompiled_name ${output_header_name_we}.h.pch) + else() + # Silently ignore unknown compiler. + return() + endif() + + # Headers live in subdirectories. + set(rel_output_header_path "${PXR_PREFIX}/${TARGET_NAME}/${output_header_name}") + set(abs_output_header_path "${CMAKE_BINARY_DIR}/include/${rel_output_header_path}") + set(abs_precompiled_path ${CMAKE_BINARY_DIR}/include/${PXR_PREFIX}/${TARGET_NAME}/${precompiled_name}) + + # Additional compile flags to use precompiled header. This will be + set(compile_flags "") + if(MSVC) + # Build with precompiled header (/Yu, /Fp) and automatically + # include the header (/FI). + set(compile_flags "/Yu\"${rel_output_header_path}\" /FI\"${rel_output_header_path}\" /Fp\"${abs_precompiled_path}\"") + else() + # Automatically include the header (-include) and warn if there's + # a problem with the precompiled header. + set(compile_flags "-Winvalid-pch -include \"${rel_output_header_path}\"") + endif() + + # Use FALSE if we have an external precompiled header we can use. + if(TRUE) + if(MSVC) + # Copy the header to precompile. + add_custom_command( + OUTPUT "${abs_output_header_path}" + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/${source_header_name}" "${abs_output_header_path}" + DEPENDS "${source_header_name}" + COMMENT "Copying ${source_header_name}" + ) + + # Make an empty trigger file. We need a source file to do the + # precompilation. This file only needs to include the header to + # precompile but we're implicitly including that header so this + # file can be empty. + set(abs_output_source_path ${CMAKE_CURRENT_BINARY_DIR}/${output_header_name_we}.cpp) + add_custom_command( + OUTPUT "${abs_output_source_path}" + COMMAND ${CMAKE_COMMAND} -E touch ${abs_output_source_path} + ) + + # The trigger file gets a special compile flag (/Yc). + set_source_files_properties(${abs_output_source_path} PROPERTIES + COMPILE_FLAGS "/Yc\"${rel_output_header_path}\" /FI\"${rel_output_header_path}\" /Fp\"${abs_precompiled_path}\"" + OBJECT_OUTPUTS "${abs_precompiled_path}" + OBJECT_DEPENDS "${abs_output_header_path}" + ) + + # Add the header file to the target. + target_sources(${TARGET_NAME} PRIVATE "${abs_output_header_path}") + + # Add the trigger file to the target. + target_sources(${TARGET_NAME} PRIVATE "${abs_output_source_path}") + + # Exclude the trigger. + list(APPEND pch_EXCLUDE ${abs_output_source_path}) + else() + # Copy the header to precompile. + add_custom_command( + OUTPUT "${abs_output_header_path}" + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/${source_header_name}" "${abs_output_header_path}" + DEPENDS "${source_header_name}" + COMMENT "Copying ${source_header_name}" + ) + + # CMake has no simple way of invoking the compiler with additional + # arguments so we must make a custom command and pass the compiler + # arguments we collect here. + # + # $ is available starting with 2.8.12. In later + # cmake versions getting the target properties may not + # report all values (in particular, some include directories + # may not be reported). + if(CMAKE_VERSION VERSION_LESS "2.8.12") + _pch_get_property(${TARGET_NAME} INCLUDE_DIRECTORIES incs) + _pch_get_property(${TARGET_NAME} COMPILE_DEFINITIONS defs) + _pch_get_property(${TARGET_NAME} COMPILE_FLAGS flags) + _pch_get_property(${TARGET_NAME} COMPILE_OPTIONS opts) + if(NOT "${incs}" STREQUAL "") + string(REPLACE ";" ";-I" incs "${incs}") + set(incs "-I${incs}") + endif() + if(NOT "${defs}" STREQUAL "") + string(REPLACE ";" ";-D" defs "${defs}") + set(defs "-D${defs}") + endif() + separate_arguments(flags UNIX_COMMAND "${flags}") + + # Command to generate the precompiled header. + add_custom_command( + OUTPUT "${abs_precompiled_path}" + COMMAND ${CMAKE_CXX_COMPILER} ${flags} ${opts} ${defs} ${incs} -c -x c++-header -o "${abs_precompiled_path}" "${abs_output_header_path}" + DEPENDS "${abs_output_header_path}" + COMMENT "Precompiling ${source_header_name} in ${TARGET_NAME}" + ) + else() + set(incs "$") + set(defs "$") + set(opts "$") + set(incs "$<$:-I$>") + set(defs "$<$:-D$>") + _pch_get_property(${TARGET_NAME} COMPILE_FLAGS flags) + + # Ideally we'd just put have generator expressions in the + # COMMAND in add_custom_command(). However that will + # write the result of the JOINs as single strings (escaping + # spaces) and we want them as individual options. + # + # So we use file(GENERATE) which doesn't suffer from that + # problem and execute the generated cmake script as the + # COMMAND. + file(GENERATE + OUTPUT "$.pchgen" + CONTENT "execute_process(COMMAND ${CMAKE_CXX_COMPILER} ${flags} ${opt} ${defs} ${incs} -c -x c++-header -o \"${abs_precompiled_path}\" \"${abs_output_header_path}\")" + ) + + # Command to generate the precompiled header. + add_custom_command( + OUTPUT "${abs_precompiled_path}" + COMMAND ${CMAKE_COMMAND} -P "$.pchgen" + DEPENDS "${abs_output_header_path}" + COMMENT "Precompiling ${source_header_name} in ${TARGET_NAME}" + ) + endif() + endif() + endif() + + # Update every C++ source in the target to implicitly include and + # depend on the precompiled header. + get_property(target_sources TARGET ${TARGET_NAME} PROPERTY SOURCES) + foreach(source ${target_sources}) + # All target C++ sources not in EXCLUDE list. + if(source MATCHES \\.cpp$) + if (NOT ";${pch_EXCLUDE};" MATCHES ";${source};") + set_source_files_properties(${source} PROPERTIES + COMPILE_FLAGS "${compile_flags}" + OBJECT_DEPENDS "${abs_precompiled_path}") + endif() + endif() + endforeach() +endfunction() + +# Initialize a variable to accumulate an rpath. The origin is the +# RUNTIME DESTINATION of the target. If not absolute it's appended +# to CMAKE_INSTALL_PREFIX. +function(_pxr_init_rpath rpathRef origin) + if(NOT IS_ABSOLUTE ${origin}) + set(origin "${CMAKE_INSTALL_PREFIX}/${origin}") + get_filename_component(origin "${origin}" REALPATH) + endif() + set(${rpathRef} "${origin}" PARENT_SCOPE) +endfunction() + +# Add a relative target path to the rpath. If target is absolute compute +# and add a relative path from the origin to the target. +function(_pxr_add_rpath rpathRef target) + if(IS_ABSOLUTE "${target}") + # Make target relative to $ORIGIN (which is the first element in + # rpath when initialized with _pxr_init_rpath()). + list(GET ${rpathRef} 0 origin) + file(RELATIVE_PATH + target + "${origin}" + "${target}" + ) + if("x${target}" STREQUAL "x") + set(target ".") + endif() + endif() + file(TO_CMAKE_PATH "${target}" target) + set(new_rpath "${${rpathRef}}") + list(APPEND new_rpath "$ORIGIN/${target}") + set(${rpathRef} "${new_rpath}" PARENT_SCOPE) +endfunction() + +function(_pxr_install_rpath rpathRef NAME) + # Get and remove the origin. + list(GET ${rpathRef} 0 origin) + set(rpath ${${rpathRef}}) + list(REMOVE_AT rpath 0) + + # Canonicalize and uniquify paths. + set(final "") + foreach(path ${rpath}) + # Absolutize on Mac. SIP disallows relative rpaths. + if(APPLE) + if("${path}/" MATCHES "^[$]ORIGIN/") + # Replace with origin path. + string(REPLACE "$ORIGIN/" "${origin}/" path "${path}/") + + # Simplify. + get_filename_component(path "${path}" REALPATH) + endif() + endif() + + # Strip trailing slashes. + string(REGEX REPLACE "/+$" "" path "${path}") + + # Ignore paths we already have. + if (NOT ";${final};" MATCHES ";${path};") + list(APPEND final "${path}") + endif() + endforeach() + + set_target_properties(${NAME} + PROPERTIES + INSTALL_RPATH_USE_LINK_PATH TRUE + INSTALL_RPATH "${final}" + ) +endfunction() + +# Split the library (target) names in libs into internal-to-the-monolithic- +# library and external-of-it lists. +function(_pxr_split_libraries libs internal_result external_result) + set(internal "") + set(external "") + foreach(lib ${libs}) + if(";${PXR_CORE_LIBS};" MATCHES ";${lib};") + list(APPEND internal "${lib}") + else() + list(APPEND external "${lib}") + endif() + endforeach() + set(${internal_result} ${internal} PARENT_SCOPE) + set(${external_result} ${external} PARENT_SCOPE) +endfunction() + +# Helper functions for _pxr_transitive_internal_libraries. +function(_pxr_transitive_internal_libraries_r libs transitive_libs) + set(result "${${transitive_libs}}") + foreach(lib ${libs}) + # Handle library ${lib} only if it hasn't been seen yet. + if(NOT ";${result};" MATCHES ";${lib};") + # Add to result. + list(APPEND result ${lib}) + + # Get the implicit link libraries. + get_property(implicit TARGET ${lib} PROPERTY INTERFACE_LINK_LIBRARIES) + + # Discard the external libraries. + _pxr_split_libraries("${implicit}" internal external) + + # Recurse on the internal libraries. + _pxr_transitive_internal_libraries_r("${internal}" result) + endif() + endforeach() + set(${transitive_libs} "${result}" PARENT_SCOPE) +endfunction() + +function(_pxr_transitive_internal_libraries libs transitive_libs) + # Get the transitive libs in some order. + set(transitive "") + _pxr_transitive_internal_libraries_r("${libs}" transitive) + + # Get the transitive libs in build order. + set(result "") + foreach(lib ${PXR_ALL_LIBS}) + if(";${transitive};" MATCHES ";${lib};") + list(APPEND result "${lib}") + endif() + endforeach() + + # Reverse the order to get the link order. + list(REVERSE result) + set(${transitive_libs} "${result}" PARENT_SCOPE) +endfunction() + +# This function is equivalent to target_link_libraries except it does +# a few extra things: +# +# 1) We can't call target_link_libraries() on a target that's an OBJECT +# library but we do need the transitive definitions and include +# directories so we manually add them. We also manually set the +# INTERFACE_LINK_LIBRARIES so we can use it for targets that want to +# "link" the OBJECT library. And we manually add a dependency. +# This would all be a lot easier if cmake treated OBJECT libraries +# like a STATIC or SHARED library in target_link_libraries(). +# +# 2) If the target is not an OBJECT library and this is a monolithic +# build and we're linking to core libraries then link against the +# monolithic library instead. +# +# 3) If the target is not an OBJECT library and this is not a monolithic +# build and we're not building shared libraries and we're linking +# with core libraries then we must link the static libraries using +# whole-archive functionality. Without this any object file in a +# static library that doesn't have any symbols used from it will not +# be linked at all. If the object file has global constructors with +# side effects then those constructors and side effects will not +# run. We depend on these constructs (e.g. TF_REGISTRY_FUNCTION). +# +# 4) We link against PXR_MALLOC_LIBRARY and PXR_THREAD_LIBS because we +# always want those. +# +function(_pxr_target_link_libraries NAME) + # Split core libraries from non-core libraries. + _pxr_split_libraries("${ARGN}" internal external) + + get_property(type TARGET ${NAME} PROPERTY TYPE) + if("${type}" STREQUAL "OBJECT_LIBRARY") + # Collect the definitions and include directories. + set(finalDefs "") + set(finalIncs "") + _pxr_transitive_internal_libraries("${internal}" internal) + foreach(lib ${internal}) + get_property(defs TARGET ${lib} PROPERTY INTERFACE_COMPILE_DEFINITIONS) + foreach(def ${defs}) + if(NOT ";${finalDefs};" MATCHES ";${def};") + list(APPEND finalDefs "${def}") + endif() + endforeach() + get_property(incs TARGET ${lib} PROPERTY INTERFACE_INCLUDE_DIRECTORIES) + foreach(inc ${incs}) + if(NOT ";${finalIncs};" MATCHES ";${inc};") + list(APPEND finalIncs "${inc}") + endif() + endforeach() + endforeach() + + # Collect libraries. We must convert debug/optimized/general + # link-type keywords to generator expressions in order to add + # them to the INTERFACE_LINK_LIBRARIES. + set(finalLibs "") + set(keyword "") + foreach(lib ${external}) + if("${lib}" STREQUAL "debug" OR "${lib}" STREQUAL "optimized") + set(keyword ${lib}) + elseif("${lib}" STREQUAL "general") + set(keyword "") + elseif(lib) + if("${keyword}" STREQUAL "debug") + set(keyword "") + set(entry "$<$:${lib}>") + elseif("${keyword}" STREQUAL "optimized") + set(keyword "") + set(entry "$<$>:${lib}>") + else() + set(entry "${lib}") + endif() + if(entry AND NOT ";${finalLibs};" MATCHES ";${entry};") + list(APPEND finalLibs "${entry}") + endif() + endif() + endforeach() + + # Record the definitions, include directories and "linked" libraries. + target_compile_definitions(${NAME} PUBLIC ${finalDefs}) + target_include_directories(${NAME} PUBLIC ${finalIncs}) + set_property(TARGET ${NAME} PROPERTY + INTERFACE_LINK_LIBRARIES + ${finalLibs} + ${PXR_MALLOC_LIBRARY} + ${PXR_THREAD_LIBS} + ) + + # Depend on core libraries we use. + if(internal) + add_dependencies(${NAME} ${internal}) + endif() + else() + # If we use any internal libraries then just link against the + # monolithic library. + if(PXR_BUILD_MONOLITHIC) + if(internal) + if(TARGET usd_ms) + set(internal usd_ms) + else() + set(internal usd_m) + endif() + endif() + elseif(NOT BUILD_SHARED_LIBS) + # Indicate that all symbols should be pulled in from internal + # static libraries. This ensures we don't drop unused symbols + # with dynamic initialization side effects. The exceptions are + # any libraries explicitly static; not only does that explicitly + # say we don't have to worry about the dynamic initialization, but + # also would maybe cause multiple symbol definitions if we tried + # to get all symbols. + # + # On gcc use: --whole_archive LIB --no-whole-archive. + # On clang use: -force_load LIB + # On Windows use: /WHOLEARCHIVE:LIB + # + # A final complication is that we must also process transitive + # link libraries since any transitively linked internal libraries + # need the same treatment. + _pxr_transitive_internal_libraries("${internal}" internal) + set(final "") + foreach(lib ${internal}) + if(";${PXR_STATIC_LIBS};" MATCHES ";${lib};") + # The library is explicitly static. + list(APPEND final ${lib}) + elseif(MSVC) + # The syntax here is -WHOLEARCHIVE[:lib] but CMake will + # treat that as a link flag and not "see" the library. + # As a result it won't replace a target with the path + # to the built target and it won't add a dependency. + # + # We can't simply link against the full path to the + # library because we CMake will not add a dependency + # and won't use interface link libraries and flags + # from the targets. Rather than trying to add those + # things manually we instead link against the target + # and link against the full path to the built target + # with WHOLEARCHIVE. + # + # This ends up with the library on the link line twice. + # That's okay, though, because the linker will read + # the WHOLEARCHIVE one first and will use none of the + # (duplicate) symbols from the second since they're + # all provided by the first. The order doesn't really + # matter; we pull in the whole archive first. + # + list(APPEND final -WHOLEARCHIVE:$) + list(APPEND final ${lib}) + elseif(CMAKE_COMPILER_IS_GNUCXX) + list(APPEND final -Wl,--whole-archive ${lib} -Wl,--no-whole-archive) + elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") + list(APPEND final -Wl,-force_load ${lib}) + else() + # Unknown platform. + list(APPEND final ${lib}) + endif() + endforeach() + set(internal ${final}) + endif() + target_link_libraries(${NAME} + ${internal} + ${external} + ${PXR_MALLOC_LIBRARY} + ${PXR_THREAD_LIBS} + ) + endif() +endfunction() + +# Add a python module for the target named NAME. It implicitly links +# against the library named NAME (or the monolithic library if +# PXR_BUILD_MONOLITHIC is enabled). +function(_pxr_python_module NAME) + set(oneValueArgs + PRECOMPILED_HEADERS + PRECOMPILED_HEADER_NAME + WRAPPED_LIB_INSTALL_PREFIX + ) + set(multiValueArgs + CPPFILES + PYTHON_FILES + PYSIDE_UI_FILES + INCLUDE_DIRS + ) + cmake_parse_arguments(args + "${options}" + "${oneValueArgs}" + "${multiValueArgs}" + ${ARGN} + ) + + # If we can't build Python modules then do nothing. + if(NOT TARGET shared_libs) + message(STATUS "Skipping Python module ${NAME}, shared libraries required") + return() + endif() + + set(LIBRARY_NAME "_${NAME}") + + # Install .py files. + if(args_PYTHON_FILES) + _install_python(${LIBRARY_NAME} + FILES ${args_PYTHON_FILES} + ) + endif() + + # Install .ui files. + if (args_PYSIDE_UI_FILES) + _install_pyside_ui_files(${LIBRARY_NAME} ${args_PYSIDE_UI_FILES}) + endif() + + # If no C++ files then we're done. + if (NOT args_CPPFILES) + return() + endif() + + # Add the module target. + add_library(${LIBRARY_NAME} + SHARED + ${args_CPPFILES} + ) + add_dependencies(python ${LIBRARY_NAME}) + if(args_PYTHON_FILES) + add_dependencies(${LIBRARY_NAME} ${LIBRARY_NAME}_pythonfiles) + endif() + if (args_PYSIDE_UI_FILES) + add_dependencies(${LIBRARY_NAME} ${LIBRARY_NAME}_pysideuifiles) + endif() + + # Convert the name of the library into the python module name + # , e.g. _tf.so -> Tf. This is later used to determine the eventual + # install location as well as for inclusion into the __init__.py's + # __all__ list. + _get_python_module_name(${LIBRARY_NAME} pyModuleName) + + # Accumulate Python module names. + set_property(GLOBAL + APPEND PROPERTY PXR_PYTHON_MODULES ${pyModuleName} + ) + + # Always install under the 'pxr' module, rather than base on the + # project name. This makes importing consistent, e.g. + # 'from pxr import X'. Additionally, python libraries always install + # into the default lib install, not into the third_party subdirectory + # or similar. + set(libInstallPrefix "lib/python/pxr/${pyModuleName}") + + # Python modules need to be able to access their corresponding + # wrapped library and the install lib directory. + _pxr_init_rpath(rpath "${libInstallPrefix}") + _pxr_add_rpath(rpath + "${CMAKE_INSTALL_PREFIX}/${args_WRAPPED_LIB_INSTALL_PREFIX}") + _pxr_add_rpath(rpath "${CMAKE_INSTALL_PREFIX}/lib") + _pxr_install_rpath(rpath ${LIBRARY_NAME}) + + _get_folder("_python" folder) + set_target_properties(${LIBRARY_NAME} + PROPERTIES + PREFIX "" + FOLDER "${folder}" + ) + if(WIN32) + # Python modules must be suffixed with .pyd on Windows. + set_target_properties(${LIBRARY_NAME} + PROPERTIES + SUFFIX ".pyd" + ) + elseif(APPLE) + # Python modules must be suffixed with .so on Mac. + set_target_properties(${LIBRARY_NAME} + PROPERTIES + SUFFIX ".so" + ) + endif() + + target_compile_definitions(${LIBRARY_NAME} + PRIVATE + MFB_PACKAGE_NAME=${PXR_PACKAGE} + MFB_ALT_PACKAGE_NAME=${PXR_PACKAGE} + MFB_PACKAGE_MODULE=${pyModuleName} + ) + + _pxr_target_link_libraries(${LIBRARY_NAME} + ${NAME} + ${PXR_MALLOC_LIBRARY} + ) + + # All Python modules require support code from tf. Linking with the + # monolithic library will (deliberately) not pick up the dependency + # on tf. + add_dependencies(${LIBRARY_NAME} tf) + + # Include headers from the build directory. + get_filename_component( + PRIVATE_INC_DIR + "${CMAKE_BINARY_DIR}/include" + ABSOLUTE + ) + if (PXR_INSTALL_SUBDIR) + get_filename_component( + SUBDIR_INC_DIR + "${CMAKE_BINARY_DIR}/${PXR_INSTALL_SUBDIR}/include" + ABSOLUTE + ) + endif() + target_include_directories(${LIBRARY_NAME} + PRIVATE + ${PRIVATE_INC_DIR} + ${SUBDIR_INC_DIR} + ) + + if (args_INCLUDE_DIRS) + target_include_directories(${LIBRARY_NAME} + PUBLIC + ${args_INCLUDE_DIRS} + ) + endif() + + install( + TARGETS ${LIBRARY_NAME} + LIBRARY DESTINATION ${libInstallPrefix} + RUNTIME DESTINATION ${libInstallPrefix} + ) + + if(NOT "${PXR_PREFIX}" STREQUAL "") + if(args_PRECOMPILED_HEADERS) + _pxr_enable_precompiled_header(${LIBRARY_NAME} + OUTPUT_NAME_PREFIX "py" + SOURCE_NAME "${args_PRECOMPILED_HEADER_NAME}" + ) + endif() + endif() +endfunction() # pxr_python_module + +# Add a library target named NAME. +function(_pxr_library NAME) + # Argument parsing. + set(options + ) + set(oneValueArgs + PREFIX + SUBDIR + SUFFIX + TYPE + PRECOMPILED_HEADERS + PRECOMPILED_HEADER_NAME + ) + set(multiValueArgs + PUBLIC_HEADERS + PRIVATE_HEADERS + CPPFILES + LIBRARIES + INCLUDE_DIRS + RESOURCE_FILES + LIB_INSTALL_PREFIX_RESULT + ) + cmake_parse_arguments(args + "${options}" + "${oneValueArgs}" + "${multiValueArgs}" + ${ARGN} + ) + + # + # Set up the target. + # + + # Note OBJECT and PLUGIN types. + set(isObject FALSE) + set(isPlugin FALSE) + if(args_TYPE STREQUAL "OBJECT" OR args_TYPE STREQUAL "OBJECT_PLUGIN") + set(isObject TRUE) + endif() + if(args_TYPE STREQUAL "PLUGIN" OR args_TYPE STREQUAL "OBJECT_PLUGIN") + set(isPlugin TRUE) + endif() + + if(_building_core) + # We need to distinguish core libraries. We keep track of them here. + get_property(help CACHE PXR_CORE_LIBS PROPERTY HELPSTRING) + list(APPEND PXR_CORE_LIBS ${NAME}) + set(PXR_CORE_LIBS "${PXR_CORE_LIBS}" CACHE INTERNAL "${help}") + + # Keep track of core OBJECT libraries. + if(isObject) + get_property(help CACHE PXR_OBJECT_LIBS PROPERTY HELPSTRING) + list(APPEND PXR_OBJECT_LIBS ${NAME}) + set(PXR_OBJECT_LIBS "${PXR_OBJECT_LIBS}" CACHE INTERNAL "${help}") + endif() + endif() + + # Add the target. We also add the headers because that's the easiest + # way to get them to appear in IDE projects. + if(isObject) + # When building a monolithic library we don't build individual + # static or shared libraries. Instead we build OBJECT libraries + # which simply compile the sources. + # + # These can't be linked like other libraries and as a result we + # don't automatically get transitive compiler definitions, + # include directories or link libraries. We have to do that + # manually. See pxr_monolithic_epilogue(). + add_library(${NAME} + OBJECT + ${args_CPPFILES} + ${args_PUBLIC_HEADERS} + ${args_PRIVATE_HEADERS} + ) + + elseif(args_TYPE STREQUAL "STATIC") + # Building an explicitly static library. + add_library(${NAME} + STATIC + ${args_CPPFILES} + ${args_PUBLIC_HEADERS} + ${args_PRIVATE_HEADERS} + ) + + else() + # Building an explicitly shared library or plugin. + add_library(${NAME} + SHARED + ${args_CPPFILES} + ${args_PUBLIC_HEADERS} + ${args_PRIVATE_HEADERS} + ) + endif() + + # + # Compute names and paths. + # + + # Where do we install to? + _get_install_dir("include" headerInstallDir) + _get_install_dir("include/${PXR_PREFIX}/${NAME}" headerInstallPrefix) + _get_install_dir("lib" libInstallPrefix) + if(isPlugin) + _get_install_dir("plugin" pluginInstallPrefix) + if(NOT PXR_INSTALL_SUBDIR) + # XXX -- Why this difference? + _get_install_dir("plugin/usd" pluginInstallPrefix) + endif() + if(NOT isObject) + # A plugin embedded in the monolithic library is found in + # the usual library location, otherwise plugin libraries + # are in the plugin install location. + set(libInstallPrefix "${pluginInstallPrefix}") + endif() + else() + _get_install_dir("lib/usd" pluginInstallPrefix) + endif() + if(args_SUBDIR) + set(libInstallPrefix "${libInstallPrefix}/${args_SUBDIR}") + set(pluginInstallPrefix "${pluginInstallPrefix}/${args_SUBDIR}") + endif() + # Return libInstallPrefix to caller. + if(args_LIB_INSTALL_PREFIX_RESULT) + set(${args_LIB_INSTALL_PREFIX_RESULT} "${libInstallPrefix}" PARENT_SCOPE) + endif() + + # Names and paths passed to the compile via macros. Paths should be + # relative to facilitate relocating the build. + _get_python_module_name(${NAME} pythonModuleName) + string(TOUPPER ${NAME} uppercaseName) + if(PXR_INSTALL_LOCATION) + file(TO_CMAKE_PATH "${PXR_INSTALL_LOCATION}" pxrInstallLocation) + set(pxrInstallLocation "PXR_INSTALL_LOCATION=${pxrInstallLocation}") + endif() + + # API macros. + set(apiPublic "") + set(apiPrivate ${uppercaseName}_EXPORTS=1) + if(NOT _building_monolithic AND args_TYPE STREQUAL "STATIC") + set(apiPublic PXR_STATIC=1) + endif() + + # Final name. + set(libraryFilename "${args_PREFIX}${NAME}${args_SUFFIX}") + set(pluginToLibraryPath "") + + # Figure out the relative path from this library's plugin location + # (in the libplug sense, which applies even to non-plugins, and is + # where we can find external resources for the library) to the + # library's location. This can be embedded into resource files. + # + # If we're building a monolithic library or individual static libraries, + # these libraries are not separately loadable at runtime. In these cases, + # we don't need to specify the library's location, so we leave + # pluginToLibraryPath empty. + if(NOT args_TYPE STREQUAL "STATIC") + if(NOT (";${PXR_CORE_LIBS};" MATCHES ";${NAME};" AND _building_monolithic)) + file(RELATIVE_PATH + pluginToLibraryPath + ${CMAKE_INSTALL_PREFIX}/${pluginInstallPrefix}/${NAME} + ${CMAKE_INSTALL_PREFIX}/${libInstallPrefix}/${libraryFilename}) + endif() + endif() + + # + # Set up the compile/link. + # + + # PIC is required by shared libraries. It's on for static libraries + # because we'll likely link them into a shared library. + # + # We set PUBLIC_HEADER so we install directly from the source tree. + # We don't want to install the headers copied to the build tree + # because they have #line directives embedded to aid in debugging. + _get_folder("" folder) + set_target_properties(${NAME} + PROPERTIES + FOLDER "${folder}" + POSITION_INDEPENDENT_CODE ON + IMPORT_PREFIX "${args_PREFIX}" + PREFIX "${args_PREFIX}" + SUFFIX "${args_SUFFIX}" + PUBLIC_HEADER "${args_PUBLIC_HEADERS}" + ) + + set(pythonEnabled "PXR_PYTHON_ENABLED=1") + if(TARGET shared_libs) + set(pythonModulesEnabled "PXR_PYTHON_MODULES_ENABLED=1") + endif() + target_compile_definitions(${NAME} + PUBLIC + ${pythonEnabled} + ${apiPublic} + PRIVATE + MFB_PACKAGE_NAME=${PXR_PACKAGE} + MFB_ALT_PACKAGE_NAME=${PXR_PACKAGE} + MFB_PACKAGE_MODULE=${pythonModuleName} + PXR_BUILD_LOCATION=usd + PXR_PLUGIN_BUILD_LOCATION=../plugin/usd + ${pxrInstallLocation} + ${pythonModulesEnabled} + ${apiPrivate} + ) + + # Copy headers to the build directory and include from there and from + # external packages. + _copy_headers(${NAME} + FILES + ${args_PUBLIC_HEADERS} + ${args_PRIVATE_HEADERS} + PREFIX + ${PXR_PREFIX} + ) + + # XXX: Versions of CMake 2.8.11 and earlier complain about + # INTERFACE_INCLUDE_DIRECTORIES containing a relative path if we include + # the INTERFACE directory here, so only do so for more recent versions. + if(${CMAKE_VERSION} VERSION_GREATER 2.8.11.2) + target_include_directories(${NAME} + PRIVATE + "${CMAKE_BINARY_DIR}/include" + "${CMAKE_BINARY_DIR}/${PXR_INSTALL_SUBDIR}/include" + PUBLIC + ${args_INCLUDE_DIRS} + INTERFACE + $ + ) + else() + target_include_directories(${NAME} + PRIVATE + "${CMAKE_BINARY_DIR}/include" + "${CMAKE_BINARY_DIR}/${PXR_INSTALL_SUBDIR}/include" + PUBLIC + ${args_INCLUDE_DIRS} + ) + endif() + + # XXX -- May want some plugins to be baked into monolithic. + _pxr_target_link_libraries(${NAME} ${args_LIBRARIES}) + + # Rpath has libraries under the third party prefix and the install prefix. + # The former is for helper libraries for a third party application and + # the latter for core USD libraries. + _pxr_init_rpath(rpath "${libInstallPrefix}") + _pxr_add_rpath(rpath "${CMAKE_INSTALL_PREFIX}/${PXR_INSTALL_SUBDIR}/lib") + _pxr_add_rpath(rpath "${CMAKE_INSTALL_PREFIX}/lib") + _pxr_install_rpath(rpath ${NAME}) + + # + # Set up the install. + # + + if(isObject) + get_target_property(install_headers ${NAME} PUBLIC_HEADER) + if (install_headers) + install( + FILES ${install_headers} + DESTINATION ${headerInstallPrefix} + ) + endif() + else() + # Do not include plugins libs in externally linkable targets + if(isPlugin) + install( + TARGETS ${NAME} + LIBRARY DESTINATION ${libInstallPrefix} + ARCHIVE DESTINATION ${libInstallPrefix} + RUNTIME DESTINATION ${libInstallPrefix} + PUBLIC_HEADER DESTINATION ${headerInstallPrefix} + ) + if(WIN32) + install( + FILES $ + DESTINATION ${libInstallPrefix} + OPTIONAL + ) + endif() + elseif(BUILD_SHARED_LIBS) + install( + TARGETS ${NAME} + EXPORT pxrTargets + LIBRARY DESTINATION ${libInstallPrefix} + ARCHIVE DESTINATION ${libInstallPrefix} + RUNTIME DESTINATION ${libInstallPrefix} + PUBLIC_HEADER DESTINATION ${headerInstallPrefix} + ) + if(WIN32) + install( + FILES $ + EXPORT pxrTargets + DESTINATION ${libInstallPrefix} + OPTIONAL + ) + endif() + else() + install( + TARGETS ${NAME} + EXPORT pxrTargets + LIBRARY DESTINATION ${libInstallPrefix} + ARCHIVE DESTINATION ${libInstallPrefix} + RUNTIME DESTINATION ${libInstallPrefix} + PUBLIC_HEADER DESTINATION ${headerInstallPrefix} + ) + endif() + + if(NOT isPlugin) + export(TARGETS ${NAME} + APPEND + FILE "${PROJECT_BINARY_DIR}/pxrTargets.cmake" + ) + endif() + + endif() + + _install_resource_files( + ${NAME} + "${pluginInstallPrefix}" + "${pluginToLibraryPath}" + ${args_RESOURCE_FILES}) + + # + # Set up precompiled headers. + # + + if(NOT "${PXR_PREFIX}" STREQUAL "") + if(args_PRECOMPILED_HEADERS) + _pxr_enable_precompiled_header(${NAME} + SOURCE_NAME "${args_PRECOMPILED_HEADER_NAME}" + ) + endif() + endif() +endfunction() # _pxr_library \ No newline at end of file diff --git a/cmake/macros/Public.cmake b/cmake/macros/Public.cmake new file mode 100644 index 00000000..dec10280 --- /dev/null +++ b/cmake/macros/Public.cmake @@ -0,0 +1,1108 @@ +# +# Copyright 2016 Pixar +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# +include(Private) + +function(pxr_python_bin BIN_NAME) + set(oneValueArgs + PYTHON_FILE + ) + set(multiValueArgs + DEPENDENCIES + ) + cmake_parse_arguments(pb + "" + "${oneValueArgs}" + "${multiValueArgs}" + ${ARGN} + ) + + # If we can't build Python modules then do nothing. + if(NOT TARGET python) + message(STATUS "Skipping Python program ${BIN_NAME}, Python modules required") + return() + endif() + + _get_install_dir(bin installDir) + + # Source file. + if( "${pb_PYTHON_FILE}" STREQUAL "") + set(infile ${CMAKE_CURRENT_SOURCE_DIR}/${BIN_NAME}.py) + else() + set(infile ${CMAKE_CURRENT_SOURCE_DIR}/${pb_PYTHON_FILE}) + endif() + + # Destination file. + set(outfile ${CMAKE_CURRENT_BINARY_DIR}/${BIN_NAME}) + + # /pxrpythonsubst will be replaced with the full path to the configured + # python executable. This doesn't use the CMake ${...} or @...@ syntax + # for backwards compatibility with other build systems. + add_custom_command( + OUTPUT ${outfile} + DEPENDS ${infile} + COMMENT "Substituting Python shebang" + COMMAND + ${PYTHON_EXECUTABLE} + ${PROJECT_SOURCE_DIR}/cmake/macros/shebang.py + ${PXR_PYTHON_SHEBANG} + ${infile} + ${outfile} + ) + list(APPEND outputs ${outfile}) + + install( + PROGRAMS ${outfile} + DESTINATION ${installDir} + RENAME ${BIN_NAME} + ) + + # Windows by default cannot execute Python files so here + # we create a batch file wrapper that invokes the python + # files. + if(WIN32) + add_custom_command( + OUTPUT ${outfile}.cmd + COMMENT "Creating Python cmd wrapper" + COMMAND + ${PYTHON_EXECUTABLE} + ${PROJECT_SOURCE_DIR}/cmake/macros/shebang.py + ${BIN_NAME} + ${outfile}.cmd + ) + list(APPEND outputs ${outfile}.cmd) + + install( + PROGRAMS ${outfile}.cmd + DESTINATION ${installDir} + RENAME ${BIN_NAME}.cmd + ) + endif() + + # Add the target. + add_custom_target(${BIN_NAME}_script + DEPENDS ${outputs} ${pb_DEPENDENCIES} + ) + add_dependencies(python ${BIN_NAME}_script) + + _get_folder("" folder) + set_target_properties(${BIN_NAME}_script + PROPERTIES + FOLDER "${folder}" + ) +endfunction() # pxr_python_bin + +function(pxr_cpp_bin BIN_NAME) + _get_install_dir(bin installDir) + + set(multiValueArgs + LIBRARIES + INCLUDE_DIRS + ) + + cmake_parse_arguments(cb + "" + "" + "${multiValueArgs}" + ${ARGN} + ) + + add_executable(${BIN_NAME} ${BIN_NAME}.cpp) + + # Turn PIC ON otherwise ArchGetAddressInfo() on Linux may yield + # unexpected results. + _get_folder("" folder) + set_target_properties(${BIN_NAME} + PROPERTIES + FOLDER "${folder}" + POSITION_INDEPENDENT_CODE ON + ) + + # Install and include headers from the build directory. + get_filename_component( + PRIVATE_INC_DIR + "${CMAKE_BINARY_DIR}/include" + ABSOLUTE + ) + + target_include_directories(${BIN_NAME} + PRIVATE + ${PRIVATE_INC_DIR} + ${cb_INCLUDE_DIRS} + ) + + _pxr_init_rpath(rpath "${installDir}") + _pxr_install_rpath(rpath ${BIN_NAME}) + + _pxr_target_link_libraries(${BIN_NAME} + ${cb_LIBRARIES} + ${PXR_MALLOC_LIBRARY} + ) + + install( + TARGETS ${BIN_NAME} + DESTINATION ${installDir} + ) +endfunction() + +function(pxr_library NAME) + set(options + DISABLE_PRECOMPILED_HEADERS + KATANA_PLUGIN + ) + set(oneValueArgs + TYPE + PRECOMPILED_HEADER_NAME + ) + set(multiValueArgs + PUBLIC_CLASSES + PUBLIC_HEADERS + PRIVATE_CLASSES + PRIVATE_HEADERS + CPPFILES + LIBRARIES + INCLUDE_DIRS + RESOURCE_FILES + PYTHON_PUBLIC_CLASSES + PYTHON_PRIVATE_CLASSES + PYTHON_PUBLIC_HEADERS + PYTHON_PRIVATE_HEADERS + PYTHON_CPPFILES + PYMODULE_CPPFILES + PYMODULE_FILES + PYSIDE_UI_FILES + ) + + cmake_parse_arguments(args + "${options}" + "${oneValueArgs}" + "${multiValueArgs}" + ${ARGN} + ) + + # If python support is enabled, merge the python specific categories + # with the more general before setting up compilation. + if(PXR_ENABLE_PYTHON_SUPPORT) + if(args_PYTHON_PUBLIC_CLASSES) + list(APPEND args_PUBLIC_CLASSES ${args_PYTHON_PUBLIC_CLASSES}) + endif() + if(args_PYTHON_PUBLIC_HEADERS) + list(APPEND args_PUBLIC_HEADERS ${args_PYTHON_PUBLIC_HEADERS}) + endif() + if(args_PYTHON_PRIVATE_CLASSES) + list(APPEND args_PRIVATE_CLASSES ${args_PYTHON_PRIVATE_CLASSES}) + endif() + if(args_PYTHON_PRIVATE_HEADERS) + list(APPEND args_PRIVATE_HEADERS ${args_PYTHON_PRIVATE_HEADERS}) + endif() + if(args_PYTHON_CPPFILES) + list(APPEND args_CPPFILES ${args_PYTHON_CPPFILES}) + endif() + endif() + + # Collect libraries. + if(NOT args_TYPE STREQUAL "PLUGIN") + get_property(help CACHE PXR_ALL_LIBS PROPERTY HELPSTRING) + list(APPEND PXR_ALL_LIBS ${NAME}) + set(PXR_ALL_LIBS "${PXR_ALL_LIBS}" CACHE INTERNAL "${help}") + if(args_TYPE STREQUAL "STATIC") + # Note if this library is explicitly STATIC. + get_property(help CACHE PXR_STATIC_LIBS PROPERTY HELPSTRING) + list(APPEND PXR_STATIC_LIBS ${NAME}) + set(PXR_STATIC_LIBS "${PXR_STATIC_LIBS}" CACHE INTERNAL "${help}") + endif() + endif() + + # Expand classes into filenames. + _classes(${NAME} ${args_PRIVATE_CLASSES} PRIVATE) + _classes(${NAME} ${args_PUBLIC_CLASSES} PUBLIC) + + # If building a core plugin for a monolithic build then treat it almost + # like any other library and include it in the monolithic library. + if (_building_core AND _building_monolithic AND args_TYPE STREQUAL "PLUGIN") + set(args_TYPE "OBJECT_PLUGIN") + endif() + + # Custom tweaks. + if(args_TYPE STREQUAL "PLUGIN") + # We can't build plugins if we're not building shared libraries. + if(NOT TARGET shared_libs) + message(STATUS "Skipping plugin ${NAME}, shared libraries required") + return() + endif() + + set(prefix "") + set(suffix ${CMAKE_SHARED_LIBRARY_SUFFIX}) + + # Katana plugins install into a specific sub directory structure. + # In particular, shared objects install into plugin/Libs + if(args_KATANA_PLUGIN) + set(subdir "Libs") + endif() + else() + # If the caller didn't specify the library type then choose the + # type now. + if("x${args_TYPE}" STREQUAL "x") + if(_building_monolithic) + set(args_TYPE "OBJECT") + elseif(BUILD_SHARED_LIBS) + set(args_TYPE "SHARED") + else() + set(args_TYPE "STATIC") + endif() + endif() + + set(prefix "${PXR_LIB_PREFIX}") + if(args_TYPE STREQUAL "STATIC") + set(suffix ${CMAKE_STATIC_LIBRARY_SUFFIX}) + else() + set(suffix ${CMAKE_SHARED_LIBRARY_SUFFIX}) + endif() + endif() + + set(pch "ON") + if(args_DISABLE_PRECOMPILED_HEADERS) + set(pch "OFF") + endif() + + _pxr_library(${NAME} + TYPE "${args_TYPE}" + PREFIX "${prefix}" + SUFFIX "${suffix}" + SUBDIR "${subdir}" + CPPFILES "${args_CPPFILES};${${NAME}_CPPFILES}" + PUBLIC_HEADERS "${args_PUBLIC_HEADERS};${${NAME}_PUBLIC_HEADERS}" + PRIVATE_HEADERS "${args_PRIVATE_HEADERS};${${NAME}_PRIVATE_HEADERS}" + LIBRARIES "${args_LIBRARIES}" + INCLUDE_DIRS "${args_INCLUDE_DIRS}" + RESOURCE_FILES "${args_RESOURCE_FILES}" + PRECOMPILED_HEADERS "${pch}" + PRECOMPILED_HEADER_NAME "${args_PRECOMPILED_HEADER_NAME}" + LIB_INSTALL_PREFIX_RESULT libInstallPrefix + ) + + if(PXR_ENABLE_PYTHON_SUPPORT AND (args_PYMODULE_CPPFILES OR args_PYMODULE_FILES OR args_PYSIDE_UI_FILES)) + _pxr_python_module( + ${NAME} + WRAPPED_LIB_INSTALL_PREFIX "${libInstallPrefix}" + PYTHON_FILES ${args_PYMODULE_FILES} + PYSIDE_UI_FILES ${args_PYSIDE_UI_FILES} + CPPFILES ${args_PYMODULE_CPPFILES} + INCLUDE_DIRS ${args_INCLUDE_DIRS} + PRECOMPILED_HEADERS ${pch} + PRECOMPILED_HEADER_NAME ${args_PRECOMPILED_HEADER_NAME} + ) + endif() +endfunction() + +macro(pxr_shared_library NAME) + pxr_library(${NAME} TYPE "SHARED" ${ARGN}) +endmacro(pxr_shared_library) + +macro(pxr_static_library NAME) + pxr_library(${NAME} TYPE "STATIC" ${ARGN}) +endmacro(pxr_static_library) + +macro(pxr_plugin NAME) + pxr_library(${NAME} TYPE "PLUGIN" ${ARGN}) +endmacro(pxr_plugin) + +function(pxr_setup_python) + get_property(pxrPythonModules GLOBAL PROPERTY PXR_PYTHON_MODULES) + + # A new list where each python module is quoted + set(converted "") + foreach(module ${pxrPythonModules}) + list(APPEND converted "'${module}'") + endforeach() + + # Join these with a ', ' + string(REPLACE ";" ", " pyModulesStr "${converted}") + + # Install a pxr __init__.py with an appropriate __all__ + _get_install_dir(lib/python/pxr installPrefix) + + file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/generated_modules_init.py" + "__all__ = [${pyModulesStr}]\n") + + install( + FILES "${CMAKE_CURRENT_BINARY_DIR}/generated_modules_init.py" + DESTINATION ${installPrefix} + RENAME "__init__.py" + ) +endfunction() # pxr_setup_python + +function (pxr_create_test_module MODULE_NAME) + # If we can't build Python modules then do nothing. + if(NOT TARGET python) + return() + endif() + + if (NOT PXR_BUILD_TESTS) + return() + endif() + + cmake_parse_arguments(tm "" "INSTALL_PREFIX;SOURCE_DIR" "" ${ARGN}) + + if (NOT tm_SOURCE_DIR) + set(tm_SOURCE_DIR testenv) + endif() + + # Look specifically for an __init__.py and a plugInfo.json prefixed by the + # module name. These will be installed without the module prefix. + set(initPyFile ${tm_SOURCE_DIR}/${MODULE_NAME}__init__.py) + set(plugInfoFile ${tm_SOURCE_DIR}/${MODULE_NAME}_plugInfo.json) + + # XXX -- We shouldn't have to install to run tests. + if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${initPyFile}") + install( + FILES + ${initPyFile} + RENAME + __init__.py + DESTINATION + tests/${tm_INSTALL_PREFIX}/lib/python/${MODULE_NAME} + ) + endif() + if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${plugInfoFile}") + install( + FILES + ${plugInfoFile} + RENAME + plugInfo.json + DESTINATION + tests/${tm_INSTALL_PREFIX}/lib/python/${MODULE_NAME} + ) + endif() +endfunction() # pxr_create_test_module + +function(pxr_build_test_shared_lib LIBRARY_NAME) + if (PXR_BUILD_TESTS) + cmake_parse_arguments(bt + "" + "INSTALL_PREFIX;SOURCE_DIR" + "LIBRARIES;CPPFILES" + ${ARGN} + ) + + add_library(${LIBRARY_NAME} + SHARED + ${bt_CPPFILES} + ) + _pxr_target_link_libraries(${LIBRARY_NAME} + ${bt_LIBRARIES} + ) + _get_folder("tests/lib" folder) + set_target_properties(${LIBRARY_NAME} + PROPERTIES + FOLDER "${folder}" + ) + + # Find libraries under the install prefix, which has the core USD + # libraries. + _pxr_init_rpath(rpath "tests/lib") + _pxr_add_rpath(rpath "${CMAKE_INSTALL_PREFIX}/lib") + _pxr_install_rpath(rpath ${LIBRARY_NAME}) + + if (NOT bt_SOURCE_DIR) + set(bt_SOURCE_DIR testenv) + endif() + set(testPlugInfoSrcPath ${bt_SOURCE_DIR}/${LIBRARY_NAME}_plugInfo.json) + + if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${testPlugInfoSrcPath}") + set(TEST_PLUG_INFO_RESOURCE_PATH "Resources") + set(TEST_PLUG_INFO_ROOT "..") + set(LIBRARY_FILE "${CMAKE_SHARED_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}") + + set(testPlugInfoLibDir "tests/${bt_INSTALL_PREFIX}/lib/${LIBRARY_NAME}") + set(testPlugInfoResourceDir "${testPlugInfoLibDir}/${TEST_PLUG_INFO_RESOURCE_PATH}") + set(testPlugInfoPath "${CMAKE_BINARY_DIR}/${testPlugInfoResourceDir}/plugInfo.json") + + file(RELATIVE_PATH + TEST_PLUG_INFO_LIBRARY_PATH + "${CMAKE_INSTALL_PREFIX}/${testPlugInfoLibDir}" + "${CMAKE_INSTALL_PREFIX}/tests/lib/${LIBRARY_FILE}") + + configure_file("${testPlugInfoSrcPath}" "${testPlugInfoPath}") + # XXX -- We shouldn't have to install to run tests. + install( + FILES ${testPlugInfoPath} + DESTINATION ${testPlugInfoResourceDir}) + endif() + + # We always want this test to build after the package it's under, even if + # it doesn't link directly. This ensures that this test is able to include + # headers from its parent package. + add_dependencies(${LIBRARY_NAME} ${PXR_PACKAGE}) + + # Test libraries can include the private headers of their parent PXR_PACKAGE + # library + target_include_directories(${LIBRARY_NAME} + PRIVATE $ + ) + + # XXX -- We shouldn't have to install to run tests. + install( + TARGETS ${LIBRARY_NAME} + LIBRARY DESTINATION "tests/lib" + ARCHIVE DESTINATION "tests/lib" + RUNTIME DESTINATION "tests/lib" + ) + endif() +endfunction() # pxr_build_test_shared_lib + +function(pxr_build_test TEST_NAME) + if (PXR_BUILD_TESTS) + cmake_parse_arguments(bt + "" "" + "LIBRARIES;CPPFILES" + ${ARGN} + ) + + add_executable(${TEST_NAME} + ${bt_CPPFILES} + ) + + # Turn PIC ON otherwise ArchGetAddressInfo() on Linux may yield + # unexpected results. + _get_folder("tests/bin" folder) + set_target_properties(${TEST_NAME} + PROPERTIES + FOLDER "${folder}" + POSITION_INDEPENDENT_CODE ON + ) + target_include_directories(${TEST_NAME} + PRIVATE $ + ) + _pxr_target_link_libraries(${TEST_NAME} + ${bt_LIBRARIES} + ) + + # Find libraries under the install prefix, which has the core USD + # libraries. + _pxr_init_rpath(rpath "tests") + _pxr_add_rpath(rpath "${CMAKE_INSTALL_PREFIX}/lib") + _pxr_install_rpath(rpath ${TEST_NAME}) + + # XXX -- We shouldn't have to install to run tests. + install(TARGETS ${TEST_NAME} + RUNTIME DESTINATION "tests" + ) + endif() +endfunction() # pxr_build_test + +function(pxr_test_scripts) + # If we can't build Python modules then do nothing. + if(NOT TARGET python) + return() + endif() + + if (NOT PXR_BUILD_TESTS) + return() + endif() + + foreach(file ${ARGN}) + get_filename_component(destFile ${file} NAME_WE) + # XXX -- We shouldn't have to install to run tests. + install( + PROGRAMS ${file} + DESTINATION tests + RENAME ${destFile} + ) + endforeach() +endfunction() # pxr_test_scripts + +function(pxr_install_test_dir) + if (PXR_BUILD_TESTS) + cmake_parse_arguments(bt + "" + "SRC;DEST" + "" + ${ARGN} + ) + + # XXX -- We shouldn't have to install to run tests. + install( + DIRECTORY ${bt_SRC}/ + DESTINATION tests/ctest/${bt_DEST} + ) + endif() +endfunction() # pxr_install_test_dir + +function(pxr_register_test TEST_NAME) + if (PXR_BUILD_TESTS) + cmake_parse_arguments(bt + "RUN_SERIAL;PYTHON;REQUIRES_SHARED_LIBS;REQUIRES_PYTHON_MODULES" + "CUSTOM_PYTHON;COMMAND;STDOUT_REDIRECT;STDERR_REDIRECT;DIFF_COMPARE;POST_COMMAND;POST_COMMAND_STDOUT_REDIRECT;POST_COMMAND_STDERR_REDIRECT;PRE_COMMAND;PRE_COMMAND_STDOUT_REDIRECT;PRE_COMMAND_STDERR_REDIRECT;FILES_EXIST;FILES_DONT_EXIST;CLEAN_OUTPUT;EXPECTED_RETURN_CODE;TESTENV" + "ENV;PRE_PATH;POST_PATH" + ${ARGN} + ) + + # Discard tests that required shared libraries. + if(NOT TARGET shared_libs) + # Explicit requirement. This is for C++ tests that dynamically + # load libraries linked against USD code. These tests will have + # multiple copies of symbols and will likely re-execute + # ARCH_CONSTRUCTOR and registration functions, which will almost + # certainly cause problems. + if(bt_REQUIRES_SHARED_LIBS) + message(STATUS "Skipping test ${TEST_NAME}, shared libraries required") + return() + endif() + endif() + + if(NOT TARGET python) + # Implicit requirement. Python modules require shared USD + # libraries. If the test runs python it's certainly going + # to load USD modules. If the test uses C++ to load USD + # modules it tells us via REQUIRES_PYTHON_MODULES. + if(bt_PYTHON OR bt_CUSTOM_PYTHON OR bt_REQUIRES_PYTHON_MODULES) + message(STATUS "Skipping test ${TEST_NAME}, Python modules required") + return() + endif() + endif() + + # This harness is a filter which allows us to manipulate the test run, + # e.g. by changing the environment, changing the expected return code, etc. + set(testWrapperCmd ${PROJECT_SOURCE_DIR}/cmake/macros/testWrapper.py --verbose) + + if (bt_STDOUT_REDIRECT) + set(testWrapperCmd ${testWrapperCmd} --stdout-redirect=${bt_STDOUT_REDIRECT}) + endif() + + if (bt_STDERR_REDIRECT) + set(testWrapperCmd ${testWrapperCmd} --stderr-redirect=${bt_STDERR_REDIRECT}) + endif() + + if (bt_PRE_COMMAND_STDOUT_REDIRECT) + set(testWrapperCmd ${testWrapperCmd} --pre-command-stdout-redirect=${bt_PRE_COMMAND_STDOUT_REDIRECT}) + endif() + + if (bt_PRE_COMMAND_STDERR_REDIRECT) + set(testWrapperCmd ${testWrapperCmd} --pre-command-stderr-redirect=${bt_PRE_COMMAND_STDERR_REDIRECT}) + endif() + + if (bt_POST_COMMAND_STDOUT_REDIRECT) + set(testWrapperCmd ${testWrapperCmd} --post-command-stdout-redirect=${bt_POST_COMMAND_STDOUT_REDIRECT}) + endif() + + if (bt_POST_COMMAND_STDERR_REDIRECT) + set(testWrapperCmd ${testWrapperCmd} --post-command-stderr-redirect=${bt_POST_COMMAND_STDERR_REDIRECT}) + endif() + + # Not all tests will have testenvs, but if they do let the wrapper know so + # it can copy the testenv contents into the run directory. By default, + # assume the testenv has the same name as the test but allow it to be + # overridden by specifying TESTENV. + if (bt_TESTENV) + set(testenvDir ${CMAKE_INSTALL_PREFIX}/tests/ctest/${bt_TESTENV}) + else() + set(testenvDir ${CMAKE_INSTALL_PREFIX}/tests/ctest/${TEST_NAME}) + endif() + + set(testWrapperCmd ${testWrapperCmd} --testenv-dir=${testenvDir}) + + if (bt_DIFF_COMPARE) + set(testWrapperCmd ${testWrapperCmd} --diff-compare=${bt_DIFF_COMPARE}) + + # For now the baseline directory is assumed by convention from the test + # name. There may eventually be cases where we'd want to specify it by + # an argument though. + set(baselineDir ${testenvDir}/baseline) + set(testWrapperCmd ${testWrapperCmd} --baseline-dir=${baselineDir}) + endif() + + if (bt_CLEAN_OUTPUT) + set(testWrapperCmd ${testWrapperCmd} --clean-output-paths=${bt_CLEAN_OUTPUT}) + endif() + + if (bt_FILES_EXIST) + set(testWrapperCmd ${testWrapperCmd} --files-exist=${bt_FILES_EXIST}) + endif() + + if (bt_FILES_DONT_EXIST) + set(testWrapperCmd ${testWrapperCmd} --files-dont-exist=${bt_FILES_DONT_EXIST}) + endif() + + if (bt_PRE_COMMAND) + set(testWrapperCmd ${testWrapperCmd} --pre-command=${bt_PRE_COMMAND}) + endif() + + if (bt_POST_COMMAND) + set(testWrapperCmd ${testWrapperCmd} --post-command=${bt_POST_COMMAND}) + endif() + + if (bt_EXPECTED_RETURN_CODE) + set(testWrapperCmd ${testWrapperCmd} --expected-return-code=${bt_EXPECTED_RETURN_CODE}) + endif() + + if (bt_ENV) + foreach(env ${bt_ENV}) + set(testWrapperCmd ${testWrapperCmd} --env-var=${env}) + endforeach() + endif() + + if (bt_PRE_PATH) + foreach(path ${bt_PRE_PATH}) + set(testWrapperCmd ${testWrapperCmd} --pre-path=${path}) + endforeach() + endif() + + if (bt_POST_PATH) + foreach(path ${bt_POST_PATH}) + set(testWrapperCmd ${testWrapperCmd} --post-path=${path}) + endforeach() + endif() + + # If we're building static libraries, the C++ tests that link against + # these libraries will look for resource files in the "usd" subdirectory + # relative to where the tests are installed. However, the build installs + # these files in the "lib" directory where the libraries are installed. + # + # We don't want to copy these resource files for each test, so instead + # we set the PXR_PLUGINPATH_NAME env var to point to the "lib/usd" + # directory where these files are installed. + if (NOT TARGET shared_libs) + set(_plugSearchPathEnvName "PXR_PLUGINPATH_NAME") + if (PXR_OVERRIDE_PLUGINPATH_NAME) + set(_plugSearchPathEnvName ${PXR_OVERRIDE_PLUGINPATH_NAME}) + endif() + + set(testWrapperCmd ${testWrapperCmd} --env-var=${_plugSearchPathEnvName}=${CMAKE_INSTALL_PREFIX}/lib/usd) + endif() + + # Ensure that Python imports the Python files built by this build. + # On Windows convert backslash to slash and don't change semicolons + # to colons. + set(_testPythonPath "${CMAKE_INSTALL_PREFIX}/lib/python;$ENV{PYTHONPATH}") + if(WIN32) + string(REGEX REPLACE "\\\\" "/" _testPythonPath "${_testPythonPath}") + else() + string(REPLACE ";" ":" _testPythonPath "${_testPythonPath}") + endif() + + # Ensure we run with the appropriate python executable. + if (bt_CUSTOM_PYTHON) + set(testCmd "${bt_CUSTOM_PYTHON} ${bt_COMMAND}") + elseif (bt_PYTHON) + set(testCmd "${PYTHON_EXECUTABLE} ${bt_COMMAND}") + else() + set(testCmd "${bt_COMMAND}") + endif() + + add_test( + NAME ${TEST_NAME} + COMMAND ${PYTHON_EXECUTABLE} ${testWrapperCmd} + "--env-var=PYTHONPATH=${_testPythonPath}" ${testCmd} + ) + + # But in some cases, we need to pass cmake properties directly to cmake + # run_test, rather than configuring the environment + if (bt_RUN_SERIAL) + set_tests_properties(${TEST_NAME} PROPERTIES RUN_SERIAL TRUE) + endif() + endif() +endfunction() # pxr_register_test + +function(pxr_setup_plugins) + # Install a top-level plugInfo.json in the shared area and into the + # top-level plugin area + _get_resources_dir_name(resourcesDir) + + # Add extra plugInfo.json include paths to the top-level plugInfo.json, + # relative to that top-level file. + set(extraIncludes "") + list(REMOVE_DUPLICATES PXR_EXTRA_PLUGINS) + foreach(dirName ${PXR_EXTRA_PLUGINS}) + file(RELATIVE_PATH + relDirName + "${CMAKE_INSTALL_PREFIX}/lib/usd" + "${CMAKE_INSTALL_PREFIX}/${dirName}" + ) + set(extraIncludes "${extraIncludes},\n \"${relDirName}/\"") + endforeach() + + set(plugInfoContents "{\n \"Includes\": [\n \"*/${resourcesDir}/\"${extraIncludes}\n ]\n}\n") + file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/plugins_plugInfo.json" + "${plugInfoContents}") + install( + FILES "${CMAKE_CURRENT_BINARY_DIR}/plugins_plugInfo.json" + DESTINATION lib/usd + RENAME "plugInfo.json" + ) + + set(plugInfoContents "{\n \"Includes\": [ \"*/${resourcesDir}/\" ]\n}\n") + file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/usd_plugInfo.json" + "${plugInfoContents}") + install( + FILES "${CMAKE_CURRENT_BINARY_DIR}/usd_plugInfo.json" + DESTINATION plugin/usd + RENAME "plugInfo.json" + ) +endfunction() # pxr_setup_plugins + +function(pxr_add_extra_plugins PLUGIN_AREAS) + # Install a top-level plugInfo.json in the given plugin areas. + _get_resources_dir_name(resourcesDir) + set(plugInfoContents "{\n \"Includes\": [ \"*/${resourcesDir}/\" ]\n}\n") + + get_property(help CACHE PXR_EXTRA_PLUGINS PROPERTY HELPSTRING) + + foreach(area ${PLUGIN_AREAS}) + file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/${area}_plugInfo.json" + "${plugInfoContents}") + install( + FILES "${CMAKE_CURRENT_BINARY_DIR}/${area}_plugInfo.json" + DESTINATION "${PXR_INSTALL_SUBDIR}/${area}" + RENAME "plugInfo.json" + ) + list(APPEND PXR_EXTRA_PLUGINS "${PXR_INSTALL_SUBDIR}/${area}") + endforeach() + + set(PXR_EXTRA_PLUGINS "${PXR_EXTRA_PLUGINS}" CACHE INTERNAL "${help}") +endfunction() # pxr_setup_third_plugins + +function(pxr_katana_nodetypes NODE_TYPES) + set(installDir ${PXR_INSTALL_SUBDIR}/plugin/Plugins/NodeTypes) + + set(pyFiles "") + set(importLines "") + + foreach (nodeType ${NODE_TYPES}) + list(APPEND pyFiles ${nodeType}.py) + set(importLines "import ${nodeType}\n") + endforeach() + + install( + PROGRAMS ${pyFiles} + DESTINATION ${installDir} + ) + + # Install a __init__.py that imports all the known node types + file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/generated_NodeTypes_init.py" + "${importLines}") + install( + FILES "${CMAKE_CURRENT_BINARY_DIR}/generated_NodeTypes_init.py" + DESTINATION "${installDir}" + RENAME "__init__.py" + ) +endfunction() # pxr_katana_nodetypes + +function(pxr_toplevel_prologue) + # Generate a namespace declaration header, pxr.h, at the top level of + # pxr at configuration time. + configure_file(${CMAKE_SOURCE_DIR}/pxr/pxr.h.in + ${CMAKE_BINARY_DIR}/include/pxr/pxr.h + ) + install( + FILES ${CMAKE_BINARY_DIR}/include/pxr/pxr.h + DESTINATION include/pxr + ) + + # Create a monolithic shared library target if we should import one + # or create one. + if(PXR_BUILD_MONOLITHIC) + if(PXR_MONOLITHIC_IMPORT) + # Gather the export information for usd_ms. + include("${PXR_MONOLITHIC_IMPORT}" OPTIONAL RESULT_VARIABLE found) + + # If the import wasn't found then create it and import it. + # This ensures that the build files will be regenerated if + # the file's contents change. If this isn't desired or + # write permissions aren't granted the client can configure + # first without PXR_MONOLITHIC_IMPORT, build the 'monolithic' + # target, build their own shared library and export file, + # then configure again with PXR_MONOLITHIC_IMPORT. + if(found STREQUAL "NOTFOUND") + file(WRITE "${PXR_MONOLITHIC_IMPORT}" "") + include("${PXR_MONOLITHIC_IMPORT}") + endif() + + # If there's an IMPORTED_LOCATION then its parent must be + # the install directory ${CMAKE_INSTALL_PREFIX}. If it + # isn't then we won't be able to find plugInfo.json files + # at runtime because they're found relative to the library + # that contains pxr/base/lib/plug/initConfig.cpp. The + # exception is if ${PXR_INSTALL_LOCATION} is set; in that + # case we assume the files will be found there regardless + # of IMPORTED_LOCATION. Note, however, that the install + # cannot be relocated in this case. + if(NOT PXR_INSTALL_LOCATION AND TARGET usd_ms) + get_property(location TARGET usd_ms PROPERTY IMPORTED_LOCATION) + if(location) + # Remove filename and directory. + get_filename_component(parent "${location}" PATH) + get_filename_component(parent "${parent}" PATH) + get_filename_component(parent "${parent}" ABSOLUTE) + get_filename_component(prefix "${CMAKE_INSTALL_PREFIX}" ABSOLUTE) + if(NOT "${parent}" STREQUAL "${prefix}") + message("IMPORTED_LOCATION for usd_ms ${location} inconsistent with install directory ${CMAKE_INSTALL_PREFIX}.") + message(WARNING "May not find plugins at runtime.") + endif() + endif() + endif() + else() + # Note that we ignore BUILD_SHARED_LIBS when building monolithic + # when PXR_MONOLITHIC_IMPORT isn't set: we always build an + # archive library from the core libraries and then build a + # shared library from that. BUILD_SHARED_LIBS is still used + # for libraries outside of the core. + + # We need at least one source file for the library so we + # create an empty one. + add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/usd_ms.cpp" + COMMAND ${CMAKE_COMMAND} -E touch "${CMAKE_CURRENT_BINARY_DIR}/usd_ms.cpp" + ) + + # Our shared library. + add_library(usd_ms SHARED "${CMAKE_CURRENT_BINARY_DIR}/usd_ms.cpp") + _get_folder("" folder) + set_target_properties(usd_ms + PROPERTIES + FOLDER "${folder}" + PREFIX "${PXR_LIB_PREFIX}" + IMPORT_PREFIX "${PXR_LIB_PREFIX}" + ) + _get_install_dir("lib" libInstallPrefix) + install( + TARGETS usd_ms + LIBRARY DESTINATION ${libInstallPrefix} + ARCHIVE DESTINATION ${libInstallPrefix} + RUNTIME DESTINATION ${libInstallPrefix} + ) + if(WIN32) + install( + FILES $ + DESTINATION ${libInstallPrefix} + OPTIONAL + ) + endif() + endif() + endif() + + # Create a target for shared libraries. We currently use this only + # to test its existence. + if(BUILD_SHARED_LIBS OR TARGET usd_ms) + add_custom_target(shared_libs) + endif() + + # Create a target for targets that require Python. Each should add + # itself as a dependency to the "python" target. + if(TARGET shared_libs AND PXR_ENABLE_PYTHON_SUPPORT) + add_custom_target(python ALL) + endif() +endfunction() # pxr_toplevel_prologue + +function(pxr_toplevel_epilogue) + # If we're building a shared monolithic library then link it against + # usd_m. + if(TARGET usd_ms AND NOT PXR_MONOLITHIC_IMPORT) + # We need to use whole-archive to get all the symbols. Also note + # that we carefully avoid adding the usd_m target itself by using + # TARGET_FILE. Linking the usd_m target would link usd_m and + # everything it links to. + if(MSVC) + target_link_libraries(usd_ms + PRIVATE + -WHOLEARCHIVE:$ + ) + elseif(CMAKE_COMPILER_IS_GNUCXX) + target_link_libraries(usd_ms + PRIVATE + -Wl,--whole-archive $ -Wl,--no-whole-archive + ) + elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") + target_link_libraries(usd_ms + PRIVATE + -Wl,-force_load $ + ) + endif() + + # Since we didn't add a dependency to usd_ms on usd_m above, we + # manually add it here along with compile definitions, include + # directories, etc + add_dependencies(usd_ms usd_m) + + # Add the stuff we didn't get because we didn't link against the + # usd_m target. + target_compile_definitions(usd_ms + PUBLIC + $ + ) + target_include_directories(usd_ms + PUBLIC + $ + ) + foreach(lib ${PXR_OBJECT_LIBS}) + get_property(libs TARGET ${lib} PROPERTY INTERFACE_LINK_LIBRARIES) + target_link_libraries(usd_ms + PUBLIC + ${libs} + ) + endforeach() + target_link_libraries(usd_ms + PUBLIC + ${PXR_MALLOC_LIBRARY} + ${PXR_THREAD_LIBS} + ) + + _pxr_init_rpath(rpath "${libInstallPrefix}") + _pxr_add_rpath(rpath "${CMAKE_INSTALL_PREFIX}/${PXR_INSTALL_SUBDIR}/lib") + _pxr_add_rpath(rpath "${CMAKE_INSTALL_PREFIX}/lib") + _pxr_install_rpath(rpath usd_ms) + endif() + + # Setup the plugins in the top epilogue to ensure that everybody has had a + # chance to update PXR_EXTRA_PLUGINS with their plugin paths. + pxr_setup_plugins() +endfunction() # pxr_toplevel_epilogue + +function(pxr_monolithic_epilogue) + # When building a monolithic library we want all API functions to be + # exported. So add FOO_EXPORTS=1 for every library in PXR_OBJECT_LIBS, + # where FOO is the uppercase version of the library name, to every + # library in PXR_OBJECT_LIBS. + set(exports "") + foreach(lib ${PXR_OBJECT_LIBS}) + string(TOUPPER ${lib} uppercaseName) + list(APPEND exports "${uppercaseName}_EXPORTS=1") + endforeach() + foreach(lib ${PXR_OBJECT_LIBS}) + set(objects "${objects};\$") + target_compile_definitions(${lib} PRIVATE ${exports}) + endforeach() + + # Collect all of the objects for all of the core libraries to add to + # the monolithic library. + set(objects "") + foreach(lib ${PXR_OBJECT_LIBS}) + set(objects "${objects};\$") + endforeach() + + # Add the monolithic library. This has to be delayed until now + # because $ isn't a real generator expression + # in that it can only appear in the sources of add_library() or + # add_executable(); it can't appear in target_sources(). We + # need at least one source file so we create an empty one + add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/usd_m.cpp" + COMMAND ${CMAKE_COMMAND} -E touch "${CMAKE_CURRENT_BINARY_DIR}/usd_m.cpp" + ) + add_library(usd_m STATIC "${CMAKE_CURRENT_BINARY_DIR}/usd_m.cpp" ${objects}) + + _get_folder("" folder) + set_target_properties(usd_m + PROPERTIES + FOLDER "${folder}" + POSITION_INDEPENDENT_CODE ON + PREFIX "${PXR_LIB_PREFIX}" + IMPORT_PREFIX "${PXR_LIB_PREFIX}" + ) + + # Adding $ will not bring along compile + # definitions, include directories, etc. Since we'll want those + # attached to usd_m we explicitly add them. + foreach(lib ${PXR_OBJECT_LIBS}) + target_compile_definitions(usd_m + PUBLIC + $ + ) + target_include_directories(usd_m + PUBLIC + $ + ) + + get_property(libs TARGET ${lib} PROPERTY INTERFACE_LINK_LIBRARIES) + target_link_libraries(usd_m + PUBLIC + ${libs} + ) + endforeach() + + # Manual export targets. We can't use install(EXPORT) because usd_m + # depends on OBJECT libraries which cannot be exported yet must be + # in order to export usd_m. We also have boilerplate for usd_ms, the + # externally built monolithic shared library containing usd_m. The + # client should replace the FIXMEs with the appropriate paths or + # use the usd_m export to build against and generate a usd_ms export. + set(export "") + set(export "${export}add_library(usd_m STATIC IMPORTED)\n") + set(export "${export}set_property(TARGET usd_m PROPERTY IMPORTED_LOCATION $)\n") + set(export "${export}set_property(TARGET usd_m PROPERTY INTERFACE_COMPILE_DEFINITIONS $)\n") + set(export "${export}set_property(TARGET usd_m PROPERTY INTERFACE_INCLUDE_DIRECTORIES $)\n") + set(export "${export}set_property(TARGET usd_m PROPERTY INTERFACE_LINK_LIBRARIES $)\n") + file(GENERATE + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/usd-targets-$.cmake" + CONTENT "${export}" + ) + set(export "") + set(export "${export}# Boilerplate for export of usd_ms. Replace FIXMEs with appropriate paths\n") + set(export "${export}# or include usd-targets-$.cmake in your own build and generate your\n") + set(export "${export}# own export file. Configure with PXR_MONOLITHIC_IMPORT set to the path of\n") + set(export "${export}# the export file.\n") + set(export "${export}add_library(usd_ms SHARED IMPORTED)\n") + set(export "${export}set_property(TARGET usd_ms PROPERTY IMPORTED_LOCATION FIXME)\n") + set(export "${export}#set_property(TARGET usd_ms PROPERTY IMPORTED_IMPLIB FIXME)\n") + set(export "${export}set_property(TARGET usd_ms PROPERTY INTERFACE_COMPILE_DEFINITIONS $)\n") + set(export "${export}set_property(TARGET usd_ms PROPERTY INTERFACE_INCLUDE_DIRECTORIES $)\n") + set(export "${export}set_property(TARGET usd_ms PROPERTY INTERFACE_LINK_LIBRARIES $)\n") + file(GENERATE + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/usd-imports-$.cmake" + CONTENT "${export}" + ) + + # Convenient name for building the monolithic library. + add_custom_target(monolithic + DEPENDS + usd_m + COMMAND ${CMAKE_COMMAND} -E copy + "${CMAKE_CURRENT_BINARY_DIR}/usd-targets-$.cmake" + "${CMAKE_BINARY_DIR}/usd-targets-$.cmake" + COMMAND ${CMAKE_COMMAND} -E copy + "${CMAKE_CURRENT_BINARY_DIR}/usd-imports-$.cmake" + "${CMAKE_BINARY_DIR}/usd-imports-$.cmake" + COMMAND ${CMAKE_COMMAND} -E echo Export file: ${CMAKE_BINARY_DIR}/usd-targets-$.cmake + COMMAND ${CMAKE_COMMAND} -E echo Import file: ${CMAKE_BINARY_DIR}/usd-imports-$.cmake + ) +endfunction() # pxr_monolithic_epilogue + +function(pxr_core_prologue) + set(_building_core TRUE PARENT_SCOPE) + if(PXR_BUILD_MONOLITHIC) + set(_building_monolithic TRUE PARENT_SCOPE) + endif() +endfunction() # pxr_core_prologue + +function(pxr_core_epilogue) + if(_building_core) + if(_building_monolithic) + pxr_monolithic_epilogue() + set(_building_monolithic FALSE PARENT_SCOPE) + endif() + if(PXR_ENABLE_PYTHON_SUPPORT) + pxr_setup_python() + endif() + set(_building_core FALSE PARENT_SCOPE) + endif() +endfunction() # pxr_core_epilogue \ No newline at end of file diff --git a/cmake/macros/compilePython.py b/cmake/macros/compilePython.py new file mode 100644 index 00000000..58ab77ca --- /dev/null +++ b/cmake/macros/compilePython.py @@ -0,0 +1,66 @@ +# +# Copyright 2016 Pixar +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# +# +# Usage: compilePython source.py dest.pyc +# +# This program compiles python code, providing a reasonable +# gcc-esque error message if errors occur. +# +# parameters: +# src.py - the source file to report errors for +# file.py - the installed location of the file +# file.pyc - the precompiled python file + +import sys +import py_compile + +if len(sys.argv) < 4: + print "Usage: %s src.py file.py file.pyc" % sys.argv[0] + sys.exit(1) + +try: + py_compile.compile(sys.argv[2], sys.argv[3], sys.argv[1], doraise=True) +except py_compile.PyCompileError as compileError: + exc_value = compileError.exc_value + if compileError.exc_type_name == SyntaxError.__name__: + # py_compile.compile stashes the type name and args of the exception + # in the raised PyCompileError rather than the exception itself. This + # is especially annoying because the args member of some SyntaxError + # instances are lacking the source information tuple, but do have a + # usable lineno. + error = exc_value[0] + try: + linenumber = exc_value[1][1] + line = exc_value[1][3] + print '%s:%s: %s: "%s"' % (sys.argv[1], linenumber, error, line) + except IndexError: + print '%s: Syntax error: "%s"' % (sys.argv[1], error) + else: + print "%s: Unhandled compile error: (%s) %s" % ( + sys.argv[1], compileError.exc_type_name, exc_value) + sys.exit(1) +except: + exc_type, exc_value, exc_traceback = sys.exc_info() + print "%s: Unhandled exception: %s" % (sys.argv[1], exc_value) + sys.exit(1) diff --git a/cmake/macros/copyHeaderForBuild.cmake b/cmake/macros/copyHeaderForBuild.cmake new file mode 100644 index 00000000..b987d999 --- /dev/null +++ b/cmake/macros/copyHeaderForBuild.cmake @@ -0,0 +1,29 @@ +# +# Copyright 2018 Pixar +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# +file(TO_NATIVE_PATH ${infile} INFILE) +file(TO_NATIVE_PATH ${outfile} OUTFILE) + +file(READ "${INFILE}" _tmp_file_content) +file(WRITE "${OUTFILE}" "\#line 1 ${infile}\n") +file(APPEND "${OUTFILE}" "${_tmp_file_content}") diff --git a/cmake/macros/generateDocs.py b/cmake/macros/generateDocs.py new file mode 100644 index 00000000..eba5ed99 --- /dev/null +++ b/cmake/macros/generateDocs.py @@ -0,0 +1,158 @@ +# +# Copyright 2017 Pixar +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# +# Generate doxygen based docs for USD. + +import sys, os, argparse, shutil, subprocess, tempfile, platform, stat + +# This finds all modules in the source area. For example, it will find ar, +# usdGeom, etc. in the pxr source area, or usdKatana, etc. in the third_party +# source area. +def _getModules(sourceRoot): + modules = [] + topLevel = [] + for p in os.listdir(sourceRoot): + # Ignore any hidden directories + if os.path.basename(p).startswith('.'): + continue + + # add all library dirs, such as usdGeom + path = os.path.join(sourceRoot, p) + if os.path.isdir(path): + topLevel.append(path) + + # add all plugin subdirs, such as usdAbc + path = os.path.join(os.path.join(sourceRoot, p), 'plugin/') + if os.path.isdir(path): + topLevel.append(path) + + for t in topLevel: + for p in os.listdir(t): + if os.path.basename(p).startswith('.'): + continue + # Ignore any irrelevant directories + elif os.path.basename(p).startswith('CMakeFiles'): + continue + elif os.path.basename(p).startswith('testenv'): + continue + path = os.path.join(os.path.join(sourceRoot, t), p) + if os.path.isdir(path): + modules.append(path) + return modules + +def _generateDocs(pxrSourceRoot, thirdSourceRoot, pxrBuildRoot, installLoc, + doxygenBin, dotBin): + docsLoc = os.path.join(installLoc, 'src/modules') + if not os.path.exists(docsLoc): + os.makedirs(docsLoc) + + modules = _getModules(pxrSourceRoot) + modules.extend(_getModules(thirdSourceRoot)) + + for mod in modules: + target = os.path.join(docsLoc, os.path.basename(mod)) + # This ensures that we get a fresh view of the source + # on each build invocation. + if os.path.exists(target): + def _removeReadOnly(func, path, exc): + try: + os.chmod(path, stat.S_IWRITE) + func(path) + except Exception as exc: + print >>sys.stderr, "Failed to remove %s: %s" % (path, str(exc)) + shutil.rmtree(target, onerror=_removeReadOnly) + shutil.copytree(mod, target) + + + + # We need to copy the namespace header separately because + # its a generated file from CMake + pxrMod = os.path.join(docsLoc, 'pxr') + if not os.path.exists(pxrMod): + os.makedirs(pxrMod) + + pxrHeaderSrc = os.path.join(pxrBuildRoot, 'include', 'pxr', 'pxr.h') + pxrHeaderDest = os.path.join(pxrMod, 'pxr.h') + if os.path.exists(pxrHeaderDest): + os.remove(pxrHeaderDest) + + shutil.copyfile(pxrHeaderSrc, pxrHeaderDest) + + doxyConfigSrc = os.path.join(pxrBuildRoot, 'Doxyfile') + doxyConfigDest = os.path.join(docsLoc, 'usd', 'Doxyfile') + if os.path.exists(doxyConfigDest): + os.remove(doxyConfigDest) + shutil.copyfile(doxyConfigSrc, doxyConfigDest) + + os.chdir(installLoc) + cmd = [doxygenBin, doxyConfigDest] + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + output = proc.communicate()[0] + if proc.wait() != 0: + print >>sys.stderr, output.replace('\r\n', '\n') + sys.exit('Error: doxygen failed to complete; ' + 'exit code %d.' % proc.returncode) + +def _checkPath(path, ident, perm): + if not os.path.exists(path): + sys.exit('Error: file path %s (arg=%s) ' + 'does not exist, exiting.' % (path,ident)) + elif not os.access(path, perm): + permString = '-permission' + if perm == os.W_OK: + permString = 'write'+permString + elif perm == os.R_OK: + permString = 'read'+permString + elif perm == os.X_OK: + permString = 'execute'+permString + sys.exit('Error: insufficient permission for path %s, ' + '%s required.' % (path, permString)) + +def _generateInstallLoc(installRoot): + installPath = os.path.join(installRoot, 'docs') + if not os.path.exists(installPath): + os.mkdir(installPath) + return installPath + +if __name__ == "__main__": + p = argparse.ArgumentParser(description="Generate USD documentation.") + p.add_argument("source", help="The path to the pxr source root.") + p.add_argument("third_source", help="The path to the third_party source root.") + p.add_argument("build", help="The path to the build directory root.") + p.add_argument("install", help="The install root for generated docs.") + p.add_argument("doxygen", help="The path to the doxygen executable.") + p.add_argument("dot", help="The path to the dot executable.") + args = p.parse_args() + + # Ensure all paths exist first + _checkPath(args.source, 'source', os.R_OK) + _checkPath(args.third_source, 'third_source', os.R_OK) + _checkPath(args.build, 'build', os.R_OK) + _checkPath(args.install, 'install', os.W_OK) + _checkPath(args.doxygen, 'doxygen', os.X_OK) + _checkPath(args.dot, 'dot', os.X_OK) + + installPath = _generateInstallLoc(args.install) + _generateDocs(args.source, args.third_source, args.build, installPath, + args.doxygen, args.dot) diff --git a/cmake/macros/shebang.py b/cmake/macros/shebang.py new file mode 100644 index 00000000..537ffac5 --- /dev/null +++ b/cmake/macros/shebang.py @@ -0,0 +1,49 @@ +# +# Copyright 2017 Pixar +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# +# +# Usage: +# shebang shebang-str source.py dest.py +# shebang file output.cmd +# +# The former substitutes '/pxrpythonsubst' in with +# and writes the result to . The latter generates a file named +# with the contents '@python "%~dp0"'; this is a +# Windows command script to execute "python " where is in +# the same directory as the command script. + +import sys + +if len(sys.argv) < 3 or len(sys.argv) > 4: + print "Usage: %s {shebang-str source.py dest|file output.cmd}" % sys.argv[0] + sys.exit(1) + +if len(sys.argv) == 3: + with open(sys.argv[2], 'w') as f: + print >>f, '@python "%%~dp0%s" %%*' % (sys.argv[1], ) + +else: + with open(sys.argv[2], 'r') as s: + with open(sys.argv[3], 'w') as d: + for line in s: + d.write(line.replace('/pxrpythonsubst', sys.argv[1])) diff --git a/cmake/macros/testWrapper.py b/cmake/macros/testWrapper.py new file mode 100644 index 00000000..e61e5e3c --- /dev/null +++ b/cmake/macros/testWrapper.py @@ -0,0 +1,309 @@ +# +# Copyright 2016 Pixar +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# +# +# Usage: testWrapper.py +# +# Wrapper for test commands which allows us to control the test environment +# and provide additional conditions for passing. +# +# CTest only cares about the eventual return code of the test command it runs +# with 0 being success and anything else indicating failure. This script allows +# us to wrap a test command and provide additional conditions which must be met +# for that test to pass. +# +# Current features include: +# - specifying non-zero return codes +# - comparing output files against a baseline +# +import argparse +import os +import platform +import shutil +import subprocess +import sys +import tempfile + +def _parseArgs(): + parser = argparse.ArgumentParser(description='USD test wrapper') + parser.add_argument('--stdout-redirect', type=str, + help='File to redirect stdout to') + parser.add_argument('--stderr-redirect', type=str, + help='File to redirect stderr to') + parser.add_argument('--diff-compare', nargs='*', + help=('Compare output file with a file in the baseline-dir of the ' + 'same name')) + parser.add_argument('--files-exist', nargs='*', + help=('Check that a set of files exist.')) + parser.add_argument('--files-dont-exist', nargs='*', + help=('Check that a set of files exist.')) + parser.add_argument('--clean-output-paths', nargs='*', + help=('Path patterns to remove from the output files being diff\'d.')) + parser.add_argument('--post-command', nargs='*', + help=('Command line action to run after running COMMAND.')) + parser.add_argument('--pre-command', nargs='*', + help=('Command line action to run before running COMMAND.')) + parser.add_argument('--pre-command-stdout-redirect', type=str, + help=('File to redirect stdout to when running PRE_COMMAND.')) + parser.add_argument('--pre-command-stderr-redirect', type=str, + help=('File to redirect stderr to when running PRE_COMMAND.')) + parser.add_argument('--post-command-stdout-redirect', type=str, + help=('File to redirect stdout to when running POST_COMMAND.')) + parser.add_argument('--post-command-stderr-redirect', type=str, + help=('File to redirect stderr to when running POST_COMMAND.')) + parser.add_argument('--testenv-dir', type=str, + help='Testenv directory to copy into test run directory') + parser.add_argument('--baseline-dir', + help='Baseline directory to use with --diff-compare') + parser.add_argument('--expected-return-code', type=int, default=0, + help='Expected return code of this test.') + parser.add_argument('--env-var', dest='envVars', default=[], type=str, + action='append', + help=('Variable to set in the test environment, in KEY=VALUE form. ' + 'If "" is in the value, it is replaced with the ' + 'absolute path of the temp directory the tests are run in')) + parser.add_argument('--pre-path', dest='prePaths', default=[], type=str, + action='append', help='Path to prepend to the PATH env var.') + parser.add_argument('--post-path', dest='postPaths', default=[], type=str, + action='append', help='Path to append to the PATH env var.') + parser.add_argument('--verbose', '-v', action='store_true', + help='Verbose output.') + parser.add_argument('cmd', metavar='CMD', type=str, nargs='+', + help='Test command to run') + return parser.parse_args() + +def _resolvePath(baselineDir, fileName): + # Some test envs are contained within a non-specific subdirectory, if that + # exists then use it for the baselines + nonSpecific = os.path.join(baselineDir, 'non-specific') + if os.path.isdir(nonSpecific): + baselineDir = nonSpecific + + return os.path.normpath(os.path.join(baselineDir, fileName)) + +def _stripPath(f, pathPattern): + import re + import platform + + # Paths on Windows may have backslashes but test baselines never do. + # On Windows, we rewrite the pattern so slash and backslash both match + # either a slash or backslash. In addition, we match the remainder of + # the path and rewrite it to have forward slashes so it matches the + # baseline. + repl = '' + if platform.system() == 'Windows': + def _windowsReplacement(m): + return m.group(1).replace('\\', '/') + pathPattern = pathPattern.replace('\\', '/') + pathPattern = pathPattern.replace('/', '[/\\\\]') + pathPattern = pathPattern + '(\S*)' + repl = _windowsReplacement + + # Read entire file and perform substitution. + with open(f, 'r+') as inputFile: + data = inputFile.read() + data = re.sub(pathPattern, repl, data) + inputFile.seek(0) + inputFile.write(data) + inputFile.truncate() + +def _cleanOutput(pathPattern, fileName, verbose): + if verbose: + print "stripping path pattern {0} from file {1}".format(pathPattern, + fileName) + _stripPath(fileName, pathPattern) + return True + +def _diff(fileName, baselineDir, verbose): + # Use the diff program or equivalent, rather than filecmp or similar + # because it's possible we might want to specify other diff programs + # in the future. + import platform + if platform.system() == 'Windows': + diff = 'fc.exe' + else: + diff = '/usr/bin/diff' + cmd = [diff, _resolvePath(baselineDir, fileName), fileName] + if verbose: + print "diffing with {0}".format(cmd) + + # This will print any diffs to stdout which is a nice side-effect + return subprocess.call(cmd) == 0 + +def _copyTree(src, dest): + ''' Copies the contents of src into dest.''' + if not os.path.exists(dest): + os.makedirs(dest) + for item in os.listdir(src): + s = os.path.join(src, item) + d = os.path.join(dest, item) + if os.path.isdir(s): + shutil.copytree(s, d) + else: + shutil.copy2(s, d) + + +# Windows command prompt will not split arguments so we do it instead. +# args.cmd is a list of strings to split so the inner list comprehension +# makes a list of argument lists. The outer double comprehension flattens +# that into a single list. +def _splitCmd(cmd): + return [arg for tmp in [arg.split() for arg in cmd] for arg in tmp] + +# subprocess.call returns -N if the process raised signal N. Convert this +# to the standard positive error code matching that signal. e.g. if the +# process encounters an SIGABRT signal it will return -6, but we really +# want the exit code 134 as that is what the script would return when run +# from the shell. This is well defined to be 128 + (signal number). +def _convertRetCode(retcode): + if retcode < 0: + return 128 + abs(retcode) + return retcode + +def _getRedirects(out_redir, err_redir): + return (open(out_redir, 'w') if out_redir else None, + open(err_redir, 'w') if err_redir else None) + +def _runCommand(raw_command, stdout_redir, stderr_redir, env, + expected_return_code): + cmd = _splitCmd(raw_command) + fout, ferr = _getRedirects(stdout_redir, stderr_redir) + try: + print "cmd: %s" % (cmd, ) + retcode = _convertRetCode(subprocess.call(cmd, shell=False, env=env, + stdout=(fout or sys.stdout), + stderr=(ferr or sys.stderr))) + finally: + if fout: + fout.close() + if ferr: + ferr.close() + + # The return code didn't match our expected error code, even if it + # returned 0 -- this is a failure. + if retcode != expected_return_code: + if args.verbose: + sys.stderr.write( + "Error: return code {0} doesn't match " + "expected {1} (EXPECTED_RETURN_CODE).".format(retcode, + expected_return_code)) + sys.exit(1) + +if __name__ == '__main__': + args = _parseArgs() + + if args.diff_compare and not args.baseline_dir: + sys.stderr.write("Error: --baseline-dir must be specified with " + "--diff-compare.") + sys.exit(1) + + if args.clean_output_paths and not args.diff_compare: + sys.stderr.write("Error: --diff-compare must be specified with " + "--clean-output-paths.") + sys.exit(1) + + testDir = tempfile.mkdtemp() + os.chdir(testDir) + if args.verbose: + print "chdir: {0}".format(testDir) + + # Copy the contents of the testenv directory into our test run directory so + # the test has it's own copy that it can reference and possibly modify. + if args.testenv_dir and os.path.isdir(args.testenv_dir): + if args.verbose: + print "copying testenv dir: {0}".format(args.testenv_dir) + try: + _copyTree(args.testenv_dir, os.getcwd()) + except Exception as e: + sys.stderr.write("Error: copying testenv directory: {0}".format(e)) + sys.exit(1) + + # Add any envvars specified with --env-var options into the environment + env = os.environ.copy() + for varStr in args.envVars: + try: + k, v = varStr.split('=', 1) + if k == 'PATH': + sys.stderr.write("Error: use --pre-path or --post-path to edit PATH.") + sys.exit(1) + v = v.replace('', testDir) + env[k] = v + except IndexError: + sys.stderr.write("Error: envvar '{0}' not of the form " + "key=value".format(varStr)) + sys.exit(1) + + # Modify the PATH env var. The delimiter depends on the platform. + pathDelim = ';' if platform.system() == 'Windows' else ':' + paths = env.get('PATH', '').split(pathDelim) + paths = args.prePaths + paths + args.postPaths + env['PATH'] = pathDelim.join(paths) + + # Avoid the just-in-time debugger where possible when running tests. + env['ARCH_AVOID_JIT'] = '1' + + if args.pre_command: + _runCommand(args.pre_command, args.pre_command_stdout_redirect, + args.pre_command_stderr_redirect, env, 0) + + _runCommand(args.cmd, args.stdout_redirect, args.stderr_redirect, + env, args.expected_return_code) + + if args.post_command: + _runCommand(args.post_command, args.post_command_stdout_redirect, + args.post_command_stderr_redirect, env, 0) + + if args.clean_output_paths: + for path in args.clean_output_paths: + for diff in args.diff_compare: + _cleanOutput(path, diff, args.verbose) + + if args.files_exist: + for f in args.files_exist: + if args.verbose: + print 'checking if {0} exists.'.format(f) + if not os.path.exists(f): + sys.stderr.write('Error: {0} does not exist ' + '(FILES_EXIST).'.format(f)) + sys.exit(1) + + if args.files_dont_exist: + for f in args.files_dont_exist: + if args.verbose: + print 'checking if {0} does not exist.'.format(f) + if os.path.exists(f): + sys.stderr.write('Error: {0} does exist ' + '(FILES_DONT_EXIST).'.format(f)) + sys.exit(1) + + # If desired, diff the provided file(s) (must be generated by the test somehow) + # with a file of the same name in the baseline directory + if args.diff_compare: + for diff in args.diff_compare: + if not _diff(diff, args.baseline_dir, args.verbose): + if args.verbose: + sys.stderr.write('Error: diff for {0} failed ' + '(DIFF_COMPARE).'.format(diff)) + sys.exit(1) + + sys.exit(0) diff --git a/cmake/modules/FindCycles.cmake b/cmake/modules/FindCycles.cmake new file mode 100644 index 00000000..a55ef95d --- /dev/null +++ b/cmake/modules/FindCycles.cmake @@ -0,0 +1,95 @@ +# Copyright 2020 Tangent Animation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +if(UNIX) + set(CYCLES_LIB_PREFIX lib) +endif() + +# Cycles Includes + +find_path(CYCLES_INCLUDE_DIRS "render/graph.h" + HINTS + ${CYCLES_ROOT}/include + $ENV{CYCLES_ROOT}/include + ${CYCLES_ROOT}/src + $ENV{CYCLES_ROOT}/src + DOC "Cycles Include directory") + +# Cycles Libraries + +find_path(CYCLES_LIBRARY_DIR + NAMES + ${CYCLES_LIB_PREFIX}cycles_bvh${CMAKE_STATIC_LIBRARY_SUFFIX} + ${CYCLES_LIB_PREFIX}cycles_device${CMAKE_STATIC_LIBRARY_SUFFIX} + ${CYCLES_LIB_PREFIX}cycles_graph${CMAKE_STATIC_LIBRARY_SUFFIX} + ${CYCLES_LIB_PREFIX}cycles_kernel${CMAKE_STATIC_LIBRARY_SUFFIX} + ${CYCLES_LIB_PREFIX}cycles_render${CMAKE_STATIC_LIBRARY_SUFFIX} + ${CYCLES_LIB_PREFIX}cycles_subd${CMAKE_STATIC_LIBRARY_SUFFIX} + ${CYCLES_LIB_PREFIX}cycles_util${CMAKE_STATIC_LIBRARY_SUFFIX} + ${CYCLES_LIB_PREFIX}extern_clew${CMAKE_STATIC_LIBRARY_SUFFIX} + ${CYCLES_LIB_PREFIX}extern_cuew${CMAKE_STATIC_LIBRARY_SUFFIX} + ${CYCLES_LIB_PREFIX}extern_numaapi${CMAKE_STATIC_LIBRARY_SUFFIX} + + HINTS + ${CYCLES_ROOT}/lib + $ENV{CYCLES_ROOT}/lib + + DOC "Cycles Libraries directory") + +set(CYCLES_LIBS cycles_bvh;cycles_device;cycles_graph;cycles_kernel;cycles_render;cycles_subd;cycles_util;extern_clew;extern_cuew;extern_numaapi) + +foreach (lib ${CYCLES_LIBS}) + find_library(${lib}_LIBRARY + NAMES ${CYCLES_LIB_PREFIX}${lib}${CMAKE_STATIC_LIBRARY_SUFFIX} + HINTS ${CYCLES_LIBRARY_DIR}) + if (${lib}_LIBRARY) + add_library(${lib} INTERFACE IMPORTED) + set_target_properties(${lib} + PROPERTIES + INTERFACE_LINK_LIBRARIES ${${lib}_LIBRARY} + ) + list(APPEND CYCLES_LIBRARIES ${${lib}_LIBRARY}) + endif () +endforeach () + +# Version Parsing + +if(CYCLES_INCLUDE_DIRS AND EXISTS "${CYCLES_INCLUDE_DIRS}/pxr/pxr.h") + foreach(_cycles_comp MAJOR MINOR PATCH) + file(STRINGS + "${CYCLES_INCLUDE_DIRS}/util/util_version.h" + _cycles_tmp + REGEX "#define CYCLES_VERSION_${_cycles_comp} .*$") + string(REGEX MATCHALL "[0-9]+" CYCLES_VERSION_${_cycles_comp} ${_cycles_tmp}) + endforeach() + set(CYCLES_VERSION ${CYCLES_MAJOR_VERSION}.${CYCLES_MINOR_VERSION}.${CYCLES_PATCH_VERSION}) +endif() + +find_path(ATOMIC_INCLUDE_DIRS "atomic_ops.h" + HINTS + ${CYCLES_ROOT}/third_party/atomic + $ENV{CYCLES_ROOT}/third_party/atomic + DOC "Atomic Include directory") + +list(APPEND CYCLES_INCLUDE_DIRS ${ATOMIC_INCLUDE_DIRS}) + +include(FindPackageHandleStandardArgs) + +find_package_handle_standard_args(Cycles + REQUIRED_VARS + CYCLES_INCLUDE_DIRS + CYCLES_LIBRARY_DIR + CYCLES_LIBRARIES + VERSION_VAR + CYCLES_VERSION) \ No newline at end of file diff --git a/cmake/modules/FindDraco.cmake b/cmake/modules/FindDraco.cmake new file mode 100644 index 00000000..6d25cb3c --- /dev/null +++ b/cmake/modules/FindDraco.cmake @@ -0,0 +1,30 @@ +# +# Copyright 2019 Pixar +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# +# Find Draco Compression Library using DRACO_ROOT as a hint location and +# provides the results by defining the variables DRACO_INCLUDES and +# DRACO_LIBRARY. +# + +find_library(DRACO_LIBRARY NAMES draco PATHS "${DRACO_ROOT}/lib") +find_path(DRACO_INCLUDES draco/compression/decode.h PATHS "${DRACO_ROOT}/include") diff --git a/cmake/modules/FindGLEW.cmake b/cmake/modules/FindGLEW.cmake new file mode 100644 index 00000000..b27be071 --- /dev/null +++ b/cmake/modules/FindGLEW.cmake @@ -0,0 +1,132 @@ +# +# Copyright 2016 Pixar +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# +# Try to find GLEW library and include path. +# Once done this will define +# +# GLEW_FOUND +# GLEW_INCLUDE_DIR +# GLEW_LIBRARY +# + +include(FindPackageHandleStandardArgs) + +if (WIN32) + find_path(GLEW_INCLUDE_DIR + NAMES + GL/glew.h + HINTS + "${GLEW_LOCATION}/include" + "$ENV{GLEW_LOCATION}/include" + PATHS + "$ENV{PROGRAMFILES}/GLEW/include" + "${PROJECT_SOURCE_DIR}/extern/glew/include" + DOC "The directory where GL/glew.h resides" ) + + if ("${CMAKE_GENERATOR}" MATCHES "[Ww]in64") + set(ARCH x64) + else() + set(ARCH x86) + endif() + + find_library(GLEW_LIBRARY + NAMES + glew GLEW glew32 glew32s + HINTS + "${GLEW_LOCATION}/lib" + "$ENV{GLEW_LOCATION}/lib" + PATHS + "$ENV{PROGRAMFILES}/GLEW/lib" + "${PROJECT_SOURCE_DIR}/extern/glew/bin" + "${PROJECT_SOURCE_DIR}/extern/glew/lib" + PATH_SUFFIXES + Release/${ARCH} + DOC "The GLEW library") +endif () + +if (${CMAKE_HOST_UNIX}) + find_path( GLEW_INCLUDE_DIR + NAMES + GL/glew.h + HINTS + "${GLEW_LOCATION}/include" + "$ENV{GLEW_LOCATION}/include" + PATHS + /usr/include + /usr/local/include + /sw/include + /opt/local/include + NO_SYSTEM_ENVIRONMENT_PATH + NO_CMAKE_SYSTEM_PATH + DOC "The directory where GL/glew.h resides" + ) + find_library( GLEW_LIBRARY + NAMES + GLEW glew + HINTS + "${GLEW_LOCATION}/lib" + "${GLEW_LOCATION}/lib64" + "$ENV{GLEW_LOCATION}/lib" + "$ENV{GLEW_LOCATION}/lib64" + PATHS + "${GLEW_LOCATION}/lib" + /usr/lib64 + /usr/lib + /usr/lib/${CMAKE_LIBRARY_ARCHITECTURE} + /usr/local/lib64 + /usr/local/lib + /sw/lib + /opt/local/lib + NO_SYSTEM_ENVIRONMENT_PATH + NO_CMAKE_SYSTEM_PATH + DOC "The GLEW library") +endif () + + +if (GLEW_INCLUDE_DIR AND EXISTS "${GLEW_INCLUDE_DIR}/GL/glew.h") + + file(STRINGS "${GLEW_INCLUDE_DIR}/GL/glew.h" GLEW_4_2 REGEX "^#define GL_VERSION_4_2.*$") + if (GLEW_4_2) + set(OPENGL_4_2_FOUND TRUE) + else () + message(WARNING + "glew-1.7.0 or newer needed for supporting OpenGL 4.2 dependent features" + ) + endif () + + file(STRINGS "${GLEW_INCLUDE_DIR}/GL/glew.h" GLEW_4_3 REGEX "^#define GL_VERSION_4_3.*$") + if (GLEW_4_3) + SET(OPENGL_4_3_FOUND TRUE) + else () + message(WARNING + "glew-1.9.0 or newer needed for supporting OpenGL 4.3 dependent features" + ) + endif () + +endif () + +find_package_handle_standard_args(GLEW + REQUIRED_VARS + GLEW_INCLUDE_DIR + GLEW_LIBRARY +) diff --git a/cmake/modules/FindHoudini.cmake b/cmake/modules/FindHoudini.cmake new file mode 100644 index 00000000..6ff07b17 --- /dev/null +++ b/cmake/modules/FindHoudini.cmake @@ -0,0 +1,194 @@ +# +# Copyright 2017 Pixar +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# +# - Find Houdini Development Kit +# +# Finds an installed Houdini Development Kit +# +# Variables that will be defined: +# HOUDINI_FOUND Defined if HDK installation has been detected +# HOUDINI_BASE_DIR Path to the root of the Houdini installation +# HOUDINI_INCLUDE_DIRS Path to the HDK include directories +# HOUDINI_LIB_DIRS Path to the HDK libraray directories +# HOUDINI_VERSION Full Houdini version, 16.0.596 for example +# HOUDINI_MAJOR_VERSION +# HOUDINI_MINOR_VERSION +# HOUDINI_BUILD_VERSION + +# +# In: +# HOUDINI_ROOT +# +# Out: +# HOUDINI_FOUND +# HOUDINI_VERSION +# HOUDINI_BUILD_VERSION +# HOUDINI_INCLUDE_DIRS +# HOUDINI_LIBRARY_DIRS +# HOUDINI_LIBRARIES +# HOUDINI_APPS1_LIBRARY +# HOUDINI_APPS2_LIBRARY +# HOUDINI_APPS3_LIBRARY +# HOUDINI_DEVICE_LIBRARY +# HOUDINI_GEO_LIBRARY +# HOUDINI_OP1_LIBRARY +# HOUDINI_OP2_LIBRARY +# HOUDINI_OP3_LIBRARY +# HOUDINI_OPZ_LIBRARY +# HOUDINI_PRM_LIBRARY +# HOUDINI_RAY_LIBRARY +# HOUDINI_SIM_LIBRARY +# HOUDINI_UI_LIBRARY +# HOUDINI_UT_LIBRARY + +find_path(HOUDINI_BASE_DIR + NAMES + houdini + HINTS + "${HOUDINI_ROOT}" + "$ENV{HOUDINI_ROOT}" + ) + +find_path(HOUDINI_INCLUDE_DIRS + UT/UT_Version.h + HINTS + "${HOUDINI_ROOT}" + "$ENV{HOUDINI_ROOT}" + "${HOUDINI_BASE_DIR}" + PATH_SUFFIXES + toolkit/include/ + DOC + "Houdini Development Kit Header Path" +) + +if (UNIX) + set(HOUDINI_LIB_NAME "libHoudiniGEO.so") + set(HOUDINI_LIB_PATH_SUFFIX "dsolib/") +elseif(WIN32) + set(HOUDINI_LIB_NAME "libGEO.lib") + set(HOUDINI_LIB_PATH_SUFFIX "custom/houdini/dsolib/") +endif() + +find_path(HOUDINI_LIB_DIRS + ${HOUDINI_LIB_NAME} + HINTS + "${HOUDINI_ROOT}" + "$ENV{HOUDINI_ROOT}" + "${HOUDINI_BASE_DIR}" + PATH_SUFFIXES + ${HOUDINI_LIB_PATH_SUFFIX} + DOC + "Houdini Development Kit Library Path" +) + +foreach(HOUDINI_LIB + APPS1 + APPS2 + APPS3 + DEVICE + GEO + OP1 + OP2 + OP3 + OPZ + PRM + RAY + SIM + UI + UT) + find_library(HOUDINI_${HOUDINI_LIB}_LIBRARY + Houdini${HOUDINI_LIB} + HINTS + "${HOUDINI_LIB_DIRS}" + DOC + "Houdini's ${HOUDINI_LIB} library path" + NO_CMAKE_SYSTEM_PATH + ) + + if (HOUDINI_${HOUDINI_LIB}_LIBRARY) + list(APPEND HOUDINI_LIBRARIES ${HOUDINI_${HOUDINI_LIB}_LIBRARY}) + endif () +endforeach() + +foreach(HOUDINI_LIB_DEP + OpenImageIO_sidefx + OpenImageIO_Util_sidefx + hboost_python-mt + hboost_filesystem-mt + hboost_system-mt + ) + find_library(HOUDINI_${HOUDINI_LIB_DEP}_LIBRARY + ${HOUDINI_LIB_DEP} + HINTS + "${HOUDINI_LIB_DIRS}" + DOC + "Houdini's ${HOUDINI_LIB_DEP} library path" + NO_CMAKE_SYSTEM_PATH + ) + + if (HOUDINI_${HOUDINI_LIB_DEP}_LIBRARY) + list(APPEND HOUDINI_LIBRARIES_DEPS ${HOUDINI_${HOUDINI_LIB_DEP}_LIBRARY}) + endif () +endforeach() + +if(HOUDINI_INCLUDE_DIRS AND EXISTS "${HOUDINI_INCLUDE_DIRS}/SYS/SYS_Version.h") + foreach(comp FULL MAJOR MINOR BUILD) + file(STRINGS + ${HOUDINI_INCLUDE_DIRS}/SYS/SYS_Version.h + TMP + REGEX "#define SYS_VERSION_${comp} .*$") + string(REGEX MATCHALL "[0-9.]+" HOUDINI_${comp}_VERSION ${TMP}) + endforeach() +endif() + +set(HOUDINI_VERSION ${HOUDINI_FULL_VERSION}) + +if(HOUDINI_INCLUDE_DIRS AND EXISTS "${HOUDINI_INCLUDE_DIRS}/pxr/pxr.h") + foreach(_usd_comp MAJOR MINOR PATCH) + file(STRINGS + "${HOUDINI_INCLUDE_DIRS}/pxr/pxr.h" + _usd_tmp + REGEX "#define PXR_${_usd_comp}_VERSION .*$") + string(REGEX MATCHALL "[0-9]+" USD_${_usd_comp}_VERSION ${_usd_tmp}) + endforeach() + set(USD_VERSION ${USD_MAJOR_VERSION}.${USD_MINOR_VERSION}.${USD_PATCH_VERSION}) + set(USD_MAJOR_VERSION ${USD_MAJOR_VERSION}) + set(USD_MINOR_VERSION ${USD_MINOR_VERSION}) + set(USD_PATCH_VERSION ${USD_PATCH_VERSION}) +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Houdini + REQUIRED_VARS + HOUDINI_BASE_DIR + HOUDINI_INCLUDE_DIRS + HOUDINI_LIB_DIRS + + USD_MINOR_VERSION + USD_PATCH_VERSION + VERSION_VAR + HOUDINI_VERSION +) + + + diff --git a/cmake/modules/FindMaterialX.cmake b/cmake/modules/FindMaterialX.cmake new file mode 100644 index 00000000..d10d0a33 --- /dev/null +++ b/cmake/modules/FindMaterialX.cmake @@ -0,0 +1,125 @@ +# +# Copyright 2018 Pixar +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# +# - Find MaterialX Development Kit +# +# Finds an installed MaterialX Development Kit +# +# Variables that will be defined: +# MATERIALX_FOUND Defined if MaterialX has been detected +# MATERIALX_BASE_DIR Path to the root of the MaterialX installation +# MATERIALX_INCLUDE_DIRS Path to the MaterialX include directories +# MATERIALX_LIB_DIRS Path to the MaterialX libraray directories +# MATERIALX_STDLIB_DIR Path to the MaterialX standard library directory +# MATERIALX_LIBRARIES List of MaterialX libraries + +# +# In: +# MATERIALX_ROOT Path to the root of the MaterialX installation +# MATERIALX_DATA_ROOT Path where MaterialX data files (libraries and +# resources) are installed, if different from +# MATERIALX_ROOT +# +# Out: +# MATERIALX_FOUND +# MATERIALX_INCLUDE_DIRS +# MATERIALX_LIB_DIRS +# MATERIALX_LIBRARIES + +find_path(MATERIALX_BASE_DIR + NAMES + include/MaterialXCore/Library.h + HINTS + "${MATERIALX_ROOT}" + "$ENV{MATERIALX_ROOT}" + ) + +find_path(MATERIALX_INCLUDE_DIRS + MaterialXCore/Library.h + HINTS + "${MATERIALX_ROOT}" + "$ENV{MATERIALX_ROOT}" + "${MATERIALX_BASE_DIR}" + PATH_SUFFIXES + include + DOC + "MaterialX Header Path" +) + +if (WIN32) + set(MATERIALX_CORE_LIB_NAME MaterialXCore.lib) +else() + set(MATERIALX_CORE_LIB_NAME libMaterialXCore.a) +endif() + +find_path(MATERIALX_LIB_DIRS + "${MATERIALX_CORE_LIB_NAME}" + HINTS + "${MATERIALX_ROOT}" + "$ENV{MATERIALX_ROOT}" + "${MATERIALX_BASE_DIR}" + PATH_SUFFIXES + lib + DOC + "MaterialX Library Path" +) + +find_path(MATERIALX_STDLIB_DIR + stdlib_defs.mtlx + HINTS + "${MATERIALX_ROOT}" + "$ENV{MATERIALX_ROOT}" + "${MATERIALX_BASE_DIR}" + "${MATERIALX_DATA_ROOT}" + PATH_SUFFIXES + documents/Libraries + libraries/stdlib + DOC + "MaterialX Standard Libraries Path" +) + +foreach(MATERIALX_LIB + Core + Format) + find_library(MATERIALX_${MATERIALX_LIB}_LIBRARY + MaterialX${MATERIALX_LIB} + HINTS + "${MATERIALX_LIB_DIRS}" + DOC + "MaterialX's ${MATERIALX_LIB} library path" + NO_CMAKE_SYSTEM_PATH + ) + + if (MATERIALX_${MATERIALX_LIB}_LIBRARY) + list(APPEND MATERIALX_LIBRARIES ${MATERIALX_${MATERIALX_LIB}_LIBRARY}) + endif () +endforeach() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(MaterialX + REQUIRED_VARS + MATERIALX_BASE_DIR + MATERIALX_INCLUDE_DIRS + MATERIALX_LIB_DIRS + MATERIALX_STDLIB_DIR +) diff --git a/cmake/modules/FindOSL.cmake b/cmake/modules/FindOSL.cmake new file mode 100644 index 00000000..ebeea486 --- /dev/null +++ b/cmake/modules/FindOSL.cmake @@ -0,0 +1,111 @@ +# +# Copyright 2018 Pixar +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +# Find OSL header. +find_path(OSL_INCLUDE_DIR + NAMES + OSL/oslversion.h + PATH_SUFFIXES + include/ + HINTS + "${OSL_LOCATION}" + "$ENV{OSL_LOCATION}" + DOC + "OSL headers path" + ) + +# Parse OSL version. +if(OSL_INCLUDE_DIR) + set(osl_config_file "${OSL_INCLUDE_DIR}/OSL/oslversion.h") + if(EXISTS ${osl_config_file}) + file(STRINGS + ${osl_config_file} + TMP + REGEX "#define OSL_LIBRARY_VERSION_MAJOR.*$") + string(REGEX MATCHALL "[0-9]" OSL_MAJOR_VERSION ${TMP}) + file(STRINGS + ${osl_config_file} + TMP + REGEX "#define OSL_LIBRARY_VERSION_MINOR.*$") + string(REGEX MATCHALL "[0-9]" OSL_MINOR_VERSION ${TMP}) + endif() +endif() + +# Find libraries and binaries +find_library (OSL_EXEC_LIBRARY + NAMES + oslexec + PATH_SUFFIXES + lib/ + HINTS + "${OSL_LOCATION}" + "$ENV{OSL_LOCATION}" + ) +find_library (OSL_COMP_LIBRARY + NAMES + oslcomp + PATH_SUFFIXES + lib/ + HINTS + "${OSL_LOCATION}" + "$ENV{OSL_LOCATION}" + ) +find_library (OSL_QUERY_LIBRARY + NAMES + oslquery + PATH_SUFFIXES + lib/ + HINTS + "${OSL_LOCATION}" + "$ENV{OSL_LOCATION}" + ) +find_program (OSL_OSLC_EXECUTABLE + NAMES + oslc + PATH_SUFFIXES + bin/ + HINTS + "${OSL_LOCATION}" + "$ENV{OSL_LOCATION}" + ) +find_program (OSL_OSLINFO_EXECUTABLE + NAMES + oslinfo + PATH_SUFFIXES + bin/ + HINTS + "${OSL_LOCATION}" + "$ENV{OSL_LOCATION}" + ) + +include (FindPackageHandleStandardArgs) +find_package_handle_standard_args (OSL + DEFAULT_MSG + OSL_INCLUDE_DIR + OSL_EXEC_LIBRARY + OSL_COMP_LIBRARY + OSL_QUERY_LIBRARY + OSL_OSLC_EXECUTABLE + OSL_OSLINFO_EXECUTABLE + ) diff --git a/cmake/modules/FindOpenColorIO.cmake b/cmake/modules/FindOpenColorIO.cmake new file mode 100644 index 00000000..26b8229a --- /dev/null +++ b/cmake/modules/FindOpenColorIO.cmake @@ -0,0 +1,110 @@ +# +# Copyright 2019 Pixar +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +if(UNIX) + find_path(OCIO_BASE_DIR + include/OpenColorIO/OpenColorABI.h + HINTS + "${OCIO_LOCATION}" + "$ENV{OCIO_LOCATION}" + "/opt/ocio" + ) + find_path(OCIO_LIBRARY_DIR + libOpenColorIO.so + HINTS + "${OCIO_LOCATION}" + "$ENV{OCIO_LOCATION}" + "${OCIO_BASE_DIR}" + PATH_SUFFIXES + lib/ + DOC + "OpenColorIO library path" + ) +elseif(WIN32) + find_path(OCIO_BASE_DIR + include/OpenColorIO/OpenColorABI.h + HINTS + "${OCIO_LOCATION}" + "$ENV{OCIO_LOCATION}" + ) + find_path(OCIO_LIBRARY_DIR + OpenColorIO.lib + HINTS + "${OCIO_LOCATION}" + "$ENV{OCIO_LOCATION}" + "${OCIO_BASE_DIR}" + PATH_SUFFIXES + lib/ + DOC + "OpenColorIO library path" + ) +endif() + +find_path(OCIO_INCLUDE_DIR + OpenColorIO/OpenColorABI.h + HINTS + "${OCIO_LOCATION}" + "$ENV{OCIO_LOCATION}" + "${OCIO_BASE_DIR}" + PATH_SUFFIXES + include/ + DOC + "OpenColorIO headers path" +) + +list(APPEND OCIO_INCLUDE_DIRS ${OCIO_INCLUDE_DIR}) + +find_library(OCIO_LIBRARY + OpenColorIO + HINTS + "${OCIO_LOCATION}" + "$ENV{OCIO_LOCATION}" + "${OCIO_BASE_DIR}" + PATH_SUFFIXES + lib/ + DOC + "OCIO's ${OCIO_LIB} library path" +) + +list(APPEND OCIO_LIBRARIES ${OCIO_LIBRARY}) + +if(OCIO_INCLUDE_DIRS AND EXISTS "${OCIO_INCLUDE_DIR}/OpenColorIO/OpenColorABI.h") + file(STRINGS ${OCIO_INCLUDE_DIR}/OpenColorIO/OpenColorABI.h + fullVersion + REGEX + "#define OCIO_VERSION .*$") + string(REGEX MATCH "[0-9]+.[0-9]+.[0-9]+" OCIO_VERSION ${fullVersion}) +endif() + +# handle the QUIETLY and REQUIRED arguments and set OCIO_FOUND to TRUE if +# all listed variables are TRUE +include(FindPackageHandleStandardArgs) + +find_package_handle_standard_args(OpenColorIO + REQUIRED_VARS + OCIO_LIBRARIES + OCIO_INCLUDE_DIRS + VERSION_VAR + OCIO_VERSION +) diff --git a/cmake/modules/FindOpenEXR.cmake b/cmake/modules/FindOpenEXR.cmake new file mode 100644 index 00000000..dbb2750c --- /dev/null +++ b/cmake/modules/FindOpenEXR.cmake @@ -0,0 +1,100 @@ +# +# Copyright 2016 Pixar +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +find_path(OPENEXR_INCLUDE_DIR + OpenEXR/half.h +HINTS + "${OPENEXR_LOCATION}" + "$ENV{OPENEXR_LOCATION}" +PATH_SUFFIXES + include/ +DOC + "OpenEXR headers path" +) + +if(OPENEXR_INCLUDE_DIR) + set(openexr_config_file "${OPENEXR_INCLUDE_DIR}/OpenEXR/OpenEXRConfig.h") + if(EXISTS ${openexr_config_file}) + file(STRINGS + ${openexr_config_file} + TMP + REGEX "#define OPENEXR_VERSION_STRING.*$") + string(REGEX MATCHALL "[0-9.]+" OPENEXR_VERSION ${TMP}) + + file(STRINGS + ${openexr_config_file} + TMP + REGEX "#define OPENEXR_VERSION_MAJOR.*$") + string(REGEX MATCHALL "[0-9]" OPENEXR_MAJOR_VERSION ${TMP}) + + file(STRINGS + ${openexr_config_file} + TMP + REGEX "#define OPENEXR_VERSION_MINOR.*$") + string(REGEX MATCHALL "[0-9]" OPENEXR_MINOR_VERSION ${TMP}) + endif() +endif() + +foreach(OPENEXR_LIB + Half + Iex + Imath + IlmImf + IlmThread + ) + + # OpenEXR libraries may be suffixed with the version number, so we search + # using both versioned and unversioned names. + find_library(OPENEXR_${OPENEXR_LIB}_LIBRARY + NAMES + ${OPENEXR_LIB}-${OPENEXR_MAJOR_VERSION}_${OPENEXR_MINOR_VERSION} + ${OPENEXR_LIB} + ${OPENEXR_LIB}_s + HINTS + "${OPENEXR_LOCATION}" + "$ENV{OPENEXR_LOCATION}" + PATH_SUFFIXES + lib/ + DOC + "OPENEXR's ${OPENEXR_LIB} library path" + ) + + if(OPENEXR_${OPENEXR_LIB}_LIBRARY) + list(APPEND OPENEXR_LIBRARIES ${OPENEXR_${OPENEXR_LIB}_LIBRARY}) + endif() +endforeach(OPENEXR_LIB) + +# So #include works +list(APPEND OPENEXR_INCLUDE_DIRS ${OPENEXR_INCLUDE_DIR}) +list(APPEND OPENEXR_INCLUDE_DIRS ${OPENEXR_INCLUDE_DIR}/OpenEXR) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(OpenEXR + REQUIRED_VARS + OPENEXR_INCLUDE_DIRS + OPENEXR_LIBRARIES + VERSION_VAR + OPENEXR_VERSION +) + diff --git a/cmake/modules/FindOpenImageIO.cmake b/cmake/modules/FindOpenImageIO.cmake new file mode 100644 index 00000000..9df47ff2 --- /dev/null +++ b/cmake/modules/FindOpenImageIO.cmake @@ -0,0 +1,154 @@ +# +# Copyright 2016 Pixar +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +if(UNIX) + find_path(OIIO_BASE_DIR + include/OpenImageIO/oiioversion.h + HINTS + "${OIIO_LOCATION}" + "$ENV{OIIO_LOCATION}" + "/opt/oiio" + ) + find_path(OIIO_LIBRARY_DIR + libOpenImageIO.so + HINTS + "${OIIO_LOCATION}" + "$ENV{OIIO_LOCATION}" + "${OIIO_BASE_DIR}" + PATH_SUFFIXES + lib/ + DOC + "OpenImageIO library path" + ) +elseif(WIN32) + find_path(OIIO_BASE_DIR + include/OpenImageIO/oiioversion.h + HINTS + "${OIIO_LOCATION}" + "$ENV{OIIO_LOCATION}" + ) + find_path(OIIO_LIBRARY_DIR + OpenImageIO.lib + HINTS + "${OIIO_LOCATION}" + "$ENV{OIIO_LOCATION}" + "${OIIO_BASE_DIR}" + PATH_SUFFIXES + lib/ + DOC + "OpenImageIO library path" + ) +endif() + +find_path(OIIO_INCLUDE_DIR + OpenImageIO/oiioversion.h + HINTS + "${OIIO_LOCATION}" + "$ENV{OIIO_LOCATION}" + "${OIIO_BASE_DIR}" + PATH_SUFFIXES + include/ + DOC + "OpenImageIO headers path" +) + +list(APPEND OIIO_INCLUDE_DIRS ${OIIO_INCLUDE_DIR}) + +foreach(OIIO_LIB + OpenImageIO + OpenImageIO_Util + ) + + find_library(OIIO_${OIIO_LIB}_LIBRARY + ${OIIO_LIB} + HINTS + "${OIIO_LOCATION}" + "$ENV{OIIO_LOCATION}" + "${OIIO_BASE_DIR}" + PATH_SUFFIXES + lib/ + DOC + "OIIO's ${OIIO_LIB} library path" + ) + + if(OIIO_${OIIO_LIB}_LIBRARY) + list(APPEND OIIO_LIBRARIES ${OIIO_${OIIO_LIB}_LIBRARY}) + endif() +endforeach(OIIO_LIB) + +foreach(OIIO_BIN + iconvert + idiff + igrep + iinfo + iv + maketx + oiiotool) + + find_program(OIIO_${OIIO_BIN}_BINARY + ${OIIO_BIN} + HINTS + "${OIIO_LOCATION}" + "$ENV{OIIO_LOCATION}" + "${OIIO_BASE_DIR}" + PATH_SUFFIXES + bin/ + DOC + "OIIO's ${OIIO_BIN} binary" + ) + if(OIIO_${OIIO_BIN}_BINARY) + list(APPEND OIIO_BINARIES ${OIIO_${OIIO_BIN}_BINARY}) + endif() +endforeach(OIIO_BIN) + +if(OIIO_INCLUDE_DIRS AND EXISTS "${OIIO_INCLUDE_DIR}/OpenImageIO/oiioversion.h") + file(STRINGS ${OIIO_INCLUDE_DIR}/OpenImageIO/oiioversion.h + MAJOR + REGEX + "#define OIIO_VERSION_MAJOR.*$") + file(STRINGS ${OIIO_INCLUDE_DIR}/OpenImageIO/oiioversion.h + MINOR + REGEX + "#define OIIO_VERSION_MINOR.*$") + file(STRINGS ${OIIO_INCLUDE_DIR}/OpenImageIO/oiioversion.h + PATCH + REGEX + "#define OIIO_VERSION_PATCH.*$") + string(REGEX MATCHALL "[0-9]+" MAJOR ${MAJOR}) + string(REGEX MATCHALL "[0-9]+" MINOR ${MINOR}) + string(REGEX MATCHALL "[0-9]+" PATCH ${PATCH}) + set(OIIO_VERSION "${MAJOR}.${MINOR}.${PATCH}") +endif() + +# handle the QUIETLY and REQUIRED arguments and set OIIO_FOUND to TRUE if +# all listed variables are TRUE +include(FindPackageHandleStandardArgs) + +find_package_handle_standard_args(OpenImageIO + REQUIRED_VARS + OIIO_LIBRARIES + OIIO_INCLUDE_DIRS + VERSION_VAR + OIIO_VERSION +) diff --git a/cmake/modules/FindOpenJPEG.cmake b/cmake/modules/FindOpenJPEG.cmake new file mode 100644 index 00000000..ddd98237 --- /dev/null +++ b/cmake/modules/FindOpenJPEG.cmake @@ -0,0 +1,75 @@ +# - Find OpenJPEG library +# Find the native OpenJPEG includes and library +# This module defines +# OPENJPEG_INCLUDE_DIRS, where to find openjpeg.h, Set when +# OPENJPEG_INCLUDE_DIR is found. +# OPENJPEG_LIBRARIES, libraries to link against to use OpenJPEG. +# OPENJPEG_ROOT_DIR, The base directory to search for OpenJPEG. +# This can also be an environment variable. +# OPENJPEG_FOUND, If false, do not try to use OpenJPEG. +# +# also defined, but not for general use are +# OPENJPEG_LIBRARY, where to find the OpenJPEG library. + +#============================================================================= +# Copyright 2011 Blender Foundation. +# +# Distributed under the OSI-approved BSD 3-Clause License, +# see accompanying file BSD-3-Clause-license.txt for details. +#============================================================================= + +# If OPENJPEG_ROOT_DIR was defined in the environment, use it. +IF(NOT OPENJPEG_ROOT_DIR AND NOT $ENV{OPENJPEG_ROOT_DIR} STREQUAL "") + SET(OPENJPEG_ROOT_DIR $ENV{OPENJPEG_ROOT_DIR}) +ENDIF() + +SET(_openjpeg_SEARCH_DIRS + ${OPENJPEG_ROOT_DIR} +) + +FIND_PATH(OPENJPEG_INCLUDE_DIR + NAMES + openjpeg.h + HINTS + ${_openjpeg_SEARCH_DIRS} + PATH_SUFFIXES + include + # Support future versions + openjpeg-2.9 + openjpeg-2.8 + openjpeg-2.7 + openjpeg-2.6 + openjpeg-2.5 + openjpeg-2.4 + openjpeg-2.3 + openjpeg-2.2 + openjpeg-2.1 + openjpeg-2.0 +) + +FIND_LIBRARY(OPENJPEG_LIBRARY + NAMES + openjp2 + HINTS + ${_openjpeg_SEARCH_DIRS} + PATH_SUFFIXES + lib64 lib + ) + +# handle the QUIETLY and REQUIRED arguments and set OPENJPEG_FOUND to TRUE if +# all listed variables are TRUE +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(OpenJPEG DEFAULT_MSG + OPENJPEG_LIBRARY OPENJPEG_INCLUDE_DIR) + +IF(OPENJPEG_FOUND) + SET(OPENJPEG_LIBRARIES ${OPENJPEG_LIBRARY}) + SET(OPENJPEG_INCLUDE_DIRS ${OPENJPEG_INCLUDE_DIR}) +ENDIF(OPENJPEG_FOUND) + +MARK_AS_ADVANCED( + OPENJPEG_INCLUDE_DIR + OPENJPEG_LIBRARY +) + +UNSET(_openjpeg_SEARCH_DIRS) diff --git a/cmake/modules/FindOpenSubdiv.cmake b/cmake/modules/FindOpenSubdiv.cmake new file mode 100644 index 00000000..5c561212 --- /dev/null +++ b/cmake/modules/FindOpenSubdiv.cmake @@ -0,0 +1,132 @@ +# - Find OpenSubdiv library +# Find the native OpenSubdiv includes and library +# This module defines +# OPENSUBDIV_INCLUDE_DIRS, where to find OpenSubdiv headers, Set when +# OPENSUBDIV_INCLUDE_DIR is found. +# OPENSUBDIV_LIBRARIES, libraries to link against to use OpenSubdiv. +# OPENSUBDIV_ROOT_DIR, the base directory to search for OpenSubdiv. +# This can also be an environment variable. +# OPENSUBDIV_FOUND, if false, do not try to use OpenSubdiv. +# +# also defined, but not for general use are +# OPENSUBDIV_LIBRARY, where to find the OpenSubdiv library. + +#============================================================================= +# Copyright 2013 Blender Foundation. +# +# Distributed under the OSI-approved BSD 3-Clause License, +# see accompanying file BSD-3-Clause-license.txt for details. +#============================================================================= + +# If OPENSUBDIV_ROOT_DIR was defined in the environment, use it. +IF(NOT OPENSUBDIV_ROOT_DIR AND NOT $ENV{OPENSUBDIV_ROOT_DIR} STREQUAL "") + SET(OPENSUBDIV_ROOT_DIR $ENV{OPENSUBDIV_ROOT_DIR}) +ENDIF() + +SET(_opensubdiv_FIND_COMPONENTS + osdCPU +) +if(OPENSUBDIV_USE_GPU) + list(APPEND + _opensubdiv_FIND_COMPONENTS + osdGPU) +endif() + + +SET(_opensubdiv_SEARCH_DIRS + ${OPENSUBDIV_ROOT_DIR} + /usr/local + /sw # Fink + /opt/local # DarwinPorts + /opt/csw # Blastwave + /opt/lib/opensubdiv + /opt/lib/osd # install_deps.sh +) + +FIND_PATH(OPENSUBDIV_INCLUDE_DIR + NAMES + opensubdiv/osd/mesh.h + HINTS + ${_opensubdiv_SEARCH_DIRS} + PATH_SUFFIXES + include +) + +if(OPENSUBDIV_INCLUDE_DIR AND EXISTS "${OPENSUBDIV_INCLUDE_DIR}/opensubdiv/version.h") + file(STRINGS + ${OPENSUBDIV_INCLUDE_DIR}/opensubdiv/version.h + TMP + REGEX "#define OPENSUBDIV_VERSION v.*$" + ) + string(REGEX + MATCHALL "[0-9]_[0-9]_[0-9]" + OPENSUBDIV_VERSION + ${TMP} + ) + string(REPLACE + "_" + "." + OPENSUBDIV_VERSION + ${OPENSUBDIV_VERSION} + ) +endif() + +SET(_opensubdiv_LIBRARIES) +FOREACH(COMPONENT ${_opensubdiv_FIND_COMPONENTS}) + STRING(TOUPPER ${COMPONENT} UPPERCOMPONENT) + FIND_LIBRARY(OPENSUBDIV_${UPPERCOMPONENT}_LIBRARY + NAMES + ${COMPONENT} + HINTS + ${_opensubdiv_SEARCH_DIRS} + PATH_SUFFIXES + lib64 lib + ) + if(DEFINED OPENSUBDIV_${UPPERCOMPONENT}_LIBRARY) + LIST(APPEND + _opensubdiv_LIBRARIES + "${OPENSUBDIV_${UPPERCOMPONENT}_LIBRARY}") + set(OPENSUBDIV_FOUND TRUE) + endif() +ENDFOREACH() + +MACRO(OPENSUBDIV_CHECK_CONTROLLER + controller_include_file + variable_name) + IF(EXISTS + "${OPENSUBDIV_INCLUDE_DIR}/opensubdiv/osd/${controller_include_file}" + ) + SET(${variable_name} TRUE) + ELSE() + SET(${variable_name} FALSE) + ENDIF() +ENDMACRO() + + +IF(OPENSUBDIV_FOUND) + SET(OPENSUBDIV_LIBRARIES ${_opensubdiv_LIBRARIES}) + SET(OPENSUBDIV_INCLUDE_DIRS ${OPENSUBDIV_INCLUDE_DIR}) + + # Find available compute controllers that USD may want to use. + OPENSUBDIV_CHECK_CONTROLLER("glXFBEvaluator.h" OPENSUBDIV_HAS_GLSL_TRANSFORM_FEEDBACK) + OPENSUBDIV_CHECK_CONTROLLER("glComputeEvaluator.h" OPENSUBDIV_HAS_GLSL_COMPUTE) +ENDIF(OPENSUBDIV_FOUND) + +MARK_AS_ADVANCED( + OPENSUBDIV_INCLUDE_DIR +) +FOREACH(COMPONENT ${_opensubdiv_FIND_COMPONENTS}) + STRING(TOUPPER ${COMPONENT} UPPERCOMPONENT) + MARK_AS_ADVANCED(OPENSUBDIV_${UPPERCOMPONENT}_LIBRARY) +ENDFOREACH() + +# handle the QUIETLY and REQUIRED arguments and set OPENSUBDIV_FOUND to TRUE if +# all listed variables are TRUE +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(OpenSubdiv + REQUIRED_VARS + OPENSUBDIV_INCLUDE_DIR + OPENSUBDIV_LIBRARIES + VERSION_VAR + OPENSUBDIV_VERSION +) diff --git a/cmake/modules/FindOpenVDB.cmake b/cmake/modules/FindOpenVDB.cmake new file mode 100644 index 00000000..2968a229 --- /dev/null +++ b/cmake/modules/FindOpenVDB.cmake @@ -0,0 +1,56 @@ +# +# Copyright 2019 Pixar +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +# Find OpenVDB header. +find_path( OPENVDB_INCLUDE_DIR + NAMES + openvdb/openvdb.h + PATH_SUFFIXES + include/ + HINTS + "${OPENVDB_LOCATION}" + "$ENV{OPENVDB_LOCATION}" + DOC + "OpenVDB headers path" +) + +find_library( OPENVDB_LIBRARY + NAMES + openvdb + PATH_SUFFIXES + lib/ + HINTS + ${OPENVDB_LOCATION} + $ENV{OPENVDB_LOCATION} + DOC + "The OpenVDB library path" +) + +include(FindPackageHandleStandardArgs) + +find_package_handle_standard_args(OpenVDB + DEFAULT_MSG + OPENVDB_INCLUDE_DIR + OPENVDB_LIBRARY +) diff --git a/cmake/modules/FindPyOpenGL.cmake b/cmake/modules/FindPyOpenGL.cmake new file mode 100644 index 00000000..620ee598 --- /dev/null +++ b/cmake/modules/FindPyOpenGL.cmake @@ -0,0 +1,46 @@ +# +# Copyright 2016 Pixar +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# +if (NOT Python_EXECUTABLE) + message(FATAL_ERROR "Unable to find Python executable - PyOpenGL not present") + return() +endif() + +execute_process( + COMMAND + "${Python_EXECUTABLE}" "-c" "from OpenGL import *" + RESULT_VARIABLE + pyopenglImportResult +) + +if (pyopenglImportResult EQUAL 0) + message(STATUS "Found PyOpenGL") + set(PYOPENGL_AVAILABLE True) +endif() + +include(FindPackageHandleStandardArgs) + +find_package_handle_standard_args(PyOpenGL + REQUIRED_VARS + PYOPENGL_AVAILABLE +) diff --git a/cmake/modules/FindPySide.cmake b/cmake/modules/FindPySide.cmake new file mode 100644 index 00000000..daa92d36 --- /dev/null +++ b/cmake/modules/FindPySide.cmake @@ -0,0 +1,79 @@ +# +# Copyright 2016 Pixar +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# +if (NOT Python_EXECUTABLE) + message(FATAL_ERROR "Unable to find Python executable - PySide not present") + return() +endif() + +# Prefer PySide2 over PySide +# Note: Windows does not support PySide2 with Python2.7 +execute_process( + COMMAND "${Python_EXECUTABLE}" "-c" "import PySide2" + RESULT_VARIABLE pySideImportResult +) +if (pySideImportResult EQUAL 0) + set(pySideImportResult "PySide2") + set(pySideUIC pyside2-uic python2-pyside2-uic pyside2-uic-2.7) +endif() + +# PySide2 not found OR PYSIDE explicitly requested +if (pySideImportResult EQUAL 1 OR PYSIDE_USE_PYSIDE) + execute_process( + COMMAND "${Python_EXECUTABLE}" "-c" "import PySide" + RESULT_VARIABLE pySideImportResult + ) + if (pySideImportResult EQUAL 0) + set(pySideImportResult "PySide") + set(pySideUIC pyside-uic python2-pyside-uic pyside-uic-2.7) + else() + set(pySideImportResult 0) + endif() +endif() + +find_program(PYSIDEUICBINARY NAMES ${pySideUIC} HINTS ${PYSIDE_BIN_DIR}) + +if (pySideImportResult) + if (EXISTS ${PYSIDEUICBINARY}) + message(STATUS "Found ${pySideImportResult}: with ${Python_EXECUTABLE}, will use ${PYSIDEUICBINARY} for pyside-uic binary") + set(PYSIDE_AVAILABLE True) + else() + message(STATUS "Found ${pySideImportResult} but NOT pyside-uic binary") + set(PYSIDE_AVAILABLE False) + endif() +else() + if (PYSIDE_USE_PYSIDE) + message(STATUS "Did not find PySide with ${Python_EXECUTABLE}") + else() + message(STATUS "Did not find PySide2 with ${Python_EXECUTABLE}") + endif() + set(PYSIDE_AVAILABLE False) +endif() + +include(FindPackageHandleStandardArgs) + +find_package_handle_standard_args(PySide + REQUIRED_VARS + PYSIDE_AVAILABLE + PYSIDEUICBINARY +) \ No newline at end of file diff --git a/cmake/modules/FindTBB.cmake b/cmake/modules/FindTBB.cmake new file mode 100644 index 00000000..4c2d7f1f --- /dev/null +++ b/cmake/modules/FindTBB.cmake @@ -0,0 +1,219 @@ +# The MIT License (MIT) +# +# Copyright (c) 2015 Justus Calvin +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# +# FindTBB +# ------- +# +# Find TBB include directories and libraries. +# +# Usage: +# +# find_package(TBB [major[.minor]] [EXACT] +# [QUIET] [REQUIRED] +# [[COMPONENTS] [components...]] +# [OPTIONAL_COMPONENTS components...]) +# +# where the allowed components are tbbmalloc and tbb_preview. Users may modify +# the behavior of this module with the following variables: +# +# * TBB_ROOT_DIR - The base directory the of TBB installation. +# * TBB_INCLUDE_DIR - The directory that contains the TBB headers files. +# * TBB_LIBRARY - The directory that contains the TBB library files. +# * TBB__LIBRARY - The path of the TBB the corresponding TBB library. +# These libraries, if specified, override the +# corresponding library search results, where +# may be tbb, tbb_debug, tbbmalloc, tbbmalloc_debug, +# tbb_preview, or tbb_preview_debug. +# * TBB_USE_DEBUG_BUILD - The debug version of tbb libraries, if present, will +# be used instead of the release version. +# +# Users may modify the behavior of this module with the following environment +# variables: +# +# * TBB_INSTALL_DIR +# * TBBROOT +# * LIBRARY_PATH +# +# This module will set the following variables: +# +# * TBB_FOUND - Set to false, or undefined, if we haven’t found, or +# don’t want to use TBB. +# * TBB__FOUND - If False, optional part of TBB sytem is +# not available. +# * TBB_VERSION - The full version string +# * TBB_VERSION_MAJOR - The major version +# * TBB_VERSION_MINOR - The minor version +# * TBB_INTERFACE_VERSION - The interface version number defined in +# tbb/tbb_stddef.h. +# * TBB__LIBRARY_RELEASE - The path of the TBB release version of +# , where may be tbb, tbb_debug, +# tbbmalloc, tbbmalloc_debug, tbb_preview, or +# tbb_preview_debug. +# * TBB__LIBRARY_DEGUG - The path of the TBB release version of +# , where may be tbb, tbb_debug, +# tbbmalloc, tbbmalloc_debug, tbb_preview, or +# tbb_preview_debug. +# +# The following varibles should be used to build and link with TBB: +# +# * TBB_INCLUDE_DIRS - The include directory for TBB. +# * TBB_LIBRARIES - The libraries to link against to use TBB. +# * TBB_DEFINITIONS - Definitions to use when compiling code that uses TBB. + +include(FindPackageHandleStandardArgs) + +if(NOT TBB_FOUND) + + ################################## + # Check the build type + ################################## + + if(NOT DEFINED TBB_USE_DEBUG_BUILD) + if(CMAKE_BUILD_TYPE MATCHES "Debug|DEBUG|debug") + set(TBB_USE_DEBUG_BUILD TRUE) + else() + set(TBB_USE_DEBUG_BUILD FALSE) + endif() + endif() + + ################################## + # Set the TBB search directories + ################################## + + # Define search paths based on user input and environment variables + set(TBB_SEARCH_DIR ${TBB_ROOT_DIR} $ENV{TBB_INSTALL_DIR} $ENV{TBBROOT}) + + # Define the search directories based on the current platform + if(CMAKE_SYSTEM_NAME STREQUAL "Windows") + set(TBB_DEFAULT_SEARCH_DIR "C:/Program Files/Intel/TBB" + "C:/Program Files (x86)/Intel/TBB") + # TODO: Set the proper suffix paths based on compiler introspection. + + elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + # OS X + set(TBB_DEFAULT_SEARCH_DIR "/opt/intel/tbb") + + # TODO: Check to see which C++ library is being used by the compiler. + if(NOT ${CMAKE_SYSTEM_VERSION} VERSION_LESS 13.0) + # The default C++ library on OS X 10.9 and later is libc++ + set(TBB_LIB_PATH_SUFFIX "lib/libc++") + else() + set(TBB_LIB_PATH_SUFFIX "lib") + endif() + elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") + # Linux + set(TBB_DEFAULT_SEARCH_DIR "/opt/intel/tbb") + + # TODO: Check compiler version to see the suffix should be /gcc4.1 or + # /gcc4.1. For now, assume that the compiler is more recent than + # gcc 4.4.x or later. + if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64") + set(TBB_LIB_PATH_SUFFIX "lib/intel64/gcc4.4") + elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^i.86$") + set(TBB_LIB_PATH_SUFFIX "lib/ia32/gcc4.4") + endif() + endif() + + ################################## + # Find the TBB include dir + ################################## + + find_path(TBB_INCLUDE_DIRS tbb/tbb.h + HINTS ${TBB_INCLUDE_DIR} ${TBB_SEARCH_DIR} + PATHS ${TBB_DEFAULT_SEARCH_DIR} + PATH_SUFFIXES include) + + ################################## + # Find TBB components + ################################## + + # Find each component + foreach(_comp tbb_preview tbbmalloc tbb) + # Search for the libraries + find_library(TBB_${_comp}_LIBRARY_RELEASE ${_comp} + HINTS ${TBB_LIBRARY} ${TBB_SEARCH_DIR} + PATHS ${TBB_DEFAULT_SEARCH_DIR} + PATH_SUFFIXES "${TBB_LIB_PATH_SUFFIX}") + + find_library(TBB_${_comp}_LIBRARY_DEBUG ${_comp}_debug + HINTS ${TBB_LIBRARY} ${TBB_SEARCH_DIR} + PATHS ${TBB_DEFAULT_SEARCH_DIR} ENV LIBRARY_PATH + PATH_SUFFIXES "${TBB_LIB_PATH_SUFFIX}") + + + # Set the library to be used for the component + if(NOT TBB_${_comp}_LIBRARY) + if(TBB_USE_DEBUG_BUILD AND TBB_${_comp}_LIBRARY_DEBUG) + set(TBB_${_comp}_LIBRARY "${TBB_${_comp}_LIBRARY_DEBUG}") + elseif(TBB_${_comp}_LIBRARY_RELEASE) + set(TBB_${_comp}_LIBRARY "${TBB_${_comp}_LIBRARY_RELEASE}") + elseif(TBB_${_comp}_LIBRARY_DEBUG) + set(TBB_${_comp}_LIBRARY "${TBB_${_comp}_LIBRARY_DEBUG}") + endif() + endif() + + # Set the TBB library list and component found variables + if(TBB_${_comp}_LIBRARY) + list(APPEND TBB_LIBRARIES "${TBB_${_comp}_LIBRARY}") + set(TBB_${_comp}_FOUND TRUE) + else() + set(TBB_${_comp}_FOUND FALSE) + endif() + + mark_as_advanced(TBB_${_comp}_LIBRARY_RELEASE) + mark_as_advanced(TBB_${_comp}_LIBRARY_DEBUG) + mark_as_advanced(TBB_${_comp}_LIBRARY) + + endforeach() + + ################################## + # Set compile flags + ################################## + + if(TBB_tbb_LIBRARY MATCHES "debug") + set(TBB_DEFINITIONS "-DTBB_USE_DEBUG=1") + endif() + + ################################## + # Set version strings + ################################## + + if(TBB_INCLUDE_DIRS) + file(READ "${TBB_INCLUDE_DIRS}/tbb/tbb_stddef.h" _tbb_version_file) + string(REGEX REPLACE ".*#define TBB_VERSION_MAJOR ([0-9]+).*" "\\1" + TBB_VERSION_MAJOR "${_tbb_version_file}") + string(REGEX REPLACE ".*#define TBB_VERSION_MINOR ([0-9]+).*" "\\1" + TBB_VERSION_MINOR "${_tbb_version_file}") + string(REGEX REPLACE ".*#define TBB_INTERFACE_VERSION ([0-9]+).*" "\\1" + TBB_INTERFACE_VERSION "${_tbb_version_file}") + set(TBB_VERSION "${TBB_VERSION_MAJOR}.${TBB_VERSION_MINOR}") + endif() + + find_package_handle_standard_args(TBB + REQUIRED_VARS TBB_INCLUDE_DIRS TBB_LIBRARIES + HANDLE_COMPONENTS + VERSION_VAR TBB_VERSION) + + mark_as_advanced(TBB_INCLUDE_DIRS TBB_LIBRARIES) + +endif() diff --git a/cmake/modules/FindUSD.cmake b/cmake/modules/FindUSD.cmake new file mode 100644 index 00000000..7893f2b3 --- /dev/null +++ b/cmake/modules/FindUSD.cmake @@ -0,0 +1,264 @@ +# Copyright 2019 Luma Pictures +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# TODO: We might want to upgrade this FindUSD.cmake + +# Simple module to find USD. + +if (WIN32) + # On Windows we need to find ".lib"... which is CMAKE_STATIC_LIBRARY_SUFFIX + # on WIN32 (CMAKE_SHARED_LIBRARY_SUFFIX is ".dll") + set(USD_LIB_SUFFIX ${CMAKE_STATIC_LIBRARY_SUFFIX} + CACHE STRING "Extension of USD libraries") +else () + # Defaults to ".so" on Linux, ".dylib" on MacOS + set(USD_LIB_SUFFIX ${CMAKE_SHARED_LIBRARY_SUFFIX} + CACHE STRING "Extension of USD libraries") +endif () + +# Note: for USD <= 0.19.11, there was a bug where, regardless of what +# PXR_LIB_PREFIX was set to, the behavior on windows was that the .lib files +# ALWAYS had no prefix. However, the PXR_LIB_PREFIX - which defaulted to "lib", +# even on windows - WAS used for the .dll names. +# +# So, if PXR_LIB_PREFIX was left at it's default value of "lib", you +# had output libs like: +# usd.lib +# libusd.dll +# +# The upshot is that, for windows and USD <= 0.19.11, you probably want to +# leave USD_LIB_PREFIX at it's default (empty string on windows), even if you +# set a PXR_LIB_PREFIX when building USD core. + +#set(USD_LIB_PREFIX "libpxr_" +set(USD_LIB_PREFIX ${CMAKE_SHARED_LIBRARY_PREFIX} + CACHE STRING "Prefix of USD libraries") + +find_path(USD_INCLUDE_DIR pxr/pxr.h + HINTS + $ENV{USD_INCLUDE_DIR} + ${USD_ROOT}/include + $ENV{USD_ROOT}/include + DOC "USD Include directory") + + + +# Disabled because this FindUSD doesn't work with pxrConfig.cmake - see note +# below + +# find_file(USD_CONFIG_FILE +# names pxrConfig.cmake +# PATHS ${USD_ROOT} +# $ENV{USD_ROOT} +# DOC "USD cmake configuration file") + +# We need to find either usd or usd_ms (the monolithic-shared library), +# with taking the prefix into account. +find_path(USD_LIBRARY_DIR + NAMES + ${USD_LIB_PREFIX}usd${USD_LIB_SUFFIX} + ${USD_LIB_PREFIX}usd_ms${USD_LIB_SUFFIX} + HINTS + $ENV{USD_LIBRARY_DIR} + ${USD_ROOT}/lib + $ENV{USD_ROOT}/lib + DOC "USD Libraries directory") + +find_file(USD_GENSCHEMA + NAMES usdGenSchema + HINTS + ${USD_ROOT} + $ENV{USD_ROOT} + PATH_SUFFIXES + bin + DOC "USD Gen schema application") + +# USD Maya components + +find_path(USD_MAYA_INCLUDE_DIR usdMaya/api.h + HINTS + # If we're using Autodesk Maya-USD repo + ${MAYA_USD_ROOT}/plugin/pxr/maya/include + $ENV{MAYA_USD_ROOT}/plugin/pxr/maya/include + + # If we're using Pixar USD core repo (<=0.19.11) + ${USD_ROOT}/third_party/maya/include + $ENV{USD_ROOT}/third_party/maya/include + ${USD_MAYA_ROOT}/third_party/maya/include + $ENV{USD_MAYA_ROOT}/third_party/maya/include + DOC "USD Maya Include directory") + +find_path(USD_MAYA_LIBRARY_DIR + NAMES + # If we're using Autodesk Maya-USD repo + ${CMAKE_SHARED_LIBRARY_PREFIX}usdMaya${USD_LIB_SUFFIX} + + # If we're using Pixar USD core repo (<=0.19.11) + ${USD_LIB_PREFIX}usdMaya${USD_LIB_SUFFIX} + HINTS + # If we're using Autodesk Maya-USD repo + ${MAYA_USD_ROOT}/plugin/pxr/maya/lib + $ENV{MAYA_USD_ROOT}/plugin/pxr/maya/lib + + # If we're using Pixar USD core repo (<=0.19.11) + ${USD_ROOT}/third_party/maya/lib + $ENV{USD_ROOT}/third_party/maya/lib + ${USD_MAYA_ROOT}/third_party/maya/lib + $ENV{USD_MAYA_ROOT}/third_party/maya/lib + DOC "USD Maya Library directory") + +# Maya USD (autodesk repo) - only components + +find_path(MAYA_USD_INCLUDE_DIR mayaUsd/mayaUsd.h + HINTS + # If we're using Autodesk Maya-USD repo + ${MAYA_USD_ROOT}/include + $ENV{MAYA_USD_ROOT}/include + DOC "Maya-USD Core Include directory") + +find_path(MAYA_USD_LIBRARY_DIR + NAMES + mayaUsd + HINTS + # If we're using Autodesk Maya-USD repo + ${MAYA_USD_ROOT}/lib + $ENV{MAYA_USD_ROOT}/lib + DOC "USD Maya Library directory") + +# USD Katana components + +find_path(USD_KATANA_INCLUDE_DIR usdKatana/api.h + HINTS + ${USD_ROOT}/third_party/katana/include + $ENV{USD_ROOT}/third_party/katana/include + ${USD_KATANA_ROOT}/third_party/katana/include + $ENV{USD_KATANA_ROOT}/third_party/katana/include + DOC "USD Katana Include directory") + +find_path(USD_KATANA_LIBRARY_DIR + NAMES + ${USD_LIB_PREFIX}usdKatana${USD_LIB_SUFFIX} + HINTS + ${USD_ROOT}/third_party/katana/lib + $ENV{USD_ROOT}/third_party/katana/lib + ${USD_KATANA_ROOT}/third_party/katana/lib + $ENV{USD_KATANA_ROOT}/third_party/katana/lib + DOC "USD Katana Library directory") + +# USD Houdini components + +find_path(USD_HOUDINI_INCLUDE_DIR gusd/api.h + HINTS + ${USD_ROOT}/third_party/houdini/include + $ENV{USD_ROOT}/third_party/houdini/include + ${USD_HOUDINI_ROOT}/third_party/houdini/include + $ENV{USD_HOUDINI_ROOT}/third_party/houdini/include + DOC "USD Houdini Include directory") + +find_path(USD_HOUDINI_LIBRARY_DIR + NAMES + ${USD_LIB_PREFIX}gusd${USD_LIB_SUFFIX} + HINTS + ${USD_ROOT}/third_party/houdini/lib + $ENV{USD_ROOT}/third_party/houdini/lib + ${USD_HOUDINI_ROOT}/third_party/houdini/lib + $ENV{USD_HOUDINI_ROOT}/third_party/houdini/lib + DOC "USD Houdini Library directory") + +if(USD_INCLUDE_DIR AND EXISTS "${USD_INCLUDE_DIR}/pxr/pxr.h") + foreach(_usd_comp MAJOR MINOR PATCH) + file(STRINGS + "${USD_INCLUDE_DIR}/pxr/pxr.h" + _usd_tmp + REGEX "#define PXR_${_usd_comp}_VERSION .*$") + string(REGEX MATCHALL "[0-9]+" USD_${_usd_comp}_VERSION ${_usd_tmp}) + endforeach() + set(USD_VERSION ${USD_MAJOR_VERSION}.${USD_MINOR_VERSION}.${USD_PATCH_VERSION}) + set(USD_MAJOR_VERSION ${USD_MAJOR_VERSION}) + set(USD_MINOR_VERSION ${USD_MINOR_VERSION}) + set(USD_PATCH_VERSION ${USD_PATCH_VERSION}) +endif() + +# NOTE: setting the usd libs to be INTERFACE IMPORTED targets conflicts with +# usage of pxrConfig.cmake - so if you are using this FindUSD, you +# currently can't use pxrConfig.cmake. You could comment out / remove +# these sections to allow usage of pxrConfig.cmake. +# We considered using pxrConfig.cmake, but we don't like the fact that it +# bakes in full paths to the various dependencies + +set(USD_LIBS ar;arch;cameraUtil;garch;gf;glf;hd;hdSt;hdx;hf;hgi;hgiGL;hio;js;kind;ndr;pcp;plug;pxOsd;sdf;sdr;tf;trace;usd;usdAppUtils;usdGeom;usdHydra;usdImaging;usdImagingGL;usdLux;usdRender;usdRi;usdShade;usdShaders;usdSkel;usdSkelImaging;usdUI;usdUtils;usdviewq;usdVol;usdVolImaging;vt;work;usd_ms) + +foreach (lib ${USD_LIBS}) + find_library(USD_${lib}_LIBRARY + NAMES ${USD_LIB_PREFIX}${lib}${USD_LIB_SUFFIX} + HINTS ${USD_LIBRARY_DIR}) + if (USD_${lib}_LIBRARY) + add_library(${lib} INTERFACE IMPORTED) + set_target_properties(${lib} + PROPERTIES + INTERFACE_LINK_LIBRARIES ${USD_${lib}_LIBRARY} + ) + list(APPEND USD_LIBRARIES ${USD_${lib}_LIBRARY}) + endif () +endforeach () + +set(USD_MAYA_LIBS px_vp20;pxrUsdMayaGL;usdMaya) + +foreach (lib ${USD_MAYA_LIBS}) + find_library(USD_MAYA_${lib}_LIBRARY + NAMES + # If we're using Autodesk Maya-USD repo + ${lib}${USD_LIB_SUFFIX} + + # If we're using Pixar USD core repo (<=0.19.11) + ${USD_LIB_PREFIX}${lib}${USD_LIB_SUFFIX} + HINTS ${USD_MAYA_LIBRARY_DIR}) + if (USD_MAYA_${lib}_LIBRARY) + add_library(${lib} INTERFACE IMPORTED) + set_target_properties(${lib} + PROPERTIES + INTERFACE_LINK_LIBRARIES ${USD_MAYA_${lib}_LIBRARY} + ) + list(APPEND USD_MAYA_LIBRARIES ${USD_MAYA_${lib}_LIBRARY}) + endif () +endforeach () + +set(USD_KATANA_LIBS usdKatana;vtKatana) + +foreach (lib ${USD_KATANA_LIBS}) + find_library(USD_KATANA_${lib}_LIBRARY + NAMES ${USD_LIB_PREFIX}${lib}${USD_LIB_SUFFIX} + HINTS ${USD_KATANA_LIBRARY_DIR}) + if (USD_KATANA_${lib}_LIBRARY) + add_library(${lib} INTERFACE IMPORTED) + set_target_properties(${lib} + PROPERTIES + INTERFACE_LINK_LIBRARIES ${USD_KATANA_${lib}_LIBRARY} + ) + list(APPEND USD_KATANA_LIBRARIES ${USD_KATANA_${lib}_LIBRARY}) + endif () +endforeach () + +include(FindPackageHandleStandardArgs) + +find_package_handle_standard_args(USD + REQUIRED_VARS + USD_INCLUDE_DIR + USD_LIBRARY_DIR + USD_LIBRARIES + #USD_MAJOR_VERSION + USD_MINOR_VERSION + USD_PATCH_VERSION + VERSION_VAR + USD_VERSION) \ No newline at end of file diff --git a/plugin/CMakeLists.txt b/plugin/CMakeLists.txt new file mode 100644 index 00000000..c2939416 --- /dev/null +++ b/plugin/CMakeLists.txt @@ -0,0 +1,16 @@ +# Copyright 2020 Tangent Animation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +add_subdirectory(ndrCycles) +add_subdirectory(hdCycles) \ No newline at end of file diff --git a/plugin/hdCycles/CMakeLists.txt b/plugin/hdCycles/CMakeLists.txt new file mode 100644 index 00000000..3e41b746 --- /dev/null +++ b/plugin/hdCycles/CMakeLists.txt @@ -0,0 +1,84 @@ +# Copyright 2020 Tangent Animation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set(PXR_PREFIX "") +set(PXR_PACKAGE hdCycles) + +add_custom_target(python ALL) + +if(USE_USD_HOUDINI) + link_directories(${HOUDINI_LIB_DIRS}) + link_libraries(${HOUDINI_LIBRARIES_DEPS}) +endif() + +pxr_plugin(${PXR_PACKAGE} + LIBRARIES + + ${USD_LIBRARIES} + ${TBB_LIBRARIES} + ${Boost_LIBRARIES} + ${Python_LIBRARIES} + ${CYCLES_LIBRARIES} + ${OPENGL_gl_LIBRARY} + ${PNG_LIBRARIES} + ${JPEG_LIBRARIES} + ${ZLIB_LIBRARIES} + ${TIFF_LIBRARY} + ${GLEW_LIBRARY} + ${OPENJPEG_LIBRARIES} + ${OPENEXR_LIBRARIES} + ${OPENSUBDIV_LIBRARIES} + ${OIIO_LIBRARIES} + + CPPFILES + "Mikktspace/mikktspace.c" + + INCLUDE_DIRS + ${USD_INCLUDE_DIR} + ${Boost_INCLUDE_DIRS} + ${Python_INCLUDE_DIRS} + ${CYCLES_INCLUDE_DIRS} + ${OIIO_INCLUDE_DIRS} + ${OPENEXR_INCLUDE_DIRS} + ${GLEW_INCLUDE_DIR} + ${OPENGL_INCLUDE_DIR} + ${OPENSUBDIV_INCLUDE_DIR} + ${OPENVDB_INCLUDE_DIR} + + PUBLIC_CLASSES + basisCurves + camera + instancer + light + material + mesh + points + + renderDelegate + rendererPlugin + renderPass + renderParam + + config + renderBuffer + utils + + PUBLIC_HEADERS + api.h + + RESOURCE_FILES + plugInfo.json + + DISABLE_PRECOMPILED_HEADERS +) diff --git a/plugin/hdCycles/Mikktspace/mikktspace.c b/plugin/hdCycles/Mikktspace/mikktspace.c new file mode 100644 index 00000000..125615b1 --- /dev/null +++ b/plugin/hdCycles/Mikktspace/mikktspace.c @@ -0,0 +1,1890 @@ +/** \file mikktspace/mikktspace.c + * \ingroup mikktspace + */ +/** + * Copyright (C) 2011 by Morten S. Mikkelsen + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#include +#include +#include +#include +#include +#include + +#include "mikktspace.h" + +#define TFALSE 0 +#define TTRUE 1 + +#ifndef M_PI +#define M_PI 3.1415926535897932384626433832795 +#endif + +#define INTERNAL_RND_SORT_SEED 39871946 + +// internal structure +typedef struct { + float x, y, z; +} SVec3; + +static tbool veq( const SVec3 v1, const SVec3 v2 ) +{ + return (v1.x == v2.x) && (v1.y == v2.y) && (v1.z == v2.z); +} + +static SVec3 vadd( const SVec3 v1, const SVec3 v2 ) +{ + SVec3 vRes; + + vRes.x = v1.x + v2.x; + vRes.y = v1.y + v2.y; + vRes.z = v1.z + v2.z; + + return vRes; +} + + +static SVec3 vsub( const SVec3 v1, const SVec3 v2 ) +{ + SVec3 vRes; + + vRes.x = v1.x - v2.x; + vRes.y = v1.y - v2.y; + vRes.z = v1.z - v2.z; + + return vRes; +} + +static SVec3 vscale(const float fS, const SVec3 v) +{ + SVec3 vRes; + + vRes.x = fS * v.x; + vRes.y = fS * v.y; + vRes.z = fS * v.z; + + return vRes; +} + +static float LengthSquared( const SVec3 v ) +{ + return v.x*v.x + v.y*v.y + v.z*v.z; +} + +static float Length( const SVec3 v ) +{ + return sqrtf(LengthSquared(v)); +} + +static SVec3 Normalize( const SVec3 v ) +{ + return vscale(1 / Length(v), v); +} + +static float vdot( const SVec3 v1, const SVec3 v2) +{ + return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z; +} + + +static tbool NotZero(const float fX) +{ + // could possibly use FLT_EPSILON instead + return fabsf(fX) > FLT_MIN; +} + +static tbool VNotZero(const SVec3 v) +{ + // might change this to an epsilon based test + return NotZero(v.x) || NotZero(v.y) || NotZero(v.z); +} + + + +typedef struct { + int iNrFaces; + int * pTriMembers; +} SSubGroup; + +typedef struct { + int iNrFaces; + int * pFaceIndices; + int iVertexRepresentitive; + tbool bOrientPreservering; +} SGroup; + +// +#define MARK_DEGENERATE 1 +#define QUAD_ONE_DEGEN_TRI 2 +#define GROUP_WITH_ANY 4 +#define ORIENT_PRESERVING 8 + + + +typedef struct { + int FaceNeighbors[3]; + SGroup * AssignedGroup[3]; + + // normalized first order face derivatives + SVec3 vOs, vOt; + float fMagS, fMagT; // original magnitudes + + // determines if the current and the next triangle are a quad. + int iOrgFaceNumber; + int iFlag, iTSpacesOffs; + unsigned char vert_num[4]; +} STriInfo; + +typedef struct { + SVec3 vOs; + float fMagS; + SVec3 vOt; + float fMagT; + int iCounter; // this is to average back into quads. + tbool bOrient; +} STSpace; + +static int GenerateInitialVerticesIndexList(STriInfo pTriInfos[], int piTriList_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); +static void GenerateSharedVerticesIndexList(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); +static void InitTriInfo(STriInfo pTriInfos[], const int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); +static int Build4RuleGroups(STriInfo pTriInfos[], SGroup pGroups[], int piGroupTrianglesBuffer[], const int piTriListIn[], const int iNrTrianglesIn); +static tbool GenerateTSpaces(STSpace psTspace[], const STriInfo pTriInfos[], const SGroup pGroups[], + const int iNrActiveGroups, const int piTriListIn[], const float fThresCos, + const SMikkTSpaceContext * pContext); + +static int MakeIndex(const int iFace, const int iVert) +{ + assert(iVert>=0 && iVert<4 && iFace>=0); + return (iFace<<2) | (iVert&0x3); +} + +static void IndexToData(int * piFace, int * piVert, const int iIndexIn) +{ + piVert[0] = iIndexIn&0x3; + piFace[0] = iIndexIn>>2; +} + +static STSpace AvgTSpace(const STSpace * pTS0, const STSpace * pTS1) +{ + STSpace ts_res; + + // this if is important. Due to floating point precision + // averaging when ts0==ts1 will cause a slight difference + // which results in tangent space splits later on + if (pTS0->fMagS==pTS1->fMagS && pTS0->fMagT==pTS1->fMagT && + veq(pTS0->vOs,pTS1->vOs) && veq(pTS0->vOt, pTS1->vOt)) + { + ts_res.fMagS = pTS0->fMagS; + ts_res.fMagT = pTS0->fMagT; + ts_res.vOs = pTS0->vOs; + ts_res.vOt = pTS0->vOt; + } + else + { + ts_res.fMagS = 0.5f*(pTS0->fMagS+pTS1->fMagS); + ts_res.fMagT = 0.5f*(pTS0->fMagT+pTS1->fMagT); + ts_res.vOs = vadd(pTS0->vOs,pTS1->vOs); + ts_res.vOt = vadd(pTS0->vOt,pTS1->vOt); + if ( VNotZero(ts_res.vOs) ) ts_res.vOs = Normalize(ts_res.vOs); + if ( VNotZero(ts_res.vOt) ) ts_res.vOt = Normalize(ts_res.vOt); + } + + return ts_res; +} + + + +static SVec3 GetPosition(const SMikkTSpaceContext * pContext, const int index); +static SVec3 GetNormal(const SMikkTSpaceContext * pContext, const int index); +static SVec3 GetTexCoord(const SMikkTSpaceContext * pContext, const int index); + + +// degen triangles +static void DegenPrologue(STriInfo pTriInfos[], int piTriList_out[], const int iNrTrianglesIn, const int iTotTris); +static void DegenEpilogue(STSpace psTspace[], STriInfo pTriInfos[], int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn, const int iTotTris); + + +tbool genTangSpaceDefault(const SMikkTSpaceContext * pContext) +{ + return genTangSpace(pContext, 180.0f); +} + +tbool genTangSpace(const SMikkTSpaceContext * pContext, const float fAngularThreshold) +{ + // count nr_triangles + int * piTriListIn = NULL, * piGroupTrianglesBuffer = NULL; + STriInfo * pTriInfos = NULL; + SGroup * pGroups = NULL; + STSpace * psTspace = NULL; + int iNrTrianglesIn = 0, f=0, t=0, i=0; + int iNrTSPaces = 0, iTotTris = 0, iDegenTriangles = 0, iNrMaxGroups = 0; + int iNrActiveGroups = 0, index = 0; + const int iNrFaces = pContext->m_pInterface->m_getNumFaces(pContext); + tbool bRes = TFALSE; + const float fThresCos = (float) cos((fAngularThreshold*(float)M_PI)/180.0f); + + // verify all call-backs have been set + if ( pContext->m_pInterface->m_getNumFaces==NULL || + pContext->m_pInterface->m_getNumVerticesOfFace==NULL || + pContext->m_pInterface->m_getPosition==NULL || + pContext->m_pInterface->m_getNormal==NULL || + pContext->m_pInterface->m_getTexCoord==NULL ) + return TFALSE; + + // count triangles on supported faces + for (f=0; fm_pInterface->m_getNumVerticesOfFace(pContext, f); + if (verts==3) ++iNrTrianglesIn; + else if (verts==4) iNrTrianglesIn += 2; + } + if (iNrTrianglesIn<=0) return TFALSE; + + // allocate memory for an index list + piTriListIn = (int *) malloc(sizeof(int)*3*iNrTrianglesIn); + pTriInfos = (STriInfo *) malloc(sizeof(STriInfo)*iNrTrianglesIn); + if (piTriListIn==NULL || pTriInfos==NULL) + { + if (piTriListIn!=NULL) free(piTriListIn); + if (pTriInfos!=NULL) free(pTriInfos); + return TFALSE; + } + + // make an initial triangle --> face index list + iNrTSPaces = GenerateInitialVerticesIndexList(pTriInfos, piTriListIn, pContext, iNrTrianglesIn); + + // make a welded index list of identical positions and attributes (pos, norm, texc) + //printf("gen welded index list begin\n"); + GenerateSharedVerticesIndexList(piTriListIn, pContext, iNrTrianglesIn); + //printf("gen welded index list end\n"); + + // Mark all degenerate triangles + iTotTris = iNrTrianglesIn; + iDegenTriangles = 0; + for (t=0; tm_pInterface->m_getNumVerticesOfFace(pContext, f); + if (verts!=3 && verts!=4) continue; + + + // I've decided to let degenerate triangles and group-with-anythings + // vary between left/right hand coordinate systems at the vertices. + // All healthy triangles on the other hand are built to always be either or. + + /*// force the coordinate system orientation to be uniform for every face. + // (this is already the case for good triangles but not for + // degenerate ones and those with bGroupWithAnything==true) + bool bOrient = psTspace[index].bOrient; + if (psTspace[index].iCounter == 0) // tspace was not derived from a group + { + // look for a space created in GenerateTSpaces() by iCounter>0 + bool bNotFound = true; + int i=1; + while (i 0) bNotFound=false; + else ++i; + } + if (!bNotFound) bOrient = psTspace[index+i].bOrient; + }*/ + + // set data + for (i=0; ivOs.x, pTSpace->vOs.y, pTSpace->vOs.z}; + float bitang[] = {pTSpace->vOt.x, pTSpace->vOt.y, pTSpace->vOt.z}; + if (pContext->m_pInterface->m_setTSpace!=NULL) + pContext->m_pInterface->m_setTSpace(pContext, tang, bitang, pTSpace->fMagS, pTSpace->fMagT, pTSpace->bOrient, f, i); + if (pContext->m_pInterface->m_setTSpaceBasic!=NULL) + pContext->m_pInterface->m_setTSpaceBasic(pContext, tang, pTSpace->bOrient==TTRUE ? 1.0f : (-1.0f), f, i); + + ++index; + } + } + + free(psTspace); + + + return TTRUE; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +typedef struct { + float vert[3]; + int index; +} STmpVert; + +static const int g_iCells = 2048; + +#ifdef _MSC_VER + #define NOINLINE __declspec(noinline) +#else + #define NOINLINE __attribute__ ((noinline)) +#endif + +// it is IMPORTANT that this function is called to evaluate the hash since +// inlining could potentially reorder instructions and generate different +// results for the same effective input value fVal. +static NOINLINE int FindGridCell(const float fMin, const float fMax, const float fVal) +{ + const float fIndex = g_iCells * ((fVal-fMin)/(fMax-fMin)); + const int iIndex = (int)fIndex; + return iIndex < g_iCells ? (iIndex >= 0 ? iIndex : 0) : (g_iCells - 1); +} + +static void MergeVertsFast(int piTriList_in_and_out[], STmpVert pTmpVert[], const SMikkTSpaceContext * pContext, const int iL_in, const int iR_in); +static void MergeVertsSlow(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int pTable[], const int iEntries); +static void GenerateSharedVerticesIndexListSlow(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); + +static void GenerateSharedVerticesIndexList(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn) +{ + + // Generate bounding box + int * piHashTable=NULL, * piHashCount=NULL, * piHashOffsets=NULL, * piHashCount2=NULL; + STmpVert * pTmpVert = NULL; + int i=0, iChannel=0, k=0, e=0; + int iMaxCount=0; + SVec3 vMin = GetPosition(pContext, 0), vMax = vMin, vDim; + float fMin, fMax; + for (i=1; i<(iNrTrianglesIn*3); i++) + { + const int index = piTriList_in_and_out[i]; + + const SVec3 vP = GetPosition(pContext, index); + if (vMin.x > vP.x) vMin.x = vP.x; + else if (vMax.x < vP.x) vMax.x = vP.x; + if (vMin.y > vP.y) vMin.y = vP.y; + else if (vMax.y < vP.y) vMax.y = vP.y; + if (vMin.z > vP.z) vMin.z = vP.z; + else if (vMax.z < vP.z) vMax.z = vP.z; + } + + vDim = vsub(vMax,vMin); + iChannel = 0; + fMin = vMin.x; fMax=vMax.x; + if (vDim.y>vDim.x && vDim.y>vDim.z) + { + iChannel=1; + fMin = vMin.y, fMax=vMax.y; + } + else if (vDim.z>vDim.x) + { + iChannel=2; + fMin = vMin.z, fMax=vMax.z; + } + + // make allocations + piHashTable = (int *) malloc(sizeof(int)*iNrTrianglesIn*3); + piHashCount = (int *) malloc(sizeof(int)*g_iCells); + piHashOffsets = (int *) malloc(sizeof(int)*g_iCells); + piHashCount2 = (int *) malloc(sizeof(int)*g_iCells); + + if (piHashTable==NULL || piHashCount==NULL || piHashOffsets==NULL || piHashCount2==NULL) + { + if (piHashTable!=NULL) free(piHashTable); + if (piHashCount!=NULL) free(piHashCount); + if (piHashOffsets!=NULL) free(piHashOffsets); + if (piHashCount2!=NULL) free(piHashCount2); + GenerateSharedVerticesIndexListSlow(piTriList_in_and_out, pContext, iNrTrianglesIn); + return; + } + memset(piHashCount, 0, sizeof(int)*g_iCells); + memset(piHashCount2, 0, sizeof(int)*g_iCells); + + // count amount of elements in each cell unit + for (i=0; i<(iNrTrianglesIn*3); i++) + { + const int index = piTriList_in_and_out[i]; + const SVec3 vP = GetPosition(pContext, index); + const float fVal = iChannel==0 ? vP.x : (iChannel==1 ? vP.y : vP.z); + const int iCell = FindGridCell(fMin, fMax, fVal); + ++piHashCount[iCell]; + } + + // evaluate start index of each cell. + piHashOffsets[0]=0; + for (k=1; kpTmpVert[l].vert[c]) fvMin[c]=pTmpVert[l].vert[c]; + else if (fvMax[c]dx && dy>dz) channel=1; + else if (dz>dx) channel=2; + + fSep = 0.5f*(fvMax[channel]+fvMin[channel]); + + // terminate recursion when the separation/average value + // is no longer strictly between fMin and fMax values. + if (fSep>=fvMax[channel] || fSep<=fvMin[channel]) + { + // complete the weld + for (l=iL_in; l<=iR_in; l++) + { + int i = pTmpVert[l].index; + const int index = piTriList_in_and_out[i]; + const SVec3 vP = GetPosition(pContext, index); + const SVec3 vN = GetNormal(pContext, index); + const SVec3 vT = GetTexCoord(pContext, index); + + tbool bNotFound = TTRUE; + int l2=iL_in, i2rec=-1; + while (l20); // at least 2 entries + + // separate (by fSep) all points between iL_in and iR_in in pTmpVert[] + while (iL < iR) + { + tbool bReadyLeftSwap = TFALSE, bReadyRightSwap = TFALSE; + while ((!bReadyLeftSwap) && iL=iL_in && iL<=iR_in); + bReadyLeftSwap = !(pTmpVert[iL].vert[channel]=iL_in && iR<=iR_in); + bReadyRightSwap = pTmpVert[iR].vert[channel]m_pInterface->m_getNumFaces(pContext); f++) + { + const int verts = pContext->m_pInterface->m_getNumVerticesOfFace(pContext, f); + if (verts!=3 && verts!=4) continue; + + pTriInfos[iDstTriIndex].iOrgFaceNumber = f; + pTriInfos[iDstTriIndex].iTSpacesOffs = iTSpacesOffs; + + if (verts==3) + { + unsigned char * pVerts = pTriInfos[iDstTriIndex].vert_num; + pVerts[0]=0; pVerts[1]=1; pVerts[2]=2; + piTriList_out[iDstTriIndex*3+0] = MakeIndex(f, 0); + piTriList_out[iDstTriIndex*3+1] = MakeIndex(f, 1); + piTriList_out[iDstTriIndex*3+2] = MakeIndex(f, 2); + ++iDstTriIndex; // next + } + else + { + { + pTriInfos[iDstTriIndex+1].iOrgFaceNumber = f; + pTriInfos[iDstTriIndex+1].iTSpacesOffs = iTSpacesOffs; + } + + { + // need an order independent way to evaluate + // tspace on quads. This is done by splitting + // along the shortest diagonal. + const int i0 = MakeIndex(f, 0); + const int i1 = MakeIndex(f, 1); + const int i2 = MakeIndex(f, 2); + const int i3 = MakeIndex(f, 3); + const SVec3 T0 = GetTexCoord(pContext, i0); + const SVec3 T1 = GetTexCoord(pContext, i1); + const SVec3 T2 = GetTexCoord(pContext, i2); + const SVec3 T3 = GetTexCoord(pContext, i3); + const float distSQ_02 = LengthSquared(vsub(T2,T0)); + const float distSQ_13 = LengthSquared(vsub(T3,T1)); + tbool bQuadDiagIs_02; + if (distSQ_02m_pInterface->m_getPosition(pContext, pos, iF, iI); + res.x=pos[0]; res.y=pos[1]; res.z=pos[2]; + return res; +} + +static SVec3 GetNormal(const SMikkTSpaceContext * pContext, const int index) +{ + int iF, iI; + SVec3 res; float norm[3]; + IndexToData(&iF, &iI, index); + pContext->m_pInterface->m_getNormal(pContext, norm, iF, iI); + res.x=norm[0]; res.y=norm[1]; res.z=norm[2]; + return res; +} + +static SVec3 GetTexCoord(const SMikkTSpaceContext * pContext, const int index) +{ + int iF, iI; + SVec3 res; float texc[2]; + IndexToData(&iF, &iI, index); + pContext->m_pInterface->m_getTexCoord(pContext, texc, iF, iI); + res.x=texc[0]; res.y=texc[1]; res.z=1.0f; + return res; +} + +///////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////// + +typedef union { + struct + { + int i0, i1, f; + }; + int array[3]; +} SEdge; + +static void BuildNeighborsFast(STriInfo pTriInfos[], SEdge * pEdges, const int piTriListIn[], const int iNrTrianglesIn); +static void BuildNeighborsSlow(STriInfo pTriInfos[], const int piTriListIn[], const int iNrTrianglesIn); + +// returns the texture area times 2 +static float CalcTexArea(const SMikkTSpaceContext * pContext, const int indices[]) +{ + const SVec3 t1 = GetTexCoord(pContext, indices[0]); + const SVec3 t2 = GetTexCoord(pContext, indices[1]); + const SVec3 t3 = GetTexCoord(pContext, indices[2]); + + const float t21x = t2.x-t1.x; + const float t21y = t2.y-t1.y; + const float t31x = t3.x-t1.x; + const float t31y = t3.y-t1.y; + + const float fSignedAreaSTx2 = t21x*t31y - t21y*t31x; + + return fSignedAreaSTx2<0 ? (-fSignedAreaSTx2) : fSignedAreaSTx2; +} + +static void InitTriInfo(STriInfo pTriInfos[], const int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn) +{ + int f=0, i=0, t=0; + // pTriInfos[f].iFlag is cleared in GenerateInitialVerticesIndexList() which is called before this function. + + // generate neighbor info list + for (f=0; f0 ? ORIENT_PRESERVING : 0); + + if ( NotZero(fSignedAreaSTx2) ) + { + const float fAbsArea = fabsf(fSignedAreaSTx2); + const float fLenOs = Length(vOs); + const float fLenOt = Length(vOt); + const float fS = (pTriInfos[f].iFlag&ORIENT_PRESERVING)==0 ? (-1.0f) : 1.0f; + if ( NotZero(fLenOs) ) pTriInfos[f].vOs = vscale(fS/fLenOs, vOs); + if ( NotZero(fLenOt) ) pTriInfos[f].vOt = vscale(fS/fLenOt, vOt); + + // evaluate magnitudes prior to normalization of vOs and vOt + pTriInfos[f].fMagS = fLenOs / fAbsArea; + pTriInfos[f].fMagT = fLenOt / fAbsArea; + + // if this is a good triangle + if ( NotZero(pTriInfos[f].fMagS) && NotZero(pTriInfos[f].fMagT)) + pTriInfos[f].iFlag &= (~GROUP_WITH_ANY); + } + } + + // force otherwise healthy quads to a fixed orientation + while (t<(iNrTrianglesIn-1)) + { + const int iFO_a = pTriInfos[t].iOrgFaceNumber; + const int iFO_b = pTriInfos[t+1].iOrgFaceNumber; + if (iFO_a==iFO_b) // this is a quad + { + const tbool bIsDeg_a = (pTriInfos[t].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; + const tbool bIsDeg_b = (pTriInfos[t+1].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; + + // bad triangles should already have been removed by + // DegenPrologue(), but just in case check bIsDeg_a and bIsDeg_a are false + if ((bIsDeg_a||bIsDeg_b)==TFALSE) + { + const tbool bOrientA = (pTriInfos[t].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; + const tbool bOrientB = (pTriInfos[t+1].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; + // if this happens the quad has extremely bad mapping!! + if (bOrientA!=bOrientB) + { + //printf("found quad with bad mapping\n"); + tbool bChooseOrientFirstTri = TFALSE; + if ((pTriInfos[t+1].iFlag&GROUP_WITH_ANY)!=0) bChooseOrientFirstTri = TTRUE; + else if ( CalcTexArea(pContext, &piTriListIn[t*3+0]) >= CalcTexArea(pContext, &piTriListIn[(t+1)*3+0]) ) + bChooseOrientFirstTri = TTRUE; + + // force match + { + const int t0 = bChooseOrientFirstTri ? t : (t+1); + const int t1 = bChooseOrientFirstTri ? (t+1) : t; + pTriInfos[t1].iFlag &= (~ORIENT_PRESERVING); // clear first + pTriInfos[t1].iFlag |= (pTriInfos[t0].iFlag&ORIENT_PRESERVING); // copy bit + } + } + } + t += 2; + } + else + ++t; + } + + // match up edge pairs + { + SEdge * pEdges = (SEdge *) malloc(sizeof(SEdge)*iNrTrianglesIn*3); + if (pEdges==NULL) + BuildNeighborsSlow(pTriInfos, piTriListIn, iNrTrianglesIn); + else + { + BuildNeighborsFast(pTriInfos, pEdges, piTriListIn, iNrTrianglesIn); + + free(pEdges); + } + } +} + +///////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////// + +static tbool AssignRecur(const int piTriListIn[], STriInfo psTriInfos[], const int iMyTriIndex, SGroup * pGroup); +static void AddTriToGroup(SGroup * pGroup, const int iTriIndex); + +static int Build4RuleGroups(STriInfo pTriInfos[], SGroup pGroups[], int piGroupTrianglesBuffer[], const int piTriListIn[], const int iNrTrianglesIn) +{ + const int iNrMaxGroups = iNrTrianglesIn*3; + int iNrActiveGroups = 0; + int iOffset = 0, f=0, i=0; + (void)iNrMaxGroups; /* quiet warnings in non debug mode */ + for (f=0; fiVertexRepresentitive = vert_index; + pTriInfos[f].AssignedGroup[i]->bOrientPreservering = (pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0; + pTriInfos[f].AssignedGroup[i]->iNrFaces = 0; + pTriInfos[f].AssignedGroup[i]->pFaceIndices = &piGroupTrianglesBuffer[iOffset]; + ++iNrActiveGroups; + + AddTriToGroup(pTriInfos[f].AssignedGroup[i], f); + bOrPre = (pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; + neigh_indexL = pTriInfos[f].FaceNeighbors[i]; + neigh_indexR = pTriInfos[f].FaceNeighbors[i>0?(i-1):2]; + if (neigh_indexL>=0) // neighbor + { + const tbool bAnswer = + AssignRecur(piTriListIn, pTriInfos, neigh_indexL, + pTriInfos[f].AssignedGroup[i] ); + + const tbool bOrPre2 = (pTriInfos[neigh_indexL].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; + const tbool bDiff = bOrPre!=bOrPre2 ? TTRUE : TFALSE; + assert(bAnswer || bDiff); + (void)bAnswer, (void)bDiff; /* quiet warnings in non debug mode */ + } + if (neigh_indexR>=0) // neighbor + { + const tbool bAnswer = + AssignRecur(piTriListIn, pTriInfos, neigh_indexR, + pTriInfos[f].AssignedGroup[i] ); + + const tbool bOrPre2 = (pTriInfos[neigh_indexR].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; + const tbool bDiff = bOrPre!=bOrPre2 ? TTRUE : TFALSE; + assert(bAnswer || bDiff); + (void)bAnswer, (void)bDiff; /* quiet warnings in non debug mode */ + } + + // update offset + iOffset += pTriInfos[f].AssignedGroup[i]->iNrFaces; + // since the groups are disjoint a triangle can never + // belong to more than 3 groups. Subsequently something + // is completely screwed if this assertion ever hits. + assert(iOffset <= iNrMaxGroups); + } + } + } + + return iNrActiveGroups; +} + +static void AddTriToGroup(SGroup * pGroup, const int iTriIndex) +{ + pGroup->pFaceIndices[pGroup->iNrFaces] = iTriIndex; + ++pGroup->iNrFaces; +} + +static tbool AssignRecur(const int piTriListIn[], STriInfo psTriInfos[], + const int iMyTriIndex, SGroup * pGroup) +{ + STriInfo * pMyTriInfo = &psTriInfos[iMyTriIndex]; + + // track down vertex + const int iVertRep = pGroup->iVertexRepresentitive; + const int * pVerts = &piTriListIn[3*iMyTriIndex+0]; + int i=-1; + if (pVerts[0]==iVertRep) i=0; + else if (pVerts[1]==iVertRep) i=1; + else if (pVerts[2]==iVertRep) i=2; + assert(i>=0 && i<3); + + // early out + if (pMyTriInfo->AssignedGroup[i] == pGroup) return TTRUE; + else if (pMyTriInfo->AssignedGroup[i]!=NULL) return TFALSE; + if ((pMyTriInfo->iFlag&GROUP_WITH_ANY)!=0) + { + // first to group with a group-with-anything triangle + // determines it's orientation. + // This is the only existing order dependency in the code!! + if ( pMyTriInfo->AssignedGroup[0] == NULL && + pMyTriInfo->AssignedGroup[1] == NULL && + pMyTriInfo->AssignedGroup[2] == NULL ) + { + pMyTriInfo->iFlag &= (~ORIENT_PRESERVING); + pMyTriInfo->iFlag |= (pGroup->bOrientPreservering ? ORIENT_PRESERVING : 0); + } + } + { + const tbool bOrient = (pMyTriInfo->iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; + if (bOrient != pGroup->bOrientPreservering) return TFALSE; + } + + AddTriToGroup(pGroup, iMyTriIndex); + pMyTriInfo->AssignedGroup[i] = pGroup; + + { + const int neigh_indexL = pMyTriInfo->FaceNeighbors[i]; + const int neigh_indexR = pMyTriInfo->FaceNeighbors[i>0?(i-1):2]; + if (neigh_indexL>=0) + AssignRecur(piTriListIn, psTriInfos, neigh_indexL, pGroup); + if (neigh_indexR>=0) + AssignRecur(piTriListIn, psTriInfos, neigh_indexR, pGroup); + } + + + + return TTRUE; +} + +///////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////// + +static tbool CompareSubGroups(const SSubGroup * pg1, const SSubGroup * pg2); +static void QuickSort(int* pSortBuffer, int iLeft, int iRight, unsigned int uSeed); +static STSpace EvalTspace(int face_indices[], const int iFaces, const int piTriListIn[], const STriInfo pTriInfos[], const SMikkTSpaceContext * pContext, const int iVertexRepresentitive); + +static tbool GenerateTSpaces(STSpace psTspace[], const STriInfo pTriInfos[], const SGroup pGroups[], + const int iNrActiveGroups, const int piTriListIn[], const float fThresCos, + const SMikkTSpaceContext * pContext) +{ + STSpace * pSubGroupTspace = NULL; + SSubGroup * pUniSubGroups = NULL; + int * pTmpMembers = NULL; + int iMaxNrFaces=0, iUniqueTspaces=0, g=0, i=0; + for (g=0; giNrFaces; i++) // triangles + { + const int f = pGroup->pFaceIndices[i]; // triangle number + int index=-1, iVertIndex=-1, iOF_1=-1, iMembers=0, j=0, l=0; + SSubGroup tmp_group; + tbool bFound; + SVec3 n, vOs, vOt; + if (pTriInfos[f].AssignedGroup[0]==pGroup) index=0; + else if (pTriInfos[f].AssignedGroup[1]==pGroup) index=1; + else if (pTriInfos[f].AssignedGroup[2]==pGroup) index=2; + assert(index>=0 && index<3); + + iVertIndex = piTriListIn[f*3+index]; + assert(iVertIndex==pGroup->iVertexRepresentitive); + + // is normalized already + n = GetNormal(pContext, iVertIndex); + + // project + vOs = vsub(pTriInfos[f].vOs, vscale(vdot(n,pTriInfos[f].vOs), n)); + vOt = vsub(pTriInfos[f].vOt, vscale(vdot(n,pTriInfos[f].vOt), n)); + if ( VNotZero(vOs) ) vOs = Normalize(vOs); + if ( VNotZero(vOt) ) vOt = Normalize(vOt); + + // original face number + iOF_1 = pTriInfos[f].iOrgFaceNumber; + + iMembers = 0; + for (j=0; jiNrFaces; j++) + { + const int t = pGroup->pFaceIndices[j]; // triangle number + const int iOF_2 = pTriInfos[t].iOrgFaceNumber; + + // project + SVec3 vOs2 = vsub(pTriInfos[t].vOs, vscale(vdot(n,pTriInfos[t].vOs), n)); + SVec3 vOt2 = vsub(pTriInfos[t].vOt, vscale(vdot(n,pTriInfos[t].vOt), n)); + if ( VNotZero(vOs2) ) vOs2 = Normalize(vOs2); + if ( VNotZero(vOt2) ) vOt2 = Normalize(vOt2); + + { + const tbool bAny = ( (pTriInfos[f].iFlag | pTriInfos[t].iFlag) & GROUP_WITH_ANY )!=0 ? TTRUE : TFALSE; + // make sure triangles which belong to the same quad are joined. + const tbool bSameOrgFace = iOF_1==iOF_2 ? TTRUE : TFALSE; + + const float fCosS = vdot(vOs,vOs2); + const float fCosT = vdot(vOt,vOt2); + + assert(f!=t || bSameOrgFace); // sanity check + if (bAny || bSameOrgFace || (fCosS>fThresCos && fCosT>fThresCos)) + pTmpMembers[iMembers++] = t; + } + } + + // sort pTmpMembers + tmp_group.iNrFaces = iMembers; + tmp_group.pTriMembers = pTmpMembers; + if (iMembers>1) + { + unsigned int uSeed = INTERNAL_RND_SORT_SEED; // could replace with a random seed? + QuickSort(pTmpMembers, 0, iMembers-1, uSeed); + } + + // look for an existing match + bFound = TFALSE; + l=0; + while (liVertexRepresentitive); + ++iUniqueSubGroups; + } + + // output tspace + { + const int iOffs = pTriInfos[f].iTSpacesOffs; + const int iVert = pTriInfos[f].vert_num[index]; + STSpace * pTS_out = &psTspace[iOffs+iVert]; + assert(pTS_out->iCounter<2); + assert(((pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0) == pGroup->bOrientPreservering); + if (pTS_out->iCounter==1) + { + *pTS_out = AvgTSpace(pTS_out, &pSubGroupTspace[l]); + pTS_out->iCounter = 2; // update counter + pTS_out->bOrient = pGroup->bOrientPreservering; + } + else + { + assert(pTS_out->iCounter==0); + *pTS_out = pSubGroupTspace[l]; + pTS_out->iCounter = 1; // update counter + pTS_out->bOrient = pGroup->bOrientPreservering; + } + } + } + + // clean up and offset iUniqueTspaces + for (s=0; s=0 && i<3); + + // project + index = piTriListIn[3*f+i]; + n = GetNormal(pContext, index); + vOs = vsub(pTriInfos[f].vOs, vscale(vdot(n,pTriInfos[f].vOs), n)); + vOt = vsub(pTriInfos[f].vOt, vscale(vdot(n,pTriInfos[f].vOt), n)); + if ( VNotZero(vOs) ) vOs = Normalize(vOs); + if ( VNotZero(vOt) ) vOt = Normalize(vOt); + + i2 = piTriListIn[3*f + (i<2?(i+1):0)]; + i1 = piTriListIn[3*f + i]; + i0 = piTriListIn[3*f + (i>0?(i-1):2)]; + + p0 = GetPosition(pContext, i0); + p1 = GetPosition(pContext, i1); + p2 = GetPosition(pContext, i2); + v1 = vsub(p0,p1); + v2 = vsub(p2,p1); + + // project + v1 = vsub(v1, vscale(vdot(n,v1),n)); if ( VNotZero(v1) ) v1 = Normalize(v1); + v2 = vsub(v2, vscale(vdot(n,v2),n)); if ( VNotZero(v2) ) v2 = Normalize(v2); + + // weight contribution by the angle + // between the two edge vectors + fCos = vdot(v1,v2); fCos=fCos>1?1:(fCos<(-1) ? (-1) : fCos); + fAngle = (float) acos(fCos); + fMagS = pTriInfos[f].fMagS; + fMagT = pTriInfos[f].fMagT; + + res.vOs=vadd(res.vOs, vscale(fAngle,vOs)); + res.vOt=vadd(res.vOt,vscale(fAngle,vOt)); + res.fMagS+=(fAngle*fMagS); + res.fMagT+=(fAngle*fMagT); + fAngleSum += fAngle; + } + } + + // normalize + if ( VNotZero(res.vOs) ) res.vOs = Normalize(res.vOs); + if ( VNotZero(res.vOt) ) res.vOt = Normalize(res.vOt); + if (fAngleSum>0) + { + res.fMagS /= fAngleSum; + res.fMagT /= fAngleSum; + } + + return res; +} + +static tbool CompareSubGroups(const SSubGroup * pg1, const SSubGroup * pg2) +{ + tbool bStillSame=TTRUE; + int i=0; + if (pg1->iNrFaces!=pg2->iNrFaces) return TFALSE; + while (iiNrFaces && bStillSame) + { + bStillSame = pg1->pTriMembers[i]==pg2->pTriMembers[i] ? TTRUE : TFALSE; + if (bStillSame) ++i; + } + return bStillSame; +} + +static void QuickSort(int* pSortBuffer, int iLeft, int iRight, unsigned int uSeed) +{ + int iL, iR, n, index, iMid, iTmp; + + // Random + unsigned int t=uSeed&31; + t=(uSeed<>(32-t)); + uSeed=uSeed+t+3; + // Random end + + iL=iLeft; iR=iRight; + n = (iR-iL)+1; + assert(n>=0); + index = (int) (uSeed%n); + + iMid=pSortBuffer[index + iL]; + + + do + { + while (pSortBuffer[iL] < iMid) + ++iL; + while (pSortBuffer[iR] > iMid) + --iR; + + if (iL <= iR) + { + iTmp = pSortBuffer[iL]; + pSortBuffer[iL] = pSortBuffer[iR]; + pSortBuffer[iR] = iTmp; + ++iL; --iR; + } + } + while (iL <= iR); + + if (iLeft < iR) + QuickSort(pSortBuffer, iLeft, iR, uSeed); + if (iL < iRight) + QuickSort(pSortBuffer, iL, iRight, uSeed); +} + +///////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////// + +static void QuickSortEdges(SEdge * pSortBuffer, int iLeft, int iRight, const int channel, unsigned int uSeed); +static void GetEdge(int * i0_out, int * i1_out, int * edgenum_out, const int indices[], const int i0_in, const int i1_in); + +static void BuildNeighborsFast(STriInfo pTriInfos[], SEdge * pEdges, const int piTriListIn[], const int iNrTrianglesIn) +{ + // build array of edges + unsigned int uSeed = INTERNAL_RND_SORT_SEED; // could replace with a random seed? + int iEntries=0, iCurStartIndex=-1, f=0, i=0; + for (f=0; f pSortBuffer[iRight].array[channel]) + { + sTmp = pSortBuffer[iLeft]; + pSortBuffer[iLeft] = pSortBuffer[iRight]; + pSortBuffer[iRight] = sTmp; + } + return; + } + + // Random + t=uSeed&31; + t=(uSeed<>(32-t)); + uSeed=uSeed+t+3; + // Random end + + iL=iLeft, iR=iRight; + n = (iR-iL)+1; + assert(n>=0); + index = (int) (uSeed%n); + + iMid=pSortBuffer[index + iL].array[channel]; + + do + { + while (pSortBuffer[iL].array[channel] < iMid) + ++iL; + while (pSortBuffer[iR].array[channel] > iMid) + --iR; + + if (iL <= iR) + { + sTmp = pSortBuffer[iL]; + pSortBuffer[iL] = pSortBuffer[iR]; + pSortBuffer[iR] = sTmp; + ++iL; --iR; + } + } + while (iL <= iR); + + if (iLeft < iR) + QuickSortEdges(pSortBuffer, iLeft, iR, channel, uSeed); + if (iL < iRight) + QuickSortEdges(pSortBuffer, iL, iRight, channel, uSeed); +} + +// resolve ordering and edge number +static void GetEdge(int * i0_out, int * i1_out, int * edgenum_out, const int indices[], const int i0_in, const int i1_in) +{ + *edgenum_out = -1; + + // test if first index is on the edge + if (indices[0]==i0_in || indices[0]==i1_in) + { + // test if second index is on the edge + if (indices[1]==i0_in || indices[1]==i1_in) + { + edgenum_out[0]=0; // first edge + i0_out[0]=indices[0]; + i1_out[0]=indices[1]; + } + else + { + edgenum_out[0]=2; // third edge + i0_out[0]=indices[2]; + i1_out[0]=indices[0]; + } + } + else + { + // only second and third index is on the edge + edgenum_out[0]=1; // second edge + i0_out[0]=indices[1]; + i1_out[0]=indices[2]; + } +} + + +///////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////// Degenerate triangles //////////////////////////////////// + +static void DegenPrologue(STriInfo pTriInfos[], int piTriList_out[], const int iNrTrianglesIn, const int iTotTris) +{ + int iNextGoodTriangleSearchIndex=-1; + tbool bStillFindingGoodOnes; + + // locate quads with only one good triangle + int t=0; + while (t<(iTotTris-1)) + { + const int iFO_a = pTriInfos[t].iOrgFaceNumber; + const int iFO_b = pTriInfos[t+1].iOrgFaceNumber; + if (iFO_a==iFO_b) // this is a quad + { + const tbool bIsDeg_a = (pTriInfos[t].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; + const tbool bIsDeg_b = (pTriInfos[t+1].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; + if ((bIsDeg_a^bIsDeg_b)!=0) + { + pTriInfos[t].iFlag |= QUAD_ONE_DEGEN_TRI; + pTriInfos[t+1].iFlag |= QUAD_ONE_DEGEN_TRI; + } + t += 2; + } + else + ++t; + } + + // reorder list so all degen triangles are moved to the back + // without reordering the good triangles + iNextGoodTriangleSearchIndex = 1; + t=0; + bStillFindingGoodOnes = TTRUE; + while (t (t+1)); + + // swap triangle t0 and t1 + if (!bJustADegenerate) + { + int i=0; + for (i=0; i<3; i++) + { + const int index = piTriList_out[t0*3+i]; + piTriList_out[t0*3+i] = piTriList_out[t1*3+i]; + piTriList_out[t1*3+i] = index; + } + { + const STriInfo tri_info = pTriInfos[t0]; + pTriInfos[t0] = pTriInfos[t1]; + pTriInfos[t1] = tri_info; + } + } + else + bStillFindingGoodOnes = TFALSE; // this is not supposed to happen + } + + if (bStillFindingGoodOnes) ++t; + } + + assert(bStillFindingGoodOnes); // code will still work. + assert(iNrTrianglesIn == t); +} + +static void DegenEpilogue(STSpace psTspace[], STriInfo pTriInfos[], int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn, const int iTotTris) +{ + int t=0, i=0; + // deal with degenerate triangles + // punishment for degenerate triangles is O(N^2) + for (t=iNrTrianglesIn; t http://image.diku.dk/projects/media/morten.mikkelsen.08.pdf + * Note that though the tangent spaces at the vertices are generated in an order-independent way, + * by this implementation, the interpolated tangent space is still affected by which diagonal is + * chosen to split each quad. A sensible solution is to have your tools pipeline always + * split quads by the shortest diagonal. This choice is order-independent and works with mirroring. + * If these have the same length then compare the diagonals defined by the texture coordinates. + * XNormal which is a tool for baking normal maps allows you to write your own tangent space plugin + * and also quad triangulator plugin. + */ + + +typedef int tbool; +typedef struct SMikkTSpaceContext SMikkTSpaceContext; + +typedef struct { + // Returns the number of faces (triangles/quads) on the mesh to be processed. + int (*m_getNumFaces)(const SMikkTSpaceContext * pContext); + + // Returns the number of vertices on face number iFace + // iFace is a number in the range {0, 1, ..., getNumFaces()-1} + int (*m_getNumVerticesOfFace)(const SMikkTSpaceContext * pContext, const int iFace); + + // returns the position/normal/texcoord of the referenced face of vertex number iVert. + // iVert is in the range {0,1,2} for triangles and {0,1,2,3} for quads. + void (*m_getPosition)(const SMikkTSpaceContext * pContext, float fvPosOut[], const int iFace, const int iVert); + void (*m_getNormal)(const SMikkTSpaceContext * pContext, float fvNormOut[], const int iFace, const int iVert); + void (*m_getTexCoord)(const SMikkTSpaceContext * pContext, float fvTexcOut[], const int iFace, const int iVert); + + // either (or both) of the two setTSpace callbacks can be set. + // The call-back m_setTSpaceBasic() is sufficient for basic normal mapping. + + // This function is used to return the tangent and fSign to the application. + // fvTangent is a unit length vector. + // For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level. + // bitangent = fSign * cross(vN, tangent); + // Note that the results are returned unindexed. It is possible to generate a new index list + // But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results. + // DO NOT! use an already existing index list. + void (*m_setTSpaceBasic)(const SMikkTSpaceContext * pContext, const float fvTangent[], const float fSign, const int iFace, const int iVert); + + // This function is used to return tangent space results to the application. + // fvTangent and fvBiTangent are unit length vectors and fMagS and fMagT are their + // true magnitudes which can be used for relief mapping effects. + // fvBiTangent is the "real" bitangent and thus may not be perpendicular to fvTangent. + // However, both are perpendicular to the vertex normal. + // For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level. + // fSign = bIsOrientationPreserving ? 1.0f : (-1.0f); + // bitangent = fSign * cross(vN, tangent); + // Note that the results are returned unindexed. It is possible to generate a new index list + // But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results. + // DO NOT! use an already existing index list. + void (*m_setTSpace)(const SMikkTSpaceContext * pContext, const float fvTangent[], const float fvBiTangent[], const float fMagS, const float fMagT, + const tbool bIsOrientationPreserving, const int iFace, const int iVert); +} SMikkTSpaceInterface; + +struct SMikkTSpaceContext +{ + SMikkTSpaceInterface * m_pInterface; // initialized with callback functions + void * m_pUserData; // pointer to client side mesh data etc. (passed as the first parameter with every interface call) +}; + +// these are both thread safe! +tbool genTangSpaceDefault(const SMikkTSpaceContext * pContext); // Default (recommended) fAngularThreshold is 180 degrees (which means threshold disabled) +tbool genTangSpace(const SMikkTSpaceContext * pContext, const float fAngularThreshold); + + +// To avoid visual errors (distortions/unwanted hard edges in lighting), when using sampled normal maps, the +// normal map sampler must use the exact inverse of the pixel shader transformation. +// The most efficient transformation we can possibly do in the pixel shader is +// achieved by using, directly, the "unnormalized" interpolated tangent, bitangent and vertex normal: vT, vB and vN. +// pixel shader (fast transform out) +// vNout = normalize( vNt.x * vT + vNt.y * vB + vNt.z * vN ); +// where vNt is the tangent space normal. The normal map sampler must likewise use the +// interpolated and "unnormalized" tangent, bitangent and vertex normal to be compliant with the pixel shader. +// sampler does (exact inverse of pixel shader): +// float3 row0 = cross(vB, vN); +// float3 row1 = cross(vN, vT); +// float3 row2 = cross(vT, vB); +// float fSign = dot(vT, row0)<0 ? -1 : 1; +// vNt = normalize( fSign * float3(dot(vNout,row0), dot(vNout,row1), dot(vNout,row2)) ); +// where vNout is the sampled normal in some chosen 3D space. +// +// Should you choose to reconstruct the bitangent in the pixel shader instead +// of the vertex shader, as explained earlier, then be sure to do this in the normal map sampler also. +// Finally, beware of quad triangulations. If the normal map sampler doesn't use the same triangulation of +// quads as your renderer then problems will occur since the interpolated tangent spaces will differ +// eventhough the vertex level tangent spaces match. This can be solved either by triangulating before +// sampling/exporting or by using the order-independent choice of diagonal for splitting quads suggested earlier. +// However, this must be used both by the sampler and your tools/rendering pipeline. + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/plugin/hdCycles/api.h b/plugin/hdCycles/api.h new file mode 100644 index 00000000..600466df --- /dev/null +++ b/plugin/hdCycles/api.h @@ -0,0 +1,42 @@ +// Copyright 2020 Tangent Animation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef HD_CYCLES_API_H +#define HD_CYCLES_API_H + +#include + +#if defined(PXR_STATIC) +# define HDCYCLES_API +# define HDCYCLES_API_TEMPLATE_CLASS(...) +# define HDCYCLES_API_TEMPLATE_STRUCT(...) +# define HDCYCLES_LOCAL +#else +# if defined(HDCYCLES_EXPORTS) +# define HDCYCLES_API ARCH_EXPORT +# define HDCYCLES_API_TEMPLATE_CLASS(...) \ + ARCH_EXPORT_TEMPLATE(class, __VA_ARGS__) +# define HDCYCLES_API_TEMPLATE_STRUCT(...) \ + ARCH_EXPORT_TEMPLATE(struct, __VA_ARGS__) +# else +# define HDCYCLES_API ARCH_IMPORT +# define HDCYCLES_API_TEMPLATE_CLASS(...) \ + ARCH_IMPORT_TEMPLATE(class, __VA_ARGS__) +# define HDCYCLES_API_TEMPLATE_STRUCT(...) \ + ARCH_IMPORT_TEMPLATE(struct, __VA_ARGS__) +# endif +# define HDCYCLES_LOCAL ARCH_HIDDEN +#endif + +#endif // HD_CYCLES_API_H \ No newline at end of file diff --git a/plugin/hdCycles/basisCurves.cpp b/plugin/hdCycles/basisCurves.cpp new file mode 100644 index 00000000..012a7e70 --- /dev/null +++ b/plugin/hdCycles/basisCurves.cpp @@ -0,0 +1,659 @@ +// Copyright 2020 Tangent Animation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "basisCurves.h" + +#include "config.h" +#include "material.h" +#include "renderParam.h" +#include "utils.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +// TODO: Read these from usdCycles schema +// clang-format off +TF_DEFINE_PRIVATE_TOKENS(_tokens, + ((cyclesCurveStyle, "cycles:object:curve_style")) + ((cyclesCurveResolution, "cycles:object:curve_resolution")) +); +// clang-format on + +HdCyclesBasisCurves::HdCyclesBasisCurves( + SdfPath const& id, SdfPath const& instancerId, + HdCyclesRenderDelegate* a_renderDelegate) + : HdBasisCurves(id, instancerId) + , m_cyclesObject(nullptr) + , m_cyclesMesh(nullptr) + , m_cyclesGeometry(nullptr) + , m_cyclesHair(nullptr) + , m_curveStyle(CURVE_TUBE) + , m_curveResolution(5) + , m_renderDelegate(a_renderDelegate) +{ + static const HdCyclesConfig& config = HdCyclesConfig::GetInstance(); + m_useMotionBlur = config.enable_motion_blur; + + m_numTransformSamples = HD_CYCLES_MOTION_STEPS; + + if (m_useMotionBlur) { + m_motionSteps = m_numTransformSamples; + } + + m_cyclesObject = _CreateObject(); + m_renderDelegate->GetCyclesRenderParam()->AddObject(m_cyclesObject); +} + +HdCyclesBasisCurves::~HdCyclesBasisCurves() +{ + if (m_cyclesHair) { + m_renderDelegate->GetCyclesRenderParam()->RemoveCurve(m_cyclesHair); + delete m_cyclesHair; + } + if (m_cyclesMesh) { + m_renderDelegate->GetCyclesRenderParam()->RemoveMesh(m_cyclesMesh); + delete m_cyclesMesh; + } + if (m_cyclesObject) { + m_renderDelegate->GetCyclesRenderParam()->RemoveObject(m_cyclesObject); + delete m_cyclesObject; + } +} + +void +HdCyclesBasisCurves::_InitRepr(TfToken const& reprToken, HdDirtyBits* dirtyBits) +{ +} + +HdDirtyBits +HdCyclesBasisCurves::_PropagateDirtyBits(HdDirtyBits bits) const +{ + return bits; +} + +void +HdCyclesBasisCurves::Finalize(HdRenderParam* renderParam) +{ +} + +ccl::Object* +HdCyclesBasisCurves::_CreateObject() +{ + // Create container object + ccl::Object* object = new ccl::Object(); + + object->visibility = ccl::PATH_RAY_ALL_VISIBILITY; + + return object; +} + +void +HdCyclesBasisCurves::_PopulateCurveMesh(HdRenderParam* renderParam) +{ + ccl::Scene* scene = ((HdCyclesRenderParam*)renderParam)->GetCyclesScene(); + + static const HdCyclesConfig& config = HdCyclesConfig::GetInstance(); + + if (config.use_old_curves) { + if (m_curveStyle == CURVE_RIBBONS) { + _CreateRibbons(scene->camera); + } else { + _CreateTubeMesh(); + } + } else { + _CreateCurves(scene); + } +} + +void +HdCyclesBasisCurves::Sync(HdSceneDelegate* sceneDelegate, + HdRenderParam* renderParam, HdDirtyBits* dirtyBits, + TfToken const& reprSelector) +{ + SdfPath const& id = GetId(); + + HdCyclesRenderParam* param = (HdCyclesRenderParam*)renderParam; + + ccl::Scene* scene = param->GetCyclesScene(); + + HdCyclesPDPIMap pdpi; + bool generate_new_curve = false; + bool update_curve = false; + + if (*dirtyBits & HdChangeTracker::DirtyPoints) { + HdCyclesPopulatePrimvarDescsPerInterpolation(sceneDelegate, id, &pdpi); + if (HdCyclesIsPrimvarExists(HdTokens->points, pdpi)) { + m_points + = sceneDelegate->Get(id, HdTokens->points).Get(); + generate_new_curve = true; + } else { + m_points = VtVec3fArray(); + } + } + + if (*dirtyBits & HdChangeTracker::DirtyNormals) { + HdCyclesPopulatePrimvarDescsPerInterpolation(sceneDelegate, id, &pdpi); + if (HdCyclesIsPrimvarExists(HdTokens->normals, pdpi)) { + m_normals + = sceneDelegate->Get(id, HdTokens->normals).Get(); + generate_new_curve = true; + } else { + m_normals = VtVec3fArray(); + } + } + + if (*dirtyBits & HdChangeTracker::DirtyTopology) { + m_topology = sceneDelegate->GetBasisCurvesTopology(id); + m_indices = VtIntArray(); + if (m_topology.HasIndices()) { + m_indices = m_topology.GetCurveIndices(); + } + generate_new_curve = true; + } + + if (*dirtyBits & HdChangeTracker::DirtyWidths) { + HdCyclesPopulatePrimvarDescsPerInterpolation(sceneDelegate, id, &pdpi); + if (HdCyclesIsPrimvarExists(HdTokens->widths, pdpi, + &m_widthsInterpolation)) { + m_widths + = sceneDelegate->Get(id, HdTokens->widths).Get(); + } else { + m_widths = VtFloatArray(1, 0.1f); + m_widthsInterpolation = HdInterpolationConstant; + TF_WARN( + "[%s] Curve do not have widths. Fallback value is 1.0f with a constant interpolation", + id.GetText()); + } + generate_new_curve = true; + } + + if (*dirtyBits & HdChangeTracker::DirtyPrimvar) { + HdCyclesPopulatePrimvarDescsPerInterpolation(sceneDelegate, id, &pdpi); + if (HdCyclesIsPrimvarExists(_tokens->cyclesCurveStyle, pdpi)) { + VtIntArray type = sceneDelegate->Get(id, _tokens->cyclesCurveStyle) + .Get(); + if (type.size() > 0) { + if (type[0] == 0) + m_curveStyle = CURVE_RIBBONS; + else + m_curveStyle = CURVE_TUBE; + } + } else { + m_curveStyle = CURVE_TUBE; + } + + if (HdCyclesIsPrimvarExists(_tokens->cyclesCurveResolution, pdpi)) { + VtIntArray resolution + = sceneDelegate->Get(id, _tokens->cyclesCurveResolution) + .Get(); + if (resolution.size() > 0) { + m_curveResolution = resolution[0]; + } + } else { + m_curveResolution = 5; + } + } + + if (*dirtyBits & HdChangeTracker::DirtyVisibility) { + update_curve = true; + if (sceneDelegate->GetVisible(id)) { + m_cyclesObject->visibility |= ccl::PATH_RAY_ALL_VISIBILITY; + } else { + m_cyclesObject->visibility &= ~ccl::PATH_RAY_ALL_VISIBILITY; + } + } + + if (*dirtyBits & HdChangeTracker::DirtyTransform) { + m_transformSamples = HdCyclesSetTransform(m_cyclesObject, sceneDelegate, + id, m_useMotionBlur); + + generate_new_curve = true; + } + + if (generate_new_curve) { + _PopulateCurveMesh(param); + + if (m_cyclesGeometry) { + m_cyclesObject->geometry = m_cyclesGeometry; + + m_cyclesGeometry->compute_bounds(); + m_cyclesGeometry->tag_update(scene, true); + + param->AddCurve(m_cyclesGeometry); + } + } + + if (*dirtyBits & HdChangeTracker::DirtyMaterialId) { + if (m_cyclesGeometry) { + // Add default shader + const SdfPath& materialId = sceneDelegate->GetMaterialId(GetId()); + const HdCyclesMaterial* material + = static_cast( + sceneDelegate->GetRenderIndex().GetSprim( + HdPrimTypeTokens->material, materialId)); + + if (material && material->GetCyclesShader()) { + m_cyclesGeometry->used_shaders.push_back( + material->GetCyclesShader()); + material->GetCyclesShader()->tag_update(scene); + } else { + m_cyclesGeometry->used_shaders.push_back( + scene->default_surface); + } + update_curve = true; + } + } + + if (generate_new_curve || update_curve) + param->Interrupt(); + + *dirtyBits = HdChangeTracker::Clean; +} + +void +HdCyclesBasisCurves::_CreateCurves(ccl::Scene* a_scene) +{ + m_cyclesHair = new ccl::Hair(); + m_cyclesGeometry = m_cyclesHair; + + // Get USD Curve Metadata + VtIntArray curveVertexCounts = m_topology.GetCurveVertexCounts(); + TfToken curveType = m_topology.GetCurveType(); + TfToken curveBasis = m_topology.GetCurveBasis(); + TfToken curveWrap = m_topology.GetCurveWrap(); + + int num_curves = curveVertexCounts.size(); + int num_keys = 0; + + for (int i = 0; i < num_curves; i++) { + num_keys += curveVertexCounts[i]; + } + + ccl::Attribute* attr_intercept = NULL; + ccl::Attribute* attr_random = NULL; + + if (m_cyclesHair->need_attribute(a_scene, ccl::ATTR_STD_CURVE_INTERCEPT)) + attr_intercept = m_cyclesHair->attributes.add( + ccl::ATTR_STD_CURVE_INTERCEPT); + if (m_cyclesHair->need_attribute(a_scene, ccl::ATTR_STD_CURVE_RANDOM)) + attr_random = m_cyclesHair->attributes.add(ccl::ATTR_STD_CURVE_RANDOM); + + + m_cyclesHair->reserve_curves(num_curves, num_keys); + + num_curves = 0; + num_keys = 0; + + int currentPointCount = 0; + + // For every curve + for (int i = 0; i < curveVertexCounts.size(); i++) { + size_t num_curve_keys = 0; + + // For every section + for (int j = 0; j < curveVertexCounts[i]; j++) { + int idx = j + currentPointCount; + + const float time = (float)j / (float)curveVertexCounts[i]; + + if (idx > m_points.size()) { + TF_WARN("Attempted to access invalid point. Continuing"); + continue; + } + + ccl::float3 usd_location = vec3f_to_float3(m_points[idx]); + + float radius = 0.1f; + if (idx < m_widths.size()) + radius = m_widths[idx]; + + m_cyclesHair->add_curve_key(usd_location, radius); + + if (attr_intercept) + attr_intercept->add(time); + + num_curve_keys++; + } + + if (attr_random != NULL) { + attr_random->add(ccl::hash_uint2_to_float(num_curves, 0)); + } + + m_cyclesHair->add_curve(num_keys, 0); + num_keys += num_curve_keys; + currentPointCount += curveVertexCounts[i]; + num_curves++; + } + + if ((m_cyclesHair->curve_keys.size() != num_keys) + || (m_cyclesHair->num_curves() != num_curves)) { + TF_WARN("Allocation failed. Clearing data"); + + m_cyclesHair->clear(); + } +} + +void +HdCyclesBasisCurves::_CreateRibbons(ccl::Camera* a_camera) +{ + m_cyclesMesh = new ccl::Mesh(); + m_cyclesGeometry = m_cyclesMesh; + + bool isCameraOriented = false; + + ccl::float3 RotCam; + bool is_ortho; + if (m_normals.size() <= 0) { + if (a_camera != nullptr) { + isCameraOriented = true; + ccl::Transform& ctfm = a_camera->matrix; + if (a_camera->type == ccl::CAMERA_ORTHOGRAPHIC) { + RotCam = -ccl::make_float3(ctfm.x.z, ctfm.y.z, ctfm.z.z); + } else { + ccl::Transform tfm = m_cyclesObject->tfm; + ccl::Transform itfm = ccl::transform_quick_inverse(tfm); + RotCam = ccl::transform_point( + &itfm, ccl::make_float3(ctfm.x.w, ctfm.y.w, ctfm.z.w)); + } + is_ortho = a_camera->type == ccl::CAMERA_ORTHOGRAPHIC; + } + } + + // Get USD Curve Metadata + VtIntArray curveVertexCounts = m_topology.GetCurveVertexCounts(); + TfToken curveType = m_topology.GetCurveType(); + TfToken curveBasis = m_topology.GetCurveBasis(); + TfToken curveWrap = m_topology.GetCurveWrap(); + + int num_vertices = 0; + int num_tris = 0; + for (int i = 0; i < curveVertexCounts.size(); i++) { + num_vertices += curveVertexCounts[i] * 2; + num_tris += ((curveVertexCounts[i] - 1) * 2); + } + + // Start Cycles Mesh population + int vertexindex = 0; + + m_cyclesMesh->reserve_mesh(num_vertices, num_tris); + + // For every curve + for (int i = 0; i < curveVertexCounts.size(); i++) { + ccl::float3 xbasis; + ccl::float3 v1; + + ccl::float3 ickey_loc = vec3f_to_float3(m_points[0]); + + float radius = 0.1f; + if (m_widths.size() > 0) + radius = m_widths[0]; + + v1 = vec3f_to_float3(m_points[1] - m_points[0]); + if (isCameraOriented) { + if (is_ortho) + xbasis = normalize(cross(RotCam, v1)); + else + xbasis = ccl::normalize(ccl::cross(RotCam - ickey_loc, v1)); + } else { + if (m_normals.size() > 0) + xbasis = ccl::normalize(vec3f_to_float3(m_normals[0])); + else + xbasis = ccl::normalize(ccl::cross(ickey_loc, v1)); + } + ccl::float3 ickey_loc_shfl = ickey_loc - radius * xbasis; + ccl::float3 ickey_loc_shfr = ickey_loc + radius * xbasis; + m_cyclesMesh->add_vertex(ickey_loc_shfl); + m_cyclesMesh->add_vertex(ickey_loc_shfr); + vertexindex += 2; + + // For every section + for (int j = 0; j < curveVertexCounts[i]; j++) { + int first_idx = (i * curveVertexCounts[i]); + int idx = j + (i * curveVertexCounts[i]); + + ickey_loc = vec3f_to_float3(m_points[idx]); + + if (j == 0) { + // subv = 0; + // First curve point + v1 = vec3f_to_float3(m_points[idx] + - m_points[std::max(idx - 1, first_idx)]); + } else { + v1 = vec3f_to_float3(m_points[idx + 1] - m_points[idx - 1]); + } + + + float radius = 0.1f; + if (idx < m_widths.size()) + radius = m_widths[idx]; + + if (isCameraOriented) { + if (is_ortho) + xbasis = normalize(cross(RotCam, v1)); + else + xbasis = ccl::normalize(ccl::cross(RotCam - ickey_loc, v1)); + } else { + if (m_normals.size() > 0) + xbasis = ccl::normalize(vec3f_to_float3(m_normals[idx])); + else + xbasis = ccl::normalize(ccl::cross(ickey_loc, v1)); + } + ccl::float3 ickey_loc_shfl = ickey_loc - radius * xbasis; + ccl::float3 ickey_loc_shfr = ickey_loc + radius * xbasis; + m_cyclesMesh->add_vertex(ickey_loc_shfl); + m_cyclesMesh->add_vertex(ickey_loc_shfr); + m_cyclesMesh->add_triangle(vertexindex - 2, vertexindex, + vertexindex - 1, 0, true); + m_cyclesMesh->add_triangle(vertexindex + 1, vertexindex - 1, + vertexindex, 0, true); + vertexindex += 2; + } + } + + // TODO: Implement texcoords +} + +void +HdCyclesBasisCurves::_CreateTubeMesh() +{ + m_cyclesMesh = new ccl::Mesh(); + m_cyclesGeometry = m_cyclesMesh; + + // Get USD Curve Metadata + VtIntArray curveVertexCounts = m_topology.GetCurveVertexCounts(); + TfToken curveType = m_topology.GetCurveType(); + TfToken curveBasis = m_topology.GetCurveBasis(); + TfToken curveWrap = m_topology.GetCurveWrap(); + + int num_vertices = 0; + int num_tris = 0; + for (int i = 0; i < curveVertexCounts.size(); i++) { + num_vertices += curveVertexCounts[i] * m_curveResolution; + num_tris += ((curveVertexCounts[i] - 1) * 2 * m_curveResolution); + } + + // Start Cycles Mesh population + int vertexindex = m_curveResolution; + + m_cyclesMesh->reserve_mesh(num_vertices, num_tris); + + // For every curve + for (int i = 0; i < curveVertexCounts.size(); i++) { + int subv = 1; + + ccl::float3 firstxbasis = ccl::cross(ccl::make_float3(1.0f, 0.0f, 0.0f), + vec3f_to_float3(m_points[1]) + - vec3f_to_float3(m_points[0])); + + if (!ccl::is_zero(firstxbasis)) + firstxbasis = ccl::normalize(firstxbasis); + else + firstxbasis = ccl::normalize( + ccl::cross(ccl::make_float3(0.0f, 1.0f, 0.0f), + vec3f_to_float3(m_points[1]) + - vec3f_to_float3(m_points[0]))); + + // For every section + for (int j = 0; j < curveVertexCounts[i]; j++) { + int first_idx = (i * curveVertexCounts[i]); + int idx = j + (i * curveVertexCounts[i]); + + ccl::float3 xbasis = firstxbasis; + ccl::float3 v1; + ccl::float3 v2; + + if (j == 0) { + // First curve point + v1 = vec3f_to_float3( + m_points[std::min(idx + 2, (curveVertexCounts[i] + + curveVertexCounts[i] - 1))]); + v2 = vec3f_to_float3(m_points[idx + 1] - m_points[idx]); + } else if (j == (curveVertexCounts[i] - 1)) { + // Last curve point + v1 = vec3f_to_float3(m_points[idx] - m_points[idx - 1]); + v2 = vec3f_to_float3( + m_points[idx - 1] + - m_points[std::max(idx - 2, first_idx)]); // First key + } else { + v1 = vec3f_to_float3(m_points[idx + 1] - m_points[idx]); + v2 = vec3f_to_float3(m_points[idx] - m_points[idx - 1]); + } + + xbasis = ccl::cross(v1, v2); + + if (ccl::len_squared(xbasis) + >= 0.05f * ccl::len_squared(v1) * ccl::len_squared(v2)) { + firstxbasis = ccl::normalize(xbasis); + break; + } + } + + // For every section + for (int j = 0; j < curveVertexCounts[i]; j++) { + int first_idx = (i * curveVertexCounts[i]); + int idx = j + (i * curveVertexCounts[i]); + ccl::float3 xbasis; + ccl::float3 ybasis; + ccl::float3 v1; + ccl::float3 v2; + + ccl::float3 usd_location = vec3f_to_float3(m_points[idx]); + + if (j == 0) { + // First curve point + v1 = vec3f_to_float3( + m_points[std::min(idx + 2, (curveVertexCounts[i] - 1))] + - m_points[idx + 1]); + v2 = vec3f_to_float3(m_points[idx + 1] - m_points[idx]); + } else if (j == (curveVertexCounts[i] - 1)) { + v1 = vec3f_to_float3(m_points[idx] - m_points[idx - 1]); + v2 = vec3f_to_float3(m_points[idx - 1] + - m_points[std::max(idx - 2, first_idx)]); + } else { + v1 = vec3f_to_float3(m_points[idx + 1] - m_points[idx]); + v1 = vec3f_to_float3(m_points[idx] - m_points[idx - 1]); + } + + // Add vertex in circle + float radius = 0.1f; + if (idx < m_widths.size()) + radius = m_widths[idx]; + + float angle = M_2PI_F / (float)m_curveResolution; + + xbasis = ccl::cross(v1, v2); + + if (ccl::len_squared(xbasis) + >= 0.05f * ccl::len_squared(v1) * ccl::len_squared(v2)) { + xbasis = ccl::normalize(xbasis); + firstxbasis = xbasis; + } else { + xbasis = firstxbasis; + } + + ybasis = ccl::normalize(ccl::cross(xbasis, v2)); + + // Add vertices + for (int k = 0; k < m_curveResolution; k++) { + ccl::float3 vertex_location = usd_location + + radius + * (cosf(angle * k) * xbasis + + sinf(angle * k) + * ybasis); + + m_cyclesMesh->add_vertex(vertex_location); + } + + if (j < curveVertexCounts[i] - 1) { + for (int k = 0; k < m_curveResolution - 1; k++) { + int t1 = vertexindex - m_curveResolution + k; + int t2 = vertexindex + k; + int t3 = vertexindex - m_curveResolution + k + 1; + + m_cyclesMesh->add_triangle(t1, t2, t3, 0, true); + + t1 = vertexindex + k + 1; + t2 = vertexindex - m_curveResolution + k + 1; + t3 = vertexindex + k; + + m_cyclesMesh->add_triangle(t1, t2, t3, 0, true); + } + int t1 = vertexindex - 1; + int t2 = vertexindex + m_curveResolution - 1; + int t3 = vertexindex - m_curveResolution; + + m_cyclesMesh->add_triangle(t1, t2, t3, 0, true); + + t1 = vertexindex; + t2 = vertexindex - m_curveResolution; + t3 = vertexindex + m_curveResolution - 1; + + m_cyclesMesh->add_triangle(t1, t2, t3, 0, true); + } + vertexindex += m_curveResolution; + } + } + + // TODO: Implement texcoords +} + +HdDirtyBits +HdCyclesBasisCurves::GetInitialDirtyBitsMask() const +{ + return HdChangeTracker::DirtyTopology | HdChangeTracker::DirtyPoints + | HdChangeTracker::DirtyNormals | HdChangeTracker::DirtyWidths + | HdChangeTracker::DirtyPrimvar | HdChangeTracker::DirtyTransform + | HdChangeTracker::DirtyVisibility + | HdChangeTracker::DirtyMaterialId; +} + +bool +HdCyclesBasisCurves::IsValid() const +{ + return true; +} + +PXR_NAMESPACE_CLOSE_SCOPE \ No newline at end of file diff --git a/plugin/hdCycles/basisCurves.h b/plugin/hdCycles/basisCurves.h new file mode 100644 index 00000000..6be9a361 --- /dev/null +++ b/plugin/hdCycles/basisCurves.h @@ -0,0 +1,179 @@ +// Copyright 2020 Tangent Animation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef HD_CYCLES_BASIS_CURVES_H +#define HD_CYCLES_BASIS_CURVES_H + +#include "api.h" + +#include "hdcycles.h" +#include "renderDelegate.h" + +#include + +#include +#include +#include + + +namespace ccl { +class Mesh; +class Scene; +class Object; +class Camera; +class Hair; +class Geometry; +} // namespace ccl + +PXR_NAMESPACE_OPEN_SCOPE + +class HdSceneDelegate; +class HdCyclesRenderDelegate; + +enum HdCyclesCurveStyle { + CURVE_RIBBONS, + CURVE_TUBE, +}; + +/** + * @brief Cycles Basis Curve Rprim mapped to Cycles Basis Curve + * + */ +class HdCyclesBasisCurves final : public HdBasisCurves { +public: + /** + * @brief Construct a new HdCycles Basis Curve object + * + * @param id Path to the Basis Curve Primitive + * @param instancerId If specified the HdInstancer at this id uses this curve + * as a prototype + */ + HdCyclesBasisCurves(SdfPath const& id, SdfPath const& instancerId, + HdCyclesRenderDelegate* a_renderDelegate); + /** + * @brief Destroy the HdCycles Basis Curves object + * + */ + virtual ~HdCyclesBasisCurves(); + + /** + * @brief Pull invalidated material data and prepare/update the core Cycles + * representation. + * + * This must be thread safe. + * + * @param sceneDelegate The data source for the basis curve + * @param renderParam State + * @param dirtyBits Which bits of scene data has changed + */ + void Sync(HdSceneDelegate* sceneDelegate, HdRenderParam* renderParam, + HdDirtyBits* dirtyBits, TfToken const& reprSelector) override; + + /** + * @brief Inform the scene graph which state needs to be downloaded in + * the first Sync() call + * + * @return The initial dirty state this basis curve wants to query + */ + HdDirtyBits GetInitialDirtyBitsMask() const override; + + /** + * @return Return true if this light is valid. + */ + bool IsValid() const; + + /** + * @brief Not Implemented + */ + void Finalize(HdRenderParam* renderParam) override; + +protected: + /** + * @brief Initialize the given representation of this Rprim. + * This is called prior to syncing the prim. + * + * @param reprToken The name of the repr to initialize + * @param dirtyBits In/Out dirty values + */ + void _InitRepr(TfToken const& reprToken, HdDirtyBits* dirtyBits) override; + + /** + * @brief Set additional dirty bits + * + * @param bits + * @return New value of dirty bits + */ + HdDirtyBits _PropagateDirtyBits(HdDirtyBits bits) const override; + +protected: + VtVec3fArray m_points; + VtVec3fArray m_normals; + VtFloatArray m_widths; + HdBasisCurvesTopology m_topology; + HdInterpolation m_widthsInterpolation; + VtIntArray m_indices; + GfMatrix4f m_transform; + HdTimeSampleArray m_transformSamples; + + int m_numTransformSamples; + bool m_useMotionBlur; + int m_motionSteps; + + HdCyclesCurveStyle m_curveStyle; + int m_curveResolution; + +private: + /** + * @brief Create the cycles curve mesh and object representation + * + * @return New allocated pointer to ccl::Mesh + */ + ccl::Object* _CreateObject(); + + /** + * @brief Populate the Cycles mesh representation from delegate's data + */ + void _PopulateCurveMesh(HdRenderParam* renderParam); + + /** + * @brief Manually create ribbon geometry for curves + * + * @param a_camera Optional camera to orient towards + */ + void _CreateRibbons(ccl::Camera* a_camera = nullptr); + + /** + * @brief Manually create tube/bevelled geometry for curves + * + */ + void _CreateTubeMesh(); + + /** + * @brief Properly populate native cycles curves with curve data + * + * @param a_scene Scene to add to + */ + void _CreateCurves(ccl::Scene* a_scene); + + ccl::Object* m_cyclesObject; + ccl::Mesh* m_cyclesMesh; + ccl::Hair* m_cyclesHair; + ccl::Geometry* m_cyclesGeometry; + + HdCyclesRenderDelegate* m_renderDelegate; +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // HD_CYCLES_BASIS_CURVES_H diff --git a/plugin/hdCycles/camera.cpp b/plugin/hdCycles/camera.cpp new file mode 100644 index 00000000..afbdacde --- /dev/null +++ b/plugin/hdCycles/camera.cpp @@ -0,0 +1,425 @@ +// Copyright 2020 Tangent Animation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "camera.h" + +#include "config.h" +#include "renderDelegate.h" +#include "renderParam.h" +#include "utils.h" + +#include +#include + +#include + +PXR_NAMESPACE_OPEN_SCOPE + +namespace { +template +bool +EvalCameraParam(T* value, const TfToken& paramName, + HdSceneDelegate* sceneDelegate, const SdfPath& primPath, + T defaultValue) +{ + VtValue vtval = sceneDelegate->GetCameraParamValue(primPath, paramName); + if (vtval.IsEmpty()) { + *value = defaultValue; + return false; + } + if (!vtval.IsHolding()) { + *value = defaultValue; + TF_CODING_ERROR("%s: type mismatch - %s", paramName.GetText(), + vtval.GetTypeName().c_str()); + return false; + } + + *value = vtval.UncheckedGet(); + return true; +} + +template +bool +EvalCameraParam(T* value, const TfToken& paramName, + HdSceneDelegate* sceneDelegate, const SdfPath& primPath) +{ + return EvalCameraParam(value, paramName, sceneDelegate, primPath, + std::numeric_limits::quiet_NaN()); +} +} // namespace + +HdCyclesCamera::HdCyclesCamera(SdfPath const& id, + HdCyclesRenderDelegate* a_renderDelegate) + : HdCamera(id) + , m_horizontalAperture(36.0f) + , m_verticalAperture(24.0f) + , m_horizontalApertureOffset(0.0f) + , m_verticalApertureOffset(0.0f) + , m_focalLength(50.0f) + , m_fStop(2.8f) + , m_focusDistance(10.0f) + , m_shutterOpen(0.0f) + , m_shutterClose(0.0f) + , m_clippingRange(0.1f, 100000.0f) + , m_renderDelegate(a_renderDelegate) + , m_needsUpdate(false) +{ + m_cyclesCamera + = m_renderDelegate->GetCyclesRenderParam()->GetCyclesScene()->camera; + + static const HdCyclesConfig& config = HdCyclesConfig::GetInstance(); + m_useDof = config.enable_dof; + m_useMotionBlur = config.enable_motion_blur; +} + +HdCyclesCamera::~HdCyclesCamera() {} + +void +HdCyclesCamera::Sync(HdSceneDelegate* sceneDelegate, HdRenderParam* renderParam, + HdDirtyBits* dirtyBits) +{ + HD_TRACE_FUNCTION(); + HF_MALLOC_TAG_FUNCTION(); + + if (!TF_VERIFY(sceneDelegate != nullptr)) { + return; + } + + SdfPath const& id = GetId(); + + HdCyclesRenderParam* param = (HdCyclesRenderParam*)renderParam; + + ccl::Scene* scene = param->GetCyclesScene(); + + if (*dirtyBits & HdCamera::DirtyClipPlanes) { + bool has_clippingRange + = EvalCameraParam(&m_clippingRange, HdCameraTokens->clippingRange, + sceneDelegate, id, GfRange1f(0.1f, 100000.0f)); + } + + if (*dirtyBits & HdCamera::DirtyParams) { + m_needsUpdate = true; + + // TODO: + // Offset (requires viewplane work) + + EvalCameraParam(&m_horizontalApertureOffset, + HdCameraTokens->horizontalApertureOffset, sceneDelegate, + id); + EvalCameraParam(&m_verticalApertureOffset, + HdCameraTokens->verticalApertureOffset, sceneDelegate, + id); + + // TODO: + // Shutter + + EvalCameraParam(&m_shutterOpen, HdCameraTokens->shutterOpen, + sceneDelegate, id); + EvalCameraParam(&m_shutterClose, HdCameraTokens->shutterClose, + sceneDelegate, id); + + float shutter = (std::abs(m_shutterOpen) + std::abs(m_shutterClose)) + / 2.0f; + if (m_shutterOpen == 0.0f && m_shutterClose == 0.0f) + shutter = 0.5f; + m_shutterTime = shutter; + + // Projection + + bool has_projection = EvalCameraParam(&m_projectionType, + UsdGeomTokens->projection, + sceneDelegate, id, TfToken()); + + // Aperture + + float horizontalAp, verticalAp; + bool has_horizontalAp + = EvalCameraParam(&horizontalAp, HdCameraTokens->horizontalAperture, + sceneDelegate, id); + if (has_horizontalAp) + m_horizontalAperture = horizontalAp * 10.0f; + + bool has_verticalAp = EvalCameraParam(&verticalAp, + HdCameraTokens->verticalAperture, + sceneDelegate, id); + if (has_verticalAp) + m_verticalAperture = verticalAp * 10.0f; + + // Focal Length + + float focalLength; + bool has_focalLength = EvalCameraParam(&focalLength, + HdCameraTokens->focalLength, + sceneDelegate, id); + if (has_focalLength) + m_focalLength = focalLength * 10.0f; + + if (has_focalLength && has_horizontalAp && has_verticalAp) { + float y1 = m_verticalAperture; + if (m_horizontalAperture < y1) + y1 = m_horizontalAperture; + float fov = 2.0f + * atanf((m_verticalAperture / 2.0f) / m_focalLength); + // TODO: This isn't always correct. + // This is usually set in the renderpass from the proj matrix + m_fov = fov; + } + + bool has_fStop = EvalCameraParam(&m_fStop, HdCameraTokens->fStop, + sceneDelegate, id); + + bool has_focusDistance = EvalCameraParam(&m_focusDistance, + HdCameraTokens->focusDistance, + sceneDelegate, id); + + if (std::isnan(m_focalLength)) { + has_focalLength = false; + } + + if (std::isnan(m_fStop) || m_fStop < 0.000001f) { + has_fStop = false; + } + + // Depth of field + + if (m_useDof && has_fStop) { + if (has_focalLength) { + if (m_cyclesCamera->type == ccl::CAMERA_ORTHOGRAPHIC) + m_apertureSize = 1.0f / (2.0f * m_fStop); + else + m_apertureSize = (m_focalLength * 1e-3f) / (2.0f * m_fStop); + } + // TODO: We will need custom usdCycles schema for these + m_apertureRatio = 1.0f; + m_blades = 0; + m_bladesRotation = 0.0f; + } else { + m_apertureSize = 0.0f; + m_blades = 0; + m_bladesRotation = 0.0f; + m_focusDistance = 0.0f; + m_apertureRatio = 1.0f; + } + } + + if (*dirtyBits & HdCamera::DirtyProjMatrix) { + EvalCameraParam(&m_projMtx, HdCameraTokens->projectionMatrix, + sceneDelegate, id); + + sceneDelegate->SampleTransform(id, &m_transformSamples); + SetTransform(m_projMtx); + } + + if (*dirtyBits & HdCamera::DirtyViewMatrix) { + // Convert right-handed Y-up camera space (USD, Hydra) to + // left-handed Y-up (Cycles) coordinates. This just amounts to + // flipping the Z axis. + + sceneDelegate->SampleTransform(id, &m_transformSamples); + SetTransform(m_projMtx); + } + + if (m_needsUpdate) { + m_cyclesCamera->tag_update(); + m_cyclesCamera->need_update = true; + param->Interrupt(); + } + + HdCamera::Sync(sceneDelegate, renderParam, dirtyBits); + + *dirtyBits = HdChangeTracker::Clean; +} + +bool +HdCyclesCamera::ApplyCameraSettings(ccl::Camera* a_camera) +{ + a_camera->matrix = mat4d_to_transform(m_transform); + a_camera->fov = m_fov; + + a_camera->aperturesize = m_apertureSize; + a_camera->blades = m_blades; + a_camera->bladesrotation = m_bladesRotation; + a_camera->focaldistance = m_focusDistance; + a_camera->aperture_ratio = m_apertureRatio; + + a_camera->nearclip = m_clippingRange.GetMin(); + a_camera->farclip = m_clippingRange.GetMax(); + + a_camera->shuttertime = m_shutterTime; + + if (m_projectionType == UsdGeomTokens->orthographic) { + a_camera->type = ccl::CameraType::CAMERA_ORTHOGRAPHIC; + } else { + a_camera->type = ccl::CameraType::CAMERA_PERSPECTIVE; + } + + bool shouldUpdate = m_needsUpdate; + + if (shouldUpdate) + m_needsUpdate = false; + + return shouldUpdate; + + // TODO: This does not seem to work + /*a_camera->motion.clear(); + a_camera->motion.resize(HD_CYCLES_MOTION_STEPS, a_camera->matrix); + for (int i = 0; i < HD_CYCLES_MOTION_STEPS; i++) { + a_camera->motion[i] = mat4d_to_transform( + m_transformSamples.values.data()[i]); + }*/ +} + +HdDirtyBits +HdCyclesCamera::GetInitialDirtyBitsMask() const +{ + return HdCamera::AllDirty; +} + +template +static const T* +_GetDictItem(const VtDictionary& dict, const TfToken& key) +{ + const VtValue* v = TfMapLookupPtr(dict, key.GetString()); + return v && v->IsHolding() ? &v->UncheckedGet() : nullptr; +} + +bool +HdCyclesCamera::GetApertureSize(GfVec2f* v) const +{ + if (!std::isnan(m_horizontalAperture) && !std::isnan(m_verticalAperture)) { + *v = { m_horizontalAperture, m_verticalAperture }; + return true; + } + return false; +} + +bool +HdCyclesCamera::GetApertureOffset(GfVec2f* v) const +{ + if (!std::isnan(m_horizontalApertureOffset) + && !std::isnan(m_verticalApertureOffset)) { + *v = { m_horizontalApertureOffset, m_verticalApertureOffset }; + return true; + } + return false; +} + +bool +HdCyclesCamera::GetFocalLength(float* v) const +{ + if (!std::isnan(m_focalLength)) { + *v = m_focalLength; + return true; + } + return false; +} + +bool +HdCyclesCamera::GetFStop(float* v) const +{ + if (!std::isnan(m_fStop)) { + *v = m_fStop; + return true; + } + return false; +} + +bool +HdCyclesCamera::GetFocusDistance(float* v) const +{ + if (!std::isnan(m_focusDistance)) { + *v = m_focusDistance; + return true; + } + return false; +} + +bool +HdCyclesCamera::GetShutterOpen(double* v) const +{ + if (!std::isnan(m_shutterOpen)) { + *v = m_shutterOpen; + return true; + } + return false; +} + +bool +HdCyclesCamera::GetShutterClose(double* v) const +{ + if (!std::isnan(m_shutterClose)) { + *v = m_shutterClose; + return true; + } + return false; +} + +bool +HdCyclesCamera::GetClippingRange(GfRange1f* v) const +{ + if (!std::isnan(m_clippingRange.GetMin()) + && !std::isnan(m_clippingRange.GetMax())) { + *v = m_clippingRange; + return true; + } + return false; +} + +bool +HdCyclesCamera::GetProjectionType(TfToken* v) const +{ + if (!m_projectionType.IsEmpty()) { + *v = m_projectionType; + return true; + } + return false; +} + +void +HdCyclesCamera::SetFOV(const float& a_value) +{ + m_fov = a_value; +} + +void +HdCyclesCamera::SetTransform(const GfMatrix4d a_projectionMatrix) +{ + GfMatrix4d viewToWorldCorrectionMatrix(1.0); + + if (m_projectionType == UsdGeomTokens->orthographic) { + double left = -(1 + a_projectionMatrix[3][0]) + / a_projectionMatrix[0][0]; + double right = (1 - a_projectionMatrix[3][0]) + / a_projectionMatrix[0][0]; + double bottom = -(1 - a_projectionMatrix[3][1]) + / a_projectionMatrix[1][1]; + double top = (1 + a_projectionMatrix[3][1]) / a_projectionMatrix[1][1]; + double w = (right - left) / 2; + double h = (top - bottom) / 2; + GfMatrix4d scaleMatrix; + scaleMatrix.SetScale(GfVec3d(w, h, 1)); + viewToWorldCorrectionMatrix = scaleMatrix; + } + + GfMatrix4d flipZ(1.0); + flipZ[2][2] = -1.0; + viewToWorldCorrectionMatrix = flipZ * viewToWorldCorrectionMatrix; + + GfMatrix4d matrix = viewToWorldCorrectionMatrix + * m_transformSamples.values.data()[0]; + + m_transform = (matrix); +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/plugin/hdCycles/camera.h b/plugin/hdCycles/camera.h new file mode 100644 index 00000000..c03cee03 --- /dev/null +++ b/plugin/hdCycles/camera.h @@ -0,0 +1,226 @@ +// Copyright 2020 Tangent Animation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef HD_CYCLES_CAMERA_H +#define HD_CYCLES_CAMERA_H + +#include "api.h" + +#include "hdcycles.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace ccl { +class Camera; +} + +PXR_NAMESPACE_OPEN_SCOPE + +class HdSceneDelegate; +class HdCyclesRenderDelegate; + +/** + * @brief Cycles Camera Sprim mapped to Cycles Camera + * + */ +class HdCyclesCamera final : public HdCamera { +public: + /** + * @brief Construct a new HdCycles Camera object + * + * @param id Path to the Camera Primitive + */ + HdCyclesCamera(SdfPath const& id, HdCyclesRenderDelegate* a_renderDelegate); + virtual ~HdCyclesCamera(); + + /** + * @brief Pull invalidated camera data and prepare/update the core Cycles + * representation. + * + * This must be thread safe. + * + * @param sceneDelegate The data source for the mesh + * @param renderParam State + * @param dirtyBits Which bits of scene data has changed + */ + virtual void Sync(HdSceneDelegate* sceneDelegate, + HdRenderParam* renderParam, + HdDirtyBits* dirtyBits) override; + + /** + * @brief Inform the scene graph which state needs to be downloaded in + * the first Sync() call + * + * @return The initial dirty state this camera wants to query + */ + virtual HdDirtyBits GetInitialDirtyBitsMask() const override; + + /** + * @return Return time sampled xforms that were quereied during Sync + */ + HDCYCLES_API + HdTimeSampleArray const& + GetTimeSampleXforms() const + { + return m_transformSamples; + } + + /** + * @brief Get the HdCyclesCamera Aperture Size + * + * @param value Value of Aperture Size + * @return Return true if found + */ + bool GetApertureSize(GfVec2f* value) const; + + /** + * @brief Get the HdCyclesCamera Aperture Offset + * + * @param value Value of Aperture Offset + * @return Return true if found + */ + bool GetApertureOffset(GfVec2f* value) const; + + /** + * @brief Get the HdCyclesCamera Focal Lenth + * + * @param value Value of Focal Lenth + * @return Return true if found + */ + bool GetFocalLength(float* value) const; + + /** + * @brief Get the HdCyclesCamera FStop + * + * @param value Value of FStop + * @return Return true if found + */ + bool GetFStop(float* value) const; + + /** + * @brief Get the HdCyclesCamera Focus Distance + * + * @param value Value of Focus Distance + * @return Return true if found + */ + bool GetFocusDistance(float* value) const; + + /** + * @brief Get the HdCyclesCamera Shutter Open + * + * @param value Value of Shutter Open + * @return Return true if found + */ + bool GetShutterOpen(double* value) const; + + /** + * @brief Get the HdCyclesCamera Shutter Close + * + * @param value Value of Shutter Close + * @return Return true if found + */ + bool GetShutterClose(double* value) const; + + /** + * @brief Get the HdCyclesCamera Clipping Range + * + * @param value Value of Clipping Range + * @return Return true if found + */ + bool GetClippingRange(GfRange1f* value) const; + + /** + * @brief Get the HdCyclesCamera Projection Type + * + * @param value Value of Projection Type + * @return Return true if found + */ + bool GetProjectionType(TfToken* value) const; + + /** + * @brief Get the Cycles Camera object + * + * @return ccl::Camera* Camera + */ + ccl::Camera* GetCamera() { return m_cyclesCamera; } + + /** + * @brief Set value of cycles field of view + * + * @param a_value FOV + */ + void SetFOV(const float& a_value); + + /** + * @brief Set the transform based on projection matrix + * + * @param a_projectionMatrix + */ + void SetTransform(const GfMatrix4d a_projectionMatrix); + + /** + * @brief Apply this cameras stored/synced settings + * to the given cycles camera + * + * @param a_camera + * @return Return true if sync has incurred an update + */ + bool ApplyCameraSettings(ccl::Camera* a_camera); + +private: + float m_horizontalAperture; + float m_verticalAperture; + float m_horizontalApertureOffset; + float m_verticalApertureOffset; + float m_focalLength; + float m_fStop; + float m_focusDistance; + double m_shutterOpen; + double m_shutterClose; + GfRange1f m_clippingRange; + TfToken m_projectionType; + + GfMatrix4d m_projMtx; + + // Cycles camera specifics + float m_fov; + GfMatrix4d m_transform; + float m_shutterTime; + float m_apertureRatio; + int m_blades; + float m_bladesRotation; + float m_apertureSize; + + bool m_useDof; + + bool m_useMotionBlur; + + HdTimeSampleArray m_transformSamples; + + ccl::Camera* m_cyclesCamera; + + HdCyclesRenderDelegate* m_renderDelegate; + + bool m_needsUpdate; +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // HD_CYCLES_CAMERA_H diff --git a/plugin/hdCycles/config.cpp b/plugin/hdCycles/config.cpp new file mode 100644 index 00000000..5bf6ef2b --- /dev/null +++ b/plugin/hdCycles/config.cpp @@ -0,0 +1,227 @@ +// Copyright 2020 Tangent Animation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "config.h" + +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +TF_INSTANTIATE_SINGLETON(HdCyclesConfig); + +/* ====== HdCycles Settings ====== */ + +TF_DEFINE_ENV_SETTING(HD_CYCLES_ENABLE_LOGGING, false, + "Enable HdCycles Logging"); + +TF_DEFINE_ENV_SETTING(HD_CYCLES_ENABLE_MOTION_BLUR, false, + "Enable HdCycles motion blur support"); + +TF_DEFINE_ENV_SETTING(HD_CYCLES_MOTION_STEPS, 3, + "Number of frames to populate motion for"); + +TF_DEFINE_ENV_SETTING(HD_CYCLES_ENABLE_SUBDIVISION, false, + "Enable HdCycles subdiv support"); + +TF_DEFINE_ENV_SETTING(HD_CYCLES_SUBDIVISION_DICING_RATE, "0.1", + "Mesh subdivision dicing rate"); + +TF_DEFINE_ENV_SETTING(HD_CYCLES_MAX_SUBDIVISION, 12, + "Maximum levels of subdivision"); + +TF_DEFINE_ENV_SETTING(HD_CYCLES_ENABLE_DOF, true, + "Enable hdCycles depth of field support"); + +TF_DEFINE_ENV_SETTING(HD_CYCLES_RENDER_WIDTH, 1280, + "Width of a non interactive HdCycles render"); + +TF_DEFINE_ENV_SETTING(HD_CYCLES_RENDER_HEIGHT, 720, + "Width of a non interactive HdCycles render"); + +TF_DEFINE_ENV_SETTING( + HD_CYCLES_USE_OLD_CURVES, false, + "If enabled, curves will be created manually with regular mesh geometry"); + + +TF_DEFINE_ENV_SETTING( + HD_CYCLES_USE_TRANSPARENT_BACKGROUND, false, + "If enabled, the background will be transparent in renders"); + +/* ======= Cycles Settings ======= */ + +TF_DEFINE_ENV_SETTING(HD_CYCLES_ENABLE_EXPERIMENTAL, false, + "Experimental cycles support."); + +TF_DEFINE_ENV_SETTING(HD_CYCLES_BVH_TYPE, "DYNAMIC", "Cycles BVH Type "); + +TF_DEFINE_ENV_SETTING(HD_CYCLES_DEVICE_NAME, "CPU", + "Device cycles will use to render"); + +TF_DEFINE_ENV_SETTING(HD_CYCLES_SHADING_SYSTEM, "SVM", + "Shading system cycles will use"); + +TF_DEFINE_ENV_SETTING(HD_CYCLES_DISPLAY_BUFFER_LINEAR, true, + "Format of display buffer. False: byte. True: half."); + +TF_DEFINE_ENV_SETTING(HD_CYCLES_MAX_SAMPLES, 512, + "Number of samples to render per pixel"); + +TF_DEFINE_ENV_SETTING(HD_CYCLES_NUM_THREADS, 0, "Number of threads to use"); + +TF_DEFINE_ENV_SETTING(HD_CYCLES_PIXEL_SIZE, 1, "Size of pixel"); + +TF_DEFINE_ENV_SETTING(HD_CYCLES_TILE_SIZE_X, 64, "Size of tile x"); + +TF_DEFINE_ENV_SETTING(HD_CYCLES_TILE_SIZE_Y, 64, "Size of tile y"); + +TF_DEFINE_ENV_SETTING(HD_CYCLES_START_RESOLUTION, 8, + "Maximum start Resolution of render"); + +TF_DEFINE_ENV_SETTING(HD_CYCLES_EXPOSURE, "1.0", "Exposure of cycles film"); + +TF_DEFINE_ENV_SETTING( + HD_CYCLES_SHUTTER_MOTION_POSITION, 1, + "Position of shutter motion position. (0: Start, 1: Center, 2: End)"); + +TF_DEFINE_ENV_SETTING(HD_CYCLES_DEFAULT_POINT_STYLE, 0, + "Default point style. (0: Discs, 1: Spheres)"); + +TF_DEFINE_ENV_SETTING(HD_CYCLES_DEFAULT_POINT_RESOLUTION, 16, + "Default point resolution"); + +/* ===== Curve Settings ===== */ + +TF_DEFINE_ENV_SETTING(HD_CYCLES_CURVE_RESOLUTION, 3, "Resolution of curve"); +TF_DEFINE_ENV_SETTING(HD_CYCLES_CURVE_SUBDIVISIONS, 3, "Curve subdvisions"); + +TF_DEFINE_ENV_SETTING(HD_CYCLES_CURVE_USE_BACKFACES, false, + "Should curve geometry have backfaces"); +TF_DEFINE_ENV_SETTING(HD_CYCLES_CURVE_USE_ENCASING, true, + "Should curve be encased"); +TF_DEFINE_ENV_SETTING(HD_CYCLES_CURVE_USE_TANGENT_NORMAL_GEO, false, + "Should curve be encased"); + +TF_DEFINE_ENV_SETTING(HD_CYCLES_CURVE_SHAPE, "CURVE_THICK", + "Shape of curves [CURVE_RIBBON, CURVE_THICK]"); +TF_DEFINE_ENV_SETTING( + HD_CYCLES_CURVE_PRIMITIVE, "CURVE_SEGMENTS", + "Curve primitive: [CURVE_TRIANGLES, CURVE_LINE_SEGMENTS, CURVE_SEGMENTS, CURVE_RIBBONS]"); +TF_DEFINE_ENV_SETTING( + HD_CYCLES_CURVE_TRIANGLE_METHOD, "CURVE_TESSELATED_TRIANGLES", + "Curve triangle method: [CURVE_CAMERA_TRIANGLES, CURVE_TESSELATED_TRIANGLES]"); +TF_DEFINE_ENV_SETTING(HD_CYCLES_CURVE_LINE_METHOD, "CURVE_ACCURATE", + "Curve line method: [CURVE_ACCURATE, CURVE_UNCORRECTED]"); + +/* ===== Integrator Settings ===== */ + +TF_DEFINE_ENV_SETTING(HD_CYCLES_INTEGRATOR_METHOD, "PATH", + "Method of path tracing. [PATH, BRANCHED_PATH]"); + +TF_DEFINE_ENV_SETTING(HD_CYCLES_DIFFUSE_SAMPLES, 1, + "Number of diffuse samples for cycles integrator"); + +TF_DEFINE_ENV_SETTING(HD_CYCLES_GLOSSY_SAMPLES, 1, + "Number of glossy samples for cycles integrator"); + +TF_DEFINE_ENV_SETTING(HD_CYCLES_TRANSMISSION_SAMPLES, 1, + "Number of transmission samples for cycles integrator"); + +TF_DEFINE_ENV_SETTING( + HD_CYCLES_AO_SAMPLES, 1, + "Number of ambient occlusion samples for cycles integrator"); + +TF_DEFINE_ENV_SETTING(HD_CYCLES_MESH_LIGHT_SAMPLES, 1, + "Number of mesh light samples for cycles integrator"); + +TF_DEFINE_ENV_SETTING(HD_CYCLES_SUBSURFACE_SAMPLES, 1, + "Number of subsurface samples for cycles integrator"); + +TF_DEFINE_ENV_SETTING(HD_CYCLES_VOLUME_SAMPLES, 1, + "Number of volume samples for cycles integrator"); + +// HdCycles Constructor +HdCyclesConfig::HdCyclesConfig() +{ + // -- HdCycles Settings + enable_logging = TfGetEnvSetting(HD_CYCLES_ENABLE_LOGGING); + enable_motion_blur = TfGetEnvSetting(HD_CYCLES_ENABLE_MOTION_BLUR); + motion_steps = TfGetEnvSetting(HD_CYCLES_MOTION_STEPS); + enable_subdivision = TfGetEnvSetting(HD_CYCLES_ENABLE_SUBDIVISION); + subdivision_dicing_rate = atof( + TfGetEnvSetting(HD_CYCLES_SUBDIVISION_DICING_RATE).c_str()); + max_subdivision = TfGetEnvSetting(HD_CYCLES_MAX_SUBDIVISION); + enable_dof = TfGetEnvSetting(HD_CYCLES_ENABLE_DOF); + render_width = TfGetEnvSetting(HD_CYCLES_RENDER_WIDTH); + render_height = TfGetEnvSetting(HD_CYCLES_RENDER_HEIGHT); + use_old_curves = TfGetEnvSetting(HD_CYCLES_USE_OLD_CURVES); + enable_transparent_background = TfGetEnvSetting( + HD_CYCLES_USE_TRANSPARENT_BACKGROUND); + + // -- Cycles Settings + enable_experimental = TfGetEnvSetting(HD_CYCLES_ENABLE_EXPERIMENTAL); + bvh_type = TfGetEnvSetting(HD_CYCLES_BVH_TYPE); + device_name = TfGetEnvSetting(HD_CYCLES_DEVICE_NAME); + shading_system = TfGetEnvSetting(HD_CYCLES_SHADING_SYSTEM); + display_buffer_linear = TfGetEnvSetting(HD_CYCLES_DISPLAY_BUFFER_LINEAR); + max_samples = TfGetEnvSetting(HD_CYCLES_MAX_SAMPLES); + num_threads = TfGetEnvSetting(HD_CYCLES_NUM_THREADS); + pixel_size = TfGetEnvSetting(HD_CYCLES_PIXEL_SIZE); + tile_size = pxr::GfVec2i(TfGetEnvSetting(HD_CYCLES_TILE_SIZE_X), + TfGetEnvSetting(HD_CYCLES_TILE_SIZE_Y)); + start_resolution = TfGetEnvSetting(HD_CYCLES_START_RESOLUTION); + exposure = atof(TfGetEnvSetting(HD_CYCLES_EXPOSURE).c_str()); + shutter_motion_position = TfGetEnvSetting( + HD_CYCLES_SHUTTER_MOTION_POSITION); + + default_point_style = TfGetEnvSetting(HD_CYCLES_DEFAULT_POINT_STYLE); + default_point_resolution = TfGetEnvSetting( + HD_CYCLES_DEFAULT_POINT_RESOLUTION); + + + // -- Curve Settings + + curve_resolution = TfGetEnvSetting(HD_CYCLES_CURVE_RESOLUTION); + curve_subdivisions = TfGetEnvSetting(HD_CYCLES_CURVE_SUBDIVISIONS); + + curve_use_backfaces = TfGetEnvSetting(HD_CYCLES_CURVE_USE_BACKFACES); + curve_use_encasing = TfGetEnvSetting(HD_CYCLES_CURVE_USE_ENCASING); + curve_use_tangent_normal_geometry = TfGetEnvSetting( + HD_CYCLES_CURVE_USE_TANGENT_NORMAL_GEO); + + curve_shape = TfGetEnvSetting(HD_CYCLES_CURVE_SHAPE); + curve_primitive = TfGetEnvSetting(HD_CYCLES_CURVE_PRIMITIVE); + curve_triangle_method = TfGetEnvSetting(HD_CYCLES_CURVE_TRIANGLE_METHOD); + curve_line_method = TfGetEnvSetting(HD_CYCLES_CURVE_LINE_METHOD); + + + // -- Integrator Settings + integrator_method = TfGetEnvSetting(HD_CYCLES_INTEGRATOR_METHOD); + + diffuse_samples = TfGetEnvSetting(HD_CYCLES_DIFFUSE_SAMPLES); + glossy_samples = TfGetEnvSetting(HD_CYCLES_GLOSSY_SAMPLES); + transmission_samples = TfGetEnvSetting(HD_CYCLES_TRANSMISSION_SAMPLES); + ao_samples = TfGetEnvSetting(HD_CYCLES_AO_SAMPLES); + mesh_light_samples = TfGetEnvSetting(HD_CYCLES_MESH_LIGHT_SAMPLES); + subsurface_samples = TfGetEnvSetting(HD_CYCLES_SUBSURFACE_SAMPLES); + volume_samples = TfGetEnvSetting(HD_CYCLES_VOLUME_SAMPLES); +} + +const HdCyclesConfig& +HdCyclesConfig::GetInstance() +{ + return TfSingleton::GetInstance(); +} + +PXR_NAMESPACE_CLOSE_SCOPE \ No newline at end of file diff --git a/plugin/hdCycles/config.h b/plugin/hdCycles/config.h new file mode 100644 index 00000000..2fb4d101 --- /dev/null +++ b/plugin/hdCycles/config.h @@ -0,0 +1,318 @@ +// Copyright 2020 Tangent Animation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef HD_CYCLES_CONFIG_H +#define HD_CYCLES_CONFIG_H + +#include "api.h" + +#include +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +/** + * @brief Main singleton that loads and stores mutable, global variables for the lifetime of the + * cycles render delegate. + * + */ +class HdCyclesConfig { +public: + /// Return an instance of HdCyclesConfig. + HDCYCLES_API + static const HdCyclesConfig& GetInstance(); + + /* ====== HdCycles Settings ====== */ + + /** + * @brief If enabled, HdCycles will log every step + * + */ + bool enable_logging; + + /** + * @brief If enabled, HdCycles will populate object's motion and enable motion blur + * + */ + bool enable_motion_blur; + + /** + * @brief Number of frames to populate motion for + * + */ + int motion_steps; + + /** + * @brief If enabled, subdiv meshes will be subdivided + * + */ + bool enable_subdivision; + + /** + * @brief Dicing rate of mesh subdivision + * + */ + float subdivision_dicing_rate; + + /** + * @brief Maximum amount of subdivisions + * + */ + int max_subdivision; + + /** + * @brief Enable dpeth of field for cycles + * + */ + bool enable_dof; + + /** + * @brief Width of non interactive render output + * + */ + int render_width; + + /** + * @brief Height of non interactive render output + * + */ + int render_height; + + /** + * @brief Disabled by default, if enabled, curves will be generated through mesh geometry. + * + */ + bool use_old_curves; + + /** + * @brief Manual override of transparent background for renders + * + */ + bool enable_transparent_background; + + /** + * @brief Default style of HdPoints. Overridable by primvars + * 0: Discs, 1: Spheres + * + */ + int default_point_style; + + /** + * @brief Default resolution of point mesh. Overridable by primvars + * + */ + int default_point_resolution; + + /* ======= Cycles Settings ======= */ + + /** + * @brief Should cycles run in experimental mode + * + */ + bool enable_experimental; + + /** + * @brief Cycles BVH Type. Use Dynamic for any interactive viewport. (DYNAMIC, STATIC) + * + */ + std::string bvh_type; + + /** + * @brief Name of cycles render device. (CPU, GPU, etc.) + * + */ + std::string device_name; + + /** + * @brief Shading system (SVM, OSL) + * + */ + std::string shading_system; + + /** + * @brief If false, bytes will be used, if true, half's + * + */ + bool display_buffer_linear; + + /** + * @brief Number of samples to render + * + */ + int max_samples; + + /** + * @brief Number of threads to use for cycles render + * + */ + int num_threads; + + /** + * @brief Size of pixel + * + */ + int pixel_size; + + /** + * @brief Size of individual render tile + * + */ + pxr::GfVec2i tile_size; + + /** + * @brief Start Resolution of render + * + */ + int start_resolution; + + /** + * @brief Exposure of cycles film + * + */ + float exposure; + + /** + * @brief Position of shutter motion position. + * 0: Start + * 1: Center + * 2: End + * + */ + int shutter_motion_position; + + /* ===== Curve Settings ===== */ + + /** + * @brief Resolution of curve + * + */ + int curve_resolution; + + /** + * @brief Curve subdvisions + * + */ + int curve_subdivisions; + + /** + * @brief Should curve geometry have backfaces + * + */ + bool curve_use_backfaces; + + /** + * @brief Should curve be encased + * + */ + bool curve_use_encasing; + + /** + * @brief + * + */ + bool curve_use_tangent_normal_geometry; + + /** + * @brief Shape of curves CURVE_RIBBON, CURVE_THICK + * + */ + std::string curve_shape; + + /** + * @brief Curve primitive CURVE_LINE_SEGMENTS, CURVE_TRIANGLES + * + */ + std::string curve_primitive; + + /** + * @brief Curve triangle method: CURVE_CAMERA_TRIANGLES, CURVE_TESSELATED_TRIANGLES + * + */ + std::string curve_triangle_method; + + /** + * @brief Curve line method: CURVE_ACCURATE, CURVE_UNCORRECTED + * + */ + std::string curve_line_method; + + /* ===== Integrator Settings ===== */ + + /** + * @brief Method of path tracing. (PATH, BRANCHED_PATH) + * + */ + std::string integrator_method; + + /** + * @brief Number of diffuse samples for cycles integrator + * + */ + int diffuse_samples; + + /** + * @brief Number of glossy samples for cycles integrator + * + */ + int glossy_samples; + + /** + * @brief Number of transmission samples for cycles integrator + * + */ + int transmission_samples; + + /** + * @brief Number of ao samples for cycles integrator + * + */ + int ao_samples; + + /** + * @brief Number of mesh light samples for cycles integrator + * + */ + int mesh_light_samples; + + /** + * @brief Number of subsurface samples for cycles integrator + * + */ + int subsurface_samples; + + /** + * @brief Number of volume samples for cycles integrator + * + */ + int volume_samples; + +private: + /** + * @brief Constructor for reading the values from the environment variables. + * + * @return HDCYCLES_API + */ + HDCYCLES_API HdCyclesConfig(); + ~HdCyclesConfig() = default; + HdCyclesConfig(const HdCyclesConfig&) = delete; + HdCyclesConfig(HdCyclesConfig&&) = delete; + HdCyclesConfig& operator=(const HdCyclesConfig&) = delete; + + friend class TfSingleton; +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // HD_CYCLES_CONFIG_H diff --git a/plugin/hdCycles/hdcycles.h b/plugin/hdCycles/hdcycles.h new file mode 100644 index 00000000..50833fb7 --- /dev/null +++ b/plugin/hdCycles/hdcycles.h @@ -0,0 +1,20 @@ +// Copyright 2020 Tangent Animation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef HD_CYCLES_H +#define HD_CYCLES_H + +static constexpr int HD_CYCLES_MOTION_STEPS = 3; + +#endif // HD_CYCLES_H \ No newline at end of file diff --git a/plugin/hdCycles/instancer.cpp b/plugin/hdCycles/instancer.cpp new file mode 100644 index 00000000..faa8cf8c --- /dev/null +++ b/plugin/hdCycles/instancer.cpp @@ -0,0 +1,446 @@ +// Copyright 2020 Tangent Animation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "instancer.h" + +#include +#include +#include +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +// TODO: Use HdInstancerTokens when Houdini updates USD to 20.02 + +// clang-format off +TF_DEFINE_PRIVATE_TOKENS(_tokens, + (instanceTransform) + (rotate) + (scale) + (translate) +); +// clang-format on + +void +HdCyclesInstancer::Sync() +{ + HD_TRACE_FUNCTION(); + HF_MALLOC_TAG_FUNCTION(); + + const SdfPath& instancerId = GetId(); + auto& changeTracker = GetDelegate()->GetRenderIndex().GetChangeTracker(); + + // Use the double-checked locking pattern to check if this instancer's + // primvars are dirty. + int dirtyBits = changeTracker.GetInstancerDirtyBits(instancerId); + if (!HdChangeTracker::IsAnyPrimvarDirty(dirtyBits, instancerId)) { + return; + } + + std::lock_guard lock(m_syncMutex); + dirtyBits = changeTracker.GetInstancerDirtyBits(instancerId); + if (!HdChangeTracker::IsAnyPrimvarDirty(dirtyBits, instancerId)) { + return; + } + + auto primvarDescs + = GetDelegate()->GetPrimvarDescriptors(instancerId, + HdInterpolationInstance); + for (auto& desc : primvarDescs) { + if (!HdChangeTracker::IsPrimvarDirty(dirtyBits, instancerId, + desc.name)) { + continue; + } + + VtValue value = GetDelegate()->Get(instancerId, desc.name); + if (value.IsEmpty()) { + continue; + } + + if (desc.name == _tokens->translate) { + if (value.IsHolding()) { + m_translate = value.UncheckedGet(); + } + } else if (desc.name == _tokens->rotate) { + if (value.IsHolding()) { + m_rotate = value.UncheckedGet(); + } + } else if (desc.name == _tokens->scale) { + if (value.IsHolding()) { + m_scale = value.UncheckedGet(); + } + } else if (desc.name == _tokens->instanceTransform) { + if (value.IsHolding()) { + m_transform = value.UncheckedGet(); + } + } + } + + // Mark the instancer as clean + changeTracker.MarkInstancerClean(instancerId); +} + +VtMatrix4dArray +HdCyclesInstancer::ComputeTransforms(SdfPath const& prototypeId) +{ + Sync(); + + GfMatrix4d instancerTransform = GetDelegate()->GetInstancerTransform( + GetId()); + VtIntArray instanceIndices = GetDelegate()->GetInstanceIndices(GetId(), + prototypeId); + + VtMatrix4dArray transforms; + transforms.reserve(instanceIndices.size()); + for (int idx : instanceIndices) { + GfMatrix4d translateMat(1); + GfMatrix4d rotateMat(1); + GfMatrix4d scaleMat(1); + GfMatrix4d transform(1); + + if (!m_translate.empty()) { + translateMat.SetTranslate(GfVec3d(m_translate.cdata()[idx])); + } + + if (!m_rotate.empty()) { + auto& v = m_rotate.cdata()[idx]; + rotateMat.SetRotate(GfQuatd(v[0], GfVec3d(v[1], v[2], v[3]))); + } + + if (!m_scale.empty()) { + scaleMat.SetScale(GfVec3d(m_scale.cdata()[idx])); + } + + if (!m_transform.empty()) { + transform = m_transform.cdata()[idx]; + } + + transforms.push_back(transform * scaleMat * rotateMat * translateMat + * instancerTransform); + } + + auto parentInstancer = static_cast( + GetDelegate()->GetRenderIndex().GetInstancer(GetParentId())); + if (!parentInstancer) { + return transforms; + } + + VtMatrix4dArray wordTransform; + for (const GfMatrix4d& parentTransform : + parentInstancer->ComputeTransforms(GetId())) { + for (const GfMatrix4d& localTransform : transforms) { + wordTransform.push_back(parentTransform * localTransform); + } + } + + return wordTransform; +} + +namespace { +// Helper to accumulate sample times from the largest set of +// samples seen, up to maxNumSamples. +template +void +AccumulateSampleTimes(HdTimeSampleArray const& in, + HdTimeSampleArray* out) +{ + if (in.count > out->count) { + out->Resize(in.count); + out->times = in.times; + } +} + +// Apply transforms referenced by instanceIndices +template +void +ApplyTransform(VtValue const& allTransformsValue, + VtIntArray const& instanceIndices, GfMatrix4d* transforms) +{ + auto& allTransforms = allTransformsValue.Get>(); + if (allTransforms.empty()) { + TF_RUNTIME_ERROR("No transforms"); + return; + } + + for (size_t i = 0; i < instanceIndices.size(); ++i) { + transforms[i] = Op {}(allTransforms[instanceIndices[i]]) + * transforms[i]; + } +} + +// Apply interpolated transforms referenced by instanceIndices +template +void +ApplyTransform(float alpha, VtValue const& allTransformsValue0, + VtValue const& allTransformsValue1, + VtIntArray const& instanceIndices, GfMatrix4d* transforms) +{ + auto& allTransforms0 = allTransformsValue0.Get>(); + auto& allTransforms1 = allTransformsValue1.Get>(); + if (allTransforms0.empty() || allTransforms1.empty()) { + TF_RUNTIME_ERROR("No transforms"); + return; + } + + for (size_t i = 0; i < instanceIndices.size(); ++i) { + auto transform + = HdResampleNeighbors(alpha, allTransforms0[instanceIndices[i]], + allTransforms1[instanceIndices[i]]); + transforms[i] = Op {}(transform)*transforms[i]; + } +} + +template +void +ApplyTransform(HdTimeSampleArray const& samples, + VtIntArray const& instanceIndices, float time, + GfMatrix4d* transforms) +{ + size_t i = 0; + for (; i < samples.count; ++i) { + if (samples.times[i] == time) { + // Exact time match + return ApplyTransform(samples.values[i], instanceIndices, + transforms); + } + if (samples.times[i] > time) { + break; + } + } + + if (i == 0) { + // time is before the first sample. + return ApplyTransform(samples.values[0], instanceIndices, + transforms); + } else if (i == samples.count) { + // time is after the last sample. + return ApplyTransform(samples.values[samples.count - 1], + instanceIndices, transforms); + } else if (samples.times[i] == samples.times[i - 1]) { + // Neighboring samples have identical parameter. + // Arbitrarily choose a sample. + TF_WARN("overlapping samples at %f; using first sample", + samples.times[i]); + return ApplyTransform(samples.values[i - 1], instanceIndices, + transforms); + } else { + // Linear blend of neighboring samples. + float alpha = (samples.times[i] - time) + / (samples.times[i] - samples.times[i - 1]); + return ApplyTransform(alpha, samples.values[i - 1], + samples.values[i], instanceIndices, + transforms); + } +} + +struct TranslateOp { + template GfMatrix4d operator()(T const& translate) + { + return GfMatrix4d(1).SetTranslate(GfVec3d(translate)); + } +}; + +struct RotateOp { + template GfMatrix4d operator()(T const& rotate) + { + return GfMatrix4d(1).SetRotate(GfRotation(GfQuatd(rotate))); + } +}; + +struct ScaleOp { + template GfMatrix4d operator()(T const& scale) + { + return GfMatrix4d(1).SetScale(GfVec3d(scale)); + } +}; + +struct TransformOp { + GfMatrix4d const& operator()(GfMatrix4d const& transform) + { + return transform; + } + + GfMatrix4d operator()(GfMatrix4f const& transform) + { + return GfMatrix4d(transform); + } +}; + +} // namespace + +HdTimeSampleArray +HdCyclesInstancer::SampleInstanceTransforms(SdfPath const& prototypeId) +{ + HdSceneDelegate* delegate = GetDelegate(); + const SdfPath& instancerId = GetId(); + + VtIntArray instanceIndices = delegate->GetInstanceIndices(instancerId, + prototypeId); + + HdTimeSampleArray instancerXform; + HdTimeSampleArray instanceXforms; + HdTimeSampleArray translates; + HdTimeSampleArray rotates; + HdTimeSampleArray scales; + delegate->SampleInstancerTransform(instancerId, &instancerXform); + delegate->SamplePrimvar(instancerId, _tokens->instanceTransform, + &instanceXforms); + delegate->SamplePrimvar(instancerId, _tokens->translate, &translates); + delegate->SamplePrimvar(instancerId, _tokens->scale, &scales); + delegate->SamplePrimvar(instancerId, _tokens->rotate, &rotates); + + // Hydra might give us falsely varying instancerXform, i.e. more than one time sample with the sample matrix + // This will lead to huge over computation in case it's the only array with a few time samples + if (instancerXform.count > 1) { + size_t iSample = 1; + for (; iSample < instancerXform.values.size(); ++iSample) { + if (!GfIsClose(instancerXform.values[iSample - 1], + instancerXform.values[iSample], 1e-6)) { + break; + } + } + // All samples the same + if (iSample == instancerXform.values.size()) { + instancerXform.Resize(1); + } + } + + // As a simple resampling strategy, find the input with the max # + // of samples and use its sample placement. In practice we expect + // them to all be the same, i.e. to not require resampling. + HdTimeSampleArray sa; + sa.Resize(0); + AccumulateSampleTimes(instancerXform, &sa); + AccumulateSampleTimes(instanceXforms, &sa); + AccumulateSampleTimes(translates, &sa); + AccumulateSampleTimes(scales, &sa); + AccumulateSampleTimes(rotates, &sa); + + for (size_t i = 0; i < sa.count; ++i) { + const float t = sa.times[i]; + + GfMatrix4d xf(1); + if (instancerXform.count > 0) { + xf = instancerXform.Resample(t); + } + + auto& transforms = sa.values[i]; + transforms = VtMatrix4dArray(instanceIndices.size(), xf); + + if (translates.count > 0 && translates.values[0].IsArrayValued()) { + auto& type = translates.values[0].GetElementTypeid(); + if (type == typeid(GfVec3f)) { + ApplyTransform(translates, + instanceIndices, t, + transforms.data()); + } else if (type == typeid(GfVec3d)) { + ApplyTransform(translates, + instanceIndices, t, + transforms.data()); + } else if (type == typeid(GfVec3h)) { + ApplyTransform(translates, + instanceIndices, t, + transforms.data()); + } + } + + if (rotates.count > 0 && rotates.values[0].IsArrayValued()) { + auto& type = rotates.values[0].GetElementTypeid(); + if (type == typeid(GfQuath)) { + ApplyTransform(rotates, instanceIndices, t, + transforms.data()); + } else if (type == typeid(GfQuatf)) { + ApplyTransform(rotates, instanceIndices, t, + transforms.data()); + } else if (type == typeid(GfQuatd)) { + ApplyTransform(rotates, instanceIndices, t, + transforms.data()); + } + } + + if (scales.count > 0 && scales.values[0].IsArrayValued()) { + auto& type = scales.values[0].GetElementTypeid(); + if (type == typeid(GfVec3f)) { + ApplyTransform(scales, instanceIndices, t, + transforms.data()); + } else if (type == typeid(GfVec3d)) { + ApplyTransform(scales, instanceIndices, t, + transforms.data()); + } else if (type == typeid(GfVec3h)) { + ApplyTransform(scales, instanceIndices, t, + transforms.data()); + } + } + + if (instanceXforms.count > 0 + && instanceXforms.values[0].IsArrayValued()) { + auto& type = instanceXforms.values[0].GetElementTypeid(); + if (type == typeid(GfMatrix4d)) { + ApplyTransform(instanceXforms, + instanceIndices, t, + transforms.data()); + } else if (type == typeid(GfMatrix4f)) { + ApplyTransform(instanceXforms, + instanceIndices, t, + transforms.data()); + } + } + } + + // If there is a parent instancer, continue to unroll + // the child instances across the parent; otherwise we're done. + if (GetParentId().IsEmpty()) { + return sa; + } + + HdInstancer* parentInstancer = GetDelegate()->GetRenderIndex().GetInstancer( + GetParentId()); + if (!TF_VERIFY(parentInstancer)) { + return sa; + } + auto cyclesParentInstancer = static_cast( + parentInstancer); + + // Multiply the instance samples against the parent instancer samples. + auto parentXf = cyclesParentInstancer->SampleInstanceTransforms(GetId()); + if (parentXf.count == 0 || parentXf.values[0].empty()) { + // No samples for parent instancer. + return sa; + } + // Move aside previously computed child xform samples to childXf. + HdTimeSampleArray childXf(sa); + // Merge sample times, taking the densest sampling. + AccumulateSampleTimes(parentXf, &sa); + // Apply parent xforms to the children. + for (size_t i = 0; i < sa.count; ++i) { + const float t = sa.times[i]; + // Resample transforms at the same time. + VtMatrix4dArray curParentXf = parentXf.Resample(t); + VtMatrix4dArray curChildXf = childXf.Resample(t); + // Multiply out each combination. + VtMatrix4dArray& result = sa.values[i]; + result.resize(curParentXf.size() * curChildXf.size()); + for (size_t j = 0; j < curParentXf.size(); ++j) { + for (size_t k = 0; k < curChildXf.size(); ++k) { + result[j * curChildXf.size() + k] = curChildXf[k] + * curParentXf[j]; + } + } + } + + return sa; +} + +PXR_NAMESPACE_CLOSE_SCOPE \ No newline at end of file diff --git a/plugin/hdCycles/instancer.h b/plugin/hdCycles/instancer.h new file mode 100644 index 00000000..2f7abbd3 --- /dev/null +++ b/plugin/hdCycles/instancer.h @@ -0,0 +1,62 @@ +// Copyright 2020 Tangent Animation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef HD_CYCLES_INSTANCER_H +#define HD_CYCLES_INSTANCER_H + +#include + +#include +#include +#include +#include +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +class HdSceneDelegate; + +/** + * @brief Properly computes instance transforms for time varying data + * Heavily inspired by ReadeonProRenderUSD's Instancer.cpp + * + */ +class HdCyclesInstancer : public HdInstancer { +public: + HdCyclesInstancer(HdSceneDelegate* delegate, SdfPath const& id, + SdfPath const& parentInstancerId) + : HdInstancer(delegate, id, parentInstancerId) + { + } + + VtMatrix4dArray ComputeTransforms(SdfPath const& prototypeId); + + HdTimeSampleArray + SampleInstanceTransforms(SdfPath const& prototypeId); + +private: + void Sync(); + + VtMatrix4dArray m_transform; + VtVec3fArray m_translate; + VtVec4fArray m_rotate; + VtVec3fArray m_scale; + + std::mutex m_syncMutex; +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // HD_CYCLES_INSTANCER_H diff --git a/plugin/hdCycles/light.cpp b/plugin/hdCycles/light.cpp new file mode 100644 index 00000000..e4a03f32 --- /dev/null +++ b/plugin/hdCycles/light.cpp @@ -0,0 +1,430 @@ +// Copyright 2020 Tangent Animation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "light.h" + +#include "renderParam.h" +#include "utils.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +HdCyclesLight::HdCyclesLight(SdfPath const& id, TfToken const& lightType, + HdCyclesRenderDelegate* a_renderDelegate) + : HdLight(id) + , m_cyclesLight(nullptr) + , m_cyclesShader(nullptr) + , m_hdLightType(lightType) + , m_backgroundNode(nullptr) + , m_backgroundTransform(nullptr) + , m_backgroundTexture(nullptr) + , m_blackbodyNode(nullptr) + , m_renderDelegate(a_renderDelegate) +{ + // Added to prevent fallback lights + // TODO: Is this the best solution... + if (id == SdfPath::EmptyPath()) + return; + + _CreateCyclesLight(m_renderDelegate->GetCyclesRenderParam()); +} + +HdCyclesLight::~HdCyclesLight() +{ + if (m_hdLightType == HdPrimTypeTokens->domeLight) { + m_renderDelegate->GetCyclesRenderParam()->SetBackgroundShader(nullptr); + m_renderDelegate->GetCyclesRenderParam()->Interrupt(); + } + + if (m_cyclesShader) { + m_renderDelegate->GetCyclesRenderParam()->RemoveShader(m_cyclesShader); + delete m_cyclesShader; + } + + if (m_cyclesLight) { + m_renderDelegate->GetCyclesRenderParam()->RemoveLight(m_cyclesLight); + delete m_cyclesLight; + } +} + +void +HdCyclesLight::Finalize(HdRenderParam* renderParam) +{ +} + +void +HdCyclesLight::_CreateCyclesLight(HdCyclesRenderParam* renderParam) +{ + ccl::Scene* scene = renderParam->GetCyclesScene(); + m_cyclesLight = new ccl::Light(); + + m_cyclesShader = new ccl::Shader(); + m_cyclesShader->graph = new ccl::ShaderGraph(); + + m_cyclesLight->shader = m_cyclesShader; + + if (m_hdLightType == HdPrimTypeTokens->domeLight) { + m_cyclesLight->type = ccl::LIGHT_BACKGROUND; + + m_backgroundNode = new ccl::BackgroundNode(); + m_backgroundNode->color = ccl::make_float3(0.0f, 0.0f, 0.0f); + + m_backgroundNode->strength = 1.0f; + m_cyclesShader->graph->add(m_backgroundNode); + + ccl::ShaderNode* out = m_cyclesShader->graph->output(); + m_cyclesShader->graph->connect(m_backgroundNode->output("Background"), + out->input("Surface")); + + renderParam->SetBackgroundShader(m_cyclesShader); + } else { + m_emissionNode = new ccl::EmissionNode(); + m_emissionNode->color = ccl::make_float3(1.0f, 1.0f, 1.0f); + m_emissionNode->strength = 1.0f; + m_cyclesShader->graph->add(m_emissionNode); + + ccl::ShaderNode* out = m_cyclesShader->graph->output(); + m_cyclesShader->graph->connect(m_emissionNode->output("Emission"), + out->input("Surface")); + + if (m_hdLightType == HdPrimTypeTokens->diskLight) { + m_cyclesLight->type = ccl::LIGHT_AREA; + m_cyclesLight->round = true; + + m_cyclesLight->size = 1.0f; + } else if (m_hdLightType == HdPrimTypeTokens->sphereLight) { + m_cyclesLight->type = ccl::LIGHT_POINT; + } else if (m_hdLightType == HdPrimTypeTokens->distantLight) { + m_cyclesLight->type = ccl::LIGHT_DISTANT; + } else if (m_hdLightType == HdPrimTypeTokens->rectLight) { + m_cyclesLight->type = ccl::LIGHT_AREA; + m_cyclesLight->round = false; + + m_cyclesLight->size = 1.0f; + } + } + + renderParam->AddLight(m_cyclesLight); + + renderParam->AddShader(m_cyclesShader); + + // TODO: Export these from blender + m_cyclesLight->use_diffuse = true; + m_cyclesLight->use_glossy = true; + m_cyclesLight->use_transmission = true; + m_cyclesLight->use_scatter = true; + m_cyclesLight->cast_shadow = true; + m_cyclesLight->use_mis = true; + m_cyclesLight->max_bounces = 1024; + + // TODO: Get with usdCycles Schema. + //m_cyclesLight->samples = 1024; + + //TODO: Add support for random_id + //m_cyclesLight->random_id = ... + + m_cyclesShader->tag_update(scene); + m_cyclesLight->tag_update(scene); +} + +void +HdCyclesLight::_SetTransform(const ccl::Transform& a_transform) +{ + if (!m_cyclesLight) + return; + + m_cyclesLight->tfm = a_transform; + + if (m_cyclesLight->type == ccl::LIGHT_BACKGROUND) { + if (m_backgroundTransform) + m_backgroundTransform->ob_tfm = a_transform; + } else { + // Set the area light transforms + m_cyclesLight->axisu = ccl::transform_get_column(&a_transform, 0); + m_cyclesLight->axisv = ccl::transform_get_column(&a_transform, 1); + m_cyclesLight->co = ccl::transform_get_column(&a_transform, 3); + m_cyclesLight->dir = -ccl::transform_get_column(&a_transform, 2); + } +} + +void +HdCyclesLight::Sync(HdSceneDelegate* sceneDelegate, HdRenderParam* renderParam, + HdDirtyBits* dirtyBits) +{ + SdfPath id = GetId(); + + HdCyclesRenderParam* param = (HdCyclesRenderParam*)renderParam; + + ccl::Scene* scene = param->GetCyclesScene(); + + bool light_updated = false; + + if (*dirtyBits & HdLight::DirtyParams) { + light_updated = true; + + // -- Common params + + // Color + VtValue lightColor + = sceneDelegate->GetLightParamValue(id, HdLightTokens->color); + if (lightColor.IsHolding()) { + GfVec3f v = lightColor.UncheckedGet(); + m_cyclesLight->strength = ccl::make_float3(v[0], v[1], v[2]); + } + + // Normalize + VtValue normalize + = sceneDelegate->GetLightParamValue(id, HdLightTokens->normalize); + if (normalize.IsHolding()) { + m_normalize = normalize.UncheckedGet(); + } + + + // Exposure + VtValue exposureValue + = sceneDelegate->GetLightParamValue(id, HdLightTokens->exposure); + + float exposure = 1.0f; + if (exposureValue.IsHolding()) { + exposure = powf(2.0f, exposureValue.UncheckedGet()); + } + + // Intensity + VtValue intensity + = sceneDelegate->GetLightParamValue(id, HdLightTokens->intensity); + if (intensity.IsHolding()) { + m_finalIntensity = intensity.UncheckedGet() * exposure; + m_cyclesLight->strength *= m_finalIntensity; + } + + // TODO: + // These two params have no direct mapping. Kept for future implementation + + // // Diffuse + // VtValue diffuse + // = sceneDelegate->GetLightParamValue(id, HdLightTokens->diffuse); + // if (diffuse.IsHolding()) { + // m_cyclesLight->use_diffuse = (diffuse.UncheckedGet() + // == 1.0f); + // } + + // // Specular + // VtValue specular + // = sceneDelegate->GetLightParamValue(id, HdLightTokens->specular); + // if (specular.IsHolding()) { + // m_cyclesLight->use_glossy = (specular.UncheckedGet() + // == 1.0f); + // } + + // Enable Temperature + VtValue enableTemperature = sceneDelegate->GetLightParamValue( + id, HdLightTokens->enableColorTemperature); + if (enableTemperature.IsHolding()) { + m_useTemperature = enableTemperature.UncheckedGet(); + } + + if (m_useTemperature) { + // Get Temperature + VtValue temperature = sceneDelegate->GetLightParamValue( + id, HdLightTokens->colorTemperature); + if (temperature.IsHolding()) { + m_temperature = temperature.UncheckedGet(); + + // Add temperature node + if (!m_blackbodyNode) { + m_blackbodyNode = new ccl::BlackbodyNode(); + + m_cyclesShader->graph->add(m_blackbodyNode); + + m_cyclesShader->graph->connect( + m_blackbodyNode->output("Color"), + m_emissionNode->input("Color")); + } + + m_blackbodyNode->temperature = m_temperature; + m_cyclesShader->tag_update(scene); + } + } + + if (m_hdLightType == HdPrimTypeTokens->rectLight) { + m_cyclesLight->axisu + = ccl::transform_get_column(&m_cyclesLight->tfm, 0); + m_cyclesLight->axisv + = ccl::transform_get_column(&m_cyclesLight->tfm, 1); + + VtValue width + = sceneDelegate->GetLightParamValue(id, HdLightTokens->width); + if (width.IsHolding()) + m_cyclesLight->sizeu = width.UncheckedGet(); + + VtValue height + = sceneDelegate->GetLightParamValue(id, HdLightTokens->height); + if (height.IsHolding()) + m_cyclesLight->sizev = height.UncheckedGet(); + } + + if (m_hdLightType == HdPrimTypeTokens->diskLight) { + // TODO: + // Disk Lights cannot be ovals, but Blender can export oval lights... + // This will be fixed in the great light transition when the new light API + // is released + + // VtValue width = sceneDelegate->GetLightParamValue(id, HdLightTokens->width); + // if(width.IsHolding()) + // m_cyclesLight->sizeu = width.UncheckedGet(); + + // VtValue height = sceneDelegate->GetLightParamValue(id, HdLightTokens->height); + // if(height.IsHolding()) + // m_cyclesLight->sizev = height.UncheckedGet(); + + m_cyclesLight->axisu + = ccl::transform_get_column(&m_cyclesLight->tfm, 0); + m_cyclesLight->axisv + = ccl::transform_get_column(&m_cyclesLight->tfm, 1); + + VtValue radius + = sceneDelegate->GetLightParamValue(id, HdLightTokens->radius); + if (radius.IsHolding()) { + m_cyclesLight->sizeu = radius.UncheckedGet() * 2.0f; + m_cyclesLight->sizev = radius.UncheckedGet() * 2.0f; + } + } + + if (m_hdLightType == HdPrimTypeTokens->cylinderLight) { + // TODO: Implement + // Cycles has no concept of cylinder lights. + } + + if (m_hdLightType == HdPrimTypeTokens->sphereLight) { + VtValue radius + = sceneDelegate->GetLightParamValue(id, HdLightTokens->radius); + if (radius.IsHolding()) + m_cyclesLight->size = radius.UncheckedGet(); + + //Spot shaping + VtValue shapingConeAngle = sceneDelegate->GetLightParamValue( + id, HdLightTokens->shapingConeAngle); + if (shapingConeAngle.IsHolding()) { + m_cyclesLight->spot_angle + = shapingConeAngle.UncheckedGet() + * ((float)M_PI / 180.0f) * 2.0f; + m_cyclesLight->type = ccl::LIGHT_SPOT; + } + + VtValue shapingConeSoftness = sceneDelegate->GetLightParamValue( + id, HdLightTokens->shapingConeSoftness); + if (shapingConeSoftness.IsHolding()) { + m_cyclesLight->spot_smooth + = shapingConeSoftness.UncheckedGet(); + m_cyclesLight->type = ccl::LIGHT_SPOT; + } + } + + if (m_hdLightType == HdPrimTypeTokens->distantLight) { + // TODO: Test this + VtValue angle + = sceneDelegate->GetLightParamValue(id, HdLightTokens->angle); + if (angle.IsHolding()) + m_cyclesLight->angle = angle.UncheckedGet(); + } + + if (m_hdLightType == HdPrimTypeTokens->domeLight) { + m_backgroundNode->color = m_cyclesLight->strength; + + m_backgroundNode->strength = m_finalIntensity; + VtValue textureFile + = sceneDelegate->GetLightParamValue(id, + HdLightTokens->textureFile); + + if (textureFile.IsHolding()) { + SdfAssetPath ap = textureFile.UncheckedGet(); + std::string filepath = ap.GetResolvedPath(); + + // TODO: Prevent this string comparison + if (filepath != "" && m_backgroundFilePath != filepath) { + if (m_backgroundTexture == nullptr) { + // Add environment texture nodes + m_backgroundTransform = new ccl::TextureCoordinateNode(); + m_backgroundTransform->use_transform = true; + m_backgroundTransform->ob_tfm = m_cyclesLight->tfm; + + m_cyclesShader->graph->add(m_backgroundTransform); + + m_backgroundTexture = new ccl::EnvironmentTextureNode(); + + m_cyclesShader->graph->add(m_backgroundTexture); + + m_cyclesShader->graph->connect( + m_backgroundTransform->output("Object"), + m_backgroundTexture->input("Vector")); + m_cyclesShader->graph->connect( + m_backgroundTexture->output("Color"), + m_backgroundNode->input("Color")); + } + + m_backgroundTexture->filename = filepath; + m_backgroundFilePath = filepath; + + m_cyclesShader->tag_update(scene); + } + } + } + } + + // TODO: Light is_enabled doesn't seem to have any effect + /* if (*dirtyBits & HdChangeTracker::DirtyVisibility) { + light_updated = true; + m_cyclesLight->is_enabled = sceneDelegate->GetVisible(id); + }*/ + + if (*dirtyBits & HdLight::DirtyTransform) { + light_updated = true; + _SetTransform(HdCyclesExtractTransform(sceneDelegate, id)); + } + + if (light_updated) { + m_cyclesShader->tag_update(param->GetCyclesScene()); + m_cyclesLight->tag_update(scene); + + param->Interrupt(); + } + + *dirtyBits = HdChangeTracker::Clean; +} + +HdDirtyBits +HdCyclesLight::GetInitialDirtyBitsMask() const +{ + return HdChangeTracker::AllDirty | HdLight::DirtyParams + | HdLight::DirtyTransform; +} + +bool +HdCyclesLight::IsValid() const +{ + return true; +} + +PXR_NAMESPACE_CLOSE_SCOPE \ No newline at end of file diff --git a/plugin/hdCycles/light.h b/plugin/hdCycles/light.h new file mode 100644 index 00000000..6ea1fd05 --- /dev/null +++ b/plugin/hdCycles/light.h @@ -0,0 +1,141 @@ +// Copyright 2020 Tangent Animation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef HD_CYCLES_LIGHT_H +#define HD_CYCLES_LIGHT_H + +#include "api.h" + +#include "renderDelegate.h" + +#include + +#include +#include + +namespace ccl { +class Light; +class Shader; +class Scene; +class BackgroundNode; +class TextureCoordinateNode; +class EnvironmentTextureNode; +class EmissionNode; +class BlackbodyNode; +} // namespace ccl + +PXR_NAMESPACE_OPEN_SCOPE + +class HdSceneDelegate; +class HdCyclesRenderParam; +class HdCyclesRenderDelegate; + +/** + * @brief Cycles Light Sprim mapped to Cycles Light + * More work will be done here when the new light node network schema is released. + * DomeLights/WorldMaterial is currently pretty hard coded, this will also be + * unecessary with the new changes. + * + */ +class HdCyclesLight final : public HdLight { +public: + /** + * @brief Construct a new HdCycles Light object + * + * @param id Path to the Light Primitive + * @param lightType Type of light to create + * @param a_renderDelegate Associated Render Delegate + * as a prototype + */ + HdCyclesLight(SdfPath const& id, TfToken const& lightType, + HdCyclesRenderDelegate* a_renderDelegate); + + /** + * @brief Destroy the HdCycles Light object + * + */ + virtual ~HdCyclesLight(); + + /** + * @brief Pull invalidated light data and prepare/update the core Cycles + * representation. + * + * This must be thread safe. + * + * @param sceneDelegate The data source for the light + * @param renderParam State + * @param dirtyBits Which bits of scene data has changed + */ + void Sync(HdSceneDelegate* sceneDelegate, HdRenderParam* renderParam, + HdDirtyBits* dirtyBits) override; + + /** + * @brief Inform the scene graph which state needs to be downloaded in + * the first Sync() call + * + * @return The initial dirty state this light wants to query + */ + HdDirtyBits GetInitialDirtyBitsMask() const override; + + /** + * @return Return true if this light is valid. + */ + bool IsValid() const; + + /** + * @brief TODO: Implement + */ + void Finalize(HdRenderParam* renderParam) override; + +private: + /** + * @brief Create the cycles light representation + * + * @param scene Cycles scene to add light to + * @param transform Initial transform for object + * @return New allocated pointer to ccl::Light + */ + void _CreateCyclesLight(HdCyclesRenderParam* renderParam); + + /** + * @brief Set transform of light and associate light types + * + * @param a_transform Transform to use + */ + void _SetTransform(const ccl::Transform& a_transform); + + const TfToken m_hdLightType; + ccl::Light* m_cyclesLight; + ccl::Shader* m_cyclesShader; + ccl::EmissionNode* m_emissionNode; + + // Background light specifics + ccl::BackgroundNode* m_backgroundNode; + ccl::TextureCoordinateNode* m_backgroundTransform; + ccl::EnvironmentTextureNode* m_backgroundTexture; + ccl::BlackbodyNode* m_blackbodyNode; + std::string m_backgroundFilePath; + + bool m_normalize; + bool m_useTemperature; + float m_temperature; + + HdCyclesRenderDelegate* m_renderDelegate; + + float m_finalIntensity; +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // HD_CYCLES_LIGHT_H diff --git a/plugin/hdCycles/material.cpp b/plugin/hdCycles/material.cpp new file mode 100644 index 00000000..81976227 --- /dev/null +++ b/plugin/hdCycles/material.cpp @@ -0,0 +1,634 @@ +// Copyright 2020 Tangent Animation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "material.h" + +#include "renderDelegate.h" +#include "renderParam.h" +#include "utils.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +// clang-format off +TF_DEFINE_PRIVATE_TOKENS(_tokens, + (PxrDisplace) + (bxdf) + (OSL) + (diffuseColor) + (roughness) + (metallic) + (specular) + (file) + (varname) + (Color) + (rgb) + (r) + (g) + (b) + (st) + (Vector) + (base_color) + (result) + (UV) +); +// clang-format on + +TF_DEFINE_PUBLIC_TOKENS(HdCyclesMaterialTerminalTokens, + HD_CYCLES_MATERIAL_TERMINAL_TOKENS); + +TF_MAKE_STATIC_DATA(NdrTokenVec, _sourceTypes) +{ + *_sourceTypes = { TfToken("OSL"), TfToken("cycles") }; +} + +TfTokenVector const& +HdCyclesMaterial::GetShaderSourceTypes() +{ + return *_sourceTypes; +} + +HdCyclesMaterial::HdCyclesMaterial(SdfPath const& id, + HdCyclesRenderDelegate* a_renderDelegate) + : HdMaterial(id) + , m_shader(nullptr) + , m_shaderGraph(nullptr) + , m_renderDelegate(a_renderDelegate) +{ + m_shader = new ccl::Shader(); + m_shaderGraph = new ccl::ShaderGraph(); + m_shader->graph = m_shaderGraph; + if (m_renderDelegate) + m_renderDelegate->GetCyclesRenderParam()->AddShader(m_shader); +} + +HdCyclesMaterial::~HdCyclesMaterial() +{ + if (m_shader) { + m_renderDelegate->GetCyclesRenderParam()->RemoveShader(m_shader); + delete m_shader; + } +} + +// TODO: These conversion functions will be moved to a more generic +// Material Adapter... + +ccl::ShaderNode* +matConvertUSDPrimvarReader(HdMaterialNode& usd_node, + ccl::ShaderGraph* cycles_shader_graph) +{ + ccl::UVMapNode* uvmap = new ccl::UVMapNode(); + uvmap->attribute = ccl::ustring("st"); + + for (std::pair params : usd_node.parameters) { + if (params.first == _tokens->varname) { + if (params.second.IsHolding()) { + uvmap->attribute = ccl::ustring( + params.second.Get().GetString().c_str()); + } + } + } + cycles_shader_graph->add(uvmap); + return uvmap; +} + +ccl::ShaderNode* +matConvertUSDUVTexture(HdMaterialNode& usd_node, + ccl::ShaderGraph* cycles_shader_graph) +{ + ccl::ImageTextureNode* imageTexture = new ccl::ImageTextureNode(); + + for (std::pair params : usd_node.parameters) { + if (params.first == _tokens->file) { + if (params.second.IsHolding()) { + imageTexture->filename = ccl::ustring( + params.second.Get().GetResolvedPath().c_str()); + } + } + } + cycles_shader_graph->add(imageTexture); + return imageTexture; +} + +ccl::ShaderNode* +matConvertUSDPreviewSurface(HdMaterialNode& usd_node, + ccl::ShaderGraph* cycles_shader_graph) +{ + ccl::PrincipledBsdfNode* principled = new ccl::PrincipledBsdfNode(); + principled->base_color = ccl::make_float3(1.0f, 1.0f, 1.0f); + + // Convert params + for (std::pair params : usd_node.parameters) { + if (params.first == _tokens->diffuseColor) { + if (params.second.IsHolding()) { + principled->base_color = vec3f_to_float3( + params.second.UncheckedGet()); + } else if (params.second.IsHolding()) { + principled->base_color = vec4f_to_float3( + params.second.UncheckedGet()); + } + + } else if (params.first == _tokens->roughness) { + if (params.second.IsHolding()) { + principled->roughness = params.second.UncheckedGet(); + } + + } else if (params.first == _tokens->metallic) { + if (params.second.IsHolding()) { + principled->metallic = params.second.UncheckedGet(); + } + + } else if (params.first == _tokens->specular) { + if (params.second.IsHolding()) { + principled->specular = params.second.UncheckedGet(); + } + } + } + + cycles_shader_graph->add(principled); + return principled; +} + +TfToken +socketConverter(TfToken a_token) +{ + // TODO: Add check if preview surface + if (a_token == _tokens->rgb || a_token == _tokens->r + || a_token == _tokens->g || a_token == _tokens->b) { + return _tokens->Color; + } else if (a_token == _tokens->st) { + return _tokens->Vector; + } else if (a_token == _tokens->diffuseColor) { + return _tokens->base_color; + } else if (a_token == _tokens->result) { + return _tokens->UV; + } + + return a_token; +} + +ccl::ShaderNode* +convertCyclesNode(HdMaterialNode& usd_node, + ccl::ShaderGraph* cycles_shader_graph) +{ + // Get Cycles node name + std::vector node_id_parts + = TfStringSplit(usd_node.identifier.GetString().c_str(), ":"); + + if (node_id_parts.size() != 2) { + // illegal node name + TF_WARN("MATERIAL ERROR: Illegal cycles node name: %s", + usd_node.identifier.GetString().c_str()); + return nullptr; + } + + ccl::ustring cycles_node_name = ccl::ustring(node_id_parts[1].c_str()); + + // Find dynamic node type + const ccl::NodeType* node_type = ccl::NodeType::find(cycles_node_name); + if (!node_type) { + TF_WARN("OMATERIAL ERRR: Could not find cycles node of type: %s", + usd_node.identifier.GetString().c_str()); + return nullptr; + } + + // Dynamic cycles node object + ccl::ShaderNode* cyclesNode = (ccl::ShaderNode*)node_type->create( + node_type); + + cycles_shader_graph->add(cyclesNode); + + // Convert cycles params + for (std::pair params : usd_node.parameters) { + // Loop through all cycles inputs for matching usd shade param + for (const ccl::SocketType& socket : cyclesNode->type->inputs) { + // Early out if usd shade param doesn't match input name + if (!ccl::string_iequals(params.first.GetText(), + socket.name.string())) + continue; + + // Ensure param has value + if (params.second.IsEmpty()) { + continue; + } + + // Early out for invalid cycles types and flags + if (socket.type == ccl::SocketType::CLOSURE + || socket.type == ccl::SocketType::UNDEFINED) + continue; + if (socket.flags & ccl::SocketType::INTERNAL) + continue; + + // TODO: Why do we do this? + if (cycles_node_name == "normal_map") + if (ccl::string_iequals("attribute", socket.name.string())) + continue; + + switch (socket.type) { + case ccl::SocketType::INT: { + cyclesNode->set(socket, params.second.Get()); + break; + } + + case ccl::SocketType::FLOAT: { + cyclesNode->set(socket, params.second.Get()); + break; + } + + case ccl::SocketType::FLOAT_ARRAY: { + cyclesNode->set(socket, params.second.Get()); + + if (params.second.IsHolding()) { + ccl::array val; + VtFloatArray floatArray = params.second.Get(); + val.resize(floatArray.size()); + for (size_t i = 0; i < val.size(); i++) { + val[i] = floatArray[i]; + } + cyclesNode->set(socket, val); + } + break; + } + + case ccl::SocketType::ENUM: { + if (params.second.IsHolding()) { + cyclesNode->set( + socket, (*socket.enum_values)[params.second.Get()] + .string() + .c_str()); + } else if (params.second.IsHolding()) { + cyclesNode->set(socket, + params.second.Get().c_str()); + } + } break; + + case ccl::SocketType::STRING: { + std::string val; + if (params.second.IsHolding()) { +// TODO: +// USD Issue-916 means that we cant resolve relative UDIM +// paths. This is fixed in 20.08. When we upgrade to that +// (And when houdini does). We can just use resolved path. +// For now, if the string has a UDIM in it, don't resolve. +// (This means relative UDIMs won't work) +#ifdef USD_HAS_UDIM_RESOLVE_FIX + val = std::string(params.second.Get() + .GetResolvedPath() + .c_str()); +#else + std::string raw_path = std::string( + params.second.Get().GetAssetPath().c_str()); + if (HdCyclesPathIsUDIM(raw_path)) { + val = raw_path; + } else { + val = std::string(params.second.Get() + .GetResolvedPath() + .c_str()); + } +#endif + } else if (params.second.IsHolding()) { + val = params.second.Get().GetString().c_str(); + if (val.length() > 0) + val = pxr::TfMakeValidIdentifier(val); + } else if (params.second.IsHolding()) { + val = std::string(params.second.Get().c_str()); + if (val.length() > 0) + val = pxr::TfMakeValidIdentifier(val); + } + + cyclesNode->set(socket, val.c_str()); + } break; + + case ccl::SocketType::COLOR: + case ccl::SocketType::VECTOR: + case ccl::SocketType::POINT: + case ccl::SocketType::NORMAL: { + if (params.second.IsHolding()) { + cyclesNode->set(socket, vec4f_to_float3( + params.second.Get())); + } else if (params.second.IsHolding()) { + cyclesNode->set(socket, vec3f_to_float3( + params.second.Get())); + } + } break; + + case ccl::SocketType::COLOR_ARRAY: + case ccl::SocketType::VECTOR_ARRAY: + case ccl::SocketType::POINT_ARRAY: + case ccl::SocketType::NORMAL_ARRAY: { + if (params.second.IsHolding()) { + ccl::array val; + VtVec4fArray colarray = params.second.Get(); + val.resize(colarray.size()); + for (size_t i = 0; i < val.size(); i++) { + val[i] = vec4f_to_float3(colarray[i]); + } + cyclesNode->set(socket, val); + } else if (params.second.IsHolding()) { + ccl::array val; + VtVec3fArray colarray = params.second.Get(); + val.resize(colarray.size()); + for (size_t i = 0; i < val.size(); i++) { + val[i] = vec3f_to_float3(colarray[i]); + } + cyclesNode->set(socket, val); + } + } break; + } + } + } + + // TODO: Check proper type + if (cycles_node_name == "image_texture") { + ccl::ImageTextureNode* tex = (ccl::ImageTextureNode*)cyclesNode; + + // Handle udim tiles + if (HdCyclesPathIsUDIM(tex->filename.string())) { + HdCyclesParseUDIMS(tex->filename.string(), tex->tiles); + } + } + + return cyclesNode; +} + +// TODO: This should be rewritten to better handle preview surface and cycles materials. +// Pretty sure it only works because the network map has the cycles material first in a list +static bool +GetMaterialNetwork(TfToken const& terminal, HdSceneDelegate* delegate, + HdMaterialNetworkMap const& networkMap, + HdCyclesRenderParam const& renderParam, + HdMaterialNetwork const** out_network, + ccl::ShaderGraph* graph) +{ + std::map> conversionMap; + + ccl::ShaderNode* output_node = nullptr; + + if (terminal == HdCyclesMaterialTerminalTokens->surface) { + // Early out for already linked surface graph + if (graph->output()->input("Surface")->link) + return false; + } else if (terminal == HdCyclesMaterialTerminalTokens->displacement) { + // Early out for already linked displacement graph + if (graph->output()->input("Displacement")->link) + return false; + } + + for (std::pair net : networkMap.map) { + if (net.first != terminal) + continue; + // Convert material nodes + for (HdMaterialNode& node : net.second.nodes) { + ccl::ShaderNode* cycles_node = nullptr; + + if (node.identifier == UsdImagingTokens->UsdPreviewSurface) { + cycles_node = matConvertUSDPreviewSurface(node, graph); + } else if (node.identifier == UsdImagingTokens->UsdUVTexture) { + cycles_node = matConvertUSDUVTexture(node, graph); + } else if (node.identifier + == UsdImagingTokens->UsdPrimvarReader_float2) { + cycles_node = matConvertUSDPrimvarReader(node, graph); + } else { + cycles_node = convertCyclesNode(node, graph); + } + + if (cycles_node != nullptr) { + conversionMap.insert( + std::pair>( + node.path, std::make_pair(&node, cycles_node))); + } + + for (const pxr::SdfPath& tPath : networkMap.terminals) { + if (node.path == tPath) { + output_node = cycles_node; + + if (terminal == HdCyclesMaterialTerminalTokens->surface) { + if (cycles_node->output("BSDF") != NULL) { + graph->connect(cycles_node->output("BSDF"), + graph->output()->input("Surface")); + + } else if (cycles_node->output("Closure") != NULL) { + graph->connect(cycles_node->output("Closure"), + graph->output()->input("Surface")); + + } else if (cycles_node->output("Emission") != NULL) { + graph->connect(cycles_node->output("Emission"), + graph->output()->input("Surface")); + } + } + if (terminal + == HdCyclesMaterialTerminalTokens->displacement) { + if (cycles_node->output("Displacement") != NULL) { + graph->connect(cycles_node->output("Displacement"), + graph->output()->input( + "Displacement")); + } + } + } + } + } + + // Link material nodes + for (const HdMaterialRelationship& matRel : net.second.relationships) { + ccl::ShaderNode* tonode + = (ccl::ShaderNode*)conversionMap[matRel.outputId].second; + ccl::ShaderNode* fromnode + = (ccl::ShaderNode*)conversionMap[matRel.inputId].second; + + HdMaterialNode* hd_tonode = conversionMap[matRel.outputId].first; + HdMaterialNode* hd_fromnode = conversionMap[matRel.inputId].first; + + ccl::ShaderOutput* output = NULL; + ccl::ShaderInput* input = NULL; + + // TODO: Handle this check better + TfToken cInputName = matRel.inputName; + if (hd_fromnode->identifier.GetString().find("cycles:") + == std::string::npos) + cInputName = socketConverter(cInputName); + TfToken cOutputName = matRel.outputName; + if (hd_tonode->identifier.GetString().find("cycles:") + == std::string::npos) + cOutputName = socketConverter(cOutputName); + + if (tonode == nullptr) { + TF_WARN("MATERIAL ERROR: Could not link, tonode was null: %s", + matRel.outputId.GetString().c_str()); + continue; + } else if (fromnode == nullptr) { + TF_WARN("MATERIAL ERROR: Could not link, fromnode was null: %s", + matRel.inputId.GetString().c_str()); + continue; + } + + if (fromnode) { + for (ccl::ShaderOutput* out : fromnode->outputs) { + if (!out) + continue; + + if (ccl::string_iequals(out->socket_type.name.string(), + cInputName)) { + output = out; + break; + } + } + } + + if (tonode) { + for (ccl::ShaderInput* in : tonode->inputs) { + if (!in) + continue; + if (ccl::string_iequals(in->socket_type.name.string(), + cOutputName)) { + input = in; + break; + } + } + } + + if (output && input) { + if (input->link) { + continue; + } + graph->connect(output, input); + } + } + + // TODO: This is to allow retroactive material_output node support + // As this becomes phased out, we can remove this. + if (output_node != nullptr) { + if (graph->output()->input("Surface")->link == nullptr) { + if (output_node->input("Surface") != NULL) { + if (output_node->name == "output") { + if (output_node->input("Surface")->link) { + if (terminal + == HdCyclesMaterialTerminalTokens->surface) { + graph->connect( + output_node->input("Surface")->link, + graph->output()->input("Surface")); + } + } + } + } + } + } + } + + return true; +} + +void +HdCyclesMaterial::Sync(HdSceneDelegate* sceneDelegate, + HdRenderParam* renderParam, HdDirtyBits* dirtyBits) +{ + auto cyclesRenderParam = static_cast(renderParam); + HdCyclesRenderParam* param = (HdCyclesRenderParam*)renderParam; + + param->GetCyclesScene()->mutex.lock(); + + HdDirtyBits bits = *dirtyBits; + + if (*dirtyBits & HdMaterial::DirtyResource) { + VtValue vtMat = sceneDelegate->GetMaterialResource(GetId()); + + if (vtMat.IsHolding()) { + if (m_shaderGraph) { + delete m_shaderGraph; + m_shaderGraph = new ccl::ShaderGraph(); + } + + auto& networkMap = vtMat.UncheckedGet(); + + HdMaterialNetwork const* surface = nullptr; + HdMaterialNetwork const* displacement = nullptr; + + bool foundNetwork = false; + + if (GetMaterialNetwork(HdCyclesMaterialTerminalTokens->surface, + sceneDelegate, networkMap, + *cyclesRenderParam, &surface, + m_shaderGraph)) { + if (m_shader && m_shaderGraph) { + foundNetwork = true; + } + } + + if (GetMaterialNetwork(HdCyclesMaterialTerminalTokens->displacement, + sceneDelegate, networkMap, + *cyclesRenderParam, &displacement, + m_shaderGraph)) { + if (m_shader && m_shaderGraph) { + foundNetwork = true; + } + } + + if (foundNetwork) { + m_shader->graph = m_shaderGraph; + + m_shader->tag_update(param->GetCyclesScene()); + m_shader->tag_used(param->GetCyclesScene()); + param->Interrupt(); + } else { + TF_CODING_WARNING("Material type not supported"); + } + } + } + + param->GetCyclesScene()->mutex.unlock(); + + *dirtyBits = Clean; +} + +ccl::Shader* +HdCyclesMaterial::GetCyclesShader() const +{ + return m_shader; +} + +HdDirtyBits +HdCyclesMaterial::GetInitialDirtyBitsMask() const +{ + return HdChangeTracker::AllDirty; +} + +void +HdCyclesMaterial::Reload() +{ +} + +bool +HdCyclesMaterial::IsValid() const +{ + return true; +} + +PXR_NAMESPACE_CLOSE_SCOPE \ No newline at end of file diff --git a/plugin/hdCycles/material.h b/plugin/hdCycles/material.h new file mode 100644 index 00000000..b0af8ab7 --- /dev/null +++ b/plugin/hdCycles/material.h @@ -0,0 +1,130 @@ +// Copyright 2020 Tangent Animation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef HD_CYCLES_MATERIAL_H +#define HD_CYCLES_MATERIAL_H + +#include "api.h" + +#include +#include + +namespace ccl { +class Object; +class Shader; +class ShaderGraph; +} // namespace ccl + +PXR_NAMESPACE_OPEN_SCOPE + +// Terminal keys used in material networks. + +// clang-format off +#define HD_CYCLES_MATERIAL_TERMINAL_TOKENS \ + ((surface, "surface")) \ + ((cyclesSurface, "cycles:surface")) \ + ((displacement, "displacement")) \ + ((cyclesDisplacement, "cycles:displacement")) \ + ((volume, "volume")) \ + ((cyclesVolume, "cycles:volume")) + +TF_DECLARE_PUBLIC_TOKENS(HdCyclesMaterialTerminalTokens, + HDCYCLES_API, + HD_CYCLES_MATERIAL_TERMINAL_TOKENS +); +// clang-format on + +class HdSceneDelegate; +class HdCyclesRenderDelegate; + +/** + * @brief HdCycles Material Sprim mapped to Cycles Material + * + */ +class HdCyclesMaterial final : public HdMaterial { +public: + /** + * @brief Construct a new HdCycles Material + * + * @param id Path to the Material + */ + HDCYCLES_API + HdCyclesMaterial(SdfPath const& id, + HdCyclesRenderDelegate* a_renderDelegate); + + /** + * @brief Destroy the HdCycles Material + * + */ + virtual ~HdCyclesMaterial(); + + /** + * @brief Pull invalidated material data and prepare/update the core Cycles + * representation. + * + * This must be thread safe. + * + * @param sceneDelegate The data source for the material + * @param renderParam State + * @param dirtyBits Which bits of scene data has changed + */ + HDCYCLES_API + void Sync(HdSceneDelegate* sceneDelegate, HdRenderParam* renderParam, + HdDirtyBits* dirtyBits) override; + + /** + * @brief Inform the scene graph which state needs to be downloaded in + * the first Sync() call + * + * @return The initial dirty state this material wants to query + */ + HDCYCLES_API + HdDirtyBits GetInitialDirtyBitsMask() const override; + + /** + * @brief Causes the shader to be reloaded + * + */ + HDCYCLES_API + void Reload() override; + + /** + * @return Return true if this material is valid + */ + bool IsValid() const; + + /** + * @brief Return the static list of tokens supported. + * + */ + static TfTokenVector const& GetShaderSourceTypes(); + + /** + * @brief Accessor for material's associated cycles shader + * + * @return ccl::Shader* cycles shader + */ + HDCYCLES_API + ccl::Shader* GetCyclesShader() const; + +protected: + ccl::Shader* m_shader; + ccl::ShaderGraph* m_shaderGraph; + + HdCyclesRenderDelegate* m_renderDelegate; +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // HD_CYCLES_MATERIAL_H diff --git a/plugin/hdCycles/mesh.cpp b/plugin/hdCycles/mesh.cpp new file mode 100644 index 00000000..1bfea9ad --- /dev/null +++ b/plugin/hdCycles/mesh.cpp @@ -0,0 +1,936 @@ +// Copyright 2020 Tangent Animation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "mesh.h" + +#include "config.h" +#include "instancer.h" +#include "material.h" +#include "renderDelegate.h" +#include "renderParam.h" + +#include "Mikktspace/mikktspace.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +// clang-format off +TF_DEFINE_PRIVATE_TOKENS(_tokens, + (st) + (uv) +); +// clang-format on + +HdCyclesMesh::HdCyclesMesh(SdfPath const& id, SdfPath const& instancerId, + HdCyclesRenderDelegate* a_renderDelegate) + : HdMesh(id, instancerId) + , m_renderDelegate(a_renderDelegate) + , m_cyclesMesh(nullptr) + , m_cyclesObject(nullptr) + , m_hasVertexColors(false) +{ + static const HdCyclesConfig& config = HdCyclesConfig::GetInstance(); + m_subdivEnabled = config.enable_subdivision; + m_dicingRate = config.subdivision_dicing_rate; + m_maxSubdivision = config.max_subdivision; + m_useMotionBlur = config.enable_motion_blur; + + m_cyclesObject = _CreateCyclesObject(); + + m_cyclesMesh = _CreateCyclesMesh(); + + m_numTransformSamples = HD_CYCLES_MOTION_STEPS; + + if (m_useMotionBlur) { + // TODO: Get this from usdCycles schema + //m_motionSteps = config.motion_steps; + m_motionSteps = m_numTransformSamples; + + // TODO: Needed when we properly handle motion_verts + //m_cyclesMesh->motion_steps = m_motionSteps; + //m_cyclesMesh->use_motion_blur = m_useMotionBlur; + } + + m_cyclesObject->geometry = m_cyclesMesh; + + m_renderDelegate->GetCyclesRenderParam()->AddGeometry(m_cyclesMesh); + m_renderDelegate->GetCyclesRenderParam()->AddObject(m_cyclesObject); +} + +HdCyclesMesh::~HdCyclesMesh() +{ + if (m_cyclesMesh) { + m_renderDelegate->GetCyclesRenderParam()->RemoveMesh(m_cyclesMesh); + delete m_cyclesMesh; + } + + if (m_cyclesObject) { + m_renderDelegate->GetCyclesRenderParam()->RemoveObject(m_cyclesObject); + delete m_cyclesObject; + } + + if (m_cyclesInstances.size() > 0) { + for (auto instance : m_cyclesInstances) { + if (instance) { + m_renderDelegate->GetCyclesRenderParam()->RemoveObject( + instance); + delete instance; + } + } + } +} + +HdDirtyBits +HdCyclesMesh::GetInitialDirtyBitsMask() const +{ + return HdChangeTracker::Clean | HdChangeTracker::DirtyPoints + | HdChangeTracker::DirtyTransform | HdChangeTracker::DirtyPrimvar + | HdChangeTracker::DirtyTopology | HdChangeTracker::DirtyVisibility + | HdChangeTracker::DirtyMaterialId | HdChangeTracker::DirtySubdivTags + | HdChangeTracker::DirtyDisplayStyle + | HdChangeTracker::DirtyDoubleSided; +} +template +bool +HdCyclesMesh::GetPrimvarData(TfToken const& name, + HdSceneDelegate* sceneDelegate, + std::map + primvarDescsPerInterpolation, + VtArray& out_data, VtIntArray& out_indices) +{ + out_data.clear(); + out_indices.clear(); + + for (auto& primvarDescsEntry : primvarDescsPerInterpolation) { + for (auto& pv : primvarDescsEntry.second) { + if (pv.name == name) { + auto value = GetPrimvar(sceneDelegate, name); + if (value.IsHolding>()) { + out_data = value.UncheckedGet>(); + if (primvarDescsEntry.first == HdInterpolationFaceVarying) { + out_indices.reserve(m_faceVertexIndices.size()); + for (int i = 0; i < m_faceVertexIndices.size(); ++i) { + out_indices.push_back(i); + } + } + return true; + } + return false; + } + } + } + + return false; +} +template bool +HdCyclesMesh::GetPrimvarData( + TfToken const&, HdSceneDelegate*, + std::map, VtArray&, + VtIntArray&); +template bool +HdCyclesMesh::GetPrimvarData( + TfToken const&, HdSceneDelegate*, + std::map, VtArray&, + VtIntArray&); + +HdDirtyBits +HdCyclesMesh::_PropagateDirtyBits(HdDirtyBits bits) const +{ + return bits; +} + +void +HdCyclesMesh::_InitRepr(TfToken const& reprToken, HdDirtyBits* dirtyBits) +{ +} + +void +HdCyclesMesh::_ComputeTangents(bool needsign) +{ + const ccl::AttributeSet& attributes = (m_useSubdivision && m_subdivEnabled) + ? m_cyclesMesh->subd_attributes + : m_cyclesMesh->attributes; + + ccl::Attribute* attr = attributes.find(ccl::ATTR_STD_UV); + if (attr) { + mikk_compute_tangents(attr->standard_name(ccl::ATTR_STD_UV), + m_cyclesMesh, needsign, true); + } +} + +void +HdCyclesMesh::_AddUVSet(TfToken name, VtVec2fArray& uvs, + HdInterpolation interpolation) +{ + ccl::AttributeSet* attributes = (m_useSubdivision && m_subdivEnabled) + ? &m_cyclesMesh->subd_attributes + : &m_cyclesMesh->attributes; + bool subdivide_uvs = false; + + ccl::Attribute* attr = attributes->add(ccl::ATTR_STD_UV, + ccl::ustring(name.GetString())); + ccl::float2* fdata = attr->data_float2(); + + if (m_useSubdivision && subdivide_uvs && m_subdivEnabled) + attr->flags |= ccl::ATTR_SUBDIVIDED; + + if (interpolation == HdInterpolationVertex) { + VtIntArray::const_iterator idxIt = m_faceVertexIndices.begin(); + + // TODO: Add support for subd faces? + for (int i = 0; i < m_faceVertexCounts.size(); i++) { + const int vCount = m_faceVertexCounts[i]; + + for (int i = 1; i < vCount - 1; ++i) { + int v0 = *idxIt; + int v1 = *(idxIt + i + 0); + int v2 = *(idxIt + i + 1); + + fdata[0] = vec2f_to_float2(uvs[v0]); + fdata[1] = vec2f_to_float2(uvs[v1]); + fdata[2] = vec2f_to_float2(uvs[v2]); + fdata += 3; + } + idxIt += vCount; + } + } else { + for (size_t i = 0; i < uvs.size(); i++) { + fdata[0] = vec2f_to_float2(uvs[i]); + fdata += 1; + } + } +} + +void +HdCyclesMesh::_AddVelocities(VtVec3fArray& velocities, + HdInterpolation interpolation) +{ + ccl::AttributeSet* attributes = (m_useSubdivision && m_subdivEnabled) + ? &m_cyclesMesh->subd_attributes + : &m_cyclesMesh->attributes; + + ccl::Attribute* attr_mP = attributes->find( + ccl::ATTR_STD_MOTION_VERTEX_POSITION); + + if (!attr_mP) { + attr_mP = attributes->add(ccl::ATTR_STD_MOTION_VERTEX_POSITION); + } + + ccl::float3* vdata = attr_mP->data_float3(); + + if (interpolation == HdInterpolationVertex) { + VtIntArray::const_iterator idxIt = m_faceVertexIndices.begin(); + + // TODO: Add support for subd faces? + for (int i = 0; i < m_faceVertexCounts.size(); i++) { + const int vCount = m_faceVertexCounts[i]; + + for (int i = 1; i < vCount - 1; ++i) { + int v0 = *idxIt; + int v1 = *(idxIt + i + 0); + int v2 = *(idxIt + i + 1); + + vdata[0] = vec3f_to_float3(velocities[v0]); + vdata[1] = vec3f_to_float3(velocities[v1]); + vdata[2] = vec3f_to_float3(velocities[v2]); + vdata += 3; + } + idxIt += vCount; + } + } else { + for (size_t i = 0; i < velocities.size(); i++) { + vdata[0] = vec3f_to_float3(velocities[i]); + vdata += 1; + } + } +} + +void +HdCyclesMesh::_AddColors(TfToken name, VtVec3fArray& colors, ccl::Scene* scene, + HdInterpolation interpolation) +{ + if (colors.size() <= 0) + return; + + ccl::AttributeSet* attributes = (m_useSubdivision && m_subdivEnabled) + ? &m_cyclesMesh->subd_attributes + : &m_cyclesMesh->attributes; + + ccl::AttributeStandard vcol_std = ccl::ATTR_STD_VERTEX_COLOR; + ccl::ustring vcol_name = ccl::ustring(name.GetString()); + + const bool need_vcol = m_cyclesMesh->need_attribute(scene, vcol_name) + || m_cyclesMesh->need_attribute(scene, vcol_std); + + ccl::Attribute* vcol_attr = NULL; + vcol_attr = attributes->add(vcol_std, vcol_name); + + ccl::uchar4* cdata = vcol_attr->data_uchar4(); + + if (interpolation == HdInterpolationVertex) { + VtIntArray::const_iterator idxIt = m_faceVertexIndices.begin(); + + // TODO: Add support for subd faces? + for (int i = 0; i < m_faceVertexCounts.size(); i++) { + const int vCount = m_faceVertexCounts[i]; + + for (int i = 1; i < vCount - 1; ++i) { + int v0 = *idxIt; + int v1 = *(idxIt + i + 0); + int v2 = *(idxIt + i + 1); + + cdata[0] = ccl::color_float4_to_uchar4( + ccl::color_srgb_to_linear_v4(vec3f_to_float4(colors[v0]))); + cdata[1] = ccl::color_float4_to_uchar4( + ccl::color_srgb_to_linear_v4(vec3f_to_float4(colors[v1]))); + cdata[2] = ccl::color_float4_to_uchar4( + ccl::color_srgb_to_linear_v4(vec3f_to_float4(colors[v2]))); + cdata += 3; + } + idxIt += vCount; + } + + } else if (interpolation == HdInterpolationVarying + || interpolation == HdInterpolationConstant + || interpolation == HdInterpolationUniform) { + for (size_t i = 0; i < m_numMeshFaces * 3; i++) { + GfVec3f pv_col = colors[0]; + ccl::float4 col = vec3f_to_float4(pv_col); + + cdata[0] = ccl::color_float4_to_uchar4( + ccl::color_srgb_to_linear_v4(col)); + cdata += 1; + } + } else if (interpolation == HdInterpolationFaceVarying) { + for (size_t i = 0; i < m_numMeshFaces * 3; i++) { + int idx = i; + if (idx > colors.size()) + idx = colors.size() - 1; + + GfVec3f pv_col = colors[idx]; + ccl::float4 col = vec3f_to_float4(pv_col); + + cdata[0] = ccl::color_float4_to_uchar4( + ccl::color_srgb_to_linear_v4(col)); + cdata += 1; + } + } +} + +void +HdCyclesMesh::_AddNormals(VtVec3fArray& normals, HdInterpolation interpolation) +{ + m_cyclesMesh->add_face_normals(); + m_cyclesMesh->add_vertex_normals(); + + //TODO: Implement +} + +ccl::Mesh* +HdCyclesMesh::_CreateCyclesMesh() +{ + ccl::Mesh* mesh = new ccl::Mesh(); + mesh->clear(); + + static const HdCyclesConfig& config = HdCyclesConfig::GetInstance(); + + if (config.enable_motion_blur) { + mesh->use_motion_blur = config.enable_motion_blur; + // TODO: Move this to either renderParam or to the scene? + mesh->motion_steps = config.motion_steps; + } + + m_numMeshVerts = 0; + m_numMeshFaces = 0; + + mesh->subdivision_type = ccl::Mesh::SUBDIVISION_NONE; + return mesh; +} + +ccl::Object* +HdCyclesMesh::_CreateCyclesObject() +{ + ccl::Object* object = new ccl::Object(); + + object->tfm = ccl::transform_identity(); + + object->visibility = ccl::PATH_RAY_ALL_VISIBILITY; + + return object; +} + +void +HdCyclesMesh::_PopulateVertices() +{ + m_cyclesMesh->verts.reserve(m_numMeshVerts); + for (int i = 0; i < m_points.size(); i++) { + m_cyclesMesh->verts.push_back_reserved(vec3f_to_float3(m_points[i])); + } +} + +void +HdCyclesMesh::_PopulateFaces(const std::vector& a_faceMaterials, + bool a_subdivide) +{ + if (a_subdivide) { + m_cyclesMesh->subdivision_type = ccl::Mesh::SUBDIVISION_CATMULL_CLARK; + m_cyclesMesh->reserve_subd_faces(m_numMeshFaces, m_numNgons, + m_numCorners); + } else { + m_cyclesMesh->reserve_mesh(m_numMeshVerts, m_numMeshFaces); + } + + VtIntArray::const_iterator idxIt = m_faceVertexIndices.begin(); + + if (a_subdivide) { + bool smooth = true; + std::vector vi; + for (int i = 0; i < m_faceVertexCounts.size(); i++) { + const int vCount = m_faceVertexCounts[i]; + int materialId = 0; + + vi.resize(vCount); + + for (int i = 0; i < vCount; ++i) { + vi[i] = *(idxIt + i); + } + idxIt += vCount; + + m_cyclesMesh->add_subd_face(&vi[0], vCount, materialId, true); + } + } else { + for (int i = 0; i < m_faceVertexCounts.size(); i++) { + const int vCount = m_faceVertexCounts[i]; + int materialId = 0; + + if (i < a_faceMaterials.size()) { + materialId = a_faceMaterials[i]; + } + + for (int i = 1; i < vCount - 1; ++i) { + int v0 = *idxIt; + int v1 = *(idxIt + i + 0); + int v2 = *(idxIt + i + 1); + if (v0 < m_numMeshVerts && v1 < m_numMeshVerts + && v2 < m_numMeshVerts) { + m_cyclesMesh->add_triangle(v0, v1, v2, materialId, true); + } + } + idxIt += vCount; + } + } +} + +void +HdCyclesMesh::_PopulateCreases() +{ + size_t num_creases = m_creaseLengths.size(); + + m_cyclesMesh->subd_creases.resize(num_creases); + + ccl::Mesh::SubdEdgeCrease* crease = m_cyclesMesh->subd_creases.data(); + for (int i = 0; i < num_creases; i++) { + crease->v[0] = m_creaseIndices[(i * 2) + 0]; + crease->v[1] = m_creaseIndices[(i * 2) + 1]; + crease->crease = m_creaseWeights[i]; + + crease++; + } +} + +void +HdCyclesMesh::_FinishMesh(ccl::Scene* scene) +{ + _ComputeTangents(true); + + if (m_cyclesMesh->need_attribute(scene, ccl::ATTR_STD_GENERATED)) { + ccl::AttributeSet* attributes = (m_useSubdivision && m_subdivEnabled) + ? &m_cyclesMesh->subd_attributes + : &m_cyclesMesh->attributes; + ccl::Attribute* attr = attributes->add(ccl::ATTR_STD_GENERATED); + memcpy(attr->data_float3(), m_cyclesMesh->verts.data(), + sizeof(ccl::float3) * m_cyclesMesh->verts.size()); + } + + m_cyclesMesh->compute_bounds(); +} + +void +HdCyclesMesh::Sync(HdSceneDelegate* sceneDelegate, HdRenderParam* renderParam, + HdDirtyBits* dirtyBits, TfToken const& reprToken) +{ + HdCyclesRenderParam* param = (HdCyclesRenderParam*)renderParam; + ccl::Scene* scene = param->GetCyclesScene(); + + scene->mutex.lock(); + + const SdfPath& id = GetId(); + + // ------------------------------------- + // -- Pull scene data + + bool mesh_updated = false; + + bool newMesh = false; + + bool pointsIsComputed = false; + + auto extComputationDescs + = sceneDelegate->GetExtComputationPrimvarDescriptors( + id, HdInterpolationVertex); + for (auto& desc : extComputationDescs) { + if (desc.name != HdTokens->points) + continue; + + if (HdChangeTracker::IsPrimvarDirty(*dirtyBits, id, desc.name)) { + mesh_updated = true; + auto valueStore = HdExtComputationUtils::GetComputedPrimvarValues( + { desc }, sceneDelegate); + auto pointValueIt = valueStore.find(desc.name); + if (pointValueIt != valueStore.end()) { + if (!pointValueIt->second.IsEmpty()) { + m_points = pointValueIt->second.Get(); + m_numMeshVerts = m_points.size(); + + m_normalsValid = false; + pointsIsComputed = true; + newMesh = true; + } + } + } + break; + } + + if (!pointsIsComputed + && HdChangeTracker::IsPrimvarDirty(*dirtyBits, id, HdTokens->points)) { + mesh_updated = true; + VtValue pointsValue = sceneDelegate->Get(id, HdTokens->points); + if (!pointsValue.IsEmpty()) { + m_points = pointsValue.Get(); + if (m_points.size() > 0) { + m_numMeshVerts = m_points.size(); + + m_normalsValid = false; + newMesh = true; + } + } + } + + static const HdCyclesConfig& config = HdCyclesConfig::GetInstance(); + + if (HdChangeTracker::IsTopologyDirty(*dirtyBits, id)) { + m_topology = GetMeshTopology(sceneDelegate); + m_faceVertexCounts = m_topology.GetFaceVertexCounts(); + m_faceVertexIndices = m_topology.GetFaceVertexIndices(); + m_geomSubsets = m_topology.GetGeomSubsets(); + + m_numMeshFaces = 0; + for (int i = 0; i < m_faceVertexCounts.size(); i++) { + m_numMeshFaces += m_faceVertexCounts[i] - 2; + } + + m_numNgons = 0; + m_numCorners = 0; + + for (int i = 0; i < m_faceVertexCounts.size(); i++) { + // TODO: This seems wrong, but works for now + m_numNgons += 1; // (m_faceVertexCounts[i] == 4) ? 0 : 1; + m_numCorners += m_faceVertexCounts[i]; + } + + m_adjacencyValid = false; + m_normalsValid = false; + if (m_subdivEnabled) { + m_useSubdivision = m_topology.GetScheme() + == PxOsdOpenSubdivTokens->catmullClark; + } + + newMesh = true; + } + + std::map + primvarDescsPerInterpolation = { + { HdInterpolationFaceVarying, sceneDelegate->GetPrimvarDescriptors( + id, HdInterpolationFaceVarying) }, + { HdInterpolationVertex, + sceneDelegate->GetPrimvarDescriptors(id, HdInterpolationVertex) }, + { HdInterpolationConstant, + sceneDelegate->GetPrimvarDescriptors(id, + HdInterpolationConstant) }, + }; + + if (*dirtyBits & HdChangeTracker::DirtyDoubleSided) { + mesh_updated = true; + m_doubleSided = sceneDelegate->GetDoubleSided(id); + } + + // ------------------------------------- + // -- Resolve Drawstyles + + bool isRefineLevelDirty = false; + if (*dirtyBits & HdChangeTracker::DirtyDisplayStyle) { + mesh_updated = true; + + m_displayStyle = sceneDelegate->GetDisplayStyle(id); + if (m_refineLevel != m_displayStyle.refineLevel) { + isRefineLevelDirty = true; + m_refineLevel = m_displayStyle.refineLevel; + newMesh = true; + } + } + + if (HdChangeTracker::IsSubdivTagsDirty(*dirtyBits, id)) { + const PxOsdSubdivTags subdivTags = GetSubdivTags(sceneDelegate); + + m_cornerIndices = subdivTags.GetCornerIndices(); + m_cornerWeights = subdivTags.GetCornerWeights(); + m_creaseIndices = subdivTags.GetCreaseIndices(); + m_creaseLengths = subdivTags.GetCreaseLengths(); + m_creaseWeights = subdivTags.GetCreaseWeights(); + + newMesh = true; + } + + // ------------------------------------- + // -- Create Cycles Mesh + + HdMeshUtil meshUtil(&m_topology, id); + if (newMesh) { + m_cyclesMesh->clear(); + + _PopulateVertices(); + + std::vector faceMaterials; + faceMaterials.resize(m_numMeshFaces); + + for (auto const& subset : m_geomSubsets) { + int subsetMaterialIndex = 0; + + if (!subset.materialId.IsEmpty()) { + const HdCyclesMaterial* subMat + = static_cast( + sceneDelegate->GetRenderIndex().GetSprim( + HdPrimTypeTokens->material, subset.materialId)); + if (subMat && subMat->GetCyclesShader()) { + if (m_materialMap.find(subset.materialId) + == m_materialMap.end()) { + m_cyclesMesh->used_shaders.push_back( + subMat->GetCyclesShader()); + subMat->GetCyclesShader()->tag_update(scene); + + m_materialMap.insert(std::pair( + subset.materialId, + m_cyclesMesh->used_shaders.size())); + subsetMaterialIndex = m_cyclesMesh->used_shaders.size(); + } else { + subsetMaterialIndex = m_materialMap.at( + subset.materialId); + } + } + } + + for (int i : subset.indices) { + faceMaterials[i] = std::max(subsetMaterialIndex - 1, 0); + } + } + + _PopulateFaces(faceMaterials, (m_useSubdivision && m_subdivEnabled)); + + if (m_useSubdivision && m_subdivEnabled) { + _PopulateCreases(); + + if (!m_cyclesMesh->subd_params) { + m_cyclesMesh->subd_params = new ccl::SubdParams(m_cyclesMesh); + } + + ccl::SubdParams& subd_params = *m_cyclesMesh->subd_params; + + subd_params.dicing_rate = m_dicingRate + / ((m_refineLevel + 1) * 2.0f); + subd_params.max_level = m_maxSubdivision; + + subd_params.objecttoworld = ccl::transform_identity(); + } + + // Get all uvs (assumes all GfVec2f are uvs) + for (auto& primvarDescsEntry : primvarDescsPerInterpolation) { + for (auto& pv : primvarDescsEntry.second) { + if (HdChangeTracker::IsPrimvarDirty(*dirtyBits, id, pv.name)) { + auto value = GetPrimvar(sceneDelegate, pv.name); + VtValue triangulated; + + + if (pv.name == HdTokens->normals) { + VtVec3fArray normals; + normals = value.UncheckedGet>(); + + // TODO: Properly implement + /*if (primvarDescsEntry.first + == HdInterpolationFaceVarying) { + // Triangulate primvar normals + meshUtil.ComputeTriangulatedFaceVaryingPrimvar( + normals.data(), normals.size(), HdTypeFloatVec3, + &triangulated); + normals = triangulated.Get(); + }*/ + + _AddNormals(normals, primvarDescsEntry.first); + } + + // TODO: Properly implement + /*VtValue triangulatedVal; + if (pv.name == HdTokens->velocities) { + VtVec3fArray vels; + vels = value.UncheckedGet>(); + + meshUtil.ComputeTriangulatedFaceVaryingPrimvar( + vels.data(), vels.size(), HdTypeFloatVec3, + &triangulatedVal); + VtVec3fArray m_vels_tri + = triangulatedVal.Get(); + _AddVelocities(m_vels_tri, primvarDescsEntry.first); + }*/ + + if (pv.role == HdPrimvarRoleTokens->color) { + m_hasVertexColors = true; + + if (value.IsHolding>()) { + // Get primvar colors + VtVec3fArray colors; + colors = value.UncheckedGet>(); + + if (primvarDescsEntry.first + == HdInterpolationFaceVarying) { + // Triangulate primvar colors + meshUtil.ComputeTriangulatedFaceVaryingPrimvar( + colors.data(), colors.size(), + HdTypeFloatVec3, &triangulated); + colors = triangulated.Get(); + } + + // Add colors to attribute + _AddColors(pv.name, colors, scene, + primvarDescsEntry.first); + } + } + + // TODO: Add more general uv support + //if (pv.role == HdPrimvarRoleTokens->textureCoordinate) { + if (value.IsHolding>()) { + VtVec2fArray uvs; + VtIntArray uvIndices; + uvs = value.UncheckedGet>(); + if (primvarDescsEntry.first + == HdInterpolationFaceVarying) { + uvIndices.reserve(m_faceVertexIndices.size()); + for (int i = 0; i < m_faceVertexIndices.size(); + ++i) { + uvIndices.push_back(i); + } + } + + meshUtil.ComputeTriangulatedFaceVaryingPrimvar( + uvs.data(), uvs.size(), HdTypeFloatVec2, + &triangulated); + + VtVec2fArray m_uvs_tri + = triangulated.Get(); + if (m_useSubdivision && m_subdivEnabled) { + _AddUVSet(pv.name, uvs, primvarDescsEntry.first); + } else { + _AddUVSet(pv.name, m_uvs_tri, + primvarDescsEntry.first); + } + } + } + } + } + } + + if (*dirtyBits & HdChangeTracker::DirtyTransform) { + m_transformSamples = HdCyclesSetTransform(m_cyclesObject, sceneDelegate, + id, m_useMotionBlur); + + if (m_cyclesMesh && m_cyclesMesh->subd_params) { + m_cyclesMesh->subd_params->objecttoworld = m_cyclesObject->tfm; + } + + mesh_updated = true; + } + + ccl::Shader* fallbackShader = scene->default_surface; + + if (m_hasVertexColors) { + fallbackShader = param->default_vcol_surface; + } + + if (*dirtyBits & HdChangeTracker::DirtyMaterialId) { + if (m_cyclesMesh) { + m_cachedMaterialId = sceneDelegate->GetMaterialId(id); + if (m_faceVertexCounts.size() > 0) { + if (!m_cachedMaterialId.IsEmpty()) { + const HdCyclesMaterial* material + = static_cast( + sceneDelegate->GetRenderIndex().GetSprim( + HdPrimTypeTokens->material, m_cachedMaterialId)); + + if (material && material->GetCyclesShader()) { + m_cyclesMesh->used_shaders.push_back( + material->GetCyclesShader()); + + material->GetCyclesShader()->tag_update(scene); + } else { + m_cyclesMesh->used_shaders.push_back(fallbackShader); + } + } else { + m_cyclesMesh->used_shaders.push_back(fallbackShader); + } + } + } + } + + if (*dirtyBits & HdChangeTracker::DirtyVisibility) { + mesh_updated = true; + _sharedData.visible = sceneDelegate->GetVisible(id); + if (_sharedData.visible) { + m_cyclesObject->visibility |= ccl::PATH_RAY_ALL_VISIBILITY; + } else { + m_cyclesObject->visibility &= ~ccl::PATH_RAY_ALL_VISIBILITY; + } + } + + // ------------------------------------- + // -- Handle point instances + + if (newMesh || (*dirtyBits & HdChangeTracker::DirtyInstancer)) { + mesh_updated = true; + if (auto instancer = static_cast( + sceneDelegate->GetRenderIndex().GetInstancer( + GetInstancerId()))) { + auto instanceTransforms = instancer->SampleInstanceTransforms(id); + auto newNumInstances = (instanceTransforms.count > 0) + ? instanceTransforms.values[0].size() + : 0; + // Clear all instances... + if (m_cyclesInstances.size() > 0) { + for (auto instance : m_cyclesInstances) { + if (instance) { + m_renderDelegate->GetCyclesRenderParam()->RemoveObject( + instance); + delete instance; + } + } + m_cyclesInstances.clear(); + } + + if (newNumInstances != 0) { + std::vector> combinedTransforms; + combinedTransforms.reserve(newNumInstances); + for (size_t i = 0; i < newNumInstances; ++i) { + // Apply prototype transform (m_transformSamples) to all the instances + combinedTransforms.emplace_back(instanceTransforms.count); + auto& instanceTransform = combinedTransforms.back(); + + if (m_transformSamples.count == 0 + || (m_transformSamples.count == 1 + && (m_transformSamples.values[0] + == GfMatrix4d(1)))) { + for (size_t j = 0; j < instanceTransforms.count; ++j) { + instanceTransform[j] + = instanceTransforms.values[j][i]; + } + } else { + for (size_t j = 0; j < instanceTransforms.count; ++j) { + GfMatrix4d xf_j = m_transformSamples.Resample( + instanceTransforms.times[j]); + instanceTransform[j] + = xf_j * instanceTransforms.values[j][i]; + } + } + } + + for (int j = 0; j < newNumInstances; ++j) { + ccl::Object* instanceObj = _CreateCyclesObject(); + + instanceObj->tfm = mat4d_to_transform( + combinedTransforms[j].data()[0]); + instanceObj->geometry = m_cyclesMesh; + + // TODO: Implement motion blur for point instanced objects + /*if (m_useMotionBlur) { + m_cyclesMesh->motion_steps = m_motionSteps; + m_cyclesMesh->use_motion_blur = m_useMotionBlur; + + instanceObj->motion.clear(); + instanceObj->motion.resize(m_motionSteps); + for (int j = 0; j < m_motionSteps; j++) { + instanceObj->motion[j] = mat4d_to_transform( + combinedTransforms[j].data()[j]); + } + }*/ + + m_cyclesInstances.push_back(instanceObj); + + m_renderDelegate->GetCyclesRenderParam()->AddObject( + instanceObj); + } + + // Hide prototype + if (m_cyclesObject) + m_cyclesObject->visibility = 0; + } + } + } + + // ------------------------------------- + // -- Finish Mesh + + if (newMesh && m_cyclesMesh) { + _FinishMesh(scene); + } + + if (mesh_updated || newMesh) { + m_cyclesObject->tag_update(scene); + param->Interrupt(); + } + + scene->mutex.unlock(); + + *dirtyBits = HdChangeTracker::Clean; +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/plugin/hdCycles/mesh.h b/plugin/hdCycles/mesh.h new file mode 100644 index 00000000..50fa20b9 --- /dev/null +++ b/plugin/hdCycles/mesh.h @@ -0,0 +1,283 @@ +// Copyright 2020 Tangent Animation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef HD_CYCLES_MESH_H +#define HD_CYCLES_MESH_H + +#include "utils.h" + +#include "hdcycles.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ccl { +class Scene; +class Mesh; +class Object; +} // namespace ccl + +PXR_NAMESPACE_OPEN_SCOPE + +class HdCyclesRenderDelegate; + +/** + * @brief HdCycles Mesh Rprim mapped to Cycles mesh + * + */ +class HdCyclesMesh final : public HdMesh { +public: + HF_MALLOC_TAG_NEW("new HdCyclesMesh"); + + /** + * @brief Construct a new HdCycles Mesh object + * + * @param id Path to the Mesh Primitive + * @param instancerId If specified the HdInstancer at this id uses this mesh + * as a prototype + */ + HdCyclesMesh(SdfPath const& id, SdfPath const& instancerId, + HdCyclesRenderDelegate* a_renderDelegate); + + /** + * @brief Destroy the HdCycles Mesh object + * + */ + virtual ~HdCyclesMesh(); + + /** + * @brief Inform the scene graph which state needs to be downloaded in + * the first Sync() call + * + * @return The initial dirty state this mesh wants to query + */ + HdDirtyBits GetInitialDirtyBitsMask() const override; + + /** + * @brief Pull invalidated mesh data and prepare/update the core Cycles + * representation. + * + * This must be thread safe. + * + * @param sceneDelegate The data source for the mesh + * @param renderParam State + * @param dirtyBits Which bits of scene data has changed + * @param reprToken Which representation to draw with + */ + void Sync(HdSceneDelegate* sceneDelegate, HdRenderParam* renderParam, + HdDirtyBits* dirtyBits, TfToken const& reprToken) override; + +protected: + /** + * @brief Create the cycles mesh representation + * + * @return New allocated pointer to ccl::Mesh + */ + ccl::Mesh* _CreateCyclesMesh(); + + /** + * @brief Create the cycles object representation + * + * @return ccl::Object* + */ + ccl::Object* _CreateCyclesObject(); + + /** + * @brief Perform final mesh computations (bounds, tangents, etc) + * + * @param scene + */ + void _FinishMesh(ccl::Scene* scene); + + /** + * @brief Comptue Mikktspace tangents + * + * @param needsign + */ + void _ComputeTangents(bool needsign); + + /** + * @brief Add abitrary uv set + * + * @param name + * @param uvs + * @param interpolation + */ + void _AddUVSet(TfToken name, VtVec2fArray& uvs, + HdInterpolation interpolation); + + /** + * @brief Add vertex/face normals (Not implemented) + * + * @param normals + * @param interpolation + */ + void _AddNormals(VtVec3fArray& normals, HdInterpolation interpolation); + + /** + * @brief Add vertex velocities (Not tested) + * + * @param velocities + * @param interpolation + */ + void _AddVelocities(VtVec3fArray& velocities, + HdInterpolation interpolation); + + /** + * @brief Add vertex/primitive colors + * + * @param name + * @param colors + * @param scene + * @param interpolation + */ + void _AddColors(TfToken name, VtVec3fArray& colors, ccl::Scene* scene, + HdInterpolation interpolation); + +protected: + struct PrimvarSource { + VtValue data; + HdInterpolation interpolation; + }; + TfHashMap _primvarSourceMap; + +private: + template + bool GetPrimvarData(TfToken const& name, HdSceneDelegate* sceneDelegate, + std::map + primvarDescsPerInterpolation, + VtArray& out_data, VtIntArray& out_indices); + +protected: + /** + * @brief Initialize the given representation of this Rprim. + * This is called prior to syncing the prim. + * + * @param reprToken The name of the repr to initialize + * @param dirtyBits In/Out dirty values + */ + void _InitRepr(TfToken const& reprToken, HdDirtyBits* dirtyBits) override; + + /** + * @brief Set additional dirty bits + * + * @param bits + * @return New value of dirty bits + */ + HdDirtyBits _PropagateDirtyBits(HdDirtyBits bits) const override; + + /** + * @brief Do not allow this class to be copied + * + */ + HdCyclesMesh(const HdCyclesMesh&) = delete; + /** + * @brief Do not allow this class to be assigned + * + */ + HdCyclesMesh& operator=(const HdCyclesMesh&) = delete; + + /** + * @brief Populate vertices of cycles mesh + * + */ + void _PopulateVertices(); + + /** + * @brief Populate faces of cycles mesh + * + * @param a_faceMaterials pregenerated array of subset materials + * @param a_subdivide should faces be subdivided + */ + void _PopulateFaces(const std::vector& a_faceMaterials, + bool a_subdivide); + + /** + * @brief Populate subdiv creases + * + */ + void _PopulateCreases(); + + ccl::Mesh* m_cyclesMesh; + ccl::Object* m_cyclesObject; + std::vector m_cyclesInstances; + + std::map m_materialMap; + + size_t m_numMeshVerts = 0; + size_t m_numMeshFaces = 0; + + SdfPath m_cachedMaterialId; + int m_numTransformSamples; + HdTimeSampleArray m_transformSamples; + + HdMeshTopology m_topology; + HdGeomSubsets m_geomSubsets; + VtVec3fArray m_points; + VtIntArray m_faceVertexCounts; + VtIntArray m_faceVertexIndices; + + bool m_useSubdivision = false; + bool m_subdivEnabled = false; + int m_maxSubdivision = 12; + float m_dicingRate = 0.1f; + + int m_numNgons; + int m_numCorners; + + Hd_VertexAdjacency m_adjacency; + bool m_adjacencyValid = false; + + VtVec3fArray m_normals; + VtIntArray m_normalIndices; + bool m_normalsValid = false; + bool m_authoredNormals = false; + bool m_smoothNormals = false; + + VtIntArray m_cornerIndices; + VtFloatArray m_cornerWeights; + VtIntArray m_creaseIndices; + VtIntArray m_creaseLengths; + VtFloatArray m_creaseWeights; + + TfToken m_normalInterpolation; + + VtVec2fArray m_uvs; + VtIntArray m_uvIndices; + + HdDisplayStyle m_displayStyle; + int m_refineLevel = 0; + bool m_doubleSided = false; + + bool m_useMotionBlur; + int m_motionSteps; + + bool m_hasVertexColors; + +private: + HdCyclesRenderDelegate* m_renderDelegate; +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // HD_CYCLES_MESH_H diff --git a/plugin/hdCycles/plugInfo.json b/plugin/hdCycles/plugInfo.json new file mode 100644 index 00000000..d39b5e34 --- /dev/null +++ b/plugin/hdCycles/plugInfo.json @@ -0,0 +1,22 @@ +{ + "Plugins": [ + { + "Info": { + "Types": { + "HdCyclesRendererPlugin": { + "bases": [ + "HdRendererPlugin" + ], + "displayName": "Cycles", + "priority": 99 + } + } + }, + "LibraryPath": "@PLUG_INFO_LIBRARY_PATH@", + "Name": "hdCycles", + "ResourcePath": "@PLUG_INFO_RESOURCE_PATH@", + "Root": "@PLUG_INFO_ROOT@", + "Type": "library" + } + ] +} \ No newline at end of file diff --git a/plugin/hdCycles/points.cpp b/plugin/hdCycles/points.cpp new file mode 100644 index 00000000..f72fbd6b --- /dev/null +++ b/plugin/hdCycles/points.cpp @@ -0,0 +1,382 @@ +// Copyright 2020 Tangent Animation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "points.h" + +#include "config.h" +#include "material.h" +#include "renderParam.h" +#include "utils.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +// TODO: Read these from usdCycles schema +// clang-format off +TF_DEFINE_PRIVATE_TOKENS(_tokens, + ((cyclesPointStyle, "cycles:object:point_style")) + ((cyclesPointResolution, "cycles:object:point_resolution")) +); +// clang-format on + +HdCyclesPoints::HdCyclesPoints(SdfPath const& id, SdfPath const& instancerId, + HdCyclesRenderDelegate* a_renderDelegate) + : HdPoints(id, instancerId) + , m_renderDelegate(a_renderDelegate) + , m_transform(ccl::transform_identity()) +{ + static const HdCyclesConfig& config = HdCyclesConfig::GetInstance(); + m_useMotionBlur = config.enable_motion_blur; + + m_pointStyle = config.default_point_style; + m_pointResolution = config.default_point_resolution; + + if (m_useMotionBlur) { + m_motionSteps = config.motion_steps; + } +} + +HdCyclesPoints::~HdCyclesPoints() +{ + // Remove points + for (int i = 0; i < m_cyclesObjects.size(); i++) { + m_renderDelegate->GetCyclesRenderParam()->RemoveObject( + m_cyclesObjects[i]); + } + + m_cyclesObjects.clear(); + + // Remove mesh + + m_renderDelegate->GetCyclesRenderParam()->RemoveMesh(m_cyclesMesh); + + delete m_cyclesMesh; +} + +void +HdCyclesPoints::_InitRepr(TfToken const& reprToken, HdDirtyBits* dirtyBits) +{ +} + +HdDirtyBits +HdCyclesPoints::_PropagateDirtyBits(HdDirtyBits bits) const +{ + return bits; +} + +void +HdCyclesPoints::Finalize(HdRenderParam* renderParam) +{ +} + +void +HdCyclesPoints::Sync(HdSceneDelegate* sceneDelegate, HdRenderParam* renderParam, + HdDirtyBits* dirtyBits, TfToken const& reprSelector) +{ + HdCyclesRenderParam* param = (HdCyclesRenderParam*)renderParam; + + const SdfPath& id = GetId(); + + HdCyclesPDPIMap pdpi; + + ccl::Scene* scene = param->GetCyclesScene(); + + bool needs_update = false; + + // Read Cycles Primvars + + if (HdChangeTracker::IsPrimvarDirty(*dirtyBits, id, + _tokens->cyclesPointStyle)) { + needs_update = true; + + HdTimeSampleArray xf; + sceneDelegate->SamplePrimvar(id, _tokens->cyclesPointStyle, &xf); + if (xf.count > 0) { + const VtIntArray& styles = xf.values[0].Get(); + if (styles.size() > 0) { + m_pointStyle = styles[0]; + } + } + } + + if (HdChangeTracker::IsPrimvarDirty(*dirtyBits, id, + _tokens->cyclesPointResolution)) { + needs_update = true; + + HdTimeSampleArray xf; + sceneDelegate->SamplePrimvar(id, _tokens->cyclesPointResolution, &xf); + if (xf.count > 0) { + const VtIntArray& resolutions = xf.values[0].Get(); + if (resolutions.size() > 0) { + m_pointResolution = std::max(resolutions[0], 3); + } + } + } + + // Create Points + + if (*dirtyBits & HdChangeTracker::DirtyPoints) { + needs_update = true; + + if (m_pointStyle == HdCyclesPointStyle::POINT_DISCS) { + m_cyclesMesh = _CreateDiscMesh(); + } else { + m_cyclesMesh = _CreateSphereMesh(); + } + + m_cyclesMesh->tag_update(scene, true); + param->AddGeometry(m_cyclesMesh); + + const auto pointsValue = sceneDelegate->Get(id, HdTokens->points); + if (!pointsValue.IsEmpty() && pointsValue.IsHolding()) { + const VtVec3fArray& points = pointsValue.Get(); + + for (int i = 0; i < m_cyclesObjects.size(); i++) { + param->RemoveObject(m_cyclesObjects[i]); + } + + m_cyclesObjects.clear(); + + for (int i = 0; i < points.size(); i++) { + ccl::Object* pointObject = _CreatePointsObject( + ccl::transform_translate(vec3f_to_float3(points[i])), + m_cyclesMesh); + + pointObject->random_id = i; + pointObject->name + = ccl::ustring::format("%s@%08x", pointObject->name, + pointObject->random_id); + m_cyclesObjects.push_back(pointObject); + param->AddObject(pointObject); + } + } + } + + if (*dirtyBits & HdChangeTracker::DirtyTransform) { + ccl::Transform newTransform = HdCyclesExtractTransform(sceneDelegate, + id); + + for (int i = 0; i < m_cyclesObjects.size(); i++) { + m_cyclesObjects[i]->tfm = ccl::transform_inverse(m_transform) + * m_cyclesObjects[i]->tfm; + m_cyclesObjects[i]->tfm = newTransform * m_cyclesObjects[i]->tfm; + } + + m_transform = newTransform; + + needs_update = true; + } + + + if (HdChangeTracker::IsPrimvarDirty(*dirtyBits, id, HdTokens->widths)) { + needs_update = true; + + if (m_cyclesObjects.size() > 0) { + HdTimeSampleArray xf; + sceneDelegate->SamplePrimvar(id, HdTokens->widths, &xf); + if (xf.count > 0) { + const VtFloatArray& widths = xf.values[0].Get(); + for (int i = 0; i < widths.size(); i++) { + if (i < m_cyclesObjects.size()) { + float w = widths[i]; + m_cyclesObjects[i]->tfm = m_cyclesObjects[i]->tfm + * ccl::transform_scale(w, w, + w); + } + } + } + } + } + + if (HdChangeTracker::IsPrimvarDirty(*dirtyBits, id, HdTokens->normals)) { + needs_update = true; + + if (m_cyclesObjects.size() > 0) { + HdTimeSampleArray xf; + sceneDelegate->SamplePrimvar(id, HdTokens->normals, &xf); + if (xf.count > 0) { + const VtVec3fArray& normals = xf.values[0].Get(); + for (int i = 0; i < normals.size(); i++) { + if (i < m_cyclesObjects.size()) { + ccl::float3 rotAxis + = ccl::cross(ccl::make_float3(0.0f, 0.0f, 1.0f), + ccl::make_float3(normals[i][0], + normals[i][1], + normals[i][2])); + float d = ccl::dot(ccl::make_float3(0.0f, 0.0f, 1.0f), + ccl::make_float3(normals[i][0], + normals[i][1], + normals[i][2])); + float angle = atan2f(ccl::len(rotAxis), d); + m_cyclesObjects[i]->tfm + = m_cyclesObjects[i]->tfm + * ccl::transform_rotate((angle), rotAxis); + } + } + } else { + // handle orient to camera + } + } + } + + if (*dirtyBits & HdChangeTracker::DirtyVisibility) { + needs_update = true; + + bool visible = sceneDelegate->GetVisible(id); + for (int i = 0; i < m_cyclesObjects.size(); i++) { + if (visible) { + m_cyclesObjects[i]->visibility |= ccl::PATH_RAY_ALL_VISIBILITY; + } else { + m_cyclesObjects[i]->visibility &= ~ccl::PATH_RAY_ALL_VISIBILITY; + } + } + } + + if (needs_update) + param->Interrupt(); + + *dirtyBits = HdChangeTracker::Clean; +} + +HdDirtyBits +HdCyclesPoints::GetInitialDirtyBitsMask() const +{ + return HdChangeTracker::DirtyPoints | HdChangeTracker::DirtyTransform + | HdChangeTracker::DirtyVisibility | HdChangeTracker::DirtyPrimvar + | HdChangeTracker::DirtyWidths | HdChangeTracker::DirtyMaterialId + | HdChangeTracker::DirtyInstanceIndex + | HdChangeTracker::DirtyNormals; +} + +bool +HdCyclesPoints::IsValid() const +{ + return true; +} + +// Creates z up disc +ccl::Mesh* +HdCyclesPoints::_CreateDiscMesh() +{ + ccl::Mesh* mesh = new ccl::Mesh(); + mesh->clear(); + mesh->name = ccl::ustring("generated_disc"); + mesh->subdivision_type = ccl::Mesh::SUBDIVISION_NONE; + + int numVerts = m_pointResolution; + int numFaces = m_pointResolution - 2; + + mesh->reserve_mesh(numVerts, numFaces); + + mesh->verts.reserve(numVerts); + + for (int i = 0; i < m_pointResolution; i++) { + float d = ((float)i / (float)m_pointResolution) * 2.0f * M_PI; + float x = sin(d) * 0.5f; + float y = cos(d) * 0.5f; + mesh->verts.push_back_reserved(ccl::make_float3(x, y, 0.0f)); + } + + for (int i = 1; i < m_pointResolution - 1; i++) { + int v0 = 0; + int v1 = i; + int v2 = i + 1; + mesh->add_triangle(v0, v1, v2, 0, true); + } + + mesh->compute_bounds(); + + return mesh; +} + +ccl::Mesh* +HdCyclesPoints::_CreateSphereMesh() +{ + float radius = 0.5f; + + int sectorCount = m_pointResolution; + int stackCount = m_pointResolution; + + ccl::Mesh* mesh = new ccl::Mesh(); + mesh->clear(); + mesh->name = ccl::ustring("generated_sphere"); + mesh->subdivision_type = ccl::Mesh::SUBDIVISION_NONE; + + float z, xy; + + float sectorStep = 2 * M_PI / sectorCount; + float stackStep = M_PI / stackCount; + float sectorAngle, stackAngle; + + for (int i = 0; i <= stackCount; ++i) { + stackAngle = M_PI / 2 - i * stackStep; + xy = radius * cosf(stackAngle); + z = radius * sinf(stackAngle); + + for (int j = 0; j <= sectorCount; ++j) { + sectorAngle = j * sectorStep; + + mesh->verts.push_back_slow(ccl::make_float3(xy * cosf(sectorAngle), + xy * sinf(sectorAngle), + z)); + // TODO: Add normals and uvs + } + } + + int k1, k2; + for (int i = 0; i < stackCount; ++i) { + k1 = i * (sectorCount + 1); + k2 = k1 + sectorCount + 1; + + for (int j = 0; j < sectorCount; ++j, ++k1, ++k2) { + if (i != 0) { + mesh->add_triangle(k1, k2, k1 + 1, 0, true); + } + + if (i != (stackCount - 1)) { + mesh->add_triangle(k1 + 1, k2, k2 + 1, 0, true); + } + } + } + + mesh->compute_bounds(); + + return mesh; +} + +ccl::Object* +HdCyclesPoints::_CreatePointsObject(const ccl::Transform& transform, + ccl::Mesh* mesh) +{ + /* create object*/ + ccl::Object* object = new ccl::Object(); + object->geometry = mesh; + object->tfm = transform; + object->motion.clear(); + + return object; +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/plugin/hdCycles/points.h b/plugin/hdCycles/points.h new file mode 100644 index 00000000..334cc1e8 --- /dev/null +++ b/plugin/hdCycles/points.h @@ -0,0 +1,164 @@ +// Copyright 2020 Tangent Animation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef HD_CYCLES_POINTS_H +#define HD_CYCLES_POINTS_H + +#include "api.h" + +#include "hdcycles.h" +#include "renderDelegate.h" + +#include + +#include +#include + +namespace ccl { +class Object; +class Mesh; +class Scene; +} // namespace ccl + +PXR_NAMESPACE_OPEN_SCOPE + +class HdSceneDelegate; +class HdCyclesRenderDelegate; + +enum HdCyclesPointStyle { + POINT_DISCS, + POINT_SPHERES, +}; + +/** + * @brief An intermediate solution for HdPoints as Cycles doesn't + * natively support point clouds. + * + */ +class HdCyclesPoints final : public HdPoints { +public: + /** + * @brief Construct a new HdCycles Point object + * + * @param id Path to the Point Primitive + * @param instancerId If specified the HdInstancer at this id uses this curve + * as a prototype + */ + HdCyclesPoints(SdfPath const& id, SdfPath const& instancerId, + HdCyclesRenderDelegate* a_renderDelegate); + /** + * @brief Destroy the HdCycles Points object + * + */ + virtual ~HdCyclesPoints(); + + /** + * @brief Pull invalidated material data and prepare/update the core Cycles + * representation. + * + * This must be thread safe. + * + * @param sceneDelegate The data source for the Point + * @param renderParam State + * @param dirtyBits Which bits of scene data has changed + */ + void Sync(HdSceneDelegate* sceneDelegate, HdRenderParam* renderParam, + HdDirtyBits* dirtyBits, TfToken const& reprSelector) override; + + /** + * @brief Inform the scene graph which state needs to be downloaded in + * the first Sync() call + * + * @return The initial dirty state this Point wants to query + */ + HdDirtyBits GetInitialDirtyBitsMask() const override; + + /** + * @return Return true if this light is valid. + */ + bool IsValid() const; + + /** + * @brief Not Implemented + */ + void Finalize(HdRenderParam* renderParam) override; + +protected: + /** + * @brief Initialize the given representation of this Rprim. + * This is called prior to syncing the prim. + * + * @param reprToken The name of the repr to initialize + * @param dirtyBits In/Out dirty values + */ + void _InitRepr(TfToken const& reprToken, HdDirtyBits* dirtyBits) override; + + /** + * @brief Set additional dirty bits + * + * @param bits + * @return New value of dirty bits + */ + HdDirtyBits _PropagateDirtyBits(HdDirtyBits bits) const override; + +private: + /** + * @brief Create the cycles points as discs mesh and object representation + * + * @param resolution Resolution of the disc geometry + * @return New allocated pointer to ccl::Mesh + */ + ccl::Mesh* _CreateDiscMesh(); + + /** + * @brief Create the cycles points as spheres mesh and object representation + * + * @param scene Cycles scene to add mesh to + * @param transform Initial transform for object + * @return New allocated pointer to ccl::Mesh + */ + ccl::Mesh* _CreateSphereMesh(); + + /** + * @brief Create the cycles object for an individual point + * + * @param transform Transform of the point + * @param mesh Mesh to populate the point with + * @return ccl::Object* + */ + ccl::Object* _CreatePointsObject(const ccl::Transform& transform, + ccl::Mesh* mesh); + + ccl::Mesh* m_cyclesMesh; + + std::vector m_cyclesObjects; + + HdCyclesRenderDelegate* m_renderDelegate; + + ccl::Transform m_transform; + + int m_pointStyle; + int m_pointResolution; + + // -- Currently unused + + bool m_useMotionBlur; + int m_motionSteps; + + HdTimeSampleArray m_transformSamples; +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // HD_CYCLES_POINTS_H diff --git a/plugin/hdCycles/renderBuffer.cpp b/plugin/hdCycles/renderBuffer.cpp new file mode 100644 index 00000000..528cbe9c --- /dev/null +++ b/plugin/hdCycles/renderBuffer.cpp @@ -0,0 +1,236 @@ +// Copyright 2020 Tangent Animation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "renderBuffer.h" + +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +namespace { +template +void +_ConvertPixel(HdFormat dstFormat, uint8_t* dst, HdFormat srcFormat, + uint8_t const* src) +{ + HdFormat srcComponentFormat = HdGetComponentFormat(srcFormat); + HdFormat dstComponentFormat = HdGetComponentFormat(dstFormat); + size_t srcComponentCount = HdGetComponentCount(srcFormat); + size_t dstComponentCount = HdGetComponentCount(dstFormat); + + for (size_t c = 0; c < dstComponentCount; ++c) { + T readValue = 0; + if (c < srcComponentCount) { + if (srcComponentFormat == HdFormatInt32) { + readValue = ((int32_t*)src)[c]; + } else if (srcComponentFormat == HdFormatFloat16) { + GfHalf half; + half.setBits(((uint16_t*)src)[c]); + readValue = static_cast(half); + } else if (srcComponentFormat == HdFormatFloat32) { + readValue = ((float*)src)[c]; + } else if (srcComponentFormat == HdFormatUNorm8) { + readValue = ((uint8_t*)src)[c] / 255.0f; + } else if (srcComponentFormat == HdFormatSNorm8) { + readValue = ((int8_t*)src)[c] / 127.0f; + } + } + + if (dstComponentFormat == HdFormatInt32) { + ((int32_t*)dst)[c] = readValue; + } else if (dstComponentFormat == HdFormatFloat16) { + ((uint16_t*)dst)[c] = GfHalf(float(readValue)).bits(); + } else if (dstComponentFormat == HdFormatFloat32) { + ((float*)dst)[c] = readValue; + } else if (dstComponentFormat == HdFormatUNorm8) { + ((uint8_t*)dst)[c] = (readValue * 255.0f); + } else if (dstComponentFormat == HdFormatSNorm8) { + ((int8_t*)dst)[c] = (readValue * 127.0f); + } + } +} +} // namespace + +HdCyclesRenderBuffer::HdCyclesRenderBuffer(const SdfPath& id) + : HdRenderBuffer(id) + , m_width(0) + , m_height(0) + , m_format(HdFormatInvalid) + , m_pixelSize(0) + , m_mappers(0) + , m_converged(false) +{ +} + +bool +HdCyclesRenderBuffer::Allocate(const GfVec3i& dimensions, HdFormat format, + bool multiSampled) +{ + _Deallocate(); + + if (dimensions[2] != 1) { + TF_WARN( + "Render buffer allocated with dims <%d, %d, %d> and format %s; depth must be 1!", + dimensions[0], dimensions[1], dimensions[2], + TfEnum::GetName(format).c_str()); + return false; + } + + m_width = dimensions[0]; + m_height = dimensions[1]; + m_format = format; + m_pixelSize = HdDataSizeOfFormat(format); + m_buffer.resize(m_width * m_height * m_pixelSize, 0); + + return true; +} + +unsigned int +HdCyclesRenderBuffer::GetWidth() const +{ + return m_width; +} + +unsigned int +HdCyclesRenderBuffer::GetHeight() const +{ + return m_height; +} + +unsigned int +HdCyclesRenderBuffer::GetDepth() const +{ + return 1; +} + +HdFormat +HdCyclesRenderBuffer::GetFormat() const +{ + return m_format; +} + +bool +HdCyclesRenderBuffer::IsMultiSampled() const +{ + return false; +} + +void* +HdCyclesRenderBuffer::Map() +{ + m_mappers++; + return m_buffer.data(); +} + +void +HdCyclesRenderBuffer::Unmap() +{ + m_mappers--; +} + +bool +HdCyclesRenderBuffer::IsMapped() const +{ + return m_mappers.load() != 0; +} + +void +HdCyclesRenderBuffer::Resolve() +{ +} + +bool +HdCyclesRenderBuffer::IsConverged() const +{ + return m_converged.load(); +} + +void +HdCyclesRenderBuffer::SetConverged(bool cv) +{ + m_converged.store(cv); +} + +void +HdCyclesRenderBuffer::Blit(HdFormat format, int width, int height, int offset, + int stride, uint8_t const* data) +{ + if (m_format == format) { + if (static_cast(width) == m_width + && static_cast(height) == m_height) { + // Blit line by line. + for (unsigned int j = 0; j < m_height; ++j) { + memcpy(&m_buffer[(j * m_width) * m_pixelSize], + &data[(j * stride + offset) * m_pixelSize], + m_width * m_pixelSize); + } + } else { + // Blit pixel by pixel, with nearest point sampling. + float scalei = width / float(m_width); + float scalej = height / float(m_height); + for (unsigned int j = 0; j < m_height; ++j) { + for (unsigned int i = 0; i < m_width; ++i) { + unsigned int ii = scalei * i; + unsigned int jj = scalej * j; + memcpy(&m_buffer[(j * m_width + i) * m_pixelSize], + &data[(jj * stride + offset + ii) * m_pixelSize], + m_pixelSize); + } + } + } + } else { + // Convert pixel by pixel, with nearest point sampling. + // If src and dst are both int-based, don't round trip to float. + size_t pixelSize = HdDataSizeOfFormat(format); + bool convertAsInt = (HdGetComponentFormat(format) == HdFormatInt32) + && (HdGetComponentFormat(m_format) + == HdFormatInt32); + + float scalei = width / float(m_width); + float scalej = height / float(m_height); + for (unsigned int j = 0; j < m_height; ++j) { + for (unsigned int i = 0; i < m_width; ++i) { + unsigned int ii = scalei * i; + unsigned int jj = scalej * j; + if (convertAsInt) { + _ConvertPixel( + m_format, + static_cast( + &m_buffer[(j * m_width + i) * m_pixelSize]), + format, &data[(jj * stride + offset + ii) * pixelSize]); + } else { + _ConvertPixel( + m_format, + static_cast( + &m_buffer[(j * m_width + i) * m_pixelSize]), + format, &data[(jj * stride + offset + ii) * pixelSize]); + } + } + } + } +} + +void +HdCyclesRenderBuffer::_Deallocate() +{ + m_width = 0; + m_height = 0; + m_format = HdFormatInvalid; + m_buffer.resize(0); + m_mappers.store(0); + m_converged.store(false); +} + +PXR_NAMESPACE_CLOSE_SCOPE \ No newline at end of file diff --git a/plugin/hdCycles/renderBuffer.h b/plugin/hdCycles/renderBuffer.h new file mode 100644 index 00000000..d687537b --- /dev/null +++ b/plugin/hdCycles/renderBuffer.h @@ -0,0 +1,165 @@ +// Copyright 2020 Tangent Animation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef HD_CYCLES_RENDER_BUFFER_H +#define HD_CYCLES_RENDER_BUFFER_H + +#include "api.h" + +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +/** + * @brief Utility class for handling HdCycles Render Buffers + * This handles 2d images for render output. + * + */ +class HdCyclesRenderBuffer : public HdRenderBuffer { +public: + /** + * @brief Construct a new HdCycles Render Buffer object + * + * @param id Path to the Render Buffer Primitive + */ + HDCYCLES_API HdCyclesRenderBuffer(const SdfPath& id); + + /** + * @brief Destroy the HdCycles Render Buffer object + * + */ + HDCYCLES_API ~HdCyclesRenderBuffer() override = default; + + /** + * @brief Allocates the memory used by the render buffer + * + * @param dimensions 3 Dimension Vector describing the dimensions of the + * render buffer + * @param format HdFormat specifying the format of the Render Buffer + * @param multiSampled Bool to indicate if the Render Buffer is multisampled + * @return Returns true if allocation was successful + */ + HDCYCLES_API + bool Allocate(const GfVec3i& dimensions, HdFormat format, + bool multiSampled) override; + + /** + * @return Returns the width of the render buffer + */ + HDCYCLES_API + unsigned int GetWidth() const override; + + /** + * @return Returns the height of the render buffer + */ + HDCYCLES_API + unsigned int GetHeight() const override; + + /** + * @return Returns the depth of the render buffer + */ + HDCYCLES_API + unsigned int GetDepth() const override; + + /** + * @return Returns the format of the render buffer + */ + HDCYCLES_API + HdFormat GetFormat() const override; + + /** + * @return Returns if the render buffer is multi-sampled + */ + HDCYCLES_API + bool IsMultiSampled() const override; + + /** + * @brief Maps the render buffer to the system memory. + * This returns the Cycles representation of data stored in _buffer + * + * @return Pointer to the render buffer mapped to system memory + */ + HDCYCLES_API + void* Map() override; + + /** + * @brief Unmaps the render buffer by decrementing ref count. + * TODO: Should this free memory? + * + * @return HDCYCLES_API Unmap + */ + HDCYCLES_API + void Unmap() override; + + /** + * @return Returns true if the render buffer is mapped to system memory + */ + HDCYCLES_API + bool IsMapped() const override; + + /** + * @brief Resolve the buffer so that reads reflect the latest writes + * This does nothing. + */ + HDCYCLES_API + void Resolve() override; + + /** + * @return Returns true if the buffer is converged. + */ + HDCYCLES_API + bool IsConverged() const override; + /** + * @brief Set whether or not the buffer is Converged + * + * @param cv Is Converged + */ + void SetConverged(bool cv); + + /** + * @brief Helper to blit the render buffer data + * + * @param format Input format + * @param width Width of buffer + * @param height Height of buffer + * @param offset Offset between pixels + * @param stride Stride of pixel + * @param data Pointer to data + */ + void Blit(HdFormat format, int width, int height, int offset, int stride, + uint8_t const* data); + +protected: + /** + * @brief Deallocate memory allocated by the render buffer + * TODO: Implement this + */ + HDCYCLES_API + void _Deallocate() override; + +private: + unsigned int m_width; + unsigned int m_height; + HdFormat m_format; + unsigned int m_pixelSize; + + std::vector m_buffer; + std::atomic m_mappers; + std::atomic m_converged; +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // HD_CYCLES_RENDER_BUFFER_H diff --git a/plugin/hdCycles/renderDelegate.cpp b/plugin/hdCycles/renderDelegate.cpp new file mode 100644 index 00000000..f9a6655d --- /dev/null +++ b/plugin/hdCycles/renderDelegate.cpp @@ -0,0 +1,591 @@ +// Copyright 2020 Tangent Animation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "renderDelegate.h" + +#include "basisCurves.h" +#include "camera.h" +#include "config.h" +#include "instancer.h" +#include "light.h" +#include "material.h" +#include "mesh.h" +#include "points.h" +#include "renderBuffer.h" +#include "renderParam.h" +#include "renderPass.h" +#include "utils.h" + +#include + +#include + +#include +#include +#include +#include +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +// clang-format off +TF_DEFINE_PRIVATE_TOKENS(_tokens, + (cycles) + (openvdbAsset) +); +// clang-format on + +TF_DEFINE_PUBLIC_TOKENS(HdCyclesIntegratorTokens, HDCYCLES_INTEGRATOR_TOKENS); + +// clang-format off +const TfTokenVector HdCyclesRenderDelegate::SUPPORTED_RPRIM_TYPES = { + HdPrimTypeTokens->mesh, + HdPrimTypeTokens->basisCurves, + HdPrimTypeTokens->points, +}; + +const TfTokenVector HdCyclesRenderDelegate::SUPPORTED_SPRIM_TYPES = { + HdPrimTypeTokens->camera, + HdPrimTypeTokens->material, + HdPrimTypeTokens->cylinderLight, + HdPrimTypeTokens->distantLight, + HdPrimTypeTokens->diskLight, + HdPrimTypeTokens->domeLight, + HdPrimTypeTokens->rectLight, + HdPrimTypeTokens->sphereLight, +}; + +const TfTokenVector HdCyclesRenderDelegate::SUPPORTED_BPRIM_TYPES = { + HdPrimTypeTokens->renderBuffer +}; + +// clang-format on + +HdCyclesRenderDelegate::HdCyclesRenderDelegate() + : m_hasStarted(false) +{ + _Initialize(); +} + +void +HdCyclesRenderDelegate::_Initialize() +{ + // -- Initialize Render Param (Core cycles wrapper) + m_renderParam.reset(new HdCyclesRenderParam()); + + if (!m_renderParam->Initialize()) + return; + + // -- Initialize Render Delegate components + + m_resourceRegistry.reset(new HdResourceRegistry()); + + // -- Setup render settings + + _InitializeCyclesRenderSettings(); + + _PopulateDefaultSettings(m_settingDescriptors); + + // Set default render settings in cycles + for (size_t i = 0; i < m_settingDescriptors.size(); ++i) { + _SetRenderSetting(m_settingDescriptors[i].key, + m_settingDescriptors[i].defaultValue); + } +} + +HdCyclesRenderDelegate::~HdCyclesRenderDelegate() +{ + m_renderParam->StopRender(); + m_resourceRegistry.reset(); +} + +TfTokenVector const& +HdCyclesRenderDelegate::GetSupportedRprimTypes() const +{ + return SUPPORTED_RPRIM_TYPES; +} + +TfTokenVector const& +HdCyclesRenderDelegate::GetSupportedSprimTypes() const +{ + return SUPPORTED_SPRIM_TYPES; +} + +TfTokenVector const& +HdCyclesRenderDelegate::GetSupportedBprimTypes() const +{ + return SUPPORTED_BPRIM_TYPES; +} + +void +HdCyclesRenderDelegate::_InitializeCyclesRenderSettings() +{ + static const HdCyclesConfig& config = HdCyclesConfig::GetInstance(); + + m_settingDescriptors.push_back( + { std::string("Use Motion Blur"), + HdCyclesRenderSettingsTokens->useMotionBlur, + VtValue(m_renderParam->GetUseMotionBlur()) }); + + m_settingDescriptors.push_back( + { std::string("Motion Steps"), + HdCyclesRenderSettingsTokens->motionSteps, + VtValue(m_renderParam->GetMotionSteps()) }); + + // -- Featureset + + m_settingDescriptors.push_back( + { std::string("Device"), HdCyclesRenderSettingsTokens->device, + VtValue(m_renderParam->GetDeviceTypeName()) }); + + m_settingDescriptors.push_back( + { std::string("Use Experimental Cycles"), + HdCyclesRenderSettingsTokens->experimental, + VtValue(m_renderParam->GetUseExperimental()) }); + + m_settingDescriptors.push_back({ std::string("Max Samples"), + HdCyclesRenderSettingsTokens->samples, + VtValue(m_renderParam->GetMaxSamples()) }); + + m_settingDescriptors.push_back({ std::string("Num Threads"), + HdCyclesRenderSettingsTokens->threads, + VtValue(m_renderParam->GetNumThreads()) }); + + m_settingDescriptors.push_back({ std::string("Pixel Size"), + HdCyclesRenderSettingsTokens->pixelSize, + VtValue(m_renderParam->GetPixelSize()) }); + + m_settingDescriptors.push_back({ std::string("Tile Size"), + HdCyclesRenderSettingsTokens->tileSize, + VtValue(m_renderParam->GetTileSize()) }); + + m_settingDescriptors.push_back( + { std::string("Start Resolution"), + HdCyclesRenderSettingsTokens->startResolution, + VtValue(m_renderParam->GetStartResolution()) }); + + m_settingDescriptors.push_back({ std::string("Exposure"), + HdCyclesRenderSettingsTokens->exposure, + VtValue(m_renderParam->GetExposure()) }); + + m_settingDescriptors.push_back( + { std::string("Motion Position"), + HdCyclesRenderSettingsTokens->motionBlurPosition, + VtValue((int)m_renderParam->GetShutterMotionPosition()) }); + + // -- Integrator Settings + + m_settingDescriptors.push_back( + { std::string("Integrator Method"), + HdCyclesRenderSettingsTokens->integratorMethod, + VtValue(config.integrator_method) }); + + m_settingDescriptors.push_back( + { std::string("Diffuse Samples"), + HdCyclesRenderSettingsTokens->lightPathsDiffuse, + VtValue(config.diffuse_samples) }); + + m_settingDescriptors.push_back( + { std::string("Glossy Samples"), + HdCyclesRenderSettingsTokens->lightPathsGlossy, + VtValue(config.glossy_samples) }); + + m_settingDescriptors.push_back( + { std::string("Transmission Samples"), + HdCyclesRenderSettingsTokens->lightPathsTransmission, + VtValue(config.transmission_samples) }); + + m_settingDescriptors.push_back({ std::string("AO Samples"), + HdCyclesRenderSettingsTokens->lightPathsAO, + VtValue(config.ao_samples) }); + + m_settingDescriptors.push_back( + { std::string("Mesh Light Samples"), + HdCyclesRenderSettingsTokens->lightPathsMeshLight, + VtValue(config.mesh_light_samples) }); + + m_settingDescriptors.push_back( + { std::string("Subsurface Samples"), + HdCyclesRenderSettingsTokens->lightPathsSubsurface, + VtValue(config.subsurface_samples) }); + + m_settingDescriptors.push_back( + { std::string("Volume Samples"), + HdCyclesRenderSettingsTokens->lightPathsVolume, + VtValue(config.volume_samples) }); +} + +void +HdCyclesRenderDelegate::_SetRenderSetting(const TfToken& key, + const VtValue& _value) +{ + if (!m_renderParam && !m_renderParam->GetCyclesSession()) + return; + + static const HdCyclesConfig& config = HdCyclesConfig::GetInstance(); + + ccl::Integrator* integrator = m_renderParam->GetCyclesScene()->integrator; + bool integrator_updated = false; + + if (key == HdCyclesRenderSettingsTokens->useMotionBlur) { + _CheckForBoolValue(_value, [&](const bool b) { + if (m_renderParam->GetUseMotionBlur() != b) + m_renderParam->SetUseMotionBlur(b); + }); + } else if (key == HdCyclesRenderSettingsTokens->motionSteps) { + _CheckForIntValue(_value, [&](const int i) { + if (m_renderParam->GetMotionSteps() != i) + m_renderParam->SetMotionSteps(i); + }); + } else if (key == HdCyclesRenderSettingsTokens->experimental) { + _CheckForBoolValue(_value, [&](const bool b) { + if (m_renderParam->GetUseExperimental() != b) + m_renderParam->SetUseExperimental(b); + }); + } else if (key == HdCyclesRenderSettingsTokens->samples) { + _CheckForIntValue(_value, [&](const int i) { + if (m_renderParam->GetMaxSamples() != i) + m_renderParam->SetMaxSamples(i); + }); + } else if (key == HdCyclesRenderSettingsTokens->threads) { + _CheckForIntValue(_value, [&](const int i) { + if (m_renderParam->GetNumThreads() != i) + m_renderParam->SetNumThreads(i); + }); + } else if (key == HdCyclesRenderSettingsTokens->tileSize) { + _CheckForVec2iValue(_value, [&](const pxr::GfVec2i v) { + if (m_renderParam->GetTileSize() != v) + m_renderParam->SetTileSize(v); + }); + } else if (key == HdCyclesRenderSettingsTokens->pixelSize) { + _CheckForIntValue(_value, [&](const int i) { + if (m_renderParam->GetPixelSize() != i) + m_renderParam->GetCyclesSession()->params.pixel_size = i; + }); + } else if (key == HdCyclesRenderSettingsTokens->startResolution) { + _CheckForIntValue(_value, [&](const int i) { + if (m_renderParam->GetStartResolution() != i) + m_renderParam->GetCyclesSession()->params.start_resolution = i; + }); + } else if (key == HdCyclesRenderSettingsTokens->device) { + _CheckForStringValue(_value, [&](const std::string s) { + if (m_renderParam->GetDeviceTypeName() != s) + m_renderParam->SetDeviceType(s); + }); + } else if (key == HdCyclesRenderSettingsTokens->integratorMethod) { + _CheckForStringValue(_value, [&](const std::string s) { + ccl::Integrator::Method m = ccl::Integrator::PATH; + + if (boost::iequals(s, "BRANCHED_PATH")) { + m = ccl::Integrator::BRANCHED_PATH; + } + + if (integrator->method != m) { + integrator->method = m; + integrator_updated = true; + } + }); + } else if (key == HdCyclesRenderSettingsTokens->lightPathsDiffuse) { + _CheckForIntValue(_value, [&](const int i) { + if (integrator->diffuse_samples != i) { + integrator->diffuse_samples = i; + integrator_updated = true; + } + }); + } else if (key == HdCyclesRenderSettingsTokens->lightPathsGlossy) { + _CheckForIntValue(_value, [&](const int i) { + if (integrator->glossy_samples != i) { + integrator->glossy_samples = i; + integrator_updated = true; + } + }); + } else if (key == HdCyclesRenderSettingsTokens->lightPathsTransmission) { + _CheckForIntValue(_value, [&](const int i) { + if (integrator->transmission_samples != i) { + integrator->transmission_samples = i; + integrator_updated = true; + } + }); + } else if (key == HdCyclesRenderSettingsTokens->lightPathsAO) { + _CheckForIntValue(_value, [&](const int i) { + if (integrator->ao_samples != i) { + integrator->ao_samples = i; + integrator_updated = true; + } + }); + } else if (key == HdCyclesRenderSettingsTokens->lightPathsMeshLight) { + _CheckForIntValue(_value, [&](const int i) { + if (integrator->mesh_light_samples != i) { + integrator->mesh_light_samples = i; + integrator_updated = true; + } + }); + } else if (key == HdCyclesRenderSettingsTokens->lightPathsSubsurface) { + _CheckForIntValue(_value, [&](const int i) { + if (integrator->subsurface_samples != i) { + integrator->subsurface_samples = i; + integrator_updated = true; + } + }); + } else if (key == HdCyclesRenderSettingsTokens->lightPathsVolume) { + _CheckForIntValue(_value, [&](const int i) { + if (integrator->volume_samples != i) { + integrator->volume_samples = i; + integrator_updated = true; + } + }); + } else if (key == HdCyclesRenderSettingsTokens->exposure) { + _CheckForFloatValue(_value, [&](float f) { + if (m_renderParam->GetExposure() != f) + m_renderParam->SetExposure(f); + }); + _CheckForDoubleValue(_value, [&](double d) { + if (m_renderParam->GetExposure() != d) + m_renderParam->SetExposure((float)d); + }); + } + + if (integrator_updated) { + integrator->tag_update(m_renderParam->GetCyclesScene()); + } +} + +void +HdCyclesRenderDelegate::SetRenderSetting(const TfToken& key, + const VtValue& value) +{ + HdRenderDelegate::SetRenderSetting(key, value); + _SetRenderSetting(key, value); + m_renderParam->Interrupt(); +} + +HdRenderSettingDescriptorList +HdCyclesRenderDelegate::GetRenderSettingDescriptors() const +{ + return m_settingDescriptors; +} + +HdResourceRegistrySharedPtr +HdCyclesRenderDelegate::GetResourceRegistry() const +{ + return m_resourceRegistry; +} + +HdRenderPassSharedPtr +HdCyclesRenderDelegate::CreateRenderPass(HdRenderIndex* index, + HdRprimCollection const& collection) +{ + HdRenderPassSharedPtr xx = HdRenderPassSharedPtr( + new HdCyclesRenderPass(this, index, collection)); + m_renderPass = (HdCyclesRenderPass*)xx.get(); + return xx; +} + +void +HdCyclesRenderDelegate::CommitResources(HdChangeTracker* tracker) +{ + if (!m_hasStarted) { + m_renderParam->StartRender(); + m_hasStarted = true; + } + + m_renderParam->CommitResources(); +} + +HdRprim* +HdCyclesRenderDelegate::CreateRprim(TfToken const& typeId, + SdfPath const& rprimId, + SdfPath const& instancerId) +{ + if (typeId == HdPrimTypeTokens->mesh) { + return new HdCyclesMesh(rprimId, instancerId, this); + } else if (typeId == HdPrimTypeTokens->basisCurves) { + return new HdCyclesBasisCurves(rprimId, instancerId, this); + } else if (typeId == HdPrimTypeTokens->points) { + return new HdCyclesPoints(rprimId, instancerId, this); + } else { + TF_CODING_ERROR("Unknown Rprim type=%s id=%s", typeId.GetText(), + rprimId.GetText()); + } + return nullptr; +} + +void +HdCyclesRenderDelegate::DestroyRprim(HdRprim* rPrim) +{ + if (rPrim) + delete rPrim; +} + +HdSprim* +HdCyclesRenderDelegate::CreateSprim(TfToken const& typeId, + SdfPath const& sprimId) +{ + if (typeId == HdPrimTypeTokens->camera) { + return new HdCyclesCamera(sprimId, this); + } + if (typeId == HdPrimTypeTokens->material) { + return new HdCyclesMaterial(sprimId, this); + } + if (typeId == HdPrimTypeTokens->distantLight + || typeId == HdPrimTypeTokens->domeLight + || typeId == HdPrimTypeTokens->rectLight + || typeId == HdPrimTypeTokens->diskLight + || typeId == HdPrimTypeTokens->cylinderLight + || typeId == HdPrimTypeTokens->sphereLight) { + return new HdCyclesLight(sprimId, typeId, this); + } + TF_CODING_ERROR("Unknown Sprim type=%s id=%s", typeId.GetText(), + sprimId.GetText()); + return nullptr; +} + +HdSprim* +HdCyclesRenderDelegate::CreateFallbackSprim(TfToken const& typeId) +{ + if (typeId == HdPrimTypeTokens->camera) { + return new HdCyclesCamera(SdfPath::EmptyPath(), this); + } else if (typeId == HdPrimTypeTokens->material) { + return new HdCyclesMaterial(SdfPath::EmptyPath(), this); + } else if (typeId == HdPrimTypeTokens->distantLight + || typeId == HdPrimTypeTokens->domeLight + || typeId == HdPrimTypeTokens->rectLight + || typeId == HdPrimTypeTokens->diskLight + || typeId == HdPrimTypeTokens->cylinderLight + || typeId == HdPrimTypeTokens->sphereLight) { + return new HdCyclesLight(SdfPath::EmptyPath(), typeId, this); + } + TF_CODING_ERROR("Creating unknown fallback sprim type=%s", + typeId.GetText()); + return nullptr; +} + +void +HdCyclesRenderDelegate::DestroySprim(HdSprim* sPrim) +{ + if (sPrim) + delete sPrim; +} + +HdBprim* +HdCyclesRenderDelegate::CreateBprim(TfToken const& typeId, + SdfPath const& bprimId) +{ + if (typeId == HdPrimTypeTokens->renderBuffer) { + return new HdCyclesRenderBuffer(bprimId); + } + TF_CODING_ERROR("Unknown Bprim type=%s id=%s", typeId.GetText(), + bprimId.GetText()); + return nullptr; +} + +HdBprim* +HdCyclesRenderDelegate::CreateFallbackBprim(TfToken const& typeId) +{ + if (typeId == HdPrimTypeTokens->renderBuffer) { + return new HdCyclesRenderBuffer(SdfPath()); + } + + TF_CODING_ERROR("Creating unknown fallback bprim type=%s", + typeId.GetText()); + return nullptr; +} + +void +HdCyclesRenderDelegate::DestroyBprim(HdBprim* bPrim) +{ + if (bPrim) + delete bPrim; +} + +HdInstancer* +HdCyclesRenderDelegate::CreateInstancer(HdSceneDelegate* delegate, + SdfPath const& id, + SdfPath const& instancerId) +{ + return new HdCyclesInstancer(delegate, id, instancerId); +} + +void +HdCyclesRenderDelegate::DestroyInstancer(HdInstancer* instancer) +{ + delete instancer; +} + +HdRenderParam* +HdCyclesRenderDelegate::GetRenderParam() const +{ + return m_renderParam.get(); +} + +HdCyclesRenderParam* +HdCyclesRenderDelegate::GetCyclesRenderParam() const +{ + return m_renderParam.get(); +} + +HdAovDescriptor +HdCyclesRenderDelegate::GetDefaultAovDescriptor(TfToken const& name) const +{ + if (name == HdAovTokens->color) { + HdFormat colorFormat = GetCyclesRenderParam() + ->GetCyclesSession() + ->params.display_buffer_linear + ? HdFormatFloat16Vec4 + : HdFormatUNorm8Vec4; + return HdAovDescriptor(colorFormat, false, VtValue(GfVec4f(0.0f))); + } else if (name == HdAovTokens->depth) { + return HdAovDescriptor(HdFormatFloat32, false, VtValue(1.0f)); + } else if (name == HdAovTokens->primId || name == HdAovTokens->instanceId + || name == HdAovTokens->elementId) { + return HdAovDescriptor(HdFormatInt32, false, VtValue(-1)); + } + + return HdAovDescriptor(); +} + +TfToken +HdCyclesRenderDelegate::GetMaterialNetworkSelector() const +{ + return _tokens->cycles; +} + +TfToken +HdCyclesRenderDelegate::GetMaterialBindingPurpose() const +{ + return HdTokens->full; +} + +bool +HdCyclesRenderDelegate::IsPauseSupported() const +{ + return true; +} + +bool +HdCyclesRenderDelegate::Pause() +{ + m_renderParam->PauseRender(); + return true; +} + +bool +HdCyclesRenderDelegate::Resume() +{ + m_renderParam->ResumeRender(); + return true; +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/plugin/hdCycles/renderDelegate.h b/plugin/hdCycles/renderDelegate.h new file mode 100644 index 00000000..2f54ba11 --- /dev/null +++ b/plugin/hdCycles/renderDelegate.h @@ -0,0 +1,189 @@ +// Copyright 2020 Tangent Animation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef HD_CYCLES_RENDER_DELEGATE_H +#define HD_CYCLES_RENDER_DELEGATE_H + +#include "api.h" + +#include +#include +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +class HdCyclesRenderParam; +class HdCyclesRenderPass; + +// clang-format off +TF_DEFINE_PRIVATE_TOKENS(HdCyclesRenderSettingsTokens, + ((useDefaultBackground, "useDefaultBackground")) + ((device, "device")) + ((CPU, "CPU")) + ((GPU, "GPU")) + ((experimental, "experimental")) + ((samples, "samples")) + ((integrator, "integrator")) + ((integratorMethod, "integratorMethod")) + ((integratorName, "ci:integrator:name")) + ((integratorPath, "ci:integrator:path")) + ((integratorBranchedPath, "ci:integrator:branchedPath")) + ((threads, "threads")) + ((pixelSize, "pixelSize")) + ((seed, "seed")) + ((pattern, "pattern")) + ((squareSamples,"squareSamples")) + ((tileSize, "tileSize")) + ((startResolution, "startResolution")) + ((lightPathsTotal, "lightPaths:total")) + ((lightPathsDiffuse,"lightPaths:diffuse")) + ((lightPathsGlossy, "lightPaths:glossy")) + ((lightPathsTransmission, "lightPaths:transmission")) + ((lightPathsAO, "lightPaths:ambientOcclussion")) + ((lightPathsMeshLight, "lightPaths:meshLight")) + ((lightPathsSubsurface, "lightPaths:subsurface")) + ((lightPathsVolume, "lightPaths:volume")) + ((volumeStepSize, "volume:stepSize")) + ((volumeMaxSteps, "volume:maxSteps")) + ((hairShape, "hair:shape")) + ((hairShapeThick, "hair:shape:thick")) + ((hairShapeRibbons, "hair:shape:ribbons")) + ((useMotionBlur, "useMotionBlur")) + ((motionSteps, "motionSteps")) + ((motionBlurPosition, "motionBlur:position")) + ((motionBlurPositionStart, "motionBlur:position:start")) + ((motionBlurPositionCenter, "motionBlur:position:center")) + ((motionBlurPositionEnd, "motionBlur:position:end")) + ((useRollingShutter, "useRollingShutter")) + ((rollingShutterDuration, "rollingShutterDuration")) + ((exposure, "exposure")) + ((pixelFilter, "pixelFilter")) + ((pixelFilterBlackmanHarris, "pixelFilter:blackmanHarris")) + ((pixelFilterBox, "pixelFilter:box")) + ((pixelFilterGaussian, "pixelFilter:gaussian")) +); + +#define HDCYCLES_INTEGRATOR_TOKENS \ + (BranchedPathTracing) \ + (PathTracing) + +TF_DECLARE_PUBLIC_TOKENS(HdCyclesIntegratorTokens, + HDCYCLES_API, + HDCYCLES_INTEGRATOR_TOKENS +); + +// clang-format on + +/** + * @brief Represents the core interactions between Cycles and Hydra. + * Responsible for creating and deleting scene primitives, and + * renderpasses. + * + */ +class HdCyclesRenderDelegate : public HdRenderDelegate { +public: + /** + * @brief Render delegate constructor. + * + */ + HDCYCLES_API HdCyclesRenderDelegate(); + HDCYCLES_API ~HdCyclesRenderDelegate() override; + + HDCYCLES_API HdRenderParam* GetRenderParam() const override; + HDCYCLES_API HdCyclesRenderParam* GetCyclesRenderParam() const; + + /// -- Supported types + HDCYCLES_API virtual const TfTokenVector& + GetSupportedRprimTypes() const override; + HDCYCLES_API virtual const TfTokenVector& + GetSupportedSprimTypes() const override; + HDCYCLES_API virtual const TfTokenVector& + GetSupportedBprimTypes() const override; + + HDCYCLES_API bool IsPauseSupported() const override; + + HDCYCLES_API bool Pause() override; + HDCYCLES_API bool Resume() override; + + HDCYCLES_API void _InitializeCyclesRenderSettings(); + + HDCYCLES_API void SetRenderSetting(const TfToken& key, + const VtValue& value) override; + + HDCYCLES_API HdRenderSettingDescriptorList + GetRenderSettingDescriptors() const override; + + HDCYCLES_API virtual HdResourceRegistrySharedPtr + GetResourceRegistry() const override; + + // Prims + HDCYCLES_API virtual HdRenderPassSharedPtr + CreateRenderPass(HdRenderIndex* index, + HdRprimCollection const& collection) override; + + HDCYCLES_API HdInstancer* + CreateInstancer(HdSceneDelegate* delegate, SdfPath const& id, + SdfPath const& instancerId) override; + HDCYCLES_API void DestroyInstancer(HdInstancer* instancer) override; + + HDCYCLES_API HdRprim* CreateRprim(TfToken const& typeId, + SdfPath const& rprimId, + SdfPath const& instancerId) override; + HDCYCLES_API void DestroyRprim(HdRprim* rPrim) override; + + HDCYCLES_API HdSprim* CreateSprim(TfToken const& typeId, + SdfPath const& sprimId) override; + HDCYCLES_API HdSprim* CreateFallbackSprim(TfToken const& typeId) override; + HDCYCLES_API void DestroySprim(HdSprim* sprim) override; + + HDCYCLES_API HdBprim* CreateBprim(TfToken const& typeId, + SdfPath const& bprimId) override; + HDCYCLES_API HdBprim* CreateFallbackBprim(TfToken const& typeId) override; + HDCYCLES_API void DestroyBprim(HdBprim* bprim) override; + + HDCYCLES_API virtual HdAovDescriptor + GetDefaultAovDescriptor(TfToken const& name) const override; + + HDCYCLES_API void CommitResources(HdChangeTracker* tracker) override; + + HDCYCLES_API TfToken GetMaterialNetworkSelector() const override; + HDCYCLES_API virtual TfToken GetMaterialBindingPurpose() const override; + +protected: + static const TfTokenVector SUPPORTED_RPRIM_TYPES; + static const TfTokenVector SUPPORTED_SPRIM_TYPES; + static const TfTokenVector SUPPORTED_BPRIM_TYPES; + + void _SetRenderSetting(const TfToken& key, const VtValue& value); + +private: + // This class does not support copying. + HdCyclesRenderDelegate(const HdCyclesRenderDelegate&) = delete; + HdCyclesRenderDelegate& operator=(const HdCyclesRenderDelegate&) = delete; + + void _Initialize(); + +protected: // data + HdCyclesRenderPass* m_renderPass; + HdRenderSettingDescriptorList m_settingDescriptors; + HdResourceRegistrySharedPtr m_resourceRegistry; + + std::unique_ptr m_renderParam; + bool m_hasStarted; +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // HD_CYCLES_RENDER_DELEGATE_H diff --git a/plugin/hdCycles/renderParam.cpp b/plugin/hdCycles/renderParam.cpp new file mode 100644 index 00000000..dab8fca8 --- /dev/null +++ b/plugin/hdCycles/renderParam.cpp @@ -0,0 +1,858 @@ +// Copyright 2020 Tangent Animation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "renderParam.h" + +#include "config.h" +#include "renderDelegate.h" +#include "utils.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +double +clamp(double d, double min, double max) +{ + const double t = d < min ? min : d; + return t > max ? max : t; +} + +HdCyclesRenderParam::HdCyclesRenderParam() + : m_shouldUpdate(false) +{ + _InitializeDefaults(); +} + +void +HdCyclesRenderParam::_InitializeDefaults() +{ + static const HdCyclesConfig& config = HdCyclesConfig::GetInstance(); + m_deviceName = config.device_name; + m_useMotionBlur = config.enable_motion_blur; +} + +float +HdCyclesRenderParam::GetProgress() +{ + return m_cyclesSession->progress.get_progress(); +} + +void +HdCyclesRenderParam::_SessionPrintStatus() +{ + std::string status, substatus; + + /* get status */ + float progress = m_cyclesSession->progress.get_progress(); + m_cyclesSession->progress.get_status(status, substatus); + + if (substatus != "") + status += ": " + substatus; + + if (HdCyclesConfig::GetInstance().enable_logging) + std::cout << "cycles: " << progress << " : " << status << '\n'; +} + +bool +HdCyclesRenderParam::Initialize() +{ + return _CyclesInitialize(); +} + +void +HdCyclesRenderParam::StartRender() +{ + CyclesStart(); +} + +void +HdCyclesRenderParam::StopRender() +{ + _CyclesExit(); +} + +void +HdCyclesRenderParam::RestartRender() +{ + StopRender(); + Initialize(); + StartRender(); +} + +void +HdCyclesRenderParam::PauseRender() +{ + m_cyclesSession->set_pause(true); +} + +void +HdCyclesRenderParam::ResumeRender() +{ + m_cyclesSession->set_pause(false); +} + +void +HdCyclesRenderParam::Interrupt(bool a_forceUpdate) +{ + m_shouldUpdate = true; + PauseRender(); +} + +void +HdCyclesRenderParam::CommitResources() +{ + if (m_shouldUpdate) { + if (m_cyclesScene->lights.size() > 0) { + if (!m_hasDomeLight) + SetBackgroundShader(nullptr, false); + } else { + SetBackgroundShader(nullptr, true); + } + + CyclesReset(false); + m_shouldUpdate = false; + ResumeRender(); + } +} + +void +HdCyclesRenderParam::SetBackgroundShader(ccl::Shader* a_shader, bool a_emissive) +{ + if (a_shader) + m_cyclesScene->default_background = a_shader; + else { + // TODO: These aren't properly destroyed from memory + + // Create empty background shader + m_cyclesScene->default_background = new ccl::Shader(); + m_cyclesScene->default_background->name = "default_background"; + m_cyclesScene->default_background->graph = new ccl::ShaderGraph(); + if (a_emissive) { + ccl::BackgroundNode* bgNode = new ccl::BackgroundNode(); + bgNode->color = ccl::make_float3(0.6f, 0.6f, 0.6f); + + m_cyclesScene->default_background->graph->add(bgNode); + + ccl::ShaderNode* out + = m_cyclesScene->default_background->graph->output(); + m_cyclesScene->default_background->graph->connect( + bgNode->output("Background"), out->input("Surface")); + } + + m_cyclesScene->default_background->tag_update(m_cyclesScene); + + m_cyclesScene->shaders.push_back(m_cyclesScene->default_background); + } + m_cyclesScene->background->tag_update(m_cyclesScene); +} + +/* ======= Cycles Settings ======= */ + +// -- Use Experimental Cycles rendering + +const bool& +HdCyclesRenderParam::GetUseExperimental() +{ + return m_cyclesSession->params.experimental; +} + +void +HdCyclesRenderParam::SetUseExperimental(const bool& a_value) +{ + m_cyclesSession->params.experimental = a_value; +} + +// -- Maximum samples used in render + +const int& +HdCyclesRenderParam::GetMaxSamples() +{ + return m_cyclesSession->params.samples; +} + +void +HdCyclesRenderParam::SetMaxSamples(const int& a_value) +{ + m_cyclesSession->params.samples = a_value; +} + +// -- Number of threads used to render + +const int& +HdCyclesRenderParam::GetNumThreads() +{ + return m_cyclesSession->params.threads; +} + +void +HdCyclesRenderParam::SetNumThreads(const int& a_value) +{ + m_cyclesSession->params.threads = a_value; +} + +// -- Size of individual pixel + +const int& +HdCyclesRenderParam::GetPixelSize() +{ + return m_cyclesSession->params.pixel_size; +} + +void +HdCyclesRenderParam::SetPixelSize(const int& a_value) +{ + m_cyclesSession->params.pixel_size = a_value; +} + +// -- Size of render tile + +const pxr::GfVec2i +HdCyclesRenderParam::GetTileSize() +{ + return pxr::GfVec2i(m_cyclesSession->params.tile_size.x, + m_cyclesSession->params.tile_size.y); +} + +void +HdCyclesRenderParam::SetTileSize(const pxr::GfVec2i& a_value) +{ + m_cyclesSession->params.tile_size.x = a_value[0]; + m_cyclesSession->params.tile_size.y = a_value[1]; +} + +void +HdCyclesRenderParam::SetTileSize(int a_x, int a_y) +{ + m_cyclesSession->params.tile_size.x = a_x; + m_cyclesSession->params.tile_size.y = a_y; +} + +// -- Resolution of initial progressive render + +const int& +HdCyclesRenderParam::GetStartResolution() +{ + return m_cyclesSession->params.start_resolution; +} + +void +HdCyclesRenderParam::SetStartResolution(const int& a_value) +{ + m_cyclesSession->params.start_resolution = a_value; +} + +// -- Exposure of film + +const float& +HdCyclesRenderParam::GetExposure() +{ + return m_cyclesScene->film->exposure; +} + +void +HdCyclesRenderParam::SetExposure(float a_exposure) +{ + m_cyclesScene->film->exposure = a_exposure; + m_cyclesScene->film->tag_update(m_cyclesScene); +} + +// -- Cycles render device + +const ccl::DeviceType& +HdCyclesRenderParam::GetDeviceType() +{ + return m_deviceType; +} + +const std::string& +HdCyclesRenderParam::GetDeviceTypeName() +{ + return m_deviceName; +} + +bool +HdCyclesRenderParam::SetDeviceType(ccl::DeviceType a_deviceType, + ccl::SessionParams& params) +{ + if (a_deviceType == ccl::DeviceType::DEVICE_NONE) { + TF_WARN("Attempted to set device of type DEVICE_NONE."); + return false; + } + + m_deviceType = a_deviceType; + m_deviceName = ccl::Device::string_from_type(a_deviceType); + + return _SetDevice(m_deviceType, params); +} + +bool +HdCyclesRenderParam::SetDeviceType(const std::string& a_deviceType, + ccl::SessionParams& params) +{ + return SetDeviceType(ccl::Device::type_from_string(a_deviceType.c_str()), + params); +} + +bool +HdCyclesRenderParam::SetDeviceType(const std::string& a_deviceType) +{ + return SetDeviceType(a_deviceType, m_cyclesSession->params); +} + +bool +HdCyclesRenderParam::_SetDevice(const ccl::DeviceType& a_deviceType, + ccl::SessionParams& params) +{ + std::vector devices = ccl::Device::available_devices( + (ccl::DeviceTypeMask)(1 << a_deviceType)); + + bool device_available = false; + + if (!devices.empty()) { + params.device = devices.front(); + device_available = true; + } + + if (params.device.type == ccl::DEVICE_NONE || !device_available) { + TF_RUNTIME_ERROR("No device available exiting."); + } + + return device_available; +} + +// -- Shutter motion position + +void +HdCyclesRenderParam::SetShutterMotionPosition(const int& a_value) +{ + SetShutterMotionPosition((ccl::Camera::MotionPosition)a_value); +} + +void +HdCyclesRenderParam::SetShutterMotionPosition( + const ccl::Camera::MotionPosition& a_value) +{ + m_cyclesScene->camera->motion_position = a_value; +} + +const ccl::Camera::MotionPosition& +HdCyclesRenderParam::GetShutterMotionPosition() +{ + return m_cyclesScene->camera->motion_position; +} + +/* ====== HdCycles Settings ====== */ + +/* ====== Cycles Lifecycle ====== */ + +bool +HdCyclesRenderParam::_CyclesInitialize() +{ + static const HdCyclesConfig& config = HdCyclesConfig::GetInstance(); + + ccl::SessionParams params; + params.display_buffer_linear = config.display_buffer_linear; + + params.shadingsystem = ccl::SHADINGSYSTEM_SVM; + if (config.shading_system == "OSL" + || config.shading_system == "SHADINGSYSTEM_OSL") + params.shadingsystem = ccl::SHADINGSYSTEM_OSL; + + params.background = false; + + /* Use progressive rendering */ + + params.progressive = true; + params.run_denoising = false; + params.write_denoising_passes = false; + params.full_denoising = false; + params.optix_denoising = false; + + params.start_resolution = config.start_resolution; + + params.progressive_refine = false; + params.progressive_update_timeout = 0.1; + params.pixel_size = config.pixel_size; + params.tile_size.x = config.tile_size[0]; + params.tile_size.y = config.tile_size[1]; + params.samples = config.max_samples; + + /* find matching device */ + + bool foundDevice = SetDeviceType(m_deviceName, params); + + if (!foundDevice) + return false; + + m_cyclesSession = new ccl::Session(params); + + if (HdCyclesConfig::GetInstance().enable_logging) + m_cyclesSession->progress.set_update_callback( + std::bind(&HdCyclesRenderParam::_SessionPrintStatus, this)); + + // -- Scene init + ccl::SceneParams sceneParams; + sceneParams.shadingsystem = params.shadingsystem; + + sceneParams.bvh_type = ccl::SceneParams::BVH_DYNAMIC; + if (config.bvh_type == "STATIC") + sceneParams.bvh_type = ccl::SceneParams::BVH_STATIC; + + sceneParams.persistent_data = true; + + m_cyclesScene = new ccl::Scene(sceneParams, m_cyclesSession->device); + + m_width = config.render_width; + m_height = config.render_height; + + m_cyclesScene->camera->width = m_width; + m_cyclesScene->camera->height = m_height; + + m_cyclesScene->camera->compute_auto_viewplane(); + + m_cyclesSession->scene = m_cyclesScene; + + m_bufferParams.width = m_width; + m_bufferParams.height = m_height; + m_bufferParams.full_width = m_width; + m_bufferParams.full_height = m_height; + + m_cyclesScene->film->exposure = config.exposure; + + if (config.enable_transparent_background) + m_cyclesScene->background->transparent = true; + + if (m_useMotionBlur) { + SetShutterMotionPosition(config.shutter_motion_position); + + m_cyclesScene->camera->shuttertime = 0.5f; + m_cyclesScene->camera->motion.clear(); + m_cyclesScene->camera->motion.resize(m_motionSteps, + m_cyclesScene->camera->matrix); + + m_cyclesScene->integrator->motion_blur = true; + m_cyclesScene->integrator->tag_update(m_cyclesScene); + } + + default_vcol_surface = HdCyclesCreateDefaultShader(); + + default_vcol_surface->tag_update(m_cyclesScene); + m_cyclesScene->shaders.push_back(default_vcol_surface); + + // -- Setup curve system manager + + ccl::CurveSystemManager* curve_system_manager + = m_cyclesScene->curve_system_manager; + + curve_system_manager->use_curves = !config.use_old_curves; + curve_system_manager->resolution = config.curve_resolution; + curve_system_manager->subdivisions = config.curve_subdivisions; + curve_system_manager->use_backfacing = config.curve_use_backfaces; + curve_system_manager->use_tangent_normal_geometry + = config.curve_use_tangent_normal_geometry; + curve_system_manager->use_encasing = config.curve_use_encasing; + + if (ccl::string_iequals(config.curve_shape.c_str(), "CURVE_RIBBON")) { + curve_system_manager->curve_shape = ccl::CURVE_RIBBON; + } else { + curve_system_manager->curve_shape = ccl::CURVE_THICK; + } + + if (ccl::string_iequals(config.curve_primitive.c_str(), "CURVE_TRIANGLES")) { + curve_system_manager->primitive = ccl::CURVE_TRIANGLES; + } else if (ccl::string_iequals(config.curve_primitive.c_str(), + "CURVE_LINE_SEGMENTS")) { + curve_system_manager->primitive = ccl::CURVE_LINE_SEGMENTS; + } else if (ccl::string_iequals(config.curve_primitive.c_str(), + "CURVE_SEGMENTS")) { + curve_system_manager->primitive = ccl::CURVE_SEGMENTS; + } else { + curve_system_manager->primitive = ccl::CURVE_RIBBONS; + } + + if (curve_system_manager->primitive == ccl::CURVE_TRIANGLES) { + /* camera facing planes */ + if (curve_system_manager->curve_shape == ccl::CURVE_RIBBON) { + curve_system_manager->triangle_method = ccl::CURVE_CAMERA_TRIANGLES; + curve_system_manager->resolution = 1; + } else if (curve_system_manager->curve_shape == ccl::CURVE_THICK) { + curve_system_manager->triangle_method + = ccl::CURVE_TESSELATED_TRIANGLES; + } + } + /* Line Segments */ + else if (curve_system_manager->primitive == ccl::CURVE_LINE_SEGMENTS) { + if (curve_system_manager->curve_shape == ccl::CURVE_RIBBON) { + /* tangent shading */ + curve_system_manager->line_method = ccl::CURVE_UNCORRECTED; + curve_system_manager->use_encasing = true; + curve_system_manager->use_backfacing = false; + curve_system_manager->use_tangent_normal_geometry = true; + } else if (curve_system_manager->curve_shape == ccl::CURVE_THICK) { + curve_system_manager->line_method = ccl::CURVE_ACCURATE; + curve_system_manager->use_encasing = false; + curve_system_manager->use_tangent_normal_geometry = false; + } + } + /* Curve Segments */ + else if (curve_system_manager->primitive == ccl::CURVE_SEGMENTS) { + if (curve_system_manager->curve_shape == ccl::CURVE_RIBBON) { + curve_system_manager->primitive = ccl::CURVE_RIBBONS; + curve_system_manager->use_backfacing = false; + } + } + + curve_system_manager->tag_update(m_cyclesScene); + + SetBackgroundShader(nullptr); + + m_cyclesSession->reset(m_bufferParams, params.samples); + + + return true; +} + +void +HdCyclesRenderParam::CyclesStart() +{ + m_cyclesSession->start(); +} + +void +HdCyclesRenderParam::_CyclesExit() +{ + m_cyclesSession->set_pause(true); + + m_cyclesScene->mutex.lock(); + + m_cyclesScene->shaders.clear(); + m_cyclesScene->geometry.clear(); + m_cyclesScene->objects.clear(); + m_cyclesScene->lights.clear(); + m_cyclesScene->particle_systems.clear(); + + m_cyclesScene->mutex.unlock(); + + if (m_cyclesSession) { + delete m_cyclesSession; + m_cyclesSession = nullptr; + } +} + +// TODO: Refactor these two resets +void +HdCyclesRenderParam::CyclesReset(bool a_forceUpdate) +{ + m_cyclesScene->mutex.lock(); + + m_cyclesSession->progress.reset(); + + if (m_curveUpdated || m_meshUpdated || m_geometryUpdated + || m_shadersUpdated) { + m_cyclesScene->geometry_manager->tag_update(m_cyclesScene); + m_geometryUpdated = false; + m_meshUpdated = false; + } + + if (m_curveUpdated) { + m_cyclesScene->curve_system_manager->tag_update(m_cyclesScene); + m_curveUpdated = false; + } + + if (m_objectsUpdated || m_shadersUpdated) { + m_cyclesScene->object_manager->tag_update(m_cyclesScene); + m_objectsUpdated = false; + } + if (m_lightsUpdated) { + m_cyclesScene->light_manager->tag_update(m_cyclesScene); + m_lightsUpdated = false; + } + + if (a_forceUpdate) { + m_cyclesScene->curve_system_manager->tag_update(m_cyclesScene); + m_cyclesScene->integrator->tag_update(m_cyclesScene); + m_cyclesScene->background->tag_update(m_cyclesScene); + m_cyclesScene->film->tag_update(m_cyclesScene); + } + + + //m_cyclesScene->shaders->tag_update( m_cyclesScene ); + m_cyclesSession->reset(m_bufferParams, m_cyclesSession->params.samples); + m_cyclesScene->mutex.unlock(); +} + +void +HdCyclesRenderParam::CyclesReset(int w, int h) +{ + m_width = w; + m_height = h; + m_bufferParams.width = w; + m_bufferParams.height = h; + m_bufferParams.full_width = w; + m_bufferParams.full_height = h; + m_cyclesScene->camera->width = w; + m_cyclesScene->camera->height = h; + m_cyclesScene->camera->compute_auto_viewplane(); + m_cyclesScene->camera->need_update = true; + m_cyclesScene->camera->need_device_update = true; + m_cyclesSession->reset(m_bufferParams, m_cyclesSession->params.samples); +} + +void +HdCyclesRenderParam::DirectReset() +{ + m_cyclesSession->reset(m_bufferParams, m_cyclesSession->params.samples); +} + +void +HdCyclesRenderParam::AddLight(ccl::Light* a_light) +{ + if (!m_cyclesScene) { + TF_WARN("Couldn't add light to scene. Scene is null."); + return; + } + + m_lightsUpdated = true; + + m_cyclesScene->mutex.lock(); + m_cyclesScene->lights.push_back(a_light); + m_cyclesScene->mutex.unlock(); + + if (a_light->type == ccl::LIGHT_BACKGROUND) { + m_hasDomeLight = true; + } +} + +void +HdCyclesRenderParam::AddObject(ccl::Object* a_object) +{ + if (!m_cyclesScene) { + TF_WARN("Couldn't add object to scene. Scene is null."); + return; + } + + m_objectsUpdated = true; + + m_cyclesScene->mutex.lock(); + m_cyclesScene->objects.push_back(a_object); + m_cyclesScene->mutex.unlock(); + + Interrupt(); +} + +void +HdCyclesRenderParam::AddGeometry(ccl::Geometry* a_geometry) +{ + if (!m_cyclesScene) { + TF_WARN("Couldn't add geometry to scene. Scene is null."); + return; + } + + m_geometryUpdated = true; + + m_cyclesScene->mutex.lock(); + m_cyclesScene->geometry.push_back(a_geometry); + m_cyclesScene->mutex.unlock(); + + Interrupt(); +} + +void +HdCyclesRenderParam::AddMesh(ccl::Mesh* a_mesh) +{ + if (!m_cyclesScene) { + TF_WARN("Couldn't add geometry to scene. Scene is null."); + return; + } + + m_meshUpdated = true; + + m_cyclesScene->mutex.lock(); + m_cyclesScene->geometry.push_back(a_mesh); + m_cyclesScene->mutex.unlock(); + + Interrupt(); +} + +void +HdCyclesRenderParam::AddCurve(ccl::Geometry* a_curve) +{ + if (!m_cyclesScene) { + TF_WARN("Couldn't add geometry to scene. Scene is null."); + return; + } + + m_curveUpdated = true; + + m_cyclesScene->mutex.lock(); + m_cyclesScene->geometry.push_back(a_curve); + m_cyclesScene->mutex.unlock(); + + Interrupt(); +} + +void +HdCyclesRenderParam::AddShader(ccl::Shader* a_shader) +{ + if (!m_cyclesScene) { + TF_WARN("Couldn't add geometry to scene. Scene is null."); + return; + } + + m_shadersUpdated = true; + + m_cyclesScene->mutex.lock(); + m_cyclesScene->shaders.push_back(a_shader); + m_cyclesScene->mutex.unlock(); +} + +void +HdCyclesRenderParam::RemoveObject(ccl::Object* a_object) +{ + for (ccl::vector::iterator it = m_cyclesScene->objects.begin(); + it != m_cyclesScene->objects.end();) { + if (a_object == *it) { + m_cyclesScene->mutex.lock(); + it = m_cyclesScene->objects.erase(it); + + m_objectsUpdated = true; + m_cyclesScene->mutex.unlock(); + break; + } else { + ++it; + } + } + + if (m_objectsUpdated) + Interrupt(); +} + +void +HdCyclesRenderParam::RemoveLight(ccl::Light* a_light) +{ + if (a_light->type == ccl::LIGHT_BACKGROUND) { + m_hasDomeLight = false; + } + + for (ccl::vector::iterator it = m_cyclesScene->lights.begin(); + it != m_cyclesScene->lights.end();) { + if (a_light == *it) { + m_cyclesScene->mutex.lock(); + + it = m_cyclesScene->lights.erase(it); + + m_lightsUpdated = true; + + m_cyclesScene->mutex.unlock(); + + break; + } else { + ++it; + } + } + + if (m_lightsUpdated) + Interrupt(); +} + +void +HdCyclesRenderParam::RemoveMesh(ccl::Mesh* a_mesh) +{ + for (ccl::vector::iterator it + = m_cyclesScene->geometry.begin(); + it != m_cyclesScene->geometry.end();) { + if (a_mesh == *it) { + m_cyclesScene->mutex.lock(); + + it = m_cyclesScene->geometry.erase(it); + + m_meshUpdated = true; + + m_cyclesScene->mutex.unlock(); + + break; + } else { + ++it; + } + } + + if (m_geometryUpdated) + Interrupt(); +} + +void +HdCyclesRenderParam::RemoveCurve(ccl::Hair* a_hair) +{ + for (ccl::vector::iterator it + = m_cyclesScene->geometry.begin(); + it != m_cyclesScene->geometry.end();) { + if (a_hair == *it) { + m_cyclesScene->mutex.lock(); + + it = m_cyclesScene->geometry.erase(it); + + m_curveUpdated = true; + + m_cyclesScene->mutex.unlock(); + + break; + } else { + ++it; + } + } + + if (m_geometryUpdated) + Interrupt(); +} + +void +HdCyclesRenderParam::RemoveShader(ccl::Shader* a_shader) +{ + for (ccl::vector::iterator it = m_cyclesScene->shaders.begin(); + it != m_cyclesScene->shaders.end();) { + if (a_shader == *it) { + m_cyclesScene->mutex.lock(); + + it = m_cyclesScene->shaders.erase(it); + + m_shadersUpdated = true; + + m_cyclesScene->mutex.unlock(); + + break; + } else { + ++it; + } + } + + if (m_shadersUpdated) + Interrupt(); +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/plugin/hdCycles/renderParam.h b/plugin/hdCycles/renderParam.h new file mode 100644 index 00000000..c270f683 --- /dev/null +++ b/plugin/hdCycles/renderParam.h @@ -0,0 +1,531 @@ +// Copyright 2020 Tangent Animation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef HD_CYCLES_RENDER_PARAM_H +#define HD_CYCLES_RENDER_PARAM_H + +#include "api.h" + +#include +#include +#include +#include +#include + +#include +#include + +namespace ccl { +class Session; +class Scene; +class Mesh; +class RenderTile; +class Shader; +} // namespace ccl + +PXR_NAMESPACE_OPEN_SCOPE + +/** + * @brief The proposed main interface to the cycles session and scene + * Very much under construction. + * + */ +class HdCyclesRenderParam : public HdRenderParam { +public: + /** + * @brief Construct a new HdCycles Render Param object + * + */ + HdCyclesRenderParam(); + + /** + * @brief Destroy the HdCycles Render Param object + * + */ + ~HdCyclesRenderParam() = default; + + /** + * @brief Start cycles render session + * + */ + HDCYCLES_API + void StartRender(); + + /** + * @brief Stop the current render and close cycles instance + * + * @return HDCYCLES_API StopRender + */ + HDCYCLES_API + void StopRender(); + + /** + * @brief Completely restart a cycles render + * + */ + HDCYCLES_API + void RestartRender(); + + /** + * @brief Restarts the current cycles render + * + */ + HDCYCLES_API + void Interrupt(bool a_forceUpdate = false); + + /** + * @brief Initialize cycles renderer + * + */ + bool Initialize(); + + /** + * @brief Pause cycles render session + * + */ + void PauseRender(); + + /** + * @brief Resume cycles render session + * + */ + void ResumeRender(); + + /** + * @return Progress completed of render + */ + HDCYCLES_API + float GetProgress(); + + /** + * @brief Start a cycles render + * TODO: Refactor this + * + */ + void CyclesStart(); + +protected: + /** + * @brief Main initialization logic of cycles render + * + * @return Returns true if successful + */ + bool _CyclesInitialize(); + + /** + * @brief Main exit logic of cycles render + * + */ + void _CyclesExit(); + + /** + * @brief Debug callback to print the status of cycles + * + */ + void _SessionPrintStatus(); + +public: + /** + * @brief Cycles general reset + * + * @param a_forceUpdate Should force update of cycles managers + */ + void CyclesReset(bool a_forceUpdate = false); + + /** + * @brief Cycles reset based on width and height + * TODO: Refactor these + * + * @param w Width of new render + * @param h Height of new render + */ + void CyclesReset(int w, int h); + + /** + * @brief Slightly hacky workaround to directly reset the session + * + */ + void DirectReset(); + + /** + * @brief Helper to set the background shader + * + * @param a_shader Shader to use + * @param a_emissive Should the default bg be emissive + */ + void SetBackgroundShader(ccl::Shader* a_shader = nullptr, + bool a_emissive = true); + + /* ======= Cycles Settings ======= */ + + /** + * @brief Get should cycles be run in experimental mode + * + * @return Returns true if cycles will run in experimental mode + */ + const bool& GetUseExperimental(); + + /** + * @brief Set should cycles be run in experimental mode + * + * @param a_value Should use experimental mode + */ + void SetUseExperimental(const bool& a_value); + + /** + * @brief Get the maximum samples to be used in a render + * + * @return const int& Maximum number of samples + */ + const int& GetMaxSamples(); + + /** + * @brief Set the maximum samples to be used in a render + * + * @param a_value Maximum number of samples + */ + void SetMaxSamples(const int& a_value); + + /** + * @brief Get the number of threads to be used when rendering + * + * @return const int& Number of threads + */ + const int& GetNumThreads(); + + /** + * @brief Set the number of threads to be used when rendering + * 0 is automatic + * + * @param a_value Number of threads + */ + void SetNumThreads(const int& a_value); + + /** + * @brief Get the individual Pixel Size of cycles render + * + * @return const int& Pixel Size + */ + const int& GetPixelSize(); + + /** + * @brief Set the individual Pixel Size of cycles render + * + * @param a_value Pixel Size + */ + void SetPixelSize(const int& a_value); + + /** + * @brief Get the Tile Size of cycles tiled render + * + * @return const pxr::GfVec2i& Tile width and height + */ + const pxr::GfVec2i GetTileSize(); + + /** + * @brief Set the Tile Size of cycles tiled render + * + * @param a_value Tile width and height + */ + void SetTileSize(const pxr::GfVec2i& a_value); + + /** + * @brief Set the Tile Size of cycles tiled render + * + * @param a_x Tile width + * @param a_y Tile hieght + */ + void SetTileSize(int a_x, int a_y); + + /** + * @brief Get the Start Resolution of cycles render + * + * @return const int& Start Resolution + */ + const int& GetStartResolution(); + + /** + * @brief Set the Start Resolution of cycles render + * + * @param a_value Start Resolution + */ + void SetStartResolution(const int& a_value); + + /** + * @brief Get the Exposure of final render + * + * @return const float& Exposure + */ + const float& GetExposure(); + + /** + * @brief Set the Exposure of final render + * + * @param a_exposure Exposure + */ + void SetExposure(float a_exposure); + + /** + * @brief Get the current Device Type + * + * @return const ccl::DeviceType& Device Type + */ + const ccl::DeviceType& GetDeviceType(); + + /** + * @brief Get the Device Type Name as string + * + * @return const std::string& Device Type Name + */ + const std::string& GetDeviceTypeName(); + + /** + * @brief Set Cycles render device type + * + * @param a_deviceType Device type as type + * @param params Specific params + * @return Returns true if could set the device type + */ + bool SetDeviceType(ccl::DeviceType a_deviceType, + ccl::SessionParams& params); + + /** + * @brief Set Cycles render device type + * + * @param a_deviceType Device type as string + * @param params Specific params + * @return Returns true if could set the device type + */ + bool SetDeviceType(const std::string& a_deviceType, + ccl::SessionParams& params); + + /** + * @brief Set Cycles render device type + * + * @param a_deviceType Device Type as string + * @return Returns true if could set the device type + */ + bool SetDeviceType(const std::string& a_deviceType); + + /** + * @brief Get the camera's motion position + * TODO: Move this to HdCyclesCamera + * + * @return const ccl::Camera::MotionPosition& Motion Position + */ + const ccl::Camera::MotionPosition& GetShutterMotionPosition(); + + /** + * @brief Set the camera's motion position + * TODO: Move this to HdCyclesCamera + * + * @param a_value Motion Position + */ + void SetShutterMotionPosition(const int& a_value); + + /** + * @brief Set the camera's motion position + * TODO: Move this to HdCyclesCamera + * + * @param a_value Motion Position + */ + void SetShutterMotionPosition(const ccl::Camera::MotionPosition& a_value); + + /* ====== HdCycles Settings ====== */ + + /** + * @brief Set should use motion blur + * + * @param a_value Should use motion blur + */ + void SetUseMotionBlur(const bool& a_value) { m_useMotionBlur = a_value; } + + /** + * @brief Get if should use motion blur + * + * @return Return true if motion blur should be used + */ + const bool& GetUseMotionBlur() { return m_useMotionBlur; } + + /** + * @brief Set the Motion Steps + * + * @param a_value Motion Steps + */ + void SetMotionSteps(const int& a_value) { m_motionSteps = a_value; } + + /** + * @brief Get Motion Steps + * + * @return const int& Motion Steps + */ + const int& GetMotionSteps() { return m_motionSteps; } + + /** + * @brief Get the Width of render + * + * @return Width in pixels + */ + const float& GetWidth() { return m_width; } + + /** + * @brief Get the Height of render + * + * @return Height in pixels + */ + const float& GetHeight() { return m_height; } + + /** + * @brief Add light to scene + * + * @param a_light Light to add + */ + void AddLight(ccl::Light* a_light); + + /** + * @brief Add geometry to scene + * + * @param a_geometry Geometry to add + */ + void AddGeometry(ccl::Geometry* a_geometry); + + /** + * @brief Add mesh to scene + * + * @param a_geometry Mesh to add + */ + void AddMesh(ccl::Mesh* a_mesh); + + /** + * @brief Add geometry to scene + * + * @param a_geometry Geometry to add + */ + void AddCurve(ccl::Geometry* a_curve); + + /** + * @brief Add shader to scene + * + * @param a_shader Shader to add + */ + void AddShader(ccl::Shader* a_shader); + + /** + * @brief Add object to scene + * + * @param a_object Object to add + */ + void AddObject(ccl::Object* a_object); + + /** + * @brief Remove hair geometry from cycles scene + * + * @param a_hair Hair to remove + */ + void RemoveCurve(ccl::Hair* a_hair); + + /** + * @brief Remove light from cycles scene + * + * @param a_light Light to remove + */ + void RemoveLight(ccl::Light* a_light); + + /** + * @brief Remove shader from cycles scene + * + * @param a_shader Shader to remove + */ + void RemoveShader(ccl::Shader* a_shader); + + /** + * @brief Remove mesh geometry from cycles scene + * + * @param a_mesh Mesh to remove + */ + void RemoveMesh(ccl::Mesh* a_mesh); + + /** + * @brief Remove object from cycles scene + * + * @param a_object Object to remove + */ + void RemoveObject(ccl::Object* a_object); + +private: + /** + * @brief Initialize member values based on config + * TODO: Refactor this + * + */ + void _InitializeDefaults(); + + bool _SetDevice(const ccl::DeviceType& a_deviceType, + ccl::SessionParams& params); + + ccl::BufferParams m_bufferParams; + + // These values are not explicitly defined in cycles api + // They are stored here to allow for easy retrieval + bool m_useMotionBlur; + int m_motionSteps; + + ccl::DeviceType m_deviceType; + std::string m_deviceName; + + int m_width; + int m_height; + + bool m_objectsUpdated; + bool m_geometryUpdated; + bool m_curveUpdated; + bool m_meshUpdated; + bool m_lightsUpdated; + bool m_shadersUpdated; + + bool m_shouldUpdate; + + bool m_hasDomeLight; + +public: + void CommitResources(); + /** + * @brief Get the active Cycles Session + * + * @return ccl::Session* Cycles Session + */ + ccl::Session* GetCyclesSession() { return m_cyclesSession; } + + /** + * @brief Get theactive Cycles Scene + * + * @return ccl::Scene* Cycles Scene + */ + ccl::Scene* GetCyclesScene() { return m_cyclesScene; } + + /** + * @brief Replacement default surface shader for vertex color meshes + * TODO: Refactor this somewhere else + * + */ + ccl::Shader* default_vcol_surface; + +private: + ccl::Session* m_cyclesSession; + ccl::Scene* m_cyclesScene; +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // HD_CYCLES_RENDER_PARAM_H diff --git a/plugin/hdCycles/renderPass.cpp b/plugin/hdCycles/renderPass.cpp new file mode 100644 index 00000000..67ecbc70 --- /dev/null +++ b/plugin/hdCycles/renderPass.cpp @@ -0,0 +1,147 @@ +// Copyright 2020 Tangent Animation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "renderPass.h" + +#include "camera.h" +#include "renderBuffer.h" +#include "renderParam.h" +#include "utils.h" + +#include +#include +#include +#include + +#include +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +// clang-format off +TF_DEFINE_PRIVATE_TOKENS(_tokens, + (color) + (depth) +); +// clang-format on + +HdCyclesRenderPass::HdCyclesRenderPass(HdCyclesRenderDelegate* delegate, + HdRenderIndex* index, + HdRprimCollection const& collection) + : HdRenderPass(index, collection) + , m_delegate(delegate) +{ +} + +HdCyclesRenderPass::~HdCyclesRenderPass() { m_colorBuffer.clear(); } + +void +HdCyclesRenderPass::_Execute(HdRenderPassStateSharedPtr const& renderPassState, + TfTokenVector const& renderTags) +{ + auto* renderParam = reinterpret_cast( + m_delegate->GetRenderParam()); + + const auto vp = renderPassState->GetViewport(); + + GfMatrix4d projMtx = renderPassState->GetProjectionMatrix(); + GfMatrix4d viewMtx = renderPassState->GetWorldToViewMatrix(); + + // XXX: Need to cast away constness to process updated camera params since + // the Hydra camera doesn't update the Cycles camera directly. + HdCyclesCamera* hdCam = const_cast( + dynamic_cast(renderPassState->GetCamera())); + + if (projMtx != m_projMtx || viewMtx != m_viewMtx) { + m_projMtx = projMtx; + m_viewMtx = viewMtx; + + const float fov_rad = atan(1.0f / m_projMtx[1][1]) * 2.0f; + const float fov_deg = fov_rad / M_PI * 180.0f; + hdCam->SetFOV(fov_rad); + + //hdCam->SetTransform(m_projMtx); + + bool shouldUpdate = hdCam->ApplyCameraSettings( + renderParam->GetCyclesSession()->scene->camera); + + if (shouldUpdate) + renderParam->Interrupt(); + else + renderParam->DirectReset(); + } + + ccl::DisplayBuffer* display = renderParam->GetCyclesSession()->display; + + int w = display->draw_width; + int h = display->draw_height; + + const auto width = static_cast(vp[2]); + const auto height = static_cast(vp[3]); + const auto numPixels = static_cast(width * height); + + unsigned int pixelSize = (display->half_float) ? sizeof(CyRGBA16) + : sizeof(CyRGBA8); + + HdFormat colorFormat = display->half_float ? HdFormatFloat16Vec4 + : HdFormatUNorm8Vec4; + + unsigned char* hpixels + = (display->half_float) + ? (unsigned char*)display->rgba_half.host_pointer + : (unsigned char*)display->rgba_byte.host_pointer; + + if (width != m_width || height != m_height) { + renderParam->Interrupt(); + const auto oldNumPixels = static_cast(m_width * m_height); + m_width = width; + m_height = height; + renderParam->CyclesReset(m_width, m_height); + + if (numPixels != oldNumPixels) { + m_colorBuffer.resize(numPixels * pixelSize); + memset(m_colorBuffer.data(), 0, numPixels * pixelSize); + } + } + + m_isConverged = renderParam->GetProgress() >= 1.0f; + + HdRenderPassAovBindingVector aovBindings = renderPassState->GetAovBindings(); + + if (w != 0) { + m_colorBuffer.resize(w * h * pixelSize); + memcpy(m_colorBuffer.data(), hpixels, w * h * pixelSize); + } + + // Blit + if (!aovBindings.empty()) { + // Blit from the framebuffer to currently selected aovs... + for (auto& aov : aovBindings) { + if (!TF_VERIFY(aov.renderBuffer != nullptr)) { + continue; + } + + auto* rb = static_cast(aov.renderBuffer); + rb->SetConverged(m_isConverged); + + if (aov.aovName == HdAovTokens->color) { + rb->Blit(colorFormat, w, h, 0, w, + reinterpret_cast(m_colorBuffer.data())); + } + } + } +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/plugin/hdCycles/renderPass.h b/plugin/hdCycles/renderPass.h new file mode 100644 index 00000000..9c3314cd --- /dev/null +++ b/plugin/hdCycles/renderPass.h @@ -0,0 +1,99 @@ +// Copyright 2020 Tangent Animation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef HD_CYCLES_RENDER_PASS_H +#define HD_CYCLES_RENDER_PASS_H + +#include "api.h" + +#include "renderDelegate.h" + +#include + +#include +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +typedef struct { + unsigned char red; + unsigned char green; + unsigned char blue; + unsigned char alpha; +} CyRGBA8; + +typedef struct { + ccl::half red; + ccl::half green; + ccl::half blue; + ccl::half alpha; +} CyRGBA16; + +class HdCyclesRenderDelegate; + +/** + * @brief Represents a single render iteration. rendering a view of the scene + * (HdRprimCollection) for a specific viewer (camera/viewport params in + * HdRenderPassState) to the current draw target. + * + */ +class HdCyclesRenderPass final : public HdRenderPass { +public: + /** + * @brief Construct a new HdCycles Render Pass object + * + * @param delegate + * @param index The render index containing scene data to render + * @param collection Initial rprim collection for this render pass + */ + HdCyclesRenderPass(HdCyclesRenderDelegate* delegate, HdRenderIndex* index, + HdRprimCollection const& collection); + + /** + * @brief Destroy the HdCycles Render Pass object + * + */ + virtual ~HdCyclesRenderPass(); + +protected: + /** + * @brief Draw the scene with the bound renderpass state + * + * @param renderPassState Input parameters + * @param renderTags Which render tags should be drawn this pass + */ + void _Execute(HdRenderPassStateSharedPtr const& renderPassState, + TfTokenVector const& renderTags) override; + + bool IsConverged() const override { return m_isConverged; } + +protected: + HdCyclesRenderDelegate* m_delegate; + + GfMatrix4d m_projMtx; + GfMatrix4d m_viewMtx; + + std::vector m_colorBuffer; + +public: + int m_width = 0; + int m_height = 0; + + bool m_isConverged = false; +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // HD_CYCLES_RENDER_PASS_H diff --git a/plugin/hdCycles/rendererPlugin.cpp b/plugin/hdCycles/rendererPlugin.cpp new file mode 100644 index 00000000..e9845009 --- /dev/null +++ b/plugin/hdCycles/rendererPlugin.cpp @@ -0,0 +1,46 @@ +// Copyright 2020 Tangent Animation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "rendererPlugin.h" +#include "renderDelegate.h" + +#include + +PXR_NAMESPACE_OPEN_SCOPE + +// Register the plugin with the renderer plugin system. +TF_REGISTRY_FUNCTION(TfType) +{ + HdRendererPluginRegistry::Define(); +} + +HdRenderDelegate* +HdCyclesRendererPlugin::CreateRenderDelegate() +{ + return new HdCyclesRenderDelegate(); +} + +void +HdCyclesRendererPlugin::DeleteRenderDelegate(HdRenderDelegate* renderDelegate) +{ + delete renderDelegate; +} + +bool +HdCyclesRendererPlugin::IsSupported() const +{ + return true; +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/plugin/hdCycles/rendererPlugin.h b/plugin/hdCycles/rendererPlugin.h new file mode 100644 index 00000000..28f49423 --- /dev/null +++ b/plugin/hdCycles/rendererPlugin.h @@ -0,0 +1,78 @@ +// Copyright 2020 Tangent Animation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef HD_CYCLES_RENDERER_PLUGIN_H +#define HD_CYCLES_RENDERER_PLUGIN_H + +#include "api.h" + +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +/** + * @brief First entry point into HdCycles. + * Allows for the creation and deletion of the core + * render delegate classes. + * + */ +class HdCyclesRendererPlugin final : public HdRendererPlugin { +public: + /** + * @brief Use default constructor + * + */ + HDCYCLES_API HdCyclesRendererPlugin() = default; + + /** + * @brief Use default destructor + * + */ + HDCYCLES_API ~HdCyclesRendererPlugin() override = default; + + /** + * @brief Construct a new render delegate of type HdCyclesRenderDelegate + * + * @return Created Render Delegate + */ + HDCYCLES_API HdRenderDelegate* CreateRenderDelegate() override; + + /** + * @brief Destroy a render delegate created by this class + * + * @param renderDelegate The render delegate to delete + * @return HDCYCLES_API + */ + HDCYCLES_API void + DeleteRenderDelegate(HdRenderDelegate* renderDelegate) override; + + /** + * @brief Checks to see if the plugin is supported on the running system + * + */ + HDCYCLES_API bool IsSupported() const override; + +private: + /** + * @brief This class does not support copying + * + */ + HdCyclesRendererPlugin(const HdCyclesRendererPlugin&) = delete; + HdCyclesRendererPlugin& operator=(const HdCyclesRendererPlugin&) = delete; +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // HD_CYCLES_RENDERER_PLUGIN_H diff --git a/plugin/hdCycles/utils.cpp b/plugin/hdCycles/utils.cpp new file mode 100644 index 00000000..ff4ca548 --- /dev/null +++ b/plugin/hdCycles/utils.cpp @@ -0,0 +1,576 @@ +// Copyright 2020 Tangent Animation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "utils.h" + +#include +#include + +#include +#include +#include +#include + +#ifdef USE_USD_HOUDINI +# include +# define BOOST_LIB_NAME hboost +#else +# include +# define BOOST_LIB_NAME boost +#endif + +PXR_NAMESPACE_OPEN_SCOPE + +/* =========- Texture ========== */ + +bool +HdCyclesPathIsUDIM(const ccl::string& a_filepath) +{ +#ifndef USD_HAS_UDIM_RESOLVE_FIX + // Added precheck to ensure no UDIM is accepted with relative path + BOOST_LIB_NAME::filesystem::path filepath(a_filepath); + if (filepath.is_relative()) + return false; +#endif + return a_filepath.find("") != std::string::npos; +} + +// TODO: Investigate getting these tiles from uv data +// The cycles function ImageTextureNode::cull_tiles does not properly load tiles +// in an interactive session when not provided by Blender. We could assume these +// tiles based on uv primvars, but I have a feeling the material loading happens +// before the mesh syncing. More rnd needs to be done. +void +HdCyclesParseUDIMS(const ccl::string& a_filepath, ccl::vector& a_tiles) +{ + BOOST_LIB_NAME::filesystem::path filepath(a_filepath); + + size_t offset = filepath.stem().string().find(""); + std::string baseFileName = filepath.stem().string().substr(0, offset); + + std::vector files; + + BOOST_LIB_NAME::filesystem::path path(ccl::path_dirname(a_filepath)); + for (BOOST_LIB_NAME::filesystem::directory_iterator it(path); + it != BOOST_LIB_NAME::filesystem::directory_iterator(); ++it) { + if (BOOST_LIB_NAME::filesystem::is_regular_file(it->status()) + || BOOST_LIB_NAME::filesystem::is_symlink(it->status())) { + std::string foundFile = BOOST_LIB_NAME::filesystem::basename( + it->path().filename()); + + if (baseFileName == (foundFile.substr(0, offset))) { + files.push_back(foundFile); + } + } + } + + a_tiles.clear(); + + for (std::string file : files) { + a_tiles.push_back(atoi(file.substr(offset, offset + 3).c_str())); + } +} + +/* ========== Material ========== */ + +ccl::Shader* +HdCyclesCreateDefaultShader() +{ + ccl::Shader* shader = new ccl::Shader(); + + shader->graph = new ccl::ShaderGraph(); + + ccl::VertexColorNode* vc = new ccl::VertexColorNode(); + vc->layer_name = ccl::ustring("displayColor"); + + ccl::PrincipledBsdfNode* bsdf = new ccl::PrincipledBsdfNode(); + + shader->graph->add(bsdf); + shader->graph->add(vc); + + ccl::ShaderNode* out = shader->graph->output(); + shader->graph->connect(vc->output("Color"), bsdf->input("Base Color")); + shader->graph->connect(bsdf->output("BSDF"), out->input("Surface")); + + return shader; +} + +/* ========= Conversion ========= */ + +HdTimeSampleArray +HdCyclesSetTransform(ccl::Object* object, HdSceneDelegate* delegate, + const SdfPath& id, bool use_motion) +{ + if (!object) + return {}; + + HdTimeSampleArray xf {}; + + delegate->SampleTransform(id, &xf); + + if (xf.count == 0) { + object->tfm = ccl::transform_identity(); + } else { + // Set transform + object->tfm = mat4d_to_transform(xf.values.data()[0]); + + if (use_motion) { + // Set motion + object->motion.clear(); + object->motion.resize(xf.count); + for (int i = 0; i < xf.count; i++) { + object->motion[i] = mat4d_to_transform(xf.values.data()[i]); + } + } + } + + return xf; +} + +ccl::Transform +HdCyclesExtractTransform(HdSceneDelegate* delegate, const SdfPath& id) +{ + constexpr size_t maxSamples = 2; + HdTimeSampleArray xf {}; + + delegate->SampleTransform(id, &xf); + + return mat4d_to_transform(xf.values[0]); +} + +ccl::Transform +mat4d_to_transform(const GfMatrix4d& mat) +{ + ccl::Transform outTransform = ccl::transform_identity(); + + outTransform.x.x = static_cast(mat[0][0]); + outTransform.x.y = static_cast(mat[1][0]); + outTransform.x.z = static_cast(mat[2][0]); + outTransform.x.w = static_cast(mat[3][0]); + + outTransform.y.x = static_cast(mat[0][1]); + outTransform.y.y = static_cast(mat[1][1]); + outTransform.y.z = static_cast(mat[2][1]); + outTransform.y.w = static_cast(mat[3][1]); + + outTransform.z.x = static_cast(mat[0][2]); + outTransform.z.y = static_cast(mat[1][2]); + outTransform.z.z = static_cast(mat[2][2]); + outTransform.z.w = static_cast(mat[3][2]); + + return outTransform; +} + +ccl::Transform +mat4f_to_transform(const GfMatrix4f& mat) +{ + ccl::Transform outTransform = ccl::transform_identity(); + + outTransform.x.x = static_cast(mat[0][0]); + outTransform.x.y = static_cast(mat[1][0]); + outTransform.x.z = static_cast(mat[2][0]); + outTransform.x.w = static_cast(mat[3][0]); + + outTransform.y.x = static_cast(mat[0][1]); + outTransform.y.y = static_cast(mat[1][1]); + outTransform.y.z = static_cast(mat[2][1]); + outTransform.y.w = static_cast(mat[3][1]); + + outTransform.z.x = static_cast(mat[0][2]); + outTransform.z.y = static_cast(mat[1][2]); + outTransform.z.z = static_cast(mat[2][2]); + outTransform.z.w = static_cast(mat[3][2]); + + return outTransform; +} + +ccl::float2 +vec2f_to_float2(const GfVec2f& a_vec) +{ + return ccl::make_float2(a_vec[0], a_vec[1]); +} + +ccl::float3 +vec3f_to_float3(const GfVec3f& a_vec) +{ + return ccl::make_float3(a_vec[0], a_vec[1], a_vec[2]); +} + +ccl::float3 +vec4f_to_float3(const GfVec4f& a_vec) +{ + return ccl::make_float3(a_vec[0], a_vec[1], a_vec[2]); +} + +ccl::float4 +vec3f_to_float4(const GfVec3f& a_vec, float a_alpha) +{ + return ccl::make_float4(a_vec[0], a_vec[1], a_vec[2], a_alpha); +} + +ccl::float4 +vec4f_to_float4(const GfVec4f& a_vec) +{ + return ccl::make_float4(a_vec[0], a_vec[1], a_vec[2], a_vec[3]); +} + +/* ========= Primvars ========= */ + +const std::array interpolations { + HdInterpolationConstant, HdInterpolationUniform, + HdInterpolationVarying, HdInterpolationVertex, + HdInterpolationFaceVarying, HdInterpolationInstance, +}; + +inline void +_HdCyclesInsertPrimvar(HdCyclesPrimvarMap& primvars, const TfToken& name, + const TfToken& role, HdInterpolation interpolation, + const VtValue& value) +{ + auto it = primvars.find(name); + if (it == primvars.end()) { + primvars.insert({ name, { value, role, interpolation } }); + } else { + it->second.value = value; + it->second.role = role; + it->second.interpolation = interpolation; + it->second.dirtied = true; + } +} + +// Get Computed primvars +bool +HdCyclesGetComputedPrimvars(HdSceneDelegate* a_delegate, const SdfPath& a_id, + HdDirtyBits a_dirtyBits, + HdCyclesPrimvarMap& a_primvars) +{ + // First we are querying which primvars need to be computed, and storing them in a list to rely + // on the batched computation function in HdExtComputationUtils. + HdExtComputationPrimvarDescriptorVector dirtyPrimvars; + for (HdInterpolation interpolation : interpolations) { + auto computedPrimvars + = a_delegate->GetExtComputationPrimvarDescriptors(a_id, + interpolation); + for (const auto& primvar : computedPrimvars) { + if (HdChangeTracker::IsPrimvarDirty(a_dirtyBits, a_id, + primvar.name)) { + dirtyPrimvars.emplace_back(primvar); + } + } + } + + // Early exit. + if (dirtyPrimvars.empty()) { + return false; + } + + auto changed = false; + auto valueStore + = HdExtComputationUtils::GetComputedPrimvarValues(dirtyPrimvars, + a_delegate); + for (const auto& primvar : dirtyPrimvars) { + const auto itComputed = valueStore.find(primvar.name); + if (itComputed == valueStore.end()) { + continue; + } + changed = true; + _HdCyclesInsertPrimvar(a_primvars, primvar.name, primvar.role, + primvar.interpolation, itComputed->second); + } + + return changed; +} + +// Get Non-computed primvars +bool +HdCyclesGetPrimvars(HdSceneDelegate* a_delegate, const SdfPath& a_id, + HdDirtyBits a_dirtyBits, bool a_multiplePositionKeys, + HdCyclesPrimvarMap& a_primvars) +{ + for (auto interpolation : interpolations) { + const auto primvarDescs + = a_delegate->GetPrimvarDescriptors(a_id, interpolation); + for (const auto& primvarDesc : primvarDescs) { + if (primvarDesc.name == HdTokens->points) { + continue; + } + // The number of motion keys has to be matched between points and normals, so + _HdCyclesInsertPrimvar(a_primvars, primvarDesc.name, + primvarDesc.role, primvarDesc.interpolation, + (a_multiplePositionKeys + && primvarDesc.name == HdTokens->normals) + ? VtValue {} + : a_delegate->Get(a_id, + primvarDesc.name)); + } + } + + return true; +} + + +void +HdCyclesPopulatePrimvarDescsPerInterpolation( + HdSceneDelegate* a_sceneDelegate, SdfPath const& a_id, + HdCyclesPDPIMap* a_primvarDescsPerInterpolation) +{ + if (!a_primvarDescsPerInterpolation->empty()) { + return; + } + + auto interpolations = { + HdInterpolationConstant, HdInterpolationUniform, + HdInterpolationVarying, HdInterpolationVertex, + HdInterpolationFaceVarying, HdInterpolationInstance, + }; + for (auto& interpolation : interpolations) { + a_primvarDescsPerInterpolation->emplace( + interpolation, + a_sceneDelegate->GetPrimvarDescriptors(a_id, interpolation)); + } +} + +bool +HdCyclesIsPrimvarExists(TfToken const& a_name, + HdCyclesPDPIMap const& a_primvarDescsPerInterpolation, + HdInterpolation* a_interpolation) +{ + for (auto& entry : a_primvarDescsPerInterpolation) { + for (auto& pv : entry.second) { + if (pv.name == a_name) { + if (a_interpolation) { + *a_interpolation = entry.first; + } + return true; + } + } + } + return false; +} + +/* ========= MikkTSpace ========= */ + +struct MikkUserData { + MikkUserData(const char* layer_name, ccl::Mesh* mesh, ccl::float3* tangent, + float* tangent_sign) + : mesh(mesh) + , texface(NULL) + , tangent(tangent) + , tangent_sign(tangent_sign) + { + const ccl::AttributeSet& attributes = (mesh->subd_faces.size()) + ? mesh->subd_attributes + : mesh->attributes; + + ccl::Attribute* attr_vN = attributes.find(ccl::ATTR_STD_VERTEX_NORMAL); + if (!attr_vN) { + mesh->add_face_normals(); + mesh->add_vertex_normals(); + attr_vN = attributes.find(ccl::ATTR_STD_VERTEX_NORMAL); + } + vertex_normal = attr_vN->data_float3(); + + ccl::Attribute* attr_uv = attributes.find(ccl::ustring(layer_name)); + if (attr_uv != NULL) { + texface = attr_uv->data_float2(); + } + } + + ccl::Mesh* mesh; + int num_faces; + + ccl::float3* vertex_normal; + ccl::float2* texface; + + ccl::float3* tangent; + float* tangent_sign; +}; + +int +mikk_get_num_faces(const SMikkTSpaceContext* context) +{ + const MikkUserData* userdata = (const MikkUserData*)context->m_pUserData; + if (userdata->mesh->subd_faces.size()) { + return userdata->mesh->subd_faces.size(); + } else { + return userdata->mesh->num_triangles(); + } +} + +int +mikk_get_num_verts_of_face(const SMikkTSpaceContext* context, + const int face_num) +{ + const MikkUserData* userdata = (const MikkUserData*)context->m_pUserData; + if (userdata->mesh->subd_faces.size()) { + const ccl::Mesh* mesh = userdata->mesh; + return mesh->subd_faces[face_num].num_corners; + } else { + return 3; + } +} + +int +mikk_vertex_index(const ccl::Mesh* mesh, const int face_num, const int vert_num) +{ + if (mesh->subd_faces.size()) { + const ccl::Mesh::SubdFace& face = mesh->subd_faces[face_num]; + return mesh->subd_face_corners[face.start_corner + vert_num]; + } else { + return mesh->triangles[face_num * 3 + vert_num]; + } +} + +int +mikk_corner_index(const ccl::Mesh* mesh, const int face_num, const int vert_num) +{ + if (mesh->subd_faces.size()) { + const ccl::Mesh::SubdFace& face = mesh->subd_faces[face_num]; + return face.start_corner + vert_num; + } else { + return face_num * 3 + vert_num; + } +} + +void +mikk_get_position(const SMikkTSpaceContext* context, float P[3], + const int face_num, const int vert_num) +{ + const MikkUserData* userdata = (const MikkUserData*)context->m_pUserData; + const ccl::Mesh* mesh = userdata->mesh; + const int vertex_index = mikk_vertex_index(mesh, face_num, vert_num); + const ccl::float3 vP = mesh->verts[vertex_index]; + P[0] = vP.x; + P[1] = vP.y; + P[2] = vP.z; +} + +void +mikk_get_texture_coordinate(const SMikkTSpaceContext* context, float uv[2], + const int face_num, const int vert_num) +{ + const MikkUserData* userdata = (const MikkUserData*)context->m_pUserData; + const ccl::Mesh* mesh = userdata->mesh; + if (userdata->texface != NULL) { + const int corner_index = mikk_corner_index(mesh, face_num, vert_num); + ccl::float2 tfuv = userdata->texface[corner_index]; + uv[0] = tfuv.x; + uv[1] = tfuv.y; + } else { + uv[0] = 0.0f; + uv[1] = 0.0f; + } +} + +void +mikk_get_normal(const SMikkTSpaceContext* context, float N[3], + const int face_num, const int vert_num) +{ + const MikkUserData* userdata = (const MikkUserData*)context->m_pUserData; + const ccl::Mesh* mesh = userdata->mesh; + ccl::float3 vN; + if (mesh->subd_faces.size()) { + const ccl::Mesh::SubdFace& face = mesh->subd_faces[face_num]; + if (face.smooth) { + const int vertex_index = mikk_vertex_index(mesh, face_num, + vert_num); + vN = userdata->vertex_normal[vertex_index]; + } else { + vN = face.normal(mesh); + } + } else { + if (mesh->smooth[face_num]) { + const int vertex_index = mikk_vertex_index(mesh, face_num, + vert_num); + vN = userdata->vertex_normal[vertex_index]; + } else { + const ccl::Mesh::Triangle tri = mesh->get_triangle(face_num); + vN = tri.compute_normal(&mesh->verts[0]); + } + } + N[0] = vN.x; + N[1] = vN.y; + N[2] = vN.z; +} + +void +mikk_set_tangent_space(const SMikkTSpaceContext* context, const float T[], + const float sign, const int face_num, const int vert_num) +{ + MikkUserData* userdata = (MikkUserData*)context->m_pUserData; + const ccl::Mesh* mesh = userdata->mesh; + const int corner_index = mikk_corner_index(mesh, face_num, vert_num); + userdata->tangent[corner_index] = ccl::make_float3(T[0], T[1], T[2]); + if (userdata->tangent_sign != NULL) { + userdata->tangent_sign[corner_index] = sign; + } +} + +void +mikk_compute_tangents(const char* layer_name, ccl::Mesh* mesh, bool need_sign, + bool active_render) +{ + /* Create tangent attributes. */ + ccl::AttributeSet& attributes = (mesh->subd_faces.size()) + ? mesh->subd_attributes + : mesh->attributes; + ccl::Attribute* attr; + ccl::ustring name; + + name = ccl::ustring((std::string(layer_name) + ".tangent").c_str()); + + if (active_render) { + attr = attributes.add(ccl::ATTR_STD_UV_TANGENT, name); + } else { + attr = attributes.add(name, ccl::TypeDesc::TypeVector, + ccl::ATTR_ELEMENT_CORNER); + } + ccl::float3* tangent = attr->data_float3(); + /* Create bitangent sign attribute. */ + float* tangent_sign = NULL; + if (need_sign) { + ccl::Attribute* attr_sign; + ccl::ustring name_sign = ccl::ustring( + (std::string(layer_name) + ".tangent_sign").c_str()); + + if (active_render) { + attr_sign = attributes.add(ccl::ATTR_STD_UV_TANGENT_SIGN, + name_sign); + } else { + attr_sign = attributes.add(name_sign, ccl::TypeDesc::TypeFloat, + ccl::ATTR_ELEMENT_CORNER); + } + tangent_sign = attr_sign->data_float(); + } + /* Setup userdata. */ + MikkUserData userdata(layer_name, mesh, tangent, tangent_sign); + /* Setup interface. */ + SMikkTSpaceInterface sm_interface; + memset(&sm_interface, 0, sizeof(sm_interface)); + sm_interface.m_getNumFaces = mikk_get_num_faces; + sm_interface.m_getNumVerticesOfFace = mikk_get_num_verts_of_face; + sm_interface.m_getPosition = mikk_get_position; + sm_interface.m_getTexCoord = mikk_get_texture_coordinate; + sm_interface.m_getNormal = mikk_get_normal; + sm_interface.m_setTSpaceBasic = mikk_set_tangent_space; + /* Setup context. */ + SMikkTSpaceContext context; + memset(&context, 0, sizeof(context)); + context.m_pUserData = &userdata; + context.m_pInterface = &sm_interface; + /* Compute tangents. */ + genTangSpaceDefault(&context); +} + +PXR_NAMESPACE_CLOSE_SCOPE \ No newline at end of file diff --git a/plugin/hdCycles/utils.h b/plugin/hdCycles/utils.h new file mode 100644 index 00000000..fd6d5ca0 --- /dev/null +++ b/plugin/hdCycles/utils.h @@ -0,0 +1,296 @@ +// Copyright 2020 Tangent Animation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// @file utils.h +/// +/// General utilities for Hydra Cycles +#ifndef HD_CYCLES_UTILS_H +#define HD_CYCLES_UTILS_H + +#include "api.h" + +#include "hdcycles.h" + +#include "Mikktspace/mikktspace.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace ccl { +class Mesh; +} + +PXR_NAMESPACE_OPEN_SCOPE + +/* =========- Texture ========== */ + +HDCYCLES_API +bool +HdCyclesPathIsUDIM(const ccl::string& a_filepath); + +HDCYCLES_API +void +HdCyclesParseUDIMS(const ccl::string& a_filepath, ccl::vector& a_tiles); + +/* ========== Material ========== */ + +ccl::Shader* +HdCyclesCreateDefaultShader(); + +/* ========= Conversion ========= */ + +/** + * @brief Create Cycles Transform from given HdSceneDelegate and SdfPath + * + * @param delegate + * @param id + * @return Cycles Transform + */ +HDCYCLES_API +HdTimeSampleArray +HdCyclesSetTransform(ccl::Object* object, HdSceneDelegate* delegate, + const SdfPath& id, bool use_motion); + +ccl::Transform +HdCyclesExtractTransform(HdSceneDelegate* delegate, const SdfPath& id); + +/** + * @brief Convert GfMatrix4d to Cycles Transform representation + * + * @param mat + * @return ccl::Transform + */ +ccl::Transform +mat4d_to_transform(const GfMatrix4d& mat); + +/** + * @brief Convert GfMatrix4f to Cycles Transform representation + * + * @param mat + * @return ccl::Transform + */ +ccl::Transform +mat4f_to_transform(const GfMatrix4f& mat); + +/** + * @brief Convert GfVec2f to Cycles float2 representation + * + * @param a_vec + * @return Cycles float2 + */ +ccl::float2 +vec2f_to_float2(const GfVec2f& a_vec); + +/** + * @brief Convert GfVec3f to Cycles float3 representation + * + * @param a_vec + * @return Cycles float3 + */ +ccl::float3 +vec3f_to_float3(const GfVec3f& a_vec); + +/** + * @brief Lossy convert GfVec4f to Cycles float3 representation + * + * @param a_vec + * @return Cycles float3 + */ +ccl::float3 +vec4f_to_float3(const GfVec4f& a_vec); + +/** + * @brief Convert GfVec3f to Cycles float4 representation with alpha option + * + * @param a_vec + * @param a_alpha + * @return Cycles float4 + */ +ccl::float4 +vec3f_to_float4(const GfVec3f& a_vec, float a_alpha = 1.0f); + +/** + * @brief Convert GfVec4f to Cycles float4 representation + * + * @param a_vec + * @return Cycles float4 + */ +ccl::float4 +vec4f_to_float4(const GfVec4f& a_vec); + +/* ========= Primvars ========= */ + +// HdCycles primvar handling. Designed reference based on HdArnold implementation + +struct HdCyclesPrimvar { + VtValue value; // Copy-on-write value of the primvar + TfToken role; // Role of the primvar + HdInterpolation interpolation; // Type of interpolation used for the value + bool dirtied; // If the primvar has been dirtied + + HdCyclesPrimvar(const VtValue& a_value, const TfToken& a_role, + HdInterpolation a_interpolation) + : value(a_value) + , role(a_role) + , interpolation(a_interpolation) + , dirtied(true) + { + } +}; + +using HdCyclesPrimvarMap + = std::unordered_map; + +// Get Computed primvars +bool +HdCyclesGetComputedPrimvars(HdSceneDelegate* a_delegate, const SdfPath& a_id, + HdDirtyBits a_dirtyBits, + HdCyclesPrimvarMap& a_primvars); + +// Get Non-computed primvars +bool +HdCyclesGetPrimvars(HdSceneDelegate* a_delegate, const SdfPath& a_id, + HdDirtyBits a_dirtyBits, bool a_multiplePositionKeys, + HdCyclesPrimvarMap& a_primvars); + +typedef std::map HdCyclesPDPIMap; + +void +HdCyclesPopulatePrimvarDescsPerInterpolation( + HdSceneDelegate* a_sceneDelegate, SdfPath const& a_id, + HdCyclesPDPIMap* a_primvarDescsPerInterpolation); + +bool +HdCyclesIsPrimvarExists(TfToken const& a_name, + HdCyclesPDPIMap const& a_primvarDescsPerInterpolation, + HdInterpolation* a_interpolation = nullptr); + +/* ======== VtValue Utils ========= */ + + +template +void +_CheckForBoolValue(const VtValue& value, F&& f) +{ + if (value.IsHolding()) { + f(value.UncheckedGet()); + } else if (value.IsHolding()) { + f(value.UncheckedGet() != 0); + } else if (value.IsHolding()) { + f(value.UncheckedGet() != 0); + } +} + +template +void +_CheckForIntValue(const VtValue& value, F&& f) +{ + if (value.IsHolding()) { + f(value.UncheckedGet()); + } else if (value.IsHolding()) { + f(static_cast(value.UncheckedGet())); + } +} + +template +void +_CheckForFloatValue(const VtValue& value, F&& f) +{ + if (value.IsHolding()) { + f(value.UncheckedGet()); + } +} + +template +void +_CheckForDoubleValue(const VtValue& value, F&& f) +{ + if (value.IsHolding()) { + f(value.UncheckedGet()); + } +} + +template +void +_CheckForStringValue(const VtValue& value, F&& f) +{ + if (value.IsHolding()) { + f(value.UncheckedGet()); + } +} + +template +void +_CheckForVec2iValue(const VtValue& value, F&& f) +{ + if (value.IsHolding()) { + f(value.UncheckedGet()); + } +} + + +/* ========= MikkTSpace ========= */ + +int +mikk_get_num_faces(const SMikkTSpaceContext* context); + +int +mikk_get_num_verts_of_face(const SMikkTSpaceContext* context, + const int face_num); + +int +mikk_vertex_index(const ccl::Mesh* mesh, const int face_num, + const int vert_num); + +int +mikk_corner_index(const ccl::Mesh* mesh, const int face_num, + const int vert_num); + +void +mikk_get_position(const SMikkTSpaceContext* context, float P[3], + const int face_num, const int vert_num); + +void +mikk_get_texture_coordinate(const SMikkTSpaceContext* context, float uv[2], + const int face_num, const int vert_num); + +void +mikk_get_normal(const SMikkTSpaceContext* context, float N[3], + const int face_num, const int vert_num); + +void +mikk_set_tangent_space(const SMikkTSpaceContext* context, const float T[], + const float sign, const int face_num, + const int vert_num); + +void +mikk_compute_tangents(const char* layer_name, ccl::Mesh* mesh, bool need_sign, + bool active_render); + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // HD_CYCLES_UTILS_H diff --git a/plugin/ndrCycles/CMakeLists.txt b/plugin/ndrCycles/CMakeLists.txt new file mode 100644 index 00000000..2a451ccf --- /dev/null +++ b/plugin/ndrCycles/CMakeLists.txt @@ -0,0 +1,64 @@ +# Copyright 2020 Tangent Animation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set(PXR_PREFIX "") +set(PXR_PACKAGE ndrCycles) + +add_custom_target(shared_libs) + +if(USE_USD_HOUDINI) + link_directories(${HOUDINI_LIB_DIRS}) + link_libraries(${HOUDINI_LIBRARIES_DEPS}) +endif() + +pxr_plugin(${PXR_PACKAGE} + LIBRARIES + ${USD_LIBRARIES} + ${TBB_LIBRARIES} + ${Boost_LIBRARIES} + ${Python_LIBRARIES} + ${CYCLES_LIBRARIES} + ${OPENGL_gl_LIBRARY} + ${PNG_LIBRARIES} + ${JPEG_LIBRARIES} + ${ZLIB_LIBRARIES} + ${TIFF_LIBRARY} + ${PTHREADS_LIBRARIES} + ${GLEW_LIBRARY} + ${OPENJPEG_LIBRARIES} + ${OIIO_LIBRARIES} + ${OPENEXR_LIBRARIES} + + INCLUDE_DIRS + ${USD_INCLUDE_DIR} + ${Boost_INCLUDE_DIRS} + ${Python_INCLUDE_DIRS} + ${CYCLES_INCLUDE_DIRS} + ${OIIO_INCLUDE_DIRS} + ${OPENEXR_INCLUDE_DIRS} + ${GLEW_INCLUDE_DIR} + ${OPENGL_INCLUDE_DIR} + + PUBLIC_CLASSES + discovery + parser + + PUBLIC_HEADERS + api.h + + RESOURCE_FILES + plugInfo.json + + DISABLE_PRECOMPILED_HEADERS +) diff --git a/plugin/ndrCycles/api.h b/plugin/ndrCycles/api.h new file mode 100644 index 00000000..d7fb2879 --- /dev/null +++ b/plugin/ndrCycles/api.h @@ -0,0 +1,42 @@ +// Copyright 2020 Tangent Animation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef NDR_CYCLES_API_H +#define NDR_CYCLES_API_H + +#include + +#if defined(PXR_STATIC) +# define NDRCYCLES_API +# define NDRCYCLES_API_TEMPLATE_CLASS(...) +# define NDRCYCLES_API_TEMPLATE_STRUCT(...) +# define NDRCYCLES_LOCAL +#else +# if defined(NDRCYCLES_EXPORTS) +# define NDRCYCLES_API ARCH_EXPORT +# define NDRCYCLES_API_TEMPLATE_CLASS(...) \ + ARCH_EXPORT_TEMPLATE(class, __VA_ARGS__) +# define NDRCYCLES_API_TEMPLATE_STRUCT(...) \ + ARCH_EXPORT_TEMPLATE(struct, __VA_ARGS__) +# else +# define NDRCYCLES_API ARCH_IMPORT +# define NDRCYCLES_API_TEMPLATE_CLASS(...) \ + ARCH_IMPORT_TEMPLATE(class, __VA_ARGS__) +# define NDRCYCLES_API_TEMPLATE_STRUCT(...) \ + ARCH_IMPORT_TEMPLATE(struct, __VA_ARGS__) +# endif +# define NDRCYCLES_LOCAL ARCH_HIDDEN +#endif + +#endif // NDR_CYCLES_API_H diff --git a/plugin/ndrCycles/discovery.cpp b/plugin/ndrCycles/discovery.cpp new file mode 100644 index 00000000..14c0ee89 --- /dev/null +++ b/plugin/ndrCycles/discovery.cpp @@ -0,0 +1,95 @@ +// Copyright 2020 Tangent Animation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "discovery.h" + +#include +#include +#include +#include +#include +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +// clang-format off +TF_DEFINE_PRIVATE_TOKENS(_tokens, + (shader) + (cycles) + (filename) +); +// clang-format on + +NDR_REGISTER_DISCOVERY_PLUGIN(NdrCyclesDiscoveryPlugin); + +NdrCyclesDiscoveryPlugin::NdrCyclesDiscoveryPlugin() {} + +NdrCyclesDiscoveryPlugin::~NdrCyclesDiscoveryPlugin() {} + +NdrNodeDiscoveryResultVec +NdrCyclesDiscoveryPlugin::DiscoverNodes(const Context& context) +{ + TfToken filename(""); + NdrNodeDiscoveryResultVec ret; + + // TODO: Store these in proper USD Schema and read at runtime... + std::vector temp_nodes; + temp_nodes.push_back("output"); + temp_nodes.push_back("diffuse_bsdf"); + temp_nodes.push_back("principled_bsdf"); + temp_nodes.push_back("glossy_bsdf"); + temp_nodes.push_back("principled_hair_bsdf"); + temp_nodes.push_back("anisotropic_bsdf"); + temp_nodes.push_back("glass_bsdf"); + temp_nodes.push_back("refraction_bsdf"); + temp_nodes.push_back("toon_bsdf"); + temp_nodes.push_back("velvet_bsdf"); + temp_nodes.push_back("translucent_bsdf"); + temp_nodes.push_back("transparent_bsdf"); + temp_nodes.push_back("subsurface_scattering"); + temp_nodes.push_back("mix_closure"); + temp_nodes.push_back("add_closure"); + temp_nodes.push_back("hair_bsdf"); + + for (const std::string& n : temp_nodes) { + std::string cycles_id = std::string("cycles:" + n); + ret.emplace_back(NdrIdentifier( + TfStringPrintf(cycles_id.c_str())), // identifier + NdrVersion(1, 0), // version + n.c_str(), // name + _tokens->shader, // family + _tokens->cycles, // discoveryType + _tokens->cycles, // sourceType + filename, // uri + filename // resolvedUri + ); + } + + return ret; +} + +const NdrStringVec& +NdrCyclesDiscoveryPlugin::GetSearchURIs() const +{ + static const auto result = []() -> NdrStringVec { + NdrStringVec ret = TfStringSplit(TfGetenv("CYCLES_PLUGIN_PATH"), + ARCH_PATH_LIST_SEP); + ret.push_back(""); + return ret; + }(); + return result; +} + +PXR_NAMESPACE_CLOSE_SCOPE \ No newline at end of file diff --git a/plugin/ndrCycles/discovery.h b/plugin/ndrCycles/discovery.h new file mode 100644 index 00000000..db908e05 --- /dev/null +++ b/plugin/ndrCycles/discovery.h @@ -0,0 +1,66 @@ +// Copyright 2020 Tangent Animation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef NDR_CYCLES_DISCOVERY_H +#define NDR_CYCLES_DISCOVERY_H + +#include "api.h" + +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +/** + * @brief Ndr Discovery for cycles shader nodes + * + */ +class NdrCyclesDiscoveryPlugin : public NdrDiscoveryPlugin { +public: + using Context = NdrDiscoveryPluginContext; + + /** + * @brief Creates an instance of NdrCyclesDiscoveryPlugin + * + */ + NDRCYCLES_API + NdrCyclesDiscoveryPlugin(); + + /** + * @brief Destructor of NdrCyclesDiscoveryPlugin + * + */ + NDRCYCLES_API + ~NdrCyclesDiscoveryPlugin() override; + + /** + * @brief Discovers the cycles shaders + * + * @param context NdrDiscoveryPluginContext of the discovery process + * @return List of the discovered cycles nodes + */ + NDRCYCLES_API + NdrNodeDiscoveryResultVec DiscoverNodes(const Context& context) override; + + /** + * @brief Returns the URIs used to search for cycles shader nodes. + * + * @return All the paths from CYCLES_PLUGIN_PATH + */ + const NdrStringVec& GetSearchURIs() const override; +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // NDR_CYCLES_DISCOVERY_H diff --git a/plugin/ndrCycles/parser.cpp b/plugin/ndrCycles/parser.cpp new file mode 100644 index 00000000..6aa4a61f --- /dev/null +++ b/plugin/ndrCycles/parser.cpp @@ -0,0 +1,101 @@ +// Copyright 2020 Tangent Animation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "parser.h" + +#include +#include +#include +#include +#include +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +NDR_REGISTER_PARSER_PLUGIN(NdrCyclesParserPlugin); + +// clang-format off +TF_DEFINE_PRIVATE_TOKENS(_tokens, + (cycles) + ((cyclesPrefix, "cycles:")) + (binary) +); +// clang-format on + +namespace { +// We have to subclass SdrShaderProperty, because it tries to read the SdfType +// from a token, and it doesn't support all the parameter types cycles does, +// like the 4 component color. Besides this, we also guarantee that the default +// value will match the SdfType, as the SdfType comes from the default value. +class CyclesShaderProperty : public SdrShaderProperty { +public: + CyclesShaderProperty(const TfToken& name, const SdfValueTypeName& typeName, + const VtValue& defaultValue, bool isOutput, + size_t arraySize, const NdrTokenMap& metadata, + const NdrTokenMap& hints, const NdrOptionVec& options) + : SdrShaderProperty(name, typeName.GetAsToken(), defaultValue, isOutput, + arraySize, metadata, hints, options) + , _typeName(typeName) + { + } + + const SdfTypeIndicator GetTypeAsSdfType() const override + { + return { _typeName, _typeName.GetAsToken() }; + } + +private: + SdfValueTypeName _typeName; +}; + +} // namespace + +NdrCyclesParserPlugin::NdrCyclesParserPlugin() {} + +NdrCyclesParserPlugin::~NdrCyclesParserPlugin() {} + +NdrNodeUniquePtr +NdrCyclesParserPlugin::Parse(const NdrNodeDiscoveryResult& discoveryResult) +{ + NdrPropertyUniquePtrVec properties; + return NdrNodeUniquePtr( + new SdrShaderNode(discoveryResult.identifier, // identifier + discoveryResult.version, // version + discoveryResult.name, // name + discoveryResult.family, // family + discoveryResult.discoveryType, // context + discoveryResult.sourceType, // sourceType + discoveryResult.uri, // uri +// TODO: Enable this when we move to 20.05 +#ifdef USD_HAS_NEW_SDR_NODE_CONSTRUCTOR + discoveryResult.uri, // resolvedUri +#endif + std::move(properties))); +} + +const NdrTokenVec& +NdrCyclesParserPlugin::GetDiscoveryTypes() const +{ + static const NdrTokenVec ret = { _tokens->cycles }; + return ret; +} + +const TfToken& +NdrCyclesParserPlugin::GetSourceType() const +{ + return _tokens->cycles; +} + +PXR_NAMESPACE_CLOSE_SCOPE \ No newline at end of file diff --git a/plugin/ndrCycles/parser.h b/plugin/ndrCycles/parser.h new file mode 100644 index 00000000..a12fdaf1 --- /dev/null +++ b/plugin/ndrCycles/parser.h @@ -0,0 +1,71 @@ +// Copyright 2020 Tangent Animation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef NDR_CYCLES_PARSER_H +#define NDR_CYCLES_PARSER_H + +#include "api.h" + +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +/// Ndr Parser for cycles shader nodes. +class NdrCyclesParserPlugin : public NdrParserPlugin { +public: + /** + * @brief Creates an instance of NdrCyclesParserPlugin. + * + */ + NDRCYCLES_API + NdrCyclesParserPlugin(); + + /** + * @brief Destructor for NdrCyclesParserPlugin. + * + */ + NDRCYCLES_API + ~NdrCyclesParserPlugin() override; + + /** + * @brief Parses a node discovery result to a NdrNode. + * + * @param discoveryResult NdrNodeDiscoveryResult returned by the discovery plugin. + * @return The parsed Ndr Node. + */ + NDRCYCLES_API + NdrNodeUniquePtr + Parse(const NdrNodeDiscoveryResult& discoveryResult) override; + + /** + * @brief Returns all the supported discovery types. + * + * @return Returns "cycles" as the only supported discovery type. + */ + NDRCYCLES_API + const NdrTokenVec& GetDiscoveryTypes() const override; + + /** + * @brief Returns all the supported source types. + * + * @return Returns "cycles" as the only supported source type. + */ + NDRCYCLES_API + const TfToken& GetSourceType() const override; +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // NDR_CYCLES_PARSER_H diff --git a/plugin/ndrCycles/plugInfo.json b/plugin/ndrCycles/plugInfo.json new file mode 100644 index 00000000..f3339972 --- /dev/null +++ b/plugin/ndrCycles/plugInfo.json @@ -0,0 +1,38 @@ +{ + "Plugins": [ + { + "Info": { + "SdfMetadata": { + "filename": { + "appliesTo": [ + "prims" + ], + "default": "", + "displayGroup": "Cycles", + "documentation": "Cycles plugin location for a node.", + "type": "token" + } + }, + "Types": { + "NdrCyclesParserPlugin": { + "bases": [ + "NdrParserPlugin" + ], + "displayName": "Cycles Node Parser" + }, + "NdrCyclesDiscoveryPlugin": { + "bases": [ + "NdrDiscoveryPlugin" + ], + "displayName": "Cycles Node Discovery" + } + } + }, + "LibraryPath": "@PLUG_INFO_LIBRARY_PATH@", + "Name": "ndrCycles", + "ResourcePath": "resources", + "Root": "..", + "Type": "library" + } + ] +} \ No newline at end of file diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt new file mode 100644 index 00000000..36e36623 --- /dev/null +++ b/tools/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright 2020 Tangent Animation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +if(USE_USD_HOUDINI) + add_subdirectory(houdini) +endif() diff --git a/tools/houdini/CMakeLists.txt b/tools/houdini/CMakeLists.txt new file mode 100644 index 00000000..cd622c64 --- /dev/null +++ b/tools/houdini/CMakeLists.txt @@ -0,0 +1,51 @@ +# Copyright 2020 Tangent Animation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set(TOOL_NAME hda_generator) +project(${TOOL_NAME}) + +set(CMAKE_BINARY_DIR $ENV{REZ_BUILD_PATH}/tools) +set(EXECUTABLE_OUTPUT_PATH $ENV{REZ_BUILD_PATH}/tools) + +link_directories(${HOUDINI_LIB_DIRS}) +link_libraries(${HOUDINI_LIBRARIES_DEPS}) + +add_executable(${TOOL_NAME} hda_generator.cpp) + +target_include_directories(${TOOL_NAME} PRIVATE + ${CYCLES_INCLUDE_DIRS} + ${HOUDINI_INCLUDE_DIRS} + ${OPENEXR_INCLUDE_DIRS} + ${USD_INCLUDE_DIR} + ${Boost_INCLUDE_DIRS} + ${Python_INCLUDE_DIRS} + ${CYCLES_INCLUDE_DIRS} + ${OPENEXR_INCLUDE_DIRS} + ${GLEW_INCLUDE_DIR} +) + +target_link_libraries(${TOOL_NAME} + ${TBB_LIBRARIES} + ${Boost_LIBRARIES} + ${Python_LIBRARIES} + ${CYCLES_LIBRARIES} + ${OPENGL_gl_LIBRARY} + ${GLEW_LIBRARY} + ${OPENJPEG_LIBRARIES} + ${OPENEXR_LIBRARIES} + ${OPENSUBDIV_LIBRARIES} + ${OIIO_LIBRARIES} +) + +install(TARGETS ${TOOL_NAME} RUNTIME DESTINATION $ENV{REZ_BUILD_INSTALL_PATH}/build/tools) diff --git a/tools/houdini/hda_generator.cpp b/tools/houdini/hda_generator.cpp new file mode 100644 index 00000000..7beec2bf --- /dev/null +++ b/tools/houdini/hda_generator.cpp @@ -0,0 +1,519 @@ +// Copyright 2020 Tangent Animation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +using namespace ccl; + +void +create_folder(const char* path) +{ + boost::filesystem::create_directories(path); +} + +void +hda_log(const std::string& text) +{ + std::cout << "[HDA Generator]: " << text << '\n'; +} + +std::vector +split_string(const std::string& string) +{ + std::stringstream ss(string); + std::istream_iterator begin(ss); + std::istream_iterator end; + return std::vector(begin, end); +} + +std::string +create_readable_label(const std::string& string) +{ + std::string base = string; + // Prettify label + std::replace(base.begin(), base.end(), '_', ' '); + std::vector base_components = split_string(base); + + std::string output = "Cycles "; + int idx = 0; + for (std::string& s : base_components) { + s[0] = std::toupper(s[0]); + output += s; + if (idx < base_components.size() - 1) + output += ' '; + idx++; + } + + return output; +} + +std::string +get_socket_type_literal(const SocketType& socket) +{ + if (socket.type == SocketType::Type::ENUM) { + return "int"; + } + if (socket.type == SocketType::Type::CLOSURE) { + return "surface"; + } + if (socket.type == SocketType::Type::BOOLEAN) { + return "int"; + } + if (socket.type == SocketType::Type::NORMAL) { + return "vector"; + } + if (socket.type == SocketType::Type::POINT) { + return "vector"; + } + if (socket.type == SocketType::Type::VECTOR) { + if (ccl::string_iequals(socket.ui_name.string(), "displacement")) { + return "displacement"; + } else { + return "vector"; + } + } + if (socket.type == SocketType::Type::STRING) { + if (ccl::string_iequals(socket.ui_name.string(), "filename")) { + return "image"; + } else { + return "string"; + } + } + return SocketType::type_name(socket.type).string(); +} + +std::string +get_socket_name(const SocketType& socket) +{ + return socket.name.string(); +} + +std::string +get_socket_label(const SocketType& socket) +{ + return socket.ui_name.string(); +} + +std::string +get_socket_default_value(const SocketType& socket) +{ + std::stringstream ss; + ss << " default { "; + + switch (socket.type) { + case SocketType::Type::INT: + ss << *((int*)socket.default_value) << ' '; + break; + case SocketType::Type::FLOAT: + ss << *((float*)socket.default_value) << ' '; + break; + case SocketType::Type::STRING: ss << "\"\" "; break; + case SocketType::Type::COLOR: + case SocketType::Type::POINT: + case SocketType::Type::VECTOR: + ccl::float3 v = *((ccl::float3*)socket.default_value); + ss << v.x << ' ' << v.y << ' ' << v.z << ' '; + break; + + default: ss << "1 "; break; + } + + ss << "}\n"; + + return ss.str(); +} + +std::string +get_socket_enums(const SocketType& socket) +{ + std::stringstream ss; + ss << " menu {" << '\n'; + for (auto it = socket.enum_values->begin(); it != socket.enum_values->end(); + ++it) { + std::string enum_name = it->first.string(); + ss << " \"" << enum_name << "\"\t\"" << enum_name << "\"\n"; + } + ss << " }\n"; + return ss.str(); +} + + +bool +create_individual_shader(std::string path, std::string op_name, + std::string raw_name, std::string label, + const NodeType& node) +{ + // -- Create CreateScript File + + std::ofstream f_create(path + "/CreateScript"); + f_create << "# Automatically generated script\n" + "\\set noalias = 1\n" + "#\n" + "# Creation script for " + << op_name + << " operator\n" + "#\n" + "\n" + "if ( \"$arg1\" == \"\" ) then\n" + " echo This script is intended as a creation script\n" + " exit\n" + "endif\n" + "\n" + "# Node $arg1 (Vop/" + << op_name + << ")\n" + "opexprlanguage -s hscript $arg1\n" + "opuserdata -n '___Version___' -v '' $arg1" + << '\n'; + + f_create.close(); + + // -- Create DialogScript File + + std::ofstream f_dialog(path + "/DialogScript"); + f_dialog << "# Dialog script for " << op_name + << " automatically generated\n" + "\n" + "{\n" + " name\t" + << op_name << '\n' + << " script\tcycles:" << raw_name << '\n' + << " label\t" << label << '\n' + << "\n" + " rendermask\tcycles\n" + " externalshader 1\n" + " shadertype\tsurface\n"; + + // Author socket inputs + for (const SocketType& input : node.inputs) { + std::string type_name = get_socket_type_literal(input); + std::string name = get_socket_name(input); + std::string label = get_socket_label(input); + + if (name.find("tex_mapping.") != std::string::npos) + continue; + + f_dialog << " input"; + f_dialog << '\t' << type_name; + f_dialog << '\t' << name; + f_dialog << "\t\"" << label << '\"'; + f_dialog << '\n'; + } + + // Author socket outputs + for (const SocketType& output : node.outputs) { + std::string type_name = get_socket_type_literal(output); + std::string name = get_socket_name(output); + std::string label = get_socket_label(output); + + f_dialog << " output"; + f_dialog << '\t' << type_name; + f_dialog << '\t' << name; + f_dialog << '\t' << label; + f_dialog << '\n'; + } + + for (const SocketType& input : node.inputs) { + std::string name = get_socket_name(input); + if (name.find("tex_mapping.") != std::string::npos) + continue; + f_dialog << " inputflags\t"; + f_dialog << input.name; + f_dialog << "\t0\n"; + } + + f_dialog << " signature\t\"Default Inputs\"\tdefault\t{ "; + for (const SocketType& input : node.inputs) { + std::string type_name = get_socket_type_literal(input); + std::string name = get_socket_name(input); + if (name.find("tex_mapping.") != std::string::npos) + continue; + f_dialog << type_name << ' '; + } + for (const SocketType& output : node.outputs) { + std::string type_name = get_socket_type_literal(output); + f_dialog << type_name << ' '; + } + f_dialog << "}\n"; + + f_dialog << "\n" + " outputoverrides\tdefault\n" + " {\n"; + + for (const SocketType& output : node.outputs) { + f_dialog << "\t___begin\tauto\n" + "\t\t\t(0)\n"; + } + + f_dialog << " }\n" + "\n" + " help {\n" + "\t\"\"\n" + " }\n" + "\n"; + + for (const SocketType& input : node.inputs) { + std::string type_name = get_socket_type_literal(input); + std::string name = get_socket_name(input); + std::string label = get_socket_label(input); + + if (name.find("tex_mapping.") != std::string::npos) + continue; + + std::string default_value = get_socket_default_value(input); + + + int range_min = 0; + int range_max = 1; + int num_components = 1; + + if (input.type == SocketType::Type::VECTOR + || input.type == SocketType::Type::COLOR + || input.type == SocketType::Type::POINT + || input.type == SocketType::Type::NORMAL) { + num_components = 3; + } + + // Parm DS + + f_dialog << " parm {\n" + " name \"" + << name + << "\"\n" + " label \"" + << label + << "\"\n" + " type " + << type_name + << "\n" + + " size " + << num_components << "\n" + << default_value << '\n' + << " range { " << range_min << " " << range_max + << " }\n"; + if (input.type == SocketType::Type::ENUM) { + f_dialog << get_socket_enums(input); + } + f_dialog + << " parmtag { \"script_callback_language\" \"python\" }\n" + " }\n"; + } + + f_dialog << '}' << '\n'; + + // -- Create ExtraFileOptions File + + std::ofstream f_options(path + "/ExtraFileOptions"); + f_options + << "{\n" + "\t\"ViewerStateModule/CodeGenInput\":{\n" + "\t\t\"type\":\"string\",\n" + "\t\t\"value\":\"{\\n\\t\\\"state_name\\\":\\\"\\\",\\n\\t\\\"state_label\\\":\\\"\\\",\\n\\t\\\"state_descr\\\":\\\"\\\",\\n\\t\\\"state_icon\\\":\\\"$HH/config/Icons\\\",\\n\\t\\\"state_sample\\\":0,\\n\\t\\\"state_handler_indices\\\":[]\\n}\\n\"\n" + "\t}\n" + "}" + << '\n'; + + f_options.close(); + + // -- Create FunctionName File + + std::ofstream f_functionName(path + "/FunctionName"); + f_functionName << "cycles:" << raw_name; + f_functionName.close(); + + // -- Create Help File + + std::ofstream f_help(path + "/Help"); + f_help.close(); + + // -- Create Sections.list File + + std::ofstream f_sections(path + "/Sections.list"); + + f_sections << "\"\"" << '\n'; + f_sections << "DialogScript\tDialogScript" << '\n'; + f_sections << "TypePropertiesOptions\tTypePropertiesOptions" << '\n'; + f_sections << "Help\tHelp" << '\n'; + f_sections << "Tools.shelf\tTools.shelf" << '\n'; + f_sections << "FunctionName\tFunctionName" << '\n'; + f_sections << "CreateScript\tCreateScript" << '\n'; + f_sections << "ExtraFileOptions\tExtraFileOptions" << '\n'; + + f_sections.close(); + + // -- Create Tools.shelf File + + std::ofstream f_tools(path + "/Tools.shelf"); + + f_tools + << "\n" + "\n" + " \n" + "\n" + " \n" + " \n" + " VOP\n" + " \n" + " \n" + " $HDA_TABLE_AND_NAME\n" + " \n" + " Cycles\n" + " \n" + " \n" + " USD\n" + " \n" + " \n" + "\n" + << '\n'; + + f_tools.close(); + + // -- Create TypePropertiesOptions File + + std::ofstream f_type(path + "/TypePropertiesOptions"); + + f_type << "CheckExternal := 1;" << '\n'; + f_type << "ContentsCompressionType := 1;" << '\n'; + f_type << "ForbidOutsideParms := 1;" << '\n'; + f_type << "GzipContents := 1;" << '\n'; + f_type << "LockContents := 1;" << '\n'; + f_type << "MakeDefault := 1;" << '\n'; + f_type << "ParmsFromVfl := 0;" << '\n'; + f_type << "PrefixDroppedParmLabel := 0;" << '\n'; + f_type << "PrefixDroppedParmName := 0;" << '\n'; + f_type << "SaveCachedCode := 0;" << '\n'; + f_type << "SaveIcon := 1;" << '\n'; + f_type << "SaveSpareParms := 0;" << '\n'; + f_type << "UnlockOnCreate := 0;" << '\n'; + f_type << "UseDSParms := 1;" << '\n'; + + f_type.close(); + + return true; +} + +bool +create_shaders_hda(std::string path, + unordered_map& nodes) +{ + create_folder(path.c_str()); + + // Sections.list + std::ofstream f_sections(path + "/Sections.list"); + f_sections << "\"\"" << '\n'; + f_sections << "INDEX__SECTION\tINDEX_SECTION" << '\n'; + f_sections << "houdini.hdalibrary\thoudini.hdalibrary" << '\n'; + + for (auto const& n : nodes) { + std::string op_name = "cycles_" + n.first.string(); + std::string label = n.second.name.string(); + + f_sections << "Vop_1" << op_name << "\tVop/" << op_name << '\n'; + } + + f_sections.close(); + + // -- Create INDEX__SECTION + + std::ofstream f_index(path + "/INDEX__SECTION"); + + for (auto const& n : nodes) { + std::string op_name = "cycles_" + n.first.string(); + std::string label = create_readable_label(n.second.name.string()); + f_index << "Operator: " << op_name << '\n'; + f_index << "Label: " << label << '\n'; + f_index << "Path: oplib:/Vop/" << op_name << "?Vop/" << op_name + << '\n'; + f_index << "Icon: VOP_" << op_name << '\n'; + f_index << "Table: Vop" << '\n'; + f_index << "License: " << '\n'; + f_index << "Extra: usd" << '\n'; + f_index << "User: " << '\n'; + f_index << "Inputs: 0 to 1" << '\n'; + f_index << "Subnet: false" << '\n'; + f_index << "Python: false" << '\n'; + f_index << "Empty: false" << '\n'; + f_index << "Modified: Sun Aug 17 00:12:00 2020" << '\n'; + f_index << '\n'; + } + + f_index.close(); + + // -- Create hdalibrary + + std::ofstream f_hdaLibrary(path + "/houdini.hdalibrary"); + + f_hdaLibrary.close(); + + // Create node definition + for (auto const& n : nodes) { + std::string raw_name = n.first.string(); + std::string op_name = "cycles_" + n.first.string(); + std::string vop_name = "Vop_1" + op_name; + std::string label = n.second.name.string(); + + std::string vop_path = path + "/" + vop_name; + + create_folder(vop_path.c_str()); + create_individual_shader(vop_path, op_name, raw_name, label, n.second); + } + + return true; +} + +int +main(int argc, char* argv[]) +{ + hda_log("Generating Houdini Cycles VOP Nodes..."); + + SessionParams sp; + Session* sesh = new Session(sp); + + unordered_map& nodes = NodeType::types(); + hda_log("Nodes found: " + std::to_string(nodes.size())); + + std::string cwd = boost::filesystem::current_path().string(); + + if (argc >= 2) { + cwd = std::string(argv[1]); + } + + create_shaders_hda(cwd + "/hda/source", nodes); + + std::string cmd = std::string("rez-env houdini -c \"hotl -l ") + cwd + + "/hda/source" + " " + cwd + "/hda/cycles_shaders.hda\""; + system(cmd.c_str()); + + boost::filesystem::remove_all(cwd + "/hda/source"); + + hda_log("Done creating nodes..."); +} \ No newline at end of file