diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000000..f4c107d2f6 --- /dev/null +++ b/.clang-format @@ -0,0 +1,92 @@ +--- +Language: Cpp + +BasedOnStyle: WebKit +AlignAfterOpenBracket: AlwaysBreak +AlignConsecutiveMacros: 'true' +AlignConsecutiveDeclarations: 'true' +AlignEscapedNewlines: Left +AlignTrailingComments: 'true' +AllowAllConstructorInitializersOnNextLine: 'false' +AllowAllParametersOfDeclarationOnNextLine: 'false' +AllowShortCaseLabelsOnASingleLine: 'true' +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: 'false' +AlwaysBreakTemplateDeclarations: MultiLine +BinPackArguments: 'false' +BinPackParameters: 'false' +BreakBeforeBraces: Custom +BraceWrapping: + AfterClass: 'true' + AfterEnum: 'true' + AfterFunction: 'true' + AfterStruct: 'true' + AfterUnion: 'true' +BreakBeforeTernaryOperators: 'true' +BreakConstructorInitializers: BeforeComma +BreakInheritanceList: BeforeComma +ColumnLimit: '100' +FixNamespaceComments: 'true' +IncludeBlocks: Regroup +IncludeCategories: + + # Desired final ordering: + # 0. Glew must be included before any other GL header + # 1. Related header + # 2. All private headers + # 3. All public headers from this repository (maya-usd) + # 4. Pixar + USD headers + # 5. Autodesk + Maya headers + # 6. Other libraries' headers + # 7. C++ standard library headers + # 8. C system headers + # 9. Conditional includes + + # 0. GL loaders must be included before any other GL header + # Negative priority puts it above the default IncludeIsMainRegex + - Regex: '' + Priority: -1 + + # 1. Related header + # Handled by the default IncludeIsMainRegex regex, and auto-assigned + # Priority 0 + + # 3. All public headers from this repository (maya-usd) + - Regex: '^<(mayaUsd.*|mayaHydraLib|AL|usdMaya)/' + Priority: 3 + + # 4. Pixar + USD headers + - Regex: '^$' + Priority: 7 + + # 8. C system headers + # angle brackets, no directory, end with ".h" + - Regex: '^<[A-Za-z0-9_-]+\.h>$' + Priority: 8 + + # 2. All private headers + - Regex: '^"' + Priority: 2 + + # 6. Other libraries' headers + - Regex: '^<' + Priority: 6 + + # 9. Conditional includes + # Not reordered by clang-format, we need to manually make sure these come last + +MaxEmptyLinesToKeep: '1' +NamespaceIndentation: None +UseTab: Never + +... diff --git a/.clang-format-ignore b/.clang-format-ignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/.clang-format-include b/.clang-format-include new file mode 100644 index 0000000000..aad3c80ed2 --- /dev/null +++ b/.clang-format-include @@ -0,0 +1,8 @@ +\.c$ +\.cc$ +\.cpp$ +\.cxx$ +\.h$ +\.hh$ +\.hpp$ +\.hxx$ diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000000..5bc9317bc6 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# Ignore clang-format commit +7c64b5df1f1cdbf879aaef12ed64b7fdd77a3af1 diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000000..3a9fd6df02 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,34 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug +assignees: santosg87 + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**Steps to reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Attachments** +If applicable, add screenshots, sample files, etc to help explain your problem. + +**Specs (if applicable):** + - OS & version [e.g. Windows 10] + - Compiler & version [e.g. gcc 6.3.1] + - Maya version [e.g. Maya 2020] + - Maya USD commit SHA [e.g. dev at caa921c1] + - Pixar USD commit SHA [e.g. dev at b85ddac2] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/build-issue-report.md b/.github/ISSUE_TEMPLATE/build-issue-report.md new file mode 100644 index 0000000000..69d0d0396f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/build-issue-report.md @@ -0,0 +1,24 @@ +--- +name: Build issue report +about: Before opening a new build issue, please review doc/build.md +title: '' +labels: help wanted +assignees: '' + +--- + +**Describe the issue** +A description of what the issue is. + +**Build log** +Please attach a build_log.txt + +**Specs:** + - OS & version [e.g. Windows 10] + - Compiler & version [e.g. gcc 6.3.1] + - Maya version [e.g. Maya 2020] + - Maya USD commit SHA [e.g. dev at caa921c1] + - Pixar USD commit SHA [e.g. dev at b85ddac2] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000000..11fc491ef1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/run-clang-format.py b/.github/run-clang-format.py new file mode 100755 index 0000000000..3c92a8ae53 --- /dev/null +++ b/.github/run-clang-format.py @@ -0,0 +1,249 @@ +#!/usr/bin/env python + +# Copyright 2023 Autodesk +# +# 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. +# + +'''Run clang-format on files in this repository + +By default, runs on all files, but may pass specific files. +''' + +from __future__ import absolute_import, division, print_function, unicode_literals + +import argparse +import inspect +import fnmatch +import io +import os +import re +import stat +import sys +import platform +import time + +from subprocess import check_call, check_output + +if sys.version_info[0] < 3: + # Python-2 check_output doesn't have encoding + def check_output(*args, **kwargs): + import subprocess + kwargs.pop('encoding') + return subprocess.check_output(*args, **kwargs) + + +THIS_FILE = os.path.normpath(os.path.abspath(inspect.getsourcefile(lambda: None))) +THIS_DIR = os.path.dirname(THIS_FILE) +# THIS_DIR = REPO_ROOT/.github +REPO_ROOT = os.path.dirname(THIS_DIR) + +UPDATE_INTERVAL = .2 + + +_last_update_len = 0 +_on_update_line = False + + +def update_status(text): + global _last_update_len + global _on_update_line + sys.stdout.write('\r') + text_len = len(text) + extra_chars = _last_update_len - text_len + if extra_chars > 0: + text += (' ' * extra_chars) + sys.stdout.write(text) + _last_update_len = text_len + sys.stdout.flush() + _on_update_line = True + + +def post_update_print(text): + global _on_update_line + if _on_update_line: + print() + print(text) + _on_update_line = False + + +def regex_from_file(path, glob=False): + with io.open(path, 'r') as f: + patterns = f.read().splitlines() + # ignore comment lines + patterns = [x for x in patterns if x.strip() and not x.lstrip().startswith('#')] + if glob: + patterns = [fnmatch.translate(x) for x in patterns] + regex = '({})'.format('|'.join(patterns)) + return re.compile(regex) + +if platform.system() == "Windows" and sys.version_info >= (3, 6): + import pathlib # Python 3.6 is required for pathlib.Path + def canonicalpath(path): + path = os.path.abspath(os.path.realpath(os.path.normpath(os.path.normcase(path)))) + realpath = str(pathlib.Path(path).resolve()) # To get a properly cased path ie: from r'C:\WiNdOwS\SyStEm32\DeSkToP.iNi' get r'C:\Windows\System32\desktop.ini' + if len(path) == len(realpath): # This is to avoid following symbolic links, there is still the possibility that they could be equal. + path = realpath + if os.path.isabs(path) and path[0].upper() != path[0]: + path = path[0].upper() +path[1:] # path.capitalize() + path = path.replace('\\', '/') + return path +else: + def canonicalpath(path): + path = os.path.abspath(os.path.realpath(os.path.normpath(os.path.normcase(path)))) + return path.replace('\\', '/') + +def run_clang_format(paths=(), verbose=False, commit=None): + """Runs clang-format in-place on repo files + + Returns + ------- + List[str] + Files altered by clang-format + """ + if not paths and not commit: + paths = [REPO_ROOT] + + if commit: + check_call(['git', 'checkout', commit], cwd=REPO_ROOT) + text = check_output( + ['git', 'diff-tree', '--no-commit-id', '--name-only', '-r', + commit], cwd=REPO_ROOT, encoding=sys.stdout.encoding) + commit_paths = text.splitlines() + paths.extend(os.path.join(REPO_ROOT, p) for p in commit_paths) + + files = set() + folders = set() + + include_path = os.path.join(REPO_ROOT, '.clang-format-include') + include_regex = regex_from_file(include_path) + + ignore_path = os.path.join(REPO_ROOT, '.clang-format-ignore') + ignore_regex = regex_from_file(ignore_path, glob=True) + + # tried to parse .gitignore with regex_from_file, but it has + # too much special git syntax. Instead just using `git ls-files` + # as a filter... + git_files = check_output(['git', 'ls-files'], cwd=REPO_ROOT, + encoding=sys.stdout.encoding) + git_files = set(canonicalpath(x.strip()) for x in git_files.splitlines()) + + def print_path(p): + if p.startswith(REPO_ROOT): + p = os.path.relpath(p, REPO_ROOT) + return p + + def passes_filter(path): + rel_path = os.path.relpath(path, REPO_ROOT) + match_path = os.path.join('.', rel_path) + # standardize on '/', because that's what's used in files, + # and output by `git ls-files` + match_path = match_path.replace('\\', '/') + if not include_regex.search(match_path): + return False + if ignore_regex.pattern != '()' and ignore_regex.search(match_path): + return False + return True + + # parse the initial fed-in paths, which may be files or folders + for path in paths: + # Get the stat, so we only do one filesystem call, instead of + # two for os.path.isfile() + os.path.isdir() + try: + st_mode = os.stat(path).st_mode + if stat.S_ISDIR(st_mode): + folders.add(path) + elif stat.S_ISREG(st_mode): + if canonicalpath(path) in git_files: + files.add(path) + except Exception: + print("Given path did not exist: {}".format(path)) + raise + + for folder in folders: + # we already have list of potential files in git_files... + # filter to ones in this folder + folder = canonicalpath(folder) + '/' + files.update(x for x in git_files if x.startswith(folder)) + + # We apply filters even to fed-in paths... this is to aid + # in use of globs on command line + files = sorted(x for x in files if passes_filter(x)) + + clang_format_executable = os.environ.get('CLANG_FORMAT_EXECUTABLE', + 'clang-format') + if verbose: + print("Running clang-format on {} files...".format(len(files))) + last_update = time.time() + + def print_path(p): + if p.startswith(REPO_ROOT): + p = os.path.relpath(p, REPO_ROOT) + return p + + altered = [] + for i, path in enumerate(files): + if verbose: + now = time.time() + if now - last_update > UPDATE_INTERVAL: + last_update = now + update_status('File {}/{} ({:.1f}%) - {}'.format( + i + 1, len(files), (i + 1) / len(files) * 100, + print_path(path))) + # Note - couldn't find a way to get clang-format to return whether + # or not a file was altered with '-i' - so checking ourselves + # Checking mtime is not foolproof, but is faster than reading file + # and comparing, and probably good enough + mtime_orig = os.path.getmtime(path) + check_call([clang_format_executable, '-i', path]) + mtime_new = os.path.getmtime(path) + if mtime_new != mtime_orig: + post_update_print("File altered: {}".format(print_path(path))) + altered.append(path) + post_update_print("Done - altered {} files".format(len(altered))) + return altered + + +def get_parser(): + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument('paths', nargs='*', + help='Paths to run clang-format on; defaults to all files in repo') + parser.add_argument('-v', '--verbose', action='store_true', + help='Enable more output (ie, progress messages)') + parser.add_argument('-c', '--commit', + help='Git commit / revision / branch; will first check out that commit,' + " then query it for it's list of affected files, to use as the files" + ' to run clang-format on; if PATHS are also manually given, they are' + ' appended') + return parser + + +def main(raw_args=None): + parser = get_parser() + args = parser.parse_args(raw_args) + try: + altered = run_clang_format(paths=args.paths, verbose=args.verbose, + commit=args.commit) + except Exception: + import traceback + traceback.print_exc() + return 1 + if altered: + return 1 + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/.github/workflows/maya-hydra-new-issues.yml b/.github/workflows/maya-hydra-new-issues.yml new file mode 100644 index 0000000000..3d9c3d8d9c --- /dev/null +++ b/.github/workflows/maya-hydra-new-issues.yml @@ -0,0 +1,16 @@ +name: Move Issues to Triage +on: + issues: + types: [opened, reopened] + +jobs: + move-triage-card: + runs-on: ubuntu-latest + steps: + - uses: alex-page/github-project-automation-plus@v0.8.3 + # Aug 2023: Update from v0.3.0 to v0.8.3 which uses node16. + # node12 is out of support. + with: + project: Issue Triage + column: Needs triage + repo-token: ${{ secrets.REPO_ACCESS_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/maya-hydra-preflight-launcher.yml b/.github/workflows/maya-hydra-preflight-launcher.yml new file mode 100644 index 0000000000..c19d640d3a --- /dev/null +++ b/.github/workflows/maya-hydra-preflight-launcher.yml @@ -0,0 +1,93 @@ +name: Pre-flight build on pull request + +# Trigger the workflow on pull request (assigned) event for the branch +# https://help.github.com/en/actions/reference/events-that-trigger-workflows +on: + pull_request: + branches: [dev] + types: [assigned] + +jobs: + + clang_format_linter: + timeout-minutes: 30 + runs-on: ubuntu-latest + if: ${{ github.event.assignee.login == 'ecp-maya-devops-adsk' }} + steps: + # Aug 2023: Update from v2 to v3 which uses node16 (node12 is out of support). + - uses: actions/checkout@v3 + - uses: DoozyX/clang-format-lint-action@v0.16.2 + with: + source: '.' + clangFormatVersion: 10 + + # Wait for remote build to start and finish, report results + build_preflight: + timeout-minutes: 400 + runs-on: ubuntu-latest + needs: clang_format_linter + if: ${{ github.event.assignee.login == 'ecp-maya-devops-adsk' }} + steps: + + # Build start info will be committed here when the remote build is launched + - name: Setup transfer repo + uses: actions/checkout@v3 + with: + repository: ecp-maya-devops-adsk/log-transfer + ref: transfer-hydra + path: transfer-hydra + + # Echo the file name - it's hard to find the run_id in the UI + - name: Echo the expected start file name + run: "echo ${{ github.run_id }}_${{ github.run_number }}_start.txt" + + # Wait for remote build to start + - name: Wait until remote build starts + shell: bash + # 180 minutes wait time. There will be overhead for the git pull command, so actual wait time will be slightly more than 180 minutes + run: "cd transfer-hydra ; for (( i=0; i<180; i++ )) ; do if [ -f ${{ github.run_id }}_${{ github.run_number }}_start.txt ] ; then break ; fi ; git pull --quiet ; sleep 60 ; false ; done || exit 1" + + # Show contents of start file + - name: Show build start information + shell: bash + run: "cat transfer-hydra/${{ github.run_id }}_${{ github.run_number }}_start.txt" + + # Grep the start file to show failures + - name: Exit with error if a build failed to start + # Default shell includes "-o pipefail" and "-e". Specify a different shell + shell: bash --noprofile --norc {0} + run: "if grep -i 'Remote build failed' transfer-hydra/${{ github.run_id }}_${{ github.run_number }}_start.txt; then exit 1; else exit 0; fi" + + # Echo the file name - it's hard to find the run_id in the UI + - name: Echo the expected result file name + run: "echo ${{ github.run_id }}_${{ github.run_number }}_result.txt" + + # Wait for remote build to finish and commit results to git + - name: Wait until build results are available + shell: bash + # 180 minutes wait time. There will be overhead for the git pull command, so actual wait time will be slightly more than 180 minutes + run: "cd transfer-hydra ; for (( i=0; i<180; i++ )) ; do if [ -f ${{ github.run_id }}_${{ github.run_number }}_result.txt ] ; then break ; fi ; git pull --quiet ; sleep 60 ; false ; done || exit 1" + + # List files related to this build + - name: List files in transfer-hydra directory + shell: bash + run: "ls -lap transfer-hydra/${{ github.run_id }}_${{ github.run_number }}_*" + + # Upload files related to this build + - name: Upload files in transfer-hydra directory as artifacts + # Aug 2023: Update from v2 to v3 which uses node16 (node12 is out of support). + uses: actions/upload-artifact@v3 + with: + name: build logs + path: "transfer-hydra/${{ github.run_id }}_${{ github.run_number }}_*" + + # Show contents of result file + - name: Show build result information + shell: bash + run: "cat transfer-hydra/${{ github.run_id }}_${{ github.run_number }}_result.txt" + + # Grep the results file to show failures + - name: Exit with error if a build failed + # Default shell includes "-o pipefail" and "-e". Specify a different shell + shell: bash --noprofile --norc {0} + run: "if grep -i failed transfer-hydra/${{ github.run_id }}_${{ github.run_number }}_result.txt; then exit 1; else exit 0; fi" \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..f22386a5d2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,88 @@ +##### Editor Specific Ignores ##### + +## Common Editor Resources ## +GTAGS +GRTAGS +GPATH +*.clang_complete +compile_flags.txt +compile_commands.json +# Git Merge conflict files +*.orig + +## Visual Studio / Visual Studio Code ## +*.vs +*.vscode +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +## Jetbrains (CLion/PyCharm etc) ## +.idea/ + +## Eclipse ## +*.project +*.cproject +*.pydevproject +*.settings/ + +## XCode ## +xcuserdata/ +*.xcuserstate +*.pbxuser + +## Emacs ## +*.dir-locals.el +/.emacs.desktop +/.emacs.desktop.lock +*.elc + +## Vim ## +*.swp +Session.vim +Sessionx.vim + + +##### Build Specific Ignores ##### + +## CMake Artifacts ## +cmake-build-*/ +CMakePresets.json +CMakeUserPresets.json +CMakeSettings.json +CMakeCache.txt +CMakeLists.txt.user +CMakeFiles/cmake.check_cache +*build/ + +## Python ## +__pycache__/ +*.py[cod] +*$py.class +*.egg + +## Compiled Libs ## +*.so +*.dylib +*.dll + +##### Platform Specific Ignores ##### + +## macOS ## +.DS_Store +.AppleDouble +.LSOverride + +## Windows ## +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +## Linux ## +.directory +.nfs +# Files ending in ~ are Linux convention backup files +*~ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000000..10ac6e9df6 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,10 @@ +repos: +- repo: local + hooks: + + - id: clang-format + name: clang-format + description: Format files with clang-format + entry: .github/run-clang-format.py + language: python + stages: ['commit', 'manual'] diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000000..2006a247b1 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,222 @@ +# +# Copyright 2020 Autodesk +# +# 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. +# +cmake_minimum_required(VERSION 3.13...3.17) + +project(maya-usd) + +#------------------------------------------------------------------------------ +# options +#------------------------------------------------------------------------------ +option(BUILD_TESTS "Build tests." ON) +option(BUILD_STRICT_MODE "Enforce all warnings as errors." ON) +option(BUILD_SHARED_LIBS "Build libraries as shared or static." ON) +option(BUILD_WITH_PYTHON_3 "Build with python 3." OFF) +if(APPLE) + option(BUILD_UB2 "Build Universal Binary 2 (UB2) Intel64+arm64" OFF) +endif() +set(BUILD_WITH_PYTHON_3_VERSION 3.7 CACHE STRING "The version of Python 3 to build with") +option(CMAKE_WANT_MATERIALX_BUILD "Enable building with MaterialX (experimental)." OFF) + +set(PXR_OVERRIDE_PLUGINPATH_NAME PXR_PLUGINPATH_NAME + CACHE STRING "Name of env var USD searches to find plugins") + +#------------------------------------------------------------------------------ +# internal flags to control build +#------------------------------------------------------------------------------ +# MAYAHYDRA_TO_USD_RELATIVE_PATH : Set this variable to any relative path from install +# folder to USD location. If defined will set relative +# rpaths for USD libraries. + +#------------------------------------------------------------------------------ +# global options +#------------------------------------------------------------------------------ +# Avoid noisy install messages +set(CMAKE_INSTALL_MESSAGE "NEVER") + +if(APPLE) + if(BUILD_UB2) + message(STATUS "Building with Universal Binary 2") + set(CMAKE_OSX_ARCHITECTURES "x86_64;arm64") + set(CMAKE_OSX_DEPLOYMENT_TARGET 11.0) + else() + set(CMAKE_OSX_ARCHITECTURES "x86_64") + set(CMAKE_OSX_DEPLOYMENT_TARGET 10.14) + endif() +endif() + +set(CMAKE_MODULE_PATH + ${CMAKE_MODULE_PATH} + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules +) + +# Use RUNPATH instead of RPATH for all shared libs, executables and modules on Linux +if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") # IS_LINUX not yet defined + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--enable-new-dtags") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--enable-new-dtags") + set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--enable-new-dtags") +endif() + +#------------------------------------------------------------------------------ +# modules and definitions +#------------------------------------------------------------------------------ +include(cmake/utils.cmake) +find_package(Maya 2024 REQUIRED) + +if(APPLE AND BUILD_UB2 AND NOT MAYA_MACOSX_BUILT_WITH_UB2) + message(WARNING "Maya was NOT built with Universal Binary 2") +endif() + +include(cmake/mayahydra_version.info) +set(MAYAHYDRA_VERSION "${MAYAHYDRA_MAJOR_VERSION}.${MAYAHYDRA_MINOR_VERSION}.${MAYAHYDRA_PATCH_LEVEL}") +if(DEFINED ENV{PLUGIN_CUT_ID}) + set(MAYAHYDRA_CUT_ID $ENV{PLUGIN_CUT_ID}) +endif() + +include(cmake/flowViewport_version.info) +set(FLOWVIEWPORT_VERSION "${FLOWVIEWPORT_MAJOR_VERSION}.${FLOWVIEWPORT_MINOR_VERSION}.${FLOWVIEWPORT_PATCH_LEVEL}") + +if (DEFINED PYTHON_INCLUDE_DIR AND DEFINED PYTHON_LIBRARIES AND DEFINED Python_EXECUTABLE) + SET(PYTHON_INCLUDE_DIRS "${PYTHON_INCLUDE_DIR}") + SET(PYTHONLIBS_FOUND TRUE) + # Use the Python module to find the python lib. + if(BUILD_WITH_PYTHON_3) + find_package(Python ${BUILD_WITH_PYTHON_3_VERSION} EXACT REQUIRED COMPONENTS Interpreter) + else() + find_package(Python 2.7 EXACT REQUIRED COMPONENTS Interpreter) + endif() + if(NOT Python_Interpreter_FOUND) + set(PYTHONLIBS_FOUND FALSE) + endif() +endif() +if (NOT PYTHONLIBS_FOUND) + include(cmake/python.cmake) +endif() +message(STATUS "Build mayaHydra with Python3 = " ${BUILD_WITH_PYTHON_3}) +message(STATUS " PYTHON_INCLUDE_DIRS = ${PYTHON_INCLUDE_DIRS}") +message(STATUS " PYTHON_LIBRARIES = ${PYTHON_LIBRARIES}") +message(STATUS " Python_EXECUTABLE = ${Python_EXECUTABLE}") + +include(cmake/jinja.cmake) + +find_package(USD 0.22.11 REQUIRED) +if (CMAKE_WANT_MATERIALX_BUILD) + # Requires at least USD 22.11 for hdMtlx module and USD must have been built with MaterialX: + if(NOT TARGET hdMtlx) + set(CMAKE_WANT_MATERIALX_BUILD OFF) + message(WARNING "Disabling MaterialX VP2 rendering: it is not supported by this USD package.") + endif() +endif() +include(cmake/usd.cmake) + +option(BUILD_MAYAHYDRALIB "Build the Maya-To-Hydra plugin and scene delegate." ON) + +find_package(UFE REQUIRED) +if(UFE_FOUND) + message(STATUS "Building with UFE ${UFE_VERSION}.") +else() + message(STATUS "UFE not found.") +endif() + +if(CMAKE_WANT_MATERIALX_BUILD AND MAYA_LIGHTAPI_VERSION LESS 2) + set(CMAKE_WANT_MATERIALX_BUILD OFF) + message(WARNING "Disabling MaterialX VP2 rendering: it is not supported by this version of Maya.") +endif() + +if(TARGET hdMtlx) + # MaterialX was built into USD. We need to know where to find MaterialX targets. + find_package(MaterialX REQUIRED) + if(TARGET MaterialXCore) + message(STATUS "Found MaterialX") + endif() +endif() + +if(MAYA_APP_VERSION VERSION_GREATER 2024) + # Look for Qt6 in the Maya devkit. + # The Qt6 archive in the Maya devkit contains everything needed for the normal cmake find_package. + set(CMAKE_PREFIX_PATH "${MAYA_DEVKIT_LOCATION}/Qt") + set(WANT_QT_VERSION 6.5) + find_package(Qt6 ${WANT_QT_VERSION} COMPONENTS Core Widgets QUIET) + if (Qt6_FOUND) + message(STATUS "Found Qt ${Qt6_VERSION} in Maya devkit. Hydra Scene Browser will be built.") + set(BUILD_HDSB_PLUGIN TRUE) + else() + message(WARNING "Could not find Qt ${WANT_QT_VERSION} in Maya devkit directory: ${MAYA_DEVKIT_LOCATION}. \ + You must extract Qt.tar.gz. Hydra Scene Browser will not be built.") + endif() +else() + message(WARNING "Maya ${MAYA_APP_VERSION} does not contain Qt6 in its devkit. \ + Hydra Scene Browser will not be built.") +endif() + +#------------------------------------------------------------------------------ +# compiler configuration +#------------------------------------------------------------------------------ +include(cmake/compiler_config.cmake) + +#------------------------------------------------------------------------------ +# gulrak filesystem +#------------------------------------------------------------------------------ +include(cmake/gulrak.cmake) + +#------------------------------------------------------------------------------ +# test +#------------------------------------------------------------------------------ +if (BUILD_TESTS) + include(cmake/googletest.cmake) + include(cmake/test.cmake) + fetch_googletest() + enable_testing() + add_subdirectory(test) +endif() + +#------------------------------------------------------------------------------ +# lib +#------------------------------------------------------------------------------ +add_subdirectory(lib) + +if(BUILD_HDSB_PLUGIN) + add_subdirectory(lib/adskHydraSceneBrowser) +endif() + +#------------------------------------------------------------------------------ +# plugin +#------------------------------------------------------------------------------ +if(BUILD_HDSB_PLUGIN) + add_subdirectory(plugin/mayaHydraSceneBrowser) + if (BUILD_TESTS) + add_subdirectory(plugin/mayaHydraSceneBrowserTest) + endif() +endif() + +#------------------------------------------------------------------------------ +# install +#------------------------------------------------------------------------------ +if (DEFINED MAYAHYDRA_TO_USD_RELATIVE_PATH) + set(USD_INSTALL_LOCATION "${CMAKE_INSTALL_PREFIX}/${MAYAHYDRA_TO_USD_RELATIVE_PATH}") +else() + set(USD_INSTALL_LOCATION ${PXR_USD_LOCATION}) +endif() + +#------------------------------------------------------------------------------ +# Maya module files +#------------------------------------------------------------------------------ +if (IS_WINDOWS) + configure_file("modules/mayaHydra_Win.mod.template" ${PROJECT_BINARY_DIR}/mayaHydra.mod) +else() + configure_file("modules/mayaHydra.mod.template" ${PROJECT_BINARY_DIR}/mayaHydra.mod) +endif() + +install(FILES ${PROJECT_BINARY_DIR}/mayaHydra.mod DESTINATION ${CMAKE_INSTALL_PREFIX}) diff --git a/README.md b/README.md new file mode 100644 index 0000000000..a3c1d2b669 --- /dev/null +++ b/README.md @@ -0,0 +1,80 @@ +# Hydra for Maya (Technology Preview) + +The _Hydra for Maya_ project creates a Maya plugin that replaces the main Maya viewport with a Hydra viewer. _Hydra for Maya_ is developed and maintained by Autodesk. The project and this documentation are a work-in-progress and under active development. The contents of this repository are fully open source and open to contributions under the [Apache license](./doc/LICENSE.md)! + +Hydra is the rendering API included with [Pixar's USD](http://openusd.org/). + +## Motivation + +The goal for _Hydra for Maya_ is to introduce Hydra as an open source viewport framework for Maya to extend the viewport render engine through Hydra render delegates. _Hydra for Maya_ uses the previous Maya to Hydra (MtoH) code, which is part of MayaUSD, as a foundation to build on. You can find more details on what changed from MtoH [here](./doc/mayaHydraDetails.md). +With _Hydra for Maya_ we are fully leveraging the new [SceneIndex API (aka Hydra v2)](https://openusd.org/release/api/class_hd_scene_index_base.html#details) for more flexibility and customizability over the Hydra scene graph. Therefore the HdSceneDelegate is *not supported* by this plugin. + +_Hydra for Maya_ is currently a Technology Preview; we are just laying the foundation and there is still work ahead. As the plugin evolves and the Hydra technology matures, you can expect changes to API and to face limitations. + +## What's it good for? + +This project allows you to use Hydra (the pluggable USD render ecosystem) +and Storm (the OpenGL renderer for Hydra, bundled with USD), as an +alternative to Viewport 2.0. + +Using Hydra has big benefits for offline renderers: any renderer that +implements a Hydra render delegate can now have an interactive render viewport +in Maya, along with support for render proxies. + +As an example, when paired with +[arnold-usd](https://github.com/Autodesk/arnold-usd) (Arnold's USD plugin + +render delegate), it provides an Arnold render of the viewport, where both maya +objects and USD objects (through proxies) can be modified interactively. + +This could also be particularly useful for newer renderers, like Radeon +ProRender (which already has a +[render delegate](https://github.com/GPUOpen-LibrariesAndSDKs/RadeonProRenderUSD)), +or in-house renderers, as an easier means of implementing an interactive +render viewport. + +What about HdStorm, Hydra's OpenGL renderer? Why would you want to use HdStorm +instead of "normal" Viewport 2.0, given that there are other methods for displaying +USD proxies in Viewport 2.0? Some potential reasons include: + +- Using HdStorm gives lighting and shading between Hydra-enabled applications: + Maya, Katana, usdview, etc +- HdStorm is open source: you can add core features as you need them +- HdStorm is extensible: you can create plugins for custom objects, which then allows + them to be displayed not just in Maya, but any Hydra-enabled application + +## Known Limitations + +- Only direct texture inputs are supported for Maya materials +- Limited support for Maya shader networks +- Drawing issues with selection and highlighting +- Hydra shading differs from Maya's Viewport 2.0 +- Animation Ghosting has the wrong shading +- Current limitations with USD prims: + - Maya layers don't show effect + - Isolate Select only functions with Maya nodes + - Viewport display modes like Xray, wireframe or default shading do not function + - Selection highlighting not showing + - Gprims currently don't cast shadows in Storm +- No material bindings on GeomSubsets (Hydra v2 limitation) + +## Currently not supported + +- Backface culling +- Screen space effects like depth of field and motion blur +- Arnold lights except the aiSkyDomeLight +- Following Maya node types: + - Bifrost + - nParticles + - Fluid +- Blue Pencil +- Maya's volume and ambient light +- Hardware Fog +- Maya's procedural textures (e.g. noise or pattern) + +## Detailed Documentation + ++ [Contributing](./doc/CONTRIBUTING.md) ++ [Building the mayaHydra.mll plugin](./doc/build.md) ++ [Coding guidelines](./doc/codingGuidelines.md) ++ [License](./doc/LICENSE.md) ++ [Plugin documentation](./README_DOC.md) \ No newline at end of file diff --git a/README_DOC.md b/README_DOC.md new file mode 100644 index 0000000000..4017936ca2 --- /dev/null +++ b/README_DOC.md @@ -0,0 +1,4 @@ +## Plugin Documentation ++ [Technical details of Hydra for Maya](./doc/mayaHydraDetails.md) ++ [Selection in Hydra for Maya](./doc/mayaHydraSelection.md) ++ [Selection Highlighting Architecture](./doc/selectionHighlightingArchitecture.md) \ No newline at end of file diff --git a/build.py b/build.py new file mode 100755 index 0000000000..2181ce9f27 --- /dev/null +++ b/build.py @@ -0,0 +1,754 @@ +#!/usr/bin/env python + +# Copyright 2023 Autodesk +# +# 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. +# + +from __future__ import print_function + +from distutils.spawn import find_executable +from glob import glob + +import argparse +import contextlib +import codecs +import datetime +import distutils +import distutils.util +import multiprocessing +import os +import platform +import re +import shlex +import shutil +import subprocess +import sys +import tarfile +import time +import zipfile + +############################################################ +# Helpers for printing output +verbosity = 1 + +def Print(msg): + if verbosity > 0: + print(msg) + +def PrintWarning(warning): + if verbosity > 0: + print("WARNING:", warning) + +def PrintStatus(status): + if verbosity >= 1: + print("STATUS:", status) + +def PrintInfo(info): + if verbosity >= 2: + print("INFO:", info) + +def PrintCommandOutput(output): + if verbosity >= 3: + sys.stdout.write(output) + +def PrintError(error): + if verbosity >= 3 and sys.exc_info()[1] is not None: + import traceback + traceback.print_exc() + print("ERROR:", error) + +def Python3(): + return sys.version_info.major == 3 +############################################################ +def Windows(): + return platform.system() == "Windows" + +def Linux(): + return platform.system() == "Linux" + +def MacOS(): + return platform.system() == "Darwin" + +def GetCommandOutput(command): + """Executes the specified command and returns output or None.""" + try: + return subprocess.check_output( + shlex.split(command), stderr=subprocess.STDOUT).strip() + except subprocess.CalledProcessError: + pass + return None + +def GetGitHeadInfo(context): + """Returns HEAD commit id and date.""" + try: + with CurrentWorkingDirectory(context.mayaHydraSrcDir): + commitSha = subprocess.check_output('git rev-parse HEAD', shell = True).decode() + commitDate = subprocess.check_output('git show -s HEAD --format="%ad"', shell = True).decode() + return commitSha, commitDate + except Exception as exp: + PrintError("Failed to run git commands : {exp}".format(exp=exp)) + sys.exit(1) + +def GetXcodeDeveloperDirectory(): + """Returns the active developer directory as reported by 'xcode-select -p'. + Returns None if none is set.""" + if not MacOS(): + return None + + return GetCommandOutput("xcode-select -p") + +def GetVisualStudioCompilerAndVersion(): + """Returns a tuple containing the path to the Visual Studio compiler + and a tuple for its version, e.g. (14, 0). If the compiler is not found + or version number cannot be determined, returns None.""" + if not Windows(): + return None + + msvcCompiler = find_executable('cl') + if msvcCompiler: + # VisualStudioVersion environment variable should be set by the + # Visual Studio Command Prompt. + match = re.search( + r"(\d+)\.(\d+)", + os.environ.get("VisualStudioVersion", "")) + if match: + return (msvcCompiler, tuple(int(v) for v in match.groups())) + return None + +def IsVisualStudioVersionOrGreater(desiredVersion): + if not Windows(): + return False + + msvcCompilerAndVersion = GetVisualStudioCompilerAndVersion() + if msvcCompilerAndVersion: + _, version = msvcCompilerAndVersion + return version >= desiredVersion + return False + +def IsVisualStudio2022OrGreater(): + VISUAL_STUDIO_2022_VERSION = (17, 0) + return IsVisualStudioVersionOrGreater(VISUAL_STUDIO_2022_VERSION) + +def IsVisualStudio2019OrGreater(): + VISUAL_STUDIO_2019_VERSION = (16, 0) + return IsVisualStudioVersionOrGreater(VISUAL_STUDIO_2019_VERSION) + +def IsVisualStudio2017OrGreater(): + VISUAL_STUDIO_2017_VERSION = (15, 0) + return IsVisualStudioVersionOrGreater(VISUAL_STUDIO_2017_VERSION) + +def GetCPUCount(): + try: + return multiprocessing.cpu_count() + except NotImplementedError: + return 1 + +def Run(context, cmd): + """Run the specified command in a subprocess.""" + PrintInfo('Running "{cmd}"'.format(cmd=cmd)) + + with codecs.open(context.logFileLocation, "a", "utf-8") as logfile: + logfile.write("#####################################################################################" + "\n") + logfile.write("log date: " + datetime.datetime.now().strftime("%Y-%m-%d %H:%M") + "\n") + commitID,commitData = GetGitHeadInfo(context) + logfile.write("commit sha: " + commitID) + logfile.write("commit date: " + commitData) + logfile.write("#####################################################################################" + "\n") + logfile.write("\n") + logfile.write(cmd) + logfile.write("\n") + + # Let exceptions escape from subprocess calls -- higher level + # code will handle them. + if context.redirectOutstreamFile: + p = subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + encoding = sys.stdout.encoding or "UTF-8" + while True: + l = p.stdout.readline().decode(encoding) + if l != "": + # Avoid "UnicodeEncodeError: 'ascii' codec can't encode + # character" errors by serializing utf8 byte strings. + logfile.write(l) + PrintCommandOutput(l) + elif p.poll() is not None: + break + else: + p = subprocess.Popen(shlex.split(cmd)) + p.wait() + + if p.returncode != 0: + # If verbosity >= 3, we'll have already been printing out command output + # so no reason to print the log file again. + if verbosity < 3: + with open(context.logFileLocation, "r") as logfile: + Print(logfile.read()) + raise RuntimeError("Failed to run '{cmd}'\nSee {log} for more details." + .format(cmd=cmd, log=os.path.abspath(context.logFileLocation))) + +def BuildVariant(context): + if context.buildDebug: + return "Debug" + elif context.buildRelease: + return "Release" + elif context.buildRelWithDebug: + return "RelWithDebInfo" + return "RelWithDebInfo" + +def FormatMultiProcs(numJobs, generator): + tag = "-j" + if generator: + if "Visual Studio" in generator: + tag = "/M:" + elif "Xcode" in generator: + tag = "-j " + + return "{tag}{procs}".format(tag=tag, procs=numJobs) + +def onerror(func, path, exc_info): + """ + If the error is due to an access error (read only file) + add write permission and then retries. + If the error is for another reason it re-raises the error. + """ + import stat + if not os.access(path, os.W_OK): + os.chmod(path, stat.S_IWUSR) + func(path) + else: + raise + +def StartBuild(): + global start_time + start_time = time.time() + +def StopBuild(): + end_time = time.time() + elapsed_seconds = end_time - start_time + hours, remainder = divmod(elapsed_seconds, 3600) + minutes, seconds = divmod(remainder, 60) + print("Elapsed time: {:02}:{:02}:{:02}".format(int(hours), int(minutes), int(seconds))) + +############################################################ +# contextmanager +@contextlib.contextmanager +def CurrentWorkingDirectory(dir): + """Context manager that sets the current working directory to the given + directory and resets it to the original directory when closed.""" + curdir = os.getcwd() + os.chdir(dir) + try: + yield + finally: + os.chdir(curdir) + +############################################################ +# CMAKE +def RunCMake(context, extraArgs=None, stages=None): + """Invoke CMake to configure, build, and install a library whose + source code is located in the current working directory.""" + + srcDir = os.getcwd() + instDir = context.instDir + buildDir = context.buildDir + + if 'clean' in stages and os.path.isdir(buildDir): + shutil.rmtree(buildDir, onerror=onerror) + + if 'clean' in stages and os.path.isdir(instDir): + shutil.rmtree(instDir) + + if not os.path.isdir(buildDir): + os.makedirs(buildDir) + + generator = context.cmakeGenerator + + # On Windows, we need to explicitly specify the generator to ensure we're + # building a 64-bit project. (Surely there is a better way to do this?) + # TODO: figure out exactly what "vcvarsall.bat x64" sets to force x64 + if generator is None and Windows(): + if IsVisualStudio2022OrGreater(): + generator = "Visual Studio 17 2022" + elif IsVisualStudio2019OrGreater(): + generator = "Visual Studio 16 2019" + elif IsVisualStudio2017OrGreater(): + generator = "Visual Studio 15 2017 Win64" + else: + generator = "Visual Studio 14 2015 Win64" + + if generator is not None: + generator = '-G "{gen}"'.format(gen=generator) + + if generator and 'Visual Studio' in generator and IsVisualStudio2019OrGreater(): + generator = generator + " -A x64" + + # get build variant + variant= BuildVariant(context) + + with CurrentWorkingDirectory(buildDir): + # recreate build_log.txt everytime the script runs + if os.path.isfile(context.logFileLocation): + os.remove(context.logFileLocation) + + if 'configure' in stages: + Run(context, + 'cmake ' + '-DCMAKE_INSTALL_PREFIX="{instDir}" ' + '-DCMAKE_BUILD_TYPE={variant} ' + '-DCMAKE_EXPORT_COMPILE_COMMANDS=ON ' + '{generator} ' + '{extraArgs} ' + '"{srcDir}"' + .format(instDir=instDir, + variant=variant, + srcDir=srcDir, + generator=(generator or ""), + extraArgs=(" ".join(extraArgs) if extraArgs else ""))) + + installArg = "" + if 'install' in stages: + installArg = "--target install" + + if 'build' in stages or 'install' in stages: + Run(context, "cmake --build . --config {variant} {installArg} -- {multiproc}" + .format(variant=variant, + installArg=installArg, + multiproc=FormatMultiProcs(context.numJobs, generator))) + +def RunCTest(context, extraArgs=None): + buildDir = context.buildDir + variant = BuildVariant(context) + + with CurrentWorkingDirectory(buildDir): + Run(context, + 'ctest ' + '--output-on-failure ' + '--timeout 500 ' + '-C {variant} ' + '{extraArgs} ' + .format(variant=variant, + extraArgs=(" ".join(extraArgs) if extraArgs else ""))) + +def RunMakeZipArchive(context): + installDir = context.instDir + buildDir = context.buildDir + pkgDir = context.pkgDir + variant = BuildVariant(context) + + # extract version from mayahydra_version.info + mayaHydraVersion = [] + cmakeInfoDir = os.path.join(context.mayaHydraSrcDir, 'cmake') + filename = os.path.join(cmakeInfoDir, 'mayahydra_version.info') + with open(filename, 'r') as filehandle: + content = filehandle.readlines() + for current_line in content: + digitList = re.findall(r'\d+', current_line) + versionStr = ''.join(str(e) for e in digitList) + mayaHydraVersion.append(versionStr) + + majorVersion = mayaHydraVersion[0] + minorVersion = mayaHydraVersion[1] + patchLevel = mayaHydraVersion[2] + + pkgName = 'MayaHydra' + '-' + majorVersion + '.' + minorVersion + '.' + patchLevel + '-' + (platform.system()) + '-' + variant + with CurrentWorkingDirectory(buildDir): + shutil.make_archive(pkgName, 'zip', installDir) + + # copy zip file to package directory + if not os.path.exists(pkgDir): + os.makedirs(pkgDir) + + for file in os.listdir(buildDir): + if file.endswith(".zip"): + zipFile = os.path.join(buildDir, file) + try: + shutil.copy(zipFile, pkgDir) + except Exception as exp: + PrintError("Failed to write to directory {pkgDir} : {exp}".format(pkgDir=pkgDir,exp=exp)) + sys.exit(1) + +def SetupMayaQt(context): + def haveQtHeaders(rootPath): + if os.path.exists(rootPath): + # MayaHydra uses these components from Qt (so at a minimum we must find them). + qtComponentsToFind = ['QtCore', 'QtWidgets'] + # Qt6 includes the entire Qt in a single zip file, which when extracted ends in folder 'Qt'. + startDir = os.path.join(rootPath, 'Qt', 'include') if os.path.exists(os.path.join(rootPath, 'Qt')) else os.path.join(rootPath, 'include') + for root,dirs,files in os.walk(startDir): + if 'qt' not in root.lower() or not files: + continue + if not any(root.endswith(qtComp) for qtComp in qtComponentsToFind): + # Skip any folders that aren't the components we are looking for. + continue + + for qtComp in qtComponentsToFind[:]: # Loop over slice copy as we remove items + if qtComp in root and '{comp}version.h'.format(comp=qtComp.lower()) in files: + qtComponentsToFind.remove(qtComp) + PrintInfo('Found {comp} in {dir}'.format(comp=qtComp, dir=root)) + break # Once we've found (and removed) a component, move to the next os.walk + + if not qtComponentsToFind: # Once we've found them all, we are done. + return True + + def safeTarfileExtract(members): + """Use a function to look for bad paths in the tarfile archive to fix + security/bandit B202: tarfile_unsafe_members.""" + + def isBadPath(path, base): + return not os.path.realpath(os.path.abspath(os.path.join(base, path))).startswith(base) + def isBadLink(info, base): + # Links are interpreted relative to the directory containing the link. + tip = os.path.realpath(os.path.abspath(os.path.join(base, os.path.dirname(info.name)))) + return isBadPath(info.linkname, base=tip) + + base = os.path.realpath(os.path.abspath('.')) + result = [] + for finfo in members: + # If any bad paths for links are found in the tarfile, print an error + # and don't extract anything from tarfile. + if isBadPath(finfo.name, base): + PrintError('Found illegal path {path} in tarfile, blocking tarfile extraction.'.format(path=finfo.name)) + return [] + elif (finfo.issym() or finfo.islnk()) and isBadLink(finfo, base): + PrintError('Found illegal link {link} in tarfile, blocking tarfile extraction.'.format(link=finfo.linkname)) + return [] + else: + result.append(finfo) + return result + + def safeZipfileExtract(zip_file, extract_path='.'): + with zipfile.ZipFile(zip_file, 'r') as zf: + for member in zf.infolist(): + file_path = os.path.realpath(os.path.join(extract_path, member.filename)) + if file_path.startswith(os.path.realpath(extract_path)): + zf.extract(member, extract_path) + + # The list of directories (in order) that we'll search. + dirsToSearch = [context.devkitLocation] + if 'MAYA_DEVKIT_LOCATION' in os.environ: + dirsToSearch.append(os.path.expandvars('$MAYA_DEVKIT_LOCATION')) + dirsToSearch.append(context.mayaLocation) + if 'MAYA_LOCATION' in os.environ: + dirsToSearch.append(os.path.expandvars('$MAYA_LOCATION')) + + # Check if the Qt zip file has been extracted (we need the Qt headers). + for dirToSearch in dirsToSearch: + if haveQtHeaders(dirToSearch): + PrintStatus('Found Maya Qt headers in: {dir}'.format(dir=dirToSearch)) + return + + # Qt6 + # The entire Qt is in a single zip file, which we extract to 'Qt'. + # Then we can simply use find_package(Qt6) on it. + for dirToSearch in dirsToSearch: + # Qt archive was originally named Qt.tar.gz on all platforms. + # Was eventually renamed to Qt.zip (Windows) and Qt.tgz (Linux/Osx). + qtArchiveNames = ['Qt.zip', 'Qt.tar.gz'] if Windows() else ['Qt.tgz', 'Qt.tar.gz'] + for qtArchiveName in qtArchiveNames: + qtArchive = os.path.join(dirToSearch, qtArchiveName) + if os.path.exists(qtArchive): + ext = os.path.splitext(qtArchiveName)[1] + qtZipDirFolder = os.path.dirname(qtArchive) + if os.access(qtZipDirFolder, os.W_OK): + PrintStatus("Could not find Maya Qt6.") + PrintStatus(" Extracting '{zip}' to '{dir}'".format(zip=qtArchive, dir=qtZipDirFolder)) + try: + if ext == '.zip': + safeZipfileExtract(qtArchive, qtZipDirFolder) + else: + archive = tarfile.open(qtArchive, mode='r') + archive.extractall(qtZipDirFolder, members=safeTarfileExtract(archive.getmembers())) + archive.close() + except zipfile.BadZipfile as error: + PrintError(str(error)) + except tarfile.TarError as error: + PrintError(str(error)) + return + +def BuildAndInstall(context, buildArgs, stages): + with CurrentWorkingDirectory(context.mayaHydraSrcDir): + extraArgs = [] + stagesArgs = [] + if context.mayaLocation: + extraArgs.append('-DMAYA_LOCATION="{mayaLocation}"' + .format(mayaLocation=context.mayaLocation)) + + if context.mayaUsdLocation: + extraArgs.append('-DMAYAUSD_LOCATION="{mayaUsdLocation}"' + .format(mayaUsdLocation=context.mayaUsdLocation)) + + if context.pxrUsdLocation: + extraArgs.append('-DPXR_USD_LOCATION="{pxrUsdLocation}"' + .format(pxrUsdLocation=context.pxrUsdLocation)) + + if context.devkitLocation: + extraArgs.append('-DMAYA_DEVKIT_LOCATION="{devkitLocation}"' + .format(devkitLocation=context.devkitLocation)) + + extraArgs += buildArgs + stagesArgs += stages + + RunCMake(context, extraArgs, stagesArgs) + + # Ensure directory structure is created and is writable. + for dir in [context.workspaceDir, context.buildDir, context.instDir]: + try: + if os.path.isdir(dir): + testFile = os.path.join(dir, "canwrite") + open(testFile, "w").close() + os.remove(testFile) + else: + os.makedirs(dir) + except Exception as e: + PrintError("Could not write to directory {dir}. Change permissions " + "or choose a different location to install to." + .format(dir=dir)) + sys.exit(1) + Print("""Success MayaHydra build and install !!!!""") + +def RunTests(context,extraArgs): + RunCTest(context,extraArgs) + Print("""Success running MayaHydra tests !!!!""") + +def Package(context): + RunMakeZipArchive(context) + Print("""Success packaging MayaHydra !!!!""") + Print('Archived package is available in {pkgDir}'.format(pkgDir=context.pkgDir)) + +############################################################ +# ArgumentParser +parser = argparse.ArgumentParser( + formatter_class=argparse.RawDescriptionHelpFormatter) + +parser.add_argument("workspace_location", type=str, + help="Directory where the project use as a workspace to build and install plugin/libraries.") + +parser.add_argument("--generator", type=str, + help=("CMake generator to use when building libraries with " + "cmake")) + +parser.add_argument("-v", "--verbosity", type=int, default=verbosity, + help=("How much output to print while building: 0 = no " + "output; 1 = warnings + status; 2 = info; 3 = " + "command output and tracebacks (default: " + "%(default)s)")) + +parser.add_argument("--build-location", type=str, + help=("Set Build directory " + "(default: /build-location)")) + +parser.add_argument("--install-location", type=str, + help=("Set Install directory " + "(default: /install-location)")) + +parser.add_argument("--maya-location", type=str, + help="Directory where Maya is installed.") + +parser.add_argument("--mayausd-location", type=str, + help="Directory where MayaUsd is installed.") + +parser.add_argument("--pxrusd-location", type=str, + help="Directory where Pixar USD is installed.") + +parser.add_argument("--devkit-location", type=str, + help="Directory where Maya Devkit is installed.") + +varGroup = parser.add_mutually_exclusive_group() +varGroup.add_argument("--build-debug", dest="build_debug", action="store_true", + help="Build in Debug mode (default: %(default)s)") + +varGroup.add_argument("--build-release", dest="build_release", action="store_true", + help="Build in Release mode (default: %(default)s)") + +varGroup.add_argument("--build-relwithdebug", dest="build_relwithdebug", action="store_true", default=True, + help="Build in RelWithDebInfo mode (default: %(default)s)") + +parser.add_argument("--debug-python", dest="debug_python", action="store_true", + help="Define Boost Python Debug if your Python library comes with Debugging symbols (default: %(default)s).") + +# HYDRA-444 build infrastructure for Hydra Scene Browser Library +parser.add_argument("--qt-location", type=str, + help="DEPRECATED: Qt is found automatically in Maya devkit.") + +parser.add_argument("--build-args", type=str, nargs="*", default=[], + help=("Comma-separated list of arguments passed into CMake when building libraries")) + +parser.add_argument("--ctest-args", type=str, nargs="*", default=[], + help=("Comma-separated list of arguments passed into CTest.(e.g -VV, --output-on-failure)")) + +parser.add_argument("--stages", type=str, nargs="*", default=['clean','configure','build','install'], + help=("Comma-separated list of stages to execute.(possible stages: clean, configure, build, install, test, package)")) + +parser.add_argument("-j", "--jobs", type=int, default=GetCPUCount(), + help=("Number of build jobs to run in parallel. " + "(default: # of processors [{0}])" + .format(GetCPUCount()))) + +parser.add_argument("--redirect-outstream-file", type=distutils.util.strtobool, dest="redirect_outstream_file", default=True, + help="Redirect output stream to a file. Set this flag to false to redirect output stream to console instead.") + +args = parser.parse_args() +verbosity = args.verbosity + +############################################################ +# InstallContext +class InstallContext: + def __init__(self, args): + + # Assume the project's top level cmake is in the current source directory + self.mayaHydraSrcDir = os.path.normpath( + os.path.join(os.path.abspath(os.path.dirname(__file__)))) + + # Build type + # Must be done early, so we can call BuildVariant(self) + self.buildDebug = args.build_debug + self.buildRelease = args.build_release + self.buildRelWithDebug = args.build_relwithdebug + + self.debugPython = args.debug_python + + # Workspace directory + self.workspaceDir = os.path.abspath(args.workspace_location) + + # Build directory + self.buildDir = (os.path.abspath(args.build_location) if args.build_location + else os.path.join(self.workspaceDir, "build", BuildVariant(self))) + + # Install directory + self.instDir = (os.path.abspath(args.install_location) if args.install_location + else os.path.join(self.workspaceDir, "install", BuildVariant(self))) + + # Package directory + self.pkgDir = (os.path.join(self.workspaceDir, "package", BuildVariant(self))) + + # CMake generator + self.cmakeGenerator = args.generator + + # Number of jobs + self.numJobs = args.jobs + if self.numJobs <= 0: + raise ValueError("Number of jobs must be greater than 0") + + # Maya Location + self.mayaLocation = (os.path.abspath(args.maya_location) + if args.maya_location else None) + + # MayaUsd Location + self.mayaUsdLocation = (os.path.abspath(args.mayausd_location) + if args.mayausd_location else None) + + # PXR USD Location + self.pxrUsdLocation = (os.path.abspath(args.pxrusd_location) + if args.pxrusd_location else None) + + # Maya Devkit Location + self.devkitLocation = (os.path.abspath(args.devkit_location) + if args.devkit_location else None) + + # DEPRECATED: Qt Location + if args.qt_location: + PrintWarning("--qt-location flag is deprecated as Qt is found automatically in Maya devkit.") + + # Log File Name + logFileName="build_log.txt" + self.logFileLocation=os.path.join(self.buildDir, logFileName) + + # Build arguments + self.buildArgs = list() + for argList in args.build_args: + for arg in argList.split(","): + self.buildArgs.append(arg) + + # Stages arguments + self.stagesArgs = list() + for argList in args.stages: + for arg in argList.split(","): + self.stagesArgs.append(arg) + + # CTest arguments + self.ctestArgs = list() + for argList in args.ctest_args: + for arg in argList.split(","): + self.ctestArgs.append(arg) + + # Redirect output stream to file + self.redirectOutstreamFile = args.redirect_outstream_file +try: + context = InstallContext(args) +except Exception as e: + PrintError(str(e)) + sys.exit(1) + +if __name__ == "__main__": + # Summarize + summaryMsg = """ + Building with settings: + Source directory {mayaHydraSrcDir} + Workspace directory {workspaceDir} + Build directory {buildDir} + Install directory {instDir} + Variant {buildVariant} + Python Debug {debugPython} + CMake generator {cmakeGenerator}""" + + if context.redirectOutstreamFile: + summaryMsg += """ + Build Log {logFileLocation}""" + + if context.buildArgs: + summaryMsg += """ + Build arguments {buildArgs}""" + + if context.stagesArgs: + summaryMsg += """ + Stages arguments {stagesArgs}""" + + if context.ctestArgs: + summaryMsg += """ + CTest arguments {ctestArgs}""" + + summaryMsg = summaryMsg.format( + mayaHydraSrcDir=context.mayaHydraSrcDir, + workspaceDir=context.workspaceDir, + buildDir=context.buildDir, + instDir=context.instDir, + logFileLocation=context.logFileLocation, + buildArgs=context.buildArgs, + stagesArgs=context.stagesArgs, + ctestArgs=context.ctestArgs, + buildVariant=BuildVariant(context), + debugPython=("On" if context.debugPython else "Off"), + cmakeGenerator=("Default" if not context.cmakeGenerator + else context.cmakeGenerator) + ) + + Print(summaryMsg) + + # Make sure Qt from Maya devkit is ready. + if 'configure' in context.stagesArgs: + SetupMayaQt(context) + + # BuildAndInstall + if any(stage in ['clean', 'configure', 'build', 'install'] for stage in context.stagesArgs): + StartBuild() + BuildAndInstall(context, context.buildArgs, context.stagesArgs) + StopBuild() + + # Run Tests + if 'test' in context.stagesArgs: + RunTests(context, context.ctestArgs) + + # Package + if 'package' in context.stagesArgs: + Package(context) diff --git a/cmake/compiler_config.cmake b/cmake/compiler_config.cmake new file mode 100644 index 0000000000..d0612c1248 --- /dev/null +++ b/cmake/compiler_config.cmake @@ -0,0 +1,128 @@ +#------------------------------------------------------------------------------ +# compiler flags/definitions +#------------------------------------------------------------------------------ +set(GNU_CLANG_FLAGS + # we want to be as strict as possible + -Wall + $<$:-Werror> + $<$:-fstack-check> + # optimization + -msse4.2 + # disable warnings + -Wno-deprecated + -Wno-deprecated-declarations + -Wno-unused-local-typedefs +) + +if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + list(APPEND GNU_CLANG_FLAGS + -Wrange-loop-analysis + ) +endif() + +set(MSVC_FLAGS + # we want to be as strict as possible + /W3 + $<$:/WX> + # enable two-phase name lookup and other strict checks (binding a non-const reference to a temporary, etc..) + $<$>:/permissive-> + # enable pdb generation. + /Zi + # standards compliant. + /Zc:rvalueCast + # The /Zc:inline option strips out the "arch_ctor_" symbols used for + # library initialization by ARCH_CONSTRUCTOR starting in Visual Studio 2019, + # causing release builds to fail. Disable the option for this and later + # versions. + # + # For more details, see: + # https://developercommunity.visualstudio.com/content/problem/914943/zcinline-removes-extern-symbols-inside-anonymous-n.html + $,/Zc:inline-,/Zc:inline> + # enable multiprocessor builds. + /MP + # enable exception handling. + /EHsc + # enable initialization order as a level 3 warning + /w35038 + # disable warnings + /wd4244 + /wd4267 + /wd4273 + /wd4305 + /wd4506 + /wd4996 + /wd4180 + # exporting STL classes + /wd4251 + # Set some warnings as errors (to make it similar to Linux) + /we4101 + /we4189 +) + +set(MSVC_DEFINITIONS + # Make sure WinDef.h does not define min and max macros which + # will conflict with std::min() and std::max(). + NOMINMAX + + _CRT_SECURE_NO_WARNINGS + _SCL_SECURE_NO_WARNINGS + + # Boost + BOOST_ALL_DYN_LINK + BOOST_CONFIG_SUPPRESS_OUTDATED_MESSAGE + + # Needed to prevent Python from adding a define for snprintf + # since it was added in Visual Studio 2015. + HAVE_SNPRINTF +) + +#------------------------------------------------------------------------------ +# compiler configuration +#------------------------------------------------------------------------------ +# Do not use GNU extension +# Use -std=c++11 instead of -std=gnu++11 +set(CMAKE_CXX_EXTENSIONS OFF) + +function(mayaHydra_compile_config TARGET) + # required compiler feature + # Require C++14 if we're either building for Maya 2019 or later, or if we're building against + # USD 20.05 or later. Otherwise require C++11. + if ((MAYA_APP_VERSION VERSION_GREATER_EQUAL 2019) OR (PXR_VERSION VERSION_GREATER_EQUAL 2005)) + target_compile_features(${TARGET} + PRIVATE + cxx_std_14 + ) + else() + target_compile_features(${TARGET} + PRIVATE + cxx_std_11 + ) + endif() + if(IS_GNU OR IS_CLANG) + target_compile_options(${TARGET} + PRIVATE + ${GNU_CLANG_FLAGS} + ) + if(IS_LINUX) + target_compile_definitions(${TARGET} + PRIVATE + _GLIBCXX_USE_CXX11_ABI=$,1,0> + ) + endif() + elseif(IS_MSVC) + target_compile_options(${TARGET} + PRIVATE + ${MSVC_FLAGS} + ) + target_compile_definitions(${TARGET} + PRIVATE + ${MSVC_DEFINITIONS} + ) + endif() + + # Remove annoying TBB warnings. + target_compile_definitions(${TARGET} + PRIVATE + TBB_SUPPRESS_DEPRECATED_MESSAGES + ) +endfunction() diff --git a/cmake/flowViewport_version.info b/cmake/flowViewport_version.info new file mode 100644 index 0000000000..61f68f8a74 --- /dev/null +++ b/cmake/flowViewport_version.info @@ -0,0 +1,3 @@ +set(FLOWVIEWPORT_MAJOR_VERSION 0) +set(FLOWVIEWPORT_MINOR_VERSION 1) +set(FLOWVIEWPORT_PATCH_LEVEL 0) diff --git a/cmake/googletest.cmake b/cmake/googletest.cmake new file mode 100644 index 0000000000..3775389e81 --- /dev/null +++ b/cmake/googletest.cmake @@ -0,0 +1,93 @@ +macro(fetch_googletest) + + if (NOT GTEST_FOUND) + # First see if we can find a gtest that was downloaded and built. + if (NOT GOOGLETEST_BUILD_ROOT) + set(GOOGLETEST_BUILD_ROOT ${CMAKE_CURRENT_BINARY_DIR}) + endif() + if (NOT GTEST_ROOT) + set(GTEST_ROOT "${GOOGLETEST_BUILD_ROOT}/googletest-install") + endif() + find_package(GTest QUIET) + # At this point GTEST_FOUND is set to True in Release but False in Debug. + endif() + + if (NOT GTEST_FOUND) + #====================================================================== + # Download and unpack googletest at configure time. Adapted from + # + # https://github.com/abseil/googletest/blob/master/googletest/README.md + # + # PPT, 22-Nov-2018. + + # Immediately convert CMAKE_MAKE_PROGRAM to forward slashes (if required). + # Attempting to do so in execute_process fails with string invalid escape + # sequence parsing errors. PPT, 22-Nov-2018. + file(TO_CMAKE_PATH ${CMAKE_MAKE_PROGRAM} CMAKE_MAKE_PROGRAM) + + # Set some options used when compiling googletest. + set(CMAKE_CXX_STANDARD 11) + set(CMAKE_CXX_EXTENSIONS OFF) + set(CMAKE_CXX_STANDARD_REQUIRED ON) + if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") + # In gcc 11 there was a new warning (about uninit variable) in googletest. + # Since we have warnings as errors, this causes build error. + # Simply disable all warnings in googletest since we won't fix them anyways. + # We will just update to newer version, if required. + set(disable_all_warnings_flag -w) + + set(glibcxx_abi -D_GLIBCXX_USE_CXX11_ABI=$,1,0>) + endif() + + if (GOOGLETEST_SRC_DIR) + configure_file(cmake/googletest_src.txt.in ${GOOGLETEST_BUILD_ROOT}/googletest-config/CMakeLists.txt) + else() + configure_file(cmake/googletest_download.txt.in ${GOOGLETEST_BUILD_ROOT}/googletest-config/CMakeLists.txt) + endif() + + message(STATUS "========== Installing GoogleTest... ==========") + execute_process(COMMAND "${CMAKE_COMMAND}" -G "${CMAKE_GENERATOR}" -DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM} . + RESULT_VARIABLE result + WORKING_DIRECTORY ${GOOGLETEST_BUILD_ROOT}/googletest-config ) + if(result) + message(FATAL_ERROR "CMake step for googletest failed: ${result}") + endif() + + execute_process(COMMAND "${CMAKE_COMMAND}" --build . --config ${CMAKE_BUILD_TYPE} + RESULT_VARIABLE result + WORKING_DIRECTORY ${GOOGLETEST_BUILD_ROOT}/googletest-config ) + if(result) + message(FATAL_ERROR "Build step for googletest failed: ${result}") + endif() + message(STATUS "========== ... GoogleTest installed. ==========") + + set(GTEST_ROOT "${GOOGLETEST_BUILD_ROOT}/googletest-install" CACHE PATH "GoogleTest installation root") + endif() + + # FindGTest should get call after GTEST_ROOT is set + find_package(GTest QUIET) + + # https://gitlab.kitware.com/cmake/cmake/issues/17799 + # FindGtest is buggy when dealing with Debug build. + if (CMAKE_BUILD_TYPE MATCHES Debug AND GTEST_FOUND MATCHES FALSE) + # FindGTest.cmake is buggy when looking for only debug config (it expects both). + # So when in debug we set the required gtest vars to the debug libs it would have + # found in the find_package(GTest) above. Then we find again. This will then + # properly set all the vars and import targets for just debug. + set(GTEST_LIBRARY ${GTEST_LIBRARY_DEBUG}) + set(GTEST_MAIN_LIBRARY ${GTEST_MAIN_LIBRARY_DEBUG}) + find_package(GTest QUIET REQUIRED) + endif() + + # On Windows shared libraries are installed in 'bin' instead of 'lib' directory. + if(${CMAKE_SYSTEM_NAME} MATCHES "Windows") + set(GTEST_SHARED_LIB_NAME "gtest.dll") + if(CMAKE_BUILD_TYPE MATCHES Debug) + set(GTEST_SHARED_LIB_NAME "gtestd.dll") + endif() + install(FILES "${GTEST_ROOT}/bin/${GTEST_SHARED_LIB_NAME}" DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/gtest") + else() + install(FILES "${GTEST_LIBRARY}" DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/gtest") + endif() + +endmacro() diff --git a/cmake/googletest_download.txt.in b/cmake/googletest_download.txt.in new file mode 100644 index 0000000000..f0370ec3cc --- /dev/null +++ b/cmake/googletest_download.txt.in @@ -0,0 +1,31 @@ +cmake_minimum_required(VERSION 3.12.0) + +project(googletest-download NONE) + +# Not specifying CONFIGURE_COMMAND, BUILD_COMMAND, or INSTALL_COMMAND means use +# CMake. Specifying these as empty strings means omit the step, which we don't +# want. As per +# https://github.com/abseil/googletest/blob/master/googletest/README.md +# need to force the use of the shared C run-time. + +include(ExternalProject) + +ExternalProject_Add(googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG release-1.10.0 + GIT_CONFIG advice.detachedHead=false + SOURCE_DIR "${GOOGLETEST_BUILD_ROOT}/googletest-src" + BINARY_DIR "${GOOGLETEST_BUILD_ROOT}/googletest-build" + CMAKE_ARGS + "${MAYAUSD_EXTERNAL_PROJECT_GENERAL_SETTINGS}" + "-DCMAKE_INSTALL_PREFIX=${GOOGLETEST_BUILD_ROOT}/googletest-install" + "-Dgtest_force_shared_crt=ON" + "-DBUILD_GMOCK=OFF" + "-DBUILD_SHARED_LIBS=ON" + "-DCMAKE_MACOSX_RPATH=ON" + "-DCMAKE_POSITION_INDEPENDENT_CODE=ON" + "-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}" + "-DCMAKE_CXX_EXTENSIONS=${CMAKE_CXX_EXTENSIONS}" + "-DCMAKE_CXX_STANDARD_REQUIRED=${CMAKE_CXX_STANDARD_REQUIRED}" + "-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS} ${disable_all_warnings_flag} ${glibcxx_abi}" +) diff --git a/cmake/googletest_src.txt.in b/cmake/googletest_src.txt.in new file mode 100644 index 0000000000..aff6d8e55b --- /dev/null +++ b/cmake/googletest_src.txt.in @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 3.12.0) + +project(googletest-download NONE) + +# Not specifying CONFIGURE_COMMAND, BUILD_COMMAND, or INSTALL_COMMAND means use +# CMake. Specifying a XXX_COMMAND as empty string means omit the step. +# Need to force the use of the shared C run-time. + +include(ExternalProject) + +ExternalProject_Add(googletest + DOWNLOAD_COMMAND "" + UPDATE_COMMAND "" + SOURCE_DIR "${GOOGLETEST_SRC_DIR}" + BINARY_DIR "${GOOGLETEST_BUILD_ROOT}/googletest-build" + CMAKE_ARGS + "${MAYAUSD_EXTERNAL_PROJECT_GENERAL_SETTINGS}" + "-DCMAKE_INSTALL_PREFIX=${GOOGLETEST_BUILD_ROOT}/googletest-install" + "-Dgtest_force_shared_crt=ON" + "-DBUILD_GMOCK=OFF" + "-DBUILD_SHARED_LIBS=ON" + "-DCMAKE_MACOSX_RPATH=ON" + "-DCMAKE_POSITION_INDEPENDENT_CODE=ON" + "-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}" + "-DCMAKE_CXX_EXTENSIONS=${CMAKE_CXX_EXTENSIONS}" + "-DCMAKE_CXX_STANDARD_REQUIRED=${CMAKE_CXX_STANDARD_REQUIRED}" + "-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS} ${disable_all_warnings_flag} ${glibcxx_abi}" +) diff --git a/cmake/gulrak.cmake b/cmake/gulrak.cmake new file mode 100644 index 0000000000..be9be69239 --- /dev/null +++ b/cmake/gulrak.cmake @@ -0,0 +1,48 @@ +# +# Copyright 2021 Autodesk +# +# 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(FetchContent) + +set(CONTENT_NAME gulrak) + +set(FETCHCONTENT_QUIET OFF) + +# GULRAK_SOURCE_DIR : Set this to the directory where you have cloned gulrak filesystem repo, +# if you would like to bypass pulling from Github repository via Internet. +if(DEFINED GULRAK_SOURCE_DIR) + file(TO_CMAKE_PATH "${GULRAK_SOURCE_DIR}" GULRAK_SOURCE_DIR) + message(STATUS "**** Building Gulrak From " ${GULRAK_SOURCE_DIR}) + FetchContent_Declare( + ${CONTENT_NAME} + URL ${GULRAK_SOURCE_DIR} + ) + + string(TOUPPER ${CONTENT_NAME} UPPERGULARK) + mark_as_advanced(FETCHCONTENT_SOURCE_DIR_${UPPERGULARK}) + mark_as_advanced(FETCHCONTENT_UPDATES_DISCONNECTED_${UPPERGULARK}) + +else() + message(STATUS "**** Building Gulrak From Github Repository.") + FetchContent_Declare( + ${CONTENT_NAME} + GIT_REPOSITORY https://github.com/gulrak/filesystem.git + GIT_TAG 4e21ab305794f5309a1454b4ae82ab9a0f5e0d25 + USES_TERMINAL_DOWNLOAD TRUE + GIT_CONFIG advice.detachedHead=false + ) +endif() + +FetchContent_MakeAvailable(${CONTENT_NAME}) diff --git a/cmake/jinja.cmake b/cmake/jinja.cmake new file mode 100644 index 0000000000..4aad9d6084 --- /dev/null +++ b/cmake/jinja.cmake @@ -0,0 +1,56 @@ +# +# Copyright 2020 Autodesk +# +# 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. +# +#------------------------------------------------------------------------------ +# +# Gets the Jinja2 and the dependant MarkupSafe python libraries from +# artifactory and set them up. +# +function(init_markupsafe) + + mayaUsd_find_python_module(markupsafe) + + if (NOT MARKUPSAFE_FOUND) + if (NOT MARKUPSAFE_LOCATION) + message(FATAL_ERROR "MARKUPSAFE_LOCATION not set") + endif() + + set(MARKUPSAFE_ROOT "${MARKUPSAFE_LOCATION}/src") + + # Add MarkupSafe to the python path so that Jinja2 can run properly. + mayaUsd_append_path_to_env_var("PYTHONPATH" "${MARKUPSAFE_ROOT}") + endif() + +endfunction() + +function(init_jinja) + + mayaUsd_find_python_module(jinja2) + + if (NOT JINJA2_FOUND) + if (NOT JINJA_LOCATION) + message(FATAL_ERROR "JINJA_LOCATION not set") + endif() + + set(JINJA_ROOT "${JINJA_LOCATION}/src") + + # Add Jinja2 to the python path so that usdGenSchemas can run properly. + mayaUsd_append_path_to_env_var("PYTHONPATH" "${JINJA_ROOT}") + endif() + +endfunction() + +init_markupsafe() +init_jinja() diff --git a/cmake/mayahydra_version.info b/cmake/mayahydra_version.info new file mode 100644 index 0000000000..ce918eb514 --- /dev/null +++ b/cmake/mayahydra_version.info @@ -0,0 +1,3 @@ +set(MAYAHYDRA_MAJOR_VERSION 0) +set(MAYAHYDRA_MINOR_VERSION 5) +set(MAYAHYDRA_PATCH_LEVEL 0) diff --git a/cmake/modules/FindMaya.cmake b/cmake/modules/FindMaya.cmake new file mode 100644 index 0000000000..ffb03d058b --- /dev/null +++ b/cmake/modules/FindMaya.cmake @@ -0,0 +1,495 @@ +# - Maya finder module +# This module searches for a valid Maya instalation. +# It searches for Maya's devkit, libraries, executables +# and related paths (scripts) +# +# Variables that will be defined: +# MAYA_FOUND Defined if a Maya installation has been detected +# MAYA_EXECUTABLE Path to Maya's executable +# MAYA__FOUND Defined if has been found +# MAYA__LIBRARY Path to library +# MAYA_INCLUDE_DIRS Path to the devkit's include directories +# MAYA_API_VERSION Maya version (6-8 digits) +# MAYA_APP_VERSION Maya app version (4 digits) +# MAYA_LIGHTAPI_VERSION Maya light API version (1 or 2 or 3) +# MAYA_PREVIEW_RELEASE_VERSION Preview Release number (3 or more digits) in preview releases, 0 in official releases +# +# Cache variables: +# MAYA_HAS_DEFAULT_MATERIAL_API Presence of a default material API on MRenderItem. +# MAYA_NEW_POINT_SNAPPING_SUPPORT Presence of point new snapping support. +# MAYA_HAS_CRASH_DETECTION Presence of isInCrashHandler API +# MAYA_ENABLE_NEW_PRIM_DELETE Enable new delete behaviour for delete command +# MAYA_HAS_DISPLAY_STYLE_ALL_VIEWPORTS Presence of MFrameContext::getDisplayStyleOfAllViewports. +# MAYA_ARRAY_ITERATOR_DIFFERENCE_TYPE_SUPPORT Presence of maya array iterator difference_type trait +# MAYA_HAS_GET_MEMBER_PATHS Presence of MFnSet::getMemberPaths +# MAYA_HAS_DISPLAY_LAYER_API Presence of MFnDisplayLayer +# MAYA_HAS_NEW_DISPLAY_LAYER_MESSAGING_API Presence of MDisplayLayerMemberChangedFunction +# MAYA_HAS_RENDER_ITEM_HIDE_ON_PLAYBACK_API Presence of MRenderItem has HideOnPlayback API +# MAYA_LINUX_BUILT_WITH_CXX11_ABI Maya Linux was built with new cxx11 ABI. +# MAYA_MACOSX_BUILT_WITH_UB2 Maya OSX was built with Universal Binary 2. + +#============================================================================= +# Copyright 2011-2012 Francisco Requena +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# (To distribute this file outside of CMake, substitute the full +# License text for the above reference.) + +#============================================================================= +# Macro for setting up typical plugin properties. These include: +# - OS-specific plugin suffix (.mll, .so, .bundle) +# - Removal of 'lib' prefix on osx/linux +# - OS-specific defines +# - Post-commnad for correcting Qt library linking on osx +# - Windows link flags for exporting initializePlugin/uninitializePlugin +macro(maya_set_plugin_properties target) + set_target_properties(${target} PROPERTIES + SUFFIX ${MAYA_PLUGIN_SUFFIX}) + + set(_MAYA_DEFINES REQUIRE_IOSTREAM _BOOL) + + if(IS_MACOSX) + set(_MAYA_DEFINES "${_MAYA_DEFINES}" MAC_PLUGIN OSMac_ OSMac_MachO) + set_target_properties(${target} PROPERTIES + PREFIX "") + elseif(WIN32) + set(_MAYA_DEFINES "${_MAYA_DEFINES}" _AFXDLL _MBCS NT_PLUGIN) + set_target_properties( ${target} PROPERTIES + LINK_FLAGS "/export:initializePlugin /export:uninitializePlugin") + else() + set(_MAYA_DEFINES "${_MAYA_DEFINES}" LINUX LINUX_64) + set_target_properties( ${target} PROPERTIES + PREFIX "") + endif() + target_compile_definitions(${target} + PRIVATE + ${_MAYA_DEFINES} + ) +endmacro() +#============================================================================= + +if(IS_MACOSX) + set(MAYA_PLUGIN_SUFFIX ".bundle") +elseif(IS_WINDOWS) + set(MAYA_PLUGIN_SUFFIX ".mll") +else(IS_LINUX) + set(MAYA_PLUGIN_SUFFIX ".so") +endif() + +if(IS_MACOSX) + # On OSX, setting MAYA_LOCATION to either the base installation dir (ie, + # `/Application/Autodesk/maya20xx`), or the Contents folder in the Maya.app dir + # (ie, `/Application/Autodesk/maya20xx/Maya.app/Contents`) are supported. + find_path(MAYA_BASE_DIR + include/maya/MFn.h + HINTS + "${MAYA_LOCATION}/../.." + "$ENV{MAYA_LOCATION}/../.." + "${MAYA_LOCATION}" + "$ENV{MAYA_LOCATION}" + "/Applications/Autodesk/maya2024" + DOC + "Maya installation root directory" + ) + find_path(MAYA_LIBRARY_DIR + libOpenMaya.dylib + HINTS + "${MAYA_LOCATION}" + "$ENV{MAYA_LOCATION}" + "${MAYA_BASE_DIR}" + PATH_SUFFIXES + MacOS/ + Maya.app/Contents/MacOS/ + DOC + "Maya's libraries path" + ) +elseif(IS_LINUX) + find_path(MAYA_BASE_DIR + include/maya/MFn.h + HINTS + "${MAYA_LOCATION}" + "$ENV{MAYA_LOCATION}" + "/usr/autodesk/maya2024-x64" + DOC + "Maya installation root directory" + ) + find_path(MAYA_LIBRARY_DIR + libOpenMaya.so + HINTS + "${MAYA_LOCATION}" + "$ENV{MAYA_LOCATION}" + "${MAYA_BASE_DIR}" + PATH_SUFFIXES + lib/ + DOC + "Maya's libraries path" + ) +elseif(IS_WINDOWS) + find_path(MAYA_BASE_DIR + include/maya/MFn.h + HINTS + "${MAYA_LOCATION}" + "$ENV{MAYA_LOCATION}" + "C:/Program Files/Autodesk/Maya2024" + DOC + "Maya installation root directory" + ) + find_path(MAYA_LIBRARY_DIR + OpenMaya.lib + HINTS + "${MAYA_LOCATION}" + "$ENV{MAYA_LOCATION}" + "${MAYA_BASE_DIR}" + PATH_SUFFIXES + lib/ + DOC + "Maya's libraries path" + ) +endif() + +find_path(MAYA_INCLUDE_DIR + maya/MFn.h + HINTS + "${MAYA_LOCATION}" + "$ENV{MAYA_LOCATION}" + "${MAYA_BASE_DIR}" + PATH_SUFFIXES + ../../devkit/include/ + include/ + DOC + "Maya's headers path" +) + +find_path(MAYA_LIBRARY_DIR + OpenMaya + HINTS + "${MAYA_LOCATION}" + "$ENV{MAYA_LOCATION}" + "${MAYA_BASE_DIR}" + PATH_SUFFIXES + ../../devkit/include/ + include/ + DOC + "Maya's libraries path" +) + +list(APPEND MAYA_INCLUDE_DIRS ${MAYA_INCLUDE_DIR}) + +find_path(MAYA_DEVKIT_INC_DIR + GL/glext.h + HINTS + "${MAYA_LOCATION}" + "$ENV{MAYA_LOCATION}" + "${MAYA_BASE_DIR}" + PATH_SUFFIXES + ../../devkit/plug-ins/ + DOC + "Maya's devkit headers path" +) +if(NOT "${MAYA_DEVKIT_INC_DIR}" STREQUAL "MAYA_DEVKIT_INC_DIR-NOTFOUND") + list(APPEND MAYA_INCLUDE_DIRS ${MAYA_DEVKIT_INC_DIR}) +endif() + +set(MAYA_LIBS_TO_FIND + OpenMaya + OpenMayaAnim + OpenMayaFX + OpenMayaRender + OpenMayaUI + Image + Foundation + IMFbase + cg + cgGL + clew +) +if (CMAKE_BUILD_TYPE MATCHES Debug) + list(APPEND MAYA_LIBS_TO_FIND tbb_debug) +else() + list(APPEND MAYA_LIBS_TO_FIND tbb) +endif() + +foreach(MAYA_LIB ${MAYA_LIBS_TO_FIND}) + find_library(MAYA_${MAYA_LIB}_LIBRARY + ${MAYA_LIB} + HINTS + "${MAYA_LIBRARY_DIR}" + DOC + "Maya's ${MAYA_LIB} library path" + # NO_CMAKE_SYSTEM_PATH needed to avoid conflicts between + # Maya's Foundation library and OSX's framework. + NO_CMAKE_SYSTEM_PATH + ) + + if (MAYA_${MAYA_LIB}_LIBRARY) + list(APPEND MAYA_LIBRARIES ${MAYA_${MAYA_LIB}_LIBRARY}) + endif() +endforeach() + +find_program(MAYA_EXECUTABLE + maya + HINTS + "${MAYA_LOCATION}" + "$ENV{MAYA_LOCATION}" + "${MAYA_BASE_DIR}" + PATH_SUFFIXES + Maya.app/Contents/bin/ + bin/ + DOC + "Maya's executable path" +) + +if(MAYA_INCLUDE_DIRS AND EXISTS "${MAYA_INCLUDE_DIR}/maya/MTypes.h") + # Tease the MAYA_API_VERSION numbers from the lib headers + file(STRINGS ${MAYA_INCLUDE_DIR}/maya/MTypes.h TMP REGEX "#define MAYA_API_VERSION.*$") + string(REGEX MATCHALL "[0-9]+" MAYA_API_VERSION ${TMP}) + + # MAYA_APP_VERSION + file(STRINGS ${MAYA_INCLUDE_DIR}/maya/MTypes.h MAYA_APP_VERSION REGEX "#define MAYA_APP_VERSION.*$") + if(MAYA_APP_VERSION) + string(REGEX MATCHALL "[0-9]+" MAYA_APP_VERSION ${MAYA_APP_VERSION}) + else() + string(SUBSTRING ${MAYA_API_VERSION} "0" "4" MAYA_APP_VERSION) + endif() +endif() + +if(MAYA_INCLUDE_DIRS AND EXISTS "${MAYA_INCLUDE_DIR}/maya/MDefines.h") + file(STRINGS ${MAYA_INCLUDE_DIR}/maya/MDefines.h MAYA_PREVIEW_RELEASE_VERSION REGEX "#define MAYA_PREVIEW_RELEASE_VERSION.*$") + if(MAYA_PREVIEW_RELEASE_VERSION) + string(REGEX MATCHALL "[0-9]+" MAYA_PREVIEW_RELEASE_VERSION ${MAYA_PREVIEW_RELEASE_VERSION}) + else() + set(MAYA_PREVIEW_RELEASE_VERSION 0) + endif() +endif() + +# Determine the Python version and switch between mayapy and mayapy2. +set(MAYAPY_EXE mayapy) +set(MAYA_PY_VERSION 2) +if(${MAYA_APP_VERSION} STRGREATER_EQUAL "2021") + set(MAYA_PY_VERSION 3) + + # check to see if we have a mayapy2 executable + find_program(MAYA_PY_EXECUTABLE2 + mayapy2 + HINTS + "${MAYA_LOCATION}" + "$ENV{MAYA_LOCATION}" + "${MAYA_BASE_DIR}" + PATH_SUFFIXES + Maya.app/Contents/bin/ + bin/ + DOC + "Maya's Python executable path" + ) + if(NOT BUILD_WITH_PYTHON_3 AND MAYA_PY_EXECUTABLE2) + set(MAYAPY_EXE mayapy2) + set(MAYA_PY_VERSION 2) + endif() +endif() + +find_program(MAYA_PY_EXECUTABLE + ${MAYAPY_EXE} + HINTS + "${MAYA_LOCATION}" + "$ENV{MAYA_LOCATION}" + "${MAYA_BASE_DIR}" + PATH_SUFFIXES + Maya.app/Contents/bin/ + bin/ + DOC + "Maya's Python executable path" +) + +set(MAYA_LIGHTAPI_VERSION 1) +if(IS_MACOSX) + set(MAYA_DSO_SUFFIX ".dylib") + set(MAYA_DSO_PREFIX "lib") +elseif(IS_WINDOWS) + set(MAYA_DSO_SUFFIX ".dll") + set(MAYA_DSO_PREFIX "") +else(IS_LINUX) + set(MAYA_DSO_SUFFIX ".so") + set(MAYA_DSO_PREFIX "lib") +endif() +find_file(MAYA_OGSDEVICES_LIBRARY + "${MAYA_DSO_PREFIX}OGSDevices${MAYA_DSO_SUFFIX}" + HINTS + "${MAYA_LIBRARY_DIR}" + "${MAYA_LOCATION}" + PATH_SUFFIXES + lib/ + bin/ + DOC + "Maya's ${MAYA_LIB} library path" + # NO_CMAKE_SYSTEM_PATH needed to avoid conflicts between + # Maya's Foundation library and OSX's framework. + NO_CMAKE_SYSTEM_PATH +) +if (MAYA_OGSDEVICES_LIBRARY) + # Delaying the activation of Light API V2 until the shadow and SSAO issues are fixed. The + # update, to be found in a future PR, will contain this keyword, which is not present in 2022.1: + file(STRINGS ${MAYA_OGSDEVICES_LIBRARY} HAS_LIGHTAPI_2 REGEX "ConnectColorInFragments") + if (HAS_LIGHTAPI_2) + set(MAYA_LIGHTAPI_VERSION 2) + endif() + # In some future Maya updates, there might also be a function to get the ambient light, very + # useful to implement flat shading. + file(STRINGS ${MAYA_OGSDEVICES_LIBRARY} HAS_LIGHTAPI_3 REGEX "AddAmbientLight") + if (HAS_LIGHTAPI_3) + set(MAYA_LIGHTAPI_VERSION 3) + endif() +endif() +message(STATUS "Using Maya Light API Version ${MAYA_LIGHTAPI_VERSION}") + +set(MAYA_HAS_DEFAULT_MATERIAL_API FALSE CACHE INTERNAL "setDefaultMaterialHandling") +if(MAYA_INCLUDE_DIRS AND EXISTS "${MAYA_INCLUDE_DIR}/maya/MHWGeometry.h") + file(STRINGS ${MAYA_INCLUDE_DIR}/maya/MHWGeometry.h MAYA_HAS_API REGEX "setDefaultMaterialHandling") + if(MAYA_HAS_API) + set(MAYA_HAS_DEFAULT_MATERIAL_API TRUE CACHE INTERNAL "setDefaultMaterialHandling") + message(STATUS "Maya has setDefaultMaterialHandling API") + endif() +endif() + +set(MAYA_NEW_POINT_SNAPPING_SUPPORT FALSE CACHE INTERNAL "snapToActive") +if (MAYA_INCLUDE_DIRS AND EXISTS "${MAYA_INCLUDE_DIR}/maya/MSelectionContext.h") + file(STRINGS ${MAYA_INCLUDE_DIR}/maya/MSelectionContext.h MAYA_HAS_API REGEX "snapToActive") + if(MAYA_HAS_API) + set(MAYA_NEW_POINT_SNAPPING_SUPPORT TRUE CACHE INTERNAL "snapToActive") + message(STATUS "Maya has new point snapping API") + endif() +endif() + +set(MAYA_HAS_CRASH_DETECTION FALSE CACHE INTERNAL "isInCrashHandler") +if(MAYA_INCLUDE_DIRS AND EXISTS "${MAYA_INCLUDE_DIR}/maya/MGlobal.h") + file(STRINGS ${MAYA_INCLUDE_DIR}/maya/MGlobal.h MAYA_HAS_API REGEX "isInCrashHandler") + if(MAYA_HAS_API) + set(MAYA_HAS_CRASH_DETECTION TRUE CACHE INTERNAL "isInCrashHandler") + message(STATUS "Maya has isInCrashHandler API") + endif() +endif() + +set(MAYA_ENABLE_NEW_PRIM_DELETE FALSE CACHE INTERNAL "enableNewPrimDelete") +if (MAYA_API_VERSION VERSION_GREATER_EQUAL 20230000) + set(MAYA_ENABLE_NEW_PRIM_DELETE TRUE CACHE INTERNAL "enableNewPrimDelete") +endif() + +set(MAYA_HAS_DISPLAY_STYLE_ALL_VIEWPORTS FALSE CACHE INTERNAL "DisplayStyleOfAllViewports") +if(MAYA_INCLUDE_DIRS AND EXISTS "${MAYA_INCLUDE_DIR}/maya/MFrameContext.h") + file(STRINGS ${MAYA_INCLUDE_DIR}/maya/MFrameContext.h MAYA_HAS_API REGEX "getDisplayStyleOfAllViewports") + if(MAYA_HAS_API) + set(MAYA_HAS_DISPLAY_STYLE_ALL_VIEWPORTS TRUE CACHE INTERNAL "DisplayStyleOfAllViewports") + message(STATUS "Maya has getDisplayStyleOfAllViewports API") + endif() +endif() + +set(MAYA_ARRAY_ITERATOR_DIFFERENCE_TYPE_SUPPORT FALSE CACHE INTERNAL "hasArrayIteratorDifferenceType") +if(MAYA_INCLUDE_DIRS AND EXISTS "${MAYA_INCLUDE_DIR}/maya/MArrayIteratorTemplate.h") + file(STRINGS ${MAYA_INCLUDE_DIR}/maya/MArrayIteratorTemplate.h MAYA_HAS_API REGEX "difference_type") + if(MAYA_HAS_API) + set(MAYA_ARRAY_ITERATOR_DIFFERENCE_TYPE_SUPPORT TRUE CACHE INTERNAL "hasArrayIteratorDifferenceType") + message(STATUS "Maya array iterator has difference_type trait") + endif() +endif() + +set(MAYA_HAS_GET_MEMBER_PATHS FALSE CACHE INTERNAL "hasGetMemberPaths") +if(MAYA_INCLUDE_DIRS AND EXISTS "${MAYA_INCLUDE_DIR}/maya/MFnSet.h") + file(STRINGS ${MAYA_INCLUDE_DIR}/maya/MFnSet.h MAYA_HAS_API REGEX "getMemberPaths") + if(MAYA_HAS_API) + set(MAYA_HAS_GET_MEMBER_PATHS TRUE CACHE INTERNAL "hasGetMemberPaths") + message(STATUS "MFnSet has getMemberPaths function") + endif() +endif() + +set(MAYA_HAS_DISPLAY_LAYER_API FALSE CACHE INTERNAL "hasDisplayLayerAPI") +if(MAYA_INCLUDE_DIRS AND EXISTS "${MAYA_INCLUDE_DIR}/maya/MFnDisplayLayer.h") + set(MAYA_HAS_DISPLAY_LAYER_API TRUE CACHE INTERNAL "hasDisplayLayerAPI") + message(STATUS "MFnDisplayLayer exists") +endif() + +set(MAYA_HAS_NEW_DISPLAY_LAYER_MESSAGING_API FALSE CACHE INTERNAL "hasDisplayLayerMemberChangedFunction") +if(MAYA_INCLUDE_DIRS AND EXISTS "${MAYA_INCLUDE_DIR}/maya/MDisplayLayerMessage.h") + file(STRINGS ${MAYA_INCLUDE_DIR}/maya/MDisplayLayerMessage.h MAYA_HAS_API REGEX "MDisplayLayerMemberChangedFunction") + if(MAYA_HAS_API) + set(MAYA_HAS_NEW_DISPLAY_LAYER_MESSAGING_API TRUE CACHE INTERNAL "hasDisplayLayerMemberChangedFunction") + message(STATUS "MDisplayLayerMessage has MDisplayLayerMemberChangedFunction") + endif() +endif() + +set(MAYA_HAS_RENDER_ITEM_HIDE_ON_PLAYBACK_API FALSE CACHE INTERNAL "hasRenderItemHideOnPlaybackFunction") +if(MAYA_INCLUDE_DIRS AND EXISTS "${MAYA_INCLUDE_DIR}/maya/MHWGeometry.h") + file(STRINGS ${MAYA_INCLUDE_DIR}/maya/MHWGeometry.h MAYA_HAS_API REGEX "isHideOnPlayback") + if(MAYA_HAS_API) + set(MAYA_HAS_RENDER_ITEM_HIDE_ON_PLAYBACK_API TRUE CACHE INTERNAL "hasRenderItemHideOnPlaybackFunction") + message(STATUS "MRenderItem has HideOnPlayback API") + endif() +endif() + +set(MAYA_LINUX_BUILT_WITH_CXX11_ABI FALSE CACHE INTERNAL "MayaLinuxBuiltWithCxx11ABI") +if(IS_LINUX AND MAYA_Foundation_LIBRARY) + # Determine if Maya (on Linux) was built using the new CXX11 ABI. + # If yes, then MayaUsd MUST also be built with new ABI. + execute_process( + COMMAND + nm "${MAYA_Foundation_LIBRARY}" + COMMAND + grep findVariableReplacement + COMMAND + grep " T " + WORKING_DIRECTORY + ${MAYA_LIBRARY_DIR} + OUTPUT_VARIABLE + maya_cxx11_abi) + if (NOT ("${maya_cxx11_abi}" STREQUAL "")) + string(FIND ${maya_cxx11_abi} "__cxx1112basic_string" maya_cxx11_abi_index) + if(NOT (${maya_cxx11_abi_index} STREQUAL "-1")) + set(MAYA_LINUX_BUILT_WITH_CXX11_ABI TRUE CACHE INTERNAL "MayaLinuxBuiltWithCxx11ABI") + message(STATUS "Linux: Maya was built with new cxx11 ABI") + endif() + endif() +endif() + +set(MAYA_MACOSX_BUILT_WITH_UB2 FALSE CACHE INTERNAL "MayaMacOSXBuiltWithUB2") +if(IS_MACOSX AND MAYA_Foundation_LIBRARY) + # Determine if Maya (on OSX) was built with Universal Binary 2 (x86_64 & arm64). + # If yes, then MayaUsd can be built with either: Intel, Arm or both. + execute_process( + COMMAND + lipo -archs "${MAYA_Foundation_LIBRARY}" + WORKING_DIRECTORY + ${MAYA_LIBRARY_DIR} + OUTPUT_VARIABLE + maya_lipo_output) + string(REGEX MATCHALL "(x86_64|arm64)" maya_ub2_match ${maya_lipo_output}) + if (maya_ub2_match) + list(FIND maya_ub2_match "x86_64" ub2_index1) + list(FIND maya_ub2_match "arm64" ub2_index2) + if((NOT (${ub2_index1} STREQUAL "-1")) AND (NOT (${ub2_index2} STREQUAL "-1"))) + set(MAYA_MACOSX_BUILT_WITH_UB2 TRUE CACHE INTERNAL "MayaMacOSXBuiltWithUB2") + message(STATUS "MacOSX: Maya was built with Universal Binary 2 (x86_64/arm64)") + endif() + endif() +endif() + +# handle the QUIETLY and REQUIRED arguments and set MAYA_FOUND to TRUE if +# all listed variables are TRUE +include(FindPackageHandleStandardArgs) + +find_package_handle_standard_args(Maya + REQUIRED_VARS + MAYA_EXECUTABLE + MAYA_PY_EXECUTABLE + MAYA_PY_VERSION + MAYA_INCLUDE_DIRS + MAYA_LIBRARIES + MAYA_API_VERSION + MAYA_APP_VERSION + MAYA_LIGHTAPI_VERSION + VERSION_VAR + MAYA_APP_VERSION +) diff --git a/cmake/modules/FindUFE.cmake b/cmake/modules/FindUFE.cmake new file mode 100644 index 0000000000..ddd1a89674 --- /dev/null +++ b/cmake/modules/FindUFE.cmake @@ -0,0 +1,122 @@ +# +# Simple module to find UFE. +# +# This module searches for a valid UFE installation. +# It searches for UFE's libraries and include header files. +# +# Variables that will be defined: +# UFE_FOUND Defined if a UFE installation has been detected +# UFE_LIBRARY Path to UFE library +# UFE_INCLUDE_DIR Path to the UFE include directory +# UFE_VERSION UFE version (major.minor.patch) from ufe.h +# UFE_LIGHTS_SUPPORT Presence of UFE lights support +# UFE_SCENE_SEGMENT_SUPPORT Presence of UFE scene segment support +# + +find_path(UFE_INCLUDE_DIR + ufe/versionInfo.h + HINTS + $ENV{UFE_INCLUDE_ROOT} + ${UFE_INCLUDE_ROOT} + ${MAYA_DEVKIT_LOCATION} + $ENV{MAYA_DEVKIT_LOCATION} + ${MAYA_LOCATION} + $ENV{MAYA_LOCATION} + ${MAYA_BASE_DIR} + PATH_SUFFIXES + devkit/ufe/include + include/ + DOC + "UFE header path" +) + +# Get the UFE_VERSION and features from ufe.h +if(UFE_INCLUDE_DIR AND EXISTS "${UFE_INCLUDE_DIR}/ufe/ufe.h") + # Parse the file and get the three lines that have the version info. + file(STRINGS + "${UFE_INCLUDE_DIR}/ufe/ufe.h" + _ufe_vers + REGEX "#define[ ]+(UFE_MAJOR_VERSION|UFE_MINOR_VERSION|UFE_PATCH_LEVEL)[ ]+[0-9]+$") + + # Then extract the number from each one. + foreach(_ufe_tmp ${_ufe_vers}) + if(_ufe_tmp MATCHES "#define[ ]+(UFE_MAJOR_VERSION|UFE_MINOR_VERSION|UFE_PATCH_LEVEL)[ ]+([0-9]+)$") + set(${CMAKE_MATCH_1} ${CMAKE_MATCH_2}) + endif() + endforeach() + set(UFE_VERSION ${UFE_MAJOR_VERSION}.${UFE_MINOR_VERSION}.${UFE_PATCH_LEVEL}) + + if("${UFE_MAJOR_VERSION}" STREQUAL "0") + math(EXPR UFE_PREVIEW_VERSION_NUM "${UFE_MINOR_VERSION} * 1000 + ${UFE_PATCH_LEVEL}") + endif() + + file(STRINGS + "${UFE_INCLUDE_DIR}/ufe/ufe.h" + _ufe_features + REGEX "#define UFE_V[0-9]+_FEATURES_AVAILABLE$") + foreach(_ufe_tmp ${_ufe_features}) + if(_ufe_tmp MATCHES "#define UFE_V([0-9]+)_FEATURES_AVAILABLE$") + set(CMAKE_UFE_V${CMAKE_MATCH_1}_FEATURES_AVAILABLE ON) + endif() + endforeach() +endif() + +find_library(UFE_LIBRARY + NAMES + ufe_${UFE_MAJOR_VERSION} + HINTS + $ENV{UFE_LIB_ROOT} + ${UFE_LIB_ROOT} + ${MAYA_DEVKIT_LOCATION} + $ENV{MAYA_DEVKIT_LOCATION} + ${MAYA_LOCATION} + $ENV{MAYA_LOCATION} + ${MAYA_BASE_DIR} + PATHS + ${UFE_LIBRARY_DIR} + PATH_SUFFIXES + devkit/ufe/lib + lib/ + DOC + "UFE library" + NO_DEFAULT_PATH +) + +# Handle the QUIETLY and REQUIRED arguments and set UFE_FOUND to TRUE if +# all listed variables are TRUE. +include(FindPackageHandleStandardArgs) + +find_package_handle_standard_args(UFE + REQUIRED_VARS + UFE_INCLUDE_DIR + UFE_LIBRARY + VERSION_VAR + UFE_VERSION +) + +if(UFE_FOUND) + message(STATUS "UFE include dir: ${UFE_INCLUDE_DIR}") + message(STATUS "UFE library: ${UFE_LIBRARY}") + message(STATUS "UFE version: ${UFE_VERSION}") +endif() + +set(UFE_LIGHTS_SUPPORT FALSE CACHE INTERNAL "ufeLights") +if (UFE_INCLUDE_DIR AND EXISTS "${UFE_INCLUDE_DIR}/ufe/lightHandler.h") + set(UFE_LIGHTS_SUPPORT TRUE CACHE INTERNAL "ufeLights") + message(STATUS "Maya has UFE lights API") +endif() + +set(UFE_SCENE_SEGMENT_SUPPORT FALSE CACHE INTERNAL "ufeSceneSegment") +if (UFE_INCLUDE_DIR AND EXISTS "${UFE_INCLUDE_DIR}/ufe/sceneSegmentHandler.h") + set(UFE_SCENE_SEGMENT_SUPPORT TRUE CACHE INTERNAL "ufeSceneSegment") + message(STATUS "Maya has UFE scene segment API") +endif() + +set(UFE_TRIE_NODE_HAS_CHILDREN_COMPONENTS_ACCESSOR FALSE CACHE INTERNAL "ufeTrieNodeHasChildrenComponentsAccessor") +if(UFE_INCLUDE_DIR AND EXISTS "${UFE_INCLUDE_DIR}/ufe/trie.h") + file(STRINGS ${UFE_INCLUDE_DIR}/ufe/trie.h UFE_HAS_API REGEX "childrenComponents") + if(UFE_HAS_API) + set(UFE_TRIE_NODE_HAS_CHILDREN_COMPONENTS_ACCESSOR TRUE CACHE INTERNAL "ufeTrieNodeHasChildrenComponentsAccessor") + message(STATUS "Maya has UFE TrieNode childrenComponents accessor") + endif() +endif() diff --git a/cmake/modules/FindUSD.cmake b/cmake/modules/FindUSD.cmake new file mode 100644 index 0000000000..955cc983c5 --- /dev/null +++ b/cmake/modules/FindUSD.cmake @@ -0,0 +1,180 @@ +# Simple module to find USD. + +# On a system with an existing USD /usr/local installation added to the system +# PATH, use of PATHS in find_path incorrectly causes the existing USD +# installation to be found. As per +# https://cmake.org/cmake/help/v3.4/command/find_path.html +# and +# https://cmake.org/pipermail/cmake/2010-October/040460.html +# HINTS get searched before system paths, which produces the desired result. +find_path(USD_INCLUDE_DIR + NAMES + pxr/pxr.h + HINTS + ${PXR_USD_LOCATION} + $ENV{PXR_USD_LOCATION} + PATH_SUFFIXES + include + DOC + "USD Include directory" +) + +find_file(USD_GENSCHEMA + NAMES + usdGenSchema + PATHS + ${PXR_USD_LOCATION} + $ENV{PXR_USD_LOCATION} + PATH_SUFFIXES + bin + DOC + "USD Gen schema application" +) + +find_file(USD_CONFIG_FILE + NAMES + pxrConfig.cmake + PATHS + ${PXR_USD_LOCATION} + $ENV{PXR_USD_LOCATION} + DOC "USD cmake configuration file" +) + +# PXR_USD_LOCATION might have come in as an environment variable, and +# it could also have been a hint-list, so we'll make sure we set it to +# wherever we found pxrConfig, which is always the correct location. +get_filename_component(PXR_USD_LOCATION "${USD_CONFIG_FILE}" DIRECTORY) + +include(${USD_CONFIG_FILE}) + +if(DEFINED PXR_VERSION) + # Starting in core USD 21.05, pxrConfig.cmake provides the various USD + # version numbers as CMake variables, in which case PXR_VERSION should have + # been defined, along with the major, minor, and patch version numbers, so + # there is no need to extract them from the pxr/pxr.h header file anymore. + # The only thing we need to do is assemble the USD_VERSION version string. + set(USD_VERSION ${PXR_MAJOR_VERSION}.${PXR_MINOR_VERSION}.${PXR_PATCH_VERSION}) +elseif(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}) + math(EXPR PXR_VERSION "${USD_MAJOR_VERSION} * 10000 + ${USD_MINOR_VERSION} * 100 + ${USD_PATCH_VERSION}") +endif() + +# Note that on Windows with USD <= 0.19.11, USD_LIB_PREFIX should be left at +# default (or set to empty string), even if PXR_LIB_PREFIX was specified when +# building core USD, due to a bug. + +# On all other platforms / versions, it should match the PXR_LIB_PREFIX used +# for building USD (and shouldn't need to be touched if PXR_LIB_PREFIX was not +# used / left at it's default value). Starting with USD 21.11, the default +# value for PXR_LIB_PREFIX was changed to include "usd_". + +if (USD_VERSION VERSION_GREATER_EQUAL "0.21.11") + set(USD_LIB_PREFIX "${CMAKE_SHARED_LIBRARY_PREFIX}usd_" + CACHE STRING "Prefix of USD libraries; generally matches the PXR_LIB_PREFIX used when building core USD") +else() + set(USD_LIB_PREFIX ${CMAKE_SHARED_LIBRARY_PREFIX} + CACHE STRING "Prefix of USD libraries; generally matches the PXR_LIB_PREFIX used when building core USD") +endif() + +if (WIN32) + # ".lib" on Windows + set(USD_LIB_SUFFIX ${CMAKE_STATIC_LIBRARY_SUFFIX} + CACHE STRING "Extension of USD libraries") +else () + # ".so" on Linux, ".dylib" on MacOS + set(USD_LIB_SUFFIX ${CMAKE_SHARED_LIBRARY_SUFFIX} + CACHE STRING "Extension of USD libraries") +endif () + +find_library(USD_LIBRARY + NAMES + ${USD_LIB_PREFIX}usd${USD_LIB_SUFFIX} + HINTS + ${PXR_USD_LOCATION} + $ENV{PXR_USD_LOCATION} + PATH_SUFFIXES + lib + DOC + "Main USD library" +) + +get_filename_component(USD_LIBRARY_DIR ${USD_LIBRARY} DIRECTORY) + +# Get the boost version from the one built with USD +if(USD_INCLUDE_DIR) + file(GLOB _USD_VERSION_HPP_FILE "${USD_INCLUDE_DIR}/boost-*/boost/version.hpp") + list(LENGTH _USD_VERSION_HPP_FILE found_one) + if(${found_one} STREQUAL "1") + list(GET _USD_VERSION_HPP_FILE 0 USD_VERSION_HPP) + file(STRINGS + "${USD_VERSION_HPP}" + _usd_tmp + REGEX "#define BOOST_VERSION .*$") + string(REGEX MATCH "[0-9]+" USD_BOOST_VERSION ${_usd_tmp}) + unset(_usd_tmp) + unset(_USD_VERSION_HPP_FILE) + unset(USD_VERSION_HPP) + endif() +endif() + +# See if MaterialX shaders with color4 inputs exist natively in Sdr: +# Not yet in a tagged USD version: https://github.com/PixarAnimationStudios/USD/pull/1894 +set(USD_HAS_COLOR4_SDR_SUPPORT FALSE CACHE INTERNAL "USD.Sdr.PropertyTypes.Color4") +if (USD_INCLUDE_DIR AND EXISTS "${USD_INCLUDE_DIR}/pxr/usd/sdr/shaderProperty.h") + file(STRINGS ${USD_INCLUDE_DIR}/pxr/usd/sdr/shaderProperty.h USD_HAS_API REGEX "Color4") + if(USD_HAS_API) + set(USD_HAS_COLOR4_SDR_SUPPORT TRUE CACHE INTERNAL "USD.Sdr.PropertyTypes.Color4") + message(STATUS "USD has new Sdr.PropertyTypes.Color4") + endif() +endif() + +# See if MaterialX shaders have full Metadata imported: +# Not yet in a tagged USD version: https://github.com/PixarAnimationStudios/USD/pull/1895 +set(USD_HAS_MX_METADATA_SUPPORT FALSE CACHE INTERNAL "USD.MaterialX.Metadata") +if (USD_LIBRARY_DIR AND EXISTS "${USD_LIBRARY_DIR}/${USD_LIB_PREFIX}usdMtlx${CMAKE_SHARED_LIBRARY_SUFFIX}") + file(STRINGS ${USD_LIBRARY_DIR}/${USD_LIB_PREFIX}usdMtlx${CMAKE_SHARED_LIBRARY_SUFFIX} USD_HAS_API REGEX "uisoftmin") + if(USD_HAS_API) + set(USD_HAS_MX_METADATA_SUPPORT TRUE CACHE INTERNAL "USD.MaterialX.Metadata") + message(STATUS "USD has MaterialX metadata support") + endif() +endif() + +message(STATUS "USD include dir: ${USD_INCLUDE_DIR}") +message(STATUS "USD library dir: ${USD_LIBRARY_DIR}") +message(STATUS "USD version: ${USD_VERSION}") +if(DEFINED USD_BOOST_VERSION) + message(STATUS "USD Boost::boost version: ${USD_BOOST_VERSION}") +endif() + +include(FindPackageHandleStandardArgs) + +find_package_handle_standard_args(USD + REQUIRED_VARS + PXR_USD_LOCATION + USD_INCLUDE_DIR + USD_LIBRARY_DIR + USD_GENSCHEMA + USD_CONFIG_FILE + USD_VERSION + PXR_VERSION + VERSION_VAR + USD_VERSION +) + +find_program(OIIO_idiff_BINARY + idiff + HINTS + ${PXR_USD_LOCATION} + $ENV{PXR_USD_LOCATION} + PATH_SUFFIXES + bin/ + DOC + "OIIO's idiff binary" +) \ No newline at end of file diff --git a/cmake/python.cmake b/cmake/python.cmake new file mode 100644 index 0000000000..31342cde82 --- /dev/null +++ b/cmake/python.cmake @@ -0,0 +1,224 @@ +# - Find python libraries +# This module finds the libraries corresponding to the Python interpreter +# FindPython provides. +# This code sets the following variables: +# +# PYTHONLIBS_FOUND - have the Python libs been found +# PYTHON_PREFIX - path to the Python installation +# PYTHON_LIBRARIES - path to the python library +# PYTHON_INCLUDE_DIRS - path to where Python.h is found +# PYTHON_MODULE_EXTENSION - lib extension, e.g. '.so' or '.pyd' +# PYTHON_MODULE_PREFIX - lib name prefix: usually an empty string +# PYTHON_SITE_PACKAGES - path to installation site-packages +# PYTHON_IS_DEBUG - whether the Python interpreter is a debug build +# +# Thanks to talljimbo for the patch adding the 'LDVERSION' config +# variable usage. + +#============================================================================= +# Copyright 2001-2009 Kitware, Inc. +# Copyright 2012 Continuum Analytics, Inc. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the names of Kitware, Inc., the Insight Software Consortium, +# nor the names of their 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. +#============================================================================= + +# Checking for the extension makes sure that `LibsNew` was found and not just `Libs`. +if(PYTHONLIBS_FOUND AND PYTHON_MODULE_EXTENSION) + return() +endif() + +# On Mac, with Maya 2022+, the Python binaries link against a non-existent path +# maya2022/Maya.app/Contents/Frameworks/Python.framework/Versions/Current/bin/python -> /Library/Frameworks/Python.framework/Versions/3.7/Python +# However, just using the mayapy executable is good enough for the build system +if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + MESSAGE(STATUS "Setting Python_EXECUTABLE to MayaPy ${MAYA_PY_EXECUTABLE}") + set(Python_EXECUTABLE ${MAYA_PY_EXECUTABLE}) +endif() + +# Use the Python module to find the python lib. +if(BUILD_WITH_PYTHON_3) + find_package(Python ${BUILD_WITH_PYTHON_3_VERSION} EXACT REQUIRED COMPONENTS Interpreter) +else() + find_package(Python 2.7 EXACT REQUIRED COMPONENTS Interpreter) +endif() + +if(NOT Python_Interpreter_FOUND) + set(PYTHONLIBS_FOUND FALSE) + return() +endif() + +# According to http://stackoverflow.com/questions/646518/python-how-to-detect-debug-interpreter +# testing whether sys has the gettotalrefcount function is a reliable, cross-platform +# way to detect a CPython debug interpreter. +# +# The library suffix is from the config var LDVERSION sometimes, otherwise +# VERSION. VERSION will typically be like "2.7" on unix, and "27" on windows. +execute_process(COMMAND "${Python_EXECUTABLE}" "-c" + "from distutils import sysconfig as s;import sys;import struct; +print('.'.join(str(v) for v in sys.version_info)); +print(sys.prefix); +print(s.get_python_inc(plat_specific=True)); +print(s.get_python_lib(plat_specific=True)); +print(s.get_config_var('SO')); +print(hasattr(sys, 'gettotalrefcount')+0); +print(struct.calcsize('@P')); +print(s.get_config_var('LDVERSION') or s.get_config_var('VERSION')); +print(s.get_config_var('LIBDIR') or ''); +print(s.get_config_var('MULTIARCH') or ''); +" + RESULT_VARIABLE _PYTHON_SUCCESS + OUTPUT_VARIABLE _PYTHON_VALUES + ERROR_VARIABLE _PYTHON_ERROR_VALUE) + +if(NOT _PYTHON_SUCCESS MATCHES 0) + if(PythonLibsNew_FIND_REQUIRED) + message(FATAL_ERROR + "Python config failure:\n${_PYTHON_ERROR_VALUE}") + endif() + set(PYTHONLIBS_FOUND FALSE) + return() +endif() + +# Convert the process output into a list +string(REGEX REPLACE ";" "\\\\;" _PYTHON_VALUES ${_PYTHON_VALUES}) +string(REGEX REPLACE "\n" ";" _PYTHON_VALUES ${_PYTHON_VALUES}) +list(GET _PYTHON_VALUES 0 _PYTHON_VERSION_LIST) +list(GET _PYTHON_VALUES 1 PYTHON_PREFIX) +list(GET _PYTHON_VALUES 2 PYTHON_INCLUDE_DIR) +list(GET _PYTHON_VALUES 3 PYTHON_SITE_PACKAGES) +list(GET _PYTHON_VALUES 4 PYTHON_MODULE_EXTENSION) +list(GET _PYTHON_VALUES 5 PYTHON_IS_DEBUG) +list(GET _PYTHON_VALUES 6 PYTHON_SIZEOF_VOID_P) +list(GET _PYTHON_VALUES 7 PYTHON_LIBRARY_SUFFIX) +list(GET _PYTHON_VALUES 8 PYTHON_LIBDIR) +list(GET _PYTHON_VALUES 9 PYTHON_MULTIARCH) + +message(STATUS "_PYTHON_VERSION_LIST: ${_PYTHON_VERSION_LIST}") +message(STATUS "PYTHON_PREFIX: ${PYTHON_PREFIX}") +message(STATUS "PYTHON_INCLUDE_DIR: ${PYTHON_INCLUDE_DIR}") +message(STATUS "PYTHON_SITE_PACKAGES: ${PYTHON_SITE_PACKAGES}") +message(STATUS "PYTHON_MODULE_EXTENSION: ${PYTHON_MODULE_EXTENSION}") +message(STATUS "PYTHON_IS_DEBUG: ${PYTHON_IS_DEBUG}") +message(STATUS "PYTHON_SIZEOF_VOID_P: ${PYTHON_SIZEOF_VOID_P}") +message(STATUS "PYTHON_LIBRARY_SUFFIX: ${PYTHON_LIBRARY_SUFFIX}") +message(STATUS "PYTHON_MULTIARCH: ${PYTHON_MULTIARCH}") + +# Python, especially on the mac, doesn't always return the right PYTHON_LIBDIR, +# so in the case that libdir is not able to be found, use a relative path from the include_dir +IF(NOT EXISTS ${PYTHON_LIBDIR}) + get_filename_component(_PYTHON_LIBDIR ${PYTHON_INCLUDE_DIR} PATH) + get_filename_component(_PYTHON_LIBDIR ${_PYTHON_LIBDIR} PATH) + set(PYTHON_LIBDIR "${_PYTHON_LIBDIR}/lib") +endif() +message(STATUS "PYTHON_LIBDIR: ${PYTHON_LIBDIR}") + +# Make sure the Python has the same pointer-size as the chosen compiler +# Skip if CMAKE_SIZEOF_VOID_P is not defined +if(CMAKE_SIZEOF_VOID_P AND (NOT "${PYTHON_SIZEOF_VOID_P}" STREQUAL "${CMAKE_SIZEOF_VOID_P}")) + if(PythonLibsNew_FIND_REQUIRED) + math(EXPR _PYTHON_BITS "${PYTHON_SIZEOF_VOID_P} * 8") + math(EXPR _CMAKE_BITS "${CMAKE_SIZEOF_VOID_P} * 8") + message(FATAL_ERROR + "Python config failure: Python is ${_PYTHON_BITS}-bit, " + "chosen compiler is ${_CMAKE_BITS}-bit") + endif() + set(PYTHONLIBS_FOUND FALSE) + return() +endif() + +# The built-in FindPython didn't always give the version numbers +string(REGEX REPLACE "\\." ";" _PYTHON_VERSION_LIST ${_PYTHON_VERSION_LIST}) +list(GET _PYTHON_VERSION_LIST 0 PYTHON_VERSION_MAJOR) +list(GET _PYTHON_VERSION_LIST 1 PYTHON_VERSION_MINOR) +list(GET _PYTHON_VERSION_LIST 2 PYTHON_VERSION_PATCH) + +# Make sure all directory separators are '/' +string(REGEX REPLACE "\\\\" "/" PYTHON_PREFIX ${PYTHON_PREFIX}) +string(REGEX REPLACE "\\\\" "/" PYTHON_INCLUDE_DIR ${PYTHON_INCLUDE_DIR}) +string(REGEX REPLACE "\\\\" "/" PYTHON_SITE_PACKAGES ${PYTHON_SITE_PACKAGES}) + +if(CMAKE_HOST_WIN32) + set(PYTHON_LIBRARY + "${PYTHON_PREFIX}/libs/Python${PYTHON_LIBRARY_SUFFIX}.lib") + + # when run in a venv, PYTHON_PREFIX points to it. But the libraries remain in the + # original python installation. They may be found relative to PYTHON_INCLUDE_DIR. + if(NOT EXISTS "${PYTHON_LIBRARY}") + get_filename_component(_PYTHON_ROOT ${PYTHON_INCLUDE_DIR} DIRECTORY) + set(PYTHON_LIBRARY + "${_PYTHON_ROOT}/libs/Python${PYTHON_LIBRARY_SUFFIX}.lib") + endif() + + # raise an error if the python libs are still not found. + if(NOT EXISTS "${PYTHON_LIBRARY}") + message(FATAL_ERROR "Python libraries not found") + endif() + +else() + if(PYTHON_MULTIARCH) + set(_PYTHON_LIBS_SEARCH "${PYTHON_LIBDIR}/${PYTHON_MULTIARCH}" "${PYTHON_LIBDIR}") + else() + set(_PYTHON_LIBS_SEARCH "${PYTHON_LIBDIR}") + endif() + #message(STATUS "Searching for Python libs in ${_PYTHON_LIBS_SEARCH}") + # Probably this needs to be more involved. It would be nice if the config + # information the python interpreter itself gave us were more complete. + find_library(PYTHON_LIBRARY + NAMES "python${PYTHON_LIBRARY_SUFFIX}" + PATHS ${_PYTHON_LIBS_SEARCH} + NO_DEFAULT_PATH) + + # If all else fails, just set the name/version and let the linker figure out the path. + if(NOT PYTHON_LIBRARY) + set(PYTHON_LIBRARY python${PYTHON_LIBRARY_SUFFIX}) + endif() +endif() + +MARK_AS_ADVANCED( + PYTHON_LIBRARY + PYTHON_INCLUDE_DIR +) + +# We use PYTHON_INCLUDE_DIR, PYTHON_LIBRARY and PYTHON_DEBUG_LIBRARY for the +# cache entries because they are meant to specify the location of a single +# library. We now set the variables listed by the documentation for this +# module. +SET(PYTHON_INCLUDE_DIRS "${PYTHON_INCLUDE_DIR}") +SET(PYTHON_LIBRARIES "${PYTHON_LIBRARY}") + +SET(PYTHON_DEBUG_LIBRARIES "${PYTHON_DEBUG_LIBRARY}") + + +find_package_message(PYTHON + "Found PythonLibs: ${PYTHON_LIBRARY}" + "${Python_EXECUTABLE}${Python_VERSION}") + +set(PYTHONLIBS_FOUND TRUE) diff --git a/cmake/test.cmake b/cmake/test.cmake new file mode 100644 index 0000000000..d9eed2cb85 --- /dev/null +++ b/cmake/test.cmake @@ -0,0 +1,404 @@ +set(MAYA_USD_DIR ${CMAKE_CURRENT_SOURCE_DIR}) + +function(mayaUsd_get_unittest_target unittest_target unittest_basename) + get_filename_component(unittest_name ${unittest_basename} NAME_WE) + set(${unittest_target} "${unittest_name}" PARENT_SCOPE) +endfunction() + +if (OIIO_idiff_BINARY) + set(IMAGE_DIFF_TOOL ${OIIO_idiff_BINARY} CACHE STRING "Use idiff for image comparison") +endif() + +# +# mayaUsd_add_test( +# {PYTHON_MODULE | +# PYTHON_COMMAND | +# PYTHON_SCRIPT | +# COMMAND [ ...] } +# [NO_STANDALONE_INIT] +# [INTERACTIVE] +# [ENV = ...]) +# +# PYTHON_MODULE - Module to import and test with unittest.main. +# PYTHON_COMMAND - Python code to execute; should call sys.exit +# with an appropriate exitcode to indicate success +# or failure. +# PYTHON_SCRIPT - Python script file to execute; should exit with an +# appropriate exitcode to indicate success or failure. +# WORKING_DIRECTORY - Directory from which the test executable will be called. +# COMMAND - Command line to execute as a test +# NO_STANDALONE_INIT - Only allowable with PYTHON_MODULE or +# PYTHON_COMMAND. With those modes, this +# command will generally add some boilerplate code +# to ensure that maya is initialized and exits +# correctly. Use this option to NOT add that code. +# INTERACTIVE - Only allowable with PYTHON_SCRIPT. +# The test is run using an interactive (non-standalone) +# session of Maya, including the UI. +# Tests run in this way should finish by calling Maya's +# quit command and returning an exit code of 0 for +# success or 1 for failure: +# cmds.quit(abort=True, exitCode=exitCode) +# ENV - Set or append the indicated environment variables; +# Since mayaUsd_add_test internally makes changes to +# some environment variables, if a value is given +# for these variables, it is appended; all other +# variables are set exactly as given. The variables +# that mayaUsd_add_test manages (and will append) are: +# PATH +# PYTHONPATH +# MAYA_PLUG_IN_PATH +# MAYA_SCRIPT_PATH +# PXR_PLUGINPATH_NAME +# XBMLANGPATH +# LD_LIBRARY_PATH +# Note that the format of these name/value pairs should +# be the same as that used with +# `set_property(TEST test_name APPEND PROPERTY ENVIRONMENT ...)` +# That means that if the passed in env var is a "list", it +# must already be separated by platform-appropriate +# path-separators, escaped if needed - ie, ":" on +# Linux/MacOS, and "\;" on Windows. Use +# separate_argument_list before passing to this func +# if you start with a cmake-style list. +# +function(mayaUsd_add_test test_name) + # ----------------- + # 1) Arg processing + # ----------------- + + cmake_parse_arguments(PREFIX + "NO_STANDALONE_INIT;INTERACTIVE" # options + "PYTHON_MODULE;PYTHON_COMMAND;PYTHON_SCRIPT;WORKING_DIRECTORY" # one_value keywords + "COMMAND;ENV" # multi_value keywords + ${ARGN} + ) + + # check that they provided one and ONLY 1 of: + # PYTHON_MODULE / PYTHON_COMMAND / PYTHON_SCRIPT / COMMAND + set(NUM_EXCLUSIVE_ITEMS 0) + foreach(option_name PYTHON_MODULE PYTHON_COMMAND PYTHON_SCRIPT COMMAND) + if(PREFIX_${option_name}) + math(EXPR NUM_EXCLUSIVE_ITEMS "${NUM_EXCLUSIVE_ITEMS} + 1") + endif() + endforeach() + if(NOT NUM_EXCLUSIVE_ITEMS EQUAL 1) + message(FATAL_ERROR "mayaUsd_add_test: must be called with exactly " + "one of PYTHON_MODULE, PYTHON_COMMAND, PYTHON_SCRIPT, or COMMAND") + endif() + + if(PREFIX_NO_STANDALONE_INIT AND NOT (PREFIX_PYTHON_MODULE + OR PREFIX_PYTHON_COMMAND)) + message(FATAL_ERROR "mayaUsd_add_test: NO_STANDALONE_INIT may only be " + "used with PYTHON_MODULE or PYTHON_COMMAND") + endif() + + if(PREFIX_INTERACTIVE AND NOT PREFIX_PYTHON_SCRIPT) + message(FATAL_ERROR "mayaUsd_add_test: INTERACTIVE may only be " + "used with PYTHON_SCRIPT") + endif() + + # set the working_dir + if(PREFIX_WORKING_DIRECTORY) + set(WORKING_DIR ${PREFIX_WORKING_DIRECTORY}) + else() + set(WORKING_DIR ${CMAKE_CURRENT_SOURCE_DIR}) + endif() + + # -------------- + # 2) Create test + # -------------- + + set(PYTEST_CODE "") + if(PREFIX_PYTHON_MODULE) + set(MODULE_NAME "${PREFIX_PYTHON_MODULE}") + set(PYTEST_CODE " +import sys +from unittest import main +import ${MODULE_NAME} +main(module=${MODULE_NAME}) +") + elseif(PREFIX_PYTHON_COMMAND) + set(PYTEST_CODE "${PREFIX_PYTHON_COMMAND}") + elseif(PREFIX_PYTHON_SCRIPT) + if (PREFIX_INTERACTIVE) + if(WIN32) + set(QUOTE "'") + else() + set(QUOTE "\\\"") + endif() + set(MEL_PY_EXEC_COMMAND "python(\"\\n\ +import os\\n\ +import sys\\n\ +import time\\n\ +import traceback\\n\ +file = ${QUOTE}${PREFIX_PYTHON_SCRIPT}${QUOTE}\\n\ +if not os.path.isabs(file):\\n\ + file = os.path.join(${QUOTE}${CMAKE_CURRENT_SOURCE_DIR}${QUOTE}, file)\\n\ +openMode = ${QUOTE}rb${QUOTE}\\n\ +compileMode = ${QUOTE}exec${QUOTE}\\n\ +globals = {${QUOTE}__file__${QUOTE}: file, ${QUOTE}__name__${QUOTE}: ${QUOTE}__main__${QUOTE}}\\n\ +try:\\n\ + exec(compile(open(file, openMode).read(), file, compileMode), globals)\\n\ +except Exception:\\n\ + sys.__stderr__.write(traceback.format_exc() + os.linesep)\\n\ + sys.__stderr__.flush()\\n\ + sys.__stdout__.flush()\\n\ + # sleep to give the output streams time to finish flushing - otherwise,\\n\ + # os._exit quits so hard + fast, flush may not happen!\\n\ + time.sleep(.1)\\n\ + os._exit(1)\\n\ +\")") + set(COMMAND_CALL ${MAYA_EXECUTABLE} -c ${MEL_PY_EXEC_COMMAND}) + else() + set(SCRIPT ${CMAKE_BINARY_DIR}/test/Temporary/scripts/runner_${test_name}.py) + FILE(WRITE ${SCRIPT} "${PREFIX_PYTHON_SCRIPT}") + set(COMMAND_CALL ${MAYA_PY_EXECUTABLE} ${SCRIPT}) + endif() + else() + set(COMMAND_CALL ${PREFIX_COMMAND}) + endif() + + if(PYTEST_CODE) + if(NOT PREFIX_NO_STANDALONE_INIT) + # first, indent pycode + mayaUsd_indent(indented_PYTEST_CODE "${PYTEST_CODE}") + # then wrap in try/finally, and call maya.standalone.[un]initialize() + set(PYTEST_CODE " +import maya.standalone +maya.standalone.initialize(name='python') +try: +${indented_PYTEST_CODE} +finally: + maya.standalone.uninitialize() +" + ) + endif() + + set(SCRIPT ${CMAKE_BINARY_DIR}/test/Temporary/scripts/runner_${test_name}.py) + FILE(WRITE ${SCRIPT} "${PYTEST_CODE}") + set(COMMAND_CALL ${MAYA_PY_EXECUTABLE} ${SCRIPT}) + endif() + + add_test( + NAME "${test_name}" + WORKING_DIRECTORY ${WORKING_DIR} + COMMAND ${COMMAND_CALL} + ) + + # ----------------- + # 3) Set up environ + # ----------------- + + set(ALL_PATH_VARS + PYTHONPATH + MAYA_PLUG_IN_PATH + MAYA_SCRIPT_PATH + XBMLANGPATH + ${PXR_OVERRIDE_PLUGINPATH_NAME} + PXR_MTLX_STDLIB_SEARCH_PATHS + ) + + if(IS_WINDOWS) + # Put path at the front of the list of env vars. + list(INSERT ALL_PATH_VARS 0 + PATH + ) + else() + list(APPEND ALL_PATH_VARS + LD_LIBRARY_PATH + ) + endif() + + # Set initial empty values for all path vars + foreach(pathvar ${ALL_PATH_VARS}) + set(MAYAUSD_VARNAME_${pathvar}) + endforeach() + + if(IS_WINDOWS) + list(APPEND MAYAUSD_VARNAME_PATH "${CMAKE_INSTALL_PREFIX}/lib/gtest") + list(APPEND MAYAUSD_VARNAME_PATH "${MAYA_LOCATION}/bin") + endif() + + # NOTE - we prefix varnames with "MAYAUSD_VARNAME_" just to make collision + # with some existing var less likely + + # Emulate what the module files for mayaHydra and mayaUsdPlugin would do. + + # mayaHydra + list(APPEND MAYAUSD_VARNAME_PATH + "${CMAKE_INSTALL_PREFIX}/lib") + list(APPEND MAYAUSD_VARNAME_${PXR_OVERRIDE_PLUGINPATH_NAME} + "${CMAKE_INSTALL_PREFIX}/lib/usd") + list(APPEND MAYAUSD_VARNAME_MAYA_PLUG_IN_PATH + "${CMAKE_INSTALL_PREFIX}/lib/maya") + + # mayaUsdPlugin + if(DEFINED MAYAUSD_LOCATION) + list(APPEND MAYAUSD_VARNAME_PATH + "${MAYAUSD_LOCATION}/lib") + list(APPEND MAYAUSD_VARNAME_PYTHONPATH + "${MAYAUSD_LOCATION}/lib/scripts") + list(APPEND MAYAUSD_VARNAME_MAYA_SCRIPT_PATH + "${MAYAUSD_LOCATION}/lib/scripts") + if (IS_LINUX) + # On Linux the paths in XBMLANGPATH need a /%B at the end. + list(APPEND MAYAUSD_VARNAME_XBMLANGPATH + "${MAYAUSD_LOCATION}/lib/icons/%B") + else() + list(APPEND MAYAUSD_VARNAME_XBMLANGPATH + "${MAYAUSD_LOCATION}/lib/icons") + endif() + list(APPEND MAYAUSD_VARNAME_PYTHONPATH + "${MAYAUSD_LOCATION}/lib/python") + list(APPEND MAYAUSD_VARNAME_${PXR_OVERRIDE_PLUGINPATH_NAME} + "${MAYAUSD_LOCATION}/lib/usd") + list(APPEND MAYAUSD_VARNAME_MAYA_PLUG_IN_PATH + "${MAYAUSD_LOCATION}/plugin/adsk/plugin") + list(APPEND MAYAUSD_VARNAME_PYTHONPATH + "${MAYAUSD_LOCATION}/plugin/adsk/scripts") + list(APPEND MAYAUSD_VARNAME_MAYA_SCRIPT_PATH + "${MAYAUSD_LOCATION}/plugin/adsk/scripts") + list(APPEND MAYAUSD_VARNAME_PXR_MTLX_STDLIB_SEARCH_PATHS + "${PXR_USD_LOCATION}/libraries") + list(APPEND MAYAUSD_VARNAME_PXR_MTLX_STDLIB_SEARCH_PATHS + "${MAYAUSD_LOCATION}/libraries") + endif() + + + if(IS_WINDOWS AND DEFINED ENV{PYTHONHOME}) + # If the environment contains a PYTHONHOME, also set the path to + # that folder so that we can find the python DLLs. + list(APPEND MAYAUSD_VARNAME_PATH $ENV{PYTHONHOME}) + endif() + + # Adjust PYTHONPATH to include the path to our test utilities. + list(APPEND MAYAUSD_VARNAME_PYTHONPATH "${MAYA_USD_DIR}/test/testUtils") + + # Adjust PYTHONPATH to include the path to our test. + list(APPEND MAYAUSD_VARNAME_PYTHONPATH "${CMAKE_CURRENT_SOURCE_DIR}") + + # Adjust PATH and PYTHONPATH to include USD. + # These should come last (esp PYTHONPATH, in case another module is overriding + # with pkgutil) + if (DEFINED MAYAHYDRA_TO_USD_RELATIVE_PATH) + set(USD_INSTALL_LOCATION "${CMAKE_INSTALL_PREFIX}/${MAYAHYDRA_TO_USD_RELATIVE_PATH}") + else() + set(USD_INSTALL_LOCATION ${PXR_USD_LOCATION}) + endif() + # Inherit any existing PYTHONPATH, but keep it at the end. + list(APPEND MAYAUSD_VARNAME_PYTHONPATH + "${USD_INSTALL_LOCATION}/lib/python") + if(IS_WINDOWS) + list(APPEND MAYAUSD_VARNAME_PATH + "${USD_INSTALL_LOCATION}/bin") + list(APPEND MAYAUSD_VARNAME_PATH + "${USD_INSTALL_LOCATION}/lib") + endif() + + # NOTE: this should come after any setting of PATH/PYTHONPATH so + # that our entries will come first. + # Inherit any existing PATH/PYTHONPATH, but keep it at the end. + # This is needed (especially for PATH) because we will overwrite + # both with the values from our list and we need to keep any + # system entries. + list(APPEND MAYAUSD_VARNAME_PATH $ENV{PATH}) + list(APPEND MAYAUSD_VARNAME_PYTHONPATH $ENV{PYTHONPATH}) + + # convert the internally-processed envs from cmake list + foreach(pathvar ${ALL_PATH_VARS}) + separate_argument_list(MAYAUSD_VARNAME_${pathvar}) + endforeach() + + # prepend the passed-in ENV values - assume these are already + # separated + escaped + foreach(name_value_pair ${PREFIX_ENV}) + mayaUsd_split_head_tail("${name_value_pair}" "=" env_name env_value) + if(NOT env_name) + message(FATAL_ERROR "poorly formatted NAME=VALUE pair - name " + "missing: ${name_value_pair}") + endif() + + # now either prepend to existing list, or create new + if("${env_name}" IN_LIST ALL_PATH_VARS) + if(IS_WINDOWS) + set(MAYAUSD_VARNAME_${env_name} + "${env_value}\;${MAYAUSD_VARNAME_${env_name}}") + else() + set(MAYAUSD_VARNAME_${env_name} + "${env_value}:${MAYAUSD_VARNAME_${env_name}}") + endif() + else() + set("MAYAUSD_VARNAME_${env_name}" ${env_value}) + list(APPEND ALL_PATH_VARS "${env_name}") + endif() + endforeach() + + # Unset any MAYA_MODULE_PATH as we set all the individual variables + # so we don't want to conflict with a MayaUsd module. + set_property(TEST ${test_name} APPEND PROPERTY ENVIRONMENT "MAYA_MODULE_PATH=") + + # set all env vars + foreach(pathvar ${ALL_PATH_VARS}) + set_property(TEST "${test_name}" APPEND PROPERTY ENVIRONMENT + "${pathvar}=${MAYAUSD_VARNAME_${pathvar}}") + endforeach() + + # Set a temporary folder path for the TMP,TEMP and MAYA_APP_DIR in which the + # maya profile will be created. + # Note: replace bad chars in test_name with _. + string(REGEX REPLACE "[:<>\|]" "_" SANITIZED_TEST_NAME ${test_name}) + set(MAYA_APP_TEMP_DIR "${CMAKE_BINARY_DIR}/test/Temporary/${SANITIZED_TEST_NAME}") + # Note: ${WORKING_DIR} can point to the source folder, so don't use it + # in any env var that will write files (such as MAYA_APP_DIR). + set_property(TEST "${test_name}" APPEND PROPERTY ENVIRONMENT + "TMP=${MAYA_APP_TEMP_DIR}" + "TEMP=${MAYA_APP_TEMP_DIR}" + "MAYA_APP_DIR=${MAYA_APP_TEMP_DIR}") + file(MAKE_DIRECTORY ${MAYA_APP_TEMP_DIR}) + + # Set the Python major version in MAYA_PYTHON_VERSION. Maya 2020 and + # earlier that are Python 2 only will simply ignore it. + # without "MAYA_NO_STANDALONE_ATEXIT=1", standalone.uninitialize() will + # set exitcode to 0 + # MAYA_DISABLE_CIP=1 Avoid fatal crash on start-up. + # MAYA_DISABLE_CER=1 Customer Error Reporting. + set_property(TEST "${test_name}" APPEND PROPERTY ENVIRONMENT + "MAYA_PYTHON_VERSION=${MAYA_PY_VERSION}" + "MAYA_NO_STANDALONE_ATEXIT=1" + "MAYA_DEBUG_ENABLE_CRASH_REPORTING=1" + "MAYA_DEBUG_NO_SAVE_ON_CRASH=1" + "MAYA_NO_MORE_ASSERT=1" + "MAYA_DISABLE_CIP=1" + "MAYA_DISABLE_CER=1") + + if(IS_MACOSX) + # Necessary for tests like DiffCore to find python + set_property(TEST "${test_name}" APPEND PROPERTY ENVIRONMENT + "DYLD_LIBRARY_PATH=${MAYA_LOCATION}/MacOS:$ENV{DYLD_LIBRARY_PATH}") + set_property(TEST "${test_name}" APPEND PROPERTY ENVIRONMENT + "DYLD_FRAMEWORK_PATH=${MAYA_LOCATION}/Maya.app/Contents/Frameworks") + endif() + + if (PREFIX_INTERACTIVE) + # Add the "interactive" label to all tests that launch the Maya UI. + # This allows bypassing them by using the --label-exclude/-LE option to + # ctest. This is useful when running tests in a headless configuration. + set_property(TEST "${test_name}" APPEND PROPERTY LABELS interactive) + + # When running via remote desktop this env var is needed for Maya + # to function correctly. Has no effect when not running remote. + set_property(TEST "${test_name}" APPEND PROPERTY ENVIRONMENT + "MAYA_ALLOW_OPENGL_REMOTE_SESSION=1") + + # Don't want popup when color management fails. + set_property(TEST "${test_name}" APPEND PROPERTY ENVIRONMENT + "MAYA_CM_DISABLE_ERROR_POPUPS=1") + set_property(TEST "${test_name}" APPEND PROPERTY ENVIRONMENT + "MAYA_COLOR_MGT_NO_LOGGING=1") + + else() + set_property(TEST "${test_name}" APPEND PROPERTY ENVIRONMENT + "MAYA_IGNORE_DIALOGS=1") + endif() +endfunction() diff --git a/cmake/usd.cmake b/cmake/usd.cmake new file mode 100644 index 0000000000..4661081a26 --- /dev/null +++ b/cmake/usd.cmake @@ -0,0 +1,24 @@ +# +# Copyright 2020 Autodesk +# +# 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. +# +function(init_usd) + # Adjust PYTHONPATH, PATH + mayaUsd_append_path_to_env_var("PYTHONPATH" "${PXR_USD_LOCATION}/lib/python") + if(WIN32) + mayaUsd_append_path_to_env_var("PATH" "${PXR_USD_LOCATION}/bin;${PXR_USD_LOCATION}/lib") + endif() +endfunction() + +init_usd() diff --git a/cmake/utils.cmake b/cmake/utils.cmake new file mode 100644 index 0000000000..bb129c41c4 --- /dev/null +++ b/cmake/utils.cmake @@ -0,0 +1,311 @@ +# +# Copyright 2020 Autodesk +# +# 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(CMakeParseArguments) + +# The name of the operating system for which CMake is to build +if (${CMAKE_SYSTEM_NAME} MATCHES "Windows") + set(IS_WINDOWS TRUE) +elseif (${CMAKE_SYSTEM_NAME} MATCHES "Linux") + set(IS_LINUX TRUE) +elseif (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + set(IS_MACOSX TRUE) +endif() + +# compiler type +if (CMAKE_COMPILER_IS_GNUCXX) + set(IS_GNU TRUE) +elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") + set(IS_CLANG TRUE) +elseif(MSVC) + set(IS_MSVC TRUE) +endif() + +# Appends a path to an environment variable. +# Note: if you want to append multiple paths either call this multiple +# times, or send in the paths with the proper platform separator. +# +# envVar The environment variable to modify +# pathToAppend The path to append +# +function(mayaUsd_append_path_to_env_var envVar pathToAppend) + file(TO_NATIVE_PATH "${pathToAppend}" nativePathToAppend) + if(DEFINED ENV{${envVar}}) + if(IS_WINDOWS) + set(NEWPATH "$ENV{${envVar}};${nativePathToAppend}") + else() + set(NEWPATH "$ENV{${envVar}}:${nativePathToAppend}") + endif() + set(ENV{${envVar}} "${NEWPATH}") + else() + set(ENV{${envVar}} "${nativePathToAppend}") + endif() + message("Updated ${envVar}: $ENV{${envVar}}") +endfunction() + +# Finds if a specific Python module is installed in the current Python. +# _FOUND will be set to indicate whether the module was found. +# +# module The python module to find +# +function(mayaUsd_find_python_module module) + string(TOUPPER ${module} module_upper) + set(MODULE_FOUND "${module_upper}_FOUND") + if(NOT ${MODULE_FOUND}) + # Check for Python Executable + if(NOT DEFINED Python_EXECUTABLE OR Python_EXECUTABLE STREQUAL "") + MESSAGE(FATAL_ERROR "Python_EXECUTABLE is not set") + endif() + + if(ARGC GREATER 1 AND ARGV1 STREQUAL "REQUIRED") + set(${module}_FIND_REQUIRED TRUE) + endif() + execute_process(COMMAND "${Python_EXECUTABLE}" "-c" + "from __future__ import print_function; import re, ${module}; print(re.compile('/__init__.py.*').sub('',${module}.__file__))" + RESULT_VARIABLE _${module}_status + OUTPUT_VARIABLE _${module}_location + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT _${module}_status) + set(${MODULE_FOUND} ${_${module}_location} CACHE STRING + "Location of Python module ${module}") + endif(NOT _${module}_status) + endif() +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(mayaUsd_init_rpath rpathRef origin) + if(NOT IS_ABSOLUTE ${origin}) + if(DEFINED INSTALL_DIR_SUFFIX) + set(origin "${CMAKE_INSTALL_PREFIX}/${INSTALL_DIR_SUFFIX}/${origin}") + else() + set(origin "${CMAKE_INSTALL_PREFIX}/${origin}") + endif() + endif() + # mayaUsd_add_rpath uses REALPATH, so we must make sure we always + # do so here too, to get the right relative path + get_filename_component(origin "${origin}" REALPATH) + 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(mayaUsd_add_rpath rpathRef target) + if(IS_ABSOLUTE "${target}") + # init_rpath calls get_filename_component([...] REALPATH), which does + # symlink resolution, so we must do the same, otherwise relative path + # determination below will fail. + get_filename_component(target "${target}" REALPATH) + # Make target relative to $ORIGIN (which is the first element in + # rpath when initialized with mayaUsd_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(mayaUsd_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}) + # Replace $ORIGIN with @loader_path + if(IS_MACOSX) + if("${path}/" MATCHES "^[$]ORIGIN/") + # Replace with origin path. + string(REPLACE "$ORIGIN/" "@loader_path/" path "${path}/") + 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() + +# +# mayaUsd_promoteHeaderList( +# [SUBDIR ] +# [FILES ] +# [BASEDIR ]) +# +# SUBDIR - sub-directory in which to promote files. +# FILES - list of files to promote. +# BASESDIR - base dirctory where promoted headers are installed into. +# if not defined, mayaUsd subdirectory is used by default. +# +# +function(mayaUsd_promoteHeaderList) + cmake_parse_arguments(PREFIX + "" + "SUBDIR;BASEDIR" # one_value keywords + "HEADERS" # multi_value keywords + ${ARGN} + ) + + if (PREFIX_HEADERS) + set(HEADERFILES ${PREFIX_HEADERS}) + else() + message(FATAL_ERROR "HEADERS keyword is not specified.") + endif() + + set(BASEDIR ${CMAKE_BINARY_DIR}/include) + if (PREFIX_BASEDIR) + set(BASEDIR ${BASEDIR}/${PREFIX_BASEDIR}) + else() + set(BASEDIR ${BASEDIR}/mayaUsd) + endif() + + if (PREFIX_SUBDIR) + set(BASEDIR ${BASEDIR}/${PREFIX_SUBDIR}) + endif() + + foreach(header ${HEADERFILES}) + set(SRCFILE ${CMAKE_CURRENT_SOURCE_DIR}/${header}) + set(DSTFILE ${BASEDIR}/${header}) + + set(CONTENT "#pragma once\n#include \"${SRCFILE}\"\n") + + if (NOT EXISTS ${DSTFILE}) + message(STATUS "promoting: " ${SRCFILE}) + file(WRITE ${DSTFILE} "${CONTENT}") + else() + file(READ ${DSTFILE} oldContent) + if (NOT "${CONTENT}" STREQUAL "${oldContent}") + message(STATUS "Promoting ${SRCFILE}") + file(WRITE ${DSTFILE} "${CONTENT}") + endif() + endif() + endforeach() +endfunction() + +function(mayaUsd_split_head_tail input_string split_string var_head var_tail) + string(FIND "${input_string}" "${split_string}" head_end) + if("${head_end}" EQUAL -1) + message(FATAL_ERROR "input_string '${input_string}' did not contain " + "split_string '${split_string}'") + endif() + + string(LENGTH "${split_string}" split_string_len) + math(EXPR tail_start "${head_end} + ${split_string_len}") + + string(SUBSTRING "${input_string}" 0 "${head_end}" "${var_head}") + string(SUBSTRING "${input_string}" "${tail_start}" -1 "${var_tail}") + set("${var_head}" "${${var_head}}" PARENT_SCOPE) + set("${var_tail}" "${${var_tail}}" PARENT_SCOPE) +endfunction() + +function(mayaUsd_indent outvar lines) + string(REPLACE "\n" "\n " lines "${lines}") + set("${outvar}" " ${lines}" PARENT_SCOPE) +endfunction() + +# parse list arguments into a new list separated by ";" or ":" +function(separate_argument_list listName) + if(IS_WINDOWS) + string(REPLACE ";" "\;" ${listName} "${${listName}}") + else(IS_LINUX OR IS_MACOSX) + string(REPLACE ";" ":" ${listName} "${${listName}}") + endif() + set(${listName} "${${listName}}" PARENT_SCOPE) +endfunction() + +# python extension module suffix +function(set_python_module_property target) + if(IS_WINDOWS) + set_target_properties(${target} + PROPERTIES + PREFIX "" + SUFFIX ".pyd" + ) + elseif(IS_LINUX OR IS_MACOSX) + set_target_properties(${target} + PROPERTIES + PREFIX "" + SUFFIX ".so" + ) + endif() +endfunction() + +# This fuction will populate "out_var" with the default values external +# projects are expected to need. +# It can take an optional argument that will replace the list separator +# in CMake values. For instance, if an external project uses a different +# list separator, the values in here must be changed to reflect this. +function(get_external_project_default_values out_var) + # Some of these variables might end up not being used by some projects + # Therefore avoid useless warnings in the log. + list(APPEND setting_list --no-warn-unused-cli) + + if(ARGN) + list(GET ARGN 0 custom_sep) + endif() + + # Macro to add the value only if it's present. + macro(external_project_conditional_define option) + if (${option}) + if(custom_sep) + # This will change the list separator to the desired one. + # i.e. -DCMAKE_OSX_ARCHITECTURES=x86_64;arm64 -> -DCMAKE_OSX_ARCHITECTURES=x86_64|arm64 + string(REPLACE ";" ${custom_sep} ${option} "${${option}}") + endif() + list(APPEND setting_list -D${option}=${${option}}) + endif() + endmacro(external_project_conditional_define) + + external_project_conditional_define(CMAKE_INSTALL_MESSAGE) + external_project_conditional_define(CMAKE_BUILD_TYPE) + external_project_conditional_define(CMAKE_MAKE_PROGRAM) + + if(BUILD_UB2) + # UB2 builds require this flag + external_project_conditional_define(CMAKE_OSX_ARCHITECTURES) + external_project_conditional_define(CMAKE_OSX_DEPLOYMENT_TARGET) + endif() + + # Debugging informations for external projects + external_project_conditional_define(CMAKE_VERBOSE_MAKEFILE) + external_project_conditional_define(CMAKE_FIND_DEBUG_MODE) + + set(${out_var} ${setting_list} PARENT_SCOPE) +endfunction(get_external_project_default_values) + +# Create one for all the project using the default list separator +get_external_project_default_values(MAYAUSD_EXTERNAL_PROJECT_GENERAL_SETTINGS "$") diff --git a/doc/CHANGELOG.md b/doc/CHANGELOG.md new file mode 100644 index 0000000000..89c312dcad --- /dev/null +++ b/doc/CHANGELOG.md @@ -0,0 +1,81 @@ +# Changelog + +## [v0.4.0] - 2023-08-30 + +**Build:** +- [HYDRA-453] - Update to USD 23.08 + +**Functionality:** +- [HYDRA-425] - Basic Hydra Scene Browser plugin available with mayaHydra +- [HYDRA-427] - Build up SceneIndex for Maya native scene +- [HYDRA-426] - Add new environment variable as switch for new SceneIndex approach +- [HYDRA-381] - Add support for input color space for DG nodes +- [HYDRA-447] - Default value for Display Purpose in Gloabal Settings +- [HYDRA-448] - Rename Global USD Display Purpose Tag in viewport settings +- [HYDRA-472] - Update Scene Indices chain code in maya-usd related to USD 23.08 integration + +**Fixes:** +- [HYDRA-419] - Domelight texture not dirtying with SceneIndex update +- [HYDRA-180] - aiSkyDomeLight default color does not work +- [HYDRA-182] - UsdStage rprim of sphere type is not rendered +- [HYDRA-289] - Light intensity shows big difference compared to usdview +- [HYDRA-343] - Selecting native Maya nodes in the viewport selects the shape, not the transform +- [HYDRA-350] - Crash when assigning materialX standard surface to prim +- [HYDRA-413] - Referenced maya file with usd stage doesn't render in mayaHydra (Ill-formed SdfPath) +- [HYDRA-421] - Stage Display purpose setting has no effect +- [HYDRA-423] - Hydra Generative Plugin is not loaded in Maya +- [HYDRA-436] - Update issue muting USD layers +- [HYDRA-483] - Fix Scene Browser compilation for USD 23.08 +- [HYDRA-493] - Lighting results differ between SceneIndex and SceneDelegate +- [HYDRA-494] - Shadows are not being cast on Maya native objects +- [HYDRA-320] - MaterialX shading/lighting issue + +## [v0.3.0] - 2023-07-06 + +**Build:** +- [HYDRA-322] - Compilation failures when including terminalsResolvingSceneIndex.h + +**Fixes:** +- [HYDRA-93] - Toggling between vp2 and Hydra stop updating Hydra +- [HYDRA-178] - USD instances are not shown in the mayaHydra viewport +- [HYDRA-184] - Wire frame and wire frame on shaded of USD objects not drawn +- [HYDRA-197] - Prevent scene delegate from being thrown out when we're switching back and forth between VP2 and hydra +- [HYDRA-287] - Hydra : OpenGL states not restored correctly +- [HYDRA-318] - Viewer doesn't update when working in USD stage +- [HYDRA-325] - Purpose defined prims don't render with mayaHydra +- [HYDRA-332] - Artifact with transparent surfaces in the viewport +- [HYDRA-342] - Rotation gizmo is missing its X/Y/Z axis +- [HYDRA-352] - Objects with namespaces will not draw in mayaHydra and switching back to VP2 will crash Maya +- [HYDRA-382] - Crash when going back to VP2 after loading PointMedcity.usd + +## [v0.2.0] - 2023-05-03 + +**Build:** +- [HYDRA-333] - Update mayaHydra to USD 23.02 +- [HYDRA-69] - Restructure directories in repository + +**Performance:** +- [HYDRA-339] - Hydra hit test performance optimization via render a smaller region + +**Miscellaneous:** +- [HYDRA-192] - remove UFE conditional compilation +- [HYDRA-321] - Added required ApiSchema for MaterialX bindings in USD +- [HYDRA-327] - Preserve existence of the previous selection temporarily inside of the Ufe Selection Notification (Clear, Replace) + +**Fixes:** +- [HYDRA-79] - Crash when changing to HydraViewport for a USD referenced object +- [HYDRA-101] - Crash when switching from VP2 to Hydra on MacOS +- [HYDRA-109] - Loading the Arnold plug-in after Hydra causes the viewport to not update properly +- [HYDRA-116] - Meshes are not displayed as expected when switching between viewport modes +- [HYDRA-128] - The latest module template doesn't work +- [HYDRA-179] - MaterialX does not work with usd stage scene index +- [HYDRA-200] - Ghost mesh after deleting everything in the scene +- [HYDRA-211] - Require clang-format by automation +- [HYDRA-239] - Support for Apple Silicon +- [HYDRA-250] - Code path where ria is a nullptr and not tested. +- [HYDRA-266] - Undo, redo does not work with custom scene indices +- [HYDRA-283] - Crash when switching back and forth from VP2 to Hydra GL + + +## [v0.1.0] - 2022-03-27 +- Initial release of Hydra for Maya (Technology Preview) \ No newline at end of file diff --git a/doc/CLA/Hydra for Maya - Corp Contrib Agmt.pdf b/doc/CLA/Hydra for Maya - Corp Contrib Agmt.pdf new file mode 100644 index 0000000000..7bbcda5f93 Binary files /dev/null and b/doc/CLA/Hydra for Maya - Corp Contrib Agmt.pdf differ diff --git a/doc/CLA/Hydra for Maya - Ind Contrib Agmt.pdf b/doc/CLA/Hydra for Maya - Ind Contrib Agmt.pdf new file mode 100644 index 0000000000..e75c2e869f Binary files /dev/null and b/doc/CLA/Hydra for Maya - Ind Contrib Agmt.pdf differ diff --git a/doc/CONTRIBUTING.md b/doc/CONTRIBUTING.md new file mode 100644 index 0000000000..b0f6be15b7 --- /dev/null +++ b/doc/CONTRIBUTING.md @@ -0,0 +1,32 @@ +# Contributing to Hydra for Maya + +## Contributor License Agreement # +Before contributing code to this project, we ask that you sign a Contributor License Agreement (CLA). + ++ [Hydra for Maya - Corp Contrib Agmt.pdf](<./CLA/Hydra for Maya - Corp Contrib Agmt.pdf>) please sign this one for corporate use ++ [Hydra for Maya - Ind Contrib Agmt.pdf](<./CLA/Hydra for Maya - Ind Contrib Agmt.pdf>) please sign this one if you're an individual contributor + +The documents include instructions on where to send the completed forms to. Once a signed form has been received you will be able to submit pull requests. + + +## Filing Issues + +### Suggestions + +The _Hydra for Maya_ project is meant to evolve with feedback - the project and its users greatly appreciate any thoughts on ways to improve the design or features. Please use the `enhancement` tag to specifically denote issues that are suggestions - this helps us triage and respond appropriately. + +### Bugs + +As with all pieces of software, you may end up running into bugs. Please submit bugs as regular issues on GitHub - Maya developers are regularly monitoring issues and will prioritize and schedule fixes. + +The best bug reports include a detailed way to predictably reproduce the issue, and possibly even a working example that demonstrates the issue. + +## Contributing Code + +The _Hydra for Maya_ project accepts and greatly appreciates contributions. The project follows the [fork & pull](https://help.github.com/articles/using-pull-requests/#fork--pull) model for accepting contributions. + +When contributing code, please also include appropriate tests as part of the pull request, and follow the same comment and coding style as the rest of the project. Take a look through the existing code for examples of the testing and style practices the project follows. + +All development should happen against the "develop" branch of the repository. Please make sure the base branch of your pull request is set to the "develop" branch when filing your pull request. + +It is highly recommended that an issue be logged on GitHub before any work is started. This will allow for early feedback from other developers and avoid multiple parallel efforts. diff --git a/doc/LICENSE.md b/doc/LICENSE.md new file mode 100644 index 0000000000..11069edd79 --- /dev/null +++ b/doc/LICENSE.md @@ -0,0 +1,201 @@ + 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. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +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. diff --git a/doc/build.md b/doc/build.md new file mode 100644 index 0000000000..ebea19b063 --- /dev/null +++ b/doc/build.md @@ -0,0 +1,216 @@ +# Building + +## Getting and Building the Code + +The simplest way to build the project is by running the supplied **build.py** script. This script builds the project and installs all of the necessary libraries and plug-ins for you. Follow the instructions below to learn how to use the script. + +#### 1. Tools and System Prerequisites + +Before building the project, consult the following table to ensure you use the recommended version of compiler, operating system, cmake, etc. + +| Required | ![](images/windows.png) | ![](images/mac.png) | ![](images/linux.png) | +|:---------------------:|:-------------------------:|:------------------------------------------------------------:|:---------------------------:| +| Operating System | Windows 10
Windows 11 | High Sierra (10.13)
Mojave (10.14)
Catalina (10.15)
Big Sur (11.2.x) | Rocky Linux 8.6 / Linux® Red Hat® Enterprise 8.6 WS | +| Compiler Requirement| Maya 2024 (VS 2022) | Maya 2024 (Xcode 13.4 or higher) | Maya 2024 (gcc 11.2.1) | +| CMake Version (min/max) | 3.13...3.17 | 3.13...3.17 | 3.13...3.17 | +| Python | 3.10.8 | 3.10.8 | 3.10.8 | +| Python Packages | PyYAML, PySide, PyOpenGL, Jinja2 | PyYAML, PySide2, PyOpenGL, Jinja2 | PyYAML, PySide, PyOpenGL, Jinja2 | +| Build generator | Visual Studio, Ninja (Recommended) | XCode, Ninja (Recommended) | Ninja (Recommended) | +| Command processor | Visual Studio X64 2019 command prompt | bash | bash | +| Supported Maya Version| 2024 | 2024 | 2024 | + +| Optional | ![](images/windows.png) | ![](images/mac.png) | ![](images/linux.png) | + +***NOTE:*** Visit the online Maya developer help document under ***Setting up your build environment*** for additional compiler requirements on different platforms. + +#### 2. Download and Build Pixar USD + +See Pixar's official github page for instructions on how to build USD: https://github.com/PixarAnimationStudios/USD. Pixar has recently removed support for building Maya USD libraries/plug-ins in their github repository and ```build_usd.py```. + +| | ![](images/pxr.png) | +|:------------: |:---------------: | +| CommitID/Tags | Recommended : [v23.08](https://github.com/PixarAnimationStudios/OpenUSD/releases/tag/v23.08) | +| CommitID/Tags | For older maya-hydra plugin: [v22.11](https://github.com/PixarAnimationStudios/USD/releases/tag/v22.11) | + +For additional information on building Pixar USD, see the ***Additional Build Instruction*** section below. + +***NOTE:*** Recommended version of USD for building the latest version of MayaHydra plugin is USD23.08 [v23.08](https://github.com/PixarAnimationStudios/OpenUSD/releases/tag/v23.08). If older version of USD needs to be used then maya-hydra v0.1.x is to be used for build and feature compatibility. + +***NOTE:*** Make sure that you don't have an older USD locations in your ```PATH``` and ```PYTHONPATH``` environment settings. ```PATH``` and ```PYTHONPATH``` are automatically adjusted inside the project to point to the correct USD location. See ```cmake/usd.cmake```. + +#### 3. Universal Front End (UFE) + +The Universal Front End (UFE) is a DCC-agnostic component that allows Maya to browse and edit data in multiple data models. This allows Maya to edit pipeline data such as USD. UFE comes installed as a built-in component with Maya 2019 and later. UFE is developed as a separate binary component, and therefore versioned separately from Maya. + +| Ufe Version | Maya Version | Ufe Docs (external) | +|----------------------------|--------------------------------------------------------|:-------------------:| +| v4.0.0 | Maya 2024 | https://help.autodesk.com/view/MAYAUL/2024/ENU/?guid=MAYA_API_REF_ufe_ref_index_html | +| v4.0.1 | Maya 2024.1 | | + +To build the project with UFE support, you will need to use the headers and libraries included in the ***Maya Devkit***: + +https://www.autodesk.com/developer-network/platform-technologies/maya + +#### 4. Download the source code + +Start by cloning the repository: +``` +git clone https://github.com/Autodesk/maya-hydra +cd maya-hydra +``` + +##### Repository Layout + +| Location | Description | +|------------- |--------------------------------------------------------------------------------------------- | +| lib/mayaHydra/mayaPlugin | Contains Maya plugin definition and render override registration | +| lib/mayaHydra/hydraExtensions | Contains extensions to and mechanism needed to interface with hydra classes | +| lib/mayaHydra/ufeExtensions | Contains extensions to translate paths between UFE, USD SdfPath and Maya DAGPath | + +#### 5. How To Use build.py Script + +##### Arguments + +There are four arguments that must be passed to the script: + +| Flags | Description | +|-------------------- |-------------------------------------------------------------------------------------- | +| --maya-location | directory where Maya is installed. | +| --pxrusd-location | directory where Pixar USD Core is installed. | +| --devkit-location | directory where Maya devkit is installed. | +| workspace_location | directory where the project use as a workspace to build and install plugin/libraries | + +``` +Linux: +âžœ maya-hydra python build.py --maya-location /usr/autodesk/maya2024 --pxrusd-location /usr/local/USD-Release --devkit-location /usr/local/devkitBase /usr/local/workspace + +MacOSX: +âžœ maya-hydra python build.py --maya-location /Applications/Autodesk/maya2024 --pxrusd-location /opt/local/USD-Release --devkit-location /opt/local/devkitBase /opt/local/workspace + +Windows: +c:\maya-hydra> python build.py --maya-location "C:\Program Files\Autodesk\Maya2024" --pxrusd-location C:\USD-Release --devkit-location C:\devkitBase C:\workspace +``` + +##### Build Arguments + +| Flag | Description | +|-------------------- |---------------------------------------------------------------------------------------| +| --build-args | comma-separated list of cmake variables can be also passed to build system. | + +``` +--build-args="-DBUILD_TESTS=OFF" +``` + +##### CMake Options + +Name | Description | Default +--- | --- | --- +BUILD_TESTS | builds all unit tests. | ON +BUILD_STRICT_MODE | enforces all warnings as errors. | ON +BUILD_SHARED_LIBS | build libraries as shared or static. | ON + +##### Stages + +| Flag | Description | +|-------------------- |--------------------------------------------------------------------------------------------------- | +| --stages | comma-separated list of stages can also be passed to the build system. By default "clean, configure, build, install" stages are executed if this argument is not set. | + +| Options | Description | +|----------- |--------------------------------------------------- | +| clean | clean build | +| configure | call this stage every time a cmake file is modified | +| build | builds the project | +| install | installs all the necessary plug-ins and libraries | +| test | runs all unit tests | +| package | bundles up all the installation files as a zip file inside the package directory | + +``` +Examples: +--stages=configure,build,install +--stages=test +``` +***NOTE:*** All the flags can be followed by either ```space``` or ```=``` + +##### CMake Generator + +It is up to the user to select the CMake Generator of choice, but we encourage the use of the Ninja generator. To use the Ninja Generator, you need to first install the Ninja binary from https://ninja-build.org/ + +You then need to set the generator to ```Ninja``` and the ```CMAKE_MAKE_PROGRAM``` variable to the Ninja binary you downloaded. +``` +python build.py --generator Ninja --build-args=-DCMAKE_MAKE_PROGRAM='path to ninja binary' +``` +##### Build and Install locations + +By default, the build and install directories are created inside the **workspace** directory. However, you can change these locations by setting the ```--build-location``` and ```--install-location``` flags. + +##### Build Log + +By default the build log is written into ```build_log.txt``` inside the build directory. If you want to redirect the output stream to the console instead, +you can pass ```--redirect-outstream-file``` and set it to false. + +##### Additional flags and options + +Run the script with the ```--help``` parameter to see all the possible flags and short descriptions. + +#### 6. How To Run Unit Tests + +Unit tests can be run by setting ```--stages=test``` or by simply calling `ctest` directly from the build directory. + +# Additional Build Instruction + +##### Python: + +It is important to use the Python version shipped with Maya and not the system version when building USD on MacOS. Note that this is primarily an issue on MacOS, where Maya's version of Python is likely to conflict with the version provided by the system. + +To build USD and the Maya plug-ins on MacOS for Maya (2024), run: +``` +/Applications/Autodesk/maya2024/Maya.app/Contents/bin/mayapy build_usd.py ~/Desktop/BUILD +``` +By default, ``usdview`` is built which has a dependency on PyOpenGL. Since the Python version of Maya doesn't ship with PyOpenGL you will be prompted with the following error message: +``` +PyOpenGL is not installed. If you have pip installed, run "pip install PyOpenGL" to install it, then re-run this script. +If PyOpenGL is already installed, you may need to update your ```PYTHONPATH``` to indicate where it is located. +``` +The easiest way to bypass this error is by setting ```PYTHONPATH``` to point at your system python or third-party python package manager that has PyOpenGL already installed. +e.g +``` +export PYTHONPATH=$PYTHONPATH:Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages +``` +Use `pip list` to see the list of installed packages with your python's package manager. + +e.g +``` +âžœ pip list +Package Version +---------- -------- +Jinja2 3.1.2 +MarkupSafe 2.1.1 +pip 22.2.1 +PyOpenGL 3.1.6 +PySide2 5.15.2.1 +PyYAML 6.0 +setuptools 63.2.0 +shiboken2 5.15.2.1 +``` + +##### Dependencies on Linux DSOs when running tests + +Normally either runpath or rpath are used on some DSOs in this library to specify explicit on other libraries (such as USD itself) + +If for some reason you don't want to use either of these options, and switch them off with: +``` +CMAKE_SKIP_RPATH=TRUE +``` +To allow your tests to run, you can inject LD_LIBRARY_PATH into any of the mayaHydra_add_test calls by setting the ADDITIONAL_LD_LIBRARY_PATH cmake variable to $ENV{LD_LIBRARY_PATH} or similar. + +There is a related ADDITIONAL_PXR_PLUGINPATH_NAME cmake var which can be used if schemas are installed in a non-standard location + +# How to Load Plug-ins in Maya + +The provided module files (*.mod) facilitates setting various environment variables for plugins and libraries. After the project is successfully built, ```mayaHydra.mod``` are installed inside the install directory. In order for Maya to discover these mod files, ```MAYA_MODULE_PATH``` environment variable needs to be set to point to the location where the mod files are installed. +Examples: +``` +set MAYA_MODULE_PATH=C:\workspace\install\RelWithDebInfo +export MAYA_MODULE_PATH=/usr/local/workspace/install/RelWithDebInfo +``` +Once MAYA_MODULE_PATH is set, run maya and go to ```Windows -> Setting/Preferences -> Plug-in Manager``` to load the plugins. diff --git a/doc/codingGuidelines.md b/doc/codingGuidelines.md new file mode 100644 index 0000000000..de8a13aa0e --- /dev/null +++ b/doc/codingGuidelines.md @@ -0,0 +1,376 @@ +This document outlines coding guidelines for contributions to the [maya-hydra](https://github.com/autodesk/maya-hydra) project. + +# C++ Coding Guidelines + +Many of the C++ coding guidelines below are validated and enforced through the use of `clang-format` which is provided by the [LLVM project](https://github.com/llvm/llvm-project). Since the adjustments made by `clang-format` can vary from version to version, we standardize this project on a single `clang-format` version to ensure consistent results for all contributions made to maya-hydra. + +| | Version | Source Code | Release | +|:--------------------:|:-------:|:-----------:|:-------:| +| `clang-format`/LLVM | 10.0.0 | [llvmorg-10.0.0 Tag](https://github.com/llvm/llvm-project/tree/llvmorg-10.0.0) | [LLVM 10.0.0](https://github.com/llvm/llvm-project/releases/tag/llvmorg-10.0.0) | + +## Foundation/Critical +### License notice +Every file should start with the Apache 2.0 licensing statement: +```cpp +// 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. +``` + +### Copyright notice +* Every file should contain at least one Copyright line at the top, which can be to Autodesk or the individual/company that contributed the code. +* Multiple copyright lines are allowed, and if a significant new contribution is made to an existing file then an individual or company can append a new line to the copyright section. +* The year the original contribution is made should be included but there is no requirement to update this every year. +* There is no requirement that an Autodesk copyright line should be in all files. If an individual or company contributes new files they do not have to add both their name and Autodesk's name. +* If existing code is being refactored or moved within the repo then the original copyright lines should be maintained. You should only append a new copyright line if significant new changes are added. For example, if some utility code is being moved from a plugin into a common area, and the work involved is only minor name changes or include path updates, then the original copyright should be maintained. In this case, there is no requirement to add a new copyright for the person that handled the refactoring. + +### #pragma once vs include guard +Do not use `#pragma once` to avoid files being `#include’d` multiple times (which can cause duplication of definitions & compilation errors). While it's widely supported, it's a non-standard and non-portable extension. In some cases using `#pragma once` may include two copies of the same file when they are accessible via multiple different paths. + +All code should use include guards instead `#pragma once`. To ensure uniqueness, include guards must include the path to the included header, with path components converted to uppercase, and separated by an underscore. For example, for file lib/mayaHydra/hydraExtensions/sceneIndex/mhMayaSceneIndex.h: +```cpp +#ifndef LIB_MAYAHYDRA_HYDRAEXTENSIONS_SCENEINDEX_MHMAYASCENEINDEX_H +#define LIB_MAYAHYDRA_HYDRAEXTENSIONS_SCENEINDEX_MHMAYASCENEINDEX_H +// … declarations … +#endif // LIB_MAYAHYDRA_HYDRAEXTENSIONS_SCENEINDEX_MHMAYASCENEINDEX_H +``` + +Never use reserved identifiers, like underscore followed immediately by an uppercase letter or double underscore. For example, the following is illegal: +```cpp +// file foobar.h: +#ifndef __LIBRARY_FOOBAR_H +#define __LIBRARY_FOOBAR_H +// … declarations … +#endif // __LIBRARY_FOOBAR_H + +// file foobar2.h: +#ifndef _LIBRARY_FOOBAR2_H +#define _LIBRARY_FOOBAR2_H +// … declarations … +#endif // _LIBRARY_FOOBAR2_H +``` + +### Naming (file, type, variable, constant, function, namespace, macro, template parameters, schema names) +**General Naming Rules** +The _Hydra for Maya_ project strives to use "camel case" naming. That is, each word is capitalized, except possibly the first word: +* UpperCamelCase +* lowerCamelCase + +While underscores in names (_) (e.g., as separator) are not strictly forbidden, they are strongly discouraged. +Optimize for readability by selecting names that are clear to others (e.g., people on different teams.) +Use names that describe the purpose or intent of the object. Do not worry about saving horizontal space. It is more important to make your code easily understandable by others. Minimize the use of abbreviations that would likely be unknown to someone outside your project (especially acronyms and initialisms). +***Project-Related Naming*** +When using Maya Hydra in code, the string MayaHydra or mayaHydra should be used, depending on required capitalization. The string maya-hydra should only be used when referring to the Github repository. + +**Type Names** +All type names (i.e., classes, structs, type aliases, enums, and type template parameters) should use UpperCamelCase, with no underscores. All top-level classes in the MayaHydra namespace should use a short prefix, to categorize them. Nested classes declared inside another class should not have a prefix. The only currently defined prefix is Mh; others will be added as appropriate. For example: +```cpp +class MhSceneIndex; +class MhSceneIndex::Data; +enum Roles; +``` + +**File Names** +Filenames should be lowerCamelCase and should not include underscores (_) or dashes (-). +C++ files should end in .cpp and header files should end in .h. +In general, make your filenames as specific as possible. Where file names declare a single class (which should be the majority of cases), the file name should match the class name, except for the case of the leading character. For example: +``` +mhSceneIndex.cpp +mhSceneIndex.h +``` + +**Variable Names** +Variables names, including function parameters and data members should use lowerCamelCase, with no underscores. For example: +```cpp +const MDagPath& dagPath +const MVector& rayDirection +bool* drawRenderPurpose +``` + +**Class/Struct Data Members** +Non-static data members of classes/structs are named like ordinary non-member variables with leading "_". For example: +```cpp +UsdMayaStageNoticeListener _stageNoticeListener; +std::map _boundingBoxCache; +``` + +For static data members the leading underscore should be omitted. +```cpp +static const MTypeId typeId; +``` + +**Constant Names** +Variables declared constexpr or const, whose value is fixed for the duration of the program, should be named with a leading "k" followed by UpperCamelCase. For example: +```cpp +const int kDaysInAWeek = 7; +const int kMyMagicNumber = 42; +``` + +**Function/Method Names** +All functions should be lowerCamelCase. This avoids inconsistencies with the MayaAPI and virtual methods. For example: +```cpp +MString name() const override; +void registerExitCallback(); +``` + +**Namespace Names** +Namespace names should be UpperCamelCase. Top-level namespace names are based on the project name. +```cpp +namespace MayaAttrs {} +``` +***Use of Pixar Namespace*** +For multiple reasons, some code in the maya-hydra repository places symbols in the Pixar namespace. We intend to move this code to a more appropriate namespace, *e.g.* the MayaHydra namespace. No new code can add symbols to the Pixar namespace. + +When using the Pixar namespace, the PXR_NS macro must be used. This ensures that the maya-hydra code base can be compatible with developers wishing to compile USD code with a Pixar namespace different from the default (pxr). + +**Enumerator Names** +Enumerators (for both scoped and unscoped enums) should be named like constants (i.e., `kEnumName`.) + +The enumeration name, `StringPolicy` is a type and therefore mixed case. +```cpp +enum class StringPolicy +{ + kStringOptional, + kStringMustHaveValue +}; +``` + +**Macro Names** +In general, macros should be avoided (see [Modern C++](https://docs.google.com/document/d/1Jvbpfh2WNzHxGQtjqctZ1K1lnpaAtHOUwm0kmmEcxjY/edit#heading=h.ynbggnv41p3) ). However, if they are absolutely needed, macros should be all capitals, with words separated by underscores. +```cpp +#define ROUND(x) … +#define PI_ROUNDED 3.0 +``` + +**Schema Names** + + +### Documentation (class, method, variable, comments) +* [Doxygen ](http://www.doxygen.nl/index.html) will be used to generate documentation from _Hydra for Maya_ C++ sources. +* Doxygen tags must be used to document classes, function parameters, function return values, and thrown exceptions. +* The _Hydra for Maya_ project does require the use of any Doxygen formatting style ( [Doxygen built-in formatting](http://www.doxygen.nl/manual/commands.html) ) +* Comments for users of classes and functions must be written in headers files. Comments in definition files are meant for contributors and maintainers. + +### Namespaces + +#### In header files (e.g. .h) + +* **Required:** to use fully qualified namespace names. Global scope using directives are not allowed. Inline code can use using directives in implementations, within a scope, when there is no other choice (e.g. when using macros, which are not namespaced). + +```cpp +// In aFile.h +inline PXR_NS::UsdPrim prim() const +{ + PXR_NAMESPACE_USING_DIRECTIVE + TF_AXIOM(fItem != nullptr); + return fItem->prim(); +} +``` + +#### In implementation files (e.g. .cpp) + +* **Recommended:** to use fully qualified namespace names, unless clarity or readability is degraded by use of explicit namespaces, in which case a using directive is acceptable. +* **Recommended:** to use the existing namespace style, and not make gratuitous changes. If the file is using explicit namespaces, new code should follow this style, unless the changes are so significant that clarity or readability is degraded. If the file has one or more using directives, new code should follow this style. + +### Include directive +For source files (.cpp) with an associated header file (.h) that resides in the same directory, it should be `#include`'d with double quotes and no path. This formatting should be followed regardless of with whether the associated header is public or private. For example: +```cpp +// In foobar.cpp +#include "foobar.h" +``` + +All included public header files from outside and inside the project should be `#include`’d using angle brackets. For example: +```cpp +#include +#include +``` + +Private project’s header files should be `#include`'d using double quotes, and a relative path. Private headers may live in the same directory or sub-directories, but they should never be included using "._" or ".._" as part of a relative path. For example: +```cpp +#include "privateUtils.h" +#include "pvt/helperFunctions.h" +``` + +### Include order +Headers should be included in the following order, with each section separated by a blank line and files sorted alphabetically: + +1. Related header +2. All private headers +3. All public headers from this repository (maya-hydra) +4. Pixar + USD headers +5. Autodesk + Maya headers +6. Other libraries' headers +7. C++ standard library headers +8. C system headers +9. Conditional includes + +```cpp +#include "exportTranslator.h" + +#include "private/util.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +``` + +### Cross-platform development +* Consult the [build.md](./build.md) compatibility table to ensure you use the recommended tool (i.e., compiler, operating system, CMake, etc.) versions. +* Before pull requests (PRs) are considered for integration, they must compile and all tests should pass on all suppported platforms. + +### Conditional compilation (Maya, USD version) +**Maya** + * `MAYA_API_VERSION` is the consistent macro to test Maya version (`MAYA_APP_VERSION` * 10000 + `MAJOR_VERSION` * 100 + `MINOR_VERSION`) + * `MAYA_APP_VERSION` is available only since Maya 2019 and is a simple year number, so it is not allowed. + +**USD** + * `PXR_VERSION` is the macro to test USD version (`PXR_MAJOR_VERSION` * 10000 + `PXR_MINOR_VERSION` * 100 + `PXR_PATCH_VERSION`) + +Respect the minimum supported version for Maya and USD stated in [build.md](https://github.com/Autodesk/maya-hydra/blob/dev/doc/build.md) . + +### std over boost +Recent extensions to the C++ standard introduce many features previously only found in [boost](http://boost.org). To avoid introducing additional dependencies, developers should strive to use functionality in the C++ std over boost. If you encounter usage of boost in the code, consider converting this to the equivalent std mechanism. +Our library currently has the following boost dependencies: +* `boost::python` +* `boost::make_shared` (preferable to replace with `std::shared_ptr`) + +***Update:*** +* `boost::filesystem` and `boost::system` are removed. Until the transition to C++17 std::filesystem, [ghc::filesystem](https://github.com/gulrak/filesystem) must be used as an alternative across the project. + + +## Modern C++ +Our goal is to develop [maya-hydra](https://github.com/autodesk/maya-hydra) following modern C++ practices. We’ll follow the [C++ Core Guidelines](http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines) and pay attention to: +* `using` (vs `typedef`) keyword +* `virtual`, `override` and `final` keyword +* `default` and `delete` keywords +* `auto` keyword +* initialization - `{}` +* `nullptr` keyword +* … + +## Diagnostic Facilities + +Developers are encouraged to use TF library diagnostic facilities in Maya USD. Please follow below guideline for picking the correct facility: https://graphics.pixar.com/usd/docs/api/page_tf__diagnostic.html + + +# Coding guidelines for Python +We are adopting the [PEP-8](https://www.python.org/dev/peps/pep-0008) style for Python Code with the following modification: +* Mixed-case for variable and function names are allowed + +[Pylint](https://www.pylint.org/) is recommended for automation. + + +# Coding guidelines for CMake +## Modern CMake +1. Target Build and Usage requirements should be very clear. +* build requirements ( everything that is needed to build the target ) +* usage requirements ( everything that is needed to use this target as a dependency of another target) +2. Always use target_xxx() and make sure to add the PUBLIC/PRIVATE/INTERFACE keywords as appropriate. +* target_sources +* target_compile_definitions +* target_compile_options +* target_include_directories +* target_link_libraries +* ... + +Keyword meanings: +* **PRIVATE**: requirement should apply to just this target. +* **PUBLIC**: requirement should apply to this target and anything that links to it. +* **INTERFACE**: requirement should apply just to things that link to it. + +3. Don't use Macros that affect all targets (e.g add_definitions, link_libraries, include_directories). +4. Prefer functions over macros whenever reasonable. +5. Treat warnings as errors. +6. Use cmake_parse_arguments as the recommended way for parsing the arguments given to the macro or function. +7. Don't use file(GLOB). +8. Be explicit by calling set_target_properties when it's appropriate. +9. Links against Boost or GTest using imported targets rather than variables: +e.g Boost::filesystem, Boost::system, GTest::GTest + +## Compiler features/flags/definitions +1. Setting or appending compiler flags/definitions via CMAKE_CXX_FLAGS is NOT allowed. +2. Any front-end flags (e.g. -Wno-xxx, cxx_std_xx) should be added to cmake/compiler_config.cmake +3. All current targets, as well as newly added targets, must use mayaHydra_compile_config function in order to get the project-wide flags/definitions. These flags/definitions are added privately via target_compile_features, target_compile_options, target_compile_definitions. Individual targets are still allowed to call target_compile_definitions, target_compile_features individually for any additional flags/definitions by providing appropriate PUBLIC/INTERFACE/PRIVATE keywords. For example: +```cmake +# ----------------------------------------------------------------------------- +# compiler configuration +# ----------------------------------------------------------------------------- +add_library(${UFE_PYTHON_TARGET_NAME} SHARED) +target_compile_definitions(${UFE_PYTHON_TARGET_NAME} + PRIVATE + MFB_PACKAGE_NAME=${UFE_PYTHON_MODULE_NAME} + MFB_ALT_PACKAGE_NAME=${UFE_PYTHON_MODULE_NAME} + MFB_PACKAGE_MODULE="${PROJECT_NAME}.${UFE_PYTHON_MODULE_NAME}" +) +mayaHydra_compile_config(${UFE_PYTHON_TARGET_NAME}) +``` + +## Dynamic linking and Run-time Search Path +Use provided mayaUsd_xxx_rpath() utility functions to handle run-time search path for both MacOSX and Linux. + +## Naming Conventions + 1. CMake commands are case-insensitive. Use lower_case for CMake functions and macros, Use upper_case for CMake variables + ```cmake +# e.g cmake functions +add_subdirectory(schemas) +target_compile_definitions(....) + +# e.g cmake variables +${CMAKE_CXX_COMPILER_ID} +${CMAKE_SYSTEM_NAME} +${CMAKE_INSTALL_PREFIX} +``` + + 2. Use upper_case for Option names +```cmake +# e.g for options names +option(BUILD_USDMAYA_SCHEMAS "Build optional schemas." ON) +option(BUILD_TESTS "Build tests." ON) +option(BUILD_MAYAHYDRALIB "Build the Maya-To-Hydra plugin and scene delegate." ON) +``` + +3. Use upper_case for Custom variables +```cmake +# e.g for options names +set(Boost_USE_DEBUG_PYTHON ON) + +set(HEADERS + jobArgs.h + modelKindProcessor.h + readJob.h + writeJob.h +) + +set(RESOURCES_INSTALL_PATH ${CMAKE_INSTALL_PREFIX}/lib/usd/${TARGET_NAME}/resources) + +set(USDTRANSACTION_PYTHON_LIBRARY_LOCATION ${AL_INSTALL_PREFIX}/lib/python/AL/usd/transaction) +``` + +4. Respect third-party variables ( don't change them ) +```cmake +# e.g boost +set(BOOST_ROOT ${pxr_usd_location}) +set(Boost_USE_DEBUG_PYTHON ON) +``` + +5. Avoid adding the optional name in endfunction/endmacro([]). +> Although there is no official guideline for Modern CMake practices, the following resources provide ample information on how to adopt these practices: +> [Modern cmake](https://cliutils.gitlab.io/modern-cmake/), [Effective Modern CMake](https://gist.github.com/mbinna/c61dbb39bca0e4fb7d1f73b0d66a4fd1) diff --git a/doc/hydraSelectionDataSource.png b/doc/hydraSelectionDataSource.png new file mode 100644 index 0000000000..f14ea16b2d Binary files /dev/null and b/doc/hydraSelectionDataSource.png differ diff --git a/doc/hydraSelectionHighlighting.png b/doc/hydraSelectionHighlighting.png new file mode 100644 index 0000000000..1ce4e775ed Binary files /dev/null and b/doc/hydraSelectionHighlighting.png differ diff --git a/doc/hydraSelectionReprDisplayStyle.png b/doc/hydraSelectionReprDisplayStyle.png new file mode 100644 index 0000000000..eda1085506 Binary files /dev/null and b/doc/hydraSelectionReprDisplayStyle.png differ diff --git a/doc/images/al.png b/doc/images/al.png new file mode 100644 index 0000000000..56df5fab2d Binary files /dev/null and b/doc/images/al.png differ diff --git a/doc/images/linux.png b/doc/images/linux.png new file mode 100644 index 0000000000..60b130cbb5 Binary files /dev/null and b/doc/images/linux.png differ diff --git a/doc/images/mac.png b/doc/images/mac.png new file mode 100644 index 0000000000..33fd20967c Binary files /dev/null and b/doc/images/mac.png differ diff --git a/doc/images/pxr.png b/doc/images/pxr.png new file mode 100644 index 0000000000..ceded6a49d Binary files /dev/null and b/doc/images/pxr.png differ diff --git a/doc/images/windows.png b/doc/images/windows.png new file mode 100644 index 0000000000..e23f3e43b3 Binary files /dev/null and b/doc/images/windows.png differ diff --git a/doc/mayaHydraDetails.md b/doc/mayaHydraDetails.md new file mode 100644 index 0000000000..55e9741fa8 --- /dev/null +++ b/doc/mayaHydraDetails.md @@ -0,0 +1,157 @@ +# Introduction to MayaHydra + +The _Hydra for Maya_ project builds on the foundation of the Maya to Hydra (MtoH) plugin, contributed by Luma Pictures. Our goal is to extend this plugin to cover a wider array of use cases, prioritizing performance, robustness, and customizability. + +The MayaHydra code is a Maya plugin using the MRenderOverride API. This API binds the plugin to a Maya viewport window, adds an entry in the Renderer menu to activate the plugin, and allows control of overall viewport rendering. + +### Maya API Additions + +We have begun adding new APIs to Maya to provide efficient access to renderable viewport data. The experimental MDataServerOperation(Maya 2024 onwards) gives an MRenderOverride plugin like MayaHydra access to Viewport 2.0's internal copy of renderable scene data. MayaHydra can use this as an option to render data provided by Viewport 2.0, instead of having to reconstruct internal Maya features to match viewport appearance and the behavior of Maya features. + +### Change Notifications + +The MDataServerOperation API provides change notifications that better match the requirements of a viewport. Without this API, a plugin depends on individual attribute change callback notices, something that scales poorly with large scenes. Instead of these low-level, fine-grained change notices the MayaHydra plugin can leverage the internal change notification system used by vp2. This system boils down different attributes into simpler categories that better align with Hydra HdDirtyBits. This internal change notification system is efficiently and thread-safely integrated with the Maya Evaluation Manager and the EM Caching system. + + +### Hybrid Viewport Rendering +MRenderOverride API allows a plugin interface to execute Hydra rendering from within Maya's viewport model. The MDataServerOperation is stacked with Maya native pre and post scene render passes. The pre and post scene render is controlled by Maya which does the rendering of elements like HUDs, GreasePencils and Manipulators. However, rendering of these elements could be delegated to Hydra in the future. + +```mermaid +graph LR + A[MRenderOverride ] -- MayaRender --> B(MayaHydraPreRender) + A -- Hydra Render --> C(MayaHydraRender) + A -- Maya Render --> D(MayaHydraPostRender) + C --> E[Composited Render] + B --> E + D --> E +``` + +### Color Management(CM) +Maya's Color Managment is applied to all applicable viewports including viewport overrides. MayaHydra plugin override inherits this behavior. So the final frame rendered out from Hydra undergoes Maya's CM. All workflows pertaining to CM within Maya remain the same so user can configure CM with MayaHydra as they would for a VP2 viewport. + +Hydra also support CM internally via Hydra Tasks. In the current version of the plugin, Hydra's CM is not applied. There are a couple of blockers for this to implemented at the moment(5th Oct 2023). USD is on OCIO v1 and Maya uses OCIO v2. There are some incompatibities with these two version and can lead to unpredictable behaviour. + +### Data Access and Interpretation + +Maya contains many different object types and features that affect how they render. To try to replicate that rendering, a plugin viewport would have to access the raw data of each object through node attributes and to reimplement the display logic. This makes supporting a wide range of object types and features too costly. + +Instead of always extracting state from the raw attributes of a Maya node, the MDataServerOperation API provides an abstract view of things to be rendered via the (pre-existing) MRenderItem API. A render item is similar to a Hydra RPrim and roughly corresponds to a single traditional 3D "draw call" in OpenGL or DirectX. It is an association of geometry buffers with an index buffer, a primitive type (triangles, lines, points), a shader, and shader parameters (transform matrices, colors, textures, etc.) The MRenderItem wraps an internal renderable object that VP2 uses internally to draw. + +One effect of the MRenderItem approach is that the plugin viewport no longer needs to care about the details of most Maya node types. For something like a Maya joint node, the original attribute approach involved writing an adapter class that manages all the attributes for the type and its interactions with other DAG objects. In the MRenderItem approach, the plugin doesn't necessarily need any joint-specific code. Instead, it receives an MRenderItem with the line geometry and color information, which is enough to render and support selection. In this way, we leverage the existing Maya viewport code that implements the "business logic" that decides what a joint looks like, while delegating rendering control to the Hydra framework. Here the Maya internal viewport code functions as a data source, serving primitives to Hydra. + +### MRenderItem Method Can be Optional + +The MRenderItem approach might not make sense in all circumstances, and can be optional. The lead case is that mesh geometry buffers might work better using the MFnMesh interface. This is the approach the original MtoH code used. The reason is that the default geometry format that the Maya viewport translation code produces has been triangulated and re-indexed so that all geometry streams share a single index buffer. But Hydra, and especially Storm, prefer untriangulated face data. For the simple default case of drawing a mesh, possibly with its full wireframe displayed (and nothing else), the value of the render item system is unclear. We can bypass the render items and provide raw face data using the MFnMesh extraction method from the original MtoH class HdMayaMeshAdapter, skipping normal buffer generation and re-indexing. This option is likely to be simpler and more efficient in the default case for meshes. When MRenderItems are needed for meshes using extra display modes, we can combine those approaches. We expect animation workflows to fit better with the raw face data approach and modelling workflows to require the MRenderItem approach. This requires more experimentation to validate these assumptions. + +We have the flexibility needed to handle other object types outside the MRenderItem system. This can also be a customization point for other custom sources of data. + +### Adapters for direct translation of Maya data to Hydra + +MayaHydra utilizes USD's PluginRegistry mechanism to translate Maya native data to Hydra. Maya meshes/shapes, camera, lights(spot,area, directional), NURBS Curves, materials and, as a special case, Arnold's SkyDomeLight are handled by these adapaters. MRenderItem described above is also handled by this plugin registry adapter mechanism. + +```mermaid +classDiagram +class Base Adapter { + <> + Shape Adapter + Mesh Adapter + Light Adapter + NURBS Curve Adapter + RenderItem Adapter + Material Adapter +} +``` +### Optional Adapter to Maya meshes + +It was mentioned earlier that the MRenderItem usage is optional. MayaHydra can be told to use native Maya mesh data instead of MRenderItem by setting the environment variable ```MAYA_HYDRA_USE_MESH_ADAPTER``` to 1. + +### Integration with MayaUSD and Other Maya Plugin Nodes Using Scene Indices + +The maya-usd plugin provides access to USD data files inside Maya by injecting that data into Maya, for Viewport 2.0 to render. It extracts that data from USD and tweaks it for compatibility with Maya features via a custom Hydra render delegate. This method doesn't work when the aim is to draw with Hydra Storm or a different render delegate, so we are exploring new methods to integrate MayaHydra with MayaUSD. MayaUSD continues to facilitate the USD data source (through the MayaUsdProxyShape Maya node) and the editing of that data. MayaHydra controls the rendering of that data. + +Experimentally, we have separated the two plugins so that they no longer link together. We use USD's HdSceneIndexPluginRegistry interface to query for a registered HdSceneIndex provider for a Maya node, using a naming convention based on the node type name. Our hope is that this can also be the entry point for third-party plugins to control the viewport rendering of custom node types (MPxLocatorNode or MPxSurfaceShape) purely through the Hydra API instead of through Maya's various viewport APIs. This could potentially replace the Maya APIs MPxSubsceneOverride, MPxDrawOverride, and MPxGeometryOverride. + +### Scene Delegate vs Scene Index +MayaHydra's long term plan is to migrate to using Scene Index completely and remove dependency on SceneDelegates. This aligns with the Pixar's recommendation as Hydra will eventually deprecate Scene Delegate support. During this transition phase, we are supporting both Scene Delegate and Scene Indices. By default, Scene Index is used but it can be overridden to use Scene Delegate using the environment variable ```MAYA_HYDRA_ENABLE_NATIVE_SCENE_INDEX``` to 0. Please note that Scene Delegate mode will not be actively supported and the Scene Index mode is the recommended one. + +### Plugin initialization, render loop and data flow through the plugin. +As described in the section above, the plugin hooks into Maya Viewport through the MRenderOverride API. + +A class diagram with some of the key members looks something like this. For sake of brevity only a few members are listed. Please refer to accompanying code for details. +```mermaid +classDiagram + + class MtohRenderOverride { + -_InitHydraResources() + -_sceneIndexRegistry: std::shared_ptr + -_hgi: HgiUniquePtr + -_hgiDriver: HdDriver + -_engine: HdEngine + -_rendererPlugin: HdRendererPlugin* + -_taskController: HdxTaskController* + -_renderDelegate: HdPluginRenderDelegateUniqueHandle + -_renderIndexProxy: std::unique_ptr + -_renderIndex: HdRenderIndex* + -_fvpSelectionTracker: Fvp::SelectionTrackerSharedPtr + -_selectionSceneIndex: Fvp::SelectionSceneIndexRefPtr + -_mayaHydraSceneProducer: std::unique_ptr + +Render(const MHWRender::MDrawContext& drawContext, const MHWRender::MDataServerOperation::MViewportScene& scene) + } +``` +The class is contains resources pertaining to both Maya and Hydra. + +We will do a high-level walk through of various execution phases, render loop and corresponding data flow. Please note the plugin is undergoing major design changes with newer APIs being introduced. The diagrams below are relevant as of 5th October 2023. + +### Plugin Load +```mermaid +sequenceDiagram + participant MayaHydra as "MayaHydra Plugin" + participant RendererPlugins as "Renderer Plugins" + participant RenderSettingsUI as "Render Settings and UI" + participant MtohRenderOverride as "MtohRenderOverride" + + MayaHydra->>RendererPlugins: Load Plugin + RendererPlugins-->>MayaHydra: Plugins Loaded + MayaHydra->>RendererPlugins: Search for Renderer Plugins + RendererPlugins-->>MayaHydra: Renderer Plugins Found + + loop for each Renderer Plugin + MayaHydra->>RenderSettingsUI: Build Render Settings and UI + RenderSettingsUI-->>MayaHydra: Render Settings and UI Built + end + + MayaHydra->>MtohRenderOverride: Register MtohRenderOverride with Maya + MtohRenderOverride-->>MayaHydra: MtohRenderOverride Registered +``` +### The Render Loop +```mermaid + +stateDiagram + [MayaFrameRefresh] --> MtohRenderOverride + MtohRenderOverride --> InitHydraResources() : Render() + + state InitHydraResources() { + [*] --> HydraResources + HydraResources --> MayaHydraSceneProducer() : RenderDelegate/RenderIndex + MayaHydraSceneProducer() --> Populate() : Creates MayaHydra specific Scene Indices internally + Populate() --> MayaHydraAdapter : Loop over Maya native nodes + MayaHydraAdapter --> SceneIndexRegistration : Flow Viewport API (WIP) to inject various Scene Indices including USD data and SelectionHighlighting + SceneIndexRegistration --> [*] + } + + state MayaHydraAdapter { + [*] --> InsertHydraPrims : Shape/Mesh/Light/Material/ArnoldDomelight + InsertHydraPrims --> [*] + } + + InitHydraResources() --> HandleCompleteViewportScene() : Loops over MRenderItems using DataServer API + state RenderItemAdapter { + [*] --> InsertHydraPrim + InsertHydraPrim --> [*] + } + HandleCompleteViewportScene() --> RenderItemAdapter : Handles VP2 updates + RenderItemAdapter --> UpdateDirtiedPrims + UpdateDirtiedPrims --> SetHydraRenderParams : Global values obtained from VP2 and Maya RenderSettings + SetHydraRenderParams --> HydraExecute() + HydraExecute() --> [MayaFrameRefresh] +``` \ No newline at end of file diff --git a/doc/selectionHighlightingArchitecture.md b/doc/selectionHighlightingArchitecture.md new file mode 100644 index 0000000000..7603f2fa34 --- /dev/null +++ b/doc/selectionHighlightingArchitecture.md @@ -0,0 +1,369 @@ +# Selection Highlighting Architecture + +Selection highlighting changes the in-viewport appearance of selected objects +or object components. + +In the following document we will refer to the software infrastructure that +supports Hydra rendering in this repository as the Flow Viewport library (name +subject to change). + +This document will describe the state of Flow Viewport library selection +highlighting as of 21-Sep-2023. + +## Behavior + +Applications maintain a set of selected objects that the user can add to and +remove from. Selected objects are usually the target of user operations, and +are shown differently in the viewport for ease of understanding. + +An application will provide a way to select an object, or to select components +of an object. For example, for a mesh object, these components may be points, +edges, or faces. At time of writing, only object selection highlighting is +supported, and selection highlighting of components is unimplemented. + +## Selection: Application versus Hydra + +The application maintains an edit-friendly version of the scene. This scene is +translated into a Hydra scene by scene indices. Correspondingly, there are two +versions of the selection, one in the application, with objects and their paths +described with application-specific classes, and a version of the selection in +the Hydra scene, described as prims and their `SdfPath`s + +## Requirements + +Requirements for selection highlighting are: + +- It must be possible to provide selection highlighting in an application that + supports multiple data models (e.g. Maya data and USD data), and plugins to + those data models (e.g. Maya plugin nodes). + +- Selected prims in the Hydra scene index tree must contain a data source + indicating their selection state. This data source is the one used by Pixar + in Hydra code, and thus is used in usdview. + +- Data injecting plugin scene indices must be able to specify their selection + highlight appearance. + +- It must be possible to let a data injecting data model provide prims to Hydra + that already contain selection highlighting. At time of writing + (19-Sep-2023), this is true of Maya native Dag data, where selection + highlighting is done by OGS. + +## Selection Highlighting Styles + +There are at least two approaches to selection highlighting: +- **Added geometry**: adding secondary geometry that indicates the selected +status of objects in the scene, e.g. wireframe or bounding box. +- **Pixed-based modified object appearance**: rendering selected objects in a +special way, e.g. object contour, modified object color, or object overlay. + +The former approach is handled by having a plugin provide a selection +highlighting filtering scene index to the Flow Viewport library, and is the +topic of this document at time of writing (20-Sep-2023). The +latter is handled by having a plugin provide a selection highlighting +task to the Flow Viewport library, and is currently unimplemented. + +## Added Geometry Plugin Software Architecture Requirements + +A selection highlighting plugin that provides added geometry to scene must +provide the following services: + +- A way to translate the application's selection path(s) into Hydra paths: + - So that the appropriate prims in Hydra can be dirtied on selection change. + - So that selected prims in Hydra can have a data source added. + This is embodied in a **Path interface**. + +- A way for the plugin to query the Hydra version of the application + selection: + - So that the plugin can add the appropriate selection highlighting + geometry on those prims that require it, e.g. a different color for the + first selected object, or selection highlighting for a complete hierarchy. + +## Sample Code +### Selection Change + +The following selection change code shows the use of the *Path Interface*, +through the *SceneIndexPath()* method, called on the input scene index. The +path interface allows the selection scene index to translate selected +application paths to selected Hydra scene index paths. +``` +void +SelectionSceneIndex::AddSelection(const Ufe::Path& appPath) +{ + TF_DEBUG(FVP_SELECTION_SCENE_INDEX) + .Msg("SelectionSceneIndex::AddSelection(const Ufe::Path& %s) called.\n", Ufe::PathString::string(appPath).c_str()); + + HdSelectionSchema::Builder selectionBuilder; + selectionBuilder.SetFullySelected( + HdRetainedTypedSampledDataSource::New(true)); + + // Call our input scene index to convert the application path to a scene + // index path. + auto sceneIndexPath = _inputSceneIndexPathInterface->SceneIndexPath(appPath); + + TF_DEBUG(FVP_SELECTION_SCENE_INDEX) + .Msg(" Adding %s to the Hydra selection.\n", sceneIndexPath.GetText()); + + _selection->pathToState[sceneIndexPath].selectionSources.push_back( + selectionBuilder.Build()); + + _SendPrimsDirtied({{sceneIndexPath, locators}}); +} +``` + +### Wireframe Selection Highlighting + +The following wireframe selection highlighting code shows the use of the +*Selection Interface*, through the *HasFullySelectedAncestorInclusive()* +method, called on the input scene index. The selection interface allows a +selection highlighting filtering scene index to query selected prims. +``` +bool WireframeSelectionHighlightSceneIndex::HasFullySelectedAncestorInclusive(const SdfPath& primPath) const +{ + return _inputSceneIndexSelectionInterface->HasFullySelectedAncestorInclusive(primPath); +} + +HdSceneIndexPrim +WireframeSelectionHighlightSceneIndex::GetPrim(const SdfPath &primPath) const +{ + TF_DEBUG(FVP_WIREFRAME_SELECTION_HIGHLIGHT_SCENE_INDEX) + .Msg("WireframeSelectionHighlightSceneIndex::GetPrim(%s) called.\n", primPath.GetText()); + + auto prim = _GetInputSceneIndex()->GetPrim(primPath); + + // If this isn't one of our prims, we're not responsible for providing a + // selection highlight for it. + if (primPath.HasPrefix(_sceneRoot) && + prim.primType == HdPrimTypeTokens->mesh) { + prim.dataSource = HdOverlayContainerDataSource::New( + { prim.dataSource, HasFullySelectedAncestorInclusive(primPath) ? + sSelectedDisplayStyleDataSource : + sUnselectedDisplayStyleDataSource }); + } + return prim; + +} + +void +WireframeSelectionHighlightSceneIndex::_PrimsDirtied( + const HdSceneIndexBase &sender, + const HdSceneIndexObserver::DirtiedPrimEntries &entries) +{ + TF_DEBUG(FVP_WIREFRAME_SELECTION_HIGHLIGHT_SCENE_INDEX) + .Msg("WireframeSelectionHighlightSceneIndex::_PrimsDirtied() called.\n"); + + HdSceneIndexObserver::DirtiedPrimEntries highlightEntries; + for (const auto& entry : entries) { + // If the dirtied prim isn't one of ours, we're not responsible for + // providing a selection highlight for it. + if (entry.primPath.HasPrefix(_sceneRoot) && + entry.dirtyLocators.Contains( + HdSelectionsSchema::GetDefaultLocator())) { + TF_DEBUG(FVP_WIREFRAME_SELECTION_HIGHLIGHT_SCENE_INDEX) + .Msg(" %s selections locator dirty.\n", entry.primPath.GetText()); + // All mesh prims recursively under the selection dirty prim have a + // dirty wireframe selection highlight. + dirtySelectionHighlightRecursive(entry.primPath, &highlightEntries); + } + } + + if (!highlightEntries.empty()) { + // Append all incoming dirty entries. + highlightEntries.reserve(highlightEntries.size()+entries.size()); + highlightEntries.insert( + highlightEntries.end(), entries.begin(), entries.end()); + _SendPrimsDirtied(highlightEntries); + } + else { + _SendPrimsDirtied(entries); + } +} +``` + +## Design Option Discussion + +- **Plugins use Hydra selection, not application selection**: selection + highlighting plugins should only deal with the Hydra view of the selection, + not the application view. Selected objects should be Hydra prims, and paths + to them described as `SdfPath`. This keeps plugins independent of any + particular application's representation of selection. + +- **Plugins access the Hydra selection through the scene index tree**: although + the selection is conceptually a singleton, we will provide access to it for + scene index selection highlighting plugins through the scene index tree, by + adding a mixin interface to the plugins. This avoids creating another object + to maintain and encapsulate the Hydra selection, since the selection scene + index is already performing this job. + +## Implementation + +### Hydra Scene and Viewport Selection Result + +The resulting wireframe selection highlighting of USD data is shown here: +![In-viewport wireframe selection highlighting](hydraSelectionHighlighting.png) + +The resulting prim selection data source is shown here: +![Prim selection data source](hydraSelectionDataSource.png) + +The resulting prim wireframe display style data source is shown here: +![Prim wireframe display style data source](hydraSelectionReprDisplayStyle.png) + +### Flow Viewport Library + +The complete implementation of selection highlighting is done in a new library +in the maya-hydra repository. The library is called `flowViewport`, under the +`lib` directory. The library supports [semantic +versioning](https:/semver.org), with classes in a `Fvp` namespace. +The `Fvp` namespace actually contains the flowViewport major version. All +flowViewport files have an `fvp` prefix. + +The `mayaHydraLib` library and the `mayaHydra` plugin both directly depend on +`flowViewport`. + +Having the selection highlighting code in a separate library promotes +reusability and enforces separation of concerns. + +### Added Geometry Selection Highlighting Through Scene Indices + +The Hydra scene index tree was chosen to implement selection highlighting +through additional geometry. This is because a scene index can inject +additional prims into the scene, modify data sources of prims in the scene, +and dirty prims in the scene whose selection status has changed. + +The scene index tree is now the following: +```mermaid +graph BT; + lgcy[Scene delegate legacy]-->hm[Hydra builtin merge]; + ph1[Plugin highlighting 1]-->hm; + subgraph ph[Plugin highlighting] + ph2[Plugin highlighting 2]-->ph1; + ph1-. Selection .->ph2; + end + sn[Selection scene index]-->ph2; + ph2-. Selection .->sn; + fvpm[Flow Viewport merge]-->sn; + subgraph pd[Plugin data] + p1[Plugin 1]; + p2[Plugin 2]; + end + fvpm-. Path .->p1; + p1-->fvpm; + p2-->fvpm; + sn-. Path .->fvpm; +``` +The plugin data and plugin highlighting subtrees are where plugins add their +scene indices. The data scene index is required, and the highlighting scene +index is optional. + +### Object Modeling + +The object modeling is the following: +- **Selection scene index**: builtin provided by the Flow Viewport library. + - Owns and encapsulates the Hydra selection. + - Translates the application selection to Hydra selection. + - Derives from and implements the selection interface. +- **Flow Viewport merging scene index**: builtin provided by the Flow Viewport + library. + - Receives data from data provider plugin scene indices. + - Forward path interface queries to plugin scene indices +- **Plugin data scene index**: provided by plugin. + - Injects plugin data into Hydra +- **Plugin selection highlighting scene index**: provided by plugin. + - Processes dirty selection notifications to dirty the appropriate prim(s) + in plugin data, including hierarchical selection highlighting + - Adds required geometry or data sources to implement selection + highlighting + +### New Scene Index Mixin Interface Base Classes + +The Flow Viewport library has two new mixin interface classes: + +- **Path Interface**: so that the builtin selection scene index can query + plugins to translate selected object application paths to selected Hydra + prim paths. The plugin provides the concrete implementation of this + interface. +- **Selection Interface**: so that the plugin selection highlighting scene + indices can query the selection scene index for selected object status. + Plugins provide a pass-through implementation, and the selection scene index + provides the implementation. + +### Implementation Classes + +- **Wireframe selection highlighting scene index**: + - Uses Hydra HdRepr to add wireframe representation to selected objects + *and their descendants*. + - Requires selected ancestor query from selection interface. + - Dirties descendants on selection dirty. +- **Render index proxy**: + - Provides encapsulated access to the builtin Flow Viewport merging scene + index. + - Other responsibilities to be determined, for future extension, possibly a + [facade design pattern](https://en.wikipedia.org/wiki/Facade_pattern). + +### Class Diagram + +```mermaid +classDiagram +class HdSingleInputFilteringSceneIndexBase +class HdMergingSceneIndex + +class SelectionInterface{ ++IsFullySelected(SdfPath) bool ++HasFullySelectedAncestorInclusive(SdfPath) bool +} + +class PathInterface{ ++SceneIndexPath(Ufe::Path) SdfPath +} + +class SelectionSceneIndex +class MergingSceneIndex +class WireframeSelectionHighlightSceneIndex + +class RenderIndexProxy{ ++MergingSceneIndex mergingSceneIndex ++InsertSceneIndex() ++RemoveSceneIndex() +} + +HdSingleInputFilteringSceneIndexBase <|-- SelectionSceneIndex +SelectionInterface <|-- SelectionSceneIndex + +HdMergingSceneIndex <|-- MergingSceneIndex +PathInterface <|-- MergingSceneIndex + +HdSingleInputFilteringSceneIndexBase <|-- WireframeSelectionHighlightSceneIndex +SelectionInterface <|-- WireframeSelectionHighlightSceneIndex + +RenderIndexProxy *-- MergingSceneIndex : Owns + +WireframeSelectionHighlightSceneIndex ..> SelectionSceneIndex : Selected +SelectionSceneIndex ..> MergingSceneIndex : Path +``` + +## Algorithmic Complexity + +- At time of writing, for an n-element selection, membership lookup is O(log n) + (map of SdfPath). Ancestor membership lookup is O(n), as we loop through + each selected path and inspect the selected path prefix. This could be much + improved (to amortized O(k), for a k-element path) through the use of a + prefix trie, such as `Ufe::Trie`. + +- At time of writing, merging scene index path lookup is O(n), for n input + scene indices. This could be improved by implementing a caching scheme based + on application path, as for a given application path prefix the same + input scene index will always provide the translation to scene index path. + +## Limitations + +- Little investigation of pixel-based selection highlighting capability. + - Needs task-based approach. + - Needs selection tracker object to make selection and data derived from + the selection available to tasks through the task context data + +- No selection highlighting across scene indices: selection state propagates + down app scene hierarchy, so that when an ancestor is selected, a + descendant's appearance may change. This can mean selection state must + propagate across scene index inputs, so that if a Maya Dag ancestor is + selected, a USD descendant's appearance can change. This is the same + situation as global transformation and visibility. diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt new file mode 100644 index 0000000000..0688c953f6 --- /dev/null +++ b/lib/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(mayaHydra) +add_subdirectory(flowViewport) diff --git a/lib/adskHydraSceneBrowser/CMakeLists.txt b/lib/adskHydraSceneBrowser/CMakeLists.txt new file mode 100644 index 0000000000..d028c133e5 --- /dev/null +++ b/lib/adskHydraSceneBrowser/CMakeLists.txt @@ -0,0 +1,4 @@ +add_subdirectory(lib) +if (BUILD_TESTS) + add_subdirectory(test) +endif() diff --git a/lib/adskHydraSceneBrowser/lib/CMakeLists.txt b/lib/adskHydraSceneBrowser/lib/CMakeLists.txt new file mode 100644 index 0000000000..67ff7c11c8 --- /dev/null +++ b/lib/adskHydraSceneBrowser/lib/CMakeLists.txt @@ -0,0 +1,177 @@ +# ----------------------------------------------------------------------------- +# set target name +# ----------------------------------------------------------------------------- +set(TARGET_NAME adskHydraSceneBrowser) + +# ----------------------------------------------------------------------------- +# Qt safeguard +# ----------------------------------------------------------------------------- +if (NOT Qt6_FOUND) + message(WARNING "No Qt6 package found. Cannot build ${TARGET_NAME}.") + return() +endif() + +# ----------------------------------------------------------------------------- +# add library +# ----------------------------------------------------------------------------- +add_library(${TARGET_NAME} SHARED adskHydraSceneBrowserApi.h) + +# ----------------------------------------------------------------------------- +# setup sources +# ----------------------------------------------------------------------------- +set(HEADERS + dataSourceTreeWidget.h + dataSourceValueTreeView.h + registeredSceneIndexChooser.h + sceneIndexDebuggerWidget.h + sceneIndexObserverLoggingTreeView.h + sceneIndexObserverLoggingWidget.h + sceneIndexTreeWidget.h +) +set(SOURCES + dataSourceTreeWidget.cpp + dataSourceValueTreeView.cpp + registeredSceneIndexChooser.cpp + sceneIndexDebuggerWidget.cpp + sceneIndexObserverLoggingTreeView.cpp + sceneIndexObserverLoggingWidget.cpp + sceneIndexTreeWidget.cpp +) + +# Get the versioning scheme used in the file download URLs : last two digits of the year + digits of the month +# We first strip the major version (e.g. 0.23.8 -> 23.8) +string(REGEX REPLACE "^[0-9]+\\.([0-9]+\\.[0-9]+)$" "\\1" USD_minor_patch_version ${USD_VERSION}) +# We then re-insert leading 0s to single-digit numbers (e.g. 23.8 -> 23.08) +# (note that \\10 works because CMake only captures up to 9 subgroups) +string(REGEX REPLACE "(\\.|^)([1-9])(\\.|$)" "\\10\\2\\3" USD_minor_patch_version ${USD_minor_patch_version}) +set(DOWNLOAD_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/src/download") +set(PATCH_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/src/patch") + +macro(download file) + file(DOWNLOAD + "https://raw.githubusercontent.com/PixarAnimationStudios/OpenUSD/v${USD_minor_patch_version}/extras/imaging/examples/hdui/${file}" + "${DOWNLOAD_LOCATION}/${file}") +endmacro() + +# Insert an include directive at the top a file. +macro(prepend_include FILE_CONTENTS INCLUDE_DIRECTIVE) + set(${FILE_CONTENTS} "${INCLUDE_DIRECTIVE}\n${${FILE_CONTENTS}}") +endmacro() + +# Regex to match a valid variable or class name +set(IDENTIFIER_REGEX [a-zA-Z_][a-zA-Z_0-9]*) + +# Download and patch source files +foreach(SOURCE IN ITEMS ${SOURCES}) + download(${SOURCE}) + + file(READ "${DOWNLOAD_LOCATION}/${SOURCE}" FILE_CONTENTS) + + # We can't construct a QVariant from a C string in Qt6. What we can do however is construct a QVariant + # from a QLatin1StringView constructed with a C string. To patch this in we do the following replacements: + # "QVariant(someVariable.str().c_str())" -> "QVariant(QLatin1StringView(someVariable.str().c_str()))" + # "QVariant(someVariable.str().data())" -> "QVariant(QLatin1StringView(someVariable.str().data()))" + string(REGEX REPLACE "QVariant\\(\(${IDENTIFIER_REGEX}\)\.str\\(\\)\.\(c_str|data\)\\(\\)\\)" "QVariant(QLatin1StringView(\\1.str().\\2()))" FILE_CONTENTS "${FILE_CONTENTS}") + + # Patch in '#include ' to make sure we can use QLatin1StringView + prepend_include(FILE_CONTENTS "#include ") + + # Remove an unused lambda capture to fix a warning-treated-as-error on MacOS + string(REGEX REPLACE "\(\\[this, menu\), menuTreeWidget\(\\]\\(QTreeWidgetItem \\*item, int column\\)\)" "\\1\\2" FILE_CONTENTS "${FILE_CONTENTS}") + + file(WRITE "${PATCH_LOCATION}/${SOURCE}" "${FILE_CONTENTS}") +endforeach() + +# Download and patch header files +foreach(HEADER IN ITEMS ${HEADERS}) + download(${HEADER}) + + file(READ "${DOWNLOAD_LOCATION}/${HEADER}" FILE_CONTENTS) + + # Add DLL import/export support for Windows + # Insert 'HDUI_API' symbols for class declarations + # Note: we assume class declarations are always followed by double colons + string(REGEX REPLACE "class (${IDENTIFIER_REGEX}[^\;]:)" "class HDUI_API \\1" FILE_CONTENTS "${FILE_CONTENTS}") + + # Patch in '#include ' to have HDUI_API defined + prepend_include(FILE_CONTENTS "#include ") + + file(WRITE "${PATCH_LOCATION}/${HEADER}" "${FILE_CONTENTS}") +endforeach() + +# Prepend an absolute path to the downloaded source files, located in +# the CMAKE_CURRENT_BINARY_DIR. +# Note: the sources are downloaded for PixarAnimationStudios/OpenUSD and +# are not located in this repository's folder +list(TRANSFORM SOURCES PREPEND "${PATCH_LOCATION}/") + +# Add CMAKE_CURRENT_BINARY_DIR downloaded sources to the adskHydraSceneBrowser target +target_sources(${TARGET_NAME} + PRIVATE + ${SOURCES} +) + +# ----------------------------------------------------------------------------- +# include directories +# ----------------------------------------------------------------------------- +target_include_directories(${TARGET_NAME} + PUBLIC + ${PXR_INCLUDE_DIRS} + ${CMAKE_CURRENT_SOURCE_DIR} + ${PATCH_LOCATION} +) + +# ----------------------------------------------------------------------------- +# compiler configuration +# ----------------------------------------------------------------------------- +# QT_NO_KEYWORDS prevents Qt from defining the foreach, signals, slots and emit macros. +# this avoids overlap between Qt macros and boost, and enforces using Q_ macros. +set_target_properties(Qt6::Core PROPERTIES INTERFACE_COMPILE_DEFINITIONS QT_NO_KEYWORDS) + +target_compile_definitions(${TARGET_NAME} + PUBLIC + $<$:TBB_USE_DEBUG> + $<$:BOOST_DEBUG_PYTHON> + $<$:BOOST_LINKING_PYTHON> + PRIVATE + HDUI_EXPORT +) + +mayaHydra_compile_config(${TARGET_NAME}) # TODO : This will have to be changed when moving to a standalone repo. + +# ----------------------------------------------------------------------------- +# link libraries +# ----------------------------------------------------------------------------- +target_link_libraries(${TARGET_NAME} + PUBLIC + usd + usdImaging + Qt6::Core + Qt6::Widgets +) + +# ----------------------------------------------------------------------------- +# Qt file processing (moc) +# ----------------------------------------------------------------------------- +set_property(TARGET adskHydraSceneBrowser PROPERTY AUTOMOC ON) + +# ----------------------------------------------------------------------------- +# install +# ----------------------------------------------------------------------------- +install(TARGETS ${TARGET_NAME} + LIBRARY + DESTINATION ${CMAKE_INSTALL_PREFIX}/lib + RUNTIME + DESTINATION ${CMAKE_INSTALL_PREFIX}/lib +) + +list(TRANSFORM HEADERS PREPEND "${PATCH_LOCATION}/") +install(FILES ${HEADERS} + DESTINATION + ${CMAKE_INSTALL_PREFIX}/include/adskHydraSceneBrowser +) + +if(IS_WINDOWS) + install(FILES $ + DESTINATION ${CMAKE_INSTALL_PREFIX}/lib OPTIONAL) +endif() diff --git a/lib/adskHydraSceneBrowser/lib/adskHydraSceneBrowserApi.h b/lib/adskHydraSceneBrowser/lib/adskHydraSceneBrowserApi.h new file mode 100644 index 0000000000..4a066d1b3f --- /dev/null +++ b/lib/adskHydraSceneBrowser/lib/adskHydraSceneBrowserApi.h @@ -0,0 +1,39 @@ +// +// Copyright 2023 Autodesk +// +// 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 defined _WIN32 || defined __CYGWIN__ + +// The main export symbol used for the UI library. +#ifdef HDUI_EXPORT +#ifdef __GNUC__ +#define HDUI_API __attribute__((dllexport)) +#else +#define HDUI_API __declspec(dllexport) +#endif +#else +#ifdef __GNUC__ +#define HDUI_API __attribute__((dllimport)) +#else +#define HDUI_API __declspec(dllimport) +#endif +#endif + +#else +#if __GNUC__ >= 4 +#define HDUI_API __attribute__((visibility("default"))) +#else +#define HDUI_API +#endif +#endif diff --git a/lib/adskHydraSceneBrowser/test/CMakeLists.txt b/lib/adskHydraSceneBrowser/test/CMakeLists.txt new file mode 100644 index 0000000000..1895d92e6b --- /dev/null +++ b/lib/adskHydraSceneBrowser/test/CMakeLists.txt @@ -0,0 +1,106 @@ +# ----------------------------------------------------------------------------- +# set target name +# ----------------------------------------------------------------------------- +set(TARGET_NAME adskHydraSceneBrowserTesting) + +# ----------------------------------------------------------------------------- +# Qt safeguard +# ----------------------------------------------------------------------------- +if (NOT Qt6_FOUND) + message(WARNING "No Qt6 package found. Cannot build ${TARGET_NAME}.") + return() +endif() + +# ----------------------------------------------------------------------------- +# prerequisites +# ----------------------------------------------------------------------------- +find_package(GTest REQUIRED) + +# ----------------------------------------------------------------------------- +# add library +# ----------------------------------------------------------------------------- +add_library(${TARGET_NAME} SHARED) + +# ----------------------------------------------------------------------------- +# file lists +# ----------------------------------------------------------------------------- +set(SHIPPED_HEADERS + adskHydraSceneBrowserTesting.h +) + +set(SOURCES + adskHydraSceneBrowserTestFixture.cpp + adskHydraSceneBrowserTesting.cpp +) + +# ----------------------------------------------------------------------------- +# compiler configuration +# ----------------------------------------------------------------------------- +mayaHydra_compile_config(${TARGET_NAME}) # TODO : This will have to be changed when moving to a standalone repo. + +target_compile_definitions(${TARGET_NAME} + PUBLIC + $<$:LINUX> + $<$:OSMac_> + $<$:GTEST_LINKED_AS_SHARED_LIBRARY> + $<$:TBB_USE_DEBUG> + PRIVATE + HDUITEST_EXPORT +) + +# ----------------------------------------------------------------------------- +# include directories +# ----------------------------------------------------------------------------- +target_include_directories(${TARGET_NAME} + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} + ${GTEST_INCLUDE_DIRS} +) + +# ----------------------------------------------------------------------------- +# sources +# ----------------------------------------------------------------------------- +target_sources(${TARGET_NAME} + PRIVATE + ${SOURCES} +) + +# ----------------------------------------------------------------------------- +# link libraries +# ----------------------------------------------------------------------------- +target_link_libraries(${TARGET_NAME} + PUBLIC + adskHydraSceneBrowser + ${GTEST_LIBRARIES} +) + +# ----------------------------------------------------------------------------- +# runtime search paths +# ----------------------------------------------------------------------------- +# TODO : This will have to be changed when moving to a standalone repo. +if(IS_MACOSX OR IS_LINUX) + mayaUsd_init_rpath(rpath "lib") + mayaUsd_add_rpath(rpath "${CMAKE_INSTALL_PREFIX}/lib/gtest") + mayaUsd_install_rpath(rpath ${TARGET_NAME}) +endif() + +# ----------------------------------------------------------------------------- +# install +# ----------------------------------------------------------------------------- +install(TARGETS ${TARGET_NAME} + LIBRARY + DESTINATION ${CMAKE_INSTALL_PREFIX}/lib + RUNTIME + DESTINATION ${CMAKE_INSTALL_PREFIX}/lib +) + +list(TRANSFORM SHIPPED_HEADERS PREPEND "${CMAKE_CURRENT_SOURCE_DIR}/") +install(FILES ${SHIPPED_HEADERS} + DESTINATION + ${CMAKE_INSTALL_PREFIX}/include/adskHydraSceneBrowserTesting +) + +if(IS_WINDOWS) + install(FILES $ + DESTINATION ${CMAKE_INSTALL_PREFIX}/lib OPTIONAL) +endif() diff --git a/lib/adskHydraSceneBrowser/test/adskHydraSceneBrowserTestApi.h b/lib/adskHydraSceneBrowser/test/adskHydraSceneBrowserTestApi.h new file mode 100644 index 0000000000..7b3c407f4f --- /dev/null +++ b/lib/adskHydraSceneBrowser/test/adskHydraSceneBrowserTestApi.h @@ -0,0 +1,39 @@ +// +// Copyright 2023 Autodesk +// +// 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 defined _WIN32 || defined __CYGWIN__ + +// The main export symbol used for the UI test library. +#ifdef HDUITEST_EXPORT +#ifdef __GNUC__ +#define HDUITEST_API __attribute__((dllexport)) +#else +#define HDUITEST_API __declspec(dllexport) +#endif +#else +#ifdef __GNUC__ +#define HDUITEST_API __attribute__((dllimport)) +#else +#define HDUITEST_API __declspec(dllimport) +#endif +#endif + +#else +#if __GNUC__ >= 4 +#define HDUITEST_API __attribute__((visibility("default"))) +#else +#define HDUITEST_API +#endif +#endif diff --git a/lib/adskHydraSceneBrowser/test/adskHydraSceneBrowserTestFixture.cpp b/lib/adskHydraSceneBrowser/test/adskHydraSceneBrowserTestFixture.cpp new file mode 100644 index 0000000000..aadf7da4e1 --- /dev/null +++ b/lib/adskHydraSceneBrowser/test/adskHydraSceneBrowserTestFixture.cpp @@ -0,0 +1,323 @@ +// Copyright 2023 Autodesk +// +// 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 "adskHydraSceneBrowserTestFixture.h" + +#include "dataSourceTreeWidget.h" +#include "dataSourceValueTreeView.h" +#include "sceneIndexTreeWidget.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +template ChildType* FindFirstChild(QObject* qObject) +{ + for (QObject* child : qObject->children()) { + ChildType* castChild = qobject_cast(child); + if (castChild) { + return castChild; + } + } + return nullptr; +} + +QTreeWidgetItemIterator GetIteratorForTree(QTreeWidget* treeWidget) +{ + // Expand all items so the iterator can traverse them + treeWidget->expandAll(); + // Immediately process queued events, otherwise some events might linger and lead to a crash + // trying to access since-deleted items once the Qt event loop resumes and processes the events. + // (e.g. without this there is a crash involving a setExpanded() call) + QApplication::processEvents(QEventLoop::ProcessEventsFlag::EventLoopExec); + return QTreeWidgetItemIterator(treeWidget); +} + +pxr::HdSceneIndexBasePtr AdskHydraSceneBrowserTestFixture::sceneIndex = nullptr; + +void AdskHydraSceneBrowserTestFixture::SetUp() +{ + ASSERT_NE(sceneIndex, nullptr); + + _sceneBrowserWidget->setWindowTitle("Test Hydra Scene Browser"); + _sceneBrowserWidget->SetSceneIndex("", sceneIndex, true); + _sceneBrowserWidget->show(); + + QSplitter* sceneBrowserSplitter = FindFirstChild(_sceneBrowserWidget.get()); + ASSERT_NE(sceneBrowserSplitter, nullptr); + + _primHierarchyWidget = FindFirstChild(sceneBrowserSplitter); + ASSERT_NE(_primHierarchyWidget, nullptr); + _dataSourceHierarchyWidget + = FindFirstChild(sceneBrowserSplitter); + ASSERT_NE(_dataSourceHierarchyWidget, nullptr); + _dataSourceValueView = FindFirstChild(sceneBrowserSplitter); + ASSERT_NE(_dataSourceValueView, nullptr); +} + +void AdskHydraSceneBrowserTestFixture::TearDown() { _sceneBrowserWidget->close(); } + +void AdskHydraSceneBrowserTestFixture::SetReferenceSceneIndex( + pxr::HdSceneIndexBasePtr referenceSceneIndex) +{ + sceneIndex = referenceSceneIndex; +} + +void AdskHydraSceneBrowserTestFixture::ComparePrimHierarchy( + bool compareDataSourceHierarchy, + bool compareDataSourceValues) +{ + // Setup traversal data structures (depth-first search) + QTreeWidgetItemIterator itPrimsTreeWidget = GetIteratorForTree(_primHierarchyWidget); + std::stack primPathsStack({ pxr::SdfPath::AbsoluteRootPath() }); + + // Traverse hierarchy and compare (depth-first search) + while (*itPrimsTreeWidget && !primPathsStack.empty()) { + // Get the objects for the current step + QTreeWidgetItem* primQtItem = *itPrimsTreeWidget; + pxr::SdfPath primPath = primPathsStack.top(); + + // Compare prim name + std::string actualPrimName = primQtItem->text(0).toStdString(); + // SdfPath::GetElementString returns an empty string if the path is the the absolute root + // (/), as it is not considered to be an element. However, the browser does displays it as + // "/". + std::string expectedPrimName + = primPath.IsAbsoluteRootPath() ? "/" : primPath.GetElementString(); + EXPECT_EQ(actualPrimName, expectedPrimName); + + // Compare prim type + pxr::HdSceneIndexPrim prim = sceneIndex->GetPrim(primPath); + if (primQtItem->columnCount() > 1) { + std::string actualPrimType = primQtItem->text(1).toStdString(); + std::string expectedPrimType = prim.primType; + EXPECT_EQ(actualPrimType, expectedPrimType); + } else { + // In this case, the Qt prim item only has a column for its name, + // so we at least make sure the prim type is empty. + // So far it seems this case only happens for the root path. + EXPECT_EQ(prim.primType, pxr::TfToken()) + << "Prim had a non-empty type but its Qt item had no column for it."; + } + + // Compare data source + if (compareDataSourceHierarchy) { + _primHierarchyWidget->setCurrentItem(primQtItem); + CompareDataSourceHierarchy( + { primPath.GetElementToken(), prim.dataSource }, compareDataSourceValues); + } + + // Prepare next step (need to pop the stack before pushing the next elements) + itPrimsTreeWidget++; + primPathsStack.pop(); + + // Push child paths on the stack + pxr::SdfPathVector childPaths = sceneIndex->GetChildPrimPaths(primPath); + for (auto itChildPaths = childPaths.rbegin(); itChildPaths != childPaths.rend(); + itChildPaths++) { + primPathsStack.push(*itChildPaths); + } + } +} + +void AdskHydraSceneBrowserTestFixture::CompareDataSourceHierarchy( + DataSourceEntry rootDataSourceEntry, + bool compareValues) +{ + // Setup traversal data structures (depth-first search) + QTreeWidgetItemIterator itDataSourceTreeWidget = GetIteratorForTree(_dataSourceHierarchyWidget); + std::stack dataSourceStack({ rootDataSourceEntry }); + + // Traverse hierarchy and compare (depth-first search) + while (*itDataSourceTreeWidget && !dataSourceStack.empty()) { + // Get the objects for the current step + QTreeWidgetItem* dataSourceQtItem = *itDataSourceTreeWidget; + DataSourceEntry dataSourceEntry = dataSourceStack.top(); + + // Compare data source name + std::string actualDataSourceName = dataSourceQtItem->text(0).toStdString(); + std::string expectedDataSourceName = dataSourceEntry.name; + EXPECT_EQ(actualDataSourceName, expectedDataSourceName); + + // Compare data source value + if (compareValues) { + _dataSourceHierarchyWidget->setCurrentItem(dataSourceQtItem); + if (auto sampledDataSource + = pxr::HdSampledDataSource::Cast(dataSourceEntry.dataSource)) { + CompareDataSourceValue(sampledDataSource); + } + } + + // Prepare next step (need to pop the stack before pushing the next elements) + itDataSourceTreeWidget++; + dataSourceStack.pop(); + + // Push child data sources on the stack + if (auto containerDataSource + = pxr::HdContainerDataSource::Cast(dataSourceEntry.dataSource)) { + pxr::TfTokenVector childNames = containerDataSource->GetNames(); + for (auto itChildNames = childNames.rbegin(); itChildNames != childNames.rend(); + itChildNames++) { + pxr::TfToken dataSourceName = *itChildNames; + pxr::HdDataSourceBaseHandle dataSource = containerDataSource->Get(dataSourceName); + if (dataSource) { + dataSourceStack.push({ dataSourceName, dataSource }); + } + } + } else if ( + auto vectorDataSource = pxr::HdVectorDataSource::Cast(dataSourceEntry.dataSource)) { + for (size_t iElement = 0; iElement < vectorDataSource->GetNumElements(); iElement++) { + size_t reversedElementIndex = vectorDataSource->GetNumElements() - 1 - iElement; + pxr::TfToken dataSourceName = pxr::TfToken(std::to_string(reversedElementIndex)); + pxr::HdDataSourceBaseHandle dataSource + = vectorDataSource->GetElement(reversedElementIndex); + if (dataSource) { + dataSourceStack.push({ dataSourceName, dataSource }); + } + } + } + } +} + +void AdskHydraSceneBrowserTestFixture::CompareDataSourceValue( + pxr::HdSampledDataSourceHandle sampledDataSource) +{ + _dataSourceValueView->expandAll(); + + pxr::VtValue value = sampledDataSource->GetValue(0.0f); + + // The supported value types can be found in dataSourceValueTreeView.cpp, in the + // Hdui_GetModelFromValue function. + if (!value.IsArrayValued()) { + CompareValueContent(value); + } else { + CompareIfArray(value); + CompareIfArray(value); + CompareIfArray(value); + CompareIfArray(value); + CompareIfArray(value); + CompareIfArray(value); + CompareIfArray(value); + CompareIfArray(value); + CompareIfArray(value); + } +} + +bool AdskHydraSceneBrowserTestFixture::MatchesFallbackTextOutput(const std::string& text) { + // Regex for matching the fallback text output used for types that don't provide a custom one. + // Identifies a literal <', followed by a valid C++ type name (possibly templated), then a literal ', + // then a space, an @ symbol and another space, a hexadecimal 32 to 64 bit address (case-insensitive, + // potentially prefixed with 0x), and finally a literal >. Example matches : + // <'ArResolverContext' @ 0x251ffa80> // Linux + // <'ArResolverContext' @ 000001D3A4296670> // Windows + // <'vector >' @ 0x261b8c20> // Linux + // <'vector >' @ 000001D49F3390B0> // Windows + + // The space in the second [] group is intentional and must be matched, + // see the above templated examples + std::regex fallbackTextOutputRegex("<'[a-zA-Z_][ a-zA-Z_0-9<>,&*()]*' @ (0x)?[0-9a-fA-F]{8,16}>"); + return std::regex_match(text, fallbackTextOutputRegex); +} + +void AdskHydraSceneBrowserTestFixture::CompareValueContent(const pxr::VtValue& value) +{ + QAbstractItemModel* dataSourceItemModel = _dataSourceValueView->model(); + EXPECT_EQ(dataSourceItemModel->rowCount(), 1); + + QModelIndex valueIndex = dataSourceItemModel->index(0, 0); + QVariant valueData = dataSourceItemModel->data(valueIndex, Qt::DisplayRole); + QString valueText = valueData.toString(); + std::string actualValue = valueText.toStdString(); + + std::ostringstream valueStream; + valueStream << value; + std::string expectedValue = valueStream.str(); + + if (!MatchesFallbackTextOutput(expectedValue)) { + // Happy path : the concrete type of the VtValue supports text output. + // (this is an assumption and not a truly reliable check; see the not-so-happy path + // for more details) + EXPECT_EQ(actualValue, expectedValue); + } else { + // Not-so-happy path : the concrete type of the VtValue does not support text output. + // + // If a type does not provide custom text output, this will cause Vt_StreamOutGeneric to be + // called by Vt_StreamOutImpl (see streamOut.h/.cpp). This function outputs the name of the + // concrete type followed by the address of the held object. Example outputs : + // <'ArResolverContext' @ 0x251ffa80> + // <'ArResolverContext' @ 000001D3A4296670> + // + // Since it is possible that some data sources return a copy of their underlying object when + // calling GetValue(), the object held by the VtValue passed in as the parameter to this + // function may differ from the one held by the VtValue used by the scene browser. In such + // cases, the printed addresses won't match and the test will fail. + // + // This workaround instead compares the values only up to their type name in these cases. + // The regex check could technically prevent fully comparing values if their custom text + // output perfectly matches the regex, but this seems very unlikely. + std::string valueTypeName = value.GetTypeName(); + size_t indexOfTypeName = expectedValue.find(valueTypeName); + size_t substringComparisonLength = indexOfTypeName + valueTypeName.size(); + EXPECT_EQ( + actualValue.substr(0, substringComparisonLength), + expectedValue.substr(0, substringComparisonLength)); + } +} + +template +void AdskHydraSceneBrowserTestFixture::CompareIfArray(const pxr::VtValue& value) +{ + if (value.IsHolding>()) { + CompareArrayContents(value.UncheckedGet>()); + } +} + +template +void AdskHydraSceneBrowserTestFixture::CompareArrayContents( + const pxr::VtArray& vtArray) +{ + QAbstractItemModel* dataSourceItemModel = _dataSourceValueView->model(); + EXPECT_EQ(static_cast(dataSourceItemModel->rowCount()), vtArray.size()); + + size_t nbRowsToTraverse + = std::min(static_cast(dataSourceItemModel->rowCount()), vtArray.size()); + for (size_t iRow = 0; iRow < nbRowsToTraverse; iRow++) { + QModelIndex valueIndex = dataSourceItemModel->index(iRow, 0); + QVariant valueData = dataSourceItemModel->data(valueIndex, Qt::DisplayRole); + QString valueText = valueData.toString(); + std::string actualValue = valueText.toStdString(); + + std::ostringstream valueStream; + valueStream << vtArray.cdata()[iRow]; + std::string expectedValue = valueStream.str(); + + EXPECT_EQ(actualValue, expectedValue); + } +} diff --git a/lib/adskHydraSceneBrowser/test/adskHydraSceneBrowserTestFixture.h b/lib/adskHydraSceneBrowser/test/adskHydraSceneBrowserTestFixture.h new file mode 100644 index 0000000000..df0b285521 --- /dev/null +++ b/lib/adskHydraSceneBrowser/test/adskHydraSceneBrowserTestFixture.h @@ -0,0 +1,79 @@ +// Copyright 2023 Autodesk +// +// 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 ADSK_HYDRA_SCENE_BROWSER_TEST_FIXTURE_H +#define ADSK_HYDRA_SCENE_BROWSER_TEST_FIXTURE_H + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +struct DataSourceEntry +{ + pxr::TfToken name; + pxr::HdDataSourceBaseHandle dataSource; +}; + +class AdskHydraSceneBrowserTestFixture : public ::testing::Test +{ +public: + AdskHydraSceneBrowserTestFixture() = default; + ~AdskHydraSceneBrowserTestFixture() override = default; + + void SetUp() override; + void TearDown() override; + + static void SetReferenceSceneIndex(pxr::HdSceneIndexBasePtr referenceSceneIndex); + +protected: + void ComparePrimHierarchy( + bool compareDataSourceHierarchy = false, + bool compareDataSourceValues = false); + + void + CompareDataSourceHierarchy(DataSourceEntry rootDataSourceEntry, bool compareValues = false); + + void CompareDataSourceValue(pxr::HdSampledDataSourceHandle sampledDataSource); + + bool MatchesFallbackTextOutput(const std::string& text); + + void CompareValueContent(const pxr::VtValue& value); + + template void CompareIfArray(const pxr::VtValue& value); + + template + void CompareArrayContents(const pxr::VtArray& vtArray); + + std::unique_ptr _sceneBrowserWidget + = std::make_unique(); + pxr::HduiSceneIndexTreeWidget* _primHierarchyWidget = nullptr; + pxr::HduiDataSourceTreeWidget* _dataSourceHierarchyWidget = nullptr; + pxr::HduiDataSourceValueTreeView* _dataSourceValueView = nullptr; + + static pxr::HdSceneIndexBasePtr sceneIndex; +}; + +#endif // ADSK_HYDRA_SCENE_BROWSER_TEST_FIXTURE_H diff --git a/lib/adskHydraSceneBrowser/test/adskHydraSceneBrowserTesting.cpp b/lib/adskHydraSceneBrowser/test/adskHydraSceneBrowserTesting.cpp new file mode 100644 index 0000000000..93ff0cf277 --- /dev/null +++ b/lib/adskHydraSceneBrowser/test/adskHydraSceneBrowserTesting.cpp @@ -0,0 +1,54 @@ +// Copyright 2023 Autodesk +// +// 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 "adskHydraSceneBrowserTesting.h" + +#include "adskHydraSceneBrowserTestFixture.h" + +#include + +bool RunTestsWithFilter(std::string filter) +{ + // Create GoogleTest arguments + int argc = 1; + std::vector argv(argc); + argv[0] = const_cast("AdskHydraSceneBrowserTesting"); + + // Setup GoogleTest + ::testing::GTEST_FLAG(filter) = filter; + ::testing::InitGoogleTest(&argc, argv.data()); + + // Run the tests + int testsResult = RUN_ALL_TESTS(); + + // Return pass/fail status (testsResult == 0 if all tests passed, 1 otherwise) + return testsResult == 0; +} + +namespace AdskHydraSceneBrowserTesting { +bool RunFullSceneIndexComparisonTest(pxr::HdSceneIndexBasePtr referenceSceneIndex) +{ + AdskHydraSceneBrowserTestFixture::SetReferenceSceneIndex(referenceSceneIndex); + return RunTestsWithFilter("AdskHydraSceneBrowserTestFixture.FullSceneIndexComparison"); +} +} // namespace AdskHydraSceneBrowserTesting + +TEST_F(AdskHydraSceneBrowserTestFixture, FullSceneIndexComparison) +{ + // We want to do a full comparison, so also compare both data sources hierarchy and values + bool compareDataSourceHierarchy = true; + bool compareDataSourceValues = true; + ComparePrimHierarchy(compareDataSourceHierarchy, compareDataSourceValues); +} diff --git a/lib/adskHydraSceneBrowser/test/adskHydraSceneBrowserTesting.h b/lib/adskHydraSceneBrowser/test/adskHydraSceneBrowserTesting.h new file mode 100644 index 0000000000..41047bcc3d --- /dev/null +++ b/lib/adskHydraSceneBrowser/test/adskHydraSceneBrowserTesting.h @@ -0,0 +1,28 @@ +// Copyright 2023 Autodesk +// +// 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 ADSK_HYDRA_SCENE_BROWSER_TESTING_H +#define ADSK_HYDRA_SCENE_BROWSER_TESTING_H + +#include "adskHydraSceneBrowserTestApi.h" + +#include + +namespace AdskHydraSceneBrowserTesting { +HDUITEST_API +bool RunFullSceneIndexComparisonTest(pxr::HdSceneIndexBasePtr referenceSceneIndex); +} // namespace AdskHydraSceneBrowserTesting + +#endif // ADSK_HYDRA_SCENE_BROWSER_TESTING_H diff --git a/lib/flowViewport/API/CMakeLists.txt b/lib/flowViewport/API/CMakeLists.txt new file mode 100644 index 0000000000..2df5b785c5 --- /dev/null +++ b/lib/flowViewport/API/CMakeLists.txt @@ -0,0 +1,29 @@ +set(HEADERS + fvpSelectionClient.h + fvpFlowSelectionInterface.h + fvpVersionInterface.h +) + +# ----------------------------------------------------------------------------- +# promoted headers +# ----------------------------------------------------------------------------- +mayaUsd_promoteHeaderList( + HEADERS + ${HEADERS} + BASEDIR + ${TARGET_NAME}/API +) + +# ----------------------------------------------------------------------------- +# install +# ----------------------------------------------------------------------------- +install(FILES ${HEADERS} + DESTINATION + ${CMAKE_INSTALL_PREFIX}/include/flowViewport/API +) + +# ----------------------------------------------------------------------------- +# subdirectories +# ----------------------------------------------------------------------------- +add_subdirectory(interfacesImp) +add_subdirectory(samples) diff --git a/lib/flowViewport/API/fvpFlowSelectionInterface.h b/lib/flowViewport/API/fvpFlowSelectionInterface.h new file mode 100644 index 0000000000..8140deaa22 --- /dev/null +++ b/lib/flowViewport/API/fvpFlowSelectionInterface.h @@ -0,0 +1,59 @@ +// +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +// 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 FLOW_VIEWPORT_API_SELECTION_INTERFACE_H +#define FLOW_VIEWPORT_API_SELECTION_INTERFACE_H + +#include "flowViewport/api.h" + +namespace FVP_NS_DEF +{ + class SelectionClient;//Predeclaration + + /** + * FlowSelectionInterface is used to register/unregister selection callbacks SelectionClient for an Hydra viewport. + * To get an instance of the FlowSelectionInterface class, please use : + * Fvp::FlowSelectionInterface& flowSelectionInterface = Fvp::FlowSelectionInterface::Get(); + */ + class FlowSelectionInterface + { + public: + + /** + * @brief Access to this interface + * + * @return A reference on the FlowSelectionInterface + */ + static FVP_API FlowSelectionInterface& Get(); + + /** + * @brief Register a callbacks SelectionClient instance + * + * @param[in] client is the SelectionClient instance you are registering. + */ + virtual void RegisterSelectionClient(SelectionClient& client) = 0; + + /** + * @brief Unregister a callbacks SelectionClient instance + * + * @param[in] client is an SelectionClient instance you have previously registered and want to unregister. + */ + virtual void UnregisterSelectionClient(SelectionClient& client)= 0; + }; + +}//end of namespace + +#endif //FLOW_VIEWPORT_API_SELECTION_INTERFACE_H diff --git a/lib/flowViewport/API/fvpSelectionClient.h b/lib/flowViewport/API/fvpSelectionClient.h new file mode 100644 index 0000000000..6166578a4e --- /dev/null +++ b/lib/flowViewport/API/fvpSelectionClient.h @@ -0,0 +1,50 @@ +// +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +// 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 FLOW_VIEWPORT_API_SELECTIONCLIENT_H +#define FLOW_VIEWPORT_API_SELECTIONCLIENT_H + +#include "flowViewport/api.h" + +#include +#include + +namespace FVP_NS_DEF +{ + /** + * SelectionClient is the definition of selection callbacks for an Hydra viewport. + * Subclass this class to register an instance of SelectionClient with Fvp::FlowSelectionInterface::RegisterSelectionClient + */ + class FVP_API SelectionClient + { + public: + + /** + * @brief Is a dummy selection callback as a placeholder + * + */ + virtual void DummySelectionCallback() = 0; + + /// Destructor + virtual ~SelectionClient() = default; + }; + + ///Array of SelectionClient + using SelectionClientSet = std::set; + +}//end of namespace FVP_NS_DEF + +#endif //FLOW_VIEWPORT_API_SELECTIONCLIENT_H diff --git a/lib/flowViewport/API/fvpVersionInterface.h b/lib/flowViewport/API/fvpVersionInterface.h new file mode 100644 index 0000000000..2ccdaaf067 --- /dev/null +++ b/lib/flowViewport/API/fvpVersionInterface.h @@ -0,0 +1,49 @@ +// +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +// 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 FLOW_VIEWPORT_API_VERSION_INTERFACE_H +#define FLOW_VIEWPORT_API_VERSION_INTERFACE_H + +#include "flowViewport/api.h" + +namespace FVP_NS_DEF +{ + /** + * VersionInterface is used to get the version of the Flow Viewport API. + * + * To get an instance of the VersionInterface class, please use : + * Fvp::VersionInterface& versionInterface = Fvp::VersionInterface::Get(); + */ + class VersionInterface + { + public: + static FVP_API VersionInterface& Get(); + + /** + * @brief Get the version of the flow viewport API. + * + * Get the version of the flow viewport API so you can handle different versions in your plugin if it changes. + * + * @param[out] majorVersion is the major version number. + * @param[out] minorVersion is the minor version number. + * @param[out] patchLevel is the patch level number. + */ + virtual void GetVersion(int& majorVersion, int& minorVersion, int& patchLevel) = 0; + }; + +}//end of namespace + +#endif //FLOW_VIEWPORT_API_VERSION_INTERFACE_H diff --git a/lib/flowViewport/API/interfacesImp/CMakeLists.txt b/lib/flowViewport/API/interfacesImp/CMakeLists.txt new file mode 100644 index 0000000000..5bd63fedbc --- /dev/null +++ b/lib/flowViewport/API/interfacesImp/CMakeLists.txt @@ -0,0 +1,31 @@ +# ----------------------------------------------------------------------------- +# sources +# ----------------------------------------------------------------------------- +target_sources(${TARGET_NAME} + PRIVATE + fvpSelectionInterfaceImp.cpp + fvpVersionInterfaceImp.cpp +) + +set(HEADERS + fvpSelectionInterfaceImp.h + fvpVersionInterfaceImp.h +) + +# ----------------------------------------------------------------------------- +# promoted headers +# ----------------------------------------------------------------------------- +mayaUsd_promoteHeaderList( + HEADERS + ${HEADERS} + BASEDIR + ${TARGET_NAME}/API/interfacesImp +) + +# ----------------------------------------------------------------------------- +# install +# ----------------------------------------------------------------------------- +install(FILES ${HEADERS} + DESTINATION + ${CMAKE_INSTALL_PREFIX}/include/flowViewport/API/interfacesImp +) diff --git a/lib/flowViewport/API/interfacesImp/fvpSelectionInterfaceImp.cpp b/lib/flowViewport/API/interfacesImp/fvpSelectionInterfaceImp.cpp new file mode 100644 index 0000000000..6e73ee50f2 --- /dev/null +++ b/lib/flowViewport/API/interfacesImp/fvpSelectionInterfaceImp.cpp @@ -0,0 +1,77 @@ +// +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +// 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. +// + +//Local headers +#include "fvpSelectionInterfaceImp.h" + +//STL +#include + +namespace{ + static std::mutex _viewportSelectClient_mutex; +} + +PXR_NAMESPACE_USING_DIRECTIVE + +namespace FVP_NS_DEF { + +static SelectionInterfaceImp theInterface; + +SelectionClientSet _viewportSelectionClients; + +FlowSelectionInterface& FlowSelectionInterface::Get() +{ + return theInterface; +} + +SelectionInterfaceImp& SelectionInterfaceImp::Get() +{ + return theInterface; +} + +void SelectionInterfaceImp::RegisterSelectionClient(SelectionClient& client) +{ + std::lock_guard lock(_viewportSelectClient_mutex); + + auto foundResult = _viewportSelectionClients.find(&client); + if (foundResult == _viewportSelectionClients.cend()){ + _viewportSelectionClients.insert(&client); + } +} + +void SelectionInterfaceImp::UnregisterSelectionClient(SelectionClient& client) +{ + std::lock_guard lock(_viewportSelectClient_mutex); + + auto foundResult = _viewportSelectionClients.find(&client); + if (foundResult != _viewportSelectionClients.end()){ + _viewportSelectionClients.erase(foundResult); + } +} + +void SelectionInterfaceImp::DummySelectionCallback() +{ + std::lock_guard lock(_viewportSelectClient_mutex); + + for (auto _selectionClient : _viewportSelectionClients) { + if (_selectionClient){ + _selectionClient->DummySelectionCallback(); + } + } +} + +} //End of namespace FVP_NS_DEF + diff --git a/lib/flowViewport/API/interfacesImp/fvpSelectionInterfaceImp.h b/lib/flowViewport/API/interfacesImp/fvpSelectionInterfaceImp.h new file mode 100644 index 0000000000..2bba24c526 --- /dev/null +++ b/lib/flowViewport/API/interfacesImp/fvpSelectionInterfaceImp.h @@ -0,0 +1,47 @@ +// +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +// 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 FLOW_VIEWPORT_API_INTERFACESIMP_SELECTIONINTERFACEIMP_H +#define FLOW_VIEWPORT_API_INTERFACESIMP_SELECTIONINTERFACEIMP_H + +//Local headers +#include +#include +#include + +namespace FVP_NS_DEF { + +///Is a singleton, use SelectionInterfaceImp& selInterfaceImp = SelectionInterfaceImp::Get() to get an instance of that interface +class SelectionInterfaceImp : public FVP_NS_DEF::FlowSelectionInterface +{ +public: + SelectionInterfaceImp() = default; + virtual ~SelectionInterfaceImp() = default; + + ///Interface accessor + static SelectionInterfaceImp& Get(); + + //From FVP_NS_DEF::SelectionInterface + void RegisterSelectionClient(SelectionClient& client)override; + void UnregisterSelectionClient(SelectionClient& client)override; + + //To be called by maya-hydra + void DummySelectionCallback(); +}; + +} //End of namespace FVP_NS_DEF + +#endif // FLOW_VIEWPORT_API_INTERFACESIMP_SELECTIONINTERFACEIMP_H \ No newline at end of file diff --git a/lib/flowViewport/API/interfacesImp/fvpVersionInterfaceImp.cpp b/lib/flowViewport/API/interfacesImp/fvpVersionInterfaceImp.cpp new file mode 100644 index 0000000000..398fbecbf3 --- /dev/null +++ b/lib/flowViewport/API/interfacesImp/fvpVersionInterfaceImp.cpp @@ -0,0 +1,48 @@ +// +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +// 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. +// + +//Local headers +#include "fvpVersionInterfaceImp.h" + +namespace FVP_NS_DEF { + +//Interface version +#define FLOW_VIEWPORT_API_MAJOR_VERSION 0 +#define FLOW_VIEWPORT_API_MINOR_VERSION 1 +#define FLOW_VIEWPORT_API_PATCH_LEVEL 0 + +static VersionInterfaceImp theInterface; + +VersionInterface& VersionInterface::Get() +{ + return theInterface; +} + +VersionInterfaceImp& VersionInterfaceImp::Get() +{ + return theInterface; +} + +///////////////////////////////// SelectionInterfaceImp //////////////////// +void VersionInterfaceImp::GetVersion(int& majorVersion, int& minorVersion, int&patchLevel) +{ + majorVersion = FLOW_VIEWPORT_API_MAJOR_VERSION; + minorVersion = FLOW_VIEWPORT_API_MINOR_VERSION; + patchLevel = FLOW_VIEWPORT_API_PATCH_LEVEL; +} + +} //End of namespace FVP_NS_DEF + diff --git a/lib/flowViewport/API/interfacesImp/fvpVersionInterfaceImp.h b/lib/flowViewport/API/interfacesImp/fvpVersionInterfaceImp.h new file mode 100644 index 0000000000..7f508bb06c --- /dev/null +++ b/lib/flowViewport/API/interfacesImp/fvpVersionInterfaceImp.h @@ -0,0 +1,42 @@ +// +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +// 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 FLOW_VIEWPORT_API_INTERFACESIMP_VERSIONINTERFACE_IMP_H +#define FLOW_VIEWPORT_API_INTERFACESIMP_VERSIONINTERFACE_IMP_H + +//Local headers +#include +#include + +namespace FVP_NS_DEF { + +///Is a singleton, use VersionInterfaceImp& versionInterface = VersionInterfaceImp::Get() to get an instance of that interface +class VersionInterfaceImp : public FVP_NS_DEF::VersionInterface +{ +public: + VersionInterfaceImp() = default; + virtual ~VersionInterfaceImp() = default; + + ///Interface accessor + static VersionInterfaceImp& Get(); + + //From FVP_NS_DEF::VersionInterface + void GetVersion(int& majorVersion, int& minorVersion, int& patchLevel)override; +}; + +} //End of namespace FVP_NS_DEF + +#endif // FLOW_VIEWPORT_API_INTERFACESIMP_VERSIONINTERFACE_IMP_H \ No newline at end of file diff --git a/lib/flowViewport/API/samples/CMakeLists.txt b/lib/flowViewport/API/samples/CMakeLists.txt new file mode 100644 index 0000000000..3b50d4ee66 --- /dev/null +++ b/lib/flowViewport/API/samples/CMakeLists.txt @@ -0,0 +1,29 @@ +# ----------------------------------------------------------------------------- +# sources +# ----------------------------------------------------------------------------- +target_sources(${TARGET_NAME} + PRIVATE + fvpSelectionClientExample.cpp +) + +set(HEADERS + fvpSelectionClientExample.h +) + +# ----------------------------------------------------------------------------- +# promoted headers +# ----------------------------------------------------------------------------- +mayaUsd_promoteHeaderList( + HEADERS + ${HEADERS} + BASEDIR + ${TARGET_NAME}/API/samples +) + +# ----------------------------------------------------------------------------- +# install +# ----------------------------------------------------------------------------- +install(FILES ${HEADERS} + DESTINATION + ${CMAKE_INSTALL_PREFIX}/include/flowViewport/API/samples +) diff --git a/lib/flowViewport/API/samples/fvpSelectionClientExample.cpp b/lib/flowViewport/API/samples/fvpSelectionClientExample.cpp new file mode 100644 index 0000000000..9545a8ddbe --- /dev/null +++ b/lib/flowViewport/API/samples/fvpSelectionClientExample.cpp @@ -0,0 +1,33 @@ +// +// Copyright 2023 Autodesk +// +// 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. +// + +//Local headers +#include "fvpSelectionClientExample.h" + +PXR_NAMESPACE_USING_DIRECTIVE + +namespace FVP_NS_DEF { + +SelectionClientExample::~SelectionClientExample() +{ +} + +void SelectionClientExample::DummySelectionCallback() +{ + +} + +} //end of namespace FVP_NS_DEF \ No newline at end of file diff --git a/lib/flowViewport/API/samples/fvpSelectionClientExample.h b/lib/flowViewport/API/samples/fvpSelectionClientExample.h new file mode 100644 index 0000000000..5a3ad4df10 --- /dev/null +++ b/lib/flowViewport/API/samples/fvpSelectionClientExample.h @@ -0,0 +1,48 @@ +// +// Copyright 2023 Autodesk +// +// 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 FLOW_VIEWPORT_API_SAMPLES_SELECTIONCLIENTEXAMPLE_H +#define FLOW_VIEWPORT_API_SAMPLES_SELECTIONCLIENTEXAMPLE_H + +//Local headers +#include "flowViewport/api.h" +#include "flowViewport/API/fvpSelectionClient.h" +#include "flowViewport/API/fvpFlowSelectionInterface.h" + +namespace FVP_NS_DEF { + +///Implementation of an SelectionClient which is the way to communicate with our Hydra viewport plugin to deal with selection +class FVP_API SelectionClientExample : public SelectionClient +{ +public: + SelectionClientExample() = default; + ~SelectionClientExample() override; + + ///Set the hydra interface + void SetHydraInterface(FlowSelectionInterface* si) {_hydraViewportSelectionInterface = si;} + + ///From FlowViewport::SelectionClient + ///This is a dummy callback function to be replaced + void DummySelectionCallback()override; + +protected: + ///Store the Hydra interface pointer + FlowSelectionInterface* _hydraViewportSelectionInterface = nullptr; +}; + +} //end of namespace FVP_NS_DEF + +#endif //FLOW_VIEWPORT_API_SAMPLES_SELECTIONCLIENTEXAMPLE_H diff --git a/lib/flowViewport/CMakeLists.txt b/lib/flowViewport/CMakeLists.txt new file mode 100644 index 0000000000..a331172a8a --- /dev/null +++ b/lib/flowViewport/CMakeLists.txt @@ -0,0 +1,114 @@ +set(TARGET_NAME flowViewport) + +add_library(${TARGET_NAME} SHARED) + +# ----------------------------------------------------------------------------- +# sources +# ----------------------------------------------------------------------------- +target_sources(${TARGET_NAME} + PRIVATE + debugCodes.cpp + global.cpp + tokens.cpp +) + +set(HEADERS + api.h + debugCodes.h + global.h + tokens.h +) + +# ----------------------------------------------------------------------------- +# include directories +# ----------------------------------------------------------------------------- +target_include_directories(${TARGET_NAME} + PUBLIC + ${PXR_INCLUDE_DIRS} + ${CMAKE_BINARY_DIR}/include + ${UFE_INCLUDE_DIR} +) + +# ----------------------------------------------------------------------------- +# compiler configuration +# ----------------------------------------------------------------------------- + +# Without the TBB_USE_DEBUG, BOOST_DEBUG_PYTHON and BOOST_LINKING_PYTHON +# the Debug variant link fails, even though there is no Python or Boost +# at time of writing in the Flow Viewport library. The assumption is that +# Hydra USD headers impose this requirement. PPT, 26-Sep-2023. + +target_compile_definitions(${TARGET_NAME} + PUBLIC + $<$:TBB_USE_DEBUG> + $<$:BOOST_DEBUG_PYTHON> + $<$:BOOST_LINKING_PYTHON> + PRIVATE + FVP_EXPORT + $<$:OSMac_> + # Copy-pasted from lib/mayaUsd/CMakeLists.txt +) + +mayaHydra_compile_config(${TARGET_NAME}) + +# ----------------------------------------------------------------------------- +# link libraries +# ----------------------------------------------------------------------------- +target_link_libraries(${TARGET_NAME} + PUBLIC + hd + hdx + sdf + ${UFE_LIBRARY} + PRIVATE + ar + gf + tf +) + +# ----------------------------------------------------------------------------- +# promoted headers +# ----------------------------------------------------------------------------- +set(SRCFILE ${CMAKE_CURRENT_SOURCE_DIR}/flowViewport.h.src) +set(DSTFILE ${CMAKE_BINARY_DIR}/include/flowViewport/flowViewport.h) +configure_file(${SRCFILE} ${DSTFILE}) + +mayaUsd_promoteHeaderList( + HEADERS + ${HEADERS} + BASEDIR + ${TARGET_NAME} +) + +# ----------------------------------------------------------------------------- +# install +# ----------------------------------------------------------------------------- + +install(TARGETS ${TARGET_NAME} + LIBRARY + DESTINATION ${CMAKE_INSTALL_PREFIX}/lib + RUNTIME + DESTINATION ${CMAKE_INSTALL_PREFIX}/lib +) + +install(FILES ${HEADERS} + DESTINATION + ${CMAKE_INSTALL_PREFIX}/include/flowViewport +) + +install(FILES ${CMAKE_BINARY_DIR}/include/flowViewport/flowViewport.h + DESTINATION ${CMAKE_INSTALL_PREFIX}/include/flowViewport +) + +if(IS_WINDOWS) + install(FILES $ + DESTINATION ${CMAKE_INSTALL_PREFIX}/lib OPTIONAL) +endif() + +# ----------------------------------------------------------------------------- +# subdirectories +# ----------------------------------------------------------------------------- +add_subdirectory(colorPreferences) +add_subdirectory(sceneIndex) +add_subdirectory(API) +add_subdirectory(selection) diff --git a/lib/flowViewport/api.h b/lib/flowViewport/api.h new file mode 100644 index 0000000000..f6ed103df1 --- /dev/null +++ b/lib/flowViewport/api.h @@ -0,0 +1,43 @@ +// Copyright 2023 Autodesk +// +// 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 FVP_API_H +#define FVP_API_H + +#ifdef __GNUC__ +#define FVP_API_EXPORT __attribute__((visibility("default"))) +#define FVP_API_IMPORT +#elif defined(_WIN32) || defined(_WIN64) +#define FVP_API_EXPORT __declspec(dllexport) +#define FVP_API_IMPORT __declspec(dllimport) +#else +#define FVP_API_EXPORT +#define FVP_API_IMPORT +#endif + +#if defined(FVP_STATIC) +#define FVP_API +#else +#if defined(FVP_EXPORT) +#define FVP_API FVP_API_EXPORT +#else +#define FVP_API FVP_API_IMPORT +#endif +#endif + +// Convenience symbol versioning include: because api.h is widely +// included, this reduces the need to explicitly include flowViewport.h. +#include + +#endif // FVP_API_H diff --git a/lib/flowViewport/colorPreferences/CMakeLists.txt b/lib/flowViewport/colorPreferences/CMakeLists.txt new file mode 100644 index 0000000000..a220aba4b0 --- /dev/null +++ b/lib/flowViewport/colorPreferences/CMakeLists.txt @@ -0,0 +1,34 @@ +# ----------------------------------------------------------------------------- +# sources +# ----------------------------------------------------------------------------- +target_sources(${TARGET_NAME} + PRIVATE + fvpColorChanged.cpp + fvpColorPreferences.cpp + fvpColorPreferencesTokens.cpp +) + +set(HEADERS + fvpColorChanged.h + fvpColorPreferences.h + fvpColorPreferencesTokens.h + fvpColorPreferencesTranslator.h +) + +# ----------------------------------------------------------------------------- +# promoted headers +# ----------------------------------------------------------------------------- +mayaUsd_promoteHeaderList( + HEADERS + ${HEADERS} + BASEDIR + ${TARGET_NAME}/colorPreferences +) + +# ----------------------------------------------------------------------------- +# install +# ----------------------------------------------------------------------------- +install(FILES ${HEADERS} + DESTINATION + ${CMAKE_INSTALL_PREFIX}/include/flowViewport/colorPreferences +) diff --git a/lib/flowViewport/colorPreferences/fvpColorChanged.cpp b/lib/flowViewport/colorPreferences/fvpColorChanged.cpp new file mode 100644 index 0000000000..d268ae7edf --- /dev/null +++ b/lib/flowViewport/colorPreferences/fvpColorChanged.cpp @@ -0,0 +1,36 @@ +// Copyright 2023 Autodesk +// +// 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 "fvpColorChanged.h" + +namespace FVP_NS_DEF { + +ColorChanged::ColorChanged( + const PXR_NS::TfToken& token, + const PXR_NS::GfVec4f& oldColor, + const PXR_NS::GfVec4f& newColor) + : _token(token) + , _oldColor(oldColor) + , _newColor(newColor) +{ +} + +const PXR_NS::TfToken& ColorChanged::token() const { return _token; } + +const PXR_NS::GfVec4f& ColorChanged::oldColor() const { return _oldColor; } + +const PXR_NS::GfVec4f& ColorChanged::newColor() const { return _newColor; } + +} // namespace FVP_NS_DEF diff --git a/lib/flowViewport/colorPreferences/fvpColorChanged.h b/lib/flowViewport/colorPreferences/fvpColorChanged.h new file mode 100644 index 0000000000..8644eb6b4d --- /dev/null +++ b/lib/flowViewport/colorPreferences/fvpColorChanged.h @@ -0,0 +1,53 @@ +// Copyright 2023 Autodesk +// +// 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 FVP_COLOR_CHANGED_H +#define FVP_COLOR_CHANGED_H + +#include "flowViewport/api.h" + +#include +#include + +#include + +namespace FVP_NS_DEF { + +/// \class ColorChanged +/// +/// \brief \p Notification class representing a color change and containing +/// the information associated with it. Objects of this class are immutable. +/// +class FVP_API ColorChanged : public Notification +{ +public: + ColorChanged( + const PXR_NS::TfToken& token, + const PXR_NS::GfVec4f& oldColor, + const PXR_NS::GfVec4f& newColor); + ~ColorChanged() override = default; + + const PXR_NS::TfToken& token() const; + const PXR_NS::GfVec4f& oldColor() const; + const PXR_NS::GfVec4f& newColor() const; + +private: + PXR_NS::TfToken _token; + PXR_NS::GfVec4f _oldColor; + PXR_NS::GfVec4f _newColor; +}; + +} // namespace FVP_NS_DEF + +#endif // FVP_COLOR_CHANGED_H diff --git a/lib/flowViewport/colorPreferences/fvpColorPreferences.cpp b/lib/flowViewport/colorPreferences/fvpColorPreferences.cpp new file mode 100644 index 0000000000..a9a6a1113e --- /dev/null +++ b/lib/flowViewport/colorPreferences/fvpColorPreferences.cpp @@ -0,0 +1,81 @@ +// Copyright 2023 Autodesk +// +// 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 "fvpColorPreferences.h" + +#include "fvpColorChanged.h" +#include "fvpColorPreferencesTranslator.h" + +#include + +namespace { +std::shared_ptr instance; +} // namespace + +namespace FVP_NS_DEF { + +ColorPreferences& ColorPreferences::getInstance() +{ + if (!instance) { + instance = std::shared_ptr(new ColorPreferences()); + } + return *instance; +} + +void ColorPreferences::deleteInstance() +{ + if (instance) { + instance.reset(); + } +} + +void ColorPreferences::operator()(const Notification& notification) +{ + PXR_NAMESPACE_USING_DIRECTIVE + if (TF_VERIFY(dynamic_cast(¬ification) != nullptr)) { + notify(notification); + } +} + +bool ColorPreferences::getColor(const PXR_NS::TfToken& preference, PXR_NS::GfVec4f& outColor) const +{ + PXR_NAMESPACE_USING_DIRECTIVE + + if (!TF_VERIFY(_translator != nullptr)) { + return false; + } + + return _translator->getColor(preference, outColor); +} + +void ColorPreferences::setTranslator( + const std::shared_ptr& newTranslator) +{ + if (static_cast(newTranslator) != static_cast(_translator)) { + // Happy path : we're either doing null ptr -> valid ptr, or valid ptr -> null ptr + _translator = newTranslator; + return; + } + PXR_NAMESPACE_USING_DIRECTIVE + if (newTranslator != nullptr && _translator != nullptr) { + TF_CODING_ERROR("ColorPreferences::setTranslator was called with a non-null translator " + "while already having an active one. The second call will be ignored."); + } else if (newTranslator == nullptr && _translator == nullptr) { + TF_CODING_WARNING("ColorPreferences::setTranslator was called with a null translator while " + "already having none."); + } +} + +} // namespace FVP_NS_DEF diff --git a/lib/flowViewport/colorPreferences/fvpColorPreferences.h b/lib/flowViewport/colorPreferences/fvpColorPreferences.h new file mode 100644 index 0000000000..7c6ef15a7f --- /dev/null +++ b/lib/flowViewport/colorPreferences/fvpColorPreferences.h @@ -0,0 +1,102 @@ +// Copyright 2023 Autodesk +// +// 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 FVP_COLOR_PREFERENCES_H +#define FVP_COLOR_PREFERENCES_H + +#include "flowViewport/api.h" + +#include +#include + +#include +#include +#include + +#include +#include + +namespace FVP_NS_DEF { + +class ColorPreferencesTranslator; + +/// \class ColorPreferences +/// +/// \brief Singleton used to retrieve color preferences and +/// subscribe to \p ColorChanged notifications. +/// +/// The \p ColorPreferences class acts as the entry point for Flow Viewport +/// users to get informed about the color preferences of the host. +/// It is a singleton that provides two services : +/// - It rebroadcasts the notifications it receives from the host. It will only rebroadcast +/// notifications of type ColorChanged. +/// - It forwards the \p getColor() calls it receives to its \p ColorPreferencesTranslator. +/// The \p ColorPreferencesTranslator must be supplied by the host to +/// provide the translation between the host and the Flow Viewport. +/// +class FVP_API ColorPreferences final + : public Observer + , public Subject + , public std::enable_shared_from_this +{ +public: + ~ColorPreferences() override = default; + + /// @brief Returns the singleton instance of this class. The referenced object is managed by a + /// \p shared_ptr, enabling the use of \p shared_from_this(). Creates a new instance if none + /// currently exists. + /// @return The singleton instance of this class, backed by a \p shared_ptr. + static ColorPreferences& getInstance(); + + /// @brief Deletes the current singleton instance of this class, if one exists. + static void deleteInstance(); + + /// @brief Operator overload to receive notifications about color changes. + /// This will in turn rebroadcast the notification to all its observers, + /// but only if the notification is of type ColorChanged. + /// If the notification is of any other type, it will not be rebroadcast. + /// This will get called automatically by calling notify on this \p Subject, + /// you do not need to call this manually. + void operator()(const Notification& notification) override; + + /// @brief Retrieve the color value for a given color preference. + /// + /// @param[in] preference The color preference we want to know the color of. + /// @param[out] outColor Output parameter to store the color resulting from + /// the query. + /// + /// @return True if the color was found and \p outColor was populated, + /// false otherwise. + bool getColor(const PXR_NS::TfToken& preference, PXR_NS::GfVec4f& outColor) const; + + /// @brief Set the translator for which to forward \p getColor() calls to. + /// + /// @param[in] translator is a pointer to an implementation of the + /// \p ColorPreferencesTranslator interface. It is the object to which + /// the \p getColor() calls will be forwarded to. + void setTranslator(const std::shared_ptr& newTranslator); + +private: + ColorPreferences() = default; + ColorPreferences(const ColorPreferences&) = delete; + ColorPreferences(ColorPreferences&&) = delete; + ColorPreferences& operator=(const ColorPreferences&) = delete; + ColorPreferences& operator=(ColorPreferences&&) = delete; + + std::shared_ptr _translator; +}; + +} // namespace FVP_NS_DEF + +#endif // FVP_COLOR_PREFERENCES_H diff --git a/lib/flowViewport/colorPreferences/fvpColorPreferencesTokens.cpp b/lib/flowViewport/colorPreferences/fvpColorPreferencesTokens.cpp new file mode 100644 index 0000000000..eb06c17659 --- /dev/null +++ b/lib/flowViewport/colorPreferences/fvpColorPreferencesTokens.cpp @@ -0,0 +1,24 @@ +// Copyright 2023 Autodesk +// +// 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 "fvpColorPreferencesTokens.h" + +// *** TODO / FIXME *** Figure out how to put tokens into non-Pixar namespace. + +PXR_NAMESPACE_OPEN_SCOPE + +TF_DEFINE_PUBLIC_TOKENS(FvpColorPreferencesTokens, FVP_COLOR_PREFERENCES_TOKENS); + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/flowViewport/colorPreferences/fvpColorPreferencesTokens.h b/lib/flowViewport/colorPreferences/fvpColorPreferencesTokens.h new file mode 100644 index 0000000000..95f27308ac --- /dev/null +++ b/lib/flowViewport/colorPreferences/fvpColorPreferencesTokens.h @@ -0,0 +1,43 @@ +// Copyright 2023 Autodesk +// +// 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 FVP_COLOR_PREFERENCES_TOKENS_H +#define FVP_COLOR_PREFERENCES_TOKENS_H + +#include "flowViewport/api.h" + +#include + +// *** TODO / FIXME *** Figure out how to put tokens into non-Pixar namespace. + +PXR_NAMESPACE_OPEN_SCOPE + +// clang-format off +#define FVP_COLOR_PREFERENCES_TOKENS \ + /* The "unspecified" token should only be used by hosts who are incapable of sending precise notifications. */ \ + /* In such cases, it should be used only when sending notifications, and the colors contained in the */ \ + /* corresponding ColorChanged notification should be ignored by the notification recipient. */ \ + (unspecified) \ + (wireframeSelection) \ + (wireframeSelectionSecondary) \ + (vertexSelection) \ + (edgeSelection) \ + (faceSelection) +// clang-format on + +TF_DECLARE_PUBLIC_TOKENS(FvpColorPreferencesTokens, FVP_API, FVP_COLOR_PREFERENCES_TOKENS); + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // FVP_COLOR_PREFERENCES_TOKENS_H diff --git a/lib/flowViewport/colorPreferences/fvpColorPreferencesTranslator.h b/lib/flowViewport/colorPreferences/fvpColorPreferencesTranslator.h new file mode 100644 index 0000000000..383e553567 --- /dev/null +++ b/lib/flowViewport/colorPreferences/fvpColorPreferencesTranslator.h @@ -0,0 +1,52 @@ +// Copyright 2023 Autodesk +// +// 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 FVP_COLOR_PREFERENCES_TRANSLATOR_H +#define FVP_COLOR_PREFERENCES_TRANSLATOR_H + +#include "flowViewport/api.h" + +#include +#include + +#include + +namespace FVP_NS_DEF { + +/// \class ColorPreferencesTranslator +/// +/// A \p ColorPreferencesTranslator acts as a translation layer between +/// the host and the Flow Viewport interface. It is an interface which +/// the host implements. It only needs to implement the virtual method +/// \p getColor() to return a requested color preference. +/// +class FVP_API ColorPreferencesTranslator +{ +public: + /// @brief Retrieve the color value for a given color preference. + /// + /// @param[in] preference The color preference we want to know the color of. + /// @param[out] outColor Output parameter to store the color resulting from + /// the query. + /// + /// @return True if the color was found and \p outColor was populated, + /// false otherwise. + virtual bool getColor(const PXR_NS::TfToken& preference, PXR_NS::GfVec4f& outColor) const = 0; + + virtual ~ColorPreferencesTranslator() = default; +}; + +} // namespace FVP_NS_DEF + +#endif // FVP_COLOR_PREFERENCES_TRANSLATOR_H diff --git a/lib/flowViewport/debugCodes.cpp b/lib/flowViewport/debugCodes.cpp new file mode 100644 index 0000000000..4df57df760 --- /dev/null +++ b/lib/flowViewport/debugCodes.cpp @@ -0,0 +1,49 @@ +// Copyright 2023 Autodesk +// +// 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 "debugCodes.h" + +#include + +PXR_NAMESPACE_OPEN_SCOPE + +// Some variables to enable debug printing information +TF_REGISTRY_FUNCTION(TfDebug) +{ + TF_DEBUG_ENVIRONMENT_SYMBOL( + PXR_NS::FVP_SELECTION_SCENE_INDEX, + "Print information about the Flow Viewport selection scene index."); + + TF_DEBUG_ENVIRONMENT_SYMBOL( + PXR_NS::FVP_SELECTION_TASK, + "Print information about the Flow Viewport selection task."); + + TF_DEBUG_ENVIRONMENT_SYMBOL( + PXR_NS::FVP_SELECTION_TRACKER, + "Print information about the Flow Viewport selection tracker."); + + TF_DEBUG_ENVIRONMENT_SYMBOL( + PXR_NS::FVP_APP_SELECTION_CHANGE, + "Print information about application selection changes sent to the Flow Viewport."); + + TF_DEBUG_ENVIRONMENT_SYMBOL( + PXR_NS::FVP_MERGING_SCENE_INDEX, + "Print information about the Flow Viewport merging scene index."); + + TF_DEBUG_ENVIRONMENT_SYMBOL( + PXR_NS::FVP_WIREFRAME_SELECTION_HIGHLIGHT_SCENE_INDEX, + "Print information about the Flow Viewport wireframe selection highlight scene index."); +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/flowViewport/debugCodes.h b/lib/flowViewport/debugCodes.h new file mode 100644 index 0000000000..29e5d6b04b --- /dev/null +++ b/lib/flowViewport/debugCodes.h @@ -0,0 +1,39 @@ +// Copyright 2023 Autodesk +// +// 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 FVP_DEBUG_CODES_H +#define FVP_DEBUG_CODES_H + +#include +#include + +// No success in defining TF_DEBUG codes in anything but the pxr namespace. +// PPT, 29-Jun-2023. + +PXR_NAMESPACE_OPEN_SCOPE + +// clang-format off +TF_DEBUG_CODES( + FVP_SELECTION_SCENE_INDEX + , FVP_SELECTION_TASK + , FVP_SELECTION_TRACKER + , FVP_APP_SELECTION_CHANGE + , FVP_MERGING_SCENE_INDEX + , FVP_WIREFRAME_SELECTION_HIGHLIGHT_SCENE_INDEX +); +// clang-format on + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // FVP_DEBUG_CODES_H diff --git a/lib/flowViewport/flowViewport.h.src b/lib/flowViewport/flowViewport.h.src new file mode 100644 index 0000000000..f5aac2c9f4 --- /dev/null +++ b/lib/flowViewport/flowViewport.h.src @@ -0,0 +1,63 @@ +// +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +// 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. +// + +#pragma once + +#include + +#define FLOWVIEWPORT_MAJOR_VERSION ${FLOWVIEWPORT_MAJOR_VERSION} +#define FLOWVIEWPORT_MINOR_VERSION ${FLOWVIEWPORT_MINOR_VERSION} +#define FLOWVIEWPORT_PATCH_LEVEL ${FLOWVIEWPORT_PATCH_LEVEL} +#define FLOWVIEWPORT_API_VERSION (FLOWVIEWPORT_MAJOR_VERSION * 10000 + FLOWVIEWPORT_MINOR_VERSION * 100 + FLOWVIEWPORT_PATCH_LEVEL) + +// Flow viewport public namespace string will never change. +#define FVP_NS Fvp +// C preprocessor trickery to expand arguments. +#define FLOWVIEWPORT_CONCAT(A, B) FLOWVIEWPORT_CONCAT_IMPL(A, B) +#define FLOWVIEWPORT_CONCAT_IMPL(A, B) A##B +// Versioned namespace includes the major version number. +#define FVP_VERSIONED_NS FLOWVIEWPORT_CONCAT(FVP_NS, _v${FLOWVIEWPORT_MAJOR_VERSION}) + +// 2023-10-12 : To avoid recreating redundant classes to implement the Observer pattern, +// we reuse the UFE classes for it (plus they have already been tested in the wild). +// Bringing the UFE classes into the FVP namespace allows us to simply copy over the UFE code +// without having to change the dependent code, should we want to remove the UFE dependency. +namespace UFE_VERSIONED_NS { + class Notification; + class Observer; + class Subject; +} // namespace UFE_VERSIONED_NS + +namespace FVP_VERSIONED_NS { + using UFE_VERSIONED_NS::Notification; + using UFE_VERSIONED_NS::Observer; + using UFE_VERSIONED_NS::Subject; +} // namespace FVP_VERSIONED_NS + +// With a using namespace declaration, pull in the versioned namespace into the +// Flow viewport public namespace, to allow client code to use the plain Flow +// viewport namespace, e.g. Fvp::Class. +namespace FVP_NS { + using namespace FVP_VERSIONED_NS; +} + +// Macro to place the Flow viewport symbols in the versioned namespace, which +// is how they will appear in the shared library, e.g. Fvp_v1::Class. +#ifdef DOXYGEN +#define FVP_NS_DEF FVP_NS +#else +#define FVP_NS_DEF FVP_VERSIONED_NS +#endif diff --git a/lib/flowViewport/global.cpp b/lib/flowViewport/global.cpp new file mode 100644 index 0000000000..23a16de714 --- /dev/null +++ b/lib/flowViewport/global.cpp @@ -0,0 +1,38 @@ +// Copyright 2023 Autodesk +// +// 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 "global.h" + +#include + +namespace FVP_NS_DEF { + +void initialize(const InitializationParams& params) +{ + params.colorPreferencesNotificationProvider->addObserver( + Fvp::ColorPreferences::getInstance().shared_from_this()); + Fvp::ColorPreferences::getInstance().setTranslator(params.colorPreferencesTranslator); +} + +void finalize(const InitializationParams& params) +{ + params.colorPreferencesNotificationProvider->removeObserver( + Fvp::ColorPreferences::getInstance().shared_from_this()); + Fvp::ColorPreferences::getInstance().setTranslator(nullptr); + + Fvp::ColorPreferences::deleteInstance(); +} + +} // namespace FVP_NS_DEF diff --git a/lib/flowViewport/global.h b/lib/flowViewport/global.h new file mode 100644 index 0000000000..0cf2737a4f --- /dev/null +++ b/lib/flowViewport/global.h @@ -0,0 +1,36 @@ +// Copyright 2023 Autodesk +// +// 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 FVP_GLOBAL_H +#define FVP_GLOBAL_H + +#include +#include + +#include + +namespace FVP_NS_DEF { + +struct FVP_API InitializationParams +{ + std::shared_ptr colorPreferencesNotificationProvider; + std::shared_ptr colorPreferencesTranslator; +}; + +void FVP_API initialize(const InitializationParams& params); +void FVP_API finalize(const InitializationParams& params); + +} // namespace FVP_NS_DEF + +#endif // FVP_GLOBAL_H diff --git a/lib/flowViewport/sceneIndex/CMakeLists.txt b/lib/flowViewport/sceneIndex/CMakeLists.txt new file mode 100644 index 0000000000..04dd9b47c9 --- /dev/null +++ b/lib/flowViewport/sceneIndex/CMakeLists.txt @@ -0,0 +1,43 @@ +# ----------------------------------------------------------------------------- +# sources +# ----------------------------------------------------------------------------- +target_sources(${TARGET_NAME} + PRIVATE + fvpMergingSceneIndex.cpp + fvpPassThroughSelectionInterfaceSceneIndex.cpp + fvpPathInterface.cpp + fvpPathInterfaceSceneIndex.cpp + fvpRenderIndexProxy.cpp + fvpSelectionInterface.cpp + fvpSelectionSceneIndex.cpp + fvpWireframeSelectionHighlightSceneIndex.cpp +) + +set(HEADERS + fvpMergingSceneIndex.h + fvpPassThroughSelectionInterfaceSceneIndex.h + fvpPathInterface.h + fvpPathInterfaceSceneIndex.h + fvpRenderIndexProxy.h + fvpSelectionInterface.h + fvpSelectionSceneIndex.h + fvpWireframeSelectionHighlightSceneIndex.h +) + +# ----------------------------------------------------------------------------- +# promoted headers +# ----------------------------------------------------------------------------- +mayaUsd_promoteHeaderList( + HEADERS + ${HEADERS} + BASEDIR + ${TARGET_NAME}/sceneIndex +) + +# ----------------------------------------------------------------------------- +# install +# ----------------------------------------------------------------------------- +install(FILES ${HEADERS} + DESTINATION + ${CMAKE_INSTALL_PREFIX}/include/flowViewport/sceneIndex +) diff --git a/lib/flowViewport/sceneIndex/fvpMergingSceneIndex.cpp b/lib/flowViewport/sceneIndex/fvpMergingSceneIndex.cpp new file mode 100644 index 0000000000..6a46bfbe80 --- /dev/null +++ b/lib/flowViewport/sceneIndex/fvpMergingSceneIndex.cpp @@ -0,0 +1,62 @@ +// Copyright 2023 Autodesk +// +// 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 "flowViewport/sceneIndex/fvpMergingSceneIndex.h" + +#include "flowViewport/debugCodes.h" + +PXR_NAMESPACE_USING_DIRECTIVE + +namespace FVP_NS_DEF { + +/* static */ +MergingSceneIndexRefPtr MergingSceneIndex::New() { + TF_DEBUG(FVP_MERGING_SCENE_INDEX) + .Msg("MergingSceneIndex::New() called.\n"); + return TfCreateRefPtr(new MergingSceneIndex); +} + +MergingSceneIndex::MergingSceneIndex() : HdMergingSceneIndex() +{ + TF_DEBUG(FVP_MERGING_SCENE_INDEX) + .Msg("MergingSceneIndex::MergingSceneIndex() called.\n"); +} + +SdfPath MergingSceneIndex::SceneIndexPath(const Ufe::Path& appPath) const +{ + // FLOW_VIEWPORT_TODO May be able to use a caching scheme for app path to + // scene index path conversion using the run-time ID of the UFE path, as it + // is likely that the input scene index that provided a previous answer + // will do so again. To be determined if the following direct approach has + // a measurable performance impact. PPT, 18-Sep-2023. + + // Iterate over input scene indices and ask them to convert the path if + // they support the path interface. + auto inputScenes = GetInputScenes(); + for (const auto& inputScene : inputScenes) { + // Unfortunate that we have to dynamic cast, as soon as we add an input + // scene we know whether it supports the PathInterface or not. + auto pathInterface = dynamic_cast(&*inputScene); + if (pathInterface) { + auto sceneIndexPath = pathInterface->SceneIndexPath(appPath); + if (!sceneIndexPath.IsEmpty()) { + return sceneIndexPath; + } + } + } + return SdfPath(); +} + +} diff --git a/lib/flowViewport/sceneIndex/fvpMergingSceneIndex.h b/lib/flowViewport/sceneIndex/fvpMergingSceneIndex.h new file mode 100644 index 0000000000..d4b98b26cb --- /dev/null +++ b/lib/flowViewport/sceneIndex/fvpMergingSceneIndex.h @@ -0,0 +1,52 @@ +// Copyright 2023 Autodesk +// +// 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 FVP_MERGING_SCENE_INDEX_H +#define FVP_MERGING_SCENE_INDEX_H + +#include "flowViewport/api.h" +#include "flowViewport/sceneIndex/fvpPathInterface.h" + +#include + +namespace FVP_NS_DEF { + +// Pixar declarePtrs.h TF_DECLARE_REF_PTRS macro unusable, places resulting +// type in PXR_NS. +class MergingSceneIndex; +typedef PXR_NS::TfRefPtr MergingSceneIndexRefPtr; +typedef PXR_NS::TfRefPtr MergingSceneIndexConstRefPtr; + +/// \class MergingSceneIndex +/// +/// A merging scene index that delegates conversion of application paths to +/// scene index paths to its inputs. +/// +class MergingSceneIndex + : public PXR_NS::HdMergingSceneIndex, public PathInterface +{ +public: + FVP_API + static MergingSceneIndexRefPtr New(); + + FVP_API + PXR_NS::SdfPath SceneIndexPath(const Ufe::Path& appPath) const override; + +private: + MergingSceneIndex(); +}; + +} + +#endif diff --git a/lib/flowViewport/sceneIndex/fvpPassThroughSelectionInterfaceSceneIndex.cpp b/lib/flowViewport/sceneIndex/fvpPassThroughSelectionInterfaceSceneIndex.cpp new file mode 100644 index 0000000000..7cd82362aa --- /dev/null +++ b/lib/flowViewport/sceneIndex/fvpPassThroughSelectionInterfaceSceneIndex.cpp @@ -0,0 +1,43 @@ +// +// Copyright 2023 Autodesk +// +// 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 "flowViewport/sceneIndex/fvpPassThroughSelectionInterfaceSceneIndex.h" + +#include "flowViewport/debugCodes.h" + +PXR_NAMESPACE_USING_DIRECTIVE + +namespace FVP_NS_DEF { + +PassThroughSelectionInterfaceSceneIndexBase:: +PassThroughSelectionInterfaceSceneIndexBase(HdSceneIndexBaseRefPtr const &inputSceneIndex) + : HdSingleInputFilteringSceneIndexBase(inputSceneIndex) + , _inputSceneIndexSelectionInterface(dynamic_cast(&*_GetInputSceneIndex())) +{ + TF_AXIOM(_inputSceneIndexSelectionInterface); +} + +bool PassThroughSelectionInterfaceSceneIndexBase::IsFullySelected(const SdfPath& primPath) const +{ + return _inputSceneIndexSelectionInterface->IsFullySelected(primPath); +} + +bool PassThroughSelectionInterfaceSceneIndexBase::HasFullySelectedAncestorInclusive(const SdfPath& primPath) const +{ + return _inputSceneIndexSelectionInterface->HasFullySelectedAncestorInclusive(primPath); +} + +} diff --git a/lib/flowViewport/sceneIndex/fvpPassThroughSelectionInterfaceSceneIndex.h b/lib/flowViewport/sceneIndex/fvpPassThroughSelectionInterfaceSceneIndex.h new file mode 100644 index 0000000000..641c16e3f6 --- /dev/null +++ b/lib/flowViewport/sceneIndex/fvpPassThroughSelectionInterfaceSceneIndex.h @@ -0,0 +1,58 @@ +// Copyright 2023 Autodesk +// +// 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 FVP_PASS_THROUGH_SELECTION_INTERFACE_SCENE_INDEX_H +#define FVP_PASS_THROUGH_SELECTION_INTERFACE_SCENE_INDEX_H + +#include "flowViewport/api.h" +#include "flowViewport/sceneIndex/fvpSelectionInterface.h" + +#include + +namespace FVP_NS_DEF { + +/// \class PassThroughSelectionInterfaceSceneIndexBase +/// +/// Convenience base class that passes through the SelectionInterface queries +/// to its input filtering scene index. +/// +class PassThroughSelectionInterfaceSceneIndexBase + : public PXR_NS::HdSingleInputFilteringSceneIndexBase, + public SelectionInterface +{ +public: + + //! Selection interface overrides. + //@{ + FVP_API + bool IsFullySelected(const PXR_NS::SdfPath& primPath) const override; + + FVP_API + bool HasFullySelectedAncestorInclusive(const PXR_NS::SdfPath& primPath) const override; + //@} + +protected: + + FVP_API + PassThroughSelectionInterfaceSceneIndexBase( + const PXR_NS::HdSceneIndexBaseRefPtr &inputSceneIndex); + +private: + + const SelectionInterface* const _inputSceneIndexSelectionInterface; +}; + +} + +#endif diff --git a/lib/flowViewport/sceneIndex/fvpPathInterface.cpp b/lib/flowViewport/sceneIndex/fvpPathInterface.cpp new file mode 100644 index 0000000000..28445061d3 --- /dev/null +++ b/lib/flowViewport/sceneIndex/fvpPathInterface.cpp @@ -0,0 +1,22 @@ +// Copyright 2023 Autodesk +// +// 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 "flowViewport/sceneIndex/fvpPathInterface.h" + +namespace FVP_NS_DEF { + +PathInterface::~PathInterface() {} + +} diff --git a/lib/flowViewport/sceneIndex/fvpPathInterface.h b/lib/flowViewport/sceneIndex/fvpPathInterface.h new file mode 100644 index 0000000000..55d16c944e --- /dev/null +++ b/lib/flowViewport/sceneIndex/fvpPathInterface.h @@ -0,0 +1,61 @@ +// Copyright 2023 Autodesk +// +// 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 FVP_PATH_INTERFACE_H +#define FVP_PATH_INTERFACE_H + +#include "flowViewport/api.h" + +#include + +#include + +UFE_NS_DEF { +class Path; +} + +PXR_NAMESPACE_OPEN_SCOPE +class SdfPath; +PXR_NAMESPACE_CLOSE_SCOPE + +namespace FVP_NS_DEF { + +/// \class PathInterface +/// +/// A pure interface class to allow for conversion between an application's +/// path, expressed as a Ufe::Path, into an SdfPath valid for a scene index. +/// To be used as a mix-in class for scene indices. +/// +class PathInterface +{ +public: + + //! Return the prim path corresponding to the argument application path. + //! If no such path exists, an empty SdfPath should be returned. + //! \return scene index path. + FVP_API + virtual PXR_NS::SdfPath SceneIndexPath(const Ufe::Path& appPath) const = 0; + +protected: + + FVP_API + PathInterface() = default; + + FVP_API + virtual ~PathInterface(); +}; + +} + +#endif diff --git a/lib/flowViewport/sceneIndex/fvpPathInterfaceSceneIndex.cpp b/lib/flowViewport/sceneIndex/fvpPathInterfaceSceneIndex.cpp new file mode 100644 index 0000000000..d0807f782b --- /dev/null +++ b/lib/flowViewport/sceneIndex/fvpPathInterfaceSceneIndex.cpp @@ -0,0 +1,64 @@ +// +// Copyright 2023 Autodesk +// +// 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 "flowViewport/sceneIndex/fvpPathInterfaceSceneIndex.h" + +PXR_NAMESPACE_USING_DIRECTIVE + +namespace FVP_NS_DEF { + +PathInterfaceSceneIndexBase:: +PathInterfaceSceneIndexBase(HdSceneIndexBaseRefPtr const &inputSceneIndex) + : HdSingleInputFilteringSceneIndexBase(inputSceneIndex) +{} + +HdSceneIndexPrim +PathInterfaceSceneIndexBase::GetPrim(const SdfPath &primPath) const +{ + return _GetInputSceneIndex()->GetPrim(primPath); +} + +SdfPathVector +PathInterfaceSceneIndexBase::GetChildPrimPaths(const SdfPath &primPath) const +{ + return _GetInputSceneIndex()->GetChildPrimPaths(primPath); +} + +void +PathInterfaceSceneIndexBase::_PrimsAdded( + const HdSceneIndexBase &sender, + const HdSceneIndexObserver::AddedPrimEntries &entries) +{ + _SendPrimsAdded(entries); +} + +void +PathInterfaceSceneIndexBase::_PrimsDirtied( + const HdSceneIndexBase &sender, + const HdSceneIndexObserver::DirtiedPrimEntries &entries) +{ + _SendPrimsDirtied(entries); +} + +void +PathInterfaceSceneIndexBase::_PrimsRemoved( + const HdSceneIndexBase &sender, + const HdSceneIndexObserver::RemovedPrimEntries &entries) +{ + _SendPrimsRemoved(entries); +} + +} diff --git a/lib/flowViewport/sceneIndex/fvpPathInterfaceSceneIndex.h b/lib/flowViewport/sceneIndex/fvpPathInterfaceSceneIndex.h new file mode 100644 index 0000000000..193d865662 --- /dev/null +++ b/lib/flowViewport/sceneIndex/fvpPathInterfaceSceneIndex.h @@ -0,0 +1,66 @@ +// Copyright 2023 Autodesk +// +// 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 FVP_PATH_INTERFACE_SCENE_INDEX_H +#define FVP_PATH_INTERFACE_SCENE_INDEX_H + +#include "flowViewport/api.h" +#include "flowViewport/sceneIndex/fvpPathInterface.h" + +#include + +namespace FVP_NS_DEF { + +/// \class PathInterfaceSceneIndexBase +/// +/// A simple pass-through filtering scene index that adds support for the path +/// interface. Derived classes need only implement the +/// PathInterface::SceneIndexPath() virtual. +/// +class PathInterfaceSceneIndexBase + : public PXR_NS::HdSingleInputFilteringSceneIndexBase, public PathInterface +{ +public: + + FVP_API + PXR_NS::HdSceneIndexPrim GetPrim(const PXR_NS::SdfPath &primPath) const override; + + FVP_API + PXR_NS::SdfPathVector GetChildPrimPaths(const PXR_NS::SdfPath &primPath) const override; + +protected: + + FVP_API + PathInterfaceSceneIndexBase( + const PXR_NS::HdSceneIndexBaseRefPtr &inputSceneIndex); + + FVP_API + void _PrimsAdded( + const PXR_NS::HdSceneIndexBase &sender, + const PXR_NS::HdSceneIndexObserver::AddedPrimEntries &entries) override; + + FVP_API + void _PrimsRemoved( + const PXR_NS::HdSceneIndexBase &sender, + const PXR_NS::HdSceneIndexObserver::RemovedPrimEntries &entries) override; + + FVP_API + void _PrimsDirtied( + const PXR_NS::HdSceneIndexBase &sender, + const PXR_NS::HdSceneIndexObserver::DirtiedPrimEntries &entries) override; +}; + +} + +#endif diff --git a/lib/flowViewport/sceneIndex/fvpRenderIndexProxy.cpp b/lib/flowViewport/sceneIndex/fvpRenderIndexProxy.cpp new file mode 100644 index 0000000000..8f27cbe642 --- /dev/null +++ b/lib/flowViewport/sceneIndex/fvpRenderIndexProxy.cpp @@ -0,0 +1,129 @@ +// +// Copyright 2022 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. +// +// Copyright 2023 Autodesk +// +// 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 "flowViewport/sceneIndex/fvpRenderIndexProxy.h" +#include "flowViewport/sceneIndex/fvpMergingSceneIndex.h" + +#include + +PXR_NAMESPACE_USING_DIRECTIVE + +namespace { + +// Copy-pasted and adapted from USD 0.23.08 pxr/imaging/hd/renderIndex.cpp. +// PPT, 1-Sep-2023. +HdSceneIndexBaseRefPtr +_GetInputScene(const HdPrefixingSceneIndexRefPtr &prefixingScene) +{ + const auto inputScenes = prefixingScene->GetInputScenes(); + if (inputScenes.size() == 1) { + return inputScenes[0]; + } + TF_CODING_ERROR("Expected exactly one scene index from " + "HdPrefixingSceneIndex::GetInputScenes"); + return TfNullPtr; +} + +} + +namespace FVP_NS_DEF { + +RenderIndexProxy::RenderIndexProxy(PXR_NS::HdRenderIndex* renderIndex) : + _renderIndex(renderIndex), _mergingSceneIndex(MergingSceneIndex::New()) +{ + TF_AXIOM(_renderIndex); + TF_AXIOM(_mergingSceneIndex); + _mergingSceneIndex->SetDisplayName("Flow Viewport Merging Scene Index"); +} + +void RenderIndexProxy::InsertSceneIndex( + const PXR_NS::HdSceneIndexBaseRefPtr& inputScene, + const PXR_NS::SdfPath& scenePathPrefix, + bool needsPrefixing /* = true */ +) +{ + // Copy-pasted and adapted from USD 0.23.08 + // HdRenderIndex::InsertSceneIndex() code, to preserve prefixing scene + // index creation capability. PPT, 31-Aug-2023. + auto resolvedScene = inputScene; + if (needsPrefixing && scenePathPrefix != SdfPath::AbsoluteRootPath()) { + resolvedScene = HdPrefixingSceneIndex::New(inputScene, scenePathPrefix); + } + + _mergingSceneIndex->AddInputScene(resolvedScene, scenePathPrefix); +} + +void RenderIndexProxy::RemoveSceneIndex( + const HdSceneIndexBaseRefPtr &inputScene +) +{ + // Copy-pasted and adapted from USD 0.23.08 + // HdRenderIndex::RemoveSceneIndex() code, to preserve prefixing scene + // index removal capability. PPT, 1-Sep-2023. + + const auto resolvedScenes = _mergingSceneIndex->GetInputScenes(); + + // Two cases: + // - Given scene index was added by InsertSceneIndex with + // scenePathPrefix = "/". We find it just by going over the + // input scenes of _mergingSceneIndex. + // - Given scene index was added by InsertSceneIndex with + // non-trivial scenePathPrefix. We need to find the HdPrefixingSceneIndex + // among the input scenes of _mergingSceneIndex that was constructed + // from the given scene index. + for (const auto& resolvedScene : resolvedScenes) { + if (inputScene == resolvedScene) { + _mergingSceneIndex->RemoveInputScene(resolvedScene); + return; + } + if (HdPrefixingSceneIndexRefPtr const prefixingScene = + TfDynamic_cast(resolvedScene)) { + if (inputScene == _GetInputScene(prefixingScene)) { + _mergingSceneIndex->RemoveInputScene(resolvedScene); + return; + } + } + } +} + +PXR_NS::HdSceneIndexBaseRefPtr RenderIndexProxy::GetMergingSceneIndex() const +{ + return _mergingSceneIndex; +} + +} diff --git a/lib/flowViewport/sceneIndex/fvpRenderIndexProxy.h b/lib/flowViewport/sceneIndex/fvpRenderIndexProxy.h new file mode 100644 index 0000000000..5865e439b8 --- /dev/null +++ b/lib/flowViewport/sceneIndex/fvpRenderIndexProxy.h @@ -0,0 +1,96 @@ +// +// Copyright 2022 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. +// +// Copyright 2023 Autodesk +// +// 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 FVP_RENDER_INDEX_PROXY_H +#define FVP_RENDER_INDEX_PROXY_H + +#include "flowViewport/api.h" + +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE +class HdRenderIndex; +PXR_NAMESPACE_CLOSE_SCOPE + +namespace FVP_NS_DEF { + +/// \class RenderIndexProxy +/// +/// Class to protect access to the render index, and provide a merging +/// scene index under Flow viewport control. +/// +/// The merging scene index accessed through the Hydra render has hard-coded +/// downstream filtering scene indices. This render index proxy provides its +/// own merging scene index, after which we can easily insert downstream +/// filtering scene indices. +/// +/// FLOW_VIEWPORT_TODO At time of writing, the renderIndex data member is +/// unused. Re-evaluate the responsibilities, future extension, and naming of +/// this class. PPT, 22-Sep-2023. + +class RenderIndexProxy +{ +public: + + FVP_API + RenderIndexProxy(PXR_NS::HdRenderIndex* renderIndex); + + FVP_API + void InsertSceneIndex( + const PXR_NS::HdSceneIndexBaseRefPtr& inputScene, + const PXR_NS::SdfPath& scenePathPrefix, + bool needsPrefixing = true); + + FVP_API + void RemoveSceneIndex(const PXR_NS::HdSceneIndexBaseRefPtr &inputScene); + + // Return the additional Flow Viewport merging scene index onto which input + // scenes are added. Returned as a base scene index to preserve + // encapsulation. + FVP_API + PXR_NS::HdSceneIndexBaseRefPtr GetMergingSceneIndex() const; + +private: + + PXR_NS::HdRenderIndex* const _renderIndex{nullptr}; + PXR_NS::HdMergingSceneIndexRefPtr _mergingSceneIndex; +}; + +} + +#endif diff --git a/lib/flowViewport/sceneIndex/fvpSelectionInterface.cpp b/lib/flowViewport/sceneIndex/fvpSelectionInterface.cpp new file mode 100644 index 0000000000..bb5b385f37 --- /dev/null +++ b/lib/flowViewport/sceneIndex/fvpSelectionInterface.cpp @@ -0,0 +1,22 @@ +// Copyright 2023 Autodesk +// +// 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 "flowViewport/sceneIndex/fvpSelectionInterface.h" + +namespace FVP_NS_DEF { + +SelectionInterface::~SelectionInterface() {} + +} diff --git a/lib/flowViewport/sceneIndex/fvpSelectionInterface.h b/lib/flowViewport/sceneIndex/fvpSelectionInterface.h new file mode 100644 index 0000000000..3ab73a2c78 --- /dev/null +++ b/lib/flowViewport/sceneIndex/fvpSelectionInterface.h @@ -0,0 +1,63 @@ +// Copyright 2023 Autodesk +// +// 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 FVP_SELECTION_INTERFACE_H +#define FVP_SELECTION_INTERFACE_H + +#include "flowViewport/api.h" + +#include + +PXR_NAMESPACE_OPEN_SCOPE +class SdfPath; +PXR_NAMESPACE_CLOSE_SCOPE + +namespace FVP_NS_DEF { + +/// \class SelectionInterface +/// +/// A pure interface class to allow querying a scene index for selected status +/// without inspecting data sources in scene prims. To be used as a mix-in +/// class for scene indices. +/// +class SelectionInterface +{ +public: + + //! Returns whether the prim path is a fully selected prim. + //! If no such path exists, false is returned. + //! \return Whether the argument path is a fully selected prim. + FVP_API + virtual bool IsFullySelected(const PXR_NS::SdfPath& primPath) const = 0; + + //! Returns whether the prim path is a fully selected prim, or has an + //! ancestor that is a fully selected prim. If no such path exists, false + //! is returned. + //! \return Whether the argument path is a fully selected prim, or has a + //! fully selected ancestor. + FVP_API + virtual bool HasFullySelectedAncestorInclusive(const PXR_NS::SdfPath& primPath) const = 0; + +protected: + + FVP_API + SelectionInterface() = default; + + FVP_API + virtual ~SelectionInterface(); +}; + +} + +#endif diff --git a/lib/flowViewport/sceneIndex/fvpSelectionSceneIndex.cpp b/lib/flowViewport/sceneIndex/fvpSelectionSceneIndex.cpp new file mode 100644 index 0000000000..3236f13b08 --- /dev/null +++ b/lib/flowViewport/sceneIndex/fvpSelectionSceneIndex.cpp @@ -0,0 +1,372 @@ +// +// Copyright 2022 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. +// +// Copyright 2023 Autodesk +// +// 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 "flowViewport/sceneIndex/fvpSelectionSceneIndex.h" +#include "flowViewport/sceneIndex/fvpPathInterface.h" + +#include "flowViewport/debugCodes.h" + +#include "pxr/imaging/hd/retainedDataSource.h" +#include "pxr/imaging/hd/selectionSchema.h" +#include "pxr/imaging/hd/selectionsSchema.h" + +#include +#include + +PXR_NAMESPACE_USING_DIRECTIVE + +namespace { +const HdDataSourceLocatorSet selectionsSchemaDefaultLocator{HdSelectionsSchema::GetDefaultLocator()}; +} + +namespace FVP_NS_DEF { + +namespace SelectionSceneIndex_Impl +{ + +struct _PrimSelectionState +{ + // Container data sources conforming to HdSelectionSchema + std::vector selectionSources; + + HdDataSourceBaseHandle GetVectorDataSource() { + return HdSelectionsSchema::BuildRetained( + selectionSources.size(), + selectionSources.data()); + } +}; + +struct _Selection +{ + bool IsSelected(const SdfPath& primPath) const + { + return pathToState.find(primPath) != pathToState.end(); + } + + bool HasSelectedAncestorInclusive(const SdfPath& primPath) const + { + // FLOW_VIEWPORT_TODO Prefix tree would be much higher performance + // than iterating over the whole selection, especially for a large + // selection. PPT, 13-Sep-2023. + for(const auto& entry : pathToState) { + if (primPath.HasPrefix(entry.first)) { + return true; + } + } + return false; + } + + // Maps prim path to data sources to be returned by the vector data + // source at locator selections. + std::map pathToState; +}; + +class _PrimSource : public HdContainerDataSource +{ +public: + HD_DECLARE_DATASOURCE(_PrimSource); + + _PrimSource(HdContainerDataSourceHandle const &inputSource, + _SelectionSharedPtr const selection, + const SdfPath &primPath) + : _inputSource(inputSource) + , _selection(selection) + , _primPath(primPath) + { + } + + TfTokenVector GetNames() override + { + TfTokenVector names = _inputSource->GetNames(); + if (_selection->IsSelected(_primPath)) { + names.push_back(HdSelectionsSchemaTokens->selections); + } + return names; + } + + HdDataSourceBaseHandle Get(const TfToken &name) override + { + if (name == HdSelectionsSchemaTokens->selections) { + auto it = _selection->pathToState.find(_primPath); + return (it != _selection->pathToState.end()) ? + it->second.GetVectorDataSource() : nullptr; + } + + return _inputSource->Get(name); + } + +private: + HdContainerDataSourceHandle const _inputSource; + _SelectionSharedPtr const _selection; + const SdfPath _primPath; +}; + +} + +using namespace SelectionSceneIndex_Impl; + +SelectionSceneIndexRefPtr +SelectionSceneIndex::New(HdSceneIndexBaseRefPtr const &inputSceneIndex) +{ + TF_DEBUG(FVP_SELECTION_SCENE_INDEX) + .Msg("SelectionSceneIndex::New() called.\n"); + return TfCreateRefPtr(new SelectionSceneIndex(inputSceneIndex)); +} + +SelectionSceneIndex:: +SelectionSceneIndex(HdSceneIndexBaseRefPtr const &inputSceneIndex) + : HdSingleInputFilteringSceneIndexBase(inputSceneIndex) + , _selection(std::make_shared<_Selection>()) + , _inputSceneIndexPathInterface(dynamic_cast(&*_GetInputSceneIndex())) +{ + TF_DEBUG(FVP_SELECTION_SCENE_INDEX) + .Msg("SelectionSceneIndex::SelectionSceneIndex() called.\n"); + + TF_AXIOM(_inputSceneIndexPathInterface); +} + +HdSceneIndexPrim +SelectionSceneIndex::GetPrim(const SdfPath &primPath) const +{ + TF_DEBUG(FVP_SELECTION_SCENE_INDEX) + .Msg("SelectionSceneIndex::GetPrim() called.\n"); + + HdSceneIndexPrim result = _GetInputSceneIndex()->GetPrim(primPath); + if (!result.dataSource) { + return result; + } + + result.dataSource = _PrimSource::New( + result.dataSource, _selection, primPath); + + return result; +} + +SdfPathVector +SelectionSceneIndex::GetChildPrimPaths(const SdfPath &primPath) const +{ + TF_DEBUG(FVP_SELECTION_SCENE_INDEX) + .Msg("SelectionSceneIndex::GetChildPrimPaths() called.\n"); + + return _GetInputSceneIndex()->GetChildPrimPaths(primPath); +} + +void +SelectionSceneIndex::AddSelection(const Ufe::Path& appPath) +{ + TF_DEBUG(FVP_SELECTION_SCENE_INDEX) + .Msg("SelectionSceneIndex::AddSelection(const Ufe::Path& %s) called.\n", Ufe::PathString::string(appPath).c_str()); + + HdSelectionSchema::Builder selectionBuilder; + selectionBuilder.SetFullySelected( + HdRetainedTypedSampledDataSource::New(true)); + + // Call our input scene index to convert the application path to a scene + // index path. + auto sceneIndexPath = SceneIndexPath(appPath); + + if (sceneIndexPath.IsEmpty()) { + return; + } + + TF_DEBUG(FVP_SELECTION_SCENE_INDEX) + .Msg(" Adding %s to the Hydra selection.\n", sceneIndexPath.GetText()); + + _selection->pathToState[sceneIndexPath].selectionSources.push_back( + selectionBuilder.Build()); + + _SendPrimsDirtied({{sceneIndexPath, selectionsSchemaDefaultLocator}}); +} + +void SelectionSceneIndex::RemoveSelection(const Ufe::Path& appPath) +{ + TF_DEBUG(FVP_SELECTION_SCENE_INDEX) + .Msg("SelectionSceneIndex::RemoveSelection(const Ufe::Path& %s) called.\n", Ufe::PathString::string(appPath).c_str()); + + // Call our input scene index to convert the application path to a scene + // index path. If there is no path, or the path is not selected, early out. + auto sceneIndexPath = SceneIndexPath(appPath); + + if (sceneIndexPath.IsEmpty() || + (_selection->pathToState.erase(sceneIndexPath) != 1)) { + return; + } + + HdSceneIndexObserver::DirtiedPrimEntries entry; + entry.emplace_back(sceneIndexPath, selectionsSchemaDefaultLocator); + + _SendPrimsDirtied(entry); +} + +void +SelectionSceneIndex::ClearSelection() +{ + TF_DEBUG(FVP_SELECTION_SCENE_INDEX) + .Msg("SelectionSceneIndex::ClearSelection() called.\n"); + + if (_selection->pathToState.empty()) { + return; + } + + HdSceneIndexObserver::DirtiedPrimEntries entries; + entries.reserve(_selection->pathToState.size()); + for (const auto &pathAndSelections : _selection->pathToState) { + entries.emplace_back(pathAndSelections.first, selectionsSchemaDefaultLocator); + } + + _selection->pathToState.clear(); + + _SendPrimsDirtied(entries); +} + +void SelectionSceneIndex::ReplaceSelection(const Ufe::Selection& selection) +{ + TF_DEBUG(FVP_SELECTION_SCENE_INDEX) + .Msg("SelectionSceneIndex::ReplaceSelection() called.\n"); + + HdSelectionSchema::Builder selectionBuilder; + selectionBuilder.SetFullySelected( + HdRetainedTypedSampledDataSource::New(true)); + + // Process the selection replace by performing dirty notification of the + // existing selection state. We could do this more efficiently by + // accounting for overlapping previous and new selections. + HdSceneIndexObserver::DirtiedPrimEntries entries; + entries.reserve(_selection->pathToState.size() + selection.size()); + for (const auto &pathAndSelections : _selection->pathToState) { + entries.emplace_back(pathAndSelections.first, selectionsSchemaDefaultLocator); + } + + _selection->pathToState.clear(); + + for (const auto& snItem : selection) { + // Call our input scene index to convert the application path to a scene + // index path. + auto sceneIndexPath = SceneIndexPath(snItem->path()); + + if (sceneIndexPath.IsEmpty()) { + continue; + } + + TF_DEBUG(FVP_SELECTION_SCENE_INDEX) + .Msg(" Adding %s to the Hydra selection.\n", sceneIndexPath.GetText()); + + _selection->pathToState[sceneIndexPath].selectionSources.push_back( + selectionBuilder.Build()); + + entries.emplace_back(sceneIndexPath, selectionsSchemaDefaultLocator); + } + + _SendPrimsDirtied(entries); +} + +bool SelectionSceneIndex::IsFullySelected(const SdfPath& primPath) const +{ + return _selection->IsSelected(primPath); +} + +bool SelectionSceneIndex::HasFullySelectedAncestorInclusive(const SdfPath& primPath) const +{ + return _selection->HasSelectedAncestorInclusive(primPath); +} + +SdfPath SelectionSceneIndex::SceneIndexPath(const Ufe::Path& appPath) const +{ + auto sceneIndexPath = _inputSceneIndexPathInterface->SceneIndexPath(appPath); + + if (sceneIndexPath.IsEmpty()) { + TF_WARN("SelectionSceneIndex::SceneIndexPath(%s) returned an empty path, Hydra selection will be incorrect", Ufe::PathString::string(appPath).c_str()); + } + + return sceneIndexPath; +} + +SdfPathVector SelectionSceneIndex::GetFullySelectedPaths() const +{ + SdfPathVector fullySelectedPaths; + fullySelectedPaths.reserve(_selection->pathToState.size()); + for(const auto& entry : _selection->pathToState) { + fullySelectedPaths.emplace_back(entry.first); + } + return fullySelectedPaths; +} + +void +SelectionSceneIndex::_PrimsAdded( + const HdSceneIndexBase &sender, + const HdSceneIndexObserver::AddedPrimEntries &entries) +{ + TF_DEBUG(FVP_SELECTION_SCENE_INDEX) + .Msg("SelectionSceneIndex::_PrimsAdded() called.\n"); + + _SendPrimsAdded(entries); +} + +void +SelectionSceneIndex::_PrimsDirtied( + const HdSceneIndexBase &sender, + const HdSceneIndexObserver::DirtiedPrimEntries &entries) +{ + TF_DEBUG(FVP_SELECTION_SCENE_INDEX) + .Msg("SelectionSceneIndex::_PrimsDirtied() called.\n"); + + _SendPrimsDirtied(entries); +} + +void +SelectionSceneIndex::_PrimsRemoved( + const HdSceneIndexBase &sender, + const HdSceneIndexObserver::RemovedPrimEntries &entries) +{ + TF_DEBUG(FVP_SELECTION_SCENE_INDEX) + .Msg("SelectionSceneIndex::_PrimsRemoved() called.\n"); + + if (!_selection->pathToState.empty()) { + for (const auto &entry : entries) { + auto it = _selection->pathToState.lower_bound(entry.primPath); + while (it != _selection->pathToState.end() && + it->first.HasPrefix(entry.primPath)) { + it = _selection->pathToState.erase(it); + } + } + } + + _SendPrimsRemoved(entries); +} + +} diff --git a/lib/flowViewport/sceneIndex/fvpSelectionSceneIndex.h b/lib/flowViewport/sceneIndex/fvpSelectionSceneIndex.h new file mode 100644 index 0000000000..c65b973420 --- /dev/null +++ b/lib/flowViewport/sceneIndex/fvpSelectionSceneIndex.h @@ -0,0 +1,153 @@ +// +// Copyright 2022 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. +// +// Copyright 2023 Autodesk +// +// 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 FVP_SELECTION_SCENE_INDEX_H +#define FVP_SELECTION_SCENE_INDEX_H + +#include "flowViewport/api.h" +#include "flowViewport/sceneIndex/fvpSelectionInterface.h" +#include "flowViewport/sceneIndex/fvpPathInterface.h" + +#include + +#include + +#include + +UFE_NS_DEF { +class Path; +class Selection; +} + +namespace FVP_NS_DEF { + +class PathInterface; + +// Pixar declarePtrs.h TF_DECLARE_REF_PTRS macro unusable, places resulting +// type in PXR_NS. +class SelectionSceneIndex; +typedef PXR_NS::TfRefPtr SelectionSceneIndexRefPtr; +typedef PXR_NS::TfRefPtr SelectionSceneIndexConstRefPtr; + +namespace SelectionSceneIndex_Impl +{ +using _SelectionSharedPtr = std::shared_ptr; +} + +/// \class SelectionSceneIndex +/// +/// A simple scene index adding HdSelectionsSchema to all prims selected +/// in the application. +/// +class SelectionSceneIndex final + : public PXR_NS::HdSingleInputFilteringSceneIndexBase + , public SelectionInterface + , public PathInterface +{ +public: + FVP_API + static SelectionSceneIndexRefPtr New( + PXR_NS::HdSceneIndexBaseRefPtr const &inputSceneIndex); + + FVP_API + PXR_NS::HdSceneIndexPrim GetPrim(const PXR_NS::SdfPath &primPath) const override; + + FVP_API + PXR_NS::SdfPathVector GetChildPrimPaths(const PXR_NS::SdfPath &primPath) const override; + + /// Given a path (including usd proxy path inside a native instance) of + /// a USD prim, determine the corresponding prim in the Usd scene index + /// (filtered by the UsdImagingNiPrototypePropagatingSceneIndex) and + /// populate its selections data source. + FVP_API + void AddSelection(const Ufe::Path& appPath); + + FVP_API + void RemoveSelection(const Ufe::Path& appPath); + + FVP_API + void ReplaceSelection(const Ufe::Selection& selection); + + /// Reset the scene index selection state. + FVP_API + void ClearSelection(); + + //! Selection interface overrides. + //@{ + FVP_API + bool IsFullySelected(const PXR_NS::SdfPath& primPath) const override; + + FVP_API + bool HasFullySelectedAncestorInclusive(const PXR_NS::SdfPath& primPath) const override; + //@} + + //! Path interface override. Forwards the call to the input scene index, + //! and warns about empty return paths. + //@{ + FVP_API + PXR_NS::SdfPath SceneIndexPath(const Ufe::Path& appPath) const override; + //@} + + FVP_API + PXR_NS::SdfPathVector GetFullySelectedPaths() const; + +protected: + void _PrimsAdded( + const PXR_NS::HdSceneIndexBase &sender, + const PXR_NS::HdSceneIndexObserver::AddedPrimEntries &entries) override; + + void _PrimsRemoved( + const PXR_NS::HdSceneIndexBase &sender, + const PXR_NS::HdSceneIndexObserver::RemovedPrimEntries &entries) override; + + void _PrimsDirtied( + const PXR_NS::HdSceneIndexBase &sender, + const PXR_NS::HdSceneIndexObserver::DirtiedPrimEntries &entries) override; + + +private: + SelectionSceneIndex( + const PXR_NS::HdSceneIndexBaseRefPtr &inputSceneIndex); + + SelectionSceneIndex_Impl::_SelectionSharedPtr _selection; + + const PathInterface* const _inputSceneIndexPathInterface; +}; + +} + +#endif diff --git a/lib/flowViewport/sceneIndex/fvpWireframeSelectionHighlightSceneIndex.cpp b/lib/flowViewport/sceneIndex/fvpWireframeSelectionHighlightSceneIndex.cpp new file mode 100644 index 0000000000..e8f691378c --- /dev/null +++ b/lib/flowViewport/sceneIndex/fvpWireframeSelectionHighlightSceneIndex.cpp @@ -0,0 +1,186 @@ +// +// Copyright 2023 Autodesk +// +// 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 "flowViewport/sceneIndex/fvpWireframeSelectionHighlightSceneIndex.h" + +#include "flowViewport/debugCodes.h" + +#include +#include +#include +#include + +PXR_NAMESPACE_USING_DIRECTIVE + +namespace { +const HdRetainedContainerDataSourceHandle sSelectedDisplayStyleDataSource + = HdRetainedContainerDataSource::New( + HdLegacyDisplayStyleSchemaTokens->displayStyle, + HdRetainedContainerDataSource::New( + HdLegacyDisplayStyleSchemaTokens->reprSelector, + HdRetainedTypedSampledDataSource>::New( + { HdReprTokens->refinedWireOnSurf, HdReprTokens->wireOnSurf, TfToken() }))); + +const HdRetainedContainerDataSourceHandle sUnselectedDisplayStyleDataSource + = HdRetainedContainerDataSource::New( + HdLegacyDisplayStyleSchemaTokens->displayStyle, + HdRetainedContainerDataSource::New( + HdLegacyDisplayStyleSchemaTokens->reprSelector, + HdRetainedTypedSampledDataSource>::New( + { HdReprTokens->refined, HdReprTokens->refined, TfToken() }))); + +const HdDataSourceLocator reprSelectorLocator( + HdLegacyDisplayStyleSchemaTokens->displayStyle, + HdLegacyDisplayStyleSchemaTokens->reprSelector); +} + +namespace FVP_NS_DEF { + +HdSceneIndexBaseRefPtr +WireframeSelectionHighlightSceneIndex::New(HdSceneIndexBaseRefPtr const &inputSceneIndex) +{ + return TfCreateRefPtr(new WireframeSelectionHighlightSceneIndex(inputSceneIndex)); +} + +const HdDataSourceLocator& WireframeSelectionHighlightSceneIndex::ReprSelectorLocator() +{ + return reprSelectorLocator; +} + +WireframeSelectionHighlightSceneIndex:: +WireframeSelectionHighlightSceneIndex(HdSceneIndexBaseRefPtr const &inputSceneIndex) + : PassThroughSelectionInterfaceSceneIndexBase(inputSceneIndex) +{} + +HdSceneIndexPrim +WireframeSelectionHighlightSceneIndex::GetPrim(const SdfPath &primPath) const +{ + TF_DEBUG(FVP_WIREFRAME_SELECTION_HIGHLIGHT_SCENE_INDEX) + .Msg("WireframeSelectionHighlightSceneIndex::GetPrim(%s) called.\n", primPath.GetText()); + + auto prim = _GetInputSceneIndex()->GetPrim(primPath); + + // If prim is in excluded hierarchy, don't provide selection highlighting + // for it. Selection highlighting is done only on meshes. HYDRA-569: this + // means that HdsiImplicitSurfaceSceneIndex must be used before this scene + // index to convert implicit surfaces (e.g. USD cube / cone / sphere / + // capsule primitive types) to meshes. + if (!isExcluded(primPath) && prim.primType == HdPrimTypeTokens->mesh) { + prim.dataSource = HdOverlayContainerDataSource::New( + { prim.dataSource, HasFullySelectedAncestorInclusive(primPath) ? + sSelectedDisplayStyleDataSource : + sUnselectedDisplayStyleDataSource }); + } + return prim; + +} + +SdfPathVector +WireframeSelectionHighlightSceneIndex::GetChildPrimPaths(const SdfPath &primPath) const +{ + return _GetInputSceneIndex()->GetChildPrimPaths(primPath); +} + +void +WireframeSelectionHighlightSceneIndex::_PrimsAdded( + const HdSceneIndexBase &sender, + const HdSceneIndexObserver::AddedPrimEntries &entries) +{ + TF_DEBUG(FVP_WIREFRAME_SELECTION_HIGHLIGHT_SCENE_INDEX) + .Msg("WireframeSelectionHighlightSceneIndex::_PrimsAdded() called.\n"); + + _SendPrimsAdded(entries); +} + +void +WireframeSelectionHighlightSceneIndex::_PrimsDirtied( + const HdSceneIndexBase &sender, + const HdSceneIndexObserver::DirtiedPrimEntries &entries) +{ + TF_DEBUG(FVP_WIREFRAME_SELECTION_HIGHLIGHT_SCENE_INDEX) + .Msg("WireframeSelectionHighlightSceneIndex::_PrimsDirtied() called.\n"); + + HdSceneIndexObserver::DirtiedPrimEntries highlightEntries; + for (const auto& entry : entries) { + // If the dirtied prim is excluded, don't provide selection + // highlighting for it. + if (!isExcluded(entry.primPath) && + entry.dirtyLocators.Contains( + HdSelectionsSchema::GetDefaultLocator())) { + TF_DEBUG(FVP_WIREFRAME_SELECTION_HIGHLIGHT_SCENE_INDEX) + .Msg(" %s selections locator dirty.\n", entry.primPath.GetText()); + // All mesh prims recursively under the selection dirty prim have a + // dirty wireframe selection highlight. + dirtySelectionHighlightRecursive(entry.primPath, &highlightEntries); + } + } + + if (!highlightEntries.empty()) { + // Append all incoming dirty entries. + highlightEntries.reserve(highlightEntries.size()+entries.size()); + highlightEntries.insert( + highlightEntries.end(), entries.begin(), entries.end()); + _SendPrimsDirtied(highlightEntries); + } + else { + _SendPrimsDirtied(entries); + } +} + +void +WireframeSelectionHighlightSceneIndex::_PrimsRemoved( + const HdSceneIndexBase &sender, + const HdSceneIndexObserver::RemovedPrimEntries &entries) +{ + TF_DEBUG(FVP_WIREFRAME_SELECTION_HIGHLIGHT_SCENE_INDEX) + .Msg("WireframeSelectionHighlightSceneIndex::_PrimsRemoved() called.\n"); + + _SendPrimsRemoved(entries); +} + +void WireframeSelectionHighlightSceneIndex::dirtySelectionHighlightRecursive( + const SdfPath& primPath, + HdSceneIndexObserver::DirtiedPrimEntries* highlightEntries +) +{ + TF_DEBUG(FVP_WIREFRAME_SELECTION_HIGHLIGHT_SCENE_INDEX) + .Msg(" marking %s wireframe highlight locator dirty.\n", primPath.GetText()); + highlightEntries->emplace_back(primPath, HdDataSourceLocatorSet(reprSelectorLocator)); + for (const auto& childPath : GetChildPrimPaths(primPath)) { + dirtySelectionHighlightRecursive(childPath, highlightEntries); + } +} + +void WireframeSelectionHighlightSceneIndex::addExcludedSceneRoot( + const PXR_NS::SdfPath& sceneRoot +) +{ + _excludedSceneRoots.emplace(sceneRoot); +} + +bool WireframeSelectionHighlightSceneIndex::isExcluded( + const PXR_NS::SdfPath& sceneRoot +) const +{ + for (const auto& excluded : _excludedSceneRoots) { + if (sceneRoot.HasPrefix(excluded)) { + return true; + } + } + return false; +} + +} diff --git a/lib/flowViewport/sceneIndex/fvpWireframeSelectionHighlightSceneIndex.h b/lib/flowViewport/sceneIndex/fvpWireframeSelectionHighlightSceneIndex.h new file mode 100644 index 0000000000..34a7ae590b --- /dev/null +++ b/lib/flowViewport/sceneIndex/fvpWireframeSelectionHighlightSceneIndex.h @@ -0,0 +1,100 @@ +// Copyright 2023 Autodesk +// +// 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 FVP_WIREFRAME_SELECTION_HIGHLIGHT_SCENE_INDEX_H +#define FVP_WIREFRAME_SELECTION_HIGHLIGHT_SCENE_INDEX_H + +#include "flowViewport/api.h" +#include "flowViewport/sceneIndex/fvpPassThroughSelectionInterfaceSceneIndex.h" + +#include + +#include + +namespace FVP_NS_DEF { + +// Pixar declarePtrs.h TF_DECLARE_REF_PTRS macro unusable, places resulting +// type in PXR_NS. +class WireframeSelectionHighlightSceneIndex; +typedef PXR_NS::TfRefPtr WireframeSelectionHighlightSceneIndexRefPtr; +typedef PXR_NS::TfRefPtr WireframeSelectionHighlightSceneIndexConstRefPtr; + +/// \class WireframeSelectionHighlightSceneIndex +/// +/// Uses Hydra HdRepr to add wireframe representation to selected objects +/// and their descendants. +/// +class WireframeSelectionHighlightSceneIndex + : public PassThroughSelectionInterfaceSceneIndexBase +{ +public: + + FVP_API + static PXR_NS::HdSceneIndexBaseRefPtr New( + const PXR_NS::HdSceneIndexBaseRefPtr& inputSceneIndex + ); + + FVP_API + static + const PXR_NS::HdDataSourceLocator& ReprSelectorLocator(); + + FVP_API + PXR_NS::HdSceneIndexPrim GetPrim(const PXR_NS::SdfPath &primPath) const override; + + FVP_API + PXR_NS::SdfPathVector GetChildPrimPaths(const PXR_NS::SdfPath &primPath) const override; + + // Add a scene root to the set of excluded scene roots. These are Hydra + // scene index prim hierarchies for which wireframe selection highlighting + // of meshes is not desired. + FVP_API + void addExcludedSceneRoot(const PXR_NS::SdfPath& sceneRoot); + +protected: + + FVP_API + WireframeSelectionHighlightSceneIndex( + const PXR_NS::HdSceneIndexBaseRefPtr& inputSceneIndex + ); + + FVP_API + void _PrimsAdded( + const PXR_NS::HdSceneIndexBase &sender, + const PXR_NS::HdSceneIndexObserver::AddedPrimEntries &entries) override; + + FVP_API + void _PrimsRemoved( + const PXR_NS::HdSceneIndexBase &sender, + const PXR_NS::HdSceneIndexObserver::RemovedPrimEntries &entries) override; + + FVP_API + void _PrimsDirtied( + const PXR_NS::HdSceneIndexBase &sender, + const PXR_NS::HdSceneIndexObserver::DirtiedPrimEntries &entries) override; + +private: + + void dirtySelectionHighlightRecursive( + const PXR_NS::SdfPath& primPath, + PXR_NS::HdSceneIndexObserver::DirtiedPrimEntries* highlightEntries + ); + + bool isExcluded(const PXR_NS::SdfPath& sceneRoot) const; + + std::set _excludedSceneRoots; +}; + +} + +#endif diff --git a/lib/flowViewport/selection/CMakeLists.txt b/lib/flowViewport/selection/CMakeLists.txt new file mode 100644 index 0000000000..f9ca43d0c4 --- /dev/null +++ b/lib/flowViewport/selection/CMakeLists.txt @@ -0,0 +1,31 @@ +# ----------------------------------------------------------------------------- +# sources +# ----------------------------------------------------------------------------- +target_sources(${TARGET_NAME} + PRIVATE + fvpSelectionTracker.cpp + fvpSelectionTask.cpp +) + +set(HEADERS + fvpSelectionTracker.h + fvpSelectionTask.h +) + +# ----------------------------------------------------------------------------- +# promoted headers +# ----------------------------------------------------------------------------- +mayaUsd_promoteHeaderList( + HEADERS + ${HEADERS} + BASEDIR + ${TARGET_NAME}/selection +) + +# ----------------------------------------------------------------------------- +# install +# ----------------------------------------------------------------------------- +install(FILES ${HEADERS} + DESTINATION + ${CMAKE_INSTALL_PREFIX}/include/flowViewport/selection +) diff --git a/lib/flowViewport/selection/fvpSelectionTask.cpp b/lib/flowViewport/selection/fvpSelectionTask.cpp new file mode 100644 index 0000000000..cf67e09d16 --- /dev/null +++ b/lib/flowViewport/selection/fvpSelectionTask.cpp @@ -0,0 +1,120 @@ +// +// 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. +// +// Copyright 2023 Autodesk +// +// 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 "flowViewport/selection/fvpSelectionTask.h" +#include "flowViewport/selection/fvpSelectionTracker.h" + +#include "flowViewport/debugCodes.h" +#include "flowViewport/tokens.h" + +PXR_NAMESPACE_USING_DIRECTIVE + +namespace FVP_NS_DEF { + +// -------------------------------------------------------------------------- // + +SelectionTask::SelectionTask() + : HdTask(Id()) + , _lastVersion(-1) +{ + TF_DEBUG(FVP_SELECTION_TASK) + .Msg("SelectionTask::SelectionTask() called.\n"); +} + +SelectionTask::~SelectionTask() = default; + +void +SelectionTask::Sync(HdSceneDelegate* , + HdTaskContext* ctx, + HdDirtyBits* dirtyBits) +{ + TF_DEBUG(FVP_SELECTION_TASK) + .Msg("SelectionTask::Sync() called.\n"); + + HD_TRACE_FUNCTION(); + + if ((*dirtyBits) & HdChangeTracker::DirtyParams) { + // We track the version of selection tracker in the task to see if we + // need need to update. We don't have access to the selection tracker + // (as it is in the task context) so we reset the version to -1 to + // cause a version mismatch and force the update. + _lastVersion = -1; + } + + *dirtyBits = HdChangeTracker::Clean; +} + +void +SelectionTask::Prepare( + HdTaskContext* ctx, + HdRenderIndex* renderIndex +) +{ + TF_DEBUG(FVP_SELECTION_TASK) + .Msg("SelectionTask::Prepare() called.\n"); + + Fvp::SelectionTrackerSharedPtr snTracker; + if (!_GetTaskContextData(ctx, FvpTokens->fvpSelectionState, &snTracker)) { + return; + } + + auto trackerVersion = snTracker->GetVersion(); + + if (trackerVersion == _lastVersion) { + return; + } + + _lastVersion = trackerVersion; + + // FLOW_VIEWPORT_TODO Get the selection from the selection tracker here, + // compute selection-derived buffers here, and place them back in the + // context for downstream tasks to use. PPT, 27-Sep-2023. +} + +void +SelectionTask::Execute(HdTaskContext* ctx) +{ + TF_DEBUG(FVP_SELECTION_TASK) + .Msg("SelectionTask::Execute() called.\n"); + + HD_TRACE_FUNCTION(); + HF_MALLOC_TAG_FUNCTION(); + + // Note that selectionTask comes after renderTask. +} + +} diff --git a/lib/flowViewport/selection/fvpSelectionTask.h b/lib/flowViewport/selection/fvpSelectionTask.h new file mode 100644 index 0000000000..f8cc719f7a --- /dev/null +++ b/lib/flowViewport/selection/fvpSelectionTask.h @@ -0,0 +1,102 @@ +// +// 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. +// +// Copyright 2023 Autodesk +// +// 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 FVP_SELECTION_TASK_H +#define FVP_SELECTION_TASK_H + +#include "flowViewport/api.h" + +#include + +#include + +PXR_NAMESPACE_OPEN_SCOPE +class HdRenderIndex; +class HdSceneDelegate; +PXR_NAMESPACE_CLOSE_SCOPE + +namespace FVP_NS_DEF { + +/// \class SelectionTask +/// +/// Placeholder for +/// https://github.com/PixarAnimationStudios/OpenUSD/blob/release/pxr/imaging/hdx/selectionTask.h +/// +class SelectionTask : public PXR_NS::HdTask +{ +public: + FVP_API + + SelectionTask(); + + FVP_API + ~SelectionTask() override; + + FVP_API + const PXR_NS::SdfPath& Id() { + static auto id = PXR_NS::SdfPath("FlowViewportSelectionTask"); + return id; + } + + /// Sync the render pass resources + FVP_API + void Sync(PXR_NS::HdSceneDelegate* delegate, + PXR_NS::HdTaskContext* ctx, + PXR_NS::HdDirtyBits* dirtyBits) override; + + + /// Prepare the tasks resources + FVP_API + void Prepare(PXR_NS::HdTaskContext* ctx, + PXR_NS::HdRenderIndex* renderIndex) override; + + /// Execute render pass task + FVP_API + void Execute(PXR_NS::HdTaskContext* ctx) override; + + +private: + int _lastVersion; + + SelectionTask(const SelectionTask &) = delete; + SelectionTask &operator =(const SelectionTask &) = delete; +}; + +} + +#endif //FVP_SELECTION_TASK_H + diff --git a/lib/flowViewport/selection/fvpSelectionTracker.cpp b/lib/flowViewport/selection/fvpSelectionTracker.cpp new file mode 100644 index 0000000000..f063c50597 --- /dev/null +++ b/lib/flowViewport/selection/fvpSelectionTracker.cpp @@ -0,0 +1,116 @@ +// +// 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. +// +// +// Copyright 2023 Autodesk +// +// 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. +// + +/// Placeholder for +/// +/// https://github.com/PixarAnimationStudios/OpenUSD/blob/release/pxr/imaging/hdx/selectionTracker.cpp +/// +/// which is Hydra Storm-centric. To be revised. PPT, 27-Sep-2023. + +#include "flowViewport/selection/fvpSelectionTracker.h" + +#include "flowViewport/debugCodes.h" + +#include +#include +#include +#include + +PXR_NAMESPACE_USING_DIRECTIVE + +namespace FVP_NS_DEF { + +class SelectionTracker::_Selection +{ +public: + // Selection task has initial selection version at -1, so match that. + _Selection() : _lastVersion(-1) {} + + // Returns the selection from the scene index. + HdSelectionSharedPtr + GetSelection(const HdRenderIndex * const index) + { + // Tell scene index observer what scene index to observe. + // FLOW_VIEWPORT_TODO Why are we setting the scene index onto the + // observer at every call? PPT, 27-Sep-2023. + _observer.SetSceneIndex(index->GetTerminalSceneIndex()); + + // Recompute if changed since last time it was computed. + if (_lastVersion != GetVersion()) { + _selection = _observer.GetSelection(); + _lastVersion = GetVersion(); + } + return _selection; + } + + // Version number for the selection. + int GetVersion() const { + return _observer.GetVersion(); + } + +private: + // Cache the selection. The version of the selection cached here + // is stored as _lastVersion. + HdSelectionSharedPtr _selection; + int _lastVersion; + + HdxSelectionSceneIndexObserver _observer; +}; + +SelectionTracker::SelectionTracker() + : _selection(std::make_unique<_Selection>()) +{} + +SelectionTracker::~SelectionTracker() = default; + +int +SelectionTracker::GetVersion() const +{ + return _selection->GetVersion(); +} + +HdSelectionSharedPtr SelectionTracker::GetSelection(const HdRenderIndex* index) const +{ + TF_DEBUG(FVP_SELECTION_TRACKER) + .Msg("SelectionTracker::GetSelection() called.\n"); + + return _selection->GetSelection(index); +} + +} diff --git a/lib/flowViewport/selection/fvpSelectionTracker.h b/lib/flowViewport/selection/fvpSelectionTracker.h new file mode 100644 index 0000000000..0e9c541c5a --- /dev/null +++ b/lib/flowViewport/selection/fvpSelectionTracker.h @@ -0,0 +1,101 @@ +// +// 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. +// +// Copyright 2023 Autodesk +// +// 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 FVP_SELECTION_TRACKER_H +#define FVP_SELECTION_TRACKER_H + +#include "flowViewport/api.h" + +#include +#include // For HdSelectionSharedPtr typedef + +#include + +PXR_NAMESPACE_OPEN_SCOPE + +class HdRenderIndex; + +PXR_NAMESPACE_CLOSE_SCOPE + +namespace FVP_NS_DEF { + +using SelectionTrackerSharedPtr = + std::shared_ptr; + +/// Placeholder for +/// +/// https://github.com/PixarAnimationStudios/OpenUSD/blob/release/pxr/imaging/hdx/selectionTracker.h +/// +/// which is Hydra Storm-centric. To be revised. PPT, 27-Sep-2023. + +/// \class SelectionTracker +/// +/// The selection tracker owns the HdSelection and the selection scene index +/// observer that maintains the selection up to date. +/// +/// HdxSelectionTask takes SelectionTracker as a task parameter, to inject +/// the selection into the list of tasks. + +class SelectionTracker +{ +public: + FVP_API + SelectionTracker(); + FVP_API + virtual ~SelectionTracker(); + + /// Returns a monotonically increasing version number, which increments + /// whenever the selection has changed. Note that this number may + /// overflow and become negative, thus clients should use a not-equal + /// comparison. + FVP_API + int GetVersion() const; + + FVP_API + PXR_NS::HdSelectionSharedPtr GetSelection(const PXR_NS::HdRenderIndex *index) const; + +private: + + // A helper class to obtain the selection computed by querying the scene + // indices (with the HdxSelectionSceneIndexObserver). + class _Selection; + std::unique_ptr<_Selection> _selection; +}; + +} + +#endif //FVP_SELECTION_TRACKER_H diff --git a/lib/flowViewport/tokens.cpp b/lib/flowViewport/tokens.cpp new file mode 100644 index 0000000000..7d05fe6384 --- /dev/null +++ b/lib/flowViewport/tokens.cpp @@ -0,0 +1,53 @@ +// +// 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. +// +// Copyright 2023 Autodesk +// +// 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 "flowViewport/tokens.h" + +// FLOW_VIEWPORT_TODO Figure out how to put tokens into non-Pixar namespace. +// The following does not work: +// +// PXR_NAMESPACE_USING_DIRECTIVE +// +// namespace FVP_NS_DEF { +// TF_DEFINE_PUBLIC_TOKENS(FvpTokens, FVP_TOKENS); +// } +// +// PPT, 18-Sep-2023. + +PXR_NAMESPACE_OPEN_SCOPE +TF_DEFINE_PUBLIC_TOKENS(FvpTokens, FVP_TOKENS); +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/flowViewport/tokens.h b/lib/flowViewport/tokens.h new file mode 100644 index 0000000000..81f11add3d --- /dev/null +++ b/lib/flowViewport/tokens.h @@ -0,0 +1,64 @@ +// +// 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. +// +// Copyright 2023 Autodesk +// +// 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 FVP_TOKENS_H +#define FVP_TOKENS_H + +#include "flowViewport/api.h" + +#include "pxr/pxr.h" +#include "pxr/base/tf/staticTokens.h" + +PXR_NAMESPACE_OPEN_SCOPE +#define FVP_TOKENS (fvpSelectionState) + +TF_DECLARE_PUBLIC_TOKENS(FvpTokens, FVP_API, FVP_TOKENS); + +PXR_NAMESPACE_CLOSE_SCOPE + +// FLOW_VIEWPORT_TODO Figure out how to put tokens into non-Pixar namespace. +// The following does not work: +// +// #define FVP_TOKENS (fvpSelectionState) +// +// namespace FVP_NS_DEF { +// TF_DECLARE_PUBLIC_TOKENS(FvpTokens, FVP_API, FVP_TOKENS); +// } +// +// PPT, 18-Sep-2023. + +#endif //FVP_TOKENS_H diff --git a/lib/mayaHydra/CMakeLists.txt b/lib/mayaHydra/CMakeLists.txt new file mode 100644 index 0000000000..df12f980bd --- /dev/null +++ b/lib/mayaHydra/CMakeLists.txt @@ -0,0 +1,3 @@ +add_subdirectory(mayaPlugin) +add_subdirectory(hydraExtensions) +add_subdirectory(ufeExtensions) diff --git a/lib/mayaHydra/hydraExtensions/CMakeLists.txt b/lib/mayaHydra/hydraExtensions/CMakeLists.txt new file mode 100644 index 0000000000..e8997ec658 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/CMakeLists.txt @@ -0,0 +1,214 @@ +set(TARGET_NAME mayaHydraLib) + +add_library(${TARGET_NAME} SHARED) + +# ----------------------------------------------------------------------------- +# sources +# ----------------------------------------------------------------------------- +target_sources(${TARGET_NAME} + PRIVATE + debugCodes.cpp + hydraUtils.cpp + interfaceImp.cpp + mayaUtils.cpp + mixedUtils.cpp + mayaHydraSceneProducer.cpp +) + +set(HEADERS + api.h + debugCodes.h + hydraUtils.h + interface.h + interfaceImp.h + mayaUtils.h + mixedUtils.h + mayaHydraSceneProducer.h +) + +# ----------------------------------------------------------------------------- +# compiler configuration +# ----------------------------------------------------------------------------- +target_compile_definitions(${TARGET_NAME} + PRIVATE + MAYAHYDRALIB_EXPORT + $<$:OSMac_> + # Copy-pasted from lib/mayaUsd/CMakeLists.txt +) + +mayaHydra_compile_config(${TARGET_NAME}) + +if(NOT BUILD_MAYAUSD_LIBRARY) + # ----------------------------------------------------------------------------- + # include directories + # ----------------------------------------------------------------------------- + target_include_directories(${TARGET_NAME} + PUBLIC + ${MAYA_INCLUDE_DIRS} + ${PYTHON_INCLUDE_DIR} + ${PXR_INCLUDE_DIRS} + ${BOOST_INCLUDE_DIR} + ${CMAKE_BINARY_DIR}/include + PRIVATE + $<$:${UFE_INCLUDE_DIR}> + ) + + target_compile_definitions(${TARGET_NAME} + PUBLIC + PXR_PLUGINPATH_NAME=${PXR_OVERRIDE_PLUGINPATH_NAME} + $<$:TBB_USE_DEBUG> + $<$:BOOST_DEBUG_PYTHON> + $<$:BOOST_LINKING_PYTHON> + PRIVATE + MAYAUSD_MACROS_EXPORT + MAYAUSD_CORE_EXPORT + MFB_PACKAGE_NAME="${PROJECT_NAME}" + MFB_ALT_PACKAGE_NAME="${PROJECT_NAME}" + MFB_PACKAGE_MODULE="${PROJECT_NAME}" + $<$:OSMac_> + $<$:LINUX> + $<$:GL_GLEXT_PROTOTYPES> + $<$:GLX_GLXEXT_PROTOTYPES> + $<$:WANT_MATERIALX_BUILD> + $<$:BUILD_MAYAHYDRALIB> + + # this flag is needed when building maya-usd in Maya + $<$:WIN32> + ) +endif() + +if(DEFINED MAYAUSD_VERSION) + target_compile_definitions(${TARGET_NAME} + PRIVATE + MAYAUSD_VERSION=${MAYAUSD_VERSION} + ) +endif() +if(DEFINED MAYAHYDRA_VERSION) + target_compile_definitions(${TARGET_NAME} + PRIVATE + MAYAHYDRA_VERSION=${MAYAHYDRA_VERSION} + ) +endif() +if(DEFINED MAYAHYDRA_CUT_ID) + target_compile_definitions(${TARGET_NAME} + PRIVATE + MAYAHYDRA_CUT_ID=${MAYAHYDRA_CUT_ID} + ) +endif() + + # ----------------------------------------------------------------------------- + # link libraries + # ----------------------------------------------------------------------------- +if(BUILD_MAYAUSD_LIBRARY) + target_link_libraries(${TARGET_NAME} + PUBLIC + mayaUsd + PRIVATE + $<$:hio> + ) +else() + target_link_libraries(${TARGET_NAME} + PUBLIC + ar + gf + hd + hdx + js + kind + plug + sdf + tf + trace + usd + usdGeom + usdImaging + usdImagingGL + usdLux + usdRender + usdRi + usdShade + usdSkel + usdUtils + ufeExtensions + flowViewport + ${MAYA_LIBRARIES} + PRIVATE + $<$:hio> + ${PYTHON_LIBRARIES} + $<$:${UFE_LIBRARY}> + ) +endif() + +# ----------------------------------------------------------------------------- +# promoted headers +# ----------------------------------------------------------------------------- +set(SRCFILE ${CMAKE_CURRENT_SOURCE_DIR}/mayaHydra.h.src) +set(DSTFILE ${CMAKE_BINARY_DIR}/include/mayaHydraLib/mayaHydra.h) +configure_file(${SRCFILE} ${DSTFILE}) + +mayaUsd_promoteHeaderList( + HEADERS + ${HEADERS} + BASEDIR + ${TARGET_NAME} +) + +# ----------------------------------------------------------------------------- +# install +# ----------------------------------------------------------------------------- +set(LIBFILENAME ${CMAKE_SHARED_LIBRARY_PREFIX}${TARGET_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}) +set(PLUG_INFO_PATH "plugInfo.json") +set(SHADER_DEFS_PATH +"shaderDefs.usda" +) + +set(PLUG_INFO_LIBRARY_PATH "../../../${LIBFILENAME}") +set(PLUG_INFO_RESOURCE_PATH "resources") + +configure_file(${PLUG_INFO_PATH} ${CMAKE_CURRENT_BINARY_DIR}/${PLUG_INFO_PATH}) + +# configure shader files +foreach(X ${SHADER_DEFS_PATH}) + configure_file(${X} ${CMAKE_CURRENT_BINARY_DIR}/${X}) +endforeach(X) + + +install(TARGETS ${TARGET_NAME} + LIBRARY + DESTINATION ${CMAKE_INSTALL_PREFIX}/lib + RUNTIME + DESTINATION ${CMAKE_INSTALL_PREFIX}/lib +) + +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/${PLUG_INFO_PATH} + DESTINATION + ${CMAKE_INSTALL_PREFIX}/lib/usd/mayaHydraLib/resources +) + +# install shaders +foreach(X ${SHADER_DEFS_PATH}) + install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/${X} + DESTINATION + ${CMAKE_INSTALL_PREFIX}/lib/usd/mayaHydraLib/resources + ) +endforeach(X) + + +install(FILES ${HEADERS} + DESTINATION + ${CMAKE_INSTALL_PREFIX}/include/mayaHydraLib +) + +if(IS_WINDOWS) + install(FILES $ + DESTINATION ${CMAKE_INSTALL_PREFIX}/lib OPTIONAL) +endif() + +# ----------------------------------------------------------------------------- +# subdirectories +# ----------------------------------------------------------------------------- +add_subdirectory(adapters) +add_subdirectory(delegates) +add_subdirectory(sceneIndex) diff --git a/lib/mayaHydra/hydraExtensions/adapters/CMakeLists.txt b/lib/mayaHydra/hydraExtensions/adapters/CMakeLists.txt new file mode 100644 index 0000000000..4459b895c3 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/adapters/CMakeLists.txt @@ -0,0 +1,59 @@ +# ----------------------------------------------------------------------------- +# sources +# ----------------------------------------------------------------------------- +target_sources(${TARGET_NAME} + PRIVATE + adapter.cpp + adapterDebugCodes.cpp + adapterRegistry.cpp + aiSkydomeLightAdapter.cpp + areaLightAdapter.cpp + cameraAdapter.cpp + dagAdapter.cpp + renderItemAdapter.cpp + directionalLightAdapter.cpp + lightAdapter.cpp + materialAdapter.cpp + materialNetworkConverter.cpp + mayaAttrs.cpp + meshAdapter.cpp + nurbsCurveAdapter.cpp + pointLightAdapter.cpp + shapeAdapter.cpp + spotLightAdapter.cpp + tokens.cpp +) + +set(HEADERS + adapter.h + adapterDebugCodes.h + adapterRegistry.h + cameraAdapter.h + constantShadowMatrix.h + dagAdapter.h + renderItemAdapter.h + lightAdapter.h + materialAdapter.h + materialNetworkConverter.h + mayaAttrs.h + shapeAdapter.h + tokens.h +) + +# ----------------------------------------------------------------------------- +# promoted headers +# ----------------------------------------------------------------------------- +mayaUsd_promoteHeaderList( + HEADERS + ${HEADERS} + BASEDIR + ${TARGET_NAME}/adapters +) + +# ----------------------------------------------------------------------------- +# install +# ----------------------------------------------------------------------------- +install(FILES ${HEADERS} + DESTINATION + ${CMAKE_INSTALL_PREFIX}/include/mayaHydraLib/adapters +) diff --git a/lib/mayaHydra/hydraExtensions/adapters/adapter.cpp b/lib/mayaHydra/hydraExtensions/adapters/adapter.cpp new file mode 100644 index 0000000000..989544ceb4 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/adapters/adapter.cpp @@ -0,0 +1,124 @@ +// +// 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. +// +#include "adapter.h" + +#include +#include +#include +#include + +#include + +#include + +PXR_NAMESPACE_OPEN_SCOPE + +TF_REGISTRY_FUNCTION(TfType) { TfType::Define(); } + +namespace { + +void _preRemoval(MObject& node, void* clientData) +{ + TF_UNUSED(node); + auto* adapter = reinterpret_cast(clientData); + TF_DEBUG(MAYAHYDRALIB_ADAPTER_CALLBACKS) + .Msg("Pre-removal callback triggered for prim (%s)\n", adapter->GetID().GetText()); + adapter->GetSceneProducer()->RemoveAdapter(adapter->GetID()); +} + +void _nameChanged(MObject& node, const MString& str, void* clientData) +{ + TF_UNUSED(node); + TF_UNUSED(str); + auto* adapter = reinterpret_cast(clientData); + TF_DEBUG(MAYAHYDRALIB_ADAPTER_CALLBACKS) + .Msg("Name-changed callback triggered for prim (%s)\n", adapter->GetID().GetText()); + adapter->RemoveCallbacks(); + adapter->GetSceneProducer()->RecreateAdapterOnIdle(adapter->GetID(), adapter->GetNode()); +} + +} // namespace + +// MayaHydraAdapter is the base class for all adapters. An adapter is used to translate from Maya +// data to hydra data. +MayaHydraAdapter::MayaHydraAdapter( + const MObject& node, + const SdfPath& id, + MayaHydraSceneProducer* producer) + : _id(id) + , _sceneProducer(producer) + , _node(node) +{ +} + +MayaHydraAdapter::~MayaHydraAdapter() { RemoveCallbacks(); } + +void MayaHydraAdapter::AddCallback(MCallbackId callbackId) { _callbacks.push_back(callbackId); } + +void MayaHydraAdapter::RemoveCallbacks() +{ + if (_callbacks.empty()) { + return; + } + + TF_DEBUG(MAYAHYDRALIB_ADAPTER_CALLBACKS) + .Msg("Removing all adapter callbacks for prim (%s).\n", GetID().GetText()); + for (auto c : _callbacks) { + MMessage::removeCallback(c); + } + std::vector().swap(_callbacks); +} + +VtValue MayaHydraAdapter::Get(const TfToken& key) +{ + TF_UNUSED(key); + return {}; +}; + +bool MayaHydraAdapter::HasType(const TfToken& typeId) const +{ + TF_UNUSED(typeId); + return false; +} + +void MayaHydraAdapter::CreateCallbacks() +{ + if (_node != MObject::kNullObj) { + TF_DEBUG(MAYAHYDRALIB_ADAPTER_CALLBACKS) + .Msg("Creating generic adapter callbacks for prim (%s).\n", GetID().GetText()); + + MStatus status; + auto id = MNodeMessage::addNodePreRemovalCallback(_node, _preRemoval, this, &status); + if (status) { + AddCallback(id); + } + id = MNodeMessage::addNameChangedCallback(_node, _nameChanged, this, &status); + if (status) { + AddCallback(id); + } + } +} + +MStatus MayaHydraAdapter::Initialize() +{ + auto status = MayaAttrs::initialize(); + if (status) { + MayaHydraMaterialNetworkConverter::initialize(); + } + return status; +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/mayaHydra/hydraExtensions/adapters/adapter.h b/lib/mayaHydra/hydraExtensions/adapters/adapter.h new file mode 100644 index 0000000000..d055e74a7f --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/adapters/adapter.h @@ -0,0 +1,106 @@ +// +// 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. +// +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +#ifndef MAYAHYDRALIB_ADAPTER_H +#define MAYAHYDRALIB_ADAPTER_H + +#include +#include + +#include +#include + +#include + +#include + +PXR_NAMESPACE_OPEN_SCOPE + +class MayaHydraSceneProducer; + +/** + * \brief MayaHydraAdapter is the base class for all adapters. An adapter is used to translate from + * Maya data to hydra data. + */ + +class MayaHydraAdapter +{ +public: + MAYAHYDRALIB_API + MayaHydraAdapter(const MObject& node, const SdfPath& id, MayaHydraSceneProducer* producer); + MAYAHYDRALIB_API + virtual ~MayaHydraAdapter(); + + const SdfPath& GetID() const { return _id; } + MayaHydraSceneProducer* GetSceneProducer() const { return _sceneProducer; } + MAYAHYDRALIB_API + void AddCallback(MCallbackId callbackId); + MAYAHYDRALIB_API + virtual void RemoveCallbacks(); + MAYAHYDRALIB_API + virtual VtValue Get(const TfToken& key); + const MObject& GetNode() const { return _node; } + MAYAHYDRALIB_API + virtual bool IsSupported() const = 0; + MAYAHYDRALIB_API + virtual bool HasType(const TfToken& typeId) const; + MAYAHYDRALIB_API + virtual bool GetVisible() { return true; } + + MAYAHYDRALIB_API + virtual void CreateCallbacks(); + virtual void MarkDirty(HdDirtyBits dirtyBits) = 0; + virtual void RemovePrim() = 0; + virtual void Populate() = 0; + + MAYAHYDRALIB_API + static MStatus Initialize(); + + bool IsPopulated() const { return _isPopulated; } + + MAYAHYDRALIB_API + virtual HdMeshTopology GetMeshTopology() { return {}; } + MAYAHYDRALIB_API + virtual HdBasisCurvesTopology GetBasisCurvesTopology() { return {}; } + MAYAHYDRALIB_API + virtual TfToken GetRenderTag() const { return TfToken(); } + MAYAHYDRALIB_API + virtual GfMatrix4d GetTransform() { return GfMatrix4d(); } + MAYAHYDRALIB_API + virtual HdPrimvarDescriptorVector GetPrimvarDescriptors(HdInterpolation interpolation) + { + return HdPrimvarDescriptorVector(); + } + MAYAHYDRALIB_API + virtual bool GetDoubleSided() const { return true; } + MAYAHYDRALIB_API + virtual HdCullStyle GetCullStyle() const { return HdCullStyleNothing; } + MAYAHYDRALIB_API + virtual HdDisplayStyle GetDisplayStyle() { return { 0, false, false }; } + +protected: + SdfPath _id; + std::vector _callbacks; + MayaHydraSceneProducer* _sceneProducer; + MObject _node; + + bool _isPopulated = false; +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // MAYAHYDRALIB_ADAPTER_H diff --git a/lib/mayaHydra/hydraExtensions/adapters/adapterDebugCodes.cpp b/lib/mayaHydra/hydraExtensions/adapters/adapterDebugCodes.cpp new file mode 100644 index 0000000000..7c0ffc8ae2 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/adapters/adapterDebugCodes.cpp @@ -0,0 +1,72 @@ +// +// 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. +// +#include "adapterDebugCodes.h" + +#include + +PXR_NAMESPACE_OPEN_SCOPE + +// Some variables to enable debug printing information + +TF_REGISTRY_FUNCTION(TfDebug) +{ + TF_DEBUG_ENVIRONMENT_SYMBOL( + MAYAHYDRALIB_ADAPTER_CALLBACKS, + "Print information adding and removal of adapter callbacks."); + + TF_DEBUG_ENVIRONMENT_SYMBOL( + MAYAHYDRALIB_ADAPTER_CURVE_PLUG_DIRTY, + "Print information when a nurbs curve prim is dirtied due to a plug " + "being dirtied."); + + TF_DEBUG_ENVIRONMENT_SYMBOL( + MAYAHYDRALIB_ADAPTER_CURVE_UNHANDLED_PLUG_DIRTY, + "Print information when a nurbs curve prim is NOT dirtied, even though " + "a plug was dirtied."); + + TF_DEBUG_ENVIRONMENT_SYMBOL( + MAYAHYDRALIB_ADAPTER_DAG_HIERARCHY, "Print information related to dag hierarchy changes."); + + TF_DEBUG_ENVIRONMENT_SYMBOL( + MAYAHYDRALIB_ADAPTER_DAG_PLUG_DIRTY, "Print information about the dag node plug dirtying."); + + TF_DEBUG_ENVIRONMENT_SYMBOL( + MAYAHYDRALIB_ADAPTER_GET, "Print information about 'Get' calls to the adapter."); + + TF_DEBUG_ENVIRONMENT_SYMBOL( + MAYAHYDRALIB_ADAPTER_GET_LIGHT_PARAM_VALUE, + "Print information about 'LightParamValue' " + "calls to the light adapters."); + + TF_DEBUG_ENVIRONMENT_SYMBOL( + MAYAHYDRALIB_ADAPTER_IMAGEPLANES, "Print information about drawing image planes."); + + TF_DEBUG_ENVIRONMENT_SYMBOL( + MAYAHYDRALIB_ADAPTER_LIGHT_SHADOWS, "Print information about shadow rendering."); + + TF_DEBUG_ENVIRONMENT_SYMBOL( + MAYAHYDRALIB_ADAPTER_MATERIALS, "Print information about converting materials."); + + TF_DEBUG_ENVIRONMENT_SYMBOL( + MAYAHYDRALIB_ADAPTER_MESH_PLUG_DIRTY, + "Print information about the mesh plug dirtying handled."); + + TF_DEBUG_ENVIRONMENT_SYMBOL( + MAYAHYDRALIB_ADAPTER_MESH_UNHANDLED_PLUG_DIRTY, + "Print information about unhandled mesh plug dirtying."); +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/mayaHydra/hydraExtensions/adapters/adapterDebugCodes.h b/lib/mayaHydra/hydraExtensions/adapters/adapterDebugCodes.h new file mode 100644 index 0000000000..128f65c585 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/adapters/adapterDebugCodes.h @@ -0,0 +1,46 @@ +// +// 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. +// +#ifndef MAYAHYDRALIB_ADAPTER_DEBUG_CODES_H +#define MAYAHYDRALIB_ADAPTER_DEBUG_CODES_H + +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +//! \brief Some variables to enable debug printing information for adapters + +// clang-format off +TF_DEBUG_CODES( + MAYAHYDRALIB_ADAPTER_CALLBACKS, + MAYAHYDRALIB_ADAPTER_CURVE_PLUG_DIRTY, + MAYAHYDRALIB_ADAPTER_CURVE_UNHANDLED_PLUG_DIRTY, + MAYAHYDRALIB_ADAPTER_DAG_HIERARCHY, + MAYAHYDRALIB_ADAPTER_DAG_PLUG_DIRTY, + MAYAHYDRALIB_ADAPTER_GET, + MAYAHYDRALIB_ADAPTER_GET_LIGHT_PARAM_VALUE, + MAYAHYDRALIB_ADAPTER_IMAGEPLANES, + MAYAHYDRALIB_ADAPTER_LIGHT_SHADOWS, + MAYAHYDRALIB_ADAPTER_MATERIALS, + MAYAHYDRALIB_ADAPTER_MESH_PLUG_DIRTY, + MAYAHYDRALIB_ADAPTER_MESH_UNHANDLED_PLUG_DIRTY, + MAYAHYDRALIB_ADAPTER_MATERIALS_PRINT_PARAMETERS_VALUES + ); +// clang-format on + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // MAYAHYDRALIB_ADAPTER_DEBUG_CODES_H diff --git a/lib/mayaHydra/hydraExtensions/adapters/adapterRegistry.cpp b/lib/mayaHydra/hydraExtensions/adapters/adapterRegistry.cpp new file mode 100644 index 0000000000..fcb25ffa2d --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/adapters/adapterRegistry.cpp @@ -0,0 +1,136 @@ +// +// 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. +// +#include "adapterRegistry.h" + +#if defined(MAYAUSD_VERSION) +#include +#endif + +#include +#include +#include +#include + +#include + +#include + +PXR_NAMESPACE_OPEN_SCOPE + +TF_INSTANTIATE_SINGLETON(MayaHydraAdapterRegistry); + +// An adapter is used to translate from Maya data to hydra data. MayaHydraAdapterRegistry is used to +// register/retrieve the adapters. +void MayaHydraAdapterRegistry::RegisterShapeAdapter( + const TfToken& type, + ShapeAdapterCreator creator) +{ + GetInstance()._dagAdapters.insert({ type, creator }); +} + +MayaHydraAdapterRegistry::ShapeAdapterCreator +MayaHydraAdapterRegistry::GetShapeAdapterCreator(const MDagPath& dag) +{ + MFnDependencyNode depNode(dag.node()); + ShapeAdapterCreator ret = nullptr; + TfMapLookup(GetInstance()._dagAdapters, TfToken(depNode.typeName().asChar()), &ret); + + return ret; +} + +void MayaHydraAdapterRegistry::RegisterLightAdapter( + const TfToken& type, + LightAdapterCreator creator) +{ + GetInstance()._lightAdapters.insert({ type, creator }); +} + +MayaHydraAdapterRegistry::LightAdapterCreator +MayaHydraAdapterRegistry::GetLightAdapterCreator(const MDagPath& dag) +{ + return GetLightAdapterCreator(dag.node()); +} + +MayaHydraAdapterRegistry::LightAdapterCreator +MayaHydraAdapterRegistry::GetLightAdapterCreator(const MObject& node) +{ + MFnDependencyNode depNode(node); + LightAdapterCreator ret = nullptr; + TfMapLookup(GetInstance()._lightAdapters, TfToken(depNode.typeName().asChar()), &ret); + return ret; +} + +void MayaHydraAdapterRegistry::RegisterCameraAdapter( + const TfToken& type, + CameraAdapterCreator creator) +{ + GetInstance()._cameraAdapters.insert({ type, creator }); +} + +MayaHydraAdapterRegistry::CameraAdapterCreator +MayaHydraAdapterRegistry::GetCameraAdapterCreator(const MDagPath& dag) +{ + MFnDependencyNode depNode(dag.node()); + CameraAdapterCreator ret = nullptr; + TfMapLookup(GetInstance()._cameraAdapters, TfToken(depNode.typeName().asChar()), &ret); + return ret; +} + +void MayaHydraAdapterRegistry::RegisterMaterialAdapter( + const TfToken& type, + MaterialAdapterCreator creator) +{ + GetInstance()._materialAdapters.insert({ type, creator }); +} + +MayaHydraAdapterRegistry::MaterialAdapterCreator +MayaHydraAdapterRegistry::GetMaterialAdapterCreator(const MObject& node) +{ + MFnDependencyNode depNode(node); + MaterialAdapterCreator ret = nullptr; + TfMapLookup(GetInstance()._materialAdapters, TfToken(depNode.typeName().asChar()), &ret); + return ret; +} + +void MayaHydraAdapterRegistry::LoadAllPlugin() +{ + static std::once_flag loadAllOnce; + std::call_once(loadAllOnce, []() { + TfRegistryManager::GetInstance().SubscribeTo(); + + const TfType& adapterType = TfType::Find(); + if (adapterType.IsUnknown()) { + TF_CODING_ERROR("Could not find MayaHydraAdapter type"); + return; + } + + std::set adapterTypes; + adapterType.GetAllDerivedTypes(&adapterTypes); + + PlugRegistry& plugReg = PlugRegistry::GetInstance(); + + for (auto& subType : adapterTypes) { + const PlugPluginPtr pluginForType = plugReg.GetPluginForType(subType); + if (!pluginForType) { + TF_CODING_ERROR("Could not find plugin for '%s'", subType.GetTypeName().c_str()); + return; + } + pluginForType->Load(); + } + }); +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/mayaHydra/hydraExtensions/adapters/adapterRegistry.h b/lib/mayaHydra/hydraExtensions/adapters/adapterRegistry.h new file mode 100644 index 0000000000..34fb71f29b --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/adapters/adapterRegistry.h @@ -0,0 +1,95 @@ +// +// 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. +// +#ifndef MAYAHYDRALIB_ADAPTER_REGISTRY_H +#define MAYAHYDRALIB_ADAPTER_REGISTRY_H + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include + +PXR_NAMESPACE_OPEN_SCOPE + +/** + * \brief An adapter is used to translate from Maya data to hydra data. MayaHydraAdapterRegistry is + * used to register/retrieve the adapters. + */ + +class MayaHydraAdapterRegistry : public TfSingleton +{ + friend class TfSingleton; + MAYAHYDRALIB_API + MayaHydraAdapterRegistry() = default; + +public: + using ShapeAdapterCreator + = std::function; + MAYAHYDRALIB_API + static void RegisterShapeAdapter(const TfToken& type, ShapeAdapterCreator creator); + + MAYAHYDRALIB_API + static ShapeAdapterCreator GetShapeAdapterCreator(const MDagPath& dag); + + using LightAdapterCreator + = std::function; + MAYAHYDRALIB_API + static void RegisterLightAdapter(const TfToken& type, LightAdapterCreator creator); + + MAYAHYDRALIB_API + static LightAdapterCreator GetLightAdapterCreator(const MDagPath& dag); + + MAYAHYDRALIB_API + static LightAdapterCreator GetLightAdapterCreator(const MObject& dag); + + using MaterialAdapterCreator = std::function< + MayaHydraMaterialAdapterPtr(const SdfPath&, MayaHydraSceneProducer*, const MObject&)>; + MAYAHYDRALIB_API + static void RegisterMaterialAdapter(const TfToken& type, MaterialAdapterCreator creator); + + MAYAHYDRALIB_API + static MaterialAdapterCreator GetMaterialAdapterCreator(const MObject& node); + + using CameraAdapterCreator + = std::function; + MAYAHYDRALIB_API + static void RegisterCameraAdapter(const TfToken& type, CameraAdapterCreator creator); + + MAYAHYDRALIB_API + static CameraAdapterCreator GetCameraAdapterCreator(const MDagPath& dag); + + /// Find all MayaHydraAdapter plug-ins, and load them all + MAYAHYDRALIB_API + static void LoadAllPlugin(); + +private: + std::unordered_map _dagAdapters; + std::unordered_map _lightAdapters; + std::unordered_map _materialAdapters; + std::unordered_map _cameraAdapters; +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // MAYAHYDRALIB_ADAPTER_REGISTRY_H diff --git a/lib/mayaHydra/hydraExtensions/adapters/aiSkydomeLightAdapter.cpp b/lib/mayaHydra/hydraExtensions/adapters/aiSkydomeLightAdapter.cpp new file mode 100644 index 0000000000..2cc3c5f0a7 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/adapters/aiSkydomeLightAdapter.cpp @@ -0,0 +1,248 @@ +// +// 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. +// +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +class MayaHydraSceneProducer; + +/** + * \brief MayaHydraAiSkyDomeLightAdapter is used to handle the translation from an Arnold skydome + * light to hydra. + */ +class MayaHydraAiSkyDomeLightAdapter : public MayaHydraLightAdapter +{ + ///To be able to create a dummy texture (see _dummyTexturePath) + static MHWRender::MTextureManager* _pTextureManager; + + ///Temp folder path from the OS + static std::string _tmpFolderPath; + + /**_dummyTexturePath is the fullpath filename for a dummy 1x1 texture file used when there + * is no texture connected to the color of the Arnold sky dome light + * as Hydra always wants a texture and ignores the color if no texture is present. + */ + std::string _dummyTextureFullPathFilename; + ///This is only the filename of the dummy texture to be saved + std::string _dummyTextureFilenameOnly; + + ///Is the color attribute of the sky dome light connected to something ? + bool _colorIsConnected = false; + +public: + MayaHydraAiSkyDomeLightAdapter(MayaHydraSceneProducer* producer, const MDagPath& dag) + : MayaHydraLightAdapter(producer, dag) + { + //Init static variables if needed + if (! _pTextureManager){ + MHWRender::MRenderer* const renderer = MHWRender::MRenderer::theRenderer(); + _pTextureManager = renderer ? renderer->getTextureManager() : nullptr; + } + + if (_tmpFolderPath.empty()){ + const char *tmpDir = getenv("TMPDIR"); + if (tmpDir) { + _tmpFolderPath = tmpDir; + }else { + tmpDir = getenv("TEMP"); + if (tmpDir) { + _tmpFolderPath = tmpDir; + } + } + } + + _dummyTextureFilenameOnly = TfStringPrintf("/HydraAiSkyDomeLightTex__%p__tmp.png", this); + } + + virtual ~MayaHydraAiSkyDomeLightAdapter() + { + // Delete the dummy texture files if they exist + if (! _dummyTextureFullPathFilename.empty()){ + auto dummyTextureFullPathFilename1 = _tmpFolderPath + _dummyTextureFilenameOnly; + std::remove(dummyTextureFullPathFilename1.c_str()); + } + } + + const TfToken& LightType() const override { return HdPrimTypeTokens->domeLight; } + + VtValue GetLightParamValue(const TfToken& paramName) override + { + MStatus status; + MFnDependencyNode light(GetNode(), &status); + if (ARCH_UNLIKELY(!status)) { + return {}; + } + + // We are not using precomputed attributes here, because we don't have + // a guarantee that mtoa will be loaded before mayaHydra. + if (paramName == HdLightTokens->color) { + const auto plug = light.findPlug("color", true); + MPlugArray conns; + plug.connectedTo(conns, true, false); + _colorIsConnected = (conns.length() > 0); + if (_colorIsConnected){ + return VtValue(GfVec3f(1.0f, 1.0f, 1.0f));// When there is a connection, return a white color. + } + + // If no texture is found then get unconnected plug value and make a 1x1 texture of constant color using it. + float r = 0.5f; + float g = 0.5f; + float b = 0.5f; + if (!plug.isNull()) + { + plug.child(0).getValue(r); + plug.child(1).getValue(g); + plug.child(2).getValue(b); + } + + const float rClamped = (r <= 1.f) ? r : 1.0f; + const float gClamped = (g <= 1.f) ? g : 1.0f; + const float bClamped = (b <= 1.f) ? b : 1.0f; + + unsigned char texData[4]; + texData[0] = (unsigned char)(255 * rClamped); + texData[1] = (unsigned char)(255 * gClamped); + texData[2] = (unsigned char)(255 * bClamped); + texData[3] = 255; + + //Create a 1 x 1 constant color texture + MHWRender::MTextureDescription desc; + desc.setToDefault2DTexture(); + desc.fWidth = 1; + desc.fHeight = 1; + desc.fFormat = MHWRender::kR8G8B8A8_UNORM; + if (_pTextureManager){ + auto pTexture = _pTextureManager->acquireTexture("", desc, texData);//In memory + if (pTexture && (!_tmpFolderPath.empty()) && (!_dummyTextureFilenameOnly.empty())){ + pTexture->setHasAlpha(true); + _dummyTextureFullPathFilename = _tmpFolderPath + _dummyTextureFilenameOnly; + // This texture will be used in the HdLightTokens->textureFile parameter + _pTextureManager->saveTexture(pTexture, MString(_dummyTextureFullPathFilename.c_str())); + } + } + return VtValue(GfVec3f(r,g,b)); + } else if (paramName == HdLightTokens->intensity) { + return VtValue(light.findPlug("intensity", true).asFloat()); + } else if (paramName == HdLightTokens->diffuse) { + MPlug aiDiffuse = light.findPlug("aiDiffuse", true, &status); + if (status == MS::kSuccess) { + return VtValue(aiDiffuse.asFloat()); + } + } else if (paramName == HdLightTokens->specular) { + MPlug aiSpecular = light.findPlug("aiSpecular", true, &status); + if (status == MS::kSuccess) { + return VtValue(aiSpecular.asFloat()); + } + } else if (paramName == HdLightTokens->exposure) { + return VtValue(light.findPlug("aiExposure", true).asFloat()); + } else if (paramName == HdLightTokens->normalize) { + return VtValue(light.findPlug("aiNormalize", true).asBool()); + } else if (paramName == HdLightTokens->textureFormat) { + const auto format = light.findPlug("format", true).asShort(); + // mirrored_ball : 0 + // angular : 1 + // latlong : 2 + if (format == 0) { + return VtValue(UsdLuxTokens->mirroredBall); + } else if (format == 2) { + return VtValue(UsdLuxTokens->latlong); + } else { + return VtValue(UsdLuxTokens->automatic); + } + } else if (paramName == HdLightTokens->textureFile) { + // Be aware that dome lights in HdStorm always need a texture to work correctly, + // the color is not used if no texture is present. + + if (!_colorIsConnected){ + if (!_dummyTextureFullPathFilename.empty()){ + // Update Hydra texture resource everytime Domelight color is tweaked. + auto resourceReg = GetSceneProducer()->GetRenderIndex().GetResourceRegistry(); + if (TF_VERIFY(resourceReg, "Unable to update AikSkyDomelights constant color")) + resourceReg->ReloadResource(TfToken("texture"), _dummyTextureFullPathFilename); + // SdfAssetPath requires both "path" and "resolvedPath" + return VtValue(SdfAssetPath(_dummyTextureFullPathFilename, _dummyTextureFullPathFilename)); + } + // this will produce a warning but hopefully is an edge case as + // it means we were not able to create a dummy texture + return VtValue(SdfAssetPath()); + } + + MPlugArray conns; + light.findPlug("color", true).connectedTo(conns, true, false); + if (conns.length() < 1) { + // Should never happen as it has been tested before with its equivalent : _colorIsConnected + return VtValue(SdfAssetPath()); + } + MFnDependencyNode file(conns[0].node(), &status); + if (ARCH_UNLIKELY( + !status || (file.typeName() != MayaHydraAdapterTokens->file.GetText()))) { + // Be aware that dome lights in HdStorm always need a texture to work correctly, + // the color is not used if no texture is present. + if (! _dummyTextureFullPathFilename.empty()){ + // SdfAssetPath requires both "path" "resolvedPath" + return VtValue(SdfAssetPath(_dummyTextureFullPathFilename, _dummyTextureFullPathFilename)); + } else { + return VtValue(SdfAssetPath());// this will produce a warning but hopefully is an edge case + } + } + + const char* fileTextureName + = file.findPlug(MayaAttrs::file::fileTextureName, true).asString().asChar(); + // SdfAssetPath requires both "path" "resolvedPath" + return VtValue(SdfAssetPath(fileTextureName, fileTextureName)); + + } else if (paramName == HdLightTokens->enableColorTemperature) { + return VtValue(false); + } + return {}; + } +}; + +//Static variables from MayaHydraAiSkyDomeLightAdapter +MHWRender::MTextureManager* MayaHydraAiSkyDomeLightAdapter::_pTextureManager = nullptr; +std::string MayaHydraAiSkyDomeLightAdapter::_tmpFolderPath; + +TF_REGISTRY_FUNCTION(TfType) +{ + TfType::Define>(); +} + +TF_REGISTRY_FUNCTION_WITH_TAG(MayaHydraAdapterRegistry, domeLight) +{ + MayaHydraAdapterRegistry::RegisterLightAdapter( + TfToken("aiSkyDomeLight"), + [](MayaHydraSceneProducer* producer, const MDagPath& dag) -> MayaHydraLightAdapterPtr { + return MayaHydraLightAdapterPtr(new MayaHydraAiSkyDomeLightAdapter(producer, dag)); + }); +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/mayaHydra/hydraExtensions/adapters/areaLightAdapter.cpp b/lib/mayaHydra/hydraExtensions/adapters/areaLightAdapter.cpp new file mode 100644 index 0000000000..69e4539611 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/adapters/areaLightAdapter.cpp @@ -0,0 +1,81 @@ +// +// 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. +// +#include +#include +#include +#include + +#include +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +/** + * \brief MayaHydraAreaLightAdapter is used to handle the translation from a Maya area light to + * hydra. + */ +class MayaHydraAreaLightAdapter : public MayaHydraLightAdapter +{ +public: + MayaHydraAreaLightAdapter(MayaHydraSceneProducer* producer, const MDagPath& dag) + : MayaHydraLightAdapter(producer, dag) + { + } + + void _CalculateLightParams(GlfSimpleLight& light) override { light.SetSpotCutoff(90.0f); } + + const TfToken& LightType() const override + { + if (GetSceneProducer()->IsHdSt()) { + return HdPrimTypeTokens->simpleLight; + } else { + return HdPrimTypeTokens->rectLight; + } + } + + VtValue GetLightParamValue(const TfToken& paramName) override + { + TF_DEBUG(MAYAHYDRALIB_ADAPTER_GET_LIGHT_PARAM_VALUE) + .Msg( + "Called MayaHydraAreaLightAdapter::GetLightParamValue(%s) - %s\n", + paramName.GetText(), + GetDagPath().partialPathName().asChar()); + + if (paramName == HdLightTokens->width) { + return VtValue(2.0f); + } else if (paramName == HdLightTokens->height) { + return VtValue(2.0f); + } + return MayaHydraLightAdapter::GetLightParamValue(paramName); + } +}; + +TF_REGISTRY_FUNCTION(TfType) +{ + TfType::Define>(); +} + +TF_REGISTRY_FUNCTION_WITH_TAG(MayaHydraAdapterRegistry, pointLight) +{ + MayaHydraAdapterRegistry::RegisterLightAdapter( + TfToken("areaLight"), + [](MayaHydraSceneProducer* producer, const MDagPath& dag) -> MayaHydraLightAdapterPtr { + return MayaHydraLightAdapterPtr(new MayaHydraAreaLightAdapter(producer, dag)); + }); +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/mayaHydra/hydraExtensions/adapters/cameraAdapter.cpp b/lib/mayaHydra/hydraExtensions/adapters/cameraAdapter.cpp new file mode 100644 index 0000000000..8972cda352 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/adapters/cameraAdapter.cpp @@ -0,0 +1,303 @@ +// +// Copyright 2021 Autodesk +// +// 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 "cameraAdapter.h" + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +namespace { + +TF_REGISTRY_FUNCTION(TfType) +{ + TfType::Define>(); +} + +TF_REGISTRY_FUNCTION_WITH_TAG(MayaHydraAdapterRegistry, camera) +{ + MayaHydraAdapterRegistry::RegisterCameraAdapter( + HdPrimTypeTokens->camera, + [](MayaHydraSceneProducer* producer, const MDagPath& dag) -> MayaHydraCameraAdapterPtr { + return MayaHydraCameraAdapterPtr(new MayaHydraCameraAdapter(producer, dag)); + }); +} + +} // namespace + +// MayaHydraCameraAdapter is used to handle the translation from a Maya camera to hydra. +MayaHydraCameraAdapter::MayaHydraCameraAdapter(MayaHydraSceneProducer* producer, const MDagPath& dag) + : MayaHydraShapeAdapter(producer->GetPrimPath(dag, true), producer, dag) +{ +} + +MayaHydraCameraAdapter::~MayaHydraCameraAdapter() { } + +TfToken MayaHydraCameraAdapter::CameraType() { return HdPrimTypeTokens->camera; } + +bool MayaHydraCameraAdapter::IsSupported() const +{ + return GetSceneProducer()->GetRenderIndex().IsSprimTypeSupported(CameraType()); +} + +void MayaHydraCameraAdapter::Populate() +{ + if (_isPopulated) { + return; + } + GetSceneProducer()->InsertSprim(this, CameraType(), GetID(), HdCamera::AllDirty); + _isPopulated = true; +} + +void MayaHydraCameraAdapter::MarkDirty(HdDirtyBits dirtyBits) +{ + if (_isPopulated && dirtyBits != 0) { + dirtyBits = dirtyBits & HdCamera::AllDirty; + GetSceneProducer()->MarkSprimDirty(GetID(), dirtyBits); + } +} + +void MayaHydraCameraAdapter::CreateCallbacks() +{ + MStatus status; + auto dag = GetDagPath(); + auto obj = dag.node(); + + auto paramsChanged = MNodeMessage::addNodeDirtyCallback( + obj, + +[](MObject& obj, void* clientData) { + auto* adapter = reinterpret_cast(clientData); + // Dirty everything rather than track complex param and fit to projection dependencies. + adapter->MarkDirty(HdCamera::DirtyParams | HdCamera::DirtyWindowPolicy); + }, + reinterpret_cast(this), + &status); + if (status) { + AddCallback(paramsChanged); + } + + auto xformChanged = MDagMessage::addWorldMatrixModifiedCallback( + dag, + +[](MObject& transformNode, MDagMessage::MatrixModifiedFlags& modified, void* clientData) { + auto* adapter = reinterpret_cast(clientData); + adapter->MarkDirty(HdCamera::DirtyTransform); + adapter->InvalidateTransform(); + }, + reinterpret_cast(this), + &status); + if (status) { + AddCallback(xformChanged); + } + + // Skip over MayaHydraShapeAdapter's CreateCallbacks + MayaHydraAdapter::CreateCallbacks(); +} + +void MayaHydraCameraAdapter::RemovePrim() +{ + if (!_isPopulated) { + return; + } + GetSceneProducer()->RemoveSprim(CameraType(), GetID()); + _isPopulated = false; +} + +bool MayaHydraCameraAdapter::HasType(const TfToken& typeId) const { return typeId == CameraType(); } + +VtValue MayaHydraCameraAdapter::Get(const TfToken& key) { return MayaHydraShapeAdapter::Get(key); } + +VtValue MayaHydraCameraAdapter::GetCameraParamValue(const TfToken& paramName) +{ + constexpr double mayaInchToHydraCentimeter = 0.254; + constexpr double mayaInchToHydraMillimeter = 0.0254; + constexpr double mayaFocaLenToHydra = 0.01; + + MStatus status; + + auto convertFit = [&](const MFnCamera& camera) -> CameraUtilConformWindowPolicy { + const auto mayaFit = camera.filmFit(&status); + if (mayaFit == MFnCamera::kHorizontalFilmFit) + return CameraUtilConformWindowPolicy::CameraUtilMatchHorizontally; + if (mayaFit == MFnCamera::kVerticalFilmFit) + return CameraUtilConformWindowPolicy::CameraUtilMatchVertically; + + const auto fitMatcher = camera.horizontalFilmAperture() > camera.verticalFilmAperture() + ? MFnCamera::kOverscanFilmFit + : MFnCamera::kFillFilmFit; + return mayaFit == fitMatcher ? CameraUtilConformWindowPolicy::CameraUtilMatchHorizontally + : CameraUtilConformWindowPolicy::CameraUtilMatchVertically; + }; + + auto apertureConvert = [&](const MFnCamera& camera, double glApertureX, double glApertureY) { + const auto usdFit = convertFit(camera); + const double aperture = usdFit == CameraUtilConformWindowPolicy::CameraUtilMatchHorizontally + ? camera.horizontalFilmAperture() + : camera.verticalFilmAperture(); + const double glAperture + = usdFit == CameraUtilConformWindowPolicy::CameraUtilMatchHorizontally ? glApertureX + : glApertureY; + return (0.02 / aperture) * (aperture / glAperture); + }; + + auto viewParameters = [&](const MFnCamera& camera, + const GfVec4d* viewport, + double& apertureX, + double& apertureY, + double& offsetX, + double& offsetY) -> MStatus { + double aspectRatio = viewport + ? ((*viewport)[2] - (*viewport)[0]) / ((*viewport)[3] - (*viewport)[1]) + : camera.aspectRatio(); + return camera.getViewParameters( + aspectRatio, apertureX, apertureY, offsetX, offsetY, true, false, true); + }; + + auto hadError = [&](MStatus& status) -> bool { + if (ARCH_LIKELY(status)) + return false; + TF_WARN( + "Error in MayaHydraCameraAdapter::GetCameraParamValue(%s): %s", + paramName.GetText(), + status.errorString().asChar()); + return false; + }; + + MFnCamera camera(GetDagPath(), &status); + if (hadError(status)) + return {}; + + const bool isOrtho = camera.isOrtho(&status); + if (hadError(status)) { + return {}; + } + + if (paramName == HdCameraTokens->shutterOpen) { + // No motion samples, instantaneous shutter + if (!GetSceneProducer()->GetParams().motionSamplesEnabled()) + return VtValue(double(0)); + return VtValue(double(GetSceneProducer()->GetCurrentTimeSamplingInterval().GetMin())); + } + if (paramName == HdCameraTokens->shutterClose) { + // No motion samples, instantaneous shutter + if (!GetSceneProducer()->GetParams().motionSamplesEnabled()) + return VtValue(double(0)); + const auto shutterAngle = camera.shutterAngle(&status); + if (hadError(status)) + return {}; + auto constexpr maxRadians = M_PI * 2.0; + auto shutterClose = std::min(std::max(0.0, shutterAngle), maxRadians) / maxRadians; + auto interval = GetSceneProducer()->GetCurrentTimeSamplingInterval(); + return VtValue(double(interval.GetMin() + interval.GetSize() * shutterClose)); + } + + // Don't bother with anything else for orthographic cameras + if (isOrtho) { + return {}; + } + if (paramName == HdCameraTokens->focusDistance) { + auto focusDistance = camera.focusDistance(&status); + if (hadError(status)) + return {}; + return VtValue(float(focusDistance * mayaInchToHydraCentimeter)); + } + if (paramName == HdCameraTokens->focalLength) { + const double aspectRatio = _viewport + ? (((*_viewport)[2] - (*_viewport)[0]) / ((*_viewport)[3] - (*_viewport)[1])) + : camera.aspectRatio(); + + double left, right, bottom, top; + status = camera.getViewingFrustum(aspectRatio, left, right, bottom, top, true, false, true); + + const double cameraNear = camera.nearClippingPlane(); + + const double focalLen + = (convertFit(camera) == CameraUtilConformWindowPolicy::CameraUtilMatchVertically) + ? (2.0 * cameraNear) / (top - bottom) + : (2.0 * cameraNear) / (right - left); + return VtValue(float(focalLen * mayaFocaLenToHydra)); + } + if (paramName == HdCameraTokens->fStop) { + // For USD/Hydra fStop=0 should disable depthOfField + if (!camera.isDepthOfField()) + return VtValue(0.f); + const auto fStop = camera.fStop(&status); + if (hadError(status)) + return {}; + return VtValue(float(fStop)); + } + if (paramName == HdCameraTokens->horizontalAperture) { + double apertureX, apertureY, offsetX, offsetY; + status = viewParameters(camera, _viewport.get(), apertureX, apertureY, offsetX, offsetY); + if (hadError(status)) + return {}; + return VtValue(float(apertureX * apertureConvert(camera, apertureX, apertureY))); + } + if (paramName == HdCameraTokens->verticalAperture) { + double apertureX, apertureY, offsetX, offsetY; + status = viewParameters(camera, _viewport.get(), apertureX, apertureY, offsetX, offsetY); + if (hadError(status)) + return {}; + return VtValue(float(apertureY * apertureConvert(camera, apertureX, apertureY))); + } + if (paramName == HdCameraTokens->horizontalApertureOffset) { + double apertureX, apertureY, offsetX, offsetY; + status = viewParameters(camera, _viewport.get(), apertureX, apertureY, offsetX, offsetY); + if (hadError(status)) + return {}; + return VtValue(float(offsetX * mayaInchToHydraMillimeter)); + } + if (paramName == HdCameraTokens->verticalApertureOffset) { + double apertureX, apertureY, offsetX, offsetY; + status = viewParameters(camera, _viewport.get(), apertureX, apertureY, offsetX, offsetY); + if (hadError(status)) + return {}; + return VtValue(float(offsetY * mayaInchToHydraMillimeter)); + } + if (paramName == HdCameraTokens->windowPolicy) { + const auto windowPolicy = convertFit(camera); + if (hadError(status)) + return {}; + return VtValue(windowPolicy); + } + if (paramName == HdCameraTokens->projection) { + if (isOrtho) { + return VtValue(HdCamera::Orthographic); + } else { + return VtValue(HdCamera::Perspective); + } + } + + return {}; +} + +void MayaHydraCameraAdapter::SetViewport(const GfVec4d& viewport) +{ + if (!_viewport) { + _viewport.reset(new GfVec4d); + } + *_viewport = viewport; +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/mayaHydra/hydraExtensions/adapters/cameraAdapter.h b/lib/mayaHydra/hydraExtensions/adapters/cameraAdapter.h new file mode 100644 index 0000000000..575c972732 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/adapters/cameraAdapter.h @@ -0,0 +1,81 @@ +// +// Copyright 2021 Autodesk +// +// 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 MAYAHYDRALIB_CAMERA_ADAPTER_H +#define MAYAHYDRALIB_CAMERA_ADAPTER_H + +#include +#include + +#include + +PXR_NAMESPACE_OPEN_SCOPE + +class MayaHydraSceneProducer; + +/** + * \brief MayaHydraCameraAdapter is used to handle the translation from a Maya camera to hydra. + */ +class MayaHydraCameraAdapter : public MayaHydraShapeAdapter +{ +public: + MAYAHYDRALIB_API + MayaHydraCameraAdapter(MayaHydraSceneProducer* producer, const MDagPath& dag); + + MAYAHYDRALIB_API + virtual ~MayaHydraCameraAdapter(); + + MAYAHYDRALIB_API + bool IsSupported() const override; + + MAYAHYDRALIB_API + void MarkDirty(HdDirtyBits dirtyBits) override; + + MAYAHYDRALIB_API + void Populate() override; + + MAYAHYDRALIB_API + void RemovePrim() override; + + MAYAHYDRALIB_API + bool HasType(const TfToken& typeId) const override; + + MAYAHYDRALIB_API + VtValue Get(const TfToken& key) override; + + MAYAHYDRALIB_API + VtValue GetCameraParamValue(const TfToken& paramName); + + MAYAHYDRALIB_API + void CreateCallbacks() override; + + MAYAHYDRALIB_API + void SetViewport(const GfVec4d& viewport); + +protected: + static TfToken CameraType(); + + /// The use of a pointer here helps us track whether this camera is (or has ever been) + /// the active viewport camera. NOTE: it's possible that _viewport will be out of date + /// after switching to a new camera and resizing the viewport, but _viewport will eventually + /// be re-synched before any output/pixels of the stale size is requested. + std::unique_ptr _viewport; +}; + +using MayaHydraCameraAdapterPtr = std::shared_ptr; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // MAYAHYDRALIB_CAMERA_ADAPTER_H diff --git a/lib/mayaHydra/hydraExtensions/adapters/constantShadowMatrix.h b/lib/mayaHydra/hydraExtensions/adapters/constantShadowMatrix.h new file mode 100644 index 0000000000..851734bedf --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/adapters/constantShadowMatrix.h @@ -0,0 +1,56 @@ +// +// 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. +// +#ifndef MAYAHYDRALIB_SHADOW_MATRIX_H +#define MAYAHYDRALIB_SHADOW_MATRIX_H + +#include +#include +#include + +#include + +PXR_NAMESPACE_OPEN_SCOPE + +/** + * \brief MayaHydraConstantShadowMatrix is used to provide a constant shadow matrix for hydra. + */ +class MayaHydraConstantShadowMatrix : public HdxShadowMatrixComputation +{ +public: + explicit MayaHydraConstantShadowMatrix(const GfMatrix4d& mat) + : _shadowMatrix(mat) + { + } + + inline std::vector + Compute(const CameraUtilFraming& framing, CameraUtilConformWindowPolicy policy) override + { + return { _shadowMatrix }; + } + + inline std::vector + Compute(const GfVec4f& viewport, CameraUtilConformWindowPolicy policy) override + { + return { _shadowMatrix }; + } + +private: + GfMatrix4d _shadowMatrix; +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // MAYAHYDRALIB_SHADOW_MATRIX_H diff --git a/lib/mayaHydra/hydraExtensions/adapters/dagAdapter.cpp b/lib/mayaHydra/hydraExtensions/adapters/dagAdapter.cpp new file mode 100644 index 0000000000..2fb92a7c64 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/adapters/dagAdapter.cpp @@ -0,0 +1,342 @@ +// +// 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. +// +#include "dagAdapter.h" + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE +// Bring the MayaHydra namespace into scope. +// The following code currently lives inside the pxr namespace, but it would make more sense to +// have it inside the MayaHydra namespace. This using statement allows us to use MayaHydra symbols +// from within the pxr namespace as if we were in the MayaHydra namespace. +// Remove this once the code has been moved to the MayaHydra namespace. +using namespace MayaHydra; + +TF_REGISTRY_FUNCTION(TfType) +{ + TfType::Define>(); +} + +// clang-format off +TF_DEFINE_PRIVATE_TOKENS( + _tokens, + + (translate) + (rotate) + (scale) + (instanceTransform) + (instancer) +); +// clang-format on + +namespace { + +void _TransformNodeDirty(MObject& node, MPlug& plug, void* clientData) +{ + auto* adapter = reinterpret_cast(clientData); + TF_DEBUG(MAYAHYDRALIB_ADAPTER_DAG_PLUG_DIRTY) + .Msg( + "Dag adapter marking prim (%s) dirty because .%s plug was " + "dirtied.\n", + adapter->GetID().GetText(), + plug.partialName().asChar()); + if (plug == MayaAttrs::dagNode::visibility || plug == MayaAttrs::dagNode::intermediateObject + || plug == MayaAttrs::dagNode::overrideEnabled + || plug == MayaAttrs::dagNode::overrideVisibility) { + // Unfortunately, during this callback, we can't actually + // query the new object's visiblity - the plug dirty hasn't + // really propagated yet. So we just mark our own _visibility + // as dirty, and unconditionally dirty the hd bits + + // If we're currently invisible, it's possible we were + // skipping transform updates (see below), so need to mark + // that dirty as well... + if (adapter->IsVisible(false)) { + // Transform can change while dag path is hidden. + adapter->MarkDirty(HdChangeTracker::DirtyVisibility | HdChangeTracker::DirtyTransform); + adapter->InvalidateTransform(); + } else { + adapter->MarkDirty(HdChangeTracker::DirtyVisibility); + } + // We use IsVisible(checkDirty=false) because we need to make sure we + // DON'T update visibility from within this callback, since the change + // has't propagated yet + } else if (adapter->IsVisible(false)) { + adapter->MarkDirty(HdChangeTracker::DirtyTransform); + adapter->InvalidateTransform(); + } +} + +void _HierarchyChanged(MDagPath& child, MDagPath& parent, void* clientData) +{ + TF_UNUSED(child); + auto* adapter = reinterpret_cast(clientData); + TF_DEBUG(MAYAHYDRALIB_ADAPTER_DAG_HIERARCHY) + .Msg( + "Dag hierarchy changed for prim (%s) because %s had parent %s " + "added/removed.\n", + adapter->GetID().GetText(), + child.partialPathName().asChar(), + parent.partialPathName().asChar()); + adapter->RemoveCallbacks(); + adapter->RemovePrim(); + adapter->GetSceneProducer()->RecreateAdapterOnIdle(adapter->GetID(), adapter->GetNode()); +} + +void _InstancerNodeDirty(MObject& node, MPlug& plug, void* clientData) +{ + auto* adapter = reinterpret_cast(clientData); + TF_DEBUG(MAYAHYDRALIB_ADAPTER_DAG_PLUG_DIRTY) + .Msg( + "Dag instancer adapter marking prim (%s) dirty because %s plug was " + "dirtied.\n", + adapter->GetID().GetText(), + plug.partialName().asChar()); + adapter->MarkDirty( + HdChangeTracker::DirtyInstancer | HdChangeTracker::DirtyInstanceIndex + | HdChangeTracker::DirtyPrimvar); +} + +const auto _instancePrimvarDescriptors = HdPrimvarDescriptorVector { + { _tokens->instanceTransform, HdInterpolationInstance, HdPrimvarRoleTokens->none }, +}; + +} // namespace + +// MayaHydraDagAdapter is the adapter base class for any dag object. +MayaHydraDagAdapter::MayaHydraDagAdapter( + const SdfPath& id, + MayaHydraSceneProducer* producer, + const MDagPath& dagPath) + : MayaHydraAdapter(dagPath.node(), id, producer) + , _dagPath(dagPath) +{ + // We shouldn't call virtual functions in constructors. + _isVisible = GetDagPath().isVisible(); + _visibilityDirty = false; + _isInstanced = _dagPath.isInstanced() && _dagPath.instanceNumber() == 0; +} + +GfMatrix4d MayaHydraDagAdapter::GetTransform() +{ + TF_DEBUG(MAYAHYDRALIB_ADAPTER_GET) + .Msg( + "Called MayaHydraDagAdapter::GetTransform() - %s\n", + _dagPath.partialPathName().asChar()); + + if (_invalidTransform) { + if (IsInstanced()) { + _transform.SetIdentity(); + } else { + _transform = GetGfMatrixFromMaya(_dagPath.inclusiveMatrix()); + } + _invalidTransform = false; + } + + return _transform; +} + +size_t +MayaHydraDagAdapter::SampleTransform(size_t maxSampleCount, float* times, GfMatrix4d* samples) +{ + return GetSceneProducer()->SampleValues(maxSampleCount, times, samples, [&]() -> GfMatrix4d { + return GetGfMatrixFromMaya(_dagPath.inclusiveMatrix()); + }); +} + +void MayaHydraDagAdapter::CreateCallbacks() +{ + MStatus status; + + TF_DEBUG(MAYAHYDRALIB_ADAPTER_CALLBACKS) + .Msg("Creating dag adapter callbacks for prim (%s).\n", GetID().GetText()); + + MDagPathArray dags; + if (MDagPath::getAllPathsTo(GetDagPath().node(), dags)) { + const auto numDags = dags.length(); + auto dagNodeDirtyCallback = numDags > 1 ? _InstancerNodeDirty : _TransformNodeDirty; + for (auto i = decltype(numDags) { 0 }; i < numDags; ++i) { + auto dag = dags[i]; + for (; dag.length() > 0; dag.pop()) { + MObject obj = dag.node(); + if (obj != MObject::kNullObj) { + auto id = MNodeMessage::addNodeDirtyPlugCallback( + obj, dagNodeDirtyCallback, this, &status); + if (status) { + AddCallback(id); + } + TF_DEBUG(MAYAHYDRALIB_ADAPTER_CALLBACKS) + .Msg( + "- Added _InstancerNodeDirty callback for " + "dagPath (%s).\n", + dag.partialPathName().asChar()); + _AddHierarchyChangedCallbacks(dag); + } + } + } + } + MayaHydraAdapter::CreateCallbacks(); +} + +void MayaHydraDagAdapter::MarkDirty(HdDirtyBits dirtyBits) +{ + if (dirtyBits != 0) { + GetSceneProducer()->GetRenderIndex().GetChangeTracker().MarkRprimDirty(GetID(), dirtyBits); + if (IsInstanced()) { + GetSceneProducer()->GetRenderIndex().GetChangeTracker().MarkInstancerDirty(GetInstancerID(), dirtyBits); + } + if (dirtyBits & HdChangeTracker::DirtyVisibility) { + _visibilityDirty = true; + } + } +} + +void MayaHydraDagAdapter::RemovePrim() +{ + if (!_isPopulated) { + return; + } + GetSceneProducer()->RemoveRprim(GetID()); + if (_isInstanced) { + GetSceneProducer()->GetRenderIndex().RemoveInstancer(GetID().AppendProperty(_tokens->instancer)); + } + _isPopulated = false; +} + +bool MayaHydraDagAdapter::UpdateVisibility() +{ + if (ARCH_UNLIKELY(!GetDagPath().isValid())) { + return false; + } + const auto visible = _GetVisibility(); + _visibilityDirty = false; + if (visible != _isVisible) { + _isVisible = visible; + return true; + } + return false; +} + +bool MayaHydraDagAdapter::IsVisible(bool checkDirty) +{ + if (checkDirty && _visibilityDirty) { + UpdateVisibility(); + } + return _isVisible; +} + +VtIntArray MayaHydraDagAdapter::GetInstanceIndices(const SdfPath& prototypeId) +{ + if (!IsInstanced()) { + return {}; + } + MDagPathArray dags; + if (!MDagPath::getAllPathsTo(GetDagPath().node(), dags)) { + return {}; + } + const auto numDags = dags.length(); + VtIntArray ret; + ret.reserve(numDags); + for (auto i = decltype(numDags) { 0 }; i < numDags; ++i) { + if (dags[i].isValid() && dags[i].isVisible()) { + ret.push_back(static_cast(ret.size())); + } + } + return ret; +} + +void MayaHydraDagAdapter::_AddHierarchyChangedCallbacks(MDagPath& dag) +{ + MStatus status; + auto id = MDagMessage::addParentAddedDagPathCallback(dag, _HierarchyChanged, this, &status); + if (status) { + AddCallback(id); + } + TF_DEBUG(MAYAHYDRALIB_ADAPTER_CALLBACKS) + .Msg("- Added parent added callback for dagPath (%s).\n", dag.partialPathName().asChar()); + + // We need a parent removed callback, even for non-instances, + // because when an object is removed from the scene due to an + // undo, no pre-removal (or about-to-delete, or destroyed) + // callbacks are triggered. The parent-removed callback IS + // triggered, though, so it's a way to catch deletion due to + // undo... + id = MDagMessage::addParentRemovedDagPathCallback(dag, _HierarchyChanged, this, &status); + if (status) { + AddCallback(id); + } + TF_DEBUG(MAYAHYDRALIB_ADAPTER_CALLBACKS) + .Msg("- Added parent removed callback for dagPath (%s).\n", dag.partialPathName().asChar()); +} + +SdfPath MayaHydraDagAdapter::GetInstancerID() const +{ + if (!_isInstanced) { + return {}; + } + + return GetID().AppendProperty(_tokens->instancer); +} + +HdPrimvarDescriptorVector +MayaHydraDagAdapter::GetInstancePrimvarDescriptors(HdInterpolation interpolation) const +{ + if (interpolation == HdInterpolationInstance) { + return _instancePrimvarDescriptors; + } else { + return {}; + } +} + +bool MayaHydraDagAdapter::_GetVisibility() const { return GetDagPath().isVisible(); } + +VtValue MayaHydraDagAdapter::GetInstancePrimvar(const TfToken& key) +{ + if (key == _tokens->instanceTransform) { + MDagPathArray dags; + if (!MDagPath::getAllPathsTo(GetDagPath().node(), dags)) { + return {}; + } + const auto numDags = dags.length(); + VtArray ret; + ret.reserve(numDags); + for (auto i = decltype(numDags) { 0 }; i < numDags; ++i) { + if (dags[i].isValid() && dags[i].isVisible()) { + ret.push_back(GetGfMatrixFromMaya(dags[i].inclusiveMatrix())); + } + } + return VtValue(ret); + } + return {}; +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/mayaHydra/hydraExtensions/adapters/dagAdapter.h b/lib/mayaHydra/hydraExtensions/adapters/dagAdapter.h new file mode 100644 index 0000000000..eb72c953bb --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/adapters/dagAdapter.h @@ -0,0 +1,100 @@ +// +// 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. +// +#ifndef MAYAHYDRALIB_DG_ADAPTER_H +#define MAYAHYDRALIB_DG_ADAPTER_H + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +PXR_NAMESPACE_OPEN_SCOPE + +class MayaHydraSceneProducer; + +/** + * \brief MayaHydraDagAdapter is the adapter base class for any dag object. + */ +class MayaHydraDagAdapter : public MayaHydraAdapter +{ +protected: + MAYAHYDRALIB_API + MayaHydraDagAdapter(const SdfPath& id, MayaHydraSceneProducer* producer, const MDagPath& dagPath); + +public: + MAYAHYDRALIB_API + virtual ~MayaHydraDagAdapter() = default; + MAYAHYDRALIB_API + virtual bool GetVisible() override { return IsVisible(); } + MAYAHYDRALIB_API + virtual void CreateCallbacks() override; + MAYAHYDRALIB_API + virtual void MarkDirty(HdDirtyBits dirtyBits) override; + MAYAHYDRALIB_API + virtual void RemovePrim() override; + MAYAHYDRALIB_API + GfMatrix4d GetTransform() override; + MAYAHYDRALIB_API + size_t SampleTransform(size_t maxSampleCount, float* times, GfMatrix4d* samples); + MAYAHYDRALIB_API + bool UpdateVisibility(); + bool IsVisible(bool checkDirty = true); + const MDagPath& GetDagPath() const { return _dagPath; } + void InvalidateTransform() { _invalidTransform = true; } + bool IsInstanced() const { return _isInstanced; } + MAYAHYDRALIB_API + SdfPath GetInstancerID() const; + MAYAHYDRALIB_API + virtual VtIntArray GetInstanceIndices(const SdfPath& prototypeId); + MAYAHYDRALIB_API + HdPrimvarDescriptorVector GetInstancePrimvarDescriptors(HdInterpolation interpolation) const; + MAYAHYDRALIB_API + VtValue GetInstancePrimvar(const TfToken& key); + +protected: + MAYAHYDRALIB_API + void _AddHierarchyChangedCallbacks(MDagPath& dag); + MAYAHYDRALIB_API + virtual bool _GetVisibility() const; + +private: + MDagPath _dagPath; + GfMatrix4d _transform; + bool _isVisible = true; + bool _visibilityDirty = true; + bool _invalidTransform = true; + bool _isInstanced = false; +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // MAYAHYDRALIB_DG_ADAPTER_H diff --git a/lib/mayaHydra/hydraExtensions/adapters/directionalLightAdapter.cpp b/lib/mayaHydra/hydraExtensions/adapters/directionalLightAdapter.cpp new file mode 100644 index 0000000000..4b56274957 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/adapters/directionalLightAdapter.cpp @@ -0,0 +1,118 @@ +// +// 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. +// +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include + +PXR_NAMESPACE_OPEN_SCOPE + +/** + * \brief MayaHydraDirectionalLightAdapter is used to handle the translation from a Maya directional + * light to hydra. + */ +class MayaHydraDirectionalLightAdapter : public MayaHydraLightAdapter +{ +public: + MayaHydraDirectionalLightAdapter(MayaHydraSceneProducer* producer, const MDagPath& dag) + : MayaHydraLightAdapter(producer, dag) + { + } + + const TfToken& LightType() const override + { + if (GetSceneProducer()->IsHdSt()) { + return HdPrimTypeTokens->simpleLight; + } else { + return HdPrimTypeTokens->distantLight; + } + } + + void _CalculateLightParams(GlfSimpleLight& light) override + { + // Directional lights point toward -Z, but we need the opposite + // for the position so the light acts as a directional light. + const auto direction = GfVec4f(0.0, 0.0, 1.0, 0.0) * GetTransform(); + light.SetHasShadow(true); + light.SetPosition({ direction[0], direction[1], direction[2], 0.0f }); + } + + VtValue Get(const TfToken& key) override + { + TF_DEBUG(MAYAHYDRALIB_ADAPTER_GET) + .Msg( + "Called MayaHydraDirectionalLightAdapter::Get(%s) - %s\n", + key.GetText(), + GetDagPath().partialPathName().asChar()); + + if (key == HdLightTokens->shadowParams) { + HdxShadowParams shadowParams; + MFnDirectionalLight mayaLight(GetDagPath()); + if (!GetShadowsEnabled(mayaLight)) { + shadowParams.enabled = false; + return VtValue(shadowParams); + } + + _CalculateShadowParams(mayaLight, shadowParams); + // Use the radius as the "blur" amount, for PCSS + shadowParams.blur = mayaLight.shadowRadius(); + return VtValue(shadowParams); + } + + return MayaHydraLightAdapter::Get(key); + } + + VtValue GetLightParamValue(const TfToken& paramName) override + { + if (paramName == HdLightTokens->angle) { + MStatus status; + MFnDependencyNode lightNode(GetNode(), &status); + if (ARCH_UNLIKELY(!status)) { + return VtValue(0.0f); + } + return VtValue( + lightNode.findPlug(MayaAttrs::directionalLight::lightAngle, true).asFloat()); + } else { + return MayaHydraLightAdapter::GetLightParamValue(paramName); + } + } +}; + +TF_REGISTRY_FUNCTION(TfType) +{ + TfType::Define>(); +} + +TF_REGISTRY_FUNCTION_WITH_TAG(MayaHydraAdapterRegistry, pointLight) +{ + MayaHydraAdapterRegistry::RegisterLightAdapter( + TfToken("directionalLight"), + [](MayaHydraSceneProducer* producer, const MDagPath& dag) -> MayaHydraLightAdapterPtr { + return MayaHydraLightAdapterPtr(new MayaHydraDirectionalLightAdapter(producer, dag)); + }); +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/mayaHydra/hydraExtensions/adapters/lightAdapter.cpp b/lib/mayaHydra/hydraExtensions/adapters/lightAdapter.cpp new file mode 100644 index 0000000000..96fa2aea20 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/adapters/lightAdapter.cpp @@ -0,0 +1,371 @@ +// +// 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. +// +#include "lightAdapter.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +PXR_NAMESPACE_OPEN_SCOPE +// Bring the MayaHydra namespace into scope. +// The following code currently lives inside the pxr namespace, but it would make more sense to +// have it inside the MayaHydra namespace. This using statement allows us to use MayaHydra symbols +// from within the pxr namespace as if we were in the MayaHydra namespace. +// Remove this once the code has been moved to the MayaHydra namespace. +using namespace MayaHydra; + +TF_REGISTRY_FUNCTION(TfType) +{ + TfType::Define>(); +} + +namespace { + +void _changeVisibility( + MNodeMessage::AttributeMessage msg, + MPlug& plug, + MPlug& otherPlug, + void* clientData) +{ + TF_UNUSED(msg); + TF_UNUSED(otherPlug); + if (plug == MayaAttrs::dagNode::visibility) { + auto* adapter = reinterpret_cast(clientData); + if (adapter->UpdateVisibility()) { + adapter->RemovePrim(); + adapter->Populate(); + adapter->InvalidateTransform(); + } + } +} + +void _dirtyTransform(MObject& node, void* clientData) +{ + TF_UNUSED(node); + auto* adapter = reinterpret_cast(clientData); + if (adapter->IsVisible()) { + adapter->MarkDirty( + HdLight::DirtyTransform | HdLight::DirtyParams | HdLight::DirtyShadowParams); + adapter->InvalidateTransform(); + } +} + +void _dirtyParams(MObject& node, void* clientData) +{ + TF_UNUSED(node); + auto* adapter = reinterpret_cast(clientData); + if (adapter->IsVisible()) { + adapter->MarkDirty(HdLight::DirtyParams | HdLight::DirtyShadowParams); + adapter->InvalidateTransform(); + } +} + +const MString defaultLightSet("defaultLightSet"); + +} // namespace + +// MayaHydraLightAdapter is the base class for any light adapter used to handle the translation from +// a light to hydra. + +MayaHydraLightAdapter::MayaHydraLightAdapter(MayaHydraSceneProducer* producer, const MDagPath& dag) + : MayaHydraDagAdapter(producer->GetPrimPath(dag, true), producer, dag) +{ + // This should be avoided, not a good idea to call virtual functions + // directly or indirectly in a constructor. + UpdateVisibility(); + _shadowProjectionMatrix.SetIdentity(); +} + +MayaHydraLightAdapter::~MayaHydraLightAdapter() +{ +} + +bool MayaHydraLightAdapter::IsSupported() const +{ + return GetSceneProducer()->GetRenderIndex().IsSprimTypeSupported(LightType()); +} + +void MayaHydraLightAdapter::Populate() +{ + if (_isPopulated) { + return; + } + if (IsVisible() && _isLightingOn) { + GetSceneProducer()->InsertSprim(this, LightType(), GetID(), HdLight::AllDirty); + _isPopulated = true; + } +} + +void MayaHydraLightAdapter::MarkDirty(HdDirtyBits dirtyBits) +{ + if (_isPopulated && dirtyBits != 0) { + GetSceneProducer()->MarkSprimDirty(GetID(), dirtyBits); + } +} + +void MayaHydraLightAdapter::RemovePrim() +{ + if (!_isPopulated) { + return; + } + GetSceneProducer()->RemoveSprim(LightType(), GetID()); + _isPopulated = false; +} + +bool MayaHydraLightAdapter::HasType(const TfToken& typeId) const { return typeId == LightType(); } + +VtValue MayaHydraLightAdapter::Get(const TfToken& key) +{ + TF_DEBUG(MAYAHYDRALIB_ADAPTER_GET) + .Msg( + "Called MayaHydraLightAdapter::Get(%s) - %s\n", + key.GetText(), + GetDagPath().partialPathName().asChar()); + + if (key == HdLightTokens->params) { + MFnLight mayaLight(GetDagPath()); + GlfSimpleLight light; + const auto color = mayaLight.color(); + const auto intensity = mayaLight.intensity(); + MPoint pt(0.0, 0.0, 0.0, 1.0); + const auto inclusiveMatrix = GetDagPath().inclusiveMatrix(); + const auto position = pt * inclusiveMatrix; + // This will return zero / false if the plug is nonexistent. + const auto decayRate + = mayaLight.findPlug(MayaAttrs::nonAmbientLightShapeNode::decayRate, true).asShort(); + const auto emitDiffuse + = mayaLight.findPlug(MayaAttrs::nonAmbientLightShapeNode::emitDiffuse, true).asBool(); + const auto emitSpecular + = mayaLight.findPlug(MayaAttrs::nonAmbientLightShapeNode::emitSpecular, true).asBool(); + MVector pv(0.0, 0.0, -1.0); + const auto lightDirection = (pv * inclusiveMatrix).normal(); + light.SetHasShadow(false); + const GfVec4f zeroColor(0.0f, 0.0f, 0.0f, 1.0f); + const GfVec4f lightColor( + color.r * intensity, color.g * intensity, color.b * intensity, 1.0f); + light.SetDiffuse(emitDiffuse ? lightColor : zeroColor); + light.SetAmbient(zeroColor); + light.SetSpecular(emitSpecular ? lightColor : zeroColor); + light.SetShadowResolution(1024); + light.SetID(GetID()); + light.SetPosition(GfVec4f(position.x, position.y, position.z, position.w)); + light.SetSpotDirection(GfVec3f(lightDirection.x, lightDirection.y, lightDirection.z)); + if (decayRate == 0) { + light.SetAttenuation(GfVec3f(1.0f, 0.0f, 0.0f)); + } else if (decayRate == 1) { + light.SetAttenuation(GfVec3f(0.0f, 1.0f, 0.0f)); + } else if (decayRate == 2) { + light.SetAttenuation(GfVec3f(0.0f, 0.0f, 1.0f)); + } +#if PXR_VERSION < 2308 + light.SetTransform( + GetGfMatrixFromMaya(GetDagPath().inclusiveMatrixInverse())); +#else + light.SetTransform( + GetGfMatrixFromMaya(GetDagPath().inclusiveMatrix())); +#endif + _CalculateLightParams(light); + return VtValue(light); + } else if (key == HdTokens->transform) { + return VtValue(MayaHydraDagAdapter::GetTransform()); + } else if (key == HdLightTokens->shadowCollection) { + // Exclude prims that should not be lighted by only + // taking the primitives whose root path is GetSceneProducer()->GetLightedPrimsRootPath() + const SdfPath lightedPrimsRootPath = GetSceneProducer()->GetLightedPrimsRootPath(); + HdRprimCollection coll( + HdTokens->geometry, + HdReprSelector(HdReprTokens->refined), + lightedPrimsRootPath); + return VtValue(coll); + } else if (key == HdLightTokens->shadowParams) { + HdxShadowParams shadowParams; + MFnLight mayaLight(GetDagPath()); + const bool bLightHasShadowsenabled = mayaLight.useRayTraceShadows(); + if (!bLightHasShadowsenabled) { + shadowParams.enabled = false; + } else { + _CalculateShadowParams(mayaLight, shadowParams); + } + return VtValue(shadowParams); + } + return {}; +} + +VtValue MayaHydraLightAdapter::GetLightParamValue(const TfToken& paramName) +{ + TF_DEBUG(MAYAHYDRALIB_ADAPTER_GET_LIGHT_PARAM_VALUE) + .Msg( + "Called MayaHydraLightAdapter::GetLightParamValue(%s) - %s\n", + paramName.GetText(), + GetDagPath().partialPathName().asChar()); + + MFnLight light(GetDagPath()); + if (paramName == HdLightTokens->color || paramName == HdTokens->displayColor) { + const auto color = light.color(); + return VtValue(GfVec3f(color.r, color.g, color.b)); + } else if (paramName == HdLightTokens->intensity) { + return VtValue(light.intensity()); + } else if (paramName == HdLightTokens->exposure) { + return VtValue(0.0f); + } else if (paramName == HdLightTokens->normalize) { + return VtValue(true); + } else if (paramName == HdLightTokens->enableColorTemperature) { + return VtValue(false); + } else if (paramName == HdLightTokens->diffuse) { + return VtValue(light.lightDiffuse() ? 1.0f : 0.0f); + } else if (paramName == HdLightTokens->specular) { + return VtValue(light.lightSpecular() ? 1.0f : 0.0f); + } + return {}; +} + +void MayaHydraLightAdapter::CreateCallbacks() +{ + TF_DEBUG(MAYAHYDRALIB_ADAPTER_CALLBACKS) + .Msg("Creating light adapter callbacks for prim (%s).\n", GetID().GetText()); + + MStatus status; + auto dag = GetDagPath(); + auto obj = dag.node(); + auto id = MNodeMessage::addNodeDirtyCallback(obj, _dirtyParams, this, &status); + if (status) { + AddCallback(id); + } + dag.pop(); + for (; dag.length() > 0; dag.pop()) { + // The adapter itself will free the callbacks, so we don't have to worry + // about passing raw pointers to the callbacks. Hopefully. + obj = dag.node(); + if (obj != MObject::kNullObj) { + id = MNodeMessage::addAttributeChangedCallback(obj, _changeVisibility, this, &status); + if (status) { + AddCallback(id); + } + id = MNodeMessage::addNodeDirtyCallback(obj, _dirtyTransform, this, &status); + if (status) { + AddCallback(id); + } + _AddHierarchyChangedCallbacks(dag); + } + } + MayaHydraAdapter::CreateCallbacks(); +} + +void MayaHydraLightAdapter::SetShadowProjectionMatrix(const GfMatrix4d& matrix) +{ + if (!GfIsClose(_shadowProjectionMatrix, matrix, 0.0001)) { + MarkDirty(HdLight::DirtyShadowParams); + _shadowProjectionMatrix = matrix; + } +} + +void MayaHydraLightAdapter::_CalculateShadowParams(MFnLight& light, HdxShadowParams& params) +{ + TF_DEBUG(MAYAHYDRALIB_ADAPTER_LIGHT_SHADOWS) + .Msg( + "Called MayaHydraLightAdapter::_CalculateShadowParams - %s\n", + GetDagPath().partialPathName().asChar()); + + const auto dmapResolutionPlug + = light.findPlug(MayaAttrs::nonExtendedLightShapeNode::dmapResolution, true); + const auto dmapBiasPlug = light.findPlug(MayaAttrs::nonExtendedLightShapeNode::dmapBias, true); + const auto dmapFilterSizePlug + = light.findPlug(MayaAttrs::nonExtendedLightShapeNode::dmapFilterSize, true); + + params.enabled = true; + params.resolution = dmapResolutionPlug.isNull() + ? GetSceneProducer()->GetParams().maximumShadowMapResolution + : std::min( + GetSceneProducer()->GetParams().maximumShadowMapResolution, dmapResolutionPlug.asInt()); + + params.shadowMatrix + = std::make_shared(GetTransform() * _shadowProjectionMatrix); + params.bias = dmapBiasPlug.isNull() ? -0.001 : -dmapBiasPlug.asFloat(); + params.blur = dmapFilterSizePlug.isNull() ? 0.0 + : (static_cast(dmapFilterSizePlug.asInt())) + / static_cast(params.resolution); + + if (TfDebug::IsEnabled(MAYAHYDRALIB_ADAPTER_LIGHT_SHADOWS)) { + std::cout << "Resulting HdxShadowParams:\n"; + std::cout << params << "\n"; + } +} + +bool MayaHydraLightAdapter::_GetVisibility() const +{ + if (!GetDagPath().isVisible()) { + return false; + } + // Shapes are not part of the default light set. + if (!GetNode().hasFn(MFn::kLight)) { + return true; + } + MStatus status; + MFnDependencyNode node(GetDagPath().transform(), &status); + if (ARCH_UNLIKELY(!status)) { + return true; + } + auto p = node.findPlug(MayaAttrs::dagNode::instObjGroups, true); + if (ARCH_UNLIKELY(p.isNull())) { + return true; + } + const auto numElements = p.numElements(); + MPlugArray conns; + for (auto i = decltype(numElements) { 0 }; i < numElements; ++i) { + auto ep = p[i]; // == elementByPhysicalIndex + if (!ep.connectedTo(conns, false, true) || conns.length() < 1) { + continue; + } + const auto numConns = conns.length(); + for (auto j = decltype(numConns) { 0 }; j < numConns; ++j) { + MFnDependencyNode otherNode(conns[j].node(), &status); + if (!status) { + continue; + } + if (otherNode.name() == defaultLightSet) { + return true; + } + } + } + return false; +} + +void MayaHydraLightAdapter::SetLightingOn(bool isLightingOn) +{ + if (_isLightingOn != isLightingOn) { + _isLightingOn = isLightingOn; + + RemovePrim(); + Populate(); + } +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/mayaHydra/hydraExtensions/adapters/lightAdapter.h b/lib/mayaHydra/hydraExtensions/adapters/lightAdapter.h new file mode 100644 index 0000000000..8c570f640b --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/adapters/lightAdapter.h @@ -0,0 +1,89 @@ +// +// 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. +// +#ifndef MAYAHYDRALIB_LIGHT_ADAPTER_H +#define MAYAHYDRALIB_LIGHT_ADAPTER_H + +#include + +#include +#include +#include +#include +#include + +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +class MayaHydraSceneProducer; + +/** + * \brief MayaHydraLightAdapter is the base class for any light adapter used to handle the + * translation from a light to hydra. + */ +class MayaHydraLightAdapter : public MayaHydraDagAdapter +{ +public: + inline bool GetShadowsEnabled(MFnNonExtendedLight& light) + { + return light.useDepthMapShadows() || light.useRayTraceShadows(); + } + + MAYAHYDRALIB_API + MayaHydraLightAdapter(MayaHydraSceneProducer* producer, const MDagPath& dag); + MAYAHYDRALIB_API + virtual ~MayaHydraLightAdapter(); + MAYAHYDRALIB_API + virtual const TfToken& LightType() const = 0; + MAYAHYDRALIB_API + bool IsSupported() const override; + MAYAHYDRALIB_API + void Populate() override; + MAYAHYDRALIB_API + void MarkDirty(HdDirtyBits dirtyBits) override; + MAYAHYDRALIB_API + virtual void RemovePrim() override; + MAYAHYDRALIB_API + bool HasType(const TfToken& typeId) const override; + MAYAHYDRALIB_API + virtual VtValue GetLightParamValue(const TfToken& paramName); + MAYAHYDRALIB_API + VtValue Get(const TfToken& key) override; + MAYAHYDRALIB_API + virtual void CreateCallbacks() override; + MAYAHYDRALIB_API + void SetShadowProjectionMatrix(const GfMatrix4d& matrix); + MAYAHYDRALIB_API + void SetLightingOn(bool isLightingOn); + +protected: + MAYAHYDRALIB_API + virtual void _CalculateLightParams(GlfSimpleLight& light) { } + MAYAHYDRALIB_API + void _CalculateShadowParams(MFnLight& light, HdxShadowParams& params); + MAYAHYDRALIB_API + bool _GetVisibility() const override; + + GfMatrix4d _shadowProjectionMatrix; + bool _isLightingOn = true; +}; + +using MayaHydraLightAdapterPtr = std::shared_ptr; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // MAYAHYDRALIB_LIGHT_ADAPTER_H diff --git a/lib/mayaHydra/hydraExtensions/adapters/materialAdapter.cpp b/lib/mayaHydra/hydraExtensions/adapters/materialAdapter.cpp new file mode 100644 index 0000000000..f603dd80b2 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/adapters/materialAdapter.cpp @@ -0,0 +1,314 @@ +// +// 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. +// +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +#include "materialAdapter.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +namespace { + +const VtValue _emptyValue; +const TfToken _emptyToken; +const TfTokenVector _stSamplerCoords = { TfToken("st") }; + +} // namespace + +/* MayaHydraMaterialAdapter is used to handle the translation from a Maya material to hydra. + If you are looking for how we translate the Maya shaders to hydra and how we do the parameters + mapping, please see MayaHydraMaterialNetworkConverter::initialize(). +*/ + +MayaHydraMaterialAdapter::MayaHydraMaterialAdapter( + const SdfPath& id, + MayaHydraSceneProducer* producer, + const MObject& node) + : MayaHydraAdapter(node, id, producer) +{ +} + +bool MayaHydraMaterialAdapter::IsSupported() const +{ + return GetSceneProducer()->GetRenderIndex().IsSprimTypeSupported(HdPrimTypeTokens->material); +} + +bool MayaHydraMaterialAdapter::HasType(const TfToken& typeId) const +{ + return typeId == HdPrimTypeTokens->material; +} + +void MayaHydraMaterialAdapter::MarkDirty(HdDirtyBits dirtyBits) +{ + GetSceneProducer()->MarkSprimDirty(GetID(), dirtyBits); +} + +void MayaHydraMaterialAdapter::RemovePrim() +{ + if (!_isPopulated) { + return; + } + GetSceneProducer()->RemoveSprim(HdPrimTypeTokens->material, GetID()); + _isPopulated = false; +} + +void MayaHydraMaterialAdapter::Populate() +{ + TF_DEBUG(MAYAHYDRALIB_ADAPTER_GET) + .Msg("MayaHydraMaterialAdapter::Populate() - %s\n", GetID().GetText()); + if (_isPopulated) { + return; + } + GetSceneProducer()->InsertSprim(this, HdPrimTypeTokens->material, GetID(), HdMaterial::AllDirty); + _isPopulated = true; +} + +void MayaHydraMaterialAdapter::EnableXRayShadingMode(bool enable) +{ + _enableXRayShadingMode = enable; + MarkDirty(HdMaterial::DirtyParams); +} + +VtValue MayaHydraMaterialAdapter::GetMaterialResource() +{ + TF_DEBUG(MAYAHYDRALIB_ADAPTER_MATERIALS) + .Msg("MayaHydraMaterialAdapter::GetMaterialResource()\n"); + return GetPreviewMaterialResource(GetID()); +} + +VtValue MayaHydraMaterialAdapter::GetPreviewMaterialResource(const SdfPath& materialID) +{ + HdMaterialNetworkMap map; + HdMaterialNetwork network; + HdMaterialNode node; + node.path = materialID; + node.identifier + = UsdImagingTokens->UsdPreviewSurface; // We translate to a USD preview surface material + map.terminals.push_back(node.path); + for (const auto& it : MayaHydraMaterialNetworkConverter::GetPreviewShaderParams()) { + node.parameters.emplace(it.name, it.fallbackValue); + } + network.nodes.push_back(node); + map.map.emplace(HdMaterialTerminalTokens->surface, network); + return VtValue(map); +} + +/** + * \brief MayaHydraShadingEngineAdapter is used to handle the translation from a Maya shading engine + * to hydra. + */ +class MayaHydraShadingEngineAdapter : public MayaHydraMaterialAdapter +{ +public: + typedef MayaHydraMaterialNetworkConverter::PathToMobjMap PathToMobjMap; + + MayaHydraShadingEngineAdapter( + const SdfPath& id, + MayaHydraSceneProducer* producer, + const MObject& obj) + : MayaHydraMaterialAdapter(id, producer, obj) + , _surfaceShaderCallback(0) + { + _CacheNodeAndTypes(); + } + + ~MayaHydraShadingEngineAdapter() override + { + if (_surfaceShaderCallback != 0) { + MNodeMessage::removeCallback(_surfaceShaderCallback); + } + } + + void CreateCallbacks() override + { + TF_DEBUG(MAYAHYDRALIB_ADAPTER_CALLBACKS) + .Msg("Creating shading engine adapter callbacks for prim (%s).\n", GetID().GetText()); + + MStatus status; + auto obj = GetNode(); + auto id = MNodeMessage::addNodeDirtyCallback(obj, _DirtyMaterialParams, this, &status); + if (ARCH_LIKELY(status)) { + AddCallback(id); + } + _CreateSurfaceMaterialCallback(); + MayaHydraAdapter::CreateCallbacks(); + } + + void Populate() override + { + MayaHydraMaterialAdapter::Populate(); +#ifdef MAYAHYDRALIB_OIT_ENABLED + _isTranslucent = IsTranslucent(); +#endif + } + +private: + static void _DirtyMaterialParams(MObject& /*node*/, void* clientData) + { + auto* adapter = reinterpret_cast(clientData); + adapter->_CreateSurfaceMaterialCallback(); + adapter->MarkDirty(HdMaterial::AllDirty); + } + + static void _DirtyShaderParams(MObject& /*node*/, void* clientData) + { + auto* adapter = reinterpret_cast(clientData); + adapter->MarkDirty(HdMaterial::AllDirty); + if (adapter->GetSceneProducer()->IsHdSt()) { + adapter->GetSceneProducer()->MaterialTagChanged(adapter->GetID()); + } + } + + void _CacheNodeAndTypes() + { + _surfaceShader = MObject::kNullObj; + _surfaceShaderType = _emptyToken; + MStatus status; + MFnDependencyNode node(GetNode(), &status); + if (ARCH_UNLIKELY(!status)) { + return; + } + + auto p = node.findPlug(MayaAttrs::shadingEngine::surfaceShader, true); + MPlugArray conns; + p.connectedTo(conns, true, false); + if (conns.length() > 0) { + _surfaceShader = conns[0].node(); + MFnDependencyNode surfaceNode(_surfaceShader, &status); + if (ARCH_UNLIKELY(!status)) { + return; + } + _surfaceShaderType = TfToken(surfaceNode.typeName().asChar()); + TF_DEBUG(MAYAHYDRALIB_ADAPTER_MATERIALS) + .Msg( + "Found surfaceShader %s[%s]\n", + surfaceNode.name().asChar(), + _surfaceShaderType.GetText()); + } + } + + void _CreateSurfaceMaterialCallback() + { + _CacheNodeAndTypes(); + if (_surfaceShaderCallback != 0) { + MNodeMessage::removeCallback(_surfaceShaderCallback); + _surfaceShaderCallback = 0; + } + + if (_surfaceShader != MObject::kNullObj) { + _surfaceShaderCallback + = MNodeMessage::addNodeDirtyCallback(_surfaceShader, _DirtyShaderParams, this); + } + } + + VtValue GetMaterialResource() override + { + TF_DEBUG(MAYAHYDRALIB_ADAPTER_MATERIALS) + .Msg("MayaHydraShadingEngineAdapter::GetMaterialResource(): %s\n", GetID().GetText()); + MayaHydraMaterialNetworkConverter::MayaHydraMaterialNetworkConverterInit initStruct( + GetID(), _enableXRayShadingMode, &_materialPathToMobj); + + MayaHydraMaterialNetworkConverter converter(initStruct); + if (!converter.GetMaterial(_surfaceShader)) { + return GetPreviewMaterialResource(GetID()); + } + + HdMaterialNetworkMap materialNetworkMap; + materialNetworkMap.map[HdMaterialTerminalTokens->surface] = initStruct._materialNetwork; + if (!initStruct._materialNetwork.nodes.empty()) { + materialNetworkMap.terminals.push_back(initStruct._materialNetwork.nodes.back().path); + } + + // HdMaterialNetwork displacementNetwork; + // materialNetworkMap.map[HdMaterialTerminalTokens->displacement] = + // displacementNetwork; + + return VtValue(materialNetworkMap); + }; + +#ifdef MAYAHYDRALIB_OIT_ENABLED + bool UpdateMaterialTag() override + { + if (IsTranslucent() != _isTranslucent) { + _isTranslucent = !_isTranslucent; + return true; + } + return false; + } + + bool IsTranslucent() + { + if (_surfaceShaderType == MayaHydraAdapterTokens->usdPreviewSurface + || _surfaceShaderType == MayaHydraAdapterTokens->pxrUsdPreviewSurface) { + MFnDependencyNode node(_surfaceShader); + const auto plug = node.findPlug(MayaHydraAdapterTokens->opacity.GetText(), true); + if (!plug.isNull() && (plug.asFloat() < 1.0f || plug.isConnected())) { + return true; + } + } + return false; + } + +#endif // MAYAHYDRALIB_OIT_ENABLED + + PathToMobjMap _materialPathToMobj; + + MObject _surfaceShader; + TfToken _surfaceShaderType; + // So they live long enough + + MCallbackId _surfaceShaderCallback; +#ifdef MAYAHYDRALIB_OIT_ENABLED + bool _isTranslucent = false; +#endif +}; + +TF_REGISTRY_FUNCTION(TfType) +{ + TfType::Define>(); + TfType::Define>(); +} + +TF_REGISTRY_FUNCTION_WITH_TAG(MayaHydraAdapterRegistry, shadingEngine) +{ + MayaHydraAdapterRegistry::RegisterMaterialAdapter( + TfToken("shadingEngine"), + [](const SdfPath& id, + MayaHydraSceneProducer* producer, + const MObject& obj) -> MayaHydraMaterialAdapterPtr { + return MayaHydraMaterialAdapterPtr( + new MayaHydraShadingEngineAdapter(id, producer, obj)); + }); +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/mayaHydra/hydraExtensions/adapters/materialAdapter.h b/lib/mayaHydra/hydraExtensions/adapters/materialAdapter.h new file mode 100644 index 0000000000..19e9062f7f --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/adapters/materialAdapter.h @@ -0,0 +1,78 @@ +// +// 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. +// +#ifndef MAYAHYDRALIB_MATERIAL_ADAPTER_H +#define MAYAHYDRALIB_MATERIAL_ADAPTER_H + +#include + +#include + +PXR_NAMESPACE_OPEN_SCOPE + +class MayaHydraSceneProducer; + +/** + * \brief MayaHydraMaterialAdapter is used to handle the translation from a Maya material to hydra. + * If you are looking for how we translate the Maya shaders to hydra and how we do the parameters + * mapping, please see MayaHydraMaterialNetworkConverter::initialize(). + */ +class MayaHydraMaterialAdapter : public MayaHydraAdapter +{ +public: + MAYAHYDRALIB_API + MayaHydraMaterialAdapter( + const SdfPath& id, + MayaHydraSceneProducer* producer, + const MObject& node); + MAYAHYDRALIB_API + virtual ~MayaHydraMaterialAdapter() = default; + + MAYAHYDRALIB_API + bool IsSupported() const override; + + MAYAHYDRALIB_API + bool HasType(const TfToken& typeId) const override; + + MAYAHYDRALIB_API + void MarkDirty(HdDirtyBits dirtyBits) override; + MAYAHYDRALIB_API + void RemovePrim() override; + MAYAHYDRALIB_API + void Populate() override; + + MAYAHYDRALIB_API + void EnableXRayShadingMode(bool enable); + + MAYAHYDRALIB_API + virtual VtValue GetMaterialResource(); + + /// \brief Updates the material tag for the material. + /// \return True if the material tag have changed, false otherwise. + virtual bool UpdateMaterialTag() { return false; } + + MAYAHYDRALIB_API + static VtValue GetPreviewMaterialResource(const SdfPath& materialID); + +protected: + /// Are we in viewport XRay shading mode ? + bool _enableXRayShadingMode = false; +}; + +using MayaHydraMaterialAdapterPtr = std::shared_ptr; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // MAYAHYDRALIB_MATERIAL_ADAPTER_H diff --git a/lib/mayaHydra/hydraExtensions/adapters/materialNetworkConverter.cpp b/lib/mayaHydra/hydraExtensions/adapters/materialNetworkConverter.cpp new file mode 100644 index 0000000000..db5ae71361 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/adapters/materialNetworkConverter.cpp @@ -0,0 +1,1029 @@ +// +// 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. +// +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +#include "materialNetworkConverter.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE +// Bring the MayaHydra namespace into scope. +// The following code currently lives inside the pxr namespace, but it would make more sense to +// have it inside the MayaHydra namespace. This using statement allows us to use MayaHydra symbols +// from within the pxr namespace as if we were in the MayaHydra namespace. +// Remove this once the code has been moved to the MayaHydra namespace. +using namespace MayaHydra; + +/* This file contains how we translate the Maya shaders (Blinn, Lambert, Standard Surface etc.) to + hydra and how we do the parameters mapping. See MayaHydraMaterialNetworkConverter::initialize() + for that purpose. +*/ + +namespace { + +// Print to the output window the type and value of each parameter from the std::map +void DebugPrintParameters(const std::map& params) +{ + cout << "\n"; // Add a line + // Print all parameters types and values + for (auto param : params) { + std::string valueAsString = ConvertVtValueToString(param.second); + cout << "Material parameters : (" << param.first.GetText() << " - " << valueAsString.c_str() + << ")\n"; + } +} + +const TfToken _useSpecularWorkflowToken("useSpecularWorkflow"); +const TfToken _specularColorToken("specularColor"); +const TfToken _opacityToken("opacity"); + +constexpr float defaultTextureMemoryLimit = 1e8f; +constexpr float xRayOpacityValue + = 0.3f; // Hardcoded value taken from OGSMayaRenderItem::UpdateExtraOpacityParam + +// Lists of preferred shader output names, from SdfValueTypeName to list of +// preferred output names for that type. The list that has an empty token for +// SdfValueTypeName is used as a default. +const std::vector>> preferredOutputNamesByType { + { SdfValueTypeNames->Float3, + { MayaHydraAdapterTokens->result, + MayaHydraAdapterTokens->out, + MayaHydraAdapterTokens->output, + MayaHydraAdapterTokens->rgb, + MayaHydraAdapterTokens->xyz } }, + { SdfValueTypeNames->Float2, + { MayaHydraAdapterTokens->result, + MayaHydraAdapterTokens->out, + MayaHydraAdapterTokens->output, + MayaHydraAdapterTokens->st, + MayaHydraAdapterTokens->uv } }, + { SdfValueTypeNames->Float, + { MayaHydraAdapterTokens->result, + MayaHydraAdapterTokens->out, + MayaHydraAdapterTokens->output, + MayaHydraAdapterTokens->r, + MayaHydraAdapterTokens->x } } +}; + +// Default set of preferred output names, if type not in +// preferredOutputNamesByType +const std::vector defaultPreferredOutputNames { MayaHydraAdapterTokens->result, + MayaHydraAdapterTokens->out, + MayaHydraAdapterTokens->output }; + +SdfValueTypeName GetStandardTypeName(SdfValueTypeName type) +{ + // Will map, ie, Vector3f to Float3, TexCoord2f to Float2 + return SdfGetValueTypeNameForValue(type.GetDefaultValue()); +} + +const std::vector& +GetPreferredOutputNames(SdfValueTypeName type, bool useStandardType = true) +{ + for (const auto& typeAndNames : preferredOutputNamesByType) { + if (typeAndNames.first == type) { + return typeAndNames.second; + } + } + + if (useStandardType) { + // If we were given, ie, Vector3f, check to see if there's an entry for + // Float3 + auto standardType = GetStandardTypeName(type); + if (type != standardType) { + return GetPreferredOutputNames(standardType, false); + } + } + return defaultPreferredOutputNames; +} + +TfToken GetOutputName(const HdMaterialNode& material, SdfValueTypeName type) +{ + TF_DEBUG(MAYAHYDRALIB_ADAPTER_MATERIALS) + .Msg( + "GetOutputName(%s - %s, %s)\n", + material.path.GetText(), + material.identifier.GetText(), + type.GetAsToken().GetText()); + auto& shaderReg = SdrRegistry::GetInstance(); + if (SdrShaderNodeConstPtr sdrNode = shaderReg.GetShaderNodeByIdentifier(material.identifier)) { + // First, get the list off all outputs of the correct type. + std::vector validOutputs; + auto outputNames = sdrNode->GetOutputNames(); + + auto addMatchingOutputs = [&](SdfValueTypeName matchingType) { + for (const auto& outName : outputNames) { + auto* sdrInfo = sdrNode->GetShaderOutput(outName); + if (sdrInfo && sdrInfo->GetTypeAsSdfType().first == matchingType) { + validOutputs.push_back(outName); + } + } + }; + + addMatchingOutputs(type); + if (validOutputs.empty()) { + auto standardType = GetStandardTypeName(type); + if (standardType != type) { + addMatchingOutputs(standardType); + } + } + + // If there's only one, use that + if (validOutputs.size() == 1) { + TF_DEBUG(MAYAHYDRALIB_ADAPTER_MATERIALS) + .Msg( + " found exactly one output of correct type in " + "registry: " + "%s\n", + validOutputs[0].GetText()); + return validOutputs[0]; + } + + // Then see if any preferred names are found + if (!validOutputs.empty()) { + const auto& preferredNames = GetPreferredOutputNames(type); + for (const auto& preferredName : preferredNames) { + if (std::find(validOutputs.begin(), validOutputs.end(), preferredName) + != validOutputs.end()) { + TF_DEBUG(MAYAHYDRALIB_ADAPTER_MATERIALS) + .Msg( + " found preferred name of correct type in " + "registry: %s\n", + preferredName.GetText()); + return preferredName; + } + } + // No preferred names were found, use the first valid name + TF_DEBUG(MAYAHYDRALIB_ADAPTER_MATERIALS) + .Msg( + " found no preferred names of correct type in " + "registry, returning first valid name: %s\n", + validOutputs[0].GetText()); + return validOutputs[0]; + } + } + + // We either couldn't find the entry in the SdrRegistry, or there were + // no outputs of the right type - make a guess, use the first preferred + // name + const auto& preferredNames = GetPreferredOutputNames(type); + if (TF_VERIFY(!preferredNames.empty())) { + TF_DEBUG(MAYAHYDRALIB_ADAPTER_MATERIALS) + .Msg( + " found no valid entries in registry, returning guess: " + "%s\n", + preferredNames[0].GetText()); + return preferredNames[0]; + } + + // We should never get here - preferredNames should never be empty! + return MayaHydraAdapterTokens->result; +} + +class MayaHydraGenericMaterialAttrConverter : public MayaHydraMaterialAttrConverter +{ +public: + /// Generic attr converter has no fixed type + SdfValueTypeName GetType() override { return SdfValueTypeName(); } + + TfToken GetPlugName(const TfToken& usdName) override { return usdName; } + + VtValue GetValue( + MFnDependencyNode& node, + const TfToken& paramName, + const SdfValueTypeName& type, + const VtValue* fallback = nullptr, + MPlugArray* outPlug = nullptr) override + { + return MayaHydraMaterialNetworkConverter::ConvertMayaAttrToValue( + node, paramName.GetText(), type, fallback, outPlug); + } +}; + +class MayaHydraNewDefaultMaterialAttrConverter : public MayaHydraMaterialAttrConverter +{ +public: + template + MayaHydraNewDefaultMaterialAttrConverter(const T& defaultValue) + : _defaultValue(defaultValue) + { + } + + SdfValueTypeName GetType() override { return SdfGetValueTypeNameForValue(_defaultValue); } + + TfToken GetPlugName(const TfToken& usdName) override { return usdName; } + + VtValue GetValue( + MFnDependencyNode& node, + const TfToken& paramName, + const SdfValueTypeName& type, + const VtValue* fallback = nullptr, + MPlugArray* outPlug = nullptr) override + { + return MayaHydraMaterialNetworkConverter::ConvertMayaAttrToValue( + node, paramName.GetText(), type, &_defaultValue, outPlug); + } + + const VtValue _defaultValue; +}; + +class MayaHydraRemappingMaterialAttrConverter : public MayaHydraMaterialAttrConverter +{ +public: + MayaHydraRemappingMaterialAttrConverter( + const TfToken& remappedName, + const SdfValueTypeName& type) + : _remappedName(remappedName) + , _type(type) + { + } + + SdfValueTypeName GetType() override { return _type; } + + TfToken GetPlugName(const TfToken& usdName) override { return _remappedName; } + + VtValue GetValue( + MFnDependencyNode& node, + const TfToken& paramName, + const SdfValueTypeName& type, + const VtValue* fallback = nullptr, + MPlugArray* outPlug = nullptr) override + { + return MayaHydraMaterialNetworkConverter::ConvertMayaAttrToValue( + node, _remappedName.GetText(), type, fallback, outPlug); + } + +protected: + const TfToken& _remappedName; + const SdfValueTypeName& _type; +}; + +class MayaHydraScaledRemappingMaterialAttrConverter : public MayaHydraRemappingMaterialAttrConverter +{ +public: + MayaHydraScaledRemappingMaterialAttrConverter( + const TfToken& remappedName, + const TfToken& scaleName, + const SdfValueTypeName& type) + : MayaHydraRemappingMaterialAttrConverter(remappedName, type) + , _scaleName(scaleName) + { + } + + VtValue GetValue( + MFnDependencyNode& node, + const TfToken& paramName, + const SdfValueTypeName& type, + const VtValue* fallback = nullptr, + MPlugArray* outPlug = nullptr) override + { + return MayaHydraMaterialNetworkConverter::ConvertMayaAttrToScaledValue( + node, _remappedName.GetText(), _scaleName.GetText(), type, fallback, outPlug); + } + +private: + const TfToken& _scaleName; +}; + +class MayaHydraComputedMaterialAttrConverter : public MayaHydraMaterialAttrConverter +{ +public: + /// Classes which derive from this use some sort of calculation to get + /// the right value for the node, and so don't have a single plug that + /// can be hooked into a node network. + TfToken GetPlugName(const TfToken& usdName) override { return TfToken(); } +}; + +class MayaHydraFixedMaterialAttrConverter : public MayaHydraComputedMaterialAttrConverter +{ +public: + template + MayaHydraFixedMaterialAttrConverter(const T& value) + : _value(value) + { + } + + SdfValueTypeName GetType() override { return SdfGetValueTypeNameForValue(_value); } + + VtValue GetValue( + MFnDependencyNode& node, + const TfToken& paramName, + const SdfValueTypeName& type, + const VtValue* fallback = nullptr, + MPlugArray* outPlug = nullptr) override + { + return _value; + } + +private: + const VtValue _value; +}; + +class MayaHydraUvAttrConverter : public MayaHydraMaterialAttrConverter +{ +public: + MayaHydraUvAttrConverter() + : _value(GfVec2f(0.0f, 0.0f)) + { + } + + SdfValueTypeName GetType() override { return SdfValueTypeNames->TexCoord2f; } + + TfToken GetPlugName(const TfToken& usdName) override { return MayaHydraAdapterTokens->uvCoord; } + + VtValue GetValue( + MFnDependencyNode& node, + const TfToken& paramName, + const SdfValueTypeName& type, + const VtValue* fallback = nullptr, + MPlugArray* outPlug = nullptr) override + { + if (outPlug) { + // Find a connected place2dTexture node, and set that as the + // outPlug, so that the place2dTexture node will trigger + // creation of a UsdPrimvarReader_float2 + MStatus status; + MPlugArray connections; + status = node.getConnections(connections); + if (status) { + for (size_t i = 0, len = connections.length(); i < len; ++i) { + MPlug source = connections[i].source(); + if (source.isNull()) { + continue; + } + if (source.node().hasFn(MFn::kPlace2dTexture)) { + outPlug->append(connections[i]); + break; + } + } + } + } + return _value; + } + +private: + const VtValue _value; +}; // namespace + +class MayaHydraCosinePowerMaterialAttrConverter : public MayaHydraComputedMaterialAttrConverter +{ +public: + SdfValueTypeName GetType() override { return SdfValueTypeNames->Float; } + + VtValue GetValue( + MFnDependencyNode& node, + const TfToken& paramName, + const SdfValueTypeName& type, + const VtValue* fallback = nullptr, + MPlugArray* outPlug = nullptr) override + { + VtValue cosinePower = MayaHydraMaterialNetworkConverter::ConvertMayaAttrToValue( + node, "cosinePower", type, nullptr, outPlug); + if (!cosinePower.IsHolding()) { + if (fallback) { + return *fallback; + } + TF_DEBUG(MAYAHYDRALIB_ADAPTER_GET) + .Msg("MayaHydraCosinePowerMaterialAttrConverter::GetValue(): " + "No float plug found with name: cosinePower and no " + "fallback given"); + return VtValue(); + } else { + // In the maya UI, cosinePower goes from 2.0 to 100.0 ... + // so for now, we just do a dumb linear mapping from that onto + // 1 to 0 for roughness + float roughnessFloat = 1.0f - (cosinePower.UncheckedGet() - 2.0f) / 98.0f; + return VtValue(roughnessFloat); + } + } +}; + +class MayaHydraTransmissionMaterialAttrConverter : public MayaHydraComputedMaterialAttrConverter +{ +public: + SdfValueTypeName GetType() override { return SdfValueTypeNames->Float; } + + VtValue GetValue( + MFnDependencyNode& node, + const TfToken& paramName, + const SdfValueTypeName& type, + const VtValue* fallback = nullptr, + MPlugArray* outPlug = nullptr) override + { + VtValue transmission = MayaHydraMaterialNetworkConverter::ConvertMayaAttrToValue( + node, "transmission", type, nullptr, outPlug); + // Combine transmission and Geometry-->Opacity R,G and B attributes + VtValue geometryOpacityR = MayaHydraMaterialNetworkConverter::ConvertMayaAttrToValue( + node, "opacityR", type, nullptr, outPlug); + VtValue geometryOpacityG = MayaHydraMaterialNetworkConverter::ConvertMayaAttrToValue( + node, "opacityG", type, nullptr, outPlug); + VtValue geometryOpacityB = MayaHydraMaterialNetworkConverter::ConvertMayaAttrToValue( + node, "opacityB", type, nullptr, outPlug); + + if (!transmission.IsHolding()) { + if (fallback) { + return *fallback; + } + TF_DEBUG(MAYAHYDRALIB_ADAPTER_GET) + .Msg("MayaHydraTransmissionMaterialAttrConverter::GetValue(): " + "No float plug found with name: transmission and no " + "fallback given"); + return VtValue(); + } + + float val = 1.0f - transmission.UncheckedGet(); + if (val < 1.0e-4f) { + // Clamp lower value as an opacity of 0.0 in hydra makes the object fully transparent, + // but in VP2 we still see the specular highlight if any, avoiding 0.0 leads to the same + // effect in hydra. + val = 1.0e-4f; + } + + float fGeometryOpacity = 1.0f; + if (geometryOpacityR.IsHolding() && geometryOpacityG.IsHolding() + && geometryOpacityB.IsHolding()) { + // Take the average as there is only 1 parameter in hydra + fGeometryOpacity = (1.0f / 3.0f) + * (geometryOpacityR.UncheckedGet() + geometryOpacityG.UncheckedGet() + + geometryOpacityB.UncheckedGet()); + } + + val *= fGeometryOpacity; + + return VtValue(val); + } +}; + +class MayaHydraFilenameMaterialAttrConverter : public MayaHydraComputedMaterialAttrConverter +{ +public: + SdfValueTypeName GetType() override { return SdfValueTypeNames->Asset; } + + VtValue GetValue( + MFnDependencyNode& node, + const TfToken& paramName, + const SdfValueTypeName& type, + const VtValue* fallback = nullptr, + MPlugArray* outPlug = nullptr) override + { + auto path = GetFileTexturePath(node); + return VtValue(SdfAssetPath(path.GetText(), path.GetText())); + } +}; + +class MayaHydraWrapMaterialAttrConverter : public MayaHydraComputedMaterialAttrConverter +{ +public: + MayaHydraWrapMaterialAttrConverter(MObject& wrapAttr, MObject& mirrorAttr) + : _wrapAttr(wrapAttr) + , _mirrorAttr(mirrorAttr) + { + } + + SdfValueTypeName GetType() override { return SdfValueTypeNames->Token; } + + VtValue GetValue( + MFnDependencyNode& node, + const TfToken& paramName, + const SdfValueTypeName& type, + const VtValue* fallback = nullptr, + MPlugArray* outPlug = nullptr) override + { + if (node.findPlug(_wrapAttr, true).asBool()) { + if (node.findPlug(_mirrorAttr, true).asBool()) { + return VtValue(UsdHydraTokens->mirror); + } else { + return VtValue(UsdHydraTokens->repeat); + } + } else { + return VtValue(UsdHydraTokens->clamp); + } + } + +private: + MObject _wrapAttr; + MObject _mirrorAttr; +}; + +auto _genericAttrConverter = std::make_shared(); + +typedef std::unordered_map + NameToNodeConverterMap; + +/// _nodeConverters contains how we translate from a Maya shader to hydra and the parameters mapping +/// we use. +NameToNodeConverterMap _nodeConverters; + +} // namespace + +/*static*/ +void MayaHydraMaterialNetworkConverter::initialize() +{ + // Define different converters for translating from specific Maya attributes types to hydra + auto colorConverter = std::make_shared( + MayaHydraAdapterTokens->color, + MayaHydraAdapterTokens->diffuse, + SdfValueTypeNames->Vector3f); + auto incandescenceConverter = std::make_shared( + MayaHydraAdapterTokens->incandescence, SdfValueTypeNames->Vector3f); + auto eccentricityConverter = std::make_shared( + MayaHydraAdapterTokens->eccentricity, SdfValueTypeNames->Float); + auto uvConverter = std::make_shared(); + + // Standard surface: + auto baseColorConverter = std::make_shared( + MayaHydraAdapterTokens->baseColor, + MayaHydraAdapterTokens->base, + SdfValueTypeNames->Vector3f); + auto emissionColorConverter = std::make_shared( + MayaHydraAdapterTokens->emissionColor, + MayaHydraAdapterTokens->emission, + SdfValueTypeNames->Vector3f); + auto specularColorConverter = std::make_shared( + MayaHydraAdapterTokens->specularColor, + MayaHydraAdapterTokens->specular, + SdfValueTypeNames->Vector3f); + auto specularIORConverter = std::make_shared( + MayaHydraAdapterTokens->specularIOR, SdfValueTypeNames->Float); + auto specularRoughnessConverter = std::make_shared( + MayaHydraAdapterTokens->specularRoughness, SdfValueTypeNames->Float); + auto metallicConverter = std::make_shared( + MayaHydraAdapterTokens->metalness, SdfValueTypeNames->Float); + auto coatConverter = std::make_shared( + MayaHydraAdapterTokens->coat, SdfValueTypeNames->Float); + auto coatRoughnessConverter = std::make_shared( + MayaHydraAdapterTokens->coatRoughness, SdfValueTypeNames->Float); + auto transmissionToOpacity = std::make_shared(); + + auto fixedZeroFloat = std::make_shared(0.0f); + auto fixedOneFloat = std::make_shared(1.0f); + auto fixedZeroInt = std::make_shared(0); + auto fixedOneInt = std::make_shared(1); + auto fixedStToken + = std::make_shared(MayaHydraAdapterTokens->st); + + auto cosinePowerToRoughness = std::make_shared(); + auto filenameConverter = std::make_shared(); + + auto wrapUConverter = std::make_shared( + MayaAttrs::file::wrapU, MayaAttrs::file::mirrorU); + auto wrapVConverter = std::make_shared( + MayaAttrs::file::wrapV, MayaAttrs::file::mirrorV); + + auto textureMemoryConverter + = std::make_shared(defaultTextureMemoryLimit); + + // In the following code we define how we translate from a Maya shader to hydra and how we do + // the parameters mapping + _nodeConverters = { + { MayaHydraAdapterTokens->usdPreviewSurface, { UsdImagingTokens->UsdPreviewSurface, {} } }, + { MayaHydraAdapterTokens->pxrUsdPreviewSurface, + { UsdImagingTokens->UsdPreviewSurface, {} } }, + { MayaHydraAdapterTokens->lambert, + { UsdImagingTokens + ->UsdPreviewSurface, // Maya Lambert shader translated to a UsdPreviewSurface with + // the following UsdPreviewSurface parameters mapped + { + { MayaHydraAdapterTokens->diffuseColor, colorConverter }, + { MayaHydraAdapterTokens->emissiveColor, incandescenceConverter }, + { MayaHydraAdapterTokens->roughness, fixedOneFloat }, + { MayaHydraAdapterTokens->metallic, fixedZeroFloat }, + { MayaHydraAdapterTokens->useSpecularWorkflow, fixedZeroInt }, + } } }, + { MayaHydraAdapterTokens->blinn, + { UsdImagingTokens + ->UsdPreviewSurface, // Maya Blinn shader translated to a UsdPreviewSurface with the + // following UsdPreviewSurface parameters mapped + { + { MayaHydraAdapterTokens->diffuseColor, colorConverter }, + { MayaHydraAdapterTokens->emissiveColor, incandescenceConverter }, + { MayaHydraAdapterTokens->roughness, eccentricityConverter }, + { MayaHydraAdapterTokens->metallic, fixedZeroFloat }, + { MayaHydraAdapterTokens->useSpecularWorkflow, fixedOneInt }, + } } }, + { MayaHydraAdapterTokens->phong, + { UsdImagingTokens + ->UsdPreviewSurface, // Maya Phong shader translated to a UsdPreviewSurface with the + // following UsdPreviewSurface parameters mapped + { + { MayaHydraAdapterTokens->diffuseColor, colorConverter }, + { MayaHydraAdapterTokens->emissiveColor, incandescenceConverter }, + { MayaHydraAdapterTokens->roughness, cosinePowerToRoughness }, + { MayaHydraAdapterTokens->metallic, fixedZeroFloat }, + { MayaHydraAdapterTokens->useSpecularWorkflow, fixedOneInt }, + } } }, + { MayaHydraAdapterTokens->standardSurface, + { UsdImagingTokens->UsdPreviewSurface, // Maya Standard surface shader translated to a + // UsdPreviewSurface with the following + // UsdPreviewSurface parameters mapped + { + { MayaHydraAdapterTokens->diffuseColor, baseColorConverter }, + { MayaHydraAdapterTokens->emissiveColor, emissionColorConverter }, + { MayaHydraAdapterTokens->specularColor, specularColorConverter }, + { MayaHydraAdapterTokens->ior, specularIORConverter }, + { MayaHydraAdapterTokens->roughness, specularRoughnessConverter }, + { MayaHydraAdapterTokens->clearcoat, coatConverter }, + { MayaHydraAdapterTokens->clearcoatRoughness, coatRoughnessConverter }, + { MayaHydraAdapterTokens->opacity, transmissionToOpacity }, + { MayaHydraAdapterTokens->metallic, metallicConverter }, + } } }, + { MayaHydraAdapterTokens->file, + { UsdImagingTokens->UsdUVTexture, // Maya file translated to a UsdUVTexture with the + // following UsdUVTexture parameters mapped + { + { MayaHydraAdapterTokens->file, filenameConverter }, + { MayaHydraAdapterTokens->st, uvConverter }, + { UsdHydraTokens->wrapS, wrapUConverter }, + { UsdHydraTokens->wrapT, wrapVConverter }, + { UsdHydraTokens->textureMemory, textureMemoryConverter }, + } } }, + { MayaHydraAdapterTokens + ->place2dTexture, // Maya place2dTexture translated to a UsdPrimvarReader_float2 with + // the following UsdPrimvarReader_float2 parameters mapped + { UsdImagingTokens->UsdPrimvarReader_float2, + { + { MayaHydraAdapterTokens->varname, fixedStToken }, + } } }, + }; +} + +MayaHydraMaterialNodeConverter::MayaHydraMaterialNodeConverter( + const TfToken& identifier, + const NameToAttrConverterMap& attrConverters) + : _attrConverters(attrConverters) + , _identifier(identifier) +{ +} + +MayaHydraMaterialAttrConverter::RefPtr +MayaHydraMaterialNodeConverter::GetAttrConverter(const TfToken& paramName) +{ + auto it = _attrConverters.find(paramName); + if (it == _attrConverters.end()) { + return _genericAttrConverter; + } + return it->second; +} + +MayaHydraMaterialNodeConverter* +MayaHydraMaterialNodeConverter::GetNodeConverter(const TfToken& nodeType) +{ + auto it = _nodeConverters.find(nodeType); + if (it == _nodeConverters.end()) { + return nullptr; + } + return &(it->second); +} + +MayaHydraShaderParam::MayaHydraShaderParam( + const TfToken& name, + const VtValue& value, + const SdfValueTypeName& type) + : name(name) + , fallbackValue(value) + , type(type) +{ +} + +MayaHydraMaterialNetworkConverter::MayaHydraMaterialNetworkConverter( + MayaHydraMaterialNetworkConverterInit& init) + : _network(init._materialNetwork) + , _prefix(init._prefix) + , _pathToMobj(init._pathToMobj) + , _enableXRayShadingMode(init._enableXRayShadingMode) +{ +} + +HdMaterialNode* MayaHydraMaterialNetworkConverter::GetMaterial(const MObject& mayaNode) +{ + MStatus status; + MFnDependencyNode node(mayaNode, &status); + if (ARCH_UNLIKELY(!status)) { + return nullptr; + } + const auto* chr = node.name().asChar(); + if (chr == nullptr || chr[0] == '\0') { + return nullptr; + } + TF_DEBUG(MAYAHYDRALIB_ADAPTER_MATERIALS) + .Msg("MayaHydraMaterialNetworkConverter::GetMaterial(node=%s)\n", chr); + std::string nodeNameString(chr); + SanitizeNameForSdfPath(nodeNameString); + const auto materialPath = _prefix.AppendChild(TfToken(nodeNameString)); + + auto findResult = std::find_if( + _network.nodes.begin(), + _network.nodes.end(), + [&materialPath](const HdMaterialNode& m) -> bool { return m.path == materialPath; }); + if (findResult != _network.nodes.end()) { + return &(*findResult); + } + + auto* nodeConverter + = MayaHydraMaterialNodeConverter::GetNodeConverter(TfToken(node.typeName().asChar())); + if (!nodeConverter) { + return nullptr; + } + HdMaterialNode material {}; + material.path = materialPath; + material.identifier = nodeConverter->GetIdentifier(); + if (material.identifier == UsdImagingTokens->UsdPreviewSurface) { + for (const auto& param : MayaHydraMaterialNetworkConverter::GetPreviewShaderParams()) { + this->ConvertParameter( + node, *nodeConverter, material, param.name, param.type, ¶m.fallbackValue); + } + + // If we are using a specular color which is not white, the UsdPreviewsurface specular + // workflow must be enabled to use the specular color which is done by setting the + // UsdPreviewSurface param "useSpecularWork" to 1 + { + const auto it = material.parameters.find(_specularColorToken); + if (it != material.parameters.cend()) { + const VtValue& specColorVal = it->second; + if (!specColorVal.IsEmpty() + && specColorVal.UncheckedGet() != GfVec3f(1, 1, 1)) { + material.parameters[_useSpecularWorkflowToken] = VtValue(1); + } + } + } + + if (_enableXRayShadingMode) { + // Multiply current opacity by hardcoded xRayOpacityValue + const auto it = material.parameters.find(_opacityToken); + if (it != material.parameters.cend()) { + const VtValue& opacityVal = it->second; + if (!opacityVal.IsEmpty()) { + material.parameters[_opacityToken] + = VtValue(opacityVal.UncheckedGet() * xRayOpacityValue); + } + } + } + + if (TfDebug::IsEnabled(MAYAHYDRALIB_ADAPTER_MATERIALS_PRINT_PARAMETERS_VALUES)) { + // DEBUG to print material parameters type and value to the output window + DebugPrintParameters(material.parameters); + } + + } else { + for (auto& nameAttrConverterPair : nodeConverter->GetAttrConverters()) { + auto& name = nameAttrConverterPair.first; + auto& attrConverter = nameAttrConverterPair.second; + this->ConvertParameter(node, *nodeConverter, material, name, attrConverter->GetType()); + + if (name == MayaHydraAdapterTokens->varname + && (material.identifier == UsdImagingTokens->UsdPrimvarReader_float + || material.identifier == UsdImagingTokens->UsdPrimvarReader_float2 + || material.identifier == UsdImagingTokens->UsdPrimvarReader_float3 + || material.identifier == UsdImagingTokens->UsdPrimvarReader_float4)) { + VtValue& primVarName = material.parameters[name]; + if (TF_VERIFY(primVarName.IsHolding())) { + AddPrimvar(primVarName.UncheckedGet()); + } else { + TF_WARN("Converter identified as a UsdPrimvarReader*, but " + "it's " + "varname did not hold a TfToken"); + } + } + } + } + if (_pathToMobj) { + (*_pathToMobj)[materialPath] = mayaNode; + } + _network.nodes.push_back(material); + return &_network.nodes.back(); +} + +void MayaHydraMaterialNetworkConverter::AddPrimvar(const TfToken& primvar) +{ + if (std::find(_network.primvars.begin(), _network.primvars.end(), primvar) + == _network.primvars.end()) { + _network.primvars.push_back(primvar); + } +} + +void MayaHydraMaterialNetworkConverter::ConvertParameter( + MFnDependencyNode& node, + MayaHydraMaterialNodeConverter& nodeConverter, + HdMaterialNode& material, + const TfToken& paramName, + const SdfValueTypeName& type, + const VtValue* fallback) +{ + MPlugArray plugArray; + VtValue val; + TF_DEBUG(MAYAHYDRALIB_ADAPTER_MATERIALS).Msg("ConvertParameter(%s)\n", paramName.GetText()); + + auto attrConverter = nodeConverter.GetAttrConverter(paramName); + if (attrConverter) { + // Using an array of MPlug in plugArray, as some settings may have 2 or more attributes that + // should be taken into consideration for connections. For example : specular has a specular + // color and specular weight attributes, both should be considered. So after calling + // attrConverter->GetValue, the plugArray will contain all dependents MPlug for connections. + val = attrConverter->GetValue(node, paramName, type, fallback, &plugArray); + } else if (fallback) { + val = *fallback; + } else { + TF_DEBUG(MAYAHYDRALIB_ADAPTER_GET) + .Msg( + "MayaHydraMaterialNetworkConverter::ConvertParameter(): " + "No attrConverter found with name: %s and no fallback " + "given", + paramName.GetText()); + val = VtValue(); + } + + material.parameters[paramName] = val; + + /*plugArray contains all dependents MPlug we should consider for connections. + Usually it contains 1 or 2 MPlug (2 is when dealing with a weighted attribute), + it can have more than 2 when dealing with the transmission which is combined with opacityR, + opacityG and opacityB attributes. But a limitation we have at this time is that if both the + color and the weight attributes have a connection, one of both connections will be ignored by + hydra as we have only one parameter in the UsdPreviewSurface which will have both connections + and hydra only considers the last connection added. There is no blending node we could use with + the UsdPreviewSurface. We would need the StandardSurface to be in hydra or use MaterialX to + build a shading network to handle this case with a multiply node for example. + */ + for (auto plug : plugArray) { + if (plug.isNull()) { + return; + } + + MPlug source = plug.source(); + if (!source.isNull()) { + auto* sourceMat = GetMaterial(source.node()); + if (!sourceMat) { + return; + } + const auto& sourceMatPath = sourceMat->path; + if (sourceMatPath.IsEmpty()) { + return; + } + HdMaterialRelationship rel; + rel.inputId = sourceMatPath; + rel.inputName = GetOutputName(*sourceMat, type); + rel.outputId = material.path; + rel.outputName = paramName; + _network.relationships.push_back(rel); + } + } +} + +VtValue MayaHydraMaterialNetworkConverter::ConvertMayaAttrToValue( + MFnDependencyNode& node, + const MString& plugName, + const SdfValueTypeName& type, + const VtValue* fallback, + MPlugArray* outPlug) +{ + MStatus status; + auto p = node.findPlug(plugName, true, &status); + VtValue val; + if (status) { + if (outPlug) { + outPlug->append(p); + } + val = ConvertPlugToValue(p, type, fallback); + } else if (fallback) { + val = *fallback; + } else { + TF_DEBUG(MAYAHYDRALIB_ADAPTER_GET) + .Msg( + "MayaHydraMaterialNetworkConverter::ConvertMayaAttrToValue(): " + "No plug found with name: %s and no fallback given", + plugName.asChar()); + val = VtValue(); + } + return val; +} + +VtValue MayaHydraMaterialNetworkConverter::ConvertMayaAttrToScaledValue( + MFnDependencyNode& node, + const MString& plugName, + const MString& scaleName, + const SdfValueTypeName& type, + const VtValue* fallback, + MPlugArray* outPlug) +{ + VtValue val = ConvertMayaAttrToValue(node, plugName, type, fallback, outPlug); + MStatus status; + auto p = node.findPlug(scaleName, true, &status); + if (status) { + if (!p.isNull() && outPlug) { + outPlug->append(p); + } + if (type.GetType() == SdfValueTypeNames->Vector3f.GetType()) { + val = val.UncheckedGet() * p.asFloat(); + } else if (type == SdfValueTypeNames->Float) { + val = val.UncheckedGet() * p.asFloat(); + } else if (type.GetType() == SdfValueTypeNames->Float2.GetType()) { + val = val.UncheckedGet() * p.asFloat(); + } + } else { + TF_DEBUG(MAYAHYDRALIB_ADAPTER_GET) + .Msg( + "MayaHydraMaterialNetworkConverter::ConvertMayaAttrToScaledValue(): " + "No scaling plug found with name: %s", + scaleName.asChar()); + } + return val; +} + +VtValue MayaHydraMaterialNetworkConverter::ConvertPlugToValue( + const MPlug& plug, + const SdfValueTypeName& type, + const VtValue* fallback) +{ + if (type.GetType() == SdfValueTypeNames->Vector3f.GetType()) { + return VtValue( + GfVec3f(plug.child(0).asFloat(), plug.child(1).asFloat(), plug.child(2).asFloat())); + } else if (type == SdfValueTypeNames->Float) { + return VtValue(plug.asFloat()); + } else if (type.GetType() == SdfValueTypeNames->Float2.GetType()) { + return VtValue(GfVec2f(plug.child(0).asFloat(), plug.child(1).asFloat())); + } else if (type == SdfValueTypeNames->Int) { + return VtValue(plug.asInt()); + } + TF_DEBUG(MAYAHYDRALIB_ADAPTER_GET) + .Msg( + "MayaHydraMaterialNetworkConverter::ConvertPlugToValue(): do not " + "know how to handle type: %s (cpp type: %s)\n", + type.GetAsToken().GetText(), + type.GetCPPTypeName().c_str()); + if (fallback) { + return *fallback; + } + return {}; +}; + +std::mutex _previewShaderParams_mutex; +bool _previewShaderParams_initialized = false; +MayaHydraShaderParams _previewShaderParams; +static std::map _defaultShaderParams; + +const MayaHydraShaderParams& MayaHydraMaterialNetworkConverter::GetPreviewShaderParams() +{ + if (!_previewShaderParams_initialized) { + std::lock_guard lock(_previewShaderParams_mutex); + // Once we have the lock, recheck to make sure it's still + // uninitialized... + if (!_previewShaderParams_initialized) { + auto& shaderReg = SdrRegistry::GetInstance(); + SdrShaderNodeConstPtr sdrNode + = shaderReg.GetShaderNodeByIdentifier(UsdImagingTokens->UsdPreviewSurface); + if (TF_VERIFY(sdrNode)) { + auto inputNames = sdrNode->GetInputNames(); + _previewShaderParams.reserve(inputNames.size()); + + for (auto& inputName : inputNames) { + auto property = sdrNode->GetInput(inputName); + if (!TF_VERIFY(property)) { + continue; + } + _previewShaderParams.emplace_back( + inputName, property->GetDefaultValue(), property->GetTypeAsSdfType().first); + } + std::sort( + _previewShaderParams.begin(), + _previewShaderParams.end(), + [](const MayaHydraShaderParam& a, const MayaHydraShaderParam& b) -> bool { + return a.name < b.name; + }); + _previewShaderParams_initialized = true; + } + } + } + return _previewShaderParams; +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/mayaHydra/hydraExtensions/adapters/materialNetworkConverter.h b/lib/mayaHydra/hydraExtensions/adapters/materialNetworkConverter.h new file mode 100644 index 0000000000..7f08003ab1 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/adapters/materialNetworkConverter.h @@ -0,0 +1,210 @@ +// +// 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. +// +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +#ifndef MAYAHYDRALIB_MATERIAL_NETWORK_CONVERTER_H +#define MAYAHYDRALIB_MATERIAL_NETWORK_CONVERTER_H + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include + +PXR_NAMESPACE_OPEN_SCOPE + +/** + * The MayaHydraMaterialNetworkConverter class contains how we translate the Maya shaders to hydra + * and how we do the parameters mapping, please see MayaHydraMaterialNetworkConverter::initialize() + * for that purpose. + */ + +struct MayaHydraShaderParam +{ + TfToken name; + VtValue fallbackValue; + + SdfValueTypeName type; + + MAYAHYDRALIB_API + MayaHydraShaderParam(const TfToken& name, const VtValue& value, const SdfValueTypeName& type); +}; + +using MayaHydraShaderParams = std::vector; + +/// Class which provides basic name and value translation for an attribute. +/// Used by both MayaHydraMaterialNetworkConverter (for to-usd file export +/// translation) and MayaHydraMaterialAdapter (for translation to Hydra). +class MayaHydraMaterialAttrConverter +{ +public: + typedef std::shared_ptr RefPtr; + + virtual ~MayaHydraMaterialAttrConverter() {}; + + /// Returns the default type for this attr converter - if an + /// implementation returns an invalid type, this indicates the attr + /// converter's type is undefined / variable. + virtual SdfValueTypeName GetType() = 0; + + /// If there is a simple, one-to-one mapping from the usd/hydra attribute + /// we are trying to "get", and a corresponding maya plug, AND the value + /// can be used "directly", then this should return the name of the maya + /// plug. Otherwise it should return an empty token. + /// By returning an empty token, we indicate that we want to set a value, + /// but that we don't wish to set up any network connections (ie, textures, + /// etc.) + MAYAHYDRALIB_API + virtual TfToken GetPlugName(const TfToken& usdName) = 0; + + /// Returns the value computed from maya for the usd/hydra attribute + // MAYAHYDRALIB_API + // virtual VtValue GetValue( + // const MayaHydraShaderParam& destParam, MFnDependencyNode& node, + // MPlug* outPlug = nullptr) = 0; + + MAYAHYDRALIB_API + virtual VtValue GetValue( + MFnDependencyNode& node, + const TfToken& paramName, + const SdfValueTypeName& type, + const VtValue* fallback = nullptr, + MPlugArray* outPlug + = nullptr) // Some parameters have more than one MPlug to look at such as transmission, + // specular etc. which have a weight and a color + = 0; +}; + +/// Class which provides basic name and value translation for a maya node +/// type. Used by both MayaHydraMaterialNetworkConverter (for to-usd file +/// export translation) and MayaHydraMaterialAdapter (for translation to Hydra). +class MayaHydraMaterialNodeConverter +{ +public: + typedef std:: + unordered_map + NameToAttrConverterMap; + + MAYAHYDRALIB_API + MayaHydraMaterialNodeConverter( + const TfToken& identifier, + const NameToAttrConverterMap& attrConverters); + + inline TfToken GetIdentifier() { return _identifier; } + + /// Try to find the correct attribute converter to use for the given + /// param; if nothing is found, will usually return a generic converter, + /// that will look for an attribute on the maya node with the same name, and + /// use that if possible. + MAYAHYDRALIB_API + MayaHydraMaterialAttrConverter::RefPtr GetAttrConverter(const TfToken& paramName); + + inline NameToAttrConverterMap& GetAttrConverters() { return _attrConverters; } + + MAYAHYDRALIB_API + static MayaHydraMaterialNodeConverter* GetNodeConverter(const TfToken& nodeType); + +private: + NameToAttrConverterMap _attrConverters; + mutable TfToken _identifier; +}; + +class MayaHydraMaterialNetworkConverter +{ +public: + typedef std::unordered_map PathToMobjMap; + + struct MayaHydraMaterialNetworkConverterInit + { + MayaHydraMaterialNetworkConverterInit( + const SdfPath& prefix, + bool enableXRayShadingMode, + PathToMobjMap* pathToMobj) + : _prefix(prefix) + , _enableXRayShadingMode(enableXRayShadingMode) + , _pathToMobj(pathToMobj) + { + } + MayaHydraMaterialNetworkConverterInit() = delete; + + HdMaterialNetwork _materialNetwork; + const SdfPath& _prefix; + bool _enableXRayShadingMode; + PathToMobjMap* _pathToMobj; // Can be a nullptr + }; + + MAYAHYDRALIB_API + MayaHydraMaterialNetworkConverter(MayaHydraMaterialNetworkConverterInit& init); + + MAYAHYDRALIB_API + HdMaterialNode* GetMaterial(const MObject& mayaNode); + + MAYAHYDRALIB_API + void AddPrimvar(const TfToken& primvar); + + MAYAHYDRALIB_API + void ConvertParameter( + MFnDependencyNode& node, + MayaHydraMaterialNodeConverter& nodeConverter, + HdMaterialNode& material, + const TfToken& paramName, + const SdfValueTypeName& type, + const VtValue* fallback = nullptr); + + MAYAHYDRALIB_API static VtValue ConvertMayaAttrToValue( + MFnDependencyNode& node, + const MString& plugName, + const SdfValueTypeName& type, + const VtValue* fallback = nullptr, + MPlugArray* outPlug = nullptr); + + MAYAHYDRALIB_API static VtValue ConvertMayaAttrToScaledValue( + MFnDependencyNode& node, + const MString& plugName, + const MString& scaleName, + const SdfValueTypeName& type, + const VtValue* fallback = nullptr, + MPlugArray* outPlug = nullptr); + + MAYAHYDRALIB_API + static void initialize(); + + MAYAHYDRALIB_API + static VtValue ConvertPlugToValue( + const MPlug& plug, + const SdfValueTypeName& type, + const VtValue* fallback = nullptr); + + MAYAHYDRALIB_API + static const MayaHydraShaderParams& GetPreviewShaderParams(); + +private: + HdMaterialNetwork& _network; + const SdfPath& _prefix; + PathToMobjMap* _pathToMobj; + bool _enableXRayShadingMode = false; +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // MAYAHYDRALIB_MATERIAL_NETWORK_CONVERTER_H diff --git a/lib/mayaHydra/hydraExtensions/adapters/mayaAttrs.cpp b/lib/mayaHydra/hydraExtensions/adapters/mayaAttrs.cpp new file mode 100644 index 0000000000..aeaf09bf8f --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/adapters/mayaAttrs.cpp @@ -0,0 +1,284 @@ +// +// 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. +// +#include + +#include + +#include + +#define SET_NODE_CLASS(nodeTypeName) \ + using namespace nodeTypeName; \ + MNodeClass nodeClass(#nodeTypeName); \ + if (!TF_VERIFY(nodeClass.typeId() != 0)) { \ + return MStatus::kFailure; \ + } + +#define SET_ATTR_OBJ(attr) \ + setAttrObj(attr, nodeClass, #attr); \ + if (!TF_VERIFY(status)) { \ + return status; \ + } + +PXR_NAMESPACE_OPEN_SCOPE + +namespace MayaAttrs { + +namespace node { + +MObject message; + +} // namespace node + +namespace dagNode { + +MObject visibility; +MObject worldMatrix; +MObject intermediateObject; +MObject instObjGroups; +MObject overrideEnabled; +MObject overrideVisibility; + +} // namespace dagNode + +namespace nonAmbientLightShapeNode { + +MObject decayRate; +MObject emitDiffuse; +MObject emitSpecular; + +} // namespace nonAmbientLightShapeNode + +namespace nonExtendedLightShapeNode { + +MObject dmapResolution; +MObject dmapBias; +MObject dmapFilterSize; +MObject useDepthMapShadows; + +} // namespace nonExtendedLightShapeNode + +namespace spotLight { + +MObject coneAngle; +MObject dropoff; + +} // namespace spotLight + +namespace directionalLight { + +MObject lightAngle; +} + +namespace surfaceShape { + +MObject doubleSided; + +} // namespace surfaceShape + +// mesh + +namespace mesh { + +MObject pnts; +MObject inMesh; +MObject uvPivot; +MObject displaySmoothMesh; +MObject smoothLevel; + +} // namespace mesh + +// nurbsCurve + +namespace nurbsCurve { + +MObject controlPoints; + +} // namespace nurbsCurve + +namespace shadingEngine { + +MObject surfaceShader; + +} // namespace shadingEngine + +namespace file { + +MObject computedFileTextureNamePattern; +MObject fileTextureName; +MObject fileTextureNamePattern; +MObject uvTilingMode; +MObject uvCoord; +MObject wrapU; +MObject wrapV; +MObject mirrorU; +MObject mirrorV; + +} // namespace file + +namespace imagePlane { + +MObject imageName; +MObject useFrameExtension; +MObject frameOffset; +MObject frameExtension; +MObject displayMode; + +MObject fit; +MObject coverage; +MObject coverageOrigin; +MObject depth; +MObject rotate; +MObject size; +MObject offset; +MObject width; +MObject height; +MObject imageCenter; + +} // namespace imagePlane + +MStatus initialize() +{ + MStatus status; + + auto setAttrObj = [&status](MObject& attrObj, MNodeClass& nodeClass, const MString& name) { + attrObj = nodeClass.attribute(name, &status); + if (!TF_VERIFY(status)) { + return; + } + if (!TF_VERIFY(!attrObj.isNull())) { + status = MS::kFailure; + MString errMsg("Error finding '"); + errMsg += nodeClass.typeName(); + errMsg += "."; + errMsg += name; + errMsg += "' attribute"; + status.perror(errMsg); + return; + } + }; + { + SET_NODE_CLASS(node); + + SET_ATTR_OBJ(message); + } + { + SET_NODE_CLASS(dagNode); + + SET_ATTR_OBJ(visibility); + SET_ATTR_OBJ(worldMatrix); + SET_ATTR_OBJ(intermediateObject); + SET_ATTR_OBJ(instObjGroups); + SET_ATTR_OBJ(overrideEnabled); + SET_ATTR_OBJ(overrideVisibility); + } + + { + SET_NODE_CLASS(nonAmbientLightShapeNode); + + SET_ATTR_OBJ(decayRate); + SET_ATTR_OBJ(emitDiffuse); + SET_ATTR_OBJ(emitSpecular); + } + + { + SET_NODE_CLASS(nonExtendedLightShapeNode); + + SET_ATTR_OBJ(dmapResolution); + SET_ATTR_OBJ(dmapBias); + SET_ATTR_OBJ(dmapFilterSize); + } + + { + SET_NODE_CLASS(spotLight); + + SET_ATTR_OBJ(coneAngle); + SET_ATTR_OBJ(dropoff); + } + + { + SET_NODE_CLASS(directionalLight); + + SET_ATTR_OBJ(lightAngle); + } + + { + SET_NODE_CLASS(surfaceShape); + + SET_ATTR_OBJ(doubleSided); + } + + { + SET_NODE_CLASS(mesh); + + SET_ATTR_OBJ(pnts); + SET_ATTR_OBJ(inMesh); + SET_ATTR_OBJ(uvPivot); + SET_ATTR_OBJ(displaySmoothMesh); + SET_ATTR_OBJ(smoothLevel); + } + + { + SET_NODE_CLASS(nurbsCurve); + + SET_ATTR_OBJ(controlPoints); + } + + { + SET_NODE_CLASS(shadingEngine); + + SET_ATTR_OBJ(surfaceShader); + } + + { + SET_NODE_CLASS(file); + + SET_ATTR_OBJ(computedFileTextureNamePattern); + SET_ATTR_OBJ(fileTextureName); + SET_ATTR_OBJ(fileTextureNamePattern); + SET_ATTR_OBJ(uvTilingMode); + SET_ATTR_OBJ(uvCoord); + SET_ATTR_OBJ(wrapU); + SET_ATTR_OBJ(wrapV); + SET_ATTR_OBJ(mirrorU); + SET_ATTR_OBJ(mirrorV); + } + + { + SET_NODE_CLASS(imagePlane); + + SET_ATTR_OBJ(displayMode); + SET_ATTR_OBJ(imageName); + SET_ATTR_OBJ(useFrameExtension); + SET_ATTR_OBJ(frameOffset); + SET_ATTR_OBJ(frameExtension); + SET_ATTR_OBJ(fit); + SET_ATTR_OBJ(coverage); + SET_ATTR_OBJ(coverageOrigin); + SET_ATTR_OBJ(depth); + SET_ATTR_OBJ(rotate); + SET_ATTR_OBJ(size); + SET_ATTR_OBJ(offset); + SET_ATTR_OBJ(width); + SET_ATTR_OBJ(height); + SET_ATTR_OBJ(imageCenter); + } + + return MStatus::kSuccess; +} + +} // namespace MayaAttrs + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/mayaHydra/hydraExtensions/adapters/mayaAttrs.h b/lib/mayaHydra/hydraExtensions/adapters/mayaAttrs.h new file mode 100644 index 0000000000..5122936766 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/adapters/mayaAttrs.h @@ -0,0 +1,169 @@ +// +// 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. +// +#ifndef MAYAHYDRALIB_ATTRS_H +#define MAYAHYDRALIB_ATTRS_H + +#include + +#include + +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +#ifdef __GNUC__ +#pragma GCC visibility push(hidden) +#endif + +namespace MayaAttrs { + +namespace node { + +extern MObject message; + +} // namespace node + +namespace dagNode { + +using namespace node; +extern MObject visibility; +extern MObject worldMatrix; +extern MObject intermediateObject; +extern MObject instObjGroups; +extern MObject overrideEnabled; +extern MObject overrideVisibility; + +} // namespace dagNode + +namespace nonAmbientLightShapeNode { + +using namespace dagNode; +extern MObject decayRate; +extern MObject emitDiffuse; +extern MObject emitSpecular; + +} // namespace nonAmbientLightShapeNode + +namespace nonExtendedLightShapeNode { + +using namespace nonAmbientLightShapeNode; +extern MObject dmapResolution; +extern MObject dmapBias; +extern MObject dmapFilterSize; + +} // namespace nonExtendedLightShapeNode + +namespace spotLight { + +using namespace nonExtendedLightShapeNode; +extern MObject coneAngle; +extern MObject dropoff; + +} // namespace spotLight + +namespace directionalLight { + +using namespace nonExtendedLightShapeNode; +extern MObject lightAngle; + +} // namespace directionalLight + +namespace surfaceShape { + +using namespace dagNode; +extern MObject doubleSided; + +} // namespace surfaceShape + +// mesh + +namespace mesh { + +using namespace surfaceShape; +extern MObject pnts; +extern MObject inMesh; +extern MObject uvPivot; +extern MObject displaySmoothMesh; +extern MObject smoothLevel; + +} // namespace mesh + +// curve + +namespace nurbsCurve { + +using namespace surfaceShape; +extern MObject controlPoints; + +} // namespace nurbsCurve + +namespace shadingEngine { + +using namespace node; +extern MObject surfaceShader; + +} // namespace shadingEngine + +namespace file { + +using namespace node; +extern MObject computedFileTextureNamePattern; +extern MObject fileTextureName; +extern MObject fileTextureNamePattern; +extern MObject uvTilingMode; +extern MObject uvCoord; +extern MObject wrapU; +extern MObject wrapV; +extern MObject mirrorU; +extern MObject mirrorV; + +} // namespace file + +namespace imagePlane { + +using namespace dagNode; +extern MObject imageName; +extern MObject useFrameExtension; +extern MObject frameOffset; +extern MObject frameExtension; +extern MObject displayMode; + +extern MObject fit; +extern MObject coverage; +extern MObject coverageOrigin; +extern MObject depth; +extern MObject rotate; +extern MObject size; +extern MObject offset; +extern MObject width; +extern MObject height; +extern MObject imageCenter; + +} // namespace imagePlane + +MAYAHYDRALIB_API +MStatus initialize(); + +} // namespace MayaAttrs + +#ifdef __GNUC__ +#pragma GCC visibility pop +#endif + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // MAYAHYDRALIB_ATTRS_H diff --git a/lib/mayaHydra/hydraExtensions/adapters/meshAdapter.cpp b/lib/mayaHydra/hydraExtensions/adapters/meshAdapter.cpp new file mode 100644 index 0000000000..a873b21be2 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/adapters/meshAdapter.cpp @@ -0,0 +1,471 @@ +// +// 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. +// +#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 + +/** + * This file contains the MayaHydraMeshAdapter class to translate from a Maya mesh to hydra. + * Please note that, as of May 2023, this is optionally used by mayaHydra, with + * a compile-time switch (see sceneDelegate.h). + * + * We can also translate from a MRenderitem to Hydra using the + * MayaHydraRenderItemAdapter class. + */ + +namespace { + +const std::pair _dirtyBits[] { + { MayaAttrs::mesh::pnts, + // This is useful when the user edits the mesh. + HdChangeTracker::DirtyPoints | HdChangeTracker::DirtyExtent + | HdChangeTracker::DirtySubdivTags }, + { MayaAttrs::mesh::inMesh, + // We are tracking topology changes and uv changes separately + HdChangeTracker::DirtyPoints | HdChangeTracker::DirtyExtent + | HdChangeTracker::DirtySubdivTags }, + { MayaAttrs::mesh::worldMatrix, HdChangeTracker::DirtyTransform }, + { MayaAttrs::mesh::doubleSided, HdChangeTracker::DirtyDoubleSided }, + { MayaAttrs::mesh::intermediateObject, HdChangeTracker::DirtyVisibility }, + { MayaAttrs::mesh::uvPivot, + // Tracking manual edits to uvs. + HdChangeTracker::DirtyPrimvar }, + { MayaAttrs::mesh::displaySmoothMesh, HdChangeTracker::DirtyDisplayStyle }, + { MayaAttrs::mesh::smoothLevel, HdChangeTracker::DirtyDisplayStyle } +}; + +} // namespace + +/** + * \brief MayaHydraMeshAdapter is used to handle the translation from a Maya mesh to hydra. + * Please note that, at this time, this is not used by the hydra plugin, we translate from a + * renderitem to hydra using the MayaHydraRenderItemAdapter class. + */ +class MayaHydraMeshAdapter : public MayaHydraShapeAdapter +{ +public: + MayaHydraMeshAdapter(MayaHydraSceneProducer* producer, const MDagPath& dag) + : MayaHydraShapeAdapter(producer->GetPrimPath(dag, false), producer, dag) + { + } + + ~MayaHydraMeshAdapter() = default; + + void Populate() override + { + if (_isPopulated) { + return; + } + GetSceneProducer()->InsertRprim(this, HdPrimTypeTokens->mesh, GetID(), GetInstancerID()); + _isPopulated = true; + } + + void AddBuggyCallback(MCallbackId id) { _buggyCallbacks.append(id); } + + void CreateCallbacks() override + { + MStatus status; + auto obj = GetNode(); + if (obj != MObject::kNullObj) { + TF_DEBUG(MAYAHYDRALIB_ADAPTER_CALLBACKS) + .Msg("Creating mesh adapter callbacks for prim (%s).\n", GetID().GetText()); + + auto id + = MNodeMessage::addNodeDirtyPlugCallback(obj, NodeDirtiedCallback, this, &status); + if (status) { + AddCallback(id); + } + id = MNodeMessage::addAttributeChangedCallback( + obj, AttributeChangedCallback, this, &status); + if (status) { + AddCallback(id); + } + id = MPolyMessage::addPolyTopologyChangedCallback( + obj, TopologyChangedCallback, this, &status); + if (status) { + AddCallback(id); + } + bool wantModifications[3] = { true, true, true }; + id = MPolyMessage::addPolyComponentIdChangedCallback( + obj, wantModifications, 3, ComponentIdChanged, this, &status); + if (status) { + AddBuggyCallback(id); + } + id = MPolyMessage::addUVSetChangedCallback(obj, UVSetChangedCallback, this, &status); + if (status) { + AddBuggyCallback(id); + } + } + MayaHydraDagAdapter::CreateCallbacks(); + } + + MAYAHYDRALIB_API + void RemoveCallbacks() override + { + if (_buggyCallbacks.length() > 0) { + TF_DEBUG(MAYAHYDRALIB_ADAPTER_CALLBACKS) + .Msg("Removing buggy PolyComponentIdChangedCallbacks\n"); + if (_node != MObject::kNullObj && MObjectHandle(_node).isValid()) { + MMessage::removeCallbacks(_buggyCallbacks); + } + _buggyCallbacks.clear(); + } + MayaHydraAdapter::RemoveCallbacks(); + } + + bool IsSupported() const override + { + return GetSceneProducer()->GetRenderIndex().IsRprimTypeSupported(HdPrimTypeTokens->mesh); + } + + VtValue GetUVs() + { + MStatus status; + MFnMesh mesh(GetDagPath(), &status); + if (ARCH_UNLIKELY(!status)) { + return {}; + } + VtArray uvs; + uvs.reserve(static_cast(mesh.numFaceVertices())); + for (MItMeshPolygon pit(GetDagPath()); !pit.isDone(); pit.next()) { + const auto vertexCount = pit.polygonVertexCount(); + for (auto i = decltype(vertexCount) { 0 }; i < vertexCount; ++i) { + float2 uv = { 0.0f, 0.0f }; + pit.getUV(i, uv); + uvs.push_back(GfVec2f(uv[0], uv[1])); + } + } + + return VtValue(uvs); + } + + VtValue GetPoints(const MFnMesh& mesh) + { + MStatus status; + const auto* rawPoints = reinterpret_cast(mesh.getRawPoints(&status)); + if (ARCH_UNLIKELY(!status)) { + return {}; + } + VtVec3fArray ret; + ret.assign(rawPoints, rawPoints + mesh.numVertices()); + return VtValue(ret); + } + + VtValue Get(const TfToken& key) override + { + TF_DEBUG(MAYAHYDRALIB_ADAPTER_GET) + .Msg( + "Called MayaHydraMeshAdapter::Get(%s) - %s\n", + key.GetText(), + GetDagPath().partialPathName().asChar()); + + if (key == HdTokens->points) { + MStatus status; + MFnMesh mesh(GetDagPath(), &status); + if (ARCH_UNLIKELY(!status)) { + return {}; + } + return GetPoints(mesh); + } else if (key == MayaHydraAdapterTokens->st) { + return GetUVs(); + } + return {}; + } + + size_t SamplePrimvar(const TfToken& key, size_t maxSampleCount, float* times, VtValue* samples) + override + { + if (maxSampleCount < 1) { + return 0; + } + + if (key == HdTokens->points) { + MStatus status; + MFnMesh mesh(GetDagPath(), &status); + if (ARCH_UNLIKELY(!status)) { + return 0; + } + return GetSceneProducer()->SampleValues( + maxSampleCount, times, samples, [&]() -> VtValue { return GetPoints(mesh); }); + } else if (key == MayaHydraAdapterTokens->st) { + times[0] = 0.0f; + samples[0] = GetUVs(); + return 1; + } + return 0; + } + + HdMeshTopology GetMeshTopology() override + { + MFnMesh mesh(GetDagPath()); + const auto numPolygons = mesh.numPolygons(); + VtIntArray faceVertexCounts; + faceVertexCounts.reserve(static_cast(numPolygons)); + VtIntArray faceVertexIndices; + faceVertexIndices.reserve(static_cast(mesh.numFaceVertices())); + for (MItMeshPolygon pit(GetDagPath()); !pit.isDone(); pit.next()) { + const auto vc = pit.polygonVertexCount(); + faceVertexCounts.push_back(vc); + for (auto i = decltype(vc) { 0 }; i < vc; ++i) { + faceVertexIndices.push_back(pit.vertexIndex(i)); + } + } + + return HdMeshTopology( + (GetSceneProducer()->GetParams().displaySmoothMeshes || GetDisplayStyle().refineLevel > 0) + ? PxOsdOpenSubdivTokens->catmullClark + : PxOsdOpenSubdivTokens->none, + + UsdGeomTokens->rightHanded, + faceVertexCounts, + faceVertexIndices); + } + + HdDisplayStyle GetDisplayStyle() override + { + MStatus status; + MFnDependencyNode node(GetNode(), &status); + if (ARCH_UNLIKELY(!status)) { + return { 0, false, false }; + } + const auto displaySmoothMesh + = node.findPlug(MayaAttrs::mesh::displaySmoothMesh, true).asShort(); + if (displaySmoothMesh == 0) { + return { 0, false, false }; + } + const auto smoothLevel + = std::max(0, node.findPlug(MayaAttrs::mesh::smoothLevel, true).asInt()); + return { smoothLevel, false, false }; + } + + PxOsdSubdivTags GetSubdivTags() override + { + PxOsdSubdivTags tags; + if (GetDisplayStyle().refineLevel < 1) { + return tags; + } + + MStatus status; + MFnMesh mesh(GetNode(), &status); + if (ARCH_UNLIKELY(!status)) { + return tags; + } + MUintArray creaseVertIds; + MDoubleArray creaseVertValues; + mesh.getCreaseVertices(creaseVertIds, creaseVertValues); + const auto creaseVertIdCount = creaseVertIds.length(); + if (!TF_VERIFY(creaseVertIdCount == creaseVertValues.length())) { + return tags; + } + + MUintArray creaseEdgeIds; + MDoubleArray creaseEdgeValues; + mesh.getCreaseEdges(creaseEdgeIds, creaseEdgeValues); + const auto creaseEdgeIdCount = creaseEdgeIds.length(); + if (!TF_VERIFY(creaseEdgeIdCount == creaseEdgeIds.length())) { + return tags; + } + + if (creaseVertIdCount > 0) { + VtIntArray cornerIndices(creaseVertIdCount); + VtFloatArray cornerWeights(creaseVertIdCount); + for (auto i = decltype(creaseVertIdCount) { 0 }; i < creaseVertIdCount; ++i) { + cornerIndices[i] = static_cast(creaseVertIds[i]); + cornerWeights[i] = static_cast(creaseVertValues[i]); + } + + tags.SetCornerIndices(cornerIndices); + tags.SetCornerWeights(cornerWeights); + } + + if (creaseEdgeIdCount > 0) { + VtIntArray edgeIndices(creaseEdgeIdCount * 2); + VtFloatArray edgeWeights(creaseEdgeIdCount); + int edgeVertices[2] = { 0, 0 }; + for (auto i = decltype(creaseEdgeIdCount) { 0 }; i < creaseEdgeIdCount; ++i) { + mesh.getEdgeVertices(creaseEdgeIds[i], edgeVertices); + edgeIndices[i * 2] = edgeVertices[0]; + edgeIndices[i * 2 + 1] = edgeVertices[1]; + edgeWeights[i] = static_cast(creaseEdgeValues[i]); + } + + tags.SetCreaseIndices(edgeIndices); + tags.SetCreaseLengths(VtIntArray(creaseEdgeIdCount, 2)); + tags.SetCreaseWeights(edgeWeights); + } + + tags.SetVertexInterpolationRule(UsdGeomTokens->edgeAndCorner); + tags.SetFaceVaryingInterpolationRule(UsdGeomTokens->cornersPlus1); + tags.SetTriangleSubdivision(UsdGeomTokens->catmullClark); + + return tags; + } + + HdPrimvarDescriptorVector GetPrimvarDescriptors(HdInterpolation interpolation) override + { + if (interpolation == HdInterpolationVertex) { + HdPrimvarDescriptor desc; + desc.name = UsdGeomTokens->points; + desc.interpolation = interpolation; + desc.role = HdPrimvarRoleTokens->point; + return { desc }; + } else if (interpolation == HdInterpolationFaceVarying) { + // UVs are face varying in maya. + MFnMesh mesh(GetDagPath()); + if (mesh.numUVs() > 0) { + HdPrimvarDescriptor desc; + desc.name = MayaHydraAdapterTokens->st; + desc.interpolation = interpolation; + desc.role = HdPrimvarRoleTokens->textureCoordinate; + return { desc }; + } + } + return {}; + } + + bool GetDoubleSided() const override + { + MFnMesh mesh(GetDagPath()); + auto p = mesh.findPlug(MayaAttrs::mesh::doubleSided, true); + if (ARCH_UNLIKELY(p.isNull())) { + return true; + } + bool doubleSided = true; + p.getValue(doubleSided); + return doubleSided; + } + + bool HasType(const TfToken& typeId) const override { return typeId == HdPrimTypeTokens->mesh; } + +private: + static void NodeDirtiedCallback(MObject& node, MPlug& plug, void* clientData) + { + auto* adapter = reinterpret_cast(clientData); + for (const auto& it : _dirtyBits) { + if (it.first == plug) { + adapter->MarkDirty(it.second); + TF_DEBUG(MAYAHYDRALIB_ADAPTER_MESH_PLUG_DIRTY) + .Msg( + "Marking prim dirty with bits %u because %s plug was " + "dirtied.\n", + it.second, + plug.partialName().asChar()); + return; + } + } + + TF_DEBUG(MAYAHYDRALIB_ADAPTER_MESH_UNHANDLED_PLUG_DIRTY) + .Msg( + "%s (%s) plug dirtying was not handled by " + "MayaHydraMeshAdapter::NodeDirtiedCallback.\n", + plug.name().asChar(), + plug.partialName().asChar()); + } + + // For material assignments for now. + static void AttributeChangedCallback( + MNodeMessage::AttributeMessage msg, + MPlug& plug, + MPlug& otherPlug, + void* clientData) + { + auto* adapter = reinterpret_cast(clientData); + if (plug == MayaAttrs::mesh::instObjGroups) { + adapter->MarkDirty(HdChangeTracker::DirtyMaterialId); + } else { + TF_DEBUG(MAYAHYDRALIB_ADAPTER_MESH_UNHANDLED_PLUG_DIRTY) + .Msg( + "%s (%s) plug dirtying was not handled by " + "MayaHydraMeshAdapter::attributeChangedCallback.\n", + plug.name().asChar(), + plug.name().asChar()); + } + } + + static void TopologyChangedCallback(MObject& node, void* clientData) + { + auto* adapter = reinterpret_cast(clientData); + adapter->MarkDirty( + HdChangeTracker::DirtyTopology | HdChangeTracker::DirtyPrimvar + | HdChangeTracker::DirtyPoints); + } + + static void ComponentIdChanged(MUintArray componentIds[], unsigned int count, void* clientData) + { + auto* adapter = reinterpret_cast(clientData); + adapter->MarkDirty( + HdChangeTracker::DirtyTopology | HdChangeTracker::DirtyPrimvar + | HdChangeTracker::DirtyPoints); + } + + static void UVSetChangedCallback( + MObject& node, + const MString& name, + MPolyMessage::MessageType type, + void* clientData) + { + auto* adapter = reinterpret_cast(clientData); + adapter->MarkDirty(HdChangeTracker::DirtyPrimvar); + } + + // Maya has a bug with removing some MPolyMessage callbacks. Known + // problem callbacks include: + // MPolyMessage::addPolyComponentIdChangedCallback + // MPolyMessage::addUVSetChangedCallback + // Reproduction code can be found here: + // https://gist.github.com/elrond79/668d9809873125f608e0f7360fff7fac + // To work around this, we register these callbacks specially, and only + // remove them if the underlying node is currently valid. + MCallbackIdArray _buggyCallbacks; +}; + +TF_REGISTRY_FUNCTION(TfType) +{ + TfType::Define>(); +} + +TF_REGISTRY_FUNCTION_WITH_TAG(MayaHydraAdapterRegistry, mesh) +{ + MayaHydraAdapterRegistry::RegisterShapeAdapter( + TfToken("mesh"), + [](MayaHydraSceneProducer* producer, const MDagPath& dag) -> MayaHydraShapeAdapterPtr { + return MayaHydraShapeAdapterPtr(new MayaHydraMeshAdapter(producer, dag)); + }); +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/mayaHydra/hydraExtensions/adapters/nurbsCurveAdapter.cpp b/lib/mayaHydra/hydraExtensions/adapters/nurbsCurveAdapter.cpp new file mode 100644 index 0000000000..a852d6ed8b --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/adapters/nurbsCurveAdapter.cpp @@ -0,0 +1,249 @@ +// +// 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. +// +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +/** + * This file contains the MayaHydraNurbsCurveAdapter class to translate from a Maya NURBS curve to + * hydra. Please note that, at this time, this is not used by the hydra plugin, we translate from a + * renderitem to hydra using the MayaHydraRenderItemAdapter class. + */ + +namespace { + +const std::array, 4> _dirtyBits { { + { MayaAttrs::nurbsCurve::controlPoints, + HdChangeTracker::DirtyPoints | HdChangeTracker::DirtyExtent }, + { MayaAttrs::nurbsCurve::worldMatrix, HdChangeTracker::DirtyTransform }, + { MayaAttrs::nurbsCurve::doubleSided, HdChangeTracker::DirtyDoubleSided }, + { MayaAttrs::nurbsCurve::intermediateObject, HdChangeTracker::DirtyVisibility }, +} }; + +} // namespace + +/** + * \brief MayaHydraNurbsCurveAdapter is used to handle the translation from a Maya NURBS curve to + * hydra. Please note that, at this time, this is not used by the hydra plugin, we translate from a + * renderitem to hydra using the MayaHydraRenderItemAdapter class. + */ +class MayaHydraNurbsCurveAdapter : public MayaHydraShapeAdapter +{ +public: + MayaHydraNurbsCurveAdapter(MayaHydraSceneProducer* producer, const MDagPath& dag) + : MayaHydraShapeAdapter(producer->GetPrimPath(dag, false), producer, dag) + { + } + + ~MayaHydraNurbsCurveAdapter() = default; + + bool IsSupported() const override + { + return GetSceneProducer()->GetRenderIndex().IsRprimTypeSupported(HdPrimTypeTokens->basisCurves); + } + + void Populate() override { GetSceneProducer()->InsertRprim(this, HdPrimTypeTokens->basisCurves, GetID()); } + + void CreateCallbacks() override + { + MStatus status; + auto obj = GetNode(); + if (obj != MObject::kNullObj) { + TF_DEBUG(MAYAHYDRALIB_ADAPTER_CALLBACKS) + .Msg("Creating nurbs curve adapter callbacks for prim (%s).\n", GetID().GetText()); + + auto id + = MNodeMessage::addNodeDirtyPlugCallback(obj, NodeDirtiedCallback, this, &status); + if (status) { + AddCallback(id); + } + id = MNodeMessage::addAttributeChangedCallback( + obj, AttributeChangedCallback, this, &status); + if (status) { + AddCallback(id); + } + id = MPolyMessage::addPolyTopologyChangedCallback( + obj, TopologyChangedCallback, this, &status); + if (status) { + AddCallback(id); + } + bool wantModifications[3] = { true, true, true }; + id = MPolyMessage::addPolyComponentIdChangedCallback( + obj, wantModifications, 3, ComponentIdChanged, this, &status); + if (status) { + AddCallback(id); + } + } + MayaHydraDagAdapter::CreateCallbacks(); + } + + VtValue Get(const TfToken& key) override + { + TF_DEBUG(MAYAHYDRALIB_ADAPTER_GET) + .Msg( + "Called MayaHydraNurbsCurveAdapter::Get(%s) - %s\n", + key.GetText(), + GetDagPath().partialPathName().asChar()); + + if (key == HdTokens->points) { + MFnNurbsCurve curve(GetDagPath()); + MStatus status; + MPointArray pointArray; + status = curve.getCVs(pointArray); + if (!status) { + return {}; + } + VtVec3fArray ret(pointArray.length()); + const auto pointCount = pointArray.length(); + for (auto i = decltype(pointCount) { 0 }; i < pointCount; i++) { + const auto pt = pointArray[i]; + ret[i] = GfVec3f( + static_cast(pt.x), static_cast(pt.y), static_cast(pt.z)); + } + return VtValue(ret); + } + return {}; + } + + HdBasisCurvesTopology GetBasisCurvesTopology() override + { + MFnNurbsCurve curve(GetDagPath()); + const auto pointCount = curve.numCVs(); + + VtIntArray curveVertexCounts; + const auto numIndices = (pointCount - 1) * 2; + curveVertexCounts.push_back(numIndices); + VtIntArray curveIndices(static_cast(numIndices)); + for (auto i = decltype(numIndices) { 0 }; i < numIndices / 2; i++) { + curveIndices[i * 2] = i; + curveIndices[i * 2 + 1] = i + 1; + } + + return HdBasisCurvesTopology( + HdTokens->linear, + HdTokens->bezier, + HdTokens->segmented, + curveVertexCounts, + curveIndices); + } + + HdPrimvarDescriptorVector GetPrimvarDescriptors(HdInterpolation interpolation) override + { + if (interpolation == HdInterpolationVertex) { + HdPrimvarDescriptor desc; + desc.name = UsdGeomTokens->points; + desc.interpolation = interpolation; + desc.role = HdPrimvarRoleTokens->point; + return { desc }; + } + return {}; + } + + TfToken GetRenderTag() const override { return HdRenderTagTokens->guide; } + +private: + static void NodeDirtiedCallback(MObject& node, MPlug& plug, void* clientData) + { + auto* adapter = reinterpret_cast(clientData); + for (const auto& it : _dirtyBits) { + if (it.first == plug) { + adapter->MarkDirty(it.second); + TF_DEBUG(MAYAHYDRALIB_ADAPTER_CURVE_PLUG_DIRTY) + .Msg( + "Marking prim dirty with bits %u because %s plug was " + "dirtied.\n", + it.second, + plug.partialName().asChar()); + return; + } + } + + TF_DEBUG(MAYAHYDRALIB_ADAPTER_CURVE_UNHANDLED_PLUG_DIRTY) + .Msg( + "%s (%s) plug dirtying was not handled by " + "MayaHydraNurbsCurveAdapter::NodeDirtiedCallback.\n", + plug.name().asChar(), + plug.partialName().asChar()); + } + + // For material assignments for now. + static void AttributeChangedCallback( + MNodeMessage::AttributeMessage msg, + MPlug& plug, + MPlug& otherPlug, + void* clientData) + { + auto* adapter = reinterpret_cast(clientData); + if (plug == MayaAttrs::mesh::instObjGroups) { + adapter->MarkDirty(HdChangeTracker::DirtyMaterialId); + } else { + TF_DEBUG(MAYAHYDRALIB_ADAPTER_CURVE_UNHANDLED_PLUG_DIRTY) + .Msg( + "%s (%s) plug dirtying was not handled by " + "MayaHydraNurbsCurveAdapter::attributeChangedCallback.\n", + plug.name().asChar(), + plug.name().asChar()); + } + } + + static void TopologyChangedCallback(MObject& node, void* clientData) + { + auto* adapter = reinterpret_cast(clientData); + adapter->MarkDirty( + HdChangeTracker::DirtyTopology | HdChangeTracker::DirtyPrimvar + | HdChangeTracker::DirtyPoints); + } + + static void ComponentIdChanged(MUintArray componentIds[], unsigned int count, void* clientData) + { + auto* adapter = reinterpret_cast(clientData); + adapter->MarkDirty( + HdChangeTracker::DirtyTopology | HdChangeTracker::DirtyPrimvar + | HdChangeTracker::DirtyPoints); + } +}; + +TF_REGISTRY_FUNCTION(TfType) +{ + TfType::Define>(); +} + +TF_REGISTRY_FUNCTION_WITH_TAG(MayaHydraAdapterRegistry, mesh) +{ + MayaHydraAdapterRegistry::RegisterShapeAdapter( + TfToken("nurbsCurve"), + [](MayaHydraSceneProducer* producer, const MDagPath& dag) -> MayaHydraShapeAdapterPtr { + return MayaHydraShapeAdapterPtr(new MayaHydraNurbsCurveAdapter(producer, dag)); + }); +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/mayaHydra/hydraExtensions/adapters/pointLightAdapter.cpp b/lib/mayaHydra/hydraExtensions/adapters/pointLightAdapter.cpp new file mode 100644 index 0000000000..00644bc636 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/adapters/pointLightAdapter.cpp @@ -0,0 +1,87 @@ +// +// 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. +// +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include + +PXR_NAMESPACE_OPEN_SCOPE + +/** + * \brief MayaHydraPointLightAdapter is used to handle the translation from a Maya point light to + * hydra. + */ +class MayaHydraPointLightAdapter : public MayaHydraLightAdapter +{ +public: + MayaHydraPointLightAdapter(MayaHydraSceneProducer* producer, const MDagPath& dag) + : MayaHydraLightAdapter(producer, dag) + { + } + + const TfToken& LightType() const override + { + if (GetSceneProducer()->IsHdSt()) { + return HdPrimTypeTokens->simpleLight; + } else { + return HdPrimTypeTokens->sphereLight; + } + } + + VtValue GetLightParamValue(const TfToken& paramName) override + { + TF_DEBUG(MAYAHYDRALIB_ADAPTER_GET_LIGHT_PARAM_VALUE) + .Msg( + "Called MayaHydraPointLightAdapter::GetLightParamValue(%s) - %s\n", + paramName.GetText(), + GetDagPath().partialPathName().asChar()); + + MFnPointLight light(GetDagPath()); + if (paramName == HdLightTokens->radius) { + const float radius = light.shadowRadius(); + return VtValue(radius); + } else if (paramName == UsdLuxTokens->treatAsPoint) { + const bool treatAsPoint = (light.shadowRadius() == 0.0); + return VtValue(treatAsPoint); + } + return MayaHydraLightAdapter::GetLightParamValue(paramName); + } +}; + +TF_REGISTRY_FUNCTION(TfType) +{ + TfType::Define>(); +} + +TF_REGISTRY_FUNCTION_WITH_TAG(MayaHydraAdapterRegistry, pointLight) +{ + MayaHydraAdapterRegistry::RegisterLightAdapter( + TfToken("pointLight"), + [](MayaHydraSceneProducer* producer, const MDagPath& dag) -> MayaHydraLightAdapterPtr { + return MayaHydraLightAdapterPtr(new MayaHydraPointLightAdapter(producer, dag)); + }); +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/mayaHydra/hydraExtensions/adapters/renderItemAdapter.cpp b/lib/mayaHydra/hydraExtensions/adapters/renderItemAdapter.cpp new file mode 100644 index 0000000000..d5c24f8191 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/adapters/renderItemAdapter.cpp @@ -0,0 +1,476 @@ +// +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +// 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 "renderItemAdapter.h" + +#include +#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 +// Bring the MayaHydra namespace into scope. +// The following code currently lives inside the pxr namespace, but it would make more sense to +// have it inside the MayaHydra namespace. This using statement allows us to use MayaHydra symbols +// from within the pxr namespace as if we were in the MayaHydra namespace. +// Remove this once the code has been moved to the MayaHydra namespace. +using namespace MayaHydra; + +#define PLUG_THIS_PLUGIN \ + PlugRegistry::GetInstance().GetPluginWithName(TF_PP_STRINGIZE(MFB_PACKAGE_NAME)) + +/* + * MayaHydraRenderItemAdapter is used to translate from a render item to hydra. + * This is where we translate from Maya shapes (such as meshes) to hydra using their vertex and + * index buffers, look for "MVertexBuffer" and "MIndexBuffer" in this file to get more information. + */ +MayaHydraRenderItemAdapter::MayaHydraRenderItemAdapter( + const MDagPath& dagPath, + const SdfPath& slowId, + int fastId, + MayaHydraSceneProducer* producer, + const MRenderItem& ri) + : MayaHydraAdapter(MObject(), slowId, producer) + , _dagPath(dagPath) + , _primitive(ri.primitive()) + , _name(ri.name()) + , _fastId(fastId) +{ + _InsertRprim(this); +} + +MayaHydraRenderItemAdapter::~MayaHydraRenderItemAdapter() { _RemoveRprim(); } + +TfToken MayaHydraRenderItemAdapter::GetRenderTag() const { return HdRenderTagTokens->geometry; } + +void MayaHydraRenderItemAdapter::UpdateTransform(const MRenderItem& ri) +{ + MMatrix matrix; + if (ri.getMatrix(matrix) == MStatus::kSuccess) { + _transform[0] = GetGfMatrixFromMaya(matrix); + if (GetSceneProducer()->GetParams().motionSamplesEnabled()) { + MDGContextGuard guard(MAnimControl::currentTime() + 1.0); + _transform[1] = GetGfMatrixFromMaya(matrix); + } else { + _transform[1] = _transform[0]; + } + } +} + +bool MayaHydraRenderItemAdapter::IsSupported() const +{ + switch (_primitive) { + case MHWRender::MGeometry::Primitive::kTriangles: + return GetSceneProducer()->GetRenderIndex().IsRprimTypeSupported(HdPrimTypeTokens->mesh); + case MHWRender::MGeometry::Primitive::kLines: + case MHWRender::MGeometry::Primitive::kLineStrip: + return GetSceneProducer()->GetRenderIndex().IsRprimTypeSupported(HdPrimTypeTokens->basisCurves); + case MHWRender::MGeometry::Primitive::kPoints: + return GetSceneProducer()->GetRenderIndex().IsRprimTypeSupported(HdPrimTypeTokens->points); + default: return false; + } +} + +void MayaHydraRenderItemAdapter::_InsertRprim(MayaHydraAdapter* adapter) +{ + switch (GetPrimitive()) { + case MHWRender::MGeometry::Primitive::kTriangles: + GetSceneProducer()->InsertRprim(adapter, HdPrimTypeTokens->mesh, GetID(), {}); + break; + case MHWRender::MGeometry::Primitive::kLines: + case MHWRender::MGeometry::Primitive::kLineStrip: + GetSceneProducer()->InsertRprim(adapter, HdPrimTypeTokens->basisCurves, GetID(), {}); + break; + case MHWRender::MGeometry::Primitive::kPoints: + GetSceneProducer()->InsertRprim(adapter, HdPrimTypeTokens->points, GetID(), {}); + break; + default: + assert(false); // unexpected/unsupported primitive type + break; + } +} + +void MayaHydraRenderItemAdapter::_RemoveRprim() { GetSceneProducer()->RemoveRprim(GetID());} + +// We receive in that function the changes made in the Maya viewport between the last frame rendered +// and the current frame +void MayaHydraRenderItemAdapter::UpdateFromDelta(const UpdateFromDeltaData& data) +{ + if (_primitive != MHWRender::MGeometry::Primitive::kTriangles + && _primitive != MHWRender::MGeometry::Primitive::kLines + && _primitive != MHWRender::MGeometry::Primitive::kLineStrip) { + return; + } + + const bool positionsHaveBeenReset + = (0 == _positions.size()); // when positionsHaveBeenReset is true we need to recompute the + // geometry and topology as our data has been cleared + using MVS = MDataServerOperation::MViewportScene; + // const bool isNew = flags & MViewportScene::MVS_new; //not used yet + const bool visible = data._flags & MVS::MVS_visible; + const bool matrixChanged = data._flags & MVS::MVS_changedMatrix; + const bool geomChanged = (data._flags & MVS::MVS_changedGeometry) || positionsHaveBeenReset; + const bool topoChanged = (data._flags & MVS::MVS_changedTopo) || positionsHaveBeenReset; + const bool visibChanged = data._flags & MVS::MVS_changedVisibility; + const bool effectChanged = data._flags & MVS::MVS_changedEffect; + + HdDirtyBits dirtyBits = 0; + + if (data._wireframeColor != _wireframeColor) { + _wireframeColor = data._wireframeColor; + dirtyBits |= HdChangeTracker::DirtyPrimvar; // displayColor primVar + } + + const bool displayStatusChanged = (_displayStatus != data._displayStatus); + _displayStatus = data._displayStatus; + const bool hideOnPlayback = data._ri.isHideOnPlayback(); + if (hideOnPlayback != _isHideOnPlayback) { + _isHideOnPlayback = hideOnPlayback; + dirtyBits |= HdChangeTracker::DirtyVisibility; + } + + //Special case for aiSkydomeLight which is visible only when it is selected + if (_isArnoldSkyDomeLightTriangleShape && displayStatusChanged){ + SetVisible(IsRenderItemSelected()); + dirtyBits |= HdChangeTracker::DirtyVisibility; + } else + if (visibChanged) { + SetVisible(visible); + dirtyBits |= HdChangeTracker::DirtyVisibility; + } + + if (effectChanged) { + dirtyBits |= HdChangeTracker::DirtyMaterialId; + } + if (matrixChanged) { + dirtyBits |= HdChangeTracker::DirtyTransform; + } + if (geomChanged) { + dirtyBits |= HdChangeTracker::DirtyPoints; + } + if (topoChanged) { + dirtyBits |= (HdChangeTracker::DirtyTopology | HdChangeTracker::DirtyPrimvar); + } + + MGeometry* geom = nullptr; + if (geomChanged | topoChanged) { + geom = data._ri.geometry(); + } + VtIntArray vertexIndices; + VtIntArray vertexCounts; + + // Vertices + MVertexBuffer* verts = nullptr; + if (geomChanged && geom && geom->vertexBufferCount() > 0) { + // Assume first stream contains the positions. We do not handle multiple streams for now. + verts = geom->vertexBuffer(0); + if (verts) { + int vertCount = 0; + const unsigned int originalVertexCount = verts->vertexCount(); + if (topoChanged) { + vertCount = originalVertexCount; + } else { + // Keep the previously-determined vertex count in case it was truncated. + const size_t positionSize = _positions.size(); + if (positionSize > 0 && positionSize <= originalVertexCount) { + vertCount = positionSize; + } else { + vertCount = originalVertexCount; + } + } + + _positions.clear(); + //_positions.resize(vertCount); + // map() is usually just reading from the software copy of the vp2 buffers. It was also + // showing up in vtune that it was sometimes mapping OpenGL buffers to read from, which + // is slow. Disabling processing of non-triangle render made that disappear. Maybe + // something like joint render items point to hardware only buffers? + const auto* vertexPositions = reinterpret_cast(verts->map()); + // NOTE: Looking at MayaHydraMeshAdapter::GetPoints notice assign(vertexPositions, + // vertexPositions + vertCount) Why are we not multiplying with sizeof(GfVec3f) to + // calculate the offset ? The following happens when I try to do it : Invalid Hydra prim + // - Vertex primvar points has 288 elements, while its topology references only upto + // element index 24. + if (TF_VERIFY(vertexPositions)) { + _positions.assign(vertexPositions, vertexPositions + vertCount); + } + verts->unmap(); + } + } + + // Indices + // Note that a Primitive::kLineStrip index buffer is unavailable. The following section is + // skipped. In such case, indices are implicitly defined below. + MIndexBuffer* indices = nullptr; + if (topoChanged && geom && geom->indexBufferCount() > 0) { + // Assume first stream contains the positions. + indices = geom->indexBuffer(0); + if (indices) { + int indexCount = indices->size(); + vertexIndices.resize(indexCount); + int* indicesData = (int*)indices->map(); + // USD spamming the "topology references only upto element" message is super + // slow. Scanning the index array to look for an incompletely used vertex + // buffer is innefficient, but it's better than the spammy warning. Cause of + // the incompletely used vertex buffer is unclear. Maya scene data just is + // that way sometimes. + int maxIndex = 0; + for (int i = 0; i < indexCount; i++) { + if (indicesData[i] > maxIndex) { + maxIndex = indicesData[i]; + } + } + + // VtArray operator[] is oddly expensive, ~10ms per frame here. Replace with assign(). + // for (int i = 0; i < indexCount; i++) vertexIndices[i] = indicesData[i]; + vertexIndices.assign(indicesData, indicesData + indexCount); + + if (maxIndex < (int64_t)_positions.size() - 1) { + _positions.resize(maxIndex + 1); + } + + switch (GetPrimitive()) { + case MHWRender::MGeometry::Primitive::kTriangles: + vertexCounts.resize(indexCount / 3); + vertexCounts.assign(indexCount / 3, 3); + + // UVs + if (indexCount > 0) { + MVertexBuffer* mvb = nullptr; + for (int vbIdx = 0; vbIdx < geom->vertexBufferCount(); vbIdx++) { + mvb = geom->vertexBuffer(vbIdx); + if (!mvb) + continue; + + const MVertexBufferDescriptor& desc = mvb->descriptor(); + + if (desc.semantic() != MGeometry::Semantic::kTexture) + continue; + + // Hydra expects a uv coordinate for each face-index, not 1 per vertex. + // e.g. a cube expects 36 uvs not 24. + _uvs.clear(); + _uvs.resize(indices->size()); + float* uvs = (float*)mvb->map(); + for (int i = 0; i < indexCount; ++i) { + _uvs[i].Set(&uvs[indicesData[i] * 2]); + } + mvb->unmap(); + break; + } + } + break; + case MHWRender::MGeometry::Primitive::kLines: + vertexCounts.resize(indexCount); + vertexCounts.assign(indexCount / 2, 2); + break; + + default: + assert(false); // unexpected/unsupported primitive type + break; + } + indices->unmap(); + } + } + + if (topoChanged) { + switch (GetPrimitive()) { + case MGeometry::Primitive::kTriangles: + _topology.reset(new HdMeshTopology( + (GetSceneProducer()->GetParams().displaySmoothMeshes + || GetDisplayStyle().refineLevel > 0) + ? PxOsdOpenSubdivTokens->catmullClark + : PxOsdOpenSubdivTokens->none, + UsdGeomTokens->rightHanded, + vertexCounts, + vertexIndices)); + break; + case MGeometry::Primitive::kLines: + case MGeometry::Primitive::kLineStrip: { + TfToken curveTopoType(HdTokens->segmented); + if (GetPrimitive() == MGeometry::Primitive::kLineStrip) { + // Line strips indices are implicitly defined: + // When using line strips, the GPU will draw a connected series of lines between the + // vertices specified by the indices. When specifying indices for a line strip, you + // only need to specify the order of the vertices that you want connected. This is + // implicit in Hydra when specifying an empty index buffer. + curveTopoType = HdTokens->nonperiodic; + vertexCounts.assign(1, _positions.size()); + vertexIndices = VtIntArray(); + } + _topology.reset(new HdBasisCurvesTopology( + HdTokens->linear, + // basis type is ignored, due to linear curve type + {}, + curveTopoType, + vertexCounts, + vertexIndices)); + break; + } + default: break; + } + } + + MarkDirty(dirtyBits); +} + +HdMeshTopology MayaHydraRenderItemAdapter::GetMeshTopology() +{ + return _topology ? *static_cast(_topology.get()) : HdMeshTopology(); +} + +HdBasisCurvesTopology MayaHydraRenderItemAdapter::GetBasisCurvesTopology() +{ + return _topology ? *static_cast(_topology.get()) + : HdBasisCurvesTopology(); +} + +VtValue MayaHydraRenderItemAdapter::Get(const TfToken& key) +{ + if (key == HdTokens->points) { + return VtValue(_positions); + } + if (key == MayaHydraAdapterTokens->st) { + return VtValue(_uvs); + } + if (key == HdTokens->displayColor) { + return VtValue(GfVec4f( + _wireframeColor[0], _wireframeColor[1], _wireframeColor[2], _wireframeColor[3])); + } + + return {}; +} + +void MayaHydraRenderItemAdapter::MarkDirty(HdDirtyBits dirtyBits) +{ + if (dirtyBits != 0) { + GetSceneProducer()->MarkRprimDirty(GetID(), dirtyBits); + } +} + +HdPrimvarDescriptorVector +MayaHydraRenderItemAdapter::GetPrimvarDescriptors(HdInterpolation interpolation) +{ + HdPrimvarDescriptor desc; + + // Vertices + if (interpolation == HdInterpolationVertex) { + desc.name = UsdGeomTokens->points; + desc.interpolation = interpolation; + desc.role = HdPrimvarRoleTokens->point; + return { desc }; + } else if (interpolation == HdInterpolationFaceVarying) { + // UVs are face varying in maya. + if (_primitive == MGeometry::Primitive::kTriangles) { + desc.name = MayaHydraAdapterTokens->st; + desc.interpolation = interpolation; + desc.role = HdPrimvarRoleTokens->textureCoordinate; + return { desc }; + } + } else if (interpolation == HdInterpolationConstant) { + switch(_primitive){ + case MGeometry::Primitive::kPoints: //Fall into + case MGeometry::Primitive::kLines: //Fall into + case MGeometry::Primitive::kLineStrip: //Fall into + case MGeometry::Primitive::kAdjacentLines: //Fall into + case MGeometry::Primitive::kAdjacentLineStrip: + { + desc.name = HdTokens->displayColor;//Use display color only for lines/points (avoid triangles) + desc.interpolation = interpolation; + desc.role = HdPrimvarRoleTokens->color; + return { desc }; + } + break; + default: + break; + } + } + + return {}; +} + +VtValue MayaHydraRenderItemAdapter::GetMaterialResource() { return {}; } + +bool MayaHydraRenderItemAdapter::GetVisible() +{ + // Assuming that, if the playback is in the active view only + // (MAnimControl::kPlaybackViewActive), we are called because we are in the active view + if (_isHideOnPlayback) { + // MAYA-127216: Remove dependency on parent class MayaHydraAdapter. This will let us use + // MayaHydraSceneDelegate directly + auto sceneProducer = static_cast(GetSceneProducer()); + return !sceneProducer->GetPlaybackRunning(); + } + + return _visible; +} + +void MayaHydraRenderItemAdapter::SetPlaybackChanged() +{ + // There was a change in the playblack state, it started or stopped running so update any + // primitive that is dependent on this + if (_isHideOnPlayback) { + MarkDirty(HdChangeTracker::DirtyVisibility); + } +} + +bool MayaHydraRenderItemAdapter::IsRenderItemSelected() const +{ + return (MHWRender::DisplayStatus::kActive == _displayStatus) || + (MHWRender::DisplayStatus::kLead == _displayStatus); +} + +HdCullStyle MayaHydraRenderItemAdapter::GetCullStyle() const +{ + // HdCullStyleNothing means no culling, HdCullStyledontCare means : let the renderer choose + // between back or front faces culling. We don't want culling, since we want to see the + // backfaces being unlit with MayaHydraSceneDelegate::GetDoubleSided returning false. + return _isArnoldSkyDomeLightTriangleShape ? HdCullStyleFront : HdCullStyleNothing; +} + +/////////////////////////////////////////////////////////////////////// +// TF_REGISTRY +/////////////////////////////////////////////////////////////////////// + +TF_REGISTRY_FUNCTION(TfType) +{ + TfType::Define>(); +} + +TF_REGISTRY_FUNCTION_WITH_TAG(MayaHydraAdapterRegistry, renderItem) { } + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/mayaHydra/hydraExtensions/adapters/renderItemAdapter.h b/lib/mayaHydra/hydraExtensions/adapters/renderItemAdapter.h new file mode 100644 index 0000000000..bbec8f1969 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/adapters/renderItemAdapter.h @@ -0,0 +1,216 @@ +// +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +// 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 MAYAHYDRALIB_RENDER_ITEM_ADAPTER_H +#define MAYAHYDRALIB_RENDER_ITEM_ADAPTER_H + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +class MayaHydraSceneProducer; + +namespace { +std::string kRenderItemTypeName = "renderItem"; + +static constexpr const char* kPointSize = "pointSize"; + +static const SdfPath kInvalidMaterial = SdfPath("InvalidMaterial"); +} // namespace + +using MayaHydraRenderItemAdapterPtr = std::shared_ptr; + +/** + * \brief MayaHydraRenderItemAdapter is used to translate from a render item to hydra. + * This is where we translate from Maya shapes (such as meshes) to hydra. + */ +class MayaHydraRenderItemAdapter : public MayaHydraAdapter +{ +public: + MAYAHYDRALIB_API + MayaHydraRenderItemAdapter( + const MDagPath& dagPath, + const SdfPath& slowId, + int fastId, + MayaHydraSceneProducer* producer, + const MRenderItem& ri); + + MAYAHYDRALIB_API + virtual ~MayaHydraRenderItemAdapter(); + + MAYAHYDRALIB_API + virtual void RemovePrim() override { } + + MAYAHYDRALIB_API + virtual void Populate() override { } + + MAYAHYDRALIB_API + bool HasType(const TfToken& typeId) const override { return typeId == HdPrimTypeTokens->mesh; } + + MAYAHYDRALIB_API + virtual bool IsSupported() const override; + + MAYAHYDRALIB_API + bool GetDoubleSided() const override { return false; }; + + MAYAHYDRALIB_API + HdCullStyle GetCullStyle() const override; + + MAYAHYDRALIB_API + virtual void MarkDirty(HdDirtyBits dirtyBits) override; + + MAYAHYDRALIB_API + VtValue Get(const TfToken& key) override; + + MAYAHYDRALIB_API + VtValue GetMaterialResource(); + + MAYAHYDRALIB_API + void SetPlaybackChanged(); + + MAYAHYDRALIB_API + bool GetVisible() override; + + MAYAHYDRALIB_API + void SetVisible(bool val) { _visible = val; } + + MAYAHYDRALIB_API + const MColor& GetWireframeColor() const { return _wireframeColor; } + + MAYAHYDRALIB_API + MHWRender::DisplayStatus GetDisplayStatus() const { return _displayStatus; } + + MAYAHYDRALIB_API + GfMatrix4d GetTransform() override { return _transform[0]; } + + MAYAHYDRALIB_API + void InvalidateTransform() { } + + MAYAHYDRALIB_API + bool IsInstanced() const { return false; } + + MAYAHYDRALIB_API + HdPrimvarDescriptorVector GetPrimvarDescriptors(HdInterpolation interpolation) override; + + MAYAHYDRALIB_API + void UpdateTransform(const MRenderItem& ri); + + /// Class used to pass data to the UpdateFromDelta method, so we can extend the parameters in + /// the future if needed. + class UpdateFromDeltaData + { + public: + UpdateFromDeltaData( + MRenderItem& ri, + unsigned int flags, + const MColor& wireframeColor, + MHWRender::DisplayStatus displayStatus) + : _ri(ri) + , _flags(flags) + , _wireframeColor(wireframeColor) + , _displayStatus(displayStatus) + { + } + + MRenderItem& _ri; + unsigned int _flags; + const MColor& _wireframeColor; + MHWRender::DisplayStatus _displayStatus; + }; + + /// We receive in that function the changes made in the Maya viewport between the last frame + /// rendered and the current frame + MAYAHYDRALIB_API + void UpdateFromDelta(const UpdateFromDeltaData& data); + + MAYAHYDRALIB_API + HdMeshTopology GetMeshTopology() override; + + MAYAHYDRALIB_API + HdBasisCurvesTopology GetBasisCurvesTopology() override; + + MAYAHYDRALIB_API + virtual TfToken GetRenderTag() const override; + + MAYAHYDRALIB_API + SdfPath& GetMaterial() { return _material; } + + MAYAHYDRALIB_API + void SetMaterial(const SdfPath& val) { _material = val; } + + MAYAHYDRALIB_API + int GetFastID() const { return _fastId; } + + MAYAHYDRALIB_API + const MDagPath& GetDagPath() const { return _dagPath; } + + MAYAHYDRALIB_API + MGeometry::Primitive GetPrimitive() const { return _primitive; } + + MAYAHYDRALIB_API + const char* Name() const { return _name.asChar(); } + + MAYAHYDRALIB_API + void SetIsRenderITemAnaiSkydomeLightTriangleShape(bool val) {_isArnoldSkyDomeLightTriangleShape = val;} + + MAYAHYDRALIB_API + bool GetIsRenderITemAnaiSkydomeLightTriangleShape() const {return _isArnoldSkyDomeLightTriangleShape;} + + MAYAHYDRALIB_API + bool IsRenderItemSelected() const; + +private: + MAYAHYDRALIB_API + void _RemoveRprim(); + + MAYAHYDRALIB_API + void _InsertRprim(MayaHydraAdapter* adapter); + + SdfPath _material; + MDagPath _dagPath; + std::unique_ptr _topology = nullptr; + VtVec3fArray _positions = {}; + VtVec2fArray _uvs = {}; + MGeometry::Primitive _primitive; + MString _name; + GfMatrix4d _transform[2]; + int _fastId = 0; + bool _visible = false; + MColor _wireframeColor = { 1.f, 1.f, 1.f, 1.f }; + bool _isHideOnPlayback = false; + MHWRender::DisplayStatus _displayStatus = MHWRender::DisplayStatus::kNoStatus; + bool _isArnoldSkyDomeLightTriangleShape = false; +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // MAYAHYDRALIB_RENDER_ITEM_ADAPTER_H diff --git a/lib/mayaHydra/hydraExtensions/adapters/shapeAdapter.cpp b/lib/mayaHydra/hydraExtensions/adapters/shapeAdapter.cpp new file mode 100644 index 0000000000..0c297c8f45 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/adapters/shapeAdapter.cpp @@ -0,0 +1,156 @@ +// +// 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. +// +#include "shapeAdapter.h" + +#include +#include + +#include + +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +TF_REGISTRY_FUNCTION(TfType) +{ + TfType::Define>(); +} + +/* + * MayaHydraShapeAdapter is an adapter to translate from Maya shapes to hydra + * Please note that, at this time, this is not used by the hydra plug-in, we translate from a + * renderitem to hydra using the MayaHydraRenderItemAdapter class. + */ +MayaHydraShapeAdapter::MayaHydraShapeAdapter( + const SdfPath& id, + MayaHydraSceneProducer* producer, + const MDagPath& dagPath) + : MayaHydraDagAdapter(id, producer, dagPath) +{ + _CalculateExtent(); +} + +void MayaHydraShapeAdapter::_CalculateExtent() +{ + MStatus status; + MFnDagNode dagNode(GetDagPath(), &status); + if (ARCH_LIKELY(status)) { + const auto bb = dagNode.boundingBox(); + const auto mn = bb.min(); + const auto mx = bb.max(); + _extent.SetMin({ mn.x, mn.y, mn.z }); + _extent.SetMax({ mx.x, mx.y, mx.z }); + _extentDirty = false; + } +}; + +size_t MayaHydraShapeAdapter::SamplePrimvar( + const TfToken& key, + size_t maxSampleCount, + float* times, + VtValue* samples) +{ + if (maxSampleCount < 1) { + return 0; + } + times[0] = 0.0f; + samples[0] = Get(key); + return 1; +} + +HdMeshTopology MayaHydraShapeAdapter::GetMeshTopology() { return {}; }; + +HdBasisCurvesTopology MayaHydraShapeAdapter::GetBasisCurvesTopology() { return {}; }; + +HdDisplayStyle MayaHydraShapeAdapter::GetDisplayStyle() { return { 0, false, false }; } + +PxOsdSubdivTags MayaHydraShapeAdapter::GetSubdivTags() { return {}; } + +void MayaHydraShapeAdapter::MarkDirty(HdDirtyBits dirtyBits) +{ + MayaHydraDagAdapter::MarkDirty(dirtyBits); + if (dirtyBits & HdChangeTracker::DirtyPoints) { + _extentDirty = true; + } +} + +MObject MayaHydraShapeAdapter::GetMaterial() +{ + TF_DEBUG(MAYAHYDRALIB_ADAPTER_GET) + .Msg( + "Called MayaHydraShapeAdapter::GetMaterial() - %s\n", + GetDagPath().partialPathName().asChar()); + + MStatus status; + MFnDagNode dagNode(GetDagPath(), &status); + if (!status) { + return MObject::kNullObj; + } + + auto instObjGroups = dagNode.findPlug(MayaAttrs::dagNode::instObjGroups, true); + if (instObjGroups.isNull()) { + return MObject::kNullObj; + } + + MPlugArray conns; + instObjGroups.elementByLogicalIndex(0).connectedTo(conns, false, true); + + const auto numConnections = conns.length(); + if (numConnections == 0) { + return MObject::kNullObj; + } + for (auto i = decltype(numConnections) { 0 }; i < numConnections; ++i) { + auto sg = conns[i].node(); + if (sg.apiType() == MFn::kShadingEngine) { + return sg; + } + } + + return MObject::kNullObj; +} + +const GfRange3d& MayaHydraShapeAdapter::GetExtent() +{ + if (_extentDirty) { + _CalculateExtent(); + } + return _extent; +} + +TfToken MayaHydraShapeAdapter::GetRenderTag() const { return HdTokens->geometry; } + +void MayaHydraShapeAdapter::PopulateSelectedPaths( + const MDagPath& selectedDag, + SdfPathVector& selectedSdfPaths, + std::unordered_set& selectedMasters, + const HdSelectionSharedPtr& selection) +{ + VtIntArray indices(1); + if (IsInstanced()) { + indices[0] = selectedDag.instanceNumber(); + selection->AddInstance(HdSelection::HighlightModeSelect, _id, indices); + if (selectedMasters.find(_id) == selectedMasters.end()) { + selectedSdfPaths.push_back(_id); + selectedMasters.insert(_id); + } + } else { + selection->AddRprim(HdSelection::HighlightModeSelect, _id); + selectedSdfPaths.push_back(_id); + } +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/mayaHydra/hydraExtensions/adapters/shapeAdapter.h b/lib/mayaHydra/hydraExtensions/adapters/shapeAdapter.h new file mode 100644 index 0000000000..8494c8f02c --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/adapters/shapeAdapter.h @@ -0,0 +1,95 @@ +// +// 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. +// +#ifndef MAYAHYDRALIB_SHAPE_ADAPTER_H +#define MAYAHYDRALIB_SHAPE_ADAPTER_H + +#include + +#include + +PXR_NAMESPACE_OPEN_SCOPE + +class MayaHydraSceneProducer; + +/** + * \brief MayaHydraShapeAdapter is an adapter to translate from Maya shapes to hydra + * Please note that, at this time, this is not used by the hydra plug-in, we translate from a + * renderitem to hydra using the MayaHydraRenderItemAdapter class. + */ +class MayaHydraShapeAdapter : public MayaHydraDagAdapter +{ +protected: + MAYAHYDRALIB_API + MayaHydraShapeAdapter( + const SdfPath& id, + MayaHydraSceneProducer* producer, + const MDagPath& dagPath); + +public: + MAYAHYDRALIB_API + virtual ~MayaHydraShapeAdapter() = default; + + MAYAHYDRALIB_API + virtual size_t + SamplePrimvar(const TfToken& key, size_t maxSampleCount, float* times, VtValue* samples); + MAYAHYDRALIB_API + virtual HdMeshTopology GetMeshTopology() override; + MAYAHYDRALIB_API + virtual HdBasisCurvesTopology GetBasisCurvesTopology() override; + MAYAHYDRALIB_API + virtual HdDisplayStyle GetDisplayStyle() override; + MAYAHYDRALIB_API + virtual PxOsdSubdivTags GetSubdivTags(); + MAYAHYDRALIB_API + virtual HdPrimvarDescriptorVector GetPrimvarDescriptors(HdInterpolation interpolation) override + { + return {}; + } + MAYAHYDRALIB_API + virtual void MarkDirty(HdDirtyBits dirtyBits) override; + + MAYAHYDRALIB_API + virtual MObject GetMaterial(); + MAYAHYDRALIB_API + virtual bool GetDoubleSided() const override { return true; }; + + MAYAHYDRALIB_API + const GfRange3d& GetExtent(); + + MAYAHYDRALIB_API + virtual TfToken GetRenderTag() const override; + + MAYAHYDRALIB_API + virtual void PopulateSelectedPaths( + const MDagPath& selectedDag, + SdfPathVector& selectedSdfPaths, + std::unordered_set& selectedMasters, + const HdSelectionSharedPtr& selection); + +protected: + MAYAHYDRALIB_API + void _CalculateExtent(); + +private: + GfRange3d _extent; + bool _extentDirty; +}; + +using MayaHydraShapeAdapterPtr = std::shared_ptr; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // MAYAHYDRALIB_SHAPE_ADAPTER_H diff --git a/lib/mayaHydra/hydraExtensions/adapters/spotLightAdapter.cpp b/lib/mayaHydra/hydraExtensions/adapters/spotLightAdapter.cpp new file mode 100644 index 0000000000..92f8b73bf4 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/adapters/spotLightAdapter.cpp @@ -0,0 +1,167 @@ +// +// 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. +// +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +namespace { +void GetSpotCutoffAndSoftness(MFnSpotLight& mayaLight, float& cutoffOut, float& softnessOut) +{ + // Divided by two. + auto coneAngle = static_cast(GfRadiansToDegrees(mayaLight.coneAngle())) * 0.5f; + auto penumbraAngle = static_cast(GfRadiansToDegrees(mayaLight.penumbraAngle())); + cutoffOut = coneAngle + penumbraAngle; + softnessOut = cutoffOut == 0 ? 0 : penumbraAngle / cutoffOut; +} + +float GetSpotCutoff(MFnSpotLight& mayaLight) +{ + float cutoff; + float softness; + GetSpotCutoffAndSoftness(mayaLight, cutoff, softness); + return cutoff; +} + +float GetSpotSoftness(MFnSpotLight& mayaLight) +{ + float cutoff; + float softness; + GetSpotCutoffAndSoftness(mayaLight, cutoff, softness); + return softness; +} + +float GetSpotFalloff(MFnSpotLight& mayaLight) { return static_cast(mayaLight.dropOff()); } +} // namespace + +/** + * \brief MayaHydraSpotLightAdapter is used to handle the translation from a Maya spot light to + * hydra. + */ +class MayaHydraSpotLightAdapter : public MayaHydraLightAdapter +{ +public: + MayaHydraSpotLightAdapter(MayaHydraSceneProducer* producer, const MDagPath& dag) + : MayaHydraLightAdapter(producer, dag) + { + } + + const TfToken& LightType() const override + { + if (GetSceneProducer()->IsHdSt()) { + return HdPrimTypeTokens->simpleLight; + } else { + return HdPrimTypeTokens->sphereLight; + } + } + +protected: + void _CalculateLightParams(GlfSimpleLight& light) override + { + MStatus status; + MFnSpotLight mayaLight(GetDagPath(), &status); + if (TF_VERIFY(status)) { + light.SetHasShadow(true); + light.SetSpotCutoff(GetSpotCutoff(mayaLight)); + light.SetSpotFalloff(GetSpotFalloff(mayaLight)); + } + } + + VtValue Get(const TfToken& key) override + { + TF_DEBUG(MAYAHYDRALIB_ADAPTER_GET) + .Msg( + "Called MayaHydraSpotLightAdapter::Get(%s) - %s\n", + key.GetText(), + GetDagPath().partialPathName().asChar()); + + if (key == HdLightTokens->shadowParams) { + HdxShadowParams shadowParams; + MFnSpotLight mayaLight(GetDagPath()); + if (!GetShadowsEnabled(mayaLight)) { + shadowParams.enabled = false; + return VtValue(shadowParams); + } + + _CalculateShadowParams(mayaLight, shadowParams); + // Use the radius as the "blur" amount, for PCSS + shadowParams.blur = mayaLight.shadowRadius(); + return VtValue(shadowParams); + } + + return MayaHydraLightAdapter::Get(key); + } + + VtValue GetLightParamValue(const TfToken& paramName) override + { + TF_DEBUG(MAYAHYDRALIB_ADAPTER_GET_LIGHT_PARAM_VALUE) + .Msg( + "Called MayaHydraSpotLightAdapter::GetLightParamValue(%s) - %s\n", + paramName.GetText(), + GetDagPath().partialPathName().asChar()); + + MStatus status; + MFnSpotLight light(GetDagPath(), &status); + if (TF_VERIFY(status)) { + if (paramName == HdLightTokens->radius) { + const float radius = light.shadowRadius(); + return VtValue(radius); + } else if (paramName == UsdLuxTokens->treatAsPoint) { + const bool treatAsPoint = (light.shadowRadius() == 0.0); + return VtValue(treatAsPoint); + } else if (paramName == UsdLuxTokens->inputsShapingConeAngle) { + return VtValue(GetSpotCutoff(light)); + } else if (paramName == UsdLuxTokens->inputsShapingConeSoftness) { + return VtValue(GetSpotSoftness(light)); + } else if (paramName == UsdLuxTokens->inputsShapingFocus) { + return VtValue(GetSpotFalloff(light)); + } + } + return MayaHydraLightAdapter::GetLightParamValue(paramName); + } +}; + +TF_REGISTRY_FUNCTION(TfType) +{ + TfType::Define>(); +} + +TF_REGISTRY_FUNCTION_WITH_TAG(MayaHydraAdapterRegistry, pointLight) +{ + MayaHydraAdapterRegistry::RegisterLightAdapter( + TfToken("spotLight"), + [](MayaHydraSceneProducer* producer, const MDagPath& dag) -> MayaHydraLightAdapterPtr { + return MayaHydraLightAdapterPtr(new MayaHydraSpotLightAdapter(producer, dag)); + }); +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/mayaHydra/hydraExtensions/adapters/tokens.cpp b/lib/mayaHydra/hydraExtensions/adapters/tokens.cpp new file mode 100644 index 0000000000..9d1331b0f5 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/adapters/tokens.cpp @@ -0,0 +1,28 @@ +// +// 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. +// +#include + +PXR_NAMESPACE_OPEN_SCOPE + +// clang-format off +TF_DEFINE_PUBLIC_TOKENS( + MayaHydraAdapterTokens, + + MAYAHYDRALIB_ADAPTER_TOKENS +); +// clang-format on + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/mayaHydra/hydraExtensions/adapters/tokens.h b/lib/mayaHydra/hydraExtensions/adapters/tokens.h new file mode 100644 index 0000000000..5d4b8457f5 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/adapters/tokens.h @@ -0,0 +1,92 @@ +// +// 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. +// +#ifndef MAYAHYDRALIB_ADAPTER_TOKENS_H +#define MAYAHYDRALIB_ADAPTER_TOKENS_H + +#include + +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +/** + * \brief MayaHydraAdapterTokens contains all the hydra tokens used by this plug-in. + */ + +// clang-format off +#define MAYAHYDRALIB_ADAPTER_TOKENS \ + (roughness) \ + (clearcoat) \ + (clearcoatRoughness) \ + (emissiveColor) \ + (specular) \ + (specularColor) \ + (metallic) \ + (useSpecularWorkflow) \ + (occlusion) \ + (ior) \ + (normal) \ + (opacity) \ + (diffuse) \ + (diffuseColor) \ + (displacement) \ + (base) \ + (baseColor) \ + (emission) \ + (emissionColor) \ + (metalness) \ + (specularIOR) \ + (specularRoughness) \ + (coat) \ + (coatRoughness) \ + (transmission) \ + (lambert) \ + (blinn) \ + (phong) \ + (standardSurface) \ + (file) \ + (place2dTexture) \ + (fileTextureName) \ + (color) \ + (incandescence) \ + (out) \ + (output) \ + (st) \ + (uv) \ + (uvCoord) \ + (rgb) \ + (r) \ + (xyz) \ + (x) \ + (varname) \ + (result) \ + (eccentricity) \ + (usdPreviewSurface) \ + (pxrUsdPreviewSurface) \ + (MayaHydraLambertShader) \ + (MayaHydraPhongShader) \ + (MayaHydraBlinnShader) \ + (MayaHydraStippleShader) \ + (MayaHydraSolidColorShader) + +// clang-format on + +TF_DECLARE_PUBLIC_TOKENS(MayaHydraAdapterTokens, MAYAHYDRALIB_API, MAYAHYDRALIB_ADAPTER_TOKENS); + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // MAYAHYDRALIB_ADAPTER_TOKENS_H diff --git a/lib/mayaHydra/hydraExtensions/api.h b/lib/mayaHydra/hydraExtensions/api.h new file mode 100644 index 0000000000..10f692c3be --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/api.h @@ -0,0 +1,40 @@ +// +// 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. +// +#ifndef MAYAHYDRALIB_API_H +#define MAYAHYDRALIB_API_H + +#ifdef __GNUC__ +#define MAYAHYDRALIB_API_EXPORT __attribute__((visibility("default"))) +#define MAYAHYDRALIB_API_IMPORT +#elif defined(_WIN32) || defined(_WIN64) +#define MAYAHYDRALIB_API_EXPORT __declspec(dllexport) +#define MAYAHYDRALIB_API_IMPORT __declspec(dllimport) +#else +#define MAYAHYDRALIB_API_EXPORT +#define MAYAHYDRALIB_API_IMPORT +#endif + +#if defined(MAYAHYDRALIB_STATIC) +#define MAYAHYDRALIB_API +#else +#if defined(MAYAHYDRALIB_EXPORT) +#define MAYAHYDRALIB_API MAYAHYDRALIB_API_EXPORT +#else +#define MAYAHYDRALIB_API MAYAHYDRALIB_API_IMPORT +#endif +#endif + +#endif // MAYAHYDRALIB_API_H diff --git a/lib/mayaHydra/hydraExtensions/debugCodes.cpp b/lib/mayaHydra/hydraExtensions/debugCodes.cpp new file mode 100644 index 0000000000..83cf58da73 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/debugCodes.cpp @@ -0,0 +1,29 @@ +// +// 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. +// +#include "debugCodes.h" + +#include + +PXR_NAMESPACE_OPEN_SCOPE + +// Some variables to enable debug printing information +TF_REGISTRY_FUNCTION(TfDebug) +{ + TF_DEBUG_ENVIRONMENT_SYMBOL( + MAYAHYDRALIB_SCENE_INDEX, "Print info about the Maya Hydra scene index."); +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/mayaHydra/hydraExtensions/debugCodes.h b/lib/mayaHydra/hydraExtensions/debugCodes.h new file mode 100644 index 0000000000..01f244105c --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/debugCodes.h @@ -0,0 +1,46 @@ +// +// 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. +// +// Copyright 2023 Autodesk +// +// 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 MAYAHYDRALIB_DEBUG_CODES_H +#define MAYAHYDRALIB_DEBUG_CODES_H + +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +// clang-format off +TF_DEBUG_CODES( + MAYAHYDRALIB_SCENE_INDEX +); +// clang-format on + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // MAYAHYDRALIB_DEBUG_CODES_H diff --git a/lib/mayaHydra/hydraExtensions/delegates/CMakeLists.txt b/lib/mayaHydra/hydraExtensions/delegates/CMakeLists.txt new file mode 100644 index 0000000000..b11a9fe287 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/delegates/CMakeLists.txt @@ -0,0 +1,42 @@ +# ----------------------------------------------------------------------------- +# sources +# ----------------------------------------------------------------------------- +target_sources(${TARGET_NAME} + PRIVATE + delegate.cpp + delegateCtx.cpp + delegateDebugCodes.cpp + delegateRegistry.cpp + sceneDelegate.cpp + defaultLightDelegate.cpp + testDelegate.cpp +) + +set(HEADERS + delegate.h + delegateCtx.h + delegateDebugCodes.h + delegateRegistry.h + params.h + sceneDelegate.h + defaultLightDelegate.h + testDelegate.h +) + +# ----------------------------------------------------------------------------- +# promoted headers +# ----------------------------------------------------------------------------- +mayaUsd_promoteHeaderList( + HEADERS + ${HEADERS} + BASEDIR + ${TARGET_NAME}/delegates +) + +# ----------------------------------------------------------------------------- +# install +# ----------------------------------------------------------------------------- +install(FILES ${HEADERS} + DESTINATION + ${CMAKE_INSTALL_PREFIX}/include/mayaHydraLib/delegates +) diff --git a/lib/mayaHydra/hydraExtensions/delegates/defaultLightDelegate.cpp b/lib/mayaHydra/hydraExtensions/delegates/defaultLightDelegate.cpp new file mode 100644 index 0000000000..f150605f76 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/delegates/defaultLightDelegate.cpp @@ -0,0 +1,228 @@ +// +// 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. +// +#include "defaultLightDelegate.h" + +#include + +#include +#include +#include +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +namespace { +bool AreLightsParamsWeUseDifferent(const GlfSimpleLight& light1, const GlfSimpleLight& light2) +{ + // We only update 3 parameters in the default light : position, diffuse and specular. We don't + // use the primitive's transform. + return (light1.GetPosition() != light2.GetPosition()) + || // Position (in which we actually store a direction, updated when rotating the view for + // example) + (light1.GetDiffuse() != light2.GetDiffuse()) + || (light1.GetSpecular() != light2.GetSpecular()); +} +} // namespace + +// clang-format off +TF_DEFINE_PRIVATE_TOKENS( + _tokens, + + (DefaultMayaLight) +); +// clang-format on + +// MtohDefaultLightDelegate is a separate Hydra custom scene delegate to handle the default lighting +// from Maya. We use another Hydra custom scene delegate to handle the other parts of the Maya +// scene. See sceneDelegate.h in the mayaHydraLib project. If you want to know how to add a custom +// scene index to this plug-in, then please see the registration.cpp file in the mayaHydraLib +// project. +MtohDefaultLightDelegate::MtohDefaultLightDelegate(const InitData& initData) + : HdSceneDelegate(initData.renderIndex, initData.delegateID) + , MayaHydraDelegate(initData) + , _lightPath(initData.delegateID.AppendChild(_tokens->DefaultMayaLight)) +{ +} + +MtohDefaultLightDelegate::~MtohDefaultLightDelegate() { RemovePrim(); } + +void MtohDefaultLightDelegate::Populate() +{ + if (_isPopulated) { + return; + } + if (!_isLightingOn) { + return; + } + _isSupported = IsHdSt() ? GetRenderIndex().IsSprimTypeSupported(HdPrimTypeTokens->simpleLight) + : GetRenderIndex().IsSprimTypeSupported(HdPrimTypeTokens->distantLight); + if (ARCH_UNLIKELY(!_isSupported)) { + return; + } + if (IsHdSt()) { + GetRenderIndex().InsertSprim(HdPrimTypeTokens->simpleLight, this, _lightPath); + } else { + GetRenderIndex().InsertSprim(HdPrimTypeTokens->distantLight, this, _lightPath); + } + GetRenderIndex().GetChangeTracker().SprimInserted(_lightPath, HdLight::AllDirty); + _isPopulated = true; +} + +void MtohDefaultLightDelegate::RemovePrim() +{ + if (!_isPopulated) { + return; + } + if (ARCH_UNLIKELY(!_isSupported)) { + return; + } + if (IsHdSt()) { + GetRenderIndex().RemoveSprim(HdPrimTypeTokens->simpleLight, _lightPath); + } else { + GetRenderIndex().RemoveSprim(HdPrimTypeTokens->distantLight, _lightPath); + } + _isPopulated = false; +} + +void MtohDefaultLightDelegate::SetDefaultLight(const GlfSimpleLight& light) +{ + if (!_isPopulated) { + return; + } + if (ARCH_UNLIKELY(!_isSupported)) { + return; + } + + // We only update 3 parameters in the default light : position (in which we store a direction), + // diffuse and specular + // We don't never update the transform for the default light + const bool lightsParamsWeUseAreDifferent = AreLightsParamsWeUseDifferent(_light, light); + if (lightsParamsWeUseAreDifferent) { + // Update our light + _light.SetDiffuse(light.GetDiffuse()); + _light.SetSpecular(light.GetSpecular()); + _light.SetPosition(light.GetPosition()); + GetRenderIndex().GetChangeTracker().MarkSprimDirty(_lightPath, HdLight::DirtyParams); + } +} + +GfMatrix4d MtohDefaultLightDelegate::GetTransform(const SdfPath& id) +{ + TF_UNUSED(id); + + TF_DEBUG(MAYAHYDRALIB_DELEGATE_GET_TRANSFORM) + .Msg("MtohDefaultLightDelegate::GetTransform(%s)\n", id.GetText()); + + // We have to rotate the distant to match the simple light's direction + // stored in it's position. Otherwise, the matrix needs to be an identity + // matrix. + if (!IsHdSt()) { + const auto position = _light.GetPosition(); + GfTransform transform; + transform.SetRotation( + GfRotation(GfVec3d(0.0, 0.0, -1.0), GfVec3d(-position[0], -position[1], -position[2]))); + return transform.GetMatrix(); + } + return GfMatrix4d(1.0); +} + +VtValue MtohDefaultLightDelegate::Get(const SdfPath& id, const TfToken& key) +{ + TF_UNUSED(id); + + TF_DEBUG(MAYAHYDRALIB_DELEGATE_GET) + .Msg("MtohDefaultLightDelegate::Get(%s, %s)\n", id.GetText(), key.GetText()); + + if (key == HdLightTokens->params) { + return VtValue(_light); + } else if (key == HdTokens->transform) { + return VtValue(GfMatrix4d( + 1.0)); // We don't use the transform but use the position param of the GlfsimpleLight + // Hydra might crash when this is an empty VtValue. + } else if (key == HdLightTokens->shadowCollection) { + if (!_solidPrimitivesRootPaths.empty()) { + // Exclude lines/points primitives from casting shadows by only taking the primitives + // whose root path belongs to _solidPrimitivesRootPaths + HdRprimCollection coll(HdTokens->geometry, HdReprSelector(HdReprTokens->refined)); + coll.SetRootPaths(_solidPrimitivesRootPaths); + return VtValue(coll); + } else { + HdRprimCollection coll(HdTokens->geometry, HdReprSelector(HdReprTokens->refined)); + return VtValue(coll); + } + } else if (key == HdLightTokens->shadowParams) { + HdxShadowParams shadowParams; + shadowParams.enabled = false; + return VtValue(shadowParams); + } + return {}; +} + +VtValue MtohDefaultLightDelegate::GetLightParamValue(const SdfPath& id, const TfToken& paramName) +{ + TF_UNUSED(id); + + TF_DEBUG(MAYAHYDRALIB_DELEGATE_GET_LIGHT_PARAM_VALUE) + .Msg( + "MtohDefaultLightDelegate::GetLightParamValue(%s, %s)\n", + id.GetText(), + paramName.GetText()); + + if (paramName == HdLightTokens->color || paramName == HdTokens->displayColor) { + const auto diffuse = _light.GetDiffuse(); + return VtValue(GfVec3f(diffuse[0], diffuse[1], diffuse[2])); + } else if (paramName == HdLightTokens->intensity) { + return VtValue(1.0f); + } else if (paramName == HdLightTokens->diffuse) { + return VtValue(1.0f); + } else if (paramName == HdLightTokens->specular) { + return VtValue(0.0f); + } else if (paramName == HdLightTokens->exposure) { + return VtValue(0.0f); + } else if (paramName == HdLightTokens->normalize) { + return VtValue(true); + } else if (paramName == HdLightTokens->angle) { + return VtValue(0.0f); + } else if (paramName == HdLightTokens->shadowEnable) { + return VtValue(false); + } else if (paramName == HdLightTokens->shadowColor) { + return VtValue(GfVec3f(0.0f, 0.0f, 0.0f)); + } else if (paramName == HdLightTokens->enableColorTemperature) { + return VtValue(false); + } + return {}; +} + +bool MtohDefaultLightDelegate::GetVisible(const SdfPath& id) +{ + TF_UNUSED(id); + TF_DEBUG(MAYAHYDRALIB_DELEGATE_GET_VISIBLE) + .Msg("MtohDefaultLightDelegate::GetVisible(%s)\n", id.GetText()); + return true; +} + +void MtohDefaultLightDelegate::SetLightingOn(bool isLightingOn) +{ + if (_isLightingOn != isLightingOn) { + _isLightingOn = isLightingOn; + + RemovePrim(); + Populate(); + } +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/mayaHydra/hydraExtensions/delegates/defaultLightDelegate.h b/lib/mayaHydra/hydraExtensions/delegates/defaultLightDelegate.h new file mode 100644 index 0000000000..2e154a0fa4 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/delegates/defaultLightDelegate.h @@ -0,0 +1,74 @@ +// +// 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. +// +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +#ifndef MTOH_DEFAULT_LIGHT_DELEGATE_H +#define MTOH_DEFAULT_LIGHT_DELEGATE_H + +#include + +#include +#include +#include +#include +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +//! \brief MtohDefaultLightDelegate is a separate Hydra custom scene delegate to handle the default +//! lighting from Maya. We use another Hydra custom scene delegate to handle the other parts of the +//! Maya scene. See sceneDelegate.h in the mayaHydraLib project. If you want to know how to add a +//! custom scene index to this plug-in, then please see the registration.cpp file in the +//! mayaHydraLib project. + +class MtohDefaultLightDelegate + : public HdSceneDelegate + , public MayaHydraDelegate +{ +public: + MtohDefaultLightDelegate(const InitData& initData); + + ~MtohDefaultLightDelegate() override; + + void Populate() override; + void SetDefaultLight(const GlfSimpleLight& light); + void SetLightingOn(bool isLightingOn); + void SetSolidPrimitivesRootPaths(const SdfPathVector& solidPrimitivesPaths) + { + _solidPrimitivesRootPaths = solidPrimitivesPaths; + } + void RemovePrim(); + +protected: + GfMatrix4d GetTransform(const SdfPath& id) override; + VtValue Get(const SdfPath& id, const TfToken& key) override; + VtValue GetLightParamValue(const SdfPath& id, const TfToken& paramName) override; + bool GetVisible(const SdfPath& id) override; + +private: + GlfSimpleLight _light; + SdfPath _lightPath; + bool _isSupported = false; + /// Is used to avoid lighting any non solid wireframe prim (such as line/points prims) + SdfPathVector _solidPrimitivesRootPaths; + bool _isPopulated = false; + bool _isLightingOn = true; +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // MTOH_DEFAULT_LIGHT_DELEGATE_H diff --git a/lib/mayaHydra/hydraExtensions/delegates/delegate.cpp b/lib/mayaHydra/hydraExtensions/delegates/delegate.cpp new file mode 100644 index 0000000000..a9fed73957 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/delegates/delegate.cpp @@ -0,0 +1,47 @@ +// +// 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. +// +#include "delegate.h" + +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +TF_REGISTRY_FUNCTION(TfType) { TfType::Define(); } + +MayaHydraDelegate::MayaHydraDelegate(const InitData& initData) + : _mayaDelegateID(initData.delegateID) + , _name(initData.name) + , _engine(initData.engine) + , _taskController(initData.taskController) + , _isHdSt(initData.isHdSt) + , _producer(initData.producer) +{ +} + +void MayaHydraDelegate::SetParams(const MayaHydraParams& params) { _params = params; } + +void MayaHydraDelegate::SetCameraForSampling(SdfPath const& camID) +{ + _cameraPathForSampling = camID; +} + +GfInterval MayaHydraDelegate::GetCurrentTimeSamplingInterval() const +{ + return GfInterval(_params.motionSampleStart, _params.motionSampleEnd); +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/mayaHydra/hydraExtensions/delegates/delegate.h b/lib/mayaHydra/hydraExtensions/delegates/delegate.h new file mode 100644 index 0000000000..61b5bf66c5 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/delegates/delegate.h @@ -0,0 +1,204 @@ +// +// 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. +// +#ifndef MAYAHYDRALIB_DELEGATE_H +#define MAYAHYDRALIB_DELEGATE_H + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +PXR_NAMESPACE_OPEN_SCOPE + +class MayaHydraSceneProducer; + +/** + * \brief MayaHydraDelegate is the base class for delegate classes. + */ +class MayaHydraDelegate +{ +public: + /// Structure passed to initialize this class + struct InitData + { + inline InitData( + TfToken nameIn, + HdEngine& engineIn, + HdRenderIndex* renderIndexIn, + HdRendererPlugin* rendererPluginIn, + HdxTaskController* taskControllerIn, + const SdfPath& delegateIDIn, + bool isHdStIn, + MayaHydraSceneProducer* producerIn = nullptr) + : name(nameIn) + , engine(engineIn) + , renderIndex(renderIndexIn) + , rendererPlugin(rendererPluginIn) + , taskController(taskControllerIn) + , delegateID(delegateIDIn) + , isHdSt(isHdStIn) + , producer(producerIn) + { + } + + TfToken name; + HdEngine& engine; + HdRenderIndex* renderIndex; + HdRendererPlugin* rendererPlugin; + HdxTaskController* taskController; + SdfPath delegateID; + bool isHdSt; + MayaHydraSceneProducer* producer; + }; + + MAYAHYDRALIB_API + MayaHydraDelegate(const InitData& initData); + MAYAHYDRALIB_API + virtual ~MayaHydraDelegate() = default; + + virtual void Populate() = 0; + virtual void PreFrame(const MHWRender::MDrawContext& context) { } + virtual void PostFrame() { } + + MAYAHYDRALIB_API + virtual void SetParams(const MayaHydraParams& params); + const MayaHydraParams& GetParams() const { return _params; } + + const SdfPath& GetMayaDelegateID() { return _mayaDelegateID; } + TfToken GetName() { return _name; } + bool IsHdSt() { return _isHdSt; } + + virtual void PopulateSelectedPaths( + const MSelectionList& mayaSelection, + SdfPathVector& selectedSdfPaths, + const HdSelectionSharedPtr& selection) + { + } + + virtual bool AddPickHitToSelectionList( + const HdxPickHit& hit, + const MHWRender::MSelectionInfo& selectInfo, + MSelectionList& mayaSelection, + MPointArray& worldSpaceHitPts) + { + return false; + } + + void SetLightsEnabled(const bool enabled) { _lightsEnabled = enabled; } + bool GetLightsEnabled() { return _lightsEnabled; } + + inline HdEngine& GetEngine() { return _engine; } + inline HdxTaskController* GetTaskController() { return _taskController; } + + /// Calls that mirror UsdImagingDelegate + + /// Setup for the shutter open and close to be used for motion sampling. + MAYAHYDRALIB_API + void SetCameraForSampling(SdfPath const& id); + + /// Returns the current interval that will be used when using the + /// sample* API in the scene delegate. + MAYAHYDRALIB_API + GfInterval GetCurrentTimeSamplingInterval() const; + + /// Common function to return templated sample types + template + size_t SampleValues(size_t maxSampleCount, float* times, T* samples, Getter getValue) + { + if (ARCH_UNLIKELY(maxSampleCount == 0)) { + return 0; + } + // Fast path 1 sample at current-frame + if (maxSampleCount == 1 + || (!GetParams().motionSamplesEnabled() && GetParams().motionSampleStart == 0)) { + times[0] = 0.0f; + samples[0] = getValue(); + return 1; + } + + const GfInterval shutter = GetCurrentTimeSamplingInterval(); + // Shutter for [-1, 1] (size 2) should have a step of 2 for 2 samples, and 1 for 3 samples + // For sample size of 1 tStep is unused and we match USD and to provide t=shutterOpen + // sample. + const double tStep = maxSampleCount > 1 ? (shutter.GetSize() / (maxSampleCount - 1)) : 0; + const MTime mayaTime = MAnimControl::currentTime(); + size_t nSamples = 0; + double relTime = shutter.GetMin(); + + for (size_t i = 0; i < maxSampleCount; ++i) { + T sample; + { + MDGContextGuard guard(mayaTime + relTime); + sample = getValue(); + } + // We compare the sample to the previous in order to reduce sample count on output. + // Goal is to reduce the amount of samples/keyframes the Hydra delegate has to absorb. + if (!nSamples || sample != samples[nSamples - 1]) { + samples[nSamples] = std::move(sample); + times[nSamples] = relTime; + ++nSamples; + } + relTime += tStep; + } + return nSamples; + } + + MayaHydraSceneProducer* GetProducer() { return _producer; }; + +private: + MayaHydraParams _params; + + // Note that because there may not be a 1-to-1 relationship between + // a MayaHydraDelegate and a HdSceneDelegate, this may be different than + // "the" scene delegate id. In the case of MayaHydraSceneDelegate, + // which inherits from HdSceneDelegate, they are the same; but for, ie, + // MayaHydraALProxyDelegate, for which there are multiple HdSceneDelegates + // for each MayaHydraDelegate, the _mayaDelegateID is different from each + // HdSceneDelegate's id. + const SdfPath _mayaDelegateID; + SdfPath _cameraPathForSampling; + TfToken _name; + HdEngine& _engine; + HdxTaskController* _taskController; + bool _isHdSt = false; + bool _lightsEnabled = true; + + MayaHydraSceneProducer* _producer = nullptr; +}; + +using MayaHydraDelegatePtr = std::shared_ptr; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // MAYAHYDRALIB_DELEGATE_H diff --git a/lib/mayaHydra/hydraExtensions/delegates/delegateCtx.cpp b/lib/mayaHydra/hydraExtensions/delegates/delegateCtx.cpp new file mode 100644 index 0000000000..48e674672a --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/delegates/delegateCtx.cpp @@ -0,0 +1,248 @@ +// +// 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. +// +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +#include "delegateCtx.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +PXR_NAMESPACE_OPEN_SCOPE +// Bring the MayaHydra namespace into scope. +// The following code currently lives inside the pxr namespace, but it would make more sense to +// have it inside the MayaHydra namespace. This using statement allows us to use MayaHydra symbols +// from within the pxr namespace as if we were in the MayaHydra namespace. +// Remove this once the code has been moved to the MayaHydra namespace. +using namespace MayaHydra; + +namespace { + +static const SdfPath lightedObjectsPath = SdfPath(std::string("Lighted")); + +template SdfPath toSdfPath(const T& src); +template<> inline SdfPath toSdfPath(const MDagPath& dag) { + return DagPathToSdfPath(dag, false, false); +} +template<> inline SdfPath toSdfPath(const MRenderItem& ri) { + return RenderItemToSdfPath(ri, false); +} + +template SdfPath maybePrepend(const T& src, const SdfPath& inPath); +template<> inline SdfPath maybePrepend( + const MDagPath& , const SdfPath& inPath +) { + return inPath; +} +template<> inline SdfPath maybePrepend( + const MRenderItem& ri, const SdfPath& inPath +) { + // Prepend Maya node name, for organisation and readability. + std::string dependNodeNameString (MFnDependencyNode(ri.sourceDagPath().node()).name().asChar()); + SanitizeNameForSdfPath(dependNodeNameString); + return SdfPath(dependNodeNameString).AppendPath(inPath); +} + +///Returns false if this object should not be lighted, true if it should be lighted +template bool shouldBeLighted(const T& src); +//Template specialization for MDagPath +template<> inline bool shouldBeLighted(const MDagPath& dag) { + return (MFnDependencyNode(dag.node()).typeName().asChar() == TfToken("mesh")); +} +//Template specialization for MRenderItem +template<> inline bool shouldBeLighted(const MRenderItem& ri) { + + //Special case to recognize the Arnold skydome light + if (MayaHydraDelegateCtx::isRenderItem_aiSkyDomeLightTriangleShape(ri)){ + return false;//Don't light the sky dome light shape + } + + return (MHWRender::MGeometry::Primitive::kLines != ri.primitive() + && MHWRender::MGeometry::Primitive::kLineStrip != ri.primitive() + && MHWRender::MGeometry::Primitive::kPoints != ri.primitive()); +} + +template +SdfPath GetMayaPrimPath(const T& src) +{ + SdfPath mayaPath = toSdfPath(src); + if (mayaPath.IsEmpty() || mayaPath.IsAbsoluteRootPath()) + return {}; + + // We cannot append an absolute path (I.e : starting with "/") + if (mayaPath.IsAbsolutePath()) { + mayaPath = mayaPath.MakeRelativePath(SdfPath::AbsoluteRootPath()); + } + + mayaPath = maybePrepend(src, mayaPath); + + if (shouldBeLighted(src)) { + // Use a specific prefix when it's not an object that needs to interact with lights and shadows to be able + // We filter the objects that don't have this prefix in lights HdLightTokens->shadowCollection parameter + mayaPath = lightedObjectsPath.AppendPath(mayaPath); + } + + return mayaPath; +} + +SdfPath _GetRenderItemMayaPrimPath(const MRenderItem& ri) +{ + if (ri.InternalObjectId() == 0) + return {}; + + return GetMayaPrimPath(ri); +} + +SdfPath _GetPrimPath(const SdfPath& base, const MDagPath& dg) +{ + return base.AppendPath(GetMayaPrimPath(dg)); +} + +SdfPath _GetRenderItemPrimPath(const SdfPath& base, const MRenderItem& ri) +{ + return base.AppendPath(_GetRenderItemMayaPrimPath(ri)); +} + +SdfPath _GetRenderItemShaderPrimPath(const SdfPath& base, const MRenderItem& ri) +{ + return _GetRenderItemPrimPath(base, ri); +} + +SdfPath _GetMaterialPath(const SdfPath& base, const MObject& obj) +{ + MStatus status; + MFnDependencyNode node(obj, &status); + if (!status) { + return {}; + } + const auto* chr = node.name().asChar(); + if (chr == nullptr || chr[0] == '\0') { + return {}; + } + + std::string nodeName(chr); + SanitizeNameForSdfPath(nodeName); + return base.AppendPath(SdfPath(nodeName)); +} + +} // namespace + +// MayaHydraDelegateCtx is a set of common functions, and it is the aggregation of our +// MayaHydraDelegate base class and the hydra custom scene delegate class : HdSceneDelegate. +MayaHydraDelegateCtx::MayaHydraDelegateCtx(const InitData& initData) + : HdSceneDelegate(initData.renderIndex, initData.delegateID) + , MayaHydraDelegate(initData) + , _rprimPath(initData.delegateID.AppendPath(SdfPath(std::string("rprims")))) + , _sprimPath(initData.delegateID.AppendPath(SdfPath(std::string("sprims")))) + , _materialPath(initData.delegateID.AppendPath(SdfPath(std::string("materials")))) +{ + GetChangeTracker().AddCollection(TfToken("visible")); +} + +void MayaHydraDelegateCtx::InsertRprim( + const TfToken& typeId, + const SdfPath& id, + const SdfPath& instancerId) +{ + if (!instancerId.IsEmpty()) { + GetRenderIndex().InsertInstancer(this, instancerId); + } + GetRenderIndex().InsertRprim(typeId, this, id); +} + +void MayaHydraDelegateCtx::InsertSprim( + const TfToken& typeId, + const SdfPath& id, + HdDirtyBits initialBits) +{ + GetRenderIndex().InsertSprim(typeId, this, id); + GetChangeTracker().SprimInserted(id, initialBits); +} + +void MayaHydraDelegateCtx::RemoveRprim(const SdfPath& id) { GetRenderIndex().RemoveRprim(id); } + +void MayaHydraDelegateCtx::RemoveSprim(const TfToken& typeId, const SdfPath& id) +{ + GetRenderIndex().RemoveSprim(typeId, id); +} + +void MayaHydraDelegateCtx::RemoveInstancer(const SdfPath& id) +{ + GetRenderIndex().RemoveInstancer(id); +} + +SdfPath MayaHydraDelegateCtx::GetRprimPath() const { return _rprimPath; } + +SdfPath MayaHydraDelegateCtx::GetPrimPath(const MDagPath& dg, bool isSprim) +{ + if (isSprim) { + return _GetPrimPath(_sprimPath, dg); + } else { + return _GetPrimPath(_rprimPath, dg); + } +} + +SdfPath MayaHydraDelegateCtx::GetRenderItemPrimPath(const MRenderItem& ri) +{ + return _GetRenderItemPrimPath(_rprimPath, ri); +} + +SdfPath MayaHydraDelegateCtx::GetRenderItemShaderPrimPath(const MRenderItem& ri) +{ + return _GetRenderItemShaderPrimPath(_rprimPath, ri); +} + +SdfPath MayaHydraDelegateCtx::GetMaterialPath(const MObject& obj) +{ + return _GetMaterialPath(_materialPath, obj); +} + +SdfPath MayaHydraDelegateCtx::GetLightedPrimsRootPath() const +{ + return _rprimPath.AppendPath(lightedObjectsPath); +} + +bool MayaHydraDelegateCtx::isRenderItem_aiSkyDomeLightTriangleShape(const MRenderItem& renderItem) +{ + static const std::string _aiSkyDomeLight ("aiSkyDomeLight"); + + const auto prim = renderItem.primitive(); + MDagPath dag = renderItem.sourceDagPath(); + if( dag.isValid() && (MHWRender::MGeometry::Primitive::kTriangles == prim) && (MHWRender::MRenderItem::DecorationItem == renderItem.type()) ){ + std::string fpName = dag.fullPathName().asChar(); + if (fpName.find(_aiSkyDomeLight) != std::string::npos) { + //This render item is a aiSkyDomeLight + return true; + } + } + + return false; +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/mayaHydra/hydraExtensions/delegates/delegateCtx.h b/lib/mayaHydra/hydraExtensions/delegates/delegateCtx.h new file mode 100644 index 0000000000..7d8dc665fb --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/delegates/delegateCtx.h @@ -0,0 +1,110 @@ +// +// 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. +// +#ifndef MAYAHYDRALIB_DELEGATE_BASE_H +#define MAYAHYDRALIB_DELEGATE_BASE_H + +#include + +#include +#include +#include +#include + +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE +/** + * \brief MayaHydraDelegateCtx is a set of common functions, and it is the aggregation of our + * MayaHydraDelegate base class and the hydra custom scene delegate class : HdSceneDelegate. + */ +class MayaHydraDelegateCtx + : public HdSceneDelegate + , public MayaHydraDelegate +{ +protected: + MAYAHYDRALIB_API + MayaHydraDelegateCtx(const InitData& initData); + +public: + enum RebuildFlags : uint32_t + { + RebuildFlagPrim = 1 << 1, + RebuildFlagCallbacks = 1 << 2, + }; + + using HdSceneDelegate::GetRenderIndex; + HdChangeTracker& GetChangeTracker() { return GetRenderIndex().GetChangeTracker(); } + + MAYAHYDRALIB_API + void InsertRprim(const TfToken& typeId, const SdfPath& id, const SdfPath& instancerId = {}); + MAYAHYDRALIB_API + void InsertSprim(const TfToken& typeId, const SdfPath& id, HdDirtyBits initialBits); + MAYAHYDRALIB_API + void RemoveRprim(const SdfPath& id); + MAYAHYDRALIB_API + void RemoveSprim(const TfToken& typeId, const SdfPath& id); + MAYAHYDRALIB_API + void RemoveInstancer(const SdfPath& id); + + /** + * @brief Is used to identify a Maya RenderItem as an aiSkydomeLight triangle shape. + * + * @param[in] renderItem is the Maya RenderItem which you want to test. + * + * @return returns true if it is an aiSkydomeLight triangle shape, false if not. + */ + static bool isRenderItem_aiSkyDomeLightTriangleShape(const MRenderItem& renderItem); + + virtual void RemoveAdapter(const SdfPath& id) { } + virtual void RecreateAdapter(const SdfPath& id, const MObject& obj) { } + virtual void RecreateAdapterOnIdle(const SdfPath& id, const MObject& obj) { } + virtual void RebuildAdapterOnIdle(const SdfPath& id, uint32_t flags) { } + virtual void UpdateDisplayStatusMaterial( + MHWRender::DisplayStatus displayStatus, + const MColor& wireframecolor) + { + } + + /// \brief Notifies the scene delegate when a material tag changes. + /// + /// \param id Id of the Material that changed its tag. + virtual void MaterialTagChanged(const SdfPath& id) { } + MAYAHYDRALIB_API + SdfPath GetPrimPath(const MDagPath& dg, bool isSprim); + MAYAHYDRALIB_API + SdfPath GetRenderItemPrimPath(const MRenderItem& ri); + MAYAHYDRALIB_API + SdfPath GetRenderItemShaderPrimPath(const MRenderItem& ri); + MAYAHYDRALIB_API + SdfPath GetMaterialPath(const MObject& obj); + + MAYAHYDRALIB_API + SdfPath + GetLightedPrimsRootPath() const; /// Get the root path for lighted objects, objects that don't have this in their SdfPath are not lighted + + MAYAHYDRALIB_API + SdfPath GetRprimPath() const; + +private: + SdfPath _rprimPath; + SdfPath _sprimPath; + SdfPath _materialPath; +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // MAYAHYDRALIB_DELEGATE_BASE_H diff --git a/lib/mayaHydra/hydraExtensions/delegates/delegateDebugCodes.cpp b/lib/mayaHydra/hydraExtensions/delegates/delegateDebugCodes.cpp new file mode 100644 index 0000000000..390b8e572c --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/delegates/delegateDebugCodes.cpp @@ -0,0 +1,125 @@ +// +// 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. +// +#include "delegateDebugCodes.h" + +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +// Some variables to enable debug printing information for our custom scene delegate + +TF_REGISTRY_FUNCTION(TfDebug) +{ + TF_DEBUG_ENVIRONMENT_SYMBOL( + MAYAHYDRALIB_DELEGATE_GET, "Print information about 'Get' calls to the delegates."); + + TF_DEBUG_ENVIRONMENT_SYMBOL( + MAYAHYDRALIB_DELEGATE_GET_CULL_STYLE, + "Print information about 'GetCullStyle' calls to the delegates."); + + TF_DEBUG_ENVIRONMENT_SYMBOL( + MAYAHYDRALIB_DELEGATE_GET_CURVE_TOPOLOGY, + "Print information about 'GetCurveTopology' calls to the delegates."); + + TF_DEBUG_ENVIRONMENT_SYMBOL( + MAYAHYDRALIB_DELEGATE_GET_DISPLAY_STYLE, + "Print information about 'GetDisplayStyle' calls to the delegates."); + + TF_DEBUG_ENVIRONMENT_SYMBOL( + MAYAHYDRALIB_DELEGATE_GET_DOUBLE_SIDED, + "Print information about 'GetDoubleSided' calls to the delegates."); + + TF_DEBUG_ENVIRONMENT_SYMBOL( + MAYAHYDRALIB_DELEGATE_GET_EXTENT, + "Print information about 'GetExtent' calls to the delegates."); + + TF_DEBUG_ENVIRONMENT_SYMBOL( + MAYAHYDRALIB_DELEGATE_GET_INSTANCER_ID, + "Print information about 'GetInstancerId' calls to the delegates."); + + TF_DEBUG_ENVIRONMENT_SYMBOL( + MAYAHYDRALIB_DELEGATE_GET_INSTANCE_INDICES, + "Print information about GetInstanceIndices calls to the delegates."); + + TF_DEBUG_ENVIRONMENT_SYMBOL( + MAYAHYDRALIB_DELEGATE_GET_LIGHT_PARAM_VALUE, + "Print information about 'GetLightParamValue' calls to the delegates."); + + TF_DEBUG_ENVIRONMENT_SYMBOL( + MAYAHYDRALIB_DELEGATE_GET_MATERIAL_ID, + "Print information about 'GetMaterialId' calls to the delegates."); + + TF_DEBUG_ENVIRONMENT_SYMBOL( + MAYAHYDRALIB_DELEGATE_GET_MATERIAL_RESOURCE, + "Print information about 'GetMaterialResource' calls to the " + "delegates."); + + TF_DEBUG_ENVIRONMENT_SYMBOL( + MAYAHYDRALIB_DELEGATE_GET_MESH_TOPOLOGY, + "Print information about 'GetMeshTopology' calls to the delegates."); + + TF_DEBUG_ENVIRONMENT_SYMBOL( + MAYAHYDRALIB_DELEGATE_GET_PRIMVAR_DESCRIPTORS, + "Print information about 'GetPrimvarDescriptors' calls to the " + "delegates."); + + TF_DEBUG_ENVIRONMENT_SYMBOL( + MAYAHYDRALIB_DELEGATE_GET_RENDER_TAG, + "Print information about 'GetRenderTag' calls to the delegates."); + + TF_DEBUG_ENVIRONMENT_SYMBOL( + MAYAHYDRALIB_DELEGATE_GET_SUBDIV_TAGS, + "Print information about 'GetSubdivTags' calls to the delegates."); + + TF_DEBUG_ENVIRONMENT_SYMBOL( + MAYAHYDRALIB_DELEGATE_GET_TRANSFORM, + "Print information about 'GetTransform' calls to the delegates."); + + TF_DEBUG_ENVIRONMENT_SYMBOL( + MAYAHYDRALIB_DELEGATE_GET_VISIBLE, + "Print information about 'GetVisible' calls to the delegates."); + + TF_DEBUG_ENVIRONMENT_SYMBOL( + MAYAHYDRALIB_DELEGATE_INSERTDAG, + "Print information about 'InsertDag' calls to the delegates."); + + TF_DEBUG_ENVIRONMENT_SYMBOL( + MAYAHYDRALIB_DELEGATE_IS_ENABLED, + "Print information about 'IsEnabled' calls to the delegates."); + + TF_DEBUG_ENVIRONMENT_SYMBOL( + MAYAHYDRALIB_DELEGATE_RECREATE_ADAPTER, + "Print information when the delegate recreates adapters."); + + TF_DEBUG_ENVIRONMENT_SYMBOL( + MAYAHYDRALIB_DELEGATE_REGISTRY, + "Print information about registration of MayaHydraDelegates."); + + TF_DEBUG_ENVIRONMENT_SYMBOL( + MAYAHYDRALIB_DELEGATE_SAMPLE_PRIMVAR, + "Print information about 'SamplePrimvar' calls to the delegates."); + + TF_DEBUG_ENVIRONMENT_SYMBOL( + MAYAHYDRALIB_DELEGATE_SAMPLE_TRANSFORM, + "Print information about 'SampleTransform' calls to the delegates."); + + TF_DEBUG_ENVIRONMENT_SYMBOL( + MAYAHYDRALIB_DELEGATE_SELECTION, + "Print information about mayaHydraLib delegate selection."); +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/mayaHydra/hydraExtensions/delegates/delegateDebugCodes.h b/lib/mayaHydra/hydraExtensions/delegates/delegateDebugCodes.h new file mode 100644 index 0000000000..ca002ad31a --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/delegates/delegateDebugCodes.h @@ -0,0 +1,58 @@ +// +// 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. +// +#ifndef MAYAHYDRALIB_DELEGATE_DEBUG_CODES_H +#define MAYAHYDRALIB_DELEGATE_DEBUG_CODES_H + +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +//! \brief Some variables to enable debug printing information for our custom scene delegate + +// clang-format off +TF_DEBUG_CODES( + MAYAHYDRALIB_DELEGATE_GET, + MAYAHYDRALIB_DELEGATE_GET_CULL_STYLE, + MAYAHYDRALIB_DELEGATE_GET_CURVE_TOPOLOGY, + MAYAHYDRALIB_DELEGATE_GET_DISPLAY_STYLE, + MAYAHYDRALIB_DELEGATE_GET_DOUBLE_SIDED, + MAYAHYDRALIB_DELEGATE_GET_EXTENT, + MAYAHYDRALIB_DELEGATE_GET_INSTANCER_ID, + MAYAHYDRALIB_DELEGATE_GET_INSTANCE_INDICES, + MAYAHYDRALIB_DELEGATE_GET_LIGHT_PARAM_VALUE, + MAYAHYDRALIB_DELEGATE_GET_MATERIAL_ID, + MAYAHYDRALIB_DELEGATE_GET_MATERIAL_RESOURCE, + MAYAHYDRALIB_DELEGATE_GET_MESH_TOPOLOGY, + MAYAHYDRALIB_DELEGATE_GET_PRIMVAR_DESCRIPTORS, + MAYAHYDRALIB_DELEGATE_GET_RENDER_TAG, + MAYAHYDRALIB_DELEGATE_GET_SUBDIV_TAGS, + MAYAHYDRALIB_DELEGATE_GET_TRANSFORM, + MAYAHYDRALIB_DELEGATE_GET_VISIBLE, + MAYAHYDRALIB_DELEGATE_INSERTDAG, + MAYAHYDRALIB_DELEGATE_IS_ENABLED, + MAYAHYDRALIB_DELEGATE_RECREATE_ADAPTER, + MAYAHYDRALIB_DELEGATE_REGISTRY, + MAYAHYDRALIB_DELEGATE_SAMPLE_PRIMVAR, + MAYAHYDRALIB_DELEGATE_SAMPLE_TRANSFORM, + MAYAHYDRALIB_DELEGATE_SELECTION, + MAYAHYDRALIB_DELEGATE_PRINT_LIGHTS_PARAMETERS_VALUES +); +// clang-format on + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // MAYAHYDRALIB_DELEGATE_DEBUG_CODES_H diff --git a/lib/mayaHydra/hydraExtensions/delegates/delegateRegistry.cpp b/lib/mayaHydra/hydraExtensions/delegates/delegateRegistry.cpp new file mode 100644 index 0000000000..2fabc3a581 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/delegates/delegateRegistry.cpp @@ -0,0 +1,123 @@ +// +// 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. +// +#include "delegateRegistry.h" + +#include + +#include +#include +#include +#include + +#include + +PXR_NAMESPACE_OPEN_SCOPE + +TF_INSTANTIATE_SINGLETON(MayaHydraDelegateRegistry); + +// MayaHydraDelegateRegistry is a singleton class to handle hydra delegates + +void MayaHydraDelegateRegistry::RegisterDelegate(const TfToken& name, DelegateCreator creator) +{ + auto& instance = GetInstance(); + for (auto it : instance._delegates) { + if (name == std::get<0>(it)) { + TF_DEBUG(MAYAHYDRALIB_DELEGATE_REGISTRY) + .Msg( + "MayaHydraDelegateRegistry::RegisterDelegate(%s) - existing " + "delegate\n", + name.GetText()); + return; + } + } + + TF_DEBUG(MAYAHYDRALIB_DELEGATE_REGISTRY) + .Msg("MayaHydraDelegateRegistry::RegisterDelegate(%s) - new delegate\n", name.GetText()); + instance._delegates.emplace_back(name, creator); +} + +std::vector MayaHydraDelegateRegistry::GetDelegateNames() +{ + LoadAllDelegates(); + const auto& instance = GetInstance(); + std::vector ret; + ret.reserve(instance._delegates.size()); + for (auto it : instance._delegates) { + ret.push_back(std::get<0>(it)); + } + return ret; +} + +std::vector +MayaHydraDelegateRegistry::GetDelegateCreators() +{ + LoadAllDelegates(); + const auto& instance = GetInstance(); + std::vector ret; + ret.reserve(instance._delegates.size()); + for (auto it : instance._delegates) { + ret.push_back(std::get<1>(it)); + } + return ret; +} + +void MayaHydraDelegateRegistry::SignalDelegatesChanged() +{ + for (const auto& s : GetInstance()._signals) { + s(); + } +} + +void MayaHydraDelegateRegistry::LoadAllDelegates() +{ + static std::once_flag loadAllOnce; + std::call_once(loadAllOnce, _LoadAllDelegates); +} + +void MayaHydraDelegateRegistry::InstallDelegatesChangedSignal(DelegatesChangedSignal signal) +{ + GetInstance()._signals.emplace_back(signal); +} + +void MayaHydraDelegateRegistry::_LoadAllDelegates() +{ + TF_DEBUG(MAYAHYDRALIB_DELEGATE_REGISTRY) + .Msg("MayaHydraDelegateRegistry::_LoadAllDelegates()\n"); + + TfRegistryManager::GetInstance().SubscribeTo(); + + const TfType& delegateType = TfType::Find(); + if (delegateType.IsUnknown()) { + TF_CODING_ERROR("Could not find MayaHydraDelegate type"); + return; + } + + std::set delegateTypes; + delegateType.GetAllDerivedTypes(&delegateTypes); + + PlugRegistry& plugReg = PlugRegistry::GetInstance(); + + for (auto& subType : delegateTypes) { + const PlugPluginPtr pluginForType = plugReg.GetPluginForType(subType); + if (!pluginForType) { + TF_CODING_ERROR("Could not find plugin for '%s'", subType.GetTypeName().c_str()); + return; + } + pluginForType->Load(); + } +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/mayaHydra/hydraExtensions/delegates/delegateRegistry.h b/lib/mayaHydra/hydraExtensions/delegates/delegateRegistry.h new file mode 100644 index 0000000000..177647b72d --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/delegates/delegateRegistry.h @@ -0,0 +1,76 @@ +// +// 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. +// +#ifndef MAYAHYDRALIB_DELEGATE_REGISTRY_H +#define MAYAHYDRALIB_DELEGATE_REGISTRY_H + +#include + +#include +#include +#include + +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +/** + * \brief MayaHydraDelegateRegistry is a singleton class to handle hydra delegates + */ +class MayaHydraDelegateRegistry : public TfSingleton +{ + friend class TfSingleton; + MAYAHYDRALIB_API + MayaHydraDelegateRegistry() = default; + +public: + /// function creates and returns a pointer to a MayaHydraDelegate - may return + /// a nullptr indicate failure, or that the delegate is currently disabled + using DelegateCreator = std::function; + + MAYAHYDRALIB_API + static void RegisterDelegate(const TfToken& name, DelegateCreator creator); + MAYAHYDRALIB_API + static std::vector GetDelegateNames(); + MAYAHYDRALIB_API + static std::vector GetDelegateCreators(); + + /// Signal that some delegate types are now either valid or invalid. + /// ie, say some delegate type is only useful / works when a certain maya + /// plug-in is loaded - you would call this every time that plugin was loaded + /// or unloaded. + MAYAHYDRALIB_API + static void SignalDelegatesChanged(); + + /// Find all MayaHydraDelegate plug-ins, and load them all + MAYAHYDRALIB_API + static void LoadAllDelegates(); + + using DelegatesChangedSignal = std::function; + + MAYAHYDRALIB_API + static void InstallDelegatesChangedSignal(DelegatesChangedSignal signal); + +private: + static void _LoadAllDelegates(); + + std::vector> _delegates; + std::vector _signals; +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // MAYAHYDRALIB_DELEGATE_REGISTRY_H diff --git a/lib/mayaHydra/hydraExtensions/delegates/params.h b/lib/mayaHydra/hydraExtensions/delegates/params.h new file mode 100644 index 0000000000..bff5a017a1 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/delegates/params.h @@ -0,0 +1,47 @@ +// +// 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. +// +#ifndef MAYAHYDRALIB_PARAMS_H +#define MAYAHYDRALIB_PARAMS_H + +#include + +#include + +PXR_NAMESPACE_OPEN_SCOPE + +/** + * \brief MayaHydraParams are the global parameters for this plug-in. + * Please note that we add to these parameters the parameters read from the chosen hydra render + * delegate and expose them in our UI with the MEL function mtohRenderOverride_AddAttribute see + * renderGlobals.cpp. + */ +struct MayaHydraParams +{ + bool proxyPurpose = true; + bool renderPurpose = false; + bool guidePurpose = false; + int textureMemoryPerTexture = 4 * 1024 * 1024; + int maximumShadowMapResolution = 2048; + float motionSampleStart = 0; + float motionSampleEnd = 0; + bool displaySmoothMeshes = true; + + bool motionSamplesEnabled() const { return motionSampleStart != 0 || motionSampleEnd != 0; } +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // MAYAHYDRALIB_PARAMS_H diff --git a/lib/mayaHydra/hydraExtensions/delegates/sceneDelegate.cpp b/lib/mayaHydra/hydraExtensions/delegates/sceneDelegate.cpp new file mode 100644 index 0000000000..4f13ba0e7c --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/delegates/sceneDelegate.cpp @@ -0,0 +1,1612 @@ +// +// 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. +// +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +#include "sceneDelegate.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +int _profilerCategory = MProfiler::addCategory( + "MayaHydraSceneDelegate (mayaHydra)", + "Events for MayaHydraSceneDelegate"); + +#if PXR_VERSION < 2308 +#error USD version v0.23.08+ required +#endif + +#if MAYA_API_VERSION < 20240000 +#error Maya API version 2024+ required +#endif + +namespace { + +// Pixar macros require Pixar namespace. +PXR_NAMESPACE_USING_DIRECTIVE + +TF_DEFINE_ENV_SETTING(MAYA_HYDRA_USE_MESH_ADAPTER, false, + "Use mesh adapter instead of MRenderItem for Maya meshes."); + +bool useMeshAdapter() { + static bool uma = TfGetEnvSetting(MAYA_HYDRA_USE_MESH_ADAPTER); + return uma; +} + +bool filterMesh(const MRenderItem& ri) { + return useMeshAdapter() ? + // Filter our mesh render items, and let the mesh adapter handle Maya + // meshes. The MRenderItem::name() for meshes is "StandardShadedItem", + // their MRenderItem::type() is InternalMaterialItem, but + // this type can also be used for other purposes, e.g. face groups, so + // using the name is more appropriate. + (ri.name() == "StandardShadedItem") : false; +} + +} + +PXR_NAMESPACE_OPEN_SCOPE +// Bring the MayaHydra namespace into scope. +// The following code currently lives inside the pxr namespace, but it would make more sense to +// have it inside the MayaHydra namespace. This using statement allows us to use MayaHydra symbols +// from within the pxr namespace as if we were in the MayaHydra namespace. +// Remove this once the code has been moved to the MayaHydra namespace. +using namespace MayaHydra; + +SdfPath MayaHydraSceneDelegate::_fallbackMaterial; +SdfPath MayaHydraSceneDelegate::_mayaDefaultMaterialPath; // Common to all scene delegates +VtValue MayaHydraSceneDelegate::_mayaDefaultMaterial; + +namespace { + +void _onDagNodeAdded(MObject& obj, void* clientData) +{ + reinterpret_cast(clientData)->OnDagNodeAdded(obj); +} + +void _onDagNodeRemoved(MObject& obj, void* clientData) +{ + reinterpret_cast(clientData)->OnDagNodeRemoved(obj); +} + +const MString defaultLightSet("defaultLightSet"); + +void _connectionChanged(MPlug& srcPlug, MPlug& destPlug, bool made, void* clientData) +{ + TF_UNUSED(made); + const auto srcObj = srcPlug.node(); + if (!srcObj.hasFn(MFn::kTransform)) { + return; + } + const auto destObj = destPlug.node(); + if (!destObj.hasFn(MFn::kSet)) { + return; + } + if (srcPlug != MayaAttrs::dagNode::instObjGroups) { + return; + } + MStatus status; + MFnDependencyNode destNode(destObj, &status); + if (ARCH_UNLIKELY(!status)) { + return; + } + if (destNode.name() != defaultLightSet) { + return; + } + auto* delegate = reinterpret_cast(clientData); + MDagPath dag; + status = MDagPath::getAPathTo(srcObj, dag); + if (ARCH_UNLIKELY(!status)) { + return; + } + unsigned int shapesBelow = 0; + dag.numberOfShapesDirectlyBelow(shapesBelow); + for (auto i = decltype(shapesBelow) { 0 }; i < shapesBelow; ++i) { + auto dagCopy = dag; + dagCopy.extendToShapeDirectlyBelow(i); + delegate->UpdateLightVisibility(dagCopy); + } +} + +template inline bool _FindAdapter(const SdfPath&, F) { return false; } + +template +inline bool _FindAdapter(const SdfPath& id, F f, const M0& m0, const M&... m) +{ + auto* adapterPtr = TfMapLookupPtr(m0, id); + if (adapterPtr == nullptr) { + return _FindAdapter(id, f, m...); + } else { + f(static_cast(adapterPtr->get())); + return true; + } +} + +template inline bool _RemoveAdapter(const SdfPath&, F) { return false; } + +template +inline bool _RemoveAdapter(const SdfPath& id, F f, M0& m0, M&... m) +{ + auto* adapterPtr = TfMapLookupPtr(m0, id); + if (adapterPtr == nullptr) { + return _RemoveAdapter(id, f, m...); + } else { + f(static_cast(adapterPtr->get())); + m0.erase(id); + return true; + } +} + +template inline R _GetDefaultValue() { return {}; } + +// This will be nicer to use with automatic parameter deduction for lambdas in +// C++14. +template inline R _GetValue(const SdfPath&, F) +{ + return _GetDefaultValue(); +} + +template +inline R _GetValue(const SdfPath& id, F f, const M0& m0, const M&... m) +{ + auto* adapterPtr = TfMapLookupPtr(m0, id); + if (adapterPtr == nullptr) { + return _GetValue(id, f, m...); + } else { + return f(static_cast(adapterPtr->get())); + } +} + +template inline void _MapAdapter(F) +{ + // Do nothing. +} + +template +inline void _MapAdapter(F f, const M0& m0, const M&... m) +{ + for (auto& it : m0) { + f(static_cast(it.second.get())); + } + _MapAdapter(f, m...); +} + +} // namespace + +// clang-format off +TF_DEFINE_PRIVATE_TOKENS( + _tokens, + + (MayaHydraSceneDelegate) + ((MayaDefaultMaterial, "__maya_default_material__")) + (diffuseColor) + (emissiveColor) + (roughness) + (MayaHydraMeshPoints) + (constantLighting) +); +// clang-format on + +TF_REGISTRY_FUNCTION(TfType) +{ + TfType::Define>(); +} + +TF_REGISTRY_FUNCTION_WITH_TAG(MayaHydraDelegateRegistry, MayaHydraSceneDelegate) +{ + MayaHydraDelegateRegistry::RegisterDelegate( + _tokens->MayaHydraSceneDelegate, + [](const MayaHydraDelegate::InitData& initData) -> MayaHydraDelegatePtr { + return std::static_pointer_cast( + std::make_shared(initData)); + }); +} + +// MayaHydraSceneDelegate is an Hydra custom scene delegate used to translate from a Maya scene to +// hydra. If you want to know how to add a custom scene index to this plug-in, then please see the +// registration.cpp file. +MayaHydraSceneDelegate::MayaHydraSceneDelegate(const InitData& initData) + : MayaHydraDelegateCtx(initData) +{ + // TfDebug::Enable(MAYAHYDRALIB_DELEGATE_GET_MATERIAL_ID);//Enable this line to print to the + // output window all SceneDelegate::GetMaterialID(...) calls + // TfDebug::Enable(MAYAHYDRALIB_DELEGATE_GET); //Enable this line to print to the output window + // all SceneDelegate::Get(...) calls + + // Enable the following line to print to the output window the materials parameters type and + // values when there is a change in one of them. + // TfDebug::Enable(MAYAHYDRALIB_ADAPTER_MATERIALS_PRINT_PARAMETERS_VALUES); + + // Enable the following line to print to the output window the lights parameters type and + // values. TfDebug::Enable(MAYAHYDRALIB_DELEGATE_PRINT_LIGHTS_PARAMETERS_VALUES); + + static std::once_flag once; + std::call_once(once, []() { + _mayaDefaultMaterialPath = SdfPath::AbsoluteRootPath().AppendChild( + _tokens->MayaDefaultMaterial); // Is an absolute path, not linked to a scene delegate + _mayaDefaultMaterial = MayaHydraSceneDelegate::CreateMayaDefaultMaterial(); + _fallbackMaterial = SdfPath::EmptyPath(); // Empty path for hydra fallback material + }); +} + +MayaHydraSceneDelegate::~MayaHydraSceneDelegate() +{ + for (auto callback : _callbacks) { + MMessage::removeCallback(callback); + } + _MapAdapter( + [](MayaHydraAdapter* a) { a->RemoveCallbacks(); }, + _renderItemsAdapters, + _shapeAdapters, + _lightAdapters, + _materialAdapters); +} + +VtValue MayaHydraSceneDelegate::CreateMayaDefaultMaterial() +{ + static const MColor kDefaultGrayColor = MColor(0.5f, 0.5f, 0.5f) * 0.8f; + + HdMaterialNetworkMap networkMap; + HdMaterialNetwork network; + HdMaterialNode node; + node.identifier = UsdImagingTokens->UsdPreviewSurface; + node.path = _mayaDefaultMaterialPath; + node.parameters.insert( + { _tokens->diffuseColor, + VtValue(GfVec3f(kDefaultGrayColor[0], kDefaultGrayColor[1], kDefaultGrayColor[2])) }); + network.nodes.push_back(std::move(node)); + networkMap.map.insert({ HdMaterialTerminalTokens->surface, std::move(network) }); + networkMap.terminals.push_back(_mayaDefaultMaterialPath); + return VtValue(networkMap); +} + +void MayaHydraSceneDelegate::_AddRenderItem(const MayaHydraRenderItemAdapterPtr& ria) +{ + const SdfPath& primPath = ria->GetID(); + _renderItemsAdaptersFast.insert({ ria->GetFastID(), ria }); + _renderItemsAdapters.insert({ primPath, ria }); +} + +void MayaHydraSceneDelegate::_RemoveRenderItem(const MayaHydraRenderItemAdapterPtr& ria) +{ + const SdfPath& primPath = ria->GetID(); + _renderItemsAdaptersFast.erase(ria->GetFastID()); + _renderItemsAdapters.erase(primPath); +} + +void MayaHydraSceneDelegate::HandleCompleteViewportScene( + const MDataServerOperation::MViewportScene& scene, + MFrameContext::DisplayStyle displayStyle) +{ + const bool playbackRunning = MAnimControl::isPlaying(); + + if (_isPlaybackRunning != playbackRunning) { + // The value has changed, we are calling SetPlaybackChanged so that every render item that + // has its visibility dependent on the playback should dirty its hydra visibility flag so + // its gets recomputed. + for (auto it = _renderItemsAdapters.begin(); it != _renderItemsAdapters.end(); it++) { + it->second->SetPlaybackChanged(); + } + + _isPlaybackRunning = playbackRunning; + } + + // First loop to get rid of removed items + constexpr int kInvalidId = 0; + for (size_t i = 0; i < scene.mRemovalCount; i++) { + int fastId = scene.mRemovals[i]; + if (fastId == kInvalidId) + continue; + MayaHydraRenderItemAdapterPtr ria = nullptr; + if (_GetRenderItem(fastId, ria)) { + _RemoveRenderItem(ria); + } + assert(ria != nullptr); + } + + // My version, does minimal update + // This loop could, in theory, be parallelized. Unclear how large the gains would be, but maybe + // nothing to lose unless there is some internal contention in USD. + for (size_t i = 0; i < scene.mCount; i++) { + auto flags = scene.mFlags[i]; + if (flags == 0) { + continue; + } + + auto& ri = *scene.mItems[i]; + + // Meshes can optionally be handled by the mesh adapter, rather than by + // render items. + if (filterMesh(ri)) { + continue; + } + + int fastId = ri.InternalObjectId(); + MayaHydraRenderItemAdapterPtr ria = nullptr; + if (!_GetRenderItem(fastId, ria)) { + const SdfPath slowId = GetRenderItemPrimPath(ri); + if (slowId.IsEmpty()){ + continue; + } + // MAYA-128021: We do not currently support maya instances. + MDagPath dagPath(ri.sourceDagPath()); + ria = std::make_shared(dagPath, slowId, fastId, GetProducer(), ri); + + //Update the render item adapter if this render item is an aiSkydomeLight shape + ria->SetIsRenderITemAnaiSkydomeLightTriangleShape(isRenderItem_aiSkyDomeLightTriangleShape(ri)); + + _AddRenderItem(ria); + } + + SdfPath material; + MObject shadingEngineNode; + if (!_GetRenderItemMaterial(ri, material, shadingEngineNode)) { + if (material != kInvalidMaterial) { + _CreateMaterial(material, shadingEngineNode); + } + } + + if (flags & MDataServerOperation::MViewportScene::MVS_changedEffect) { + ria->SetMaterial(material); + } + + MColor wireframeColor; + MHWRender::DisplayStatus displayStatus = MHWRender::kNoStatus; + + MDagPath dagPath = ri.sourceDagPath(); + if (dagPath.isValid()) { + wireframeColor = MGeometryUtilities::wireframeColor( + dagPath); // This is a color managed VP2 color, it will need to be unmanaged at some + // point + displayStatus = MGeometryUtilities::displayStatus(dagPath); + } + + const MayaHydraRenderItemAdapter::UpdateFromDeltaData data( + ri, flags, wireframeColor, displayStatus); + ria->UpdateFromDelta(data); + if (flags & MDataServerOperation::MViewportScene::MVS_changedMatrix) { + ria->UpdateTransform(ri); + } + } +} + +void MayaHydraSceneDelegate::Populate() +{ + MayaHydraAdapterRegistry::LoadAllPlugin(); + auto& renderIndex = GetRenderIndex(); + MStatus status; + MItDag dagIt(MItDag::kDepthFirst); + dagIt.traverseUnderWorld(true); + if (useMeshAdapter()) { + for (; !dagIt.isDone(); dagIt.next()) { + MDagPath path; + dagIt.getPath(path); + InsertDag(path); + } + } + else { + for (; !dagIt.isDone(); dagIt.next()) { + MObject node = dagIt.currentItem(&status); + if (status != MS::kSuccess) + continue; + OnDagNodeAdded(node); + } + } + + auto id = MDGMessage::addNodeAddedCallback(_onDagNodeAdded, "dagNode", this, &status); + if (status) { + _callbacks.push_back(id); + } + id = MDGMessage::addNodeRemovedCallback(_onDagNodeRemoved, "dagNode", this, &status); + if (status) { + _callbacks.push_back(id); + } + id = MDGMessage::addConnectionCallback(_connectionChanged, this, &status); + if (status) { + _callbacks.push_back(id); + } + + // Adding materials sprim to the render index. + if (renderIndex.IsSprimTypeSupported(HdPrimTypeTokens->material)) { + renderIndex.InsertSprim(HdPrimTypeTokens->material, this, _mayaDefaultMaterialPath); + } +} + +MayaHydraSceneDelegate::LightDagPathMap +MayaHydraSceneDelegate::_GetActiveLightPaths() const +{ + LightDagPathMap activeLightPaths; + activeLightPaths.reserve(_lightAdapters.size()); + // By the time this function is called _lightAdapters should already have been populated + // with both Maya and Arnold light adapters. The adapters contain the DagPath information + // we store it here in unordered_map for fast retrieval + for(const auto& entry : _lightAdapters) + { + const auto& dagpath = entry.second->GetDagPath(); + activeLightPaths.emplace(dagpath.fullPathName().asChar(), + dagpath); + } + return activeLightPaths; +} + +// +void MayaHydraSceneDelegate::PreFrame(const MHWRender::MDrawContext& context) +{ + bool useDefaultMaterial + = (context.getDisplayStyle() & MHWRender::MFrameContext::kDefaultMaterial); + if (useDefaultMaterial != _useDefaultMaterial) { + _useDefaultMaterial = useDefaultMaterial; + if (useMeshAdapter()) { + for (const auto& shape : _shapeAdapters) + shape.second->MarkDirty(HdChangeTracker::DirtyMaterialId); + } + } + + const bool xRayEnabled = (context.getDisplayStyle() & MHWRender::MFrameContext::kXray); + if (xRayEnabled != _xRayEnabled) { + _xRayEnabled = xRayEnabled; + for (auto& matAdapter : _materialAdapters) + matAdapter.second->EnableXRayShadingMode(_xRayEnabled); + } + + if (!_materialTagsChanged.empty()) { + if (IsHdSt()) { + for (const auto& id : _materialTagsChanged) { + if (_GetValue( + id, + [](MayaHydraMaterialAdapter* a) { return a->UpdateMaterialTag(); }, + _materialAdapters)) { + auto& renderIndex = GetRenderIndex(); + for (const auto& rprimId : renderIndex.GetRprimIds()) { + const auto* rprim = renderIndex.GetRprim(rprimId); + if (rprim != nullptr && rprim->GetMaterialId() == id) { + RebuildAdapterOnIdle( + rprim->GetId(), MayaHydraDelegateCtx::RebuildFlagPrim); + } + } + } + } + } + _materialTagsChanged.clear(); + } + + if (!_lightsToAdd.empty()) { + for (auto& lightToAdd : _lightsToAdd) { + MDagPath dag; + MStatus status = MDagPath::getAPathTo(lightToAdd.first, dag); + if (!status) { + return; + } + CreateLightAdapter(dag); + } + _lightsToAdd.clear(); + } + + if (useMeshAdapter() && !_addedNodes.empty()) { + for (const auto& obj : _addedNodes) { + if (obj.isNull()) { + continue; + } + MDagPath dag; + MStatus status = MDagPath::getAPathTo(obj, dag); + if (!status) { + return; + } + // We need to check if there is an instanced shape below this dag + // and insert it as well, because they won't be inserted. + if (dag.hasFn(MFn::kTransform)) { + const auto childCount = dag.childCount(); + for (auto child = decltype(childCount) { 0 }; child < childCount; ++child) { + auto dagCopy = dag; + dagCopy.push(dag.child(child)); + if (dagCopy.isInstanced() && dagCopy.instanceNumber() > 0) { + AddNewInstance(dagCopy); + } + } + } else { + InsertDag(dag); + } + } + _addedNodes.clear(); + } + + // We don't need to rebuild something that's already being recreated. + // Since we have a few elements, linear search over vectors is going to + // be okay. + if (!_adaptersToRecreate.empty()) { + for (const auto& it : _adaptersToRecreate) { + RecreateAdapter(std::get<0>(it), std::get<1>(it)); + for (auto itr = _adaptersToRebuild.begin(); itr != _adaptersToRebuild.end(); ++itr) { + if (std::get<0>(it) == std::get<0>(*itr)) { + _adaptersToRebuild.erase(itr); + break; + } + } + } + _adaptersToRecreate.clear(); + } + if (!_adaptersToRebuild.empty()) { + for (const auto& it : _adaptersToRebuild) { + _FindAdapter( + std::get<0>(it), + [&](MayaHydraAdapter* a) { + if (std::get<1>(it) & MayaHydraDelegateCtx::RebuildFlagCallbacks) { + a->RemoveCallbacks(); + a->CreateCallbacks(); + } + if (std::get<1>(it) & MayaHydraDelegateCtx::RebuildFlagPrim) { + a->RemovePrim(); + a->Populate(); + } + }, + _shapeAdapters, + _lightAdapters, + _materialAdapters); + } + _adaptersToRebuild.clear(); + } + if (!IsHdSt()) { + return; + } + + LightDagPathMap activeLightPaths = _GetActiveLightPaths(); + constexpr auto considerAllSceneLights = MHWRender::MDrawContext::kFilteredIgnoreLightLimit; + MStatus status; + const auto numLights = context.numberOfActiveLights(considerAllSceneLights, &status); + + if ((!status || numLights == 0) && (0 == activeLightPaths.size())) { + _MapAdapter( + [](MayaHydraLightAdapter* a) { a->SetLightingOn(false); }, + _lightAdapters); // Turn off all lights + return; + } + + MIntArray intVals; + MMatrix matrixVal; + for (auto i = decltype(numLights) { 0 }; i < numLights; ++i) { + auto* lightParam = context.getLightParameterInformation(i, considerAllSceneLights); + if (lightParam == nullptr) { + continue; + } + const auto lightPath = lightParam->lightPath(); + if (!lightPath.isValid()) { + continue; + } + if (IsUfeItemFromMayaUsd(lightPath)) { + // If this is a UFE light created by maya-usd, it will have already added it to Hydra + continue; + } + + // we do a fast look up here for any new lights that may have been added + auto found = activeLightPaths.find(lightPath.fullPathName().asChar()); + if (found == activeLightPaths.end()) + activeLightPaths.emplace(lightPath.fullPathName().asChar(), + lightPath); + + if (!lightParam->getParameter(MHWRender::MLightParameterInformation::kShadowOn, intVals) + || intVals.length() < 1 || intVals[0] != 1) { + continue; + } + + if (lightParam->getParameter( + MHWRender::MLightParameterInformation::kShadowViewProj, matrixVal)) { + _FindAdapter( + GetPrimPath(lightPath, true), + [&matrixVal](MayaHydraLightAdapter* a) { + a->SetShadowProjectionMatrix(GetGfMatrixFromMaya(matrixVal)); + }, + _lightAdapters); + } + } + + // Turn on active lights, turn off non-active lights, and add non-created active lights. + _MapAdapter( + [&](MayaHydraLightAdapter* a) { + auto lgtAdapter = activeLightPaths.find(a->GetDagPath().fullPathName().asChar()); + if (lgtAdapter != activeLightPaths.end()) { + a->SetLightingOn(true); + activeLightPaths.erase(lgtAdapter); + } else { + a->SetLightingOn(false); + } + }, + _lightAdapters); + for(const auto& entry : activeLightPaths) { + CreateLightAdapter(entry.second); + } +} + +void MayaHydraSceneDelegate::RemoveAdapter(const SdfPath& id) +{ + if (!_RemoveAdapter( + id, + [](MayaHydraAdapter* a) { + a->RemoveCallbacks(); + a->RemovePrim(); + }, + _renderItemsAdapters, + _shapeAdapters, + _lightAdapters, + _materialAdapters)) { + TF_WARN( + "MayaHydraSceneDelegate::RemoveAdapter(%s) -- Adapter does not exists", id.GetText()); + } +} + +void MayaHydraSceneDelegate::RecreateAdapterOnIdle(const SdfPath& id, const MObject& obj) +{ + // We expect this to be a small number of objects, so using a simple linear + // search and a vector is generally a good choice. + for (auto& it : _adaptersToRecreate) { + if (std::get<0>(it) == id) { + std::get<1>(it) = obj; + return; + } + } + _adaptersToRecreate.emplace_back(id, obj); +} + +void MayaHydraSceneDelegate::MaterialTagChanged(const SdfPath& id) +{ + if (std::find(_materialTagsChanged.begin(), _materialTagsChanged.end(), id) + == _materialTagsChanged.end()) { + _materialTagsChanged.push_back(id); + } +} + +void MayaHydraSceneDelegate::RebuildAdapterOnIdle(const SdfPath& id, uint32_t flags) +{ + // We expect this to be a small number of objects, so using a simple linear + // search and a vector is generally a good choice. + for (auto& it : _adaptersToRebuild) { + if (std::get<0>(it) == id) { + std::get<1>(it) |= flags; + return; + } + } + _adaptersToRebuild.emplace_back(id, flags); +} + +void MayaHydraSceneDelegate::RecreateAdapter(const SdfPath& id, const MObject& obj) +{ + if (_RemoveAdapter( + id, + [](MayaHydraAdapter* a) { + a->RemoveCallbacks(); + a->RemovePrim(); + }, + _lightAdapters)) { + if (MObjectHandle(obj).isValid()) { + OnDagNodeAdded(obj); + } else { + TF_DEBUG(MAYAHYDRALIB_DELEGATE_RECREATE_ADAPTER) + .Msg( + "Light prim (%s) not re-created because node no " + "longer valid\n", + id.GetText()); + } + return; + } + + if (useMeshAdapter() && _RemoveAdapter( + id, + [](MayaHydraAdapter* a) { + a->RemoveCallbacks(); + a->RemovePrim(); + }, + _shapeAdapters)) { + MFnDagNode dgNode(obj); + MDagPath path; + dgNode.getPath(path); + if (path.isValid() && MObjectHandle(obj).isValid()) { + TF_DEBUG(MAYAHYDRALIB_DELEGATE_RECREATE_ADAPTER) + .Msg( + "Shape prim (%s) re-created for dag path (%s)\n", + id.GetText(), + path.fullPathName().asChar()); + InsertDag(path); + } else { + TF_DEBUG(MAYAHYDRALIB_DELEGATE_RECREATE_ADAPTER) + .Msg( + "Shape prim (%s) not re-created because node no " + "longer valid\n", + id.GetText()); + } + return; + } + + if (_RemoveAdapter( + id, + [](MayaHydraMaterialAdapter* a) { + a->RemoveCallbacks(); + a->RemovePrim(); + }, + _materialAdapters)) { + auto& renderIndex = GetRenderIndex(); + auto& changeTracker = renderIndex.GetChangeTracker(); + for (const auto& rprimId : renderIndex.GetRprimIds()) { + const auto* rprim = renderIndex.GetRprim(rprimId); + if (rprim != nullptr && rprim->GetMaterialId() == id) { + changeTracker.MarkRprimDirty(rprimId, HdChangeTracker::DirtyMaterialId); + } + } + if (MObjectHandle(obj).isValid()) { + TF_DEBUG(MAYAHYDRALIB_DELEGATE_RECREATE_ADAPTER) + .Msg( + "Material prim (%s) re-created for node (%s)\n", + id.GetText(), + MFnDependencyNode(obj).name().asChar()); + _CreateMaterial(GetMaterialPath(obj), obj); + } else { + TF_DEBUG(MAYAHYDRALIB_DELEGATE_RECREATE_ADAPTER) + .Msg( + "Material prim (%s) not re-created because node no " + "longer valid\n", + id.GetText()); + } + + } else { + TF_WARN( + "MayaHydraSceneDelegate::RecreateAdapterOnIdle(%s) -- Adapter does " + "not exists", + id.GetText()); + } +} + +MayaHydraLightAdapterPtr MayaHydraSceneDelegate::GetLightAdapter(const SdfPath& id) +{ + auto iter = _lightAdapters.find(id); + return iter == _lightAdapters.end() ? nullptr : iter->second; +} + +MayaHydraMaterialAdapterPtr MayaHydraSceneDelegate::GetMaterialAdapter(const SdfPath& id) +{ + auto iter = _materialAdapters.find(id); + return iter == _materialAdapters.end() ? nullptr : iter->second; +} + +template +AdapterPtr MayaHydraSceneDelegate::_CreateAdapter( + const MDagPath& dag, + const std::function& adapterCreator, + Map& adapterMap, + bool isSprim) +{ + // Filter for whether we should even attempt to create the adapter + + if (!adapterCreator) { + return {}; + } + + if (IsUfeItemFromMayaUsd(dag)) { + // UFE items that have a Hydra representation will be added to Hydra by maya-usd + return {}; + } + + // Attempt to create the adapter + + TF_DEBUG(MAYAHYDRALIB_DELEGATE_INSERTDAG) + .Msg( + "MayaHydraSceneDelegate::_CreateAdapter::" + "found %s: %s\n", + MFnDependencyNode(dag.node()).typeName().asChar(), + dag.fullPathName().asChar()); + + const auto id = GetPrimPath(dag, isSprim); + if (TfMapLookupPtr(adapterMap, id) != nullptr) { + return {}; + } + auto adapter = adapterCreator(GetProducer(), dag); + if (adapter == nullptr || !adapter->IsSupported()) { + return {}; + } + adapter->Populate(); + adapter->CreateCallbacks(); + adapterMap.insert({ id, adapter }); + return adapter; +} + +MayaHydraLightAdapterPtr MayaHydraSceneDelegate::CreateLightAdapter(const MDagPath& dagPath) +{ + auto lightCreatorFunc = MayaHydraAdapterRegistry::GetLightAdapterCreator(dagPath); + return _CreateAdapter(dagPath, lightCreatorFunc, _lightAdapters, true); +} + +MayaHydraCameraAdapterPtr MayaHydraSceneDelegate::CreateCameraAdapter(const MDagPath& dagPath) +{ + auto cameraCreatorFunc = MayaHydraAdapterRegistry::GetCameraAdapterCreator(dagPath); + return _CreateAdapter(dagPath, cameraCreatorFunc, _cameraAdapters, true); +} + +MayaHydraShapeAdapterPtr MayaHydraSceneDelegate::CreateShapeAdapter(const MDagPath& dagPath) { + auto shapeCreatorFunc = MayaHydraAdapterRegistry::GetShapeAdapterCreator(dagPath); + return _CreateAdapter(dagPath, shapeCreatorFunc, _shapeAdapters); +} + +namespace { +bool GetShadingEngineNode(const MRenderItem& ri, MObject& shadingEngineNode) +{ + MDagPath dagPath = ri.sourceDagPath(); + if (dagPath.isValid()) { + MFnDagNode dagNode(dagPath.node()); + MObjectArray sets, comps; + dagNode.getConnectedSetsAndMembers(dagPath.instanceNumber(), sets, comps, true); + assert(sets.length() == comps.length()); + for (uint32_t i = 0; i < sets.length(); ++i) { + const MObject& object = sets[i]; + if (object.apiType() == MFn::kShadingEngine) { + // To support per-face shading, find the shading node matched with the render item + const MObject& comp = comps[i]; + MObject shadingComp = ri.shadingComponent(); + if (shadingComp.isNull() || comp.isNull() + || MFnComponent(comp).isEqual(shadingComp)) { + shadingEngineNode = object; + return true; + } + } + } + } + return false; +} +} // namespace + +bool MayaHydraSceneDelegate::_GetRenderItemMaterial( + const MRenderItem& ri, + SdfPath& material, + MObject& shadingEngineNode) +{ + if (MHWRender::MGeometry::Primitive::kLines == ri.primitive() + || MHWRender::MGeometry::Primitive::kLineStrip == ri.primitive()) { + material = _fallbackMaterial; // Use fallbackMaterial + constantLighting + displayColor + return true; + } + + if (GetShadingEngineNode(ri, shadingEngineNode)) + // Else try to find associated material node if this is a material shader. + // NOTE: The existing maya material support in hydra expects a shading engine node + { + material = GetMaterialPath(shadingEngineNode); + if (TfMapLookupPtr(_materialAdapters, material) != nullptr) { + return true; + } + } + + return false; +} + +// Analogous to MayaHydraSceneDelegate::InsertDag +bool MayaHydraSceneDelegate::_GetRenderItem(int fastId, MayaHydraRenderItemAdapterPtr& ria) +{ + // Using SdfPath as the hash table key is extremely slow. The cost appears to be GetPrimPath, + // which would depend on MdagPath, which is a wrapper on TdagPath. TdagPath is a very slow + // class and best to avoid in any performance- critical area. Simply workaround for the + // prototype is an additional lookup index based on InternalObjectID. Long term goal would be + // that the plug-in rarely, if ever, deals with TdagPath. + MayaHydraRenderItemAdapterPtr* result = TfMapLookupPtr(_renderItemsAdaptersFast, fastId); + + if (result != nullptr) { + // adapter already exists, return it + ria = *result; + return true; + } + + return false; +} + +void MayaHydraSceneDelegate::OnDagNodeAdded(const MObject& obj) +{ + if (obj.isNull()) + return; + + if (IsUfeItemFromMayaUsd(obj)) { + // UFE items that have a Hydra representation will be added to Hydra by maya-usd + return; + } + + // When not using the mesh adapter we care only about lights for this + // callback. It is used to create a LightAdapter when adding a new light + // in the scene for Hydra rendering. + if (auto lightFn = MayaHydraAdapterRegistry::GetLightAdapterCreator(obj)) { + _lightsToAdd.push_back({ obj, lightFn }); + } + else if (useMeshAdapter()) { + _addedNodes.push_back(obj); + } +} + +void MayaHydraSceneDelegate::OnDagNodeRemoved(const MObject& obj) +{ + const auto it + = std::remove_if(_lightsToAdd.begin(), _lightsToAdd.end(), [&obj](const auto& item) { + return item.first == obj; + }); + + if (it != _lightsToAdd.end()) { + _lightsToAdd.erase(it, _lightsToAdd.end()); + } + else if (useMeshAdapter()) { + const auto it = std::remove_if(_addedNodes.begin(), _addedNodes.end(), [&obj](const auto& item) { return item == obj; }); + + if (it != _addedNodes.end()) { + _addedNodes.erase(it, _addedNodes.end()); + } + } +} + +void MayaHydraSceneDelegate::InsertDag(const MDagPath& dag) +{ + TF_DEBUG(MAYAHYDRALIB_DELEGATE_INSERTDAG) + .Msg( + "MayaHydraSceneDelegate::InsertDag::" + "GetLightsEnabled()=%i\n", + GetLightsEnabled()); + // We don't care about transforms. + if (dag.hasFn(MFn::kTransform)) { + return; + } + + MFnDagNode dagNode(dag); + if (dagNode.isIntermediateObject()) { + return; + } + + if (IsUfeItemFromMayaUsd(dag)) { + // UFE items that have a Hydra representation will be added to Hydra by maya-usd + return; + } + + // Custom lights don't have MFn::kLight. + if (GetLightsEnabled()) { + if (CreateLightAdapter(dag)) + return; + } + if (CreateCameraAdapter(dag)) { + return; + } + // We are inserting a single prim and + // instancer for every instanced mesh. + if (dag.isInstanced() && dag.instanceNumber() > 0) { + return; + } + + auto adapter = CreateShapeAdapter(dag); + if (adapter) { + auto material = adapter->GetMaterial(); + if (material != MObject::kNullObj) { + const auto materialId = GetMaterialPath(material); + if (TfMapLookupPtr(_materialAdapters, materialId) == nullptr) { + _CreateMaterial(materialId, material); + } + } + } +} + +void MayaHydraSceneDelegate::UpdateLightVisibility(const MDagPath& dag) +{ + const auto id = GetPrimPath(dag, true); + _FindAdapter( + id, + [](MayaHydraLightAdapter* a) { + if (a->UpdateVisibility()) { + a->RemovePrim(); + a->Populate(); + a->InvalidateTransform(); + } + }, + _lightAdapters); +} + +// +void MayaHydraSceneDelegate::AddNewInstance(const MDagPath& dag) +{ + MDagPathArray dags; + MDagPath::getAllPathsTo(dag.node(), dags); + const auto dagsLength = dags.length(); + if (dagsLength == 0) { + return; + } + const auto masterDag = dags[0]; + const auto id = GetPrimPath(masterDag, false); + std::shared_ptr masterAdapter; + if (!TfMapLookup(_shapeAdapters, id, &masterAdapter) || masterAdapter == nullptr) { + return; + } + // If dags is 1, we have to recreate the adapter. + if (dags.length() == 1 || !masterAdapter->IsInstanced()) { + RecreateAdapterOnIdle(id, masterDag.node()); + } else { + // If dags is more than one, trigger rebuilding callbacks next call and + // mark dirty. + RebuildAdapterOnIdle(id, MayaHydraDelegateCtx::RebuildFlagCallbacks); + masterAdapter->MarkDirty( + HdChangeTracker::DirtyInstancer | HdChangeTracker::DirtyInstanceIndex + | HdChangeTracker::DirtyPrimvar); + } +} + +void MayaHydraSceneDelegate::SetParams(const MayaHydraParams& params) +{ + const auto& oldParams = GetParams(); + if (oldParams.displaySmoothMeshes != params.displaySmoothMeshes) { + // I couldn't find any other way to turn this on / off. + // I can't convert HdRprim to HdMesh easily and no simple way + // to get the type of the HdRprim from the render index. + // If we want to allow creating multiple rprims and returning an id + // to a subtree, we need to use the HasType function and the mark dirty + // from each adapter. + _MapAdapter( + [](MayaHydraRenderItemAdapter* a) { + if (a->HasType(HdPrimTypeTokens->mesh) || a->HasType(HdPrimTypeTokens->basisCurves) + || a->HasType(HdPrimTypeTokens->points)) { + a->MarkDirty(HdChangeTracker::DirtyTopology); + } + }, + _renderItemsAdapters); + _MapAdapter( + [](MayaHydraDagAdapter* a) { + if (a->HasType(HdPrimTypeTokens->mesh)) { + a->MarkDirty(HdChangeTracker::DirtyTopology); + } + }, + _shapeAdapters); + } + if (oldParams.motionSampleStart != params.motionSampleStart + || oldParams.motionSampleEnd != params.motionSampleEnd) { + _MapAdapter( + [](MayaHydraRenderItemAdapter* a) { + if (a->HasType(HdPrimTypeTokens->mesh) || a->HasType(HdPrimTypeTokens->basisCurves) + || a->HasType(HdPrimTypeTokens->points)) { + a->InvalidateTransform(); + a->MarkDirty(HdChangeTracker::DirtyPoints | HdChangeTracker::DirtyTransform); + } + }, + _renderItemsAdapters); + _MapAdapter( + [](MayaHydraDagAdapter* a) { + if (a->HasType(HdPrimTypeTokens->mesh)) { + a->MarkDirty(HdChangeTracker::DirtyPoints); + } else if (a->HasType(HdPrimTypeTokens->camera)) { + a->MarkDirty(HdCamera::DirtyParams); + } + a->InvalidateTransform(); + a->MarkDirty(HdChangeTracker::DirtyTransform); + }, + _shapeAdapters, + _lightAdapters, + _cameraAdapters); + } + // We need to trigger rebuilding shaders. + if (oldParams.textureMemoryPerTexture != params.textureMemoryPerTexture) { + _MapAdapter( + [](MayaHydraMaterialAdapter* a) { a->MarkDirty(HdMaterial::AllDirty); }, + _materialAdapters); + } + if (oldParams.maximumShadowMapResolution != params.maximumShadowMapResolution) { + _MapAdapter( + [](MayaHydraLightAdapter* a) { a->MarkDirty(HdLight::AllDirty); }, _lightAdapters); + } + MayaHydraDelegate::SetParams(params); +} + +//! \brief Try to obtain maya object corresponding to HdxPickHit and add it to a maya selection +//! list \return whether the conversion was a success +bool MayaHydraSceneDelegate::AddPickHitToSelectionList( + const HdxPickHit& hit, + const MHWRender::MSelectionInfo& selectInfo, + MSelectionList& selectionList, + MPointArray& worldSpaceHitPts) +{ + SdfPath hitId = hit.objectId; + // validate that hit is indeed a maya item. Alternatively, the rprim hit could be an rprim + // defined by a scene index such as maya usd. + if (hitId.HasPrefix(GetRprimPath())) { + _FindAdapter( + hitId, + [&selectionList, &worldSpaceHitPts, &hit](MayaHydraRenderItemAdapter* a) { + // prepare the selection path of the hit item, the transform path is expected if available + const auto& itemPath = a->GetDagPath(); + MDagPath selectPath; + if (MS::kSuccess != MDagPath::getAPathTo(itemPath.transform(), selectPath)) { + selectPath = itemPath; + } + selectionList.add(selectPath); + worldSpaceHitPts.append( + hit.worldSpaceHitPoint[0], + hit.worldSpaceHitPoint[1], + hit.worldSpaceHitPoint[2]); + }, + _renderItemsAdapters); + return true; + } + + return false; +} + +HdMeshTopology MayaHydraSceneDelegate::GetMeshTopology(const SdfPath& id) +{ + TF_DEBUG(MAYAHYDRALIB_DELEGATE_GET_MESH_TOPOLOGY) + .Msg("MayaHydraSceneDelegate::GetMeshTopology(%s)\n", id.GetText()); + return _GetValue( + id, + [](MayaHydraAdapter* a) -> HdMeshTopology { return a->GetMeshTopology(); }, + _shapeAdapters, + _renderItemsAdapters); +} + +HdBasisCurvesTopology MayaHydraSceneDelegate::GetBasisCurvesTopology(const SdfPath& id) +{ + TF_DEBUG(MAYAHYDRALIB_DELEGATE_GET_CURVE_TOPOLOGY) + .Msg("MayaHydraSceneDelegate::GetBasisCurvesTopology(%s)\n", id.GetText()); + return _GetValue( + id, + [](MayaHydraAdapter* a) -> HdBasisCurvesTopology { return a->GetBasisCurvesTopology(); }, + _shapeAdapters, + _renderItemsAdapters); +} + +PxOsdSubdivTags MayaHydraSceneDelegate::GetSubdivTags(const SdfPath& id) +{ + TF_DEBUG(MAYAHYDRALIB_DELEGATE_GET_SUBDIV_TAGS) + .Msg("MayaHydraSceneDelegate::GetSubdivTags(%s)\n", id.GetText()); + return _GetValue( + id, + [](MayaHydraShapeAdapter* a) -> PxOsdSubdivTags { return a->GetSubdivTags(); }, + _shapeAdapters); +} + +GfRange3d MayaHydraSceneDelegate::GetExtent(const SdfPath& id) +{ + TF_DEBUG(MAYAHYDRALIB_DELEGATE_GET_EXTENT) + .Msg("MayaHydraSceneDelegate::GetExtent(%s)\n", id.GetText()); + return _GetValue( + id, [](MayaHydraShapeAdapter* a) -> GfRange3d { return a->GetExtent(); }, _shapeAdapters); +} + +GfMatrix4d MayaHydraSceneDelegate::GetTransform(const SdfPath& id) +{ + TF_DEBUG(MAYAHYDRALIB_DELEGATE_GET_TRANSFORM) + .Msg("MayaHydraSceneDelegate::GetTransform(%s)\n", id.GetText()); + return _GetValue( + id, + [](MayaHydraAdapter* a) -> GfMatrix4d { return a->GetTransform(); }, + _shapeAdapters, + _renderItemsAdapters, + _cameraAdapters, + _lightAdapters); +} + +size_t MayaHydraSceneDelegate::SampleTransform( + const SdfPath& id, + size_t maxSampleCount, + float* times, + GfMatrix4d* samples) +{ + TF_DEBUG(MAYAHYDRALIB_DELEGATE_SAMPLE_TRANSFORM) + .Msg( + "MayaHydraSceneDelegate::SampleTransform(%s, %u)\n", + id.GetText(), + static_cast(maxSampleCount)); + return _GetValue( + id, + [maxSampleCount, times, samples](MayaHydraDagAdapter* a) -> size_t { + return a->SampleTransform(maxSampleCount, times, samples); + }, + _shapeAdapters, + _cameraAdapters, + _lightAdapters); +} + +bool MayaHydraSceneDelegate::IsEnabled(const TfToken& option) const +{ + TF_DEBUG(MAYAHYDRALIB_DELEGATE_IS_ENABLED) + .Msg("MayaHydraSceneDelegate::IsEnabled(%s)\n", option.GetText()); + // Maya scene can't be accessed on multiple threads, + // so I don't think this is safe to enable. + if (option == HdOptionTokens->parallelRprimSync) { + return false; + } + + TF_WARN("MayaHydraSceneDelegate::IsEnabled(%s) -- Unsupported option.\n", option.GetText()); + return false; +} + +VtValue MayaHydraSceneDelegate::Get(const SdfPath& id, const TfToken& key) +{ + TF_DEBUG(MAYAHYDRALIB_DELEGATE_GET) + .Msg("MayaHydraSceneDelegate::Get(%s, %s)\n", id.GetText(), key.GetText()); + + if (useMeshAdapter() && id.IsPropertyPath()) { + return _GetValue( + id.GetPrimPath(), + [&key](MayaHydraDagAdapter* a) -> VtValue { return a->GetInstancePrimvar(key); }, + _shapeAdapters); + } + + return _GetValue( + id, + [&key](MayaHydraAdapter* a) -> VtValue { return a->Get(key); }, + _shapeAdapters, + _renderItemsAdapters, + _cameraAdapters, + _lightAdapters, + _materialAdapters); +} + +size_t MayaHydraSceneDelegate::SamplePrimvar( + const SdfPath& id, + const TfToken& key, + size_t maxSampleCount, + float* times, + VtValue* samples) +{ + TF_DEBUG(MAYAHYDRALIB_DELEGATE_SAMPLE_PRIMVAR) + .Msg( + "MayaHydraSceneDelegate::SamplePrimvar(%s, %s, %u)\n", + id.GetText(), + key.GetText(), + static_cast(maxSampleCount)); + + if (!useMeshAdapter()) { + return HdSceneDelegate::SamplePrimvar(id, key, maxSampleCount, times, samples); + } + + if (maxSampleCount < 1) { + return 0; + } + if (id.IsPropertyPath()) { + times[0] = 0.0f; + samples[0] = _GetValue( + id.GetPrimPath(), + [&key](MayaHydraDagAdapter* a) -> VtValue { return a->GetInstancePrimvar(key); }, + _shapeAdapters); + return 1; + } + + return _GetValue( + id, + [&key, maxSampleCount, times, samples](MayaHydraShapeAdapter* a) -> size_t { + return a->SamplePrimvar(key, maxSampleCount, times, samples); + }, + _shapeAdapters); +} + +TfToken MayaHydraSceneDelegate::GetRenderTag(const SdfPath& id) +{ + TF_DEBUG(MAYAHYDRALIB_DELEGATE_GET_RENDER_TAG) + .Msg("MayaHydraSceneDelegate::GetRenderTag(%s)\n", id.GetText()); + return _GetValue( + id.GetPrimPath(), + [](MayaHydraAdapter* a) -> TfToken { return a->GetRenderTag(); }, + _shapeAdapters, + _renderItemsAdapters); +} + +HdPrimvarDescriptorVector +MayaHydraSceneDelegate::GetPrimvarDescriptors(const SdfPath& id, HdInterpolation interpolation) +{ + TF_DEBUG(MAYAHYDRALIB_DELEGATE_GET_PRIMVAR_DESCRIPTORS) + .Msg( + "MayaHydraSceneDelegate::GetPrimvarDescriptors(%s, %i)\n", id.GetText(), interpolation); + + if (useMeshAdapter() && id.IsPropertyPath()) { + return _GetValue( + id.GetPrimPath(), + [&interpolation](MayaHydraDagAdapter* a) -> HdPrimvarDescriptorVector { + return a->GetInstancePrimvarDescriptors(interpolation); + }, + _shapeAdapters); + } + + return _GetValue( + id, + [&interpolation](MayaHydraAdapter* a) -> HdPrimvarDescriptorVector { + return a->GetPrimvarDescriptors(interpolation); + }, + _shapeAdapters, + _renderItemsAdapters); +} + +VtValue MayaHydraSceneDelegate::GetLightParamValue(const SdfPath& id, const TfToken& paramName) +{ + TF_DEBUG(MAYAHYDRALIB_DELEGATE_GET_LIGHT_PARAM_VALUE) + .Msg( + "MayaHydraSceneDelegate::GetLightParamValue(%s, %s)\n", + id.GetText(), + paramName.GetText()); + + const VtValue val = _GetValue( + id, + [¶mName](MayaHydraLightAdapter* a) -> VtValue { + return a->GetLightParamValue(paramName); + }, + _lightAdapters); + + if (TfDebug::IsEnabled(MAYAHYDRALIB_DELEGATE_PRINT_LIGHTS_PARAMETERS_VALUES)) { + // Print the lights parameters to the output window + std::string valueAsString = ConvertVtValueToString(val); + cout << "Light : " << id.GetText() << " Parameter : " << paramName.GetText() + << " Value : " << valueAsString << endl; + } + + return val; +} + +VtValue +MayaHydraSceneDelegate::GetCameraParamValue(const SdfPath& cameraId, const TfToken& paramName) +{ + return _GetValue( + cameraId, + [¶mName](MayaHydraCameraAdapter* a) -> VtValue { + return a->GetCameraParamValue(paramName); + }, + _cameraAdapters); +} + +VtIntArray +MayaHydraSceneDelegate::GetInstanceIndices(const SdfPath& instancerId, const SdfPath& prototypeId) +{ + TF_DEBUG(MAYAHYDRALIB_DELEGATE_GET_INSTANCE_INDICES) + .Msg( + "MayaHydraSceneDelegate::GetInstanceIndices(%s, %s)\n", + instancerId.GetText(), + prototypeId.GetText()); + return _GetValue( + instancerId.GetPrimPath(), + [&prototypeId](MayaHydraDagAdapter* a) -> VtIntArray { + return a->GetInstanceIndices(prototypeId); + }, + _shapeAdapters); +} + +SdfPathVector MayaHydraSceneDelegate::GetInstancerPrototypes(SdfPath const& instancerId) +{ + return { instancerId.GetPrimPath() }; +} + +SdfPath MayaHydraSceneDelegate::GetInstancerId(const SdfPath& primId) +{ + TF_DEBUG(MAYAHYDRALIB_DELEGATE_GET_INSTANCER_ID) + .Msg("MayaHydraSceneDelegate::GetInstancerId(%s)\n", primId.GetText()); + // Instancers don't have any instancers yet. + if (primId.IsPropertyPath()) { + return SdfPath(); + } + return _GetValue( + primId, + [](MayaHydraDagAdapter* a) -> SdfPath { return a->GetInstancerID(); }, + _shapeAdapters); +} + +GfMatrix4d MayaHydraSceneDelegate::GetInstancerTransform(SdfPath const& instancerId) +{ + return GfMatrix4d(1.0); +} + +SdfPath MayaHydraSceneDelegate::GetScenePrimPath( + const SdfPath& rprimPath, + int instanceIndex, + HdInstancerContext* instancerContext) +{ + return rprimPath; +} + +bool MayaHydraSceneDelegate::GetVisible(const SdfPath& id) +{ + TF_DEBUG(MAYAHYDRALIB_DELEGATE_GET_VISIBLE) + .Msg("MayaHydraSceneDelegate::GetVisible(%s)\n", id.GetText()); + + return _GetValue( + id, + [](MayaHydraAdapter* a) -> bool { return a->GetVisible(); }, + _shapeAdapters, + _renderItemsAdapters, + _lightAdapters); +} + +bool MayaHydraSceneDelegate::GetDoubleSided(const SdfPath& id) +{ + TF_DEBUG(MAYAHYDRALIB_DELEGATE_GET_DOUBLE_SIDED) + .Msg("MayaHydraSceneDelegate::GetDoubleSided(%s)\n", id.GetText()); + return _GetValue( + id, + [](MayaHydraAdapter* a) -> bool { return a->GetDoubleSided(); }, + _shapeAdapters, + _renderItemsAdapters); +} + +HdCullStyle MayaHydraSceneDelegate::GetCullStyle(const SdfPath& id) +{ + TF_DEBUG(MAYAHYDRALIB_DELEGATE_GET_CULL_STYLE) + .Msg("MayaHydraSceneDelegate::GetCullStyle(%s)\n", id.GetText()); + + return _GetValue( + id, + [](MayaHydraAdapter* a) -> HdCullStyle { return a->GetCullStyle(); }, + _shapeAdapters, + _renderItemsAdapters); +} + +HdDisplayStyle MayaHydraSceneDelegate::GetDisplayStyle(const SdfPath& id) +{ + TF_DEBUG(MAYAHYDRALIB_DELEGATE_GET_DISPLAY_STYLE) + .Msg("MayaHydraSceneDelegate::GetDisplayStyle(%s)\n", id.GetText()); + return _GetValue( + id, + [](MayaHydraAdapter* a) -> HdDisplayStyle { return a->GetDisplayStyle(); }, + _shapeAdapters, + _renderItemsAdapters); +} + +SdfPath MayaHydraSceneDelegate::GetMaterialId(const SdfPath& id) +{ + TF_DEBUG(MAYAHYDRALIB_DELEGATE_GET_MATERIAL_ID) + .Msg("MayaHydraSceneDelegate::GetMaterialId(%s)\n", id.GetText()); + + if (_useDefaultMaterial) { + return _mayaDefaultMaterialPath; + } + + auto result = TfMapLookupPtr(_renderItemsAdapters, id); + if (result != nullptr) { + auto& renderItemAdapter = *result; + + // Check if this render item is a wireframe primitive + if (MHWRender::MGeometry::Primitive::kLines == renderItemAdapter->GetPrimitive() + || MHWRender::MGeometry::Primitive::kLineStrip == renderItemAdapter->GetPrimitive()) { + return _fallbackMaterial; + } + + auto& material = renderItemAdapter->GetMaterial(); + + if (material == kInvalidMaterial) { + return _fallbackMaterial; + } + + if (TfMapLookupPtr(_materialAdapters, material) != nullptr) { + return material; + } + } + + if (useMeshAdapter()) { + auto shapeAdapter = TfMapLookupPtr(_shapeAdapters, id); + if (shapeAdapter == nullptr) { + return _fallbackMaterial; + } + auto material = shapeAdapter->get()->GetMaterial(); + if (material == MObject::kNullObj) { + return _fallbackMaterial; + } + auto materialId = GetMaterialPath(material); + if (TfMapLookupPtr(_materialAdapters, materialId) != nullptr) { + return materialId; + } + + return _CreateMaterial(materialId, material) ? materialId : _fallbackMaterial; + } + + return _fallbackMaterial; +} + +VtValue MayaHydraSceneDelegate::GetMaterialResource(const SdfPath& id) +{ + TF_DEBUG(MAYAHYDRALIB_DELEGATE_GET_MATERIAL_RESOURCE) + .Msg("MayaHydraSceneDelegate::GetMaterialResource(%s)\n", id.GetText()); + + if (id == _mayaDefaultMaterialPath) { + return _mayaDefaultMaterial; + } + + if (id == _fallbackMaterial) { + return MayaHydraMaterialAdapter::GetPreviewMaterialResource(id); + } + + auto ret = _GetValue( + id, + [](MayaHydraMaterialAdapter* a) -> VtValue { return a->GetMaterialResource(); }, + _materialAdapters); + return ret.IsEmpty() ? MayaHydraMaterialAdapter::GetPreviewMaterialResource(id) : ret; +} + +bool MayaHydraSceneDelegate::_CreateMaterial(const SdfPath& id, const MObject& obj) +{ + TF_DEBUG(MAYAHYDRALIB_ADAPTER_MATERIALS) + .Msg("MayaHydraSceneDelegate::_CreateMaterial(%s)\n", id.GetText()); + + auto materialCreator = MayaHydraAdapterRegistry::GetMaterialAdapterCreator(obj); + if (materialCreator == nullptr) { + return false; + } + auto materialAdapter = materialCreator(id, GetProducer(), obj); + if (materialAdapter == nullptr || !materialAdapter->IsSupported()) { + return false; + } + + if (_xRayEnabled) { + materialAdapter->EnableXRayShadingMode(_xRayEnabled); // Enable XRay shading mode + } + materialAdapter->Populate(); + materialAdapter->CreateCallbacks(); + _materialAdapters.emplace(id, std::move(materialAdapter)); + return true; +} + +SdfPath MayaHydraSceneDelegate::SetCameraViewport(const MDagPath& camPath, const GfVec4d& viewport) +{ + const SdfPath camID = GetPrimPath(camPath, true); + auto&& cameraAdapter = TfMapLookupPtr(_cameraAdapters, camID); + if (cameraAdapter) { + (*cameraAdapter)->SetViewport(viewport); + return camID; + } + return {}; +} + +VtValue MayaHydraSceneDelegate::GetShadingStyle(SdfPath const& id) +{ + if (auto&& ri = TfMapLookupPtr(_renderItemsAdapters, id)) { + auto primitive = (*ri)->GetPrimitive(); + if (MHWRender::MGeometry::Primitive::kLines == primitive + || MHWRender::MGeometry::Primitive::kLineStrip == primitive) { + return VtValue( + _tokens + ->constantLighting); // Use fallbackMaterial + constantLighting + displayColor + } + } + return MayaHydraDelegateCtx::GetShadingStyle(id); +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/mayaHydra/hydraExtensions/delegates/sceneDelegate.h b/lib/mayaHydra/hydraExtensions/delegates/sceneDelegate.h new file mode 100644 index 0000000000..838827eaab --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/delegates/sceneDelegate.h @@ -0,0 +1,311 @@ +// +// 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. +// +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +#ifndef MAYAHYDRALIB_SCENE_DELEGATE_H +#define MAYAHYDRALIB_SCENE_DELEGATE_H + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +/* + * Notes. + * + * To remove the need of casting between different adapter types or + * making the base adapter class too heavy I decided to use 3 different set + * or map types. This adds a bit of extra code to the RemoveAdapter function + * but simplifies the rest of the functions significantly (and no downcasting!). + * + * All this would be probably way nicer / easier with C++14 and the polymorphic + * lambdas. + * + * This also optimizes other things, like it's easier to separate functionality + * that only affects shapes, lights or materials. + */ + +PXR_NAMESPACE_OPEN_SCOPE + +/** + * \brief Hydra custom scene delegate. + * + * MayaHydraSceneDelegate is a Hydra custom scene delegate used to translate + * from a Maya scene to Hydra. + * + * If you want to know how to add a custom scene index to this plug-in, then please see the + * registration.cpp file. + */ + +class MayaHydraSceneDelegate : public MayaHydraDelegateCtx +{ +public: + template using AdapterMap = std::unordered_map; + + MAYAHYDRALIB_API + MayaHydraSceneDelegate(const InitData& initData); + + MAYAHYDRALIB_API + ~MayaHydraSceneDelegate() override; + + MAYAHYDRALIB_API + void Populate() override; + + MAYAHYDRALIB_API + void PreFrame(const MHWRender::MDrawContext& context) override; + + MAYAHYDRALIB_API + void RemoveAdapter(const SdfPath& id) override; + + MAYAHYDRALIB_API + void RecreateAdapter(const SdfPath& id, const MObject& obj) override; + + MAYAHYDRALIB_API + void RecreateAdapterOnIdle(const SdfPath& id, const MObject& obj) override; + + MAYAHYDRALIB_API + void RebuildAdapterOnIdle(const SdfPath& id, uint32_t flags) override; + + /// \brief Notifies the scene delegate when a material tag changes. + /// + /// This function is only affects the render index when its using HdSt. + /// HdSt requires rebuilding the shapes whenever the tags affecting + /// translucency change. + /// + /// \param id Id of the Material that changed its tag. + MAYAHYDRALIB_API + void MaterialTagChanged(const SdfPath& id) override; + + MAYAHYDRALIB_API + MayaHydraLightAdapterPtr GetLightAdapter(const SdfPath& id); + + MAYAHYDRALIB_API + MayaHydraMaterialAdapterPtr GetMaterialAdapter(const SdfPath& id); + + MAYAHYDRALIB_API + void InsertDag(const MDagPath& dag); + + void OnDagNodeAdded(const MObject& obj); + + void OnDagNodeRemoved(const MObject& obj); + + MAYAHYDRALIB_API + void UpdateLightVisibility(const MDagPath& dag); + + MAYAHYDRALIB_API + void AddNewInstance(const MDagPath& dag); + + MAYAHYDRALIB_API + void SetParams(const MayaHydraParams& params) override; + + MAYAHYDRALIB_API + SdfPath SetCameraViewport(const MDagPath& camPath, const GfVec4d& viewport); + + MAYAHYDRALIB_API + void HandleCompleteViewportScene( + const MDataServerOperation::MViewportScene& scene, + MFrameContext::DisplayStyle ds); + + MAYAHYDRALIB_API + bool AddPickHitToSelectionList( + const HdxPickHit& hit, + const MHWRender::MSelectionInfo& selectInfo, + MSelectionList& selectionList, + MPointArray& worldSpaceHitPts) override; + + bool GetPlaybackRunning() const { return _isPlaybackRunning; } + +protected: + + MAYAHYDRALIB_API + HdMeshTopology GetMeshTopology(const SdfPath& id) override; + + MAYAHYDRALIB_API + HdBasisCurvesTopology GetBasisCurvesTopology(const SdfPath& id) override; + + MAYAHYDRALIB_API + PxOsdSubdivTags GetSubdivTags(const SdfPath& id) override; + + MAYAHYDRALIB_API + GfRange3d GetExtent(const SdfPath& id) override; + + MAYAHYDRALIB_API + GfMatrix4d GetTransform(const SdfPath& id) override; + + MAYAHYDRALIB_API + size_t + SampleTransform(const SdfPath& id, size_t maxSampleCount, float* times, GfMatrix4d* samples) + override; + + MAYAHYDRALIB_API + bool GetVisible(const SdfPath& id) override; + + MAYAHYDRALIB_API + bool IsEnabled(const TfToken& option) const override; + + MAYAHYDRALIB_API + bool GetDoubleSided(const SdfPath& id) override; + + MAYAHYDRALIB_API + HdCullStyle GetCullStyle(const SdfPath& id) override; + + MAYAHYDRALIB_API + VtValue GetShadingStyle(const SdfPath& id) override; + + MAYAHYDRALIB_API + HdDisplayStyle GetDisplayStyle(const SdfPath& id) override; + // TfToken GetReprName(const SdfPath& id) override; + + MAYAHYDRALIB_API + VtValue Get(const SdfPath& id, const TfToken& key) override; + + MAYAHYDRALIB_API + size_t SamplePrimvar( + const SdfPath& id, + const TfToken& key, + size_t maxSampleCount, + float* times, + VtValue* samples) override; + + MAYAHYDRALIB_API + TfToken GetRenderTag(SdfPath const& id) override; + + MAYAHYDRALIB_API + HdPrimvarDescriptorVector + GetPrimvarDescriptors(const SdfPath& id, HdInterpolation interpolation) override; + + MAYAHYDRALIB_API + VtValue GetLightParamValue(const SdfPath& id, const TfToken& paramName) override; + + MAYAHYDRALIB_API + VtValue GetCameraParamValue(const SdfPath& cameraId, const TfToken& paramName) override; + + MAYAHYDRALIB_API + VtIntArray GetInstanceIndices(const SdfPath& instancerId, const SdfPath& prototypeId) override; + + MAYAHYDRALIB_API + SdfPathVector GetInstancerPrototypes(SdfPath const& instancerId) override; + + MAYAHYDRALIB_API + SdfPath GetInstancerId(const SdfPath& primId) override; + + MAYAHYDRALIB_API + GfMatrix4d GetInstancerTransform(SdfPath const& instancerId) override; + + MAYAHYDRALIB_API + SdfPath GetScenePrimPath( + const SdfPath& rprimPath, + int instanceIndex, + HdInstancerContext* instancerContext) override; + + MAYAHYDRALIB_API + SdfPath GetMaterialId(const SdfPath& id) override; + + MAYAHYDRALIB_API + VtValue GetMaterialResource(const SdfPath& id) override; + +private: + template + AdapterPtr _CreateAdapter( + const MDagPath& dag, + const std::function& adapterCreator, + Map& adapterMap, + bool isSprim = false); + + MayaHydraLightAdapterPtr CreateLightAdapter(const MDagPath& dagPath); + MayaHydraCameraAdapterPtr CreateCameraAdapter(const MDagPath& dagPath); + MayaHydraShapeAdapterPtr CreateShapeAdapter(const MDagPath& dagPath); + + MAYAHYDRALIB_API + bool _GetRenderItem(int fastId, MayaHydraRenderItemAdapterPtr& adapter); + + + using LightDagPathMap = std::unordered_map; + LightDagPathMap _GetActiveLightPaths() const; + + MAYAHYDRALIB_API + void _AddRenderItem(const MayaHydraRenderItemAdapterPtr& ria); + + MAYAHYDRALIB_API + void _RemoveRenderItem(const MayaHydraRenderItemAdapterPtr& ria); + + MAYAHYDRALIB_API + bool + _GetRenderItemMaterial(const MRenderItem& ri, SdfPath& material, MObject& shadingEngineNode); + + static VtValue CreateMayaDefaultMaterial(); + + bool _CreateMaterial(const SdfPath& id, const MObject& obj); + + /// \brief Unordered Map storing the shape adapters. + AdapterMap _shapeAdapters; + + /// \brief Unordered Map storing the render item adapters. + AdapterMap _renderItemsAdapters; + std::unordered_map _renderItemsAdaptersFast; + + /// \brief Unordered Map storing the light adapters. + AdapterMap _lightAdapters; + /// \brief Unordered Map storing the camera adapters. + AdapterMap _cameraAdapters; + /// \brief Unordered Map storing the material adapters. + AdapterMap _materialAdapters; + std::vector _callbacks; + std::vector> _adaptersToRecreate; + std::vector> _adaptersToRebuild; + // Nodes accumulated during _onDagNodeAdded() callback. + std::vector _addedNodes; + + using LightAdapterCreator + = std::function; + std::vector> _lightsToAdd; + + std::vector _materialTagsChanged; + + /// _fallbackMaterial is an SdfPath used when there is no material assigned to a Maya object + static SdfPath _fallbackMaterial; + /// _mayaDefaultMaterialPath is common to all scene delegates, it's the SdfPath of + /// _mayaDefaultMaterial + static SdfPath _mayaDefaultMaterialPath; + /// _mayaDefaultMaterial is an hydra material used to override all materials from the scene when + /// _useDefaultMaterial is true + static VtValue _mayaDefaultMaterial; + + bool _useDefaultMaterial = false; + bool _xRayEnabled = false; + bool _isPlaybackRunning = false; +}; + +typedef std::shared_ptr MayaSceneDelegateSharedPtr; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // MAYAHYDRALIB_SCENE_DELEGATE_H diff --git a/lib/mayaHydra/hydraExtensions/delegates/testDelegate.cpp b/lib/mayaHydra/hydraExtensions/delegates/testDelegate.cpp new file mode 100644 index 0000000000..527043fd64 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/delegates/testDelegate.cpp @@ -0,0 +1,64 @@ +// +// 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. +// +#include "testDelegate.h" + +#include + +#include + +PXR_NAMESPACE_OPEN_SCOPE + +TF_DEFINE_ENV_SETTING( + MAYAHYDRALIB_TEST_DELEGATE_FILE, + "", + "Path for MayaHydraTestDelegate to load"); + +// clang-format off +TF_DEFINE_PRIVATE_TOKENS( + _tokens, + + (MayaHydraTestDelegate) +); +// clang-format on + +TF_REGISTRY_FUNCTION_WITH_TAG(MayaHydraDelegateRegistry, MayaHydraTestDelegate) +{ + if (!TfGetEnvSetting(MAYAHYDRALIB_TEST_DELEGATE_FILE).empty()) { + MayaHydraDelegateRegistry::RegisterDelegate( + _tokens->MayaHydraTestDelegate, + [](const MayaHydraDelegate::InitData& initData) -> MayaHydraDelegatePtr { + return std::static_pointer_cast( + std::make_shared(initData)); + }); + } +} + +/* + * MayaHydraTestDelegate could be used as a test scene delegate, it is not used any more. + */ +MayaHydraTestDelegate::MayaHydraTestDelegate(const InitData& initData) + : MayaHydraDelegate(initData) +{ + _delegate.reset(new UsdImagingDelegate(initData.renderIndex, initData.delegateID)); +} + +void MayaHydraTestDelegate::Populate() +{ + _stage = UsdStage::Open(TfGetEnvSetting(MAYAHYDRALIB_TEST_DELEGATE_FILE)); + _delegate->Populate(_stage->GetPseudoRoot()); +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/mayaHydra/hydraExtensions/delegates/testDelegate.h b/lib/mayaHydra/hydraExtensions/delegates/testDelegate.h new file mode 100644 index 0000000000..3f3c3e4ec0 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/delegates/testDelegate.h @@ -0,0 +1,48 @@ +// +// 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. +// +#ifndef MAYAHYDRALIB_TEST_DELEGATE_H +#define MAYAHYDRALIB_TEST_DELEGATE_H + +#include + +#include +#include +#include +#include +#include + +#include + +PXR_NAMESPACE_OPEN_SCOPE + +/** + * \brief MayaHydraTestDelegate could be used as a test scene delegate, it is not used any more. + */ +class MayaHydraTestDelegate : public MayaHydraDelegate +{ +public: + MayaHydraTestDelegate(const InitData& initData); + + void Populate() override; + +private: + std::unique_ptr _delegate; + UsdStageRefPtr _stage; +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // MAYAHYDRALIB_TEST_DELEGATE_H diff --git a/lib/mayaHydra/hydraExtensions/hydraUtils.cpp b/lib/mayaHydra/hydraExtensions/hydraUtils.cpp new file mode 100644 index 0000000000..900aed93ff --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/hydraUtils.cpp @@ -0,0 +1,248 @@ +// +// Copyright 2019 Luma Pictures +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +// 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 "hydraUtils.h" + +#include +#include +#include +#include +#include +#include + +PXR_NAMESPACE_USING_DIRECTIVE + +// This is the delimiter that Maya uses to identify levels of hierarchy in the +// Maya DAG. +constexpr char MayaDagDelimiter[] = "|"; + +// This is the delimiter that Maya uses to separate levels of namespace in +// Maya node names. +constexpr char MayaNamespaceDelimiter[] = ":"; + +namespace MAYAHYDRA_NS_DEF { + +std::string ConvertVtValueToString(const VtValue& val) +{ + if (val.IsEmpty()) { + return "No Value!"; + } + + std::ostringstream ss; + if (val.IsHolding()) { + const bool v = val.UncheckedGet(); + ss << "bool : "; + ss << v; + } else if (val.IsHolding()) { + const TfToken elem = val.UncheckedGet(); + ss << "TfToken : " << elem.GetText(); + } else if (val.IsHolding>()) { + auto arrayType = val.UncheckedGet>(); + ss << "VtArray : ("; + for (auto elem : arrayType) { + ss << std::to_string(elem) << " , "; + } + ss << ")"; + } else if (val.IsHolding>()) { + auto arrayType = val.UncheckedGet>(); + ss << "VtArray : ("; + for (auto elem : arrayType) { + ss << std::to_string(elem) << " , "; + } + ss << ")"; + } else if (val.IsHolding()) { + const float v = val.UncheckedGet(); + ss << "float : "; + ss << v; + } else if (val.IsHolding()) { + const int v = val.UncheckedGet(); + ss << "int : "; + ss << v; + } else if (val.IsHolding()) { + const GfVec2f v = val.UncheckedGet(); + ss << "GfVec2f : (" << v[0] << " , " << v[1] << ")"; + } else if (val.IsHolding()) { + const GfVec3f v = val.UncheckedGet(); + ss << "GfVec3f : (" << v[0] << " , " << v[1] << " , " << v[2] << ")"; + } else if (val.IsHolding()) { + const auto v = val.UncheckedGet(); + ss << "GfVec3d : (" << v[0] << " , " << v[1] << " , " << v[2] << ")"; + } else if (val.IsHolding()) { + const SdfAssetPath v = val.UncheckedGet(); + const std::string assetPath = v.GetAssetPath(); + ss << "SdfAssetPath : \"" << assetPath << "\""; + } else if (val.IsHolding>()) { + auto arrayType = val.UncheckedGet>(); + ss << "VtArray : ("; + for (auto elem : arrayType) { + ss << elem.GetText() << " , "; + } + ss << ")"; + } else if (val.IsHolding>()) { + auto arrayType = val.UncheckedGet>(); + ss << "VtArray : ("; + for (auto elem : arrayType) { + auto strVec3f = "(" + std::to_string(elem[0]) + ", " + std::to_string(elem[1]) + ", " + + std::to_string(elem[2]) + ")"; + ss << strVec3f + " , "; + } + ss << ")"; + } else if (val.IsHolding>()) { + auto arrayType = val.UncheckedGet>(); + ss << "VtArray : ("; + for (auto elem : arrayType) { + auto strVec3f = "(" + std::to_string(elem[0]) + ", " + std::to_string(elem[1]) + ", " + + std::to_string(elem[2]) + ")"; + ss << strVec3f + " , "; + } + ss << ")"; + } else if (val.IsHolding>()) { + auto arrayType = val.UncheckedGet>(); + ss << "VtArray : ("; + for (auto elem : arrayType) { + auto quathh = "(" + std::to_string(elem.GetReal()) + ", " + + std::to_string(elem.GetImaginary()[0]) + ", " + + std::to_string(elem.GetImaginary()[1]) + ", " + + std::to_string(elem.GetImaginary()[2]) + ")"; + ss << quathh + " , "; + } + ss << ")"; + } else if (val.IsHolding()) { + auto elem = val.UncheckedGet(); + auto quathh = "(" + std::to_string(elem.GetReal()) + ", " + + std::to_string(elem.GetImaginary()[0]) + ", " + std::to_string(elem.GetImaginary()[1]) + + ", " + std::to_string(elem.GetImaginary()[2]) + ")"; + ss << "GfQuath : " << quathh; + } else if (val.IsHolding()) { + auto mat4d = val.UncheckedGet(); + + double data[4][4]; + mat4d.Get(data); + auto strMat4d = std::string("(") + "{" + std::to_string(data[0][0]) + ", " + + std::to_string(data[0][1]) + ", " + std::to_string(data[0][2]) + ", " + + std::to_string(data[0][3]) + "}, " + "{" + std::to_string(data[1][0]) + ", " + + std::to_string(data[1][1]) + ", " + std::to_string(data[1][2]) + ", " + + std::to_string(data[1][3]) + "}, " + "{" + std::to_string(data[2][0]) + ", " + + std::to_string(data[2][1]) + ", " + std::to_string(data[2][2]) + ", " + + std::to_string(data[2][3]) + "}, " + "{" + std::to_string(data[3][0]) + ", " + + std::to_string(data[3][1]) + ", " + std::to_string(data[3][2]) + ", " + + std::to_string(data[3][3]) + "}" + ")"; + ss << "GfMatrix4d : " << strMat4d; + } + + std::string valueString = ss.str(); + if (valueString.size() > 0) { + return valueString; + } + + // Unknown + return "* Unknown Type *"; +} + +std::string StripNamespaces(const std::string& nodeName, const int nsDepth) +{ + if (nodeName.empty() || nsDepth == 0) { + return nodeName; + } + + std::stringstream ss; + + const std::vector nodeNameParts + = PXR_NS::TfStringSplit(nodeName, MayaDagDelimiter); + + const bool isAbsolute = PXR_NS::TfStringStartsWith(nodeName, MayaDagDelimiter); + + for (size_t i = 0u; i < nodeNameParts.size(); ++i) { + if (i == 0u && isAbsolute) { + // If nodeName was absolute, the first element in nodeNameParts + // will be empty, so just skip it. The output path will be made + // absolute with the next iteration. + continue; + } + + if (i != 0u) { + ss << MayaDagDelimiter; + } + + const std::vector nsNameParts + = PXR_NS::TfStringSplit(nodeNameParts[i], MayaNamespaceDelimiter); + + const size_t nodeNameIndex = nsNameParts.size() - 1u; + + auto startIter = nsNameParts.begin(); + if (nsDepth < 0) { + // If nsDepth is negative, we don't keep any namespaces, so advance + // startIter to the last element in the vector, which is just the + // node name. + startIter += nodeNameIndex; + } else { + // Otherwise we strip as many namespaces as possible up to nsDepth, + // but no more than what would leave us with just the node name. + startIter += std::min(static_cast(nsDepth), nodeNameIndex); + } + + ss << PXR_NS::TfStringJoin(startIter, nsNameParts.end(), MayaNamespaceDelimiter); + } + + return ss.str(); +} + +// Elements of the path will be sanitized such that it is a valid SdfPath. +// This means it will replace Maya's namespace delimiter (':') with +// underscores ('_'). +// A SdfPath in Pixar USD is considered invalid if it does not conform to the rules for path names. +// Some common issues that can make a path invalid include: Starting with a number : Path names +// must start with a letter, not a number. Including spaces or special characters : Path names can +// only contain letters, numbers, and the characters _, -, and : . +void SanitizeNameForSdfPath(std::string& inoutPathString, bool doStripNamespaces /*= false*/) +{ + if (doStripNamespaces) { + // Drop namespaces instead of making them part of the path. + inoutPathString = StripNamespaces(inoutPathString); + } + + std::replace( + inoutPathString.begin(), + inoutPathString.end(), + MayaDagDelimiter[0], + PXR_NS::SdfPathTokens->childDelimiter.GetString()[0]); + std::replace(inoutPathString.begin(), inoutPathString.end(), MayaNamespaceDelimiter[0], '_'); + std::replace(inoutPathString.begin(), inoutPathString.end(), ',', '_'); + std::replace(inoutPathString.begin(), inoutPathString.end(), ';', '_'); +} + +SdfPath MakeRelativeToParentPath(const SdfPath& path) +{ + return path.MakeRelativePath(path.GetParentPath()); +} + +bool GetXformMatrixFromPrim(const HdSceneIndexPrim& prim, GfMatrix4d& outMatrix) +{ + HdContainerDataSourceHandle xformContainer + = HdContainerDataSource::Cast(prim.dataSource->Get(HdXformSchemaTokens->xform)); + if (!xformContainer) { + return false; + } + HdXformSchema xform = HdXformSchema(xformContainer); + if (!xform.GetMatrix()) { + return false; + } + outMatrix = xform.GetMatrix()->GetValue(0).Get(); + return true; +} + +} // namespace MAYAHYDRA_NS_DEF diff --git a/lib/mayaHydra/hydraExtensions/hydraUtils.h b/lib/mayaHydra/hydraExtensions/hydraUtils.h new file mode 100644 index 0000000000..fbb925ec57 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/hydraUtils.h @@ -0,0 +1,93 @@ +// +// Copyright 2019 Luma Pictures +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +// 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 MAYAHYDRALIB_HYDRA_UTILS_H +#define MAYAHYDRALIB_HYDRA_UTILS_H + +#include +#include + +#include +#include +#include +#include + +#include + +namespace MAYAHYDRA_NS_DEF { + +/** + * @brief Return the \p VtValue type and value as a string for debugging purposes. + * + * @param[in] val is the \p VtValue to be converted. + * + * @return The \p VtValue type and value in string form. + */ +MAYAHYDRALIB_API +std::string ConvertVtValueToString(const pxr::VtValue& val); + +/** + * @brief Strip \p nsDepth namespaces from \p nodeName. + * + * This will turn "taco:foo:bar" into "foo:bar" for \p nsDepth == 1, or "taco:foo:bar" into + * "bar" for \p nsDepth > 1. If \p nsDepth is -1, all namespaces are stripped. + * + * @param[in] nodeName is the node name from which to strip namespaces. + * @param[in] nsDepth is the namespace depth to strip. + * + * @return The stripped version of \p nodeName. + */ +MAYAHYDRALIB_API +std::string StripNamespaces(const std::string& nodeName, const int nsDepth = -1); + +/** + * @brief Replaces the invalid characters for SdfPath in-place in \p inOutPathString. + * + * @param[in,out] inOutPathString is the path string to sanitize. + * @param[in] doStripNamespaces determines whether to strip namespaces or not. + */ +MAYAHYDRALIB_API +void SanitizeNameForSdfPath(std::string& inOutPathString, bool doStripNamespaces = false); + +/** + * @brief Get the given SdfPath without its parent path. + * + * The result is the last element of the original SdfPath. + * + * @param[in] path is the SdfPath from which to remove the parent path. + * + * @return The path without its parent path. + */ +MAYAHYDRALIB_API +pxr::SdfPath MakeRelativeToParentPath(const pxr::SdfPath& path); + +/** + * @brief Get the Hydra Xform matrix from a given prim. + * + * This method makes no guarantee on whether the matrix is flattened or not. + * + * @param[in] prim is the Hydra prim in the SceneIndex of which to get the transform matrix. + * @param[out] outMatrix is the transform matrix of the prim. + * + * @return True if the operation succeeded, false otherwise. + */ +MAYAHYDRALIB_API +bool GetXformMatrixFromPrim(const pxr::HdSceneIndexPrim& prim, pxr::GfMatrix4d& outMatrix); + +} // namespace MAYAHYDRA_NS_DEF + +#endif // MAYAHYDRALIB_HYDRA_UTILS_H diff --git a/lib/mayaHydra/hydraExtensions/interface.h b/lib/mayaHydra/hydraExtensions/interface.h new file mode 100644 index 0000000000..2aaa7e4907 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/interface.h @@ -0,0 +1,69 @@ +// +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +// 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 MAYAHYDRALIB_INTERFACE_H +#define MAYAHYDRALIB_INTERFACE_H + +#include + +#include + +#include + +PXR_NAMESPACE_OPEN_SCOPE + +using SceneIndicesVector = std::vector; + +/// In order to access this interface, call the function GetMayaHydraLibInterface() +class MayaHydraLibInterface +{ +public: + /** + * @brief Register a terminal scene index into the Hydra plugin. + * + * @param[in] sceneIndex is a pointer to the SceneIndex to be registered. + */ + virtual void RegisterTerminalSceneIndex(HdSceneIndexBasePtr sceneIndex) = 0; + + /** + * @brief Unregister a terminal scene index from the Hydra plugin. + * + * @param[in] sceneIndex is a pointer to the SceneIndex to be unregistered. + */ + virtual void UnregisterTerminalSceneIndex(HdSceneIndexBasePtr sceneIndex) = 0; + + /** + * @brief Clear the list of registered terminal scene indices + * + * This does not delete them, but just unregisters them. + */ + virtual void ClearTerminalSceneIndices() = 0; + + /** + * @brief Retrieve the list of registered terminal scene indices from the Hydra plugin. + * + * @return A const reference to the vector of registered terminal scene indices. + */ + virtual const SceneIndicesVector& GetTerminalSceneIndices() const = 0; +}; + +/// Access the MayaHydraLibInterface instance +MAYAHYDRALIB_API +MayaHydraLibInterface& GetMayaHydraLibInterface(); + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // MAYAHYDRALIB_INTERFACE_H diff --git a/lib/mayaHydra/hydraExtensions/interfaceImp.cpp b/lib/mayaHydra/hydraExtensions/interfaceImp.cpp new file mode 100644 index 0000000000..ccfb841170 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/interfaceImp.cpp @@ -0,0 +1,49 @@ +// Copyright 2023 Autodesk +// +// 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 "interfaceImp.h" + +PXR_NAMESPACE_OPEN_SCOPE + +MayaHydraLibInterface& GetMayaHydraLibInterface() +{ + static MayaHydraLibInterfaceImp libInterface; + return libInterface; +} + +void MayaHydraLibInterfaceImp::RegisterTerminalSceneIndex(HdSceneIndexBasePtr sceneIndex) +{ + auto foundSceneIndex = std::find(_sceneIndices.begin(), _sceneIndices.end(), sceneIndex); + if (foundSceneIndex == _sceneIndices.end()) { + _sceneIndices.push_back(sceneIndex); + } +} + +void MayaHydraLibInterfaceImp::UnregisterTerminalSceneIndex(HdSceneIndexBasePtr sceneIndex) +{ + auto foundSceneIndex = std::find(_sceneIndices.begin(), _sceneIndices.end(), sceneIndex); + if (foundSceneIndex != _sceneIndices.end()) { + _sceneIndices.erase(foundSceneIndex); + } +} + +void MayaHydraLibInterfaceImp::ClearTerminalSceneIndices() { _sceneIndices.clear(); } + +const SceneIndicesVector& MayaHydraLibInterfaceImp::GetTerminalSceneIndices() const +{ + return _sceneIndices; +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/mayaHydra/hydraExtensions/interfaceImp.h b/lib/mayaHydra/hydraExtensions/interfaceImp.h new file mode 100644 index 0000000000..bdcef76e5b --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/interfaceImp.h @@ -0,0 +1,46 @@ +// +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +// 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 MAYAHYDRALIB_INTERFACE_IMP_H +#define MAYAHYDRALIB_INTERFACE_IMP_H + +#include +#include + +#include + +#include + +PXR_NAMESPACE_OPEN_SCOPE + +class MayaHydraLibInterfaceImp : public MayaHydraLibInterface +{ +public: + MayaHydraLibInterfaceImp() = default; + virtual ~MayaHydraLibInterfaceImp() = default; + + void RegisterTerminalSceneIndex(HdSceneIndexBasePtr) override; + void UnregisterTerminalSceneIndex(HdSceneIndexBasePtr) override; + void ClearTerminalSceneIndices() override; + const SceneIndicesVector& GetTerminalSceneIndices() const override; + +private: + SceneIndicesVector _sceneIndices; +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // MAYAHYDRALIB_INTERFACE_IMP_H diff --git a/lib/mayaHydra/hydraExtensions/mayaHydra.h.src b/lib/mayaHydra/hydraExtensions/mayaHydra.h.src new file mode 100644 index 0000000000..aa2fc2e422 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/mayaHydra.h.src @@ -0,0 +1,50 @@ +// +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +// 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 MAYAHYDRALIB_MAYAHYDRA_H +#define MAYAHYDRALIB_MAYAHYDRA_H + +#define MAYAHYDRA_MAJOR_VERSION ${MAYAHYDRA_MAJOR_VERSION} +#define MAYAHYDRA_MINOR_VERSION ${MAYAHYDRA_MINOR_VERSION} +#define MAYAHYDRA_PATCH_LEVEL ${MAYAHYDRA_PATCH_LEVEL} +#define MAYAHYDRA_API_VERSION (MAYAHYDRA_MAJOR_VERSION * 10000 + MAYAHYDRA_MINOR_VERSION * 100 + MAYAHYDRA_PATCH_LEVEL) + +// MayaHydra public namespace string will never change. +#define MAYAHYDRA_NS MayaHydra +// C preprocessor trickery to expand arguments. +#define MAYAHYDRA_CONCAT(A, B) MAYAHYDRA_CONCAT_IMPL(A, B) +#define MAYAHYDRA_CONCAT_IMPL(A, B) A##B +// Versioned namespace includes the major version number. +#define MAYAHYDRA_VERSIONED_NS MAYAHYDRA_CONCAT(MAYAHYDRA_NS, _v${MAYAHYDRA_MAJOR_VERSION}) + +namespace MAYAHYDRA_VERSIONED_NS {} + +// With a using namespace declaration, pull in the versioned namespace into the +// MayaHydra public namespace, to allow client code to use the plain MayaHydra +// namespace, e.g. MayaHydra::Class. +namespace MAYAHYDRA_NS { + using namespace MAYAHYDRA_VERSIONED_NS; +} + +// Macro to place the MayaHydra symbols in the versioned namespace, which is how +// they will appear in the shared library, e.g. MayaHydra_v1::Class. +#ifdef DOXYGEN +#define MAYAHYDRA_NS_DEF MAYAHYDRA_NS +#else +#define MAYAHYDRA_NS_DEF MAYAHYDRA_VERSIONED_NS +#endif + +#endif // MAYAHYDRALIB_MAYAHYDRA_H diff --git a/lib/mayaHydra/hydraExtensions/mayaHydraSceneProducer.cpp b/lib/mayaHydra/hydraExtensions/mayaHydraSceneProducer.cpp new file mode 100644 index 0000000000..bcbcb0ea06 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/mayaHydraSceneProducer.cpp @@ -0,0 +1,466 @@ +// +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +// 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 "mayaHydraSceneProducer.h" + +#include +#include +#include +#include + +#include + +#include + +PXR_NAMESPACE_OPEN_SCOPE + +TF_DEFINE_ENV_SETTING(MAYA_HYDRA_ENABLE_NATIVE_SCENE_INDEX, true, + "Enable scene index for Maya native scene."); + +bool enableMayaNativeSceneIndex() { + static bool enable = TfGetEnvSetting(MAYA_HYDRA_ENABLE_NATIVE_SCENE_INDEX); + return enable; +} + +MayaHydraSceneProducer::MayaHydraSceneProducer( + Fvp::RenderIndexProxy& renderIndexProxy, + const SdfPath& id, + MayaHydraDelegate::InitData& initData, + bool lightEnabled +) : _renderIndexProxy(renderIndexProxy) +{ + if (enableMayaNativeSceneIndex()) + { + initData.name = TfToken("MayaHydraSceneIndex"); + initData.delegateID = id.AppendChild( + TfToken(TfStringPrintf("_Index_MayaHydraSceneIndex_%p", this))); + initData.producer = this; + _sceneIndex = MayaHydraSceneIndex::New(initData, lightEnabled); + TF_VERIFY(_sceneIndex, "Maya Hydra scene index not found, check mayaHydra plugin installation."); + } + else + { + SdfPathVector solidPrimsRootPaths; + auto delegateNames = MayaHydraDelegateRegistry::GetDelegateNames(); + auto creators = MayaHydraDelegateRegistry::GetDelegateCreators(); + TF_VERIFY(delegateNames.size() == creators.size()); + for (size_t i = 0, n = creators.size(); i < n; ++i) { + const auto& creator = creators[i]; + if (creator == nullptr) { + continue; + } + initData.name = delegateNames[i]; + initData.delegateID = id.AppendChild( + TfToken(TfStringPrintf("_Delegate_%s_%lu_%p", delegateNames[i].GetText(), i, this))); + initData.producer = this; + auto newDelegate = creator(initData); + if (newDelegate) { + // Call SetLightsEnabled before the delegate is populated + newDelegate->SetLightsEnabled(lightEnabled); + _sceneDelegate + = std::dynamic_pointer_cast(newDelegate); + if (TF_VERIFY( + _sceneDelegate, + "Maya Hydra scene delegate not found, check mayaHydra plugin installation.")) { + solidPrimsRootPaths.push_back(_sceneDelegate->GetLightedPrimsRootPath()); + } + _delegates.emplace_back(std::move(newDelegate)); + } + } + + initData.delegateID + = id.AppendChild(TfToken(TfStringPrintf("_DefaultLightDelegate_%p", this))); + _defaultLightDelegate.reset(new MtohDefaultLightDelegate(initData)); + // Set the scene delegate SolidPrimitivesRootPaths for the lines and points primitives to be + // ignored by the default light + _defaultLightDelegate->SetSolidPrimitivesRootPaths(solidPrimsRootPaths); + } +} + +MayaHydraSceneProducer::~MayaHydraSceneProducer() +{ + if (enableMayaNativeSceneIndex()) + { + _sceneIndex->GetRenderIndex().RemoveSceneIndex(_sceneIndex); + } + _delegates.clear(); +} + +void MayaHydraSceneProducer::HandleCompleteViewportScene(const MDataServerOperation::MViewportScene& scene, MFrameContext::DisplayStyle ds) +{ + if (enableMayaNativeSceneIndex()) + { + return _sceneIndex->HandleCompleteViewportScene(scene, ds); + } + else + { + return _sceneDelegate->HandleCompleteViewportScene(scene, ds); + } +} + +void MayaHydraSceneProducer::Populate() +{ + if (enableMayaNativeSceneIndex()) + { + _sceneIndex->Populate(); + // Call InsertSceneIndex before prims are added to scene index, would it be better to call later? + _renderIndexProxy.InsertSceneIndex(_sceneIndex, SdfPath::AbsoluteRootPath()); + } + else + { + for (auto& it : _delegates) { + it->Populate(); + } + + if (_defaultLightDelegate && !_sceneDelegate->GetLightsEnabled()) { + _defaultLightDelegate->Populate(); + } + } +} + +SdfPath MayaHydraSceneProducer::SetCameraViewport(const MDagPath& camPath, const GfVec4d& viewport) +{ + if (enableMayaNativeSceneIndex()) + { + return _sceneIndex->SetCameraViewport(camPath, viewport); + } + else + { + return _sceneDelegate->SetCameraViewport(camPath, viewport); + } +} + +void MayaHydraSceneProducer::SetLightsEnabled(const bool enabled) +{ + if (enableMayaNativeSceneIndex()) + { + return _sceneIndex->SetLightsEnabled(enabled); + } + else + { + return _sceneDelegate->SetLightsEnabled(enabled); + } +} + +void MayaHydraSceneProducer::SetDefaultLightEnabled(const bool enabled) +{ + if (enableMayaNativeSceneIndex()) + { + _sceneIndex->SetDefaultLightEnabled(enabled); + } + else + { + _defaultLightDelegate->SetLightingOn(enabled); + } +} + + +void MayaHydraSceneProducer::SetDefaultLight(const GlfSimpleLight& light) +{ + if (enableMayaNativeSceneIndex()) + { + _sceneIndex->SetDefaultLight(light); + } + else + { + _defaultLightDelegate->SetDefaultLight(light); + } +} + +const MayaHydraParams& MayaHydraSceneProducer::GetParams() const +{ + if (enableMayaNativeSceneIndex()) + { + return _sceneIndex->GetParams(); + } + else + { + return _sceneDelegate->GetParams(); + } +} + +void MayaHydraSceneProducer::SetParams(const MayaHydraParams& params) +{ + if (enableMayaNativeSceneIndex()) + { + return _sceneIndex->SetParams(params); + } + else + { + for (auto& it : _delegates) { + it->SetParams(params); + } + } +} + +bool MayaHydraSceneProducer::AddPickHitToSelectionList( + const HdxPickHit& hit, + const MHWRender::MSelectionInfo& selectInfo, + MSelectionList& selectionList, + MPointArray& worldSpaceHitPts) +{ + if (enableMayaNativeSceneIndex()) + { + return _sceneIndex->AddPickHitToSelectionList( + hit, + selectInfo, + selectionList, + worldSpaceHitPts); + } + else + { + return _sceneDelegate->AddPickHitToSelectionList( + hit, + selectInfo, + selectionList, + worldSpaceHitPts); + } +} + +HdRenderIndex& MayaHydraSceneProducer::GetRenderIndex() +{ + if (enableMayaNativeSceneIndex()) + { + return _sceneIndex->GetRenderIndex(); + } + else + { + return _sceneDelegate->GetRenderIndex(); + } +} + +bool MayaHydraSceneProducer::IsHdSt() const +{ + if (enableMayaNativeSceneIndex()) + { + return _sceneIndex->IsHdSt(); + } + else + { + return _sceneDelegate->IsHdSt(); + } +} + +bool MayaHydraSceneProducer::GetPlaybackRunning() const +{ + if (enableMayaNativeSceneIndex()) + { + return false; + } + else + { + return _sceneDelegate->GetPlaybackRunning(); + } +} + +SdfPath MayaHydraSceneProducer::GetPrimPath(const MDagPath& dg, bool isSprim) +{ + if (enableMayaNativeSceneIndex()) + { + return _sceneIndex->GetPrimPath(dg, isSprim); + } + else + { + return _sceneDelegate->GetPrimPath(dg, isSprim); + } +} + +void MayaHydraSceneProducer::InsertRprim( + MayaHydraAdapter* adapter, + const TfToken& typeId, + const SdfPath& id, + const SdfPath& instancerId) +{ + if (enableMayaNativeSceneIndex()) + { + return _sceneIndex->InsertPrim(adapter, typeId, id); + } + else + { + return _sceneDelegate->InsertRprim(typeId, id, instancerId); + } +} + +void MayaHydraSceneProducer::RemoveRprim(const SdfPath& id) +{ + if (enableMayaNativeSceneIndex()) + { + _sceneIndex->RemovePrim(id); + } + else + { + _sceneDelegate->RemoveRprim(id); + } +} + +void MayaHydraSceneProducer::MarkRprimDirty(const SdfPath& id, HdDirtyBits dirtyBits) +{ + if (enableMayaNativeSceneIndex()) + { + _sceneIndex->MarkPrimDirty(id, dirtyBits); + } + else + { + _sceneDelegate->GetRenderIndex().GetChangeTracker().MarkRprimDirty(id, dirtyBits); + } +} + +void MayaHydraSceneProducer::InsertSprim( + MayaHydraAdapter* adapter, + const TfToken& typeId, + const SdfPath& id, + HdDirtyBits initialBits) +{ + if (enableMayaNativeSceneIndex()) + { + _sceneIndex->InsertPrim(adapter, typeId, id); + } + else + { + _sceneDelegate->InsertSprim(typeId, id, initialBits); + } +} + +void MayaHydraSceneProducer::RemoveSprim(const TfToken& typeId, const SdfPath& id) +{ + if (enableMayaNativeSceneIndex()) + { + _sceneIndex->RemovePrim(id); + } + else + { + _sceneDelegate->RemoveSprim(typeId, id); + } +} + +void MayaHydraSceneProducer::MarkSprimDirty(const SdfPath& id, HdDirtyBits dirtyBits) +{ + if (enableMayaNativeSceneIndex()) + { + _sceneIndex->MarkPrimDirty(id, dirtyBits); + } + else + { + _sceneDelegate->GetRenderIndex().GetChangeTracker().MarkSprimDirty(id, dirtyBits); + } +} + +SdfPath MayaHydraSceneProducer::GetDelegateID(TfToken name) const +{ + if (enableMayaNativeSceneIndex()) + { + return _sceneIndex->GetDelegateID(name); + } + else + { + for (auto& delegate : _delegates) { + if (delegate->GetName() == name) { + return delegate->GetMayaDelegateID(); + } + } + return SdfPath(); + } +} + +void MayaHydraSceneProducer::PreFrame(const MHWRender::MDrawContext& drawContext) +{ + if (enableMayaNativeSceneIndex()) + { + _sceneIndex->PreFrame(drawContext); + } + else + { + for (auto& it : _delegates) { + it->PreFrame(drawContext); + } + } +} + +void MayaHydraSceneProducer::PostFrame() +{ + if (enableMayaNativeSceneIndex()) + { + _sceneIndex->PostFrame(); + } + else + { + for (auto& it : _delegates) { + it->PostFrame(); + } + } +} + +void MayaHydraSceneProducer::RemoveAdapter(const SdfPath& id) +{ + if (enableMayaNativeSceneIndex()) + { + return _sceneIndex->RemoveAdapter(id); + } + else + { + return _sceneDelegate->RemoveAdapter(id); + } +} + +void MayaHydraSceneProducer::RecreateAdapterOnIdle(const SdfPath& id, const MObject& obj) +{ + if (enableMayaNativeSceneIndex()) + { + return _sceneIndex->RecreateAdapterOnIdle(id, obj); + } + else + { + return _sceneDelegate->RecreateAdapterOnIdle(id, obj); + } +} + +SdfPath MayaHydraSceneProducer::GetLightedPrimsRootPath() const +{ + if (enableMayaNativeSceneIndex()) + { + return _sceneIndex->GetLightedPrimsRootPath(); + } + else + { + return _sceneDelegate->GetLightedPrimsRootPath(); + } +} + +void MayaHydraSceneProducer::MaterialTagChanged(const SdfPath& id) +{ + if (enableMayaNativeSceneIndex()) + { + _sceneIndex->MaterialTagChanged(id); + } + else + { + _sceneDelegate->MaterialTagChanged(id); + } +} + +GfInterval MayaHydraSceneProducer::GetCurrentTimeSamplingInterval() const +{ + if (enableMayaNativeSceneIndex()) + { + return _sceneIndex->GetCurrentTimeSamplingInterval(); + } + else + { + return _sceneDelegate->GetCurrentTimeSamplingInterval(); + } +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/mayaHydra/hydraExtensions/mayaHydraSceneProducer.h b/lib/mayaHydra/hydraExtensions/mayaHydraSceneProducer.h new file mode 100644 index 0000000000..1eacf77273 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/mayaHydraSceneProducer.h @@ -0,0 +1,205 @@ +// +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +// 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 MAYAHYDRASCENEPRODUCER_H +#define MAYAHYDRASCENEPRODUCER_H + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace FVP_NS_DEF { +class RenderIndexProxy; +} + +PXR_NAMESPACE_OPEN_SCOPE + +class MayaHydraSceneDelegate; +class MayaHydraAdapter; +class MtohDefaultLightDelegate; + +/** + * \brief MayaHydraSceneProducer is used to produce the hydra scene from Maya native scene. + * Under the hood, the work is delegated to MayaHydraSceneIndex or MayaHydraSceneDelegate, depends on + * if MAYA_HYDRA_ENABLE_NATIVE_SCENE_INDEX is enabled or not. + * Note that MayaHydraSceneDelegate could be deprecated in the future. + */ +class MAYAHYDRALIB_API MayaHydraSceneProducer +{ +public: + MayaHydraSceneProducer( + Fvp::RenderIndexProxy& renderIndexProxy, + const SdfPath& id, + MayaHydraDelegate::InitData& initData, + bool lightEnabled); + ~MayaHydraSceneProducer(); + + // Propogate scene changes from Maya to Hydra + void HandleCompleteViewportScene(const MDataServerOperation::MViewportScene& scene, MFrameContext::DisplayStyle ds); + + // Populate primitives from Maya + void Populate(); + + // Add hydra pick points and items to Maya's selection list + bool AddPickHitToSelectionList( + const HdxPickHit& hit, + const MHWRender::MSelectionInfo& selectInfo, + MSelectionList& selectionList, + MPointArray& worldSpaceHitPts); + + // Insert a Rprim to hydra scene + void InsertRprim( + MayaHydraAdapter* adapter, + const TfToken& typeId, + const SdfPath& id, + const SdfPath& instancerId = {}); + + // Remove a Rprim from hydra scene + void RemoveRprim(const SdfPath& id); + + // Mark a Rprim in hydra scene as dirty + void MarkRprimDirty(const SdfPath& id, HdDirtyBits dirtyBits); + + // Insert a Sprim to hydra scene + void InsertSprim( + MayaHydraAdapter* adapter, + const TfToken& typeId, + const SdfPath& id, + HdDirtyBits initialBits); + + // Remove a Sprim from hydra scene + void RemoveSprim(const TfToken& typeId, const SdfPath& id); + + // Mark a Sprim in hydra scene as dirty + void MarkSprimDirty(const SdfPath& id, HdDirtyBits dirtyBits); + + // Operation that's performed on rendering a frame + void PreFrame(const MHWRender::MDrawContext& drawContext); + void PostFrame(); + + const MayaHydraParams& GetParams() const; + void SetParams(const MayaHydraParams& params); + + // Adapter operations + void RemoveAdapter(const SdfPath& id); + void RecreateAdapterOnIdle(const SdfPath& id, const MObject& obj); + + // Update viewport info to camera + SdfPath SetCameraViewport(const MDagPath& camPath, const GfVec4d& viewport); + + // Enable or disable lighting + void SetLightsEnabled(const bool enabled); + void SetDefaultLightEnabled(const bool enabled); + void SetDefaultLight(const GlfSimpleLight& light); + + bool IsHdSt() const; + + bool GetPlaybackRunning() const; + + SdfPath GetPrimPath(const MDagPath& dg, bool isSprim); + + HdRenderIndex& GetRenderIndex(); + + SdfPath GetLightedPrimsRootPath() const; + + GfInterval GetCurrentTimeSamplingInterval() const; + + // Return the id of underlying delegate by name (MayaHydraSceneIndex or MayaHydraSceneDelegate) + SdfPath GetDelegateID(TfToken name) const; + + MayaHydraSceneIndexRefPtr GetSceneIndex() const { return _sceneIndex; } + + void MaterialTagChanged(const SdfPath& id); + + // Common function to return templated sample types + template + size_t SampleValues(size_t maxSampleCount, float* times, T* samples, Getter getValue) + { + if (ARCH_UNLIKELY(maxSampleCount == 0)) { + return 0; + } + // Fast path 1 sample at current-frame + if (maxSampleCount == 1 + || (!GetParams().motionSamplesEnabled() && GetParams().motionSampleStart == 0)) { + times[0] = 0.0f; + samples[0] = getValue(); + return 1; + } + + const GfInterval shutter = GetCurrentTimeSamplingInterval(); + // Shutter for [-1, 1] (size 2) should have a step of 2 for 2 samples, and 1 for 3 samples + // For sample size of 1 tStep is unused and we match USD and to provide t=shutterOpen + // sample. + const double tStep = maxSampleCount > 1 ? (shutter.GetSize() / (maxSampleCount - 1)) : 0; + const MTime mayaTime = MAnimControl::currentTime(); + size_t nSamples = 0; + double relTime = shutter.GetMin(); + + for (size_t i = 0; i < maxSampleCount; ++i) { + T sample; + { + MDGContextGuard guard(mayaTime + relTime); + sample = getValue(); + } + // We compare the sample to the previous in order to reduce sample count on output. + // Goal is to reduce the amount of samples/keyframes the Hydra delegate has to absorb. + if (!nSamples || sample != samples[nSamples - 1]) { + samples[nSamples] = std::move(sample); + times[nSamples] = relTime; + ++nSamples; + } + relTime += tStep; + } + return nSamples; + } + +private: + + // + // Delegates, depends on if MAYA_HYDRA_ENABLE_NATIVE_SCENE_INDEX is enabled or not. + // + // SceneDelegate + std::shared_ptr _sceneDelegate; + std::vector _delegates; + std::unique_ptr _defaultLightDelegate; + + // SceneIndex + MayaHydraSceneIndexRefPtr _sceneIndex; + Fvp::RenderIndexProxy& _renderIndexProxy; +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // MAYAHYDRASCENEPRODUCER_H diff --git a/lib/mayaHydra/hydraExtensions/mayaUtils.cpp b/lib/mayaHydra/hydraExtensions/mayaUtils.cpp new file mode 100644 index 0000000000..d5f308e84b --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/mayaUtils.cpp @@ -0,0 +1,73 @@ +// +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +// 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 "mayaUtils.h" + +#include +#include +#include +#include +#include +#include + +namespace MAYAHYDRA_NS_DEF { + +MStatus GetDagPathFromNodeName(const MString& nodeName, MDagPath& outDagPath) +{ + MSelectionList selectionList; + MStatus status = selectionList.add(nodeName); + if (status) { + status = selectionList.getDagPath(0, outDagPath); + } + return status; +} + +MStatus GetMayaMatrixFromDagPath(const MDagPath& dagPath, MMatrix& outMatrix) +{ + MStatus status; + outMatrix = dagPath.inclusiveMatrix(&status); + return status; +} + +bool IsUfeItemFromMayaUsd(const MDagPath& dagPath, MStatus* returnStatus) +{ + static const MString ufeRuntimeAttributeName = "ufeRuntime"; + static const MString mayaUsdUfeRuntimeName = "USD"; + + MFnDagNode dagNode(dagPath); + MStatus ufePlugSearchStatus; + MPlug ufeRuntimePlug = dagNode.findPlug(ufeRuntimeAttributeName, false, &ufePlugSearchStatus); + if (returnStatus) { + *returnStatus = ufePlugSearchStatus; + } + return ufePlugSearchStatus && ufeRuntimePlug.asString() == mayaUsdUfeRuntimeName; +} + +bool IsUfeItemFromMayaUsd(const MObject& obj, MStatus* returnStatus) +{ + MDagPath dagPath; + MStatus dagPathSearchStatus = MDagPath::getAPathTo(obj, dagPath); + if (!dagPathSearchStatus) { + if (returnStatus) { + *returnStatus = dagPathSearchStatus; + } + return false; + } + + return IsUfeItemFromMayaUsd(dagPath, returnStatus); +} + +} // namespace MAYAHYDRA_NS_DEF diff --git a/lib/mayaHydra/hydraExtensions/mayaUtils.h b/lib/mayaHydra/hydraExtensions/mayaUtils.h new file mode 100644 index 0000000000..40a913e05a --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/mayaUtils.h @@ -0,0 +1,99 @@ +// +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +// 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 MAYAHYDRALIB_MAYA_UTILS_H +#define MAYAHYDRALIB_MAYA_UTILS_H + +#include +#include + +#include + +#include + +namespace MAYAHYDRA_NS_DEF { + +// Names of color tables for indexed colors +const std::string kActiveColorTableName = "active"; +const std::string kDormantColorTableName = "dormant"; + +// Color names +const std::string kLeadColorName = "lead"; +const std::string kPolymeshActiveColorName = "polymeshActive"; +const std::string kPolyVertexColorName = "polyVertex"; +const std::string kPolyEdgeColorName = "polyEdge"; +const std::string kPolyFaceColorName = "polyFace"; + +/** + * @brief Get the DAG path of a node from the Maya scene graph using its name + * + * @param[in] nodeName is the name of the node to get the DAG path of. + * @param[out] outDagPath is the DAG path of the node in the Maya scene graph. + * + * @return The resulting status of the operation. + */ +MAYAHYDRALIB_API +MStatus GetDagPathFromNodeName(const MString& nodeName, MDagPath& outDagPath); + +/** + * @brief Get the Maya transform matrix of a node from its DAG path + * + * The output transform matrix is the resultant ("flattened") matrix from it and + * its parents' transforms. + * + * @param[in] dagPath is the DAG path of the node in the Maya scene graph. + * @param[out] outMatrix is the Maya transform matrix of the node. + * + * @return The resulting status of the operation. + */ +MAYAHYDRALIB_API +MStatus GetMayaMatrixFromDagPath(const MDagPath& dagPath, MMatrix& outMatrix); + +/** + * @brief Determines whether a given DAG path points to a UFE item created by maya-usd + * + * UFE stands for Universal Front End : the goal of the Universal Front End is to create a + * DCC-agnostic component that will allow a DCC to browse and edit data in multiple data models. + * + * @param[in] dagPath is the DAG path of the node in the Maya scene graph. + * @param[out] returnStatus is an optional output variable to return whether the operation was + * successful. Default value is nullptr (not going to store the result status). + * + * @return True if the item pointed to by dagPath is a UFE item created by maya-usd, false + * otherwise. + */ +MAYAHYDRALIB_API +bool IsUfeItemFromMayaUsd(const MDagPath& dagPath, MStatus* returnStatus = nullptr); + +/** + * @brief Determines whether a given object is a UFE item created by maya-usd + * + * UFE stands for Universal Front End : the goal of the Universal Front End is to create a + * DCC-agnostic component that will allow a DCC to browse and edit data in multiple data models. + * + * @param[in] obj is the object representing the DAG node. + * @param[out] returnStatus is an optional output variable to return whether the operation was + * successful. Default value is nullptr (not going to store the result status). + * + * @return True if the item represented by obj is a UFE item created by maya-usd, false + * otherwise. + */ +MAYAHYDRALIB_API +bool IsUfeItemFromMayaUsd(const MObject& obj, MStatus* returnStatus = nullptr); + +} // namespace MAYAHYDRA_NS_DEF + +#endif // MAYAHYDRALIB_MAYA_UTILS_H diff --git a/lib/mayaHydra/hydraExtensions/mixedUtils.cpp b/lib/mayaHydra/hydraExtensions/mixedUtils.cpp new file mode 100644 index 0000000000..b796aaa46a --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/mixedUtils.cpp @@ -0,0 +1,175 @@ +// +// Copyright 2019 Luma Pictures +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +// 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 "mixedUtils.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +PXR_NAMESPACE_USING_DIRECTIVE + +namespace MAYAHYDRA_NS_DEF { + +TfToken GetFileTexturePath(const MFnDependencyNode& fileNode) +{ + if (fileNode.findPlug(MayaAttrs::file::uvTilingMode, true).asShort() != 0) { + const TfToken ret { + fileNode.findPlug(MayaAttrs::file::fileTextureNamePattern, true).asString().asChar() + }; + return ret.IsEmpty() + ? TfToken { fileNode.findPlug(MayaAttrs::file::computedFileTextureNamePattern, true) + .asString() + .asChar() } + : ret; + } else { + const TfToken ret { MRenderUtil::exactFileTextureName(fileNode.object()).asChar() }; + return ret.IsEmpty() ? TfToken { fileNode.findPlug(MayaAttrs::file::fileTextureName, true) + .asString() + .asChar() } + : ret; + } +} + +bool IsShape(const MDagPath& dagPath) +{ + if (dagPath.hasFn(MFn::kTransform)) { + return false; + } + + // go to the parent + MDagPath parentDagPath = dagPath; + parentDagPath.pop(); + if (!parentDagPath.hasFn(MFn::kTransform)) { + return false; + } + + unsigned int numberOfShapesDirectlyBelow = 0; + parentDagPath.numberOfShapesDirectlyBelow(numberOfShapesDirectlyBelow); + return (numberOfShapesDirectlyBelow == 1); +} + +SdfPath DagPathToSdfPath( + const MDagPath& dagPath, + const bool mergeTransformAndShape, + const bool stripNamespaces) +{ + std::string name = dagPath.fullPathName().asChar(); + SanitizeNameForSdfPath(name, stripNamespaces); + SdfPath usdPath(name); + + if (mergeTransformAndShape && IsShape(dagPath)) { + usdPath = usdPath.GetParentPath(); + } + + return usdPath; +} + +SdfPath RenderItemToSdfPath(const MRenderItem& ri, const bool stripNamespaces) +{ + std::string internalObjectId( + "_" + std::to_string(ri.InternalObjectId())); // preventively prepend item id by underscore + std::string name(ri.name().asChar() + internalObjectId); + // Try to sanitize maya path to be used as an sdf path. + SanitizeNameForSdfPath(name, stripNamespaces); + // Path names must start with a letter, not a number + // If a number is found, prepend the path with an underscore + char digit = name[0]; + if (std::isdigit(digit)) { + name.insert(0, "_"); + } + + SdfPath sdfPath(name); + if (!TF_VERIFY( + !sdfPath.IsEmpty(), + "Render item using invalid SdfPath '%s'. Using item's id instead.", + name.c_str())) { + // If failed to include render item's name as an SdfPath simply use the item id. + return SdfPath(internalObjectId); + } + return sdfPath; +} + +bool getRGBAColorPreferenceValue(const std::string& colorName, PXR_NS::GfVec4f& outColor) +{ + MDoubleArray rgbaColorValues; + bool wasCommandSuccessful = MGlobal::executeCommand( + MString("displayRGBColor -q -a ") + MString(colorName.c_str()), rgbaColorValues); + if (!wasCommandSuccessful || rgbaColorValues.length() != 4) { + return false; + } + outColor[0] = static_cast(rgbaColorValues[0]); + outColor[1] = static_cast(rgbaColorValues[1]); + outColor[2] = static_cast(rgbaColorValues[2]); + outColor[3] = static_cast(rgbaColorValues[3]); + return true; +} + +bool getIndexedColorPreferenceIndex( + const std::string& colorName, + const std::string& tableName, + size_t& outIndex) +{ + MIntArray indexInPalette; + std::string getIndexCommand = "displayColor -q -" + tableName + " " + colorName; + bool wasCommandSuccessful + = MGlobal::executeCommand(MString(getIndexCommand.c_str()), indexInPalette); + if (!wasCommandSuccessful || indexInPalette.length() != 1) { + return false; + } + outIndex = indexInPalette[0]; + return true; +} + +bool getColorPreferencesPaletteColor( + const std::string& tableName, + size_t index, + PXR_NS::GfVec4f& outColor) +{ + MDoubleArray rgbColorValues; + std::string getColorCommand = "colorIndex -q -" + tableName + " " + std::to_string(index); + bool wasCommandSuccessful + = MGlobal::executeCommand(MString(getColorCommand.c_str()), rgbColorValues); + if (!wasCommandSuccessful || rgbColorValues.length() != 3) { + return false; + } + outColor[0] = static_cast(rgbColorValues[0]); + outColor[1] = static_cast(rgbColorValues[1]); + outColor[2] = static_cast(rgbColorValues[2]); + outColor[3] = 1.0f; + return true; +} + +bool getIndexedColorPreferenceValue( + const std::string& colorName, + const std::string& tableName, + PXR_NS::GfVec4f& outColor) +{ + size_t colorIndex = 0; + if (getIndexedColorPreferenceIndex(colorName, tableName, colorIndex)) { + return getColorPreferencesPaletteColor(tableName, colorIndex, outColor); + } + return false; +} + +} // namespace MAYAHYDRA_NS_DEF diff --git a/lib/mayaHydra/hydraExtensions/mixedUtils.h b/lib/mayaHydra/hydraExtensions/mixedUtils.h new file mode 100644 index 0000000000..3da3ae577a --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/mixedUtils.h @@ -0,0 +1,178 @@ +// +// Copyright 2019 Luma Pictures +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +// 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 MAYAHYDRALIB_MIXED_UTILS_H +#define MAYAHYDRALIB_MIXED_UTILS_H + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +namespace MAYAHYDRA_NS_DEF { + +/** + * @brief Converts a Maya matrix to a double precision GfMatrix. + * + * @param[in] mayaMat Maya `MMatrix` to be converted. + * + * @return `GfMatrix4d` equal to \p mayaMat. + */ +inline pxr::GfMatrix4d GetGfMatrixFromMaya(const MMatrix& mayaMat) +{ + pxr::GfMatrix4d mat; + memcpy(mat.GetArray(), mayaMat[0], sizeof(double) * 16); + return mat; +} + +/** + * @brief Converts a Maya float matrix to a double precision GfMatrix. + * + * @param[in] mayaMat Maya `MFloatMatrix` to be converted. + * + * @return `GfMatrix4d` equal to \p mayaMat. + */ +inline pxr::GfMatrix4d GetGfMatrixFromMaya(const MFloatMatrix& mayaMat) +{ + pxr::GfMatrix4d mat; + for (unsigned i = 0; i < 4; ++i) { + for (unsigned j = 0; j < 4; ++j) + mat[i][j] = mayaMat(i, j); + } + return mat; +} + +/** + * @brief Returns the texture file path from a "file" shader node. + * + * @param[in] fileNode "file" shader node. + * + * @return Full path to the texture pointed used by the file node. `` tags are kept intact. + */ +MAYAHYDRALIB_API +pxr::TfToken GetFileTexturePath(const MFnDependencyNode& fileNode); + +/** + * @brief Determines whether or not a given DagPath refers to a shape. + * + * @param[in] dagPath is the DagPath to the potential shape. + * + * @return True if the dagPath refers to a shape, false otherwise. + */ +MAYAHYDRALIB_API +bool IsShape(const MDagPath& dagPath); + +/** + * @brief Converts the given Maya MDagPath \p dagPath into an SdfPath. + * + * Elements of the path will be sanitized such that it is a valid SdfPath. If \p mergeTransformAndShape + * is true and \p dagPath is a shape node, it will return the parent SdfPath of the shape's SdfPath, + * such that the transform and the shape have the same SdfPath. + * + * @param[in] dagPath is the DAG path to convert to an SdfPath. + * @param[in] mergeTransformAndShape determines whether or not to consider the transform and shape + * paths as one. + * @param[in] stripNamespaces determines whether or not to strip namespaces from the path. + * + * @return The SdfPath corresponding to the given DAG path. + */ +MAYAHYDRALIB_API +pxr::SdfPath DagPathToSdfPath( + const MDagPath& dagPath, + const bool mergeTransformAndShape, + const bool stripNamespaces); + +/** + * @brief Creates an SdfPath from the given Maya MRenderItem. + * + * Elements of the path will be sanitized such that it is a valid SdfPath. + * + * @param[in] ri is the MRenderItem to create the SdfPath for. + * @param[in] stripNamespaces determines whether or not to strip namespaces from the path. + * + * @return The SdfPath corresponding to the given MRenderItem. + */ +MAYAHYDRALIB_API +pxr::SdfPath RenderItemToSdfPath(const MRenderItem& ri, const bool stripNamespaces); + +/** + * @brief Retrieves an RGB color preference from Maya. + * + * @param[in] colorName is the color name in Maya to get the color value of. + * @param[out] outColor is the color that will be populated if retrieved from Maya. + * + * @return True if the color retrieval was successful and outColor was populated, false otherwise. + */ +MAYAHYDRALIB_API +bool getRGBAColorPreferenceValue(const std::string& colorName, PXR_NS::GfVec4f& outColor); + +/** + * @brief Retrieves an indexed color preference's index from Maya. + * + * @param[in] colorName is the color name in Maya to get the color index of. + * @param[in] tableName is to indicate for which table/palette we want to get the index. + * @param[out] outIndex is the index value that will be populated if retrieved from Maya. + * + * @return True if the color index retrieval was successful and outIndex was populated, false + * otherwise. + */ +MAYAHYDRALIB_API +bool getIndexedColorPreferenceIndex( + const std::string& colorName, + const std::string& tableName, + size_t& outIndex); + +/** + * @brief Retrieves a palette color from Maya's color settings. + * + * @param[in] tableName is to indicate which table/palette we want to get the color from. + * @param[in] index is to indicate which color to get from the palette. + * @param[out] outColor is the color that will be populated if retrieved from Maya. + * + * @return True if the color retrieval was successful and outColor was populated, false otherwise. + */ +MAYAHYDRALIB_API +bool getColorPreferencesPaletteColor( + const std::string& tableName, + size_t index, + PXR_NS::GfVec4f& outColor); + +/** + * @brief Retrieves an indexed/paletted color preference from Maya. + * + * @param[in] colorName is the color name in Maya to get the color value of. + * @param[in] tableName is to indicate which table/palette we want to get the color from. + * @param[out] outColor is the color that will be populated if retrieved from Maya. + * + * @return True if the color retrieval was successful and outColor was populated, false otherwise. + */ +MAYAHYDRALIB_API +bool getIndexedColorPreferenceValue( + const std::string& colorName, + const std::string& tableName, + PXR_NS::GfVec4f& outColor); + +} // namespace MAYAHYDRA_NS_DEF + +#endif // MAYAHYDRALIB_MIXED_UTILS_H diff --git a/lib/mayaHydra/hydraExtensions/plugInfo.json b/lib/mayaHydra/hydraExtensions/plugInfo.json new file mode 100644 index 0000000000..769e5efd2e --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/plugInfo.json @@ -0,0 +1,137 @@ +{ + "Plugins": [ + { + "Info": { + "Types": { + # Delegates + "MayaHydraDelegate": { + "displayName": "Base type for all scene delegates in Hydra for Maya." + }, + "MayaHydraSceneDelegate": { + "bases": [ + "MayaHydraDelegate" + ], + "displayName": "Maya Shapes in Hydra for Maya." + }, + "MayaHydraProxyDelegate": { + "bases": [ + "MayaHydraDelegate" + ], + "displayName": "Proxy Shapes in Hydra for Maya" + }, + # Adapters + "MayaHydraAdapter": { + "displayName": "Base type for all node adapters in Hydra for Maya." + }, + "MayaHydraRenderItemAdapter": { + "bases": [ + "MayaHydraAdapter" + ], + "displayName": "Render item in Hydra for Maya." + }, + "MayaHydraDagAdapter": { + "bases": [ + "MayaHydraAdapter" + ], + "displayName": "Dag nodes in Hydra for Maya." + }, + "MayaHydraLightAdapter": { + "bases": [ + "MayaHydraDagAdapter" + ], + "displayName": "Lights in Hydra for Maya." + }, + "MayaHydraAreaLightAdapter": { + "bases": [ + "MayaHydraLightAdapter" + ], + "displayName": "Area lights in Hydra for Maya." + }, + "MayaHydraPointLightAdapter": { + "bases": [ + "MayaHydraLightAdapter" + ], + "displayName": "Point lights in Hydra for Maya." + }, + "MayaHydraSpotLightAdapter": { + "bases": [ + "MayaHydraLightAdapter" + ], + "displayName": "Spot lights in Hydra for Maya." + }, + "MayaHydraDirectionalLightAdapter": { + "bases": [ + "MayaHydraLightAdapter" + ], + "displayName": "Directional lights in Hydra for Maya." + }, + "MayaHydraShapeAdapter": { + "bases": [ + "MayaHydraDagAdapter" + ], + "displayName": "Shapes in Hydra for Maya." + }, + "MayaHydraCameraAdapter": { + "bases": [ + "MayaHydraShapeAdapter" + ], + "displayName": "Cameras in Hydra for Maya." + }, + "MayaHydraMeshAdapter": { + "bases": [ + "MayaHydraShapeAdapter" + ], + "displayName": "Meshes in Hydra for Maya." + }, + "MayaHydraNurbsCurveAdapter": { + "bases": [ + "MayaHydraShapeAdapter" + ], + "displayName": "Nurbs Curves in Hydra for Maya." + }, + "MayaHydraImagePlaneAdapter": { + "bases": [ + "MayaHydraShapeAdapter" + ], + "displayName": "ImagePlanes in Hydra for Maya." + }, + "MayaHydraAiSkyDomeLightAdapter": { + "bases": [ + "MayaHydraLightAdapter" + ], + "displayName": "Ai SkyDome Light in Hydra for Maya." + }, + "MayaHydraProxyAdapter": { + "bases": [ + "MayaHydraDagAdapter" + ], + "displayName": "Proxy Shapes in Hydra for Maya." + }, + # Materials + "MayaHydraMaterialAdapter": { + "bases": [ + "MayaHydraAdapter" + ], + "displayName": "Base adapter for materials." + }, + "MayaHydraShadingEngineAdapter": { + "bases": [ + "MayaHydraMaterialAdapter" + ], + "displayName": "Adapter for the shading engine that translates everything to a Preview Surface." + }, + "MayaHydraImagePlaneMaterialAdapter": { + "bases": [ + "MayaHydraMaterialAdapter" + ], + "displayName": "Adapter for the image plane texture." + } + } + }, + "LibraryPath": "@PLUG_INFO_LIBRARY_PATH@", + "Name": "@TARGET_NAME@", + "Root": ".", + "Type": "library" + } + ] +} diff --git a/lib/mayaHydra/hydraExtensions/sceneIndex/CMakeLists.txt b/lib/mayaHydra/hydraExtensions/sceneIndex/CMakeLists.txt new file mode 100644 index 0000000000..541d1cc9af --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/sceneIndex/CMakeLists.txt @@ -0,0 +1,43 @@ +# ----------------------------------------------------------------------------- +# sources +# ----------------------------------------------------------------------------- +target_sources(${TARGET_NAME} + PRIVATE + registration.cpp + mayaHydraSceneIndex.cpp + mayaHydraDataSource.cpp + mayaHydraPrimvarDataSource.cpp + mayaHydraDisplayStyleDataSource.cpp + mayaHydraCameraDataSource.cpp + mayaHydraLightDataSource.cpp + mayaHydraDefaultLightDataSource.cpp +) + +set(HEADERS + registration.h + mayaHydraSceneIndex.h + mayaHydraDataSource.h + mayaHydraPrimvarDataSource.h + mayaHydraDisplayStyleDataSource.h + mayaHydraCameraDataSource.h + mayaHydraLightDataSource.h + mayaHydraDefaultLightDataSource.h +) + +# ----------------------------------------------------------------------------- +# promoted headers +# ----------------------------------------------------------------------------- +mayaUsd_promoteHeaderList( + HEADERS + ${HEADERS} + BASEDIR + ${TARGET_NAME}/sceneIndex +) + +# ----------------------------------------------------------------------------- +# install +# ----------------------------------------------------------------------------- +install(FILES ${HEADERS} + DESTINATION + ${CMAKE_INSTALL_PREFIX}/include/mayaHydraLib/sceneIndex +) diff --git a/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraCameraDataSource.cpp b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraCameraDataSource.cpp new file mode 100644 index 0000000000..e33c612b5b --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraCameraDataSource.cpp @@ -0,0 +1,229 @@ +// +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +// 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 "mayaHydraCameraDataSource.h" + +#include +#include + +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +// ---------------------------------------------------------------------------- + +template +class MayaHydraTypedCameraParamValueDataSource : public HdTypedSampledDataSource +{ +public: + HD_DECLARE_DATASOURCE(MayaHydraTypedCameraParamValueDataSource); + + MayaHydraTypedCameraParamValueDataSource( + const SdfPath& id, + const TfToken& key, + MayaHydraCameraAdapter* adapter) + : _id(id) + , _key(key) + , _adapter(adapter) + { + } + + bool GetContributingSampleTimesForInterval( + HdSampledDataSource::Time startTime, + HdSampledDataSource::Time endTime, + std::vector* outSampleTimes) override + { + return MayaHydraPrimvarValueDataSource::New( + _key, _adapter)->GetContributingSampleTimesForInterval( + startTime, endTime, outSampleTimes); + } + + T GetTypedValue(HdSampledDataSource::Time shutterOffset) override + { + VtValue v; + if (shutterOffset == 0.0f) { + v = _adapter->GetCameraParamValue(_key); + } + else { + v = MayaHydraPrimvarValueDataSource::New( + _key, _adapter)->GetValue(shutterOffset); + } + + if (v.IsHolding()) { + return v.UncheckedGet(); + } + + return T(); + } + + VtValue GetValue(HdSampledDataSource::Time shutterOffset) override + { + if (shutterOffset == 0.0f) { + return _adapter->GetCameraParamValue(_key); + } + + return VtValue(GetTypedValue(shutterOffset)); + } + +private: + SdfPath _id; + TfToken _key; + MayaHydraCameraAdapter* _adapter; +}; + + +class MayaHydraCameraParamValueDataSource : public HdSampledDataSource +{ +public: + HD_DECLARE_DATASOURCE(MayaHydraCameraParamValueDataSource); + + MayaHydraCameraParamValueDataSource( + const SdfPath& id, + const TfToken& key, + MayaHydraCameraAdapter* adapter) + : _id(id) + , _key(key) + , _adapter(adapter) + { + } + + bool GetContributingSampleTimesForInterval( + HdSampledDataSource::Time startTime, + HdSampledDataSource::Time endTime, + std::vector* outSampleTimes) override + { + return MayaHydraPrimvarValueDataSource::New( + _key, _adapter)->GetContributingSampleTimesForInterval( + startTime, endTime, outSampleTimes); + } + + VtValue GetValue(HdSampledDataSource::Time shutterOffset) override + { + if (shutterOffset == 0.0f) { + return _adapter->GetCameraParamValue(_key); + } + + return MayaHydraPrimvarValueDataSource::New( + _key, _adapter)->GetValue(shutterOffset); + } + +private: + SdfPath _id; + TfToken _key; + MayaHydraCameraAdapter* _adapter; +}; + +// ---------------------------------------------------------------------------- + +MayaHydraCameraDataSource::MayaHydraCameraDataSource( + const SdfPath& id, + TfToken type, + MayaHydraAdapter* adapter) + : _id(id) + , _type(type) + , _adapter(adapter) +{ +} + + +TfTokenVector +MayaHydraCameraDataSource::GetNames() +{ + TfTokenVector results; + + results.push_back(HdCameraSchemaTokens->projection); + results.push_back(HdCameraSchemaTokens->horizontalAperture); + results.push_back(HdCameraSchemaTokens->verticalAperture); + results.push_back(HdCameraSchemaTokens->horizontalApertureOffset); + results.push_back(HdCameraSchemaTokens->verticalApertureOffset); + results.push_back(HdCameraSchemaTokens->focalLength); + results.push_back(HdCameraSchemaTokens->clippingRange); + results.push_back(HdCameraSchemaTokens->clippingPlanes); + + return results; + +} + +HdDataSourceBaseHandle +MayaHydraCameraDataSource::Get(const TfToken& name) +{ + MayaHydraCameraAdapter* camAdapter = dynamic_cast(_adapter); + if (!camAdapter) { + return nullptr; + } + + if (name == HdCameraSchemaTokens->projection) { + VtValue v = camAdapter->GetCameraParamValue(name); + + HdCamera::Projection proj = HdCamera::Perspective; + if (v.IsHolding()) { + proj = v.UncheckedGet(); + } + return HdRetainedTypedSampledDataSource::New( + proj == HdCamera::Perspective ? + HdCameraSchemaTokens->perspective : + HdCameraSchemaTokens->orthographic); + } + else if (name == HdCameraSchemaTokens->clippingRange) { + VtValue v = camAdapter->GetCameraParamValue(name); + + GfRange1f range; + if (v.IsHolding()) { + range = v.UncheckedGet(); + } + return HdRetainedTypedSampledDataSource::New( + GfVec2f(range.GetMin(), range.GetMax())); + } + else if (name == HdCameraTokens->windowPolicy) { + VtValue v = camAdapter->GetCameraParamValue(name); + + CameraUtilConformWindowPolicy wp = CameraUtilDontConform; + if (v.IsHolding()) { + wp = v.UncheckedGet(); + } + return HdRetainedTypedSampledDataSource< + CameraUtilConformWindowPolicy>::New(wp); + } + else if (name == HdCameraSchemaTokens->clippingPlanes) { + const VtValue v = camAdapter->GetCameraParamValue(HdCameraTokens->clipPlanes); + VtArray array; + if (v.IsHolding>()) { + const std::vector& vec = + v.UncheckedGet>(); + array.resize(vec.size()); + for (size_t i = 0; i < vec.size(); i++) { + array[i] = vec[i]; + } + } + return HdRetainedTypedSampledDataSource>::New( + array); + } + else if (std::find(HdCameraSchemaTokens->allTokens.begin(), + HdCameraSchemaTokens->allTokens.end(), name) + != HdCameraSchemaTokens->allTokens.end()) { + // all remaining HdCameraSchema members are floats and should + // be returned as a typed data source for schema conformance. + return MayaHydraTypedCameraParamValueDataSource::New( + _id, name, camAdapter); + } + else { + return MayaHydraCameraParamValueDataSource::New( + _id, name, camAdapter); + } +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraCameraDataSource.h b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraCameraDataSource.h new file mode 100644 index 0000000000..0ea97eaae3 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraCameraDataSource.h @@ -0,0 +1,56 @@ +// +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +// 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 MAYAHYDRACAMERADATASOURCE_H +#define MAYAHYDRACAMERADATASOURCE_H + +#include +#include +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +class MayaHydraAdapter; +/** + * \brief A container data source representing data unique to camera + */ + class MayaHydraCameraDataSource : public HdContainerDataSource +{ +public: + HD_DECLARE_DATASOURCE(MayaHydraCameraDataSource); + + // ------------------------------------------------------------------------ + // HdContainerDataSource implementations + TfTokenVector GetNames() override; + HdDataSourceBaseHandle Get(const TfToken& name) override; + +private: + MayaHydraCameraDataSource( + const SdfPath& id, + TfToken type, + MayaHydraAdapter* adapter); + + SdfPath _id; + TfToken _type; + MayaHydraAdapter* _adapter = nullptr; +}; + +HD_DECLARE_DATASOURCE_HANDLES(MayaHydraCameraDataSource); + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // MAYAHYDRACAMERADATASOURCE_H diff --git a/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraDataSource.cpp b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraDataSource.cpp new file mode 100644 index 0000000000..f18b37291a --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraDataSource.cpp @@ -0,0 +1,424 @@ +// +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +// 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 "mayaHydraDataSource.h" + +#include +#include +#include +#include +#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 + + +MayaHydraDataSource::MayaHydraDataSource( + const SdfPath& id, + TfToken type, + MayaHydraSceneIndex* sceneIndex, + MayaHydraAdapter* adapter) + : _id(id) + , _type(type) + , _sceneIndex(sceneIndex) + , _adapter(adapter) +{ +} + +TfTokenVector +MayaHydraDataSource::GetNames() +{ + TfTokenVector result; + + if (_type == HdPrimTypeTokens->mesh) { + result.push_back(HdMeshSchemaTokens->mesh); + } + + if (_type == HdPrimTypeTokens->basisCurves) { + result.push_back(HdBasisCurvesSchemaTokens->basisCurves); + } + + result.push_back(HdPrimvarsSchemaTokens->primvars); + + if (HdPrimTypeIsGprim(_type)) { + result.push_back(HdMaterialBindingsSchema::GetSchemaToken()); + result.push_back(HdLegacyDisplayStyleSchemaTokens->displayStyle); + result.push_back(HdVisibilitySchemaTokens->visibility); + result.push_back(HdXformSchemaTokens->xform); + } + + if (HdPrimTypeIsLight(_type)) { + result.push_back(HdMaterialSchemaTokens->material); + result.push_back(HdXformSchemaTokens->xform); + result.push_back(HdLightSchemaTokens->light); + } + + if (_type == HdPrimTypeTokens->material) { + result.push_back(HdMaterialSchemaTokens->material); + } + + if (_type == HdPrimTypeTokens->camera) { + result.push_back(HdCameraSchemaTokens->camera); + result.push_back(HdXformSchemaTokens->xform); + } + + return result; +} + +HdDataSourceBaseHandle +MayaHydraDataSource::Get(const TfToken& name) +{ + if (name == HdMeshSchemaTokens->mesh) { + if (_type == HdPrimTypeTokens->mesh) { + auto topology = _adapter->GetMeshTopology(); + return HdMeshSchema::Builder() + .SetTopology( + HdMeshTopologySchema::Builder() + .SetFaceVertexCounts( + HdRetainedTypedSampledDataSource::New( + topology.GetFaceVertexCounts())) + .SetFaceVertexIndices( + HdRetainedTypedSampledDataSource::New( + topology.GetFaceVertexIndices())) + .SetOrientation( + HdRetainedTypedSampledDataSource::New( + HdMeshTopologySchemaTokens->rightHanded)) + .Build()) + .SetSubdivisionScheme( + HdRetainedTypedSampledDataSource::New(topology.GetScheme())) + .SetDoubleSided( + HdRetainedTypedSampledDataSource::New(_adapter->GetDoubleSided())) + .Build(); + } + } + else if (name == HdBasisCurvesSchemaTokens->basisCurves) { + if (_type == HdPrimTypeTokens->basisCurves) { + auto topology = _adapter->GetBasisCurvesTopology(); + return HdBasisCurvesSchema::Builder() + .SetTopology( + HdBasisCurvesTopologySchema::Builder() + .SetCurveVertexCounts( + HdRetainedTypedSampledDataSource::New( + topology.GetCurveVertexCounts())) + .SetCurveIndices( + HdRetainedTypedSampledDataSource::New( + topology.GetCurveIndices())) + .SetBasis( + HdRetainedTypedSampledDataSource::New( + topology.GetCurveBasis())) + .SetType( + HdRetainedTypedSampledDataSource::New( + topology.GetCurveType())) + .SetWrap( + HdRetainedTypedSampledDataSource::New( + topology.GetCurveWrap())) + .Build()) + .Build(); + } + } + else if (name == HdPrimvarsSchemaTokens->primvars) { + return _GetPrimvarsDataSource(); + } + else if (name == + HdMaterialBindingsSchema::GetSchemaToken() + ) { + return _GetMaterialBindingDataSource(); + } + else if (name == HdXformSchemaTokens->xform) { + auto xform = _adapter->GetTransform(); + return HdXformSchema::Builder() + .SetMatrix( + HdRetainedTypedSampledDataSource::New(xform)) + .Build(); + } + else if (name == HdMaterialSchemaTokens->material) { + return _GetMaterialDataSource(); + } + else if (name == HdLegacyDisplayStyleSchemaTokens->displayStyle) { + return MayaHydraDisplayStyleDataSource::New(_id, _type, _sceneIndex, _adapter); + } + else if (name == HdVisibilitySchemaTokens->visibility) { + return _GetVisibilityDataSource(); + } + else if (name == HdCameraSchemaTokens->camera) { + return MayaHydraCameraDataSource::New(_id, _type, _adapter); + } + else if (name == HdLightSchemaTokens->light) { + return MayaHydraLightDataSource::New(_id, _type, _adapter); + } + + return nullptr; +} + +HdDataSourceBaseHandle MayaHydraDataSource::_GetVisibilityDataSource() +{ + bool vis = _adapter->GetVisible(); + if (vis) { + static const HdContainerDataSourceHandle visOn = + HdVisibilitySchema::BuildRetained( + HdRetainedTypedSampledDataSource::New(true)); + return visOn; + } + else { + static const HdContainerDataSourceHandle visOff = + HdVisibilitySchema::BuildRetained( + HdRetainedTypedSampledDataSource::New(false)); + return visOff; + } +} + +HdDataSourceBaseHandle MayaHydraDataSource::_GetPrimvarsDataSource() +{ + if (_primvarsBuilt.load()) { + return HdContainerDataSource::AtomicLoad(_primvars); + } + + MayaHydraPrimvarsDataSourceHandle primvarsDs; + + for (size_t interpolation = HdInterpolationConstant; + interpolation < HdInterpolationCount; ++interpolation) { + + HdPrimvarDescriptorVector v = _adapter->GetPrimvarDescriptors((HdInterpolation)interpolation); + + TfToken interpolationToken = _InterpolationAsToken( + (HdInterpolation)interpolation); + + for (const auto& primvarDesc : v) { + if (!primvarsDs) { + primvarsDs = MayaHydraPrimvarsDataSource::New(_adapter); + } + primvarsDs->AddDesc( + primvarDesc.name, interpolationToken, primvarDesc.role, + primvarDesc.indexed); + } + } + + HdContainerDataSourceHandle ds = primvarsDs; + HdContainerDataSource::AtomicStore(_primvars, ds); + _primvarsBuilt.store(true); + + return primvarsDs; +} + +TfToken MayaHydraDataSource::_InterpolationAsToken(HdInterpolation interpolation) +{ + switch (interpolation) { + case HdInterpolationConstant: + return HdPrimvarSchemaTokens->constant; + case HdInterpolationUniform: + return HdPrimvarSchemaTokens->uniform; + case HdInterpolationVarying: + return HdPrimvarSchemaTokens->varying; + case HdInterpolationVertex: + return HdPrimvarSchemaTokens->vertex; + case HdInterpolationFaceVarying: + return HdPrimvarSchemaTokens->faceVarying; + case HdInterpolationInstance: + return HdPrimvarSchemaTokens->instance; + + default: + return HdPrimvarSchemaTokens->constant; + } +} + +HdDataSourceBaseHandle +MayaHydraDataSource::_GetMaterialBindingDataSource() +{ + const SdfPath path = _sceneIndex->GetMaterialId(_id); + if (path.IsEmpty()) { + return nullptr; + } + static const TfToken purposes[] = { + HdMaterialBindingsSchemaTokens->allPurpose + }; + HdDataSourceBaseHandle const materialBindingSources[] = { + HdMaterialBindingSchema::Builder() + .SetPath( + HdRetainedTypedSampledDataSource::New(path)) + .Build() + }; + + return + HdMaterialBindingsSchema::BuildRetained( + TfArraySize(purposes), purposes, materialBindingSources); +} + +static bool +_ConvertHdMaterialNetworkToHdDataSources( + const HdMaterialNetworkMap &hdNetworkMap, + HdContainerDataSourceHandle *result) +{ + HD_TRACE_FUNCTION(); + + TfTokenVector terminalsNames; + std::vector terminalsValues; + std::vector nodeNames; + std::vector nodeValues; + + for (auto const &iter: hdNetworkMap.map) { + const TfToken &terminalName = iter.first; + const HdMaterialNetwork &hdNetwork = iter.second; + + if (hdNetwork.nodes.empty()) { + continue; + } + + terminalsNames.push_back(terminalName); + + // Transfer over individual nodes. + // Note that the same nodes may be shared by multiple terminals. + // We simply overwrite them here. + for (const HdMaterialNode &node : hdNetwork.nodes) { + std::vector paramsNames; + std::vector paramsValues; + + for (const auto &p : node.parameters) { + paramsNames.push_back(p.first); + paramsValues.push_back( + HdRetainedTypedSampledDataSource::New(p.second) + ); + } + + // Accumulate array connections to the same input + TfDenseHashMap, TfToken::HashFunctor> + connectionsMap; + + TfSmallVector cNames; + TfSmallVector cValues; + + for (const HdMaterialRelationship &rel : hdNetwork.relationships) { + if (rel.outputId == node.path) { + TfToken outputPath = rel.inputId.GetToken(); + TfToken outputName = TfToken(rel.inputName.GetString()); + + HdDataSourceBaseHandle c = + HdMaterialConnectionSchema::BuildRetained( + HdRetainedTypedSampledDataSource::New( + outputPath), + HdRetainedTypedSampledDataSource::New( + outputName)); + + connectionsMap[ + TfToken(rel.outputName.GetString())].push_back(c); + } + } + + cNames.reserve(connectionsMap.size()); + cValues.reserve(connectionsMap.size()); + + // NOTE: not const because HdRetainedSmallVectorDataSource needs + // a non-const HdDataSourceBaseHandle* + for (auto &entryPair : connectionsMap) { + cNames.push_back(entryPair.first); + cValues.push_back( + HdRetainedSmallVectorDataSource::New( + entryPair.second.size(), entryPair.second.data())); + } + + nodeNames.push_back(node.path.GetToken()); + nodeValues.push_back( + HdMaterialNodeSchema::BuildRetained( + HdRetainedContainerDataSource::New( + paramsNames.size(), + paramsNames.data(), + paramsValues.data()), + HdRetainedContainerDataSource::New( + cNames.size(), + cNames.data(), + cValues.data()), + HdRetainedTypedSampledDataSource::New( + node.identifier), + nullptr /*renderContextNodeIdentifiers*/ + , nullptr /* nodeTypeInfo */ + )); + } + + terminalsValues.push_back( + HdMaterialConnectionSchema::BuildRetained( + HdRetainedTypedSampledDataSource::New( + hdNetwork.nodes.back().path.GetToken()), + HdRetainedTypedSampledDataSource::New( + terminalsNames.back())) + ); + } + + HdContainerDataSourceHandle nodesDefaultContext = + HdRetainedContainerDataSource::New( + nodeNames.size(), + nodeNames.data(), + nodeValues.data()); + + HdContainerDataSourceHandle terminalsDefaultContext = + HdRetainedContainerDataSource::New( + terminalsNames.size(), + terminalsNames.data(), + terminalsValues.data()); + + // Create the material network, potentially one per network selector + HdDataSourceBaseHandle network = HdMaterialNetworkSchema::BuildRetained( + nodesDefaultContext, + terminalsDefaultContext); + + TfToken defaultContext = HdMaterialSchemaTokens->universalRenderContext; + *result = HdMaterialSchema::BuildRetained( + 1, + &defaultContext, + &network); + + return true; +} + +HdDataSourceBaseHandle +MayaHydraDataSource::_GetMaterialDataSource() +{ + VtValue materialContainer = _sceneIndex->GetMaterialResource(_id); + + if (!materialContainer.IsHolding()) { + return nullptr; + } + + HdMaterialNetworkMap hdNetworkMap = + materialContainer.UncheckedGet(); + HdContainerDataSourceHandle materialDS = nullptr; + if (!_ConvertHdMaterialNetworkToHdDataSources( + hdNetworkMap, + &materialDS) ) { + return nullptr; + } + return materialDS; +} +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraDataSource.h b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraDataSource.h new file mode 100644 index 0000000000..8f6653f1d9 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraDataSource.h @@ -0,0 +1,69 @@ +// +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +// 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 MAYAHYDRADATASOURCE_H +#define MAYAHYDRADATASOURCE_H + +#include +#include +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +class MayaHydraAdapter; +class MayaHydraSceneIndex; + +/** + * \brief A container data source representing data unique to render item + */ + class MayaHydraDataSource : public HdContainerDataSource +{ +public: + HD_DECLARE_DATASOURCE(MayaHydraDataSource); + + // ------------------------------------------------------------------------ + // HdContainerDataSource implementations + TfTokenVector GetNames() override; + HdDataSourceBaseHandle Get(const TfToken& name) override; + +private: + MayaHydraDataSource( + const SdfPath& id, + TfToken type, + MayaHydraSceneIndex* sceneIndex, + MayaHydraAdapter* adapter); + + HdDataSourceBaseHandle _GetVisibilityDataSource(); + HdDataSourceBaseHandle _GetPrimvarsDataSource(); + TfToken _InterpolationAsToken(HdInterpolation interpolation); + HdDataSourceBaseHandle _GetMaterialBindingDataSource(); + HdDataSourceBaseHandle _GetMaterialDataSource(); +private: + SdfPath _id; + TfToken _type; + MayaHydraSceneIndex* _sceneIndex = nullptr; + MayaHydraAdapter* _adapter = nullptr; + + std::atomic_bool _primvarsBuilt{false}; + HdContainerDataSourceAtomicHandle _primvars; +}; + +HD_DECLARE_DATASOURCE_HANDLES(MayaHydraDataSource); + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // MAYAHYDRADATASOURCE_H diff --git a/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraDefaultLightDataSource.cpp b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraDefaultLightDataSource.cpp new file mode 100644 index 0000000000..09cfbe1fe9 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraDefaultLightDataSource.cpp @@ -0,0 +1,177 @@ +// +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +// 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 "mayaHydraDefaultLightDataSource.h" + +#include + +#include +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +// ---------------------------------------------------------------------------- + +class MayaHydraSimpleLightDataSource : public HdContainerDataSource +{ +public: + HD_DECLARE_DATASOURCE(MayaHydraSimpleLightDataSource); + + MayaHydraSimpleLightDataSource( + const SdfPath& id, + MayaHydraSceneIndex* sceneIndex) + : _id(id) + , _sceneIndex(sceneIndex) + { + } + + TfTokenVector GetNames() override + { + TfTokenVector result = { + HdTokens->filters, + HdTokens->lightLink, + HdTokens->shadowLink, + HdTokens->lightFilterLink, + HdTokens->isLight + }; + return result; + } + + HdDataSourceBaseHandle Get(const TfToken& name) override + { + VtValue v; + if (name == HdLightTokens->params) { + v = VtValue(_sceneIndex->GetDefaultLight()); + } + else if (name == HdTokens->transform) { + v = VtValue(GfMatrix4d( + 1.0)); // We don't use the transform but use the position param of the GlfsimpleLight + // Hydra might crash when this is an empty VtValue. + } + else if (name == HdLightTokens->shadowCollection) { + // Exclude lines/points primitives from casting shadows by only taking the primitives + // whose root path belongs to LightedPrimsRootPath + HdRprimCollection coll(HdTokens->geometry, HdReprSelector(HdReprTokens->refined)); + coll.SetRootPaths({ _sceneIndex->GetLightedPrimsRootPath() }); + v = VtValue(coll); + } + else if (name == HdLightTokens->shadowParams) { + HdxShadowParams shadowParams; + shadowParams.enabled = false; + v = VtValue(shadowParams); + } + else + { + v = _GetLightParamValue(name); + } + + // Wrap as data source + if (name == HdLightTokens->params) { + return HdRetainedSampledDataSource::New(v); + } + else if (name == HdLightTokens->shadowParams) { + return HdRetainedSampledDataSource::New(v); + } + else if (name == HdLightTokens->shadowCollection) { + return HdRetainedSampledDataSource::New(v); + } + else { + return HdCreateTypedRetainedDataSource(v); + } + } + + VtValue _GetLightParamValue(const TfToken& paramName) + { + if (paramName == HdLightTokens->color || paramName == HdTokens->displayColor) { + const auto diffuse = _sceneIndex->GetDefaultLight().GetDiffuse(); + return VtValue(GfVec3f(diffuse[0], diffuse[1], diffuse[2])); + } + else if (paramName == HdLightTokens->intensity) { + return VtValue(1.0f); + } + else if (paramName == HdLightTokens->diffuse) { + return VtValue(1.0f); + } + else if (paramName == HdLightTokens->specular) { + return VtValue(0.0f); + } + else if (paramName == HdLightTokens->exposure) { + return VtValue(0.0f); + } + else if (paramName == HdLightTokens->normalize) { + return VtValue(true); + } + else if (paramName == HdLightTokens->angle) { + return VtValue(0.0f); + } + else if (paramName == HdLightTokens->shadowEnable) { + return VtValue(false); + } + else if (paramName == HdLightTokens->shadowColor) { + return VtValue(GfVec3f(0.0f, 0.0f, 0.0f)); + } + else if (paramName == HdLightTokens->enableColorTemperature) { + return VtValue(false); + } + return {}; + } + +private: + SdfPath _id; + MayaHydraSceneIndex* _sceneIndex = nullptr; +}; + +// ---------------------------------------------------------------------------- + +MayaHydraDefaultLightDataSource::MayaHydraDefaultLightDataSource( + const SdfPath& id, + TfToken type, + MayaHydraSceneIndex* sceneIndex) + : _id(id) + , _type(type) + , _sceneIndex(sceneIndex) +{ +} + + +TfTokenVector +MayaHydraDefaultLightDataSource::GetNames() +{ + TfTokenVector result = { + HdXformSchemaTokens->xform, + HdLightSchemaTokens->light, + }; + return result; +} + +HdDataSourceBaseHandle +MayaHydraDefaultLightDataSource::Get(const TfToken& name) +{ + if (name == HdLightSchemaTokens->light) { + return MayaHydraSimpleLightDataSource::New(_id, _sceneIndex); + } + else if (name == HdXformSchemaTokens->xform) { + auto xform = GfMatrix4d(1.0); + return HdXformSchema::Builder() + .SetMatrix( + HdRetainedTypedSampledDataSource::New(xform)) + .Build(); + } + return nullptr; +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraDefaultLightDataSource.h b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraDefaultLightDataSource.h new file mode 100644 index 0000000000..fc89b5d4a3 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraDefaultLightDataSource.h @@ -0,0 +1,62 @@ +// +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +// 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 MAYAHYDRADEFAULTLIGHTDATASOURCE_H +#define MAYAHYDRADEFAULTLIGHTDATASOURCE_H + +#include +#include +#include +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +class MayaHydraSceneIndex; + +/** + * \brief A container data source representing data unique to light + */ + class MayaHydraDefaultLightDataSource : public HdContainerDataSource +{ +public: + HD_DECLARE_DATASOURCE(MayaHydraDefaultLightDataSource); + + // ------------------------------------------------------------------------ + // HdContainerDataSource implementations + TfTokenVector GetNames() override; + HdDataSourceBaseHandle Get(const TfToken& name) override; + +private: + MayaHydraDefaultLightDataSource( + const SdfPath& id, + TfToken type, + MayaHydraSceneIndex* sceneIndex); + + VtValue _GetLightParamValue(const TfToken& paramName); + + SdfPath _id; + TfToken _type; + + const GlfSimpleLight* _light = nullptr; + MayaHydraSceneIndex* _sceneIndex = nullptr; +}; + +HD_DECLARE_DATASOURCE_HANDLES(MayaHydraDefaultLightDataSource); + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // MAYAHYDRADEFAULTLIGHTDATASOURCE_H diff --git a/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraDisplayStyleDataSource.cpp b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraDisplayStyleDataSource.cpp new file mode 100644 index 0000000000..dead0b832d --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraDisplayStyleDataSource.cpp @@ -0,0 +1,184 @@ +// +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +// 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 "mayaHydraDisplayStyleDataSource.h" + +#include +#include + +#include "pxr/imaging/hd/legacyDisplayStyleSchema.h" +#include + +PXR_NAMESPACE_OPEN_SCOPE + +MayaHydraDisplayStyleDataSource::MayaHydraDisplayStyleDataSource( + const SdfPath& id, + TfToken type, + MayaHydraSceneIndex* sceneIndex, + MayaHydraAdapter* adapter) + : _id(id) + , _type(type) + , _sceneIndex(sceneIndex) + , _adapter(adapter) + , _displayStyleRead(false) +{ +} + + +TfTokenVector +MayaHydraDisplayStyleDataSource::GetNames() +{ + TfTokenVector results; + results.push_back(HdLegacyDisplayStyleSchemaTokens->refineLevel); + results.push_back(HdLegacyDisplayStyleSchemaTokens->flatShadingEnabled); + results.push_back(HdLegacyDisplayStyleSchemaTokens->displacementEnabled); + results.push_back(HdLegacyDisplayStyleSchemaTokens->occludedSelectionShowsThrough); + results.push_back(HdLegacyDisplayStyleSchemaTokens->pointsShadingEnabled); + results.push_back(HdLegacyDisplayStyleSchemaTokens->materialIsFinal); + results.push_back(HdLegacyDisplayStyleSchemaTokens->shadingStyle); + results.push_back(HdLegacyDisplayStyleSchemaTokens->reprSelector); + results.push_back(HdLegacyDisplayStyleSchemaTokens->cullStyle); + return results; +} + +HdDataSourceBaseHandle +MayaHydraDisplayStyleDataSource::Get(const TfToken& name) +{ + if (name == HdLegacyDisplayStyleSchemaTokens->refineLevel) { + if (!_displayStyleRead) { + _displayStyle = _adapter->GetDisplayStyle(); + _displayStyleRead = true; + } + return (_displayStyle.refineLevel != 0) + ? HdRetainedTypedSampledDataSource::New( + _displayStyle.refineLevel) + : nullptr; + } + else if (name == HdLegacyDisplayStyleSchemaTokens->flatShadingEnabled) { + if (!_displayStyleRead) { + _displayStyle = _adapter->GetDisplayStyle(); + _displayStyleRead = true; + } + return HdRetainedTypedSampledDataSource::New( + _displayStyle.flatShadingEnabled); + } + else if (name == HdLegacyDisplayStyleSchemaTokens->displacementEnabled) { + if (!_displayStyleRead) { + _displayStyle = _adapter->GetDisplayStyle(); + _displayStyleRead = true; + } + return HdRetainedTypedSampledDataSource::New( + _displayStyle.displacementEnabled); + } + else if (name == HdLegacyDisplayStyleSchemaTokens->occludedSelectionShowsThrough) { + if (!_displayStyleRead) { + _displayStyle = _adapter->GetDisplayStyle(); + _displayStyleRead = true; + } + return HdRetainedTypedSampledDataSource::New( + _displayStyle.occludedSelectionShowsThrough); + } + else if (name == HdLegacyDisplayStyleSchemaTokens->pointsShadingEnabled) { + if (!_displayStyleRead) { + _displayStyle = _adapter->GetDisplayStyle(); + _displayStyleRead = true; + } + return HdRetainedTypedSampledDataSource::New( + _displayStyle.pointsShadingEnabled); + } + else if (name == HdLegacyDisplayStyleSchemaTokens->materialIsFinal) { + if (!_displayStyleRead) { + _displayStyle = _adapter->GetDisplayStyle(); + _displayStyleRead = true; + } + return HdRetainedTypedSampledDataSource::New( + _displayStyle.materialIsFinal); + } + else if (name == HdLegacyDisplayStyleSchemaTokens->shadingStyle) { + TfToken shadingStyle = _sceneIndex->GetShadingStyle(_id) + .GetWithDefault(); + if (shadingStyle.IsEmpty()) { + return nullptr; + } + return HdRetainedTypedSampledDataSource::New(shadingStyle); + } + else if (name == HdLegacyDisplayStyleSchemaTokens->reprSelector) { + HdReprSelector repr; + // TODO: Get the reprSelector + //HdSceneIndexPrim prim = _sceneIndex->GetPrim(_id); + //if (HdLegacyDisplayStyleSchema styleSchema = + // HdLegacyDisplayStyleSchema::GetFromParent(prim.dataSource)) { + + // if (HdTokenArrayDataSourceHandle ds = + // styleSchema.GetReprSelector()) { + // VtArray ar = ds->GetTypedValue(0.0f); + // ar.resize(HdReprSelector::MAX_TOPOLOGY_REPRS); + // repr = HdReprSelector(ar[0], ar[1], ar[2]); + // } + //} + HdTokenArrayDataSourceHandle reprSelectorDs = nullptr; + bool empty = true; + for (size_t i = 0; i < HdReprSelector::MAX_TOPOLOGY_REPRS; ++i) { + if (!repr[i].IsEmpty()) { + empty = false; + break; + } + } + if (!empty) { + VtArray array(HdReprSelector::MAX_TOPOLOGY_REPRS); + for (size_t i = 0; i < HdReprSelector::MAX_TOPOLOGY_REPRS; ++i) { + array[i] = repr[i]; + } + reprSelectorDs = + HdRetainedTypedSampledDataSource>::New( + array); + } + return reprSelectorDs; + } + else if (name == HdLegacyDisplayStyleSchemaTokens->cullStyle) { + HdCullStyle cullStyle = _adapter->GetCullStyle(); + if (cullStyle == HdCullStyleDontCare) { + return nullptr; + } + TfToken cullStyleToken; + switch (cullStyle) { + case HdCullStyleNothing: + cullStyleToken = HdCullStyleTokens->nothing; + break; + case HdCullStyleBack: + cullStyleToken = HdCullStyleTokens->back; + break; + case HdCullStyleFront: + cullStyleToken = HdCullStyleTokens->front; + break; + case HdCullStyleBackUnlessDoubleSided: + cullStyleToken = HdCullStyleTokens->backUnlessDoubleSided; + break; + case HdCullStyleFrontUnlessDoubleSided: + cullStyleToken = HdCullStyleTokens->frontUnlessDoubleSided; + break; + default: + cullStyleToken = HdCullStyleTokens->dontCare; + break; + } + return HdRetainedTypedSampledDataSource::New(cullStyleToken); + } + else { + return nullptr; + } +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraDisplayStyleDataSource.h b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraDisplayStyleDataSource.h new file mode 100644 index 0000000000..e0104418fd --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraDisplayStyleDataSource.h @@ -0,0 +1,64 @@ +// +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +// 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 MAYAHYDRADISPLAYSTYLEDATASOURCE_H +#define MAYAHYDRADISPLAYSTYLEDATASOURCE_H + +#include +#include +#include +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +class MayaHydraAdapter; +class MayaHydraSceneIndex; + +/** + * \brief A container data source representing data unique to display style + */ + class MayaHydraDisplayStyleDataSource : public HdContainerDataSource +{ +public: + HD_DECLARE_DATASOURCE(MayaHydraDisplayStyleDataSource); + + // ------------------------------------------------------------------------ + // HdContainerDataSource implementations + TfTokenVector GetNames() override; + HdDataSourceBaseHandle Get(const TfToken& name) override; + +private: + MayaHydraDisplayStyleDataSource( + const SdfPath& id, + TfToken type, + MayaHydraSceneIndex* sceneIndex, + MayaHydraAdapter* adapter); + + SdfPath _id; + TfToken _type; + MayaHydraSceneIndex* _sceneIndex = nullptr; + MayaHydraAdapter* _adapter = nullptr; + + HdDisplayStyle _displayStyle; + bool _displayStyleRead; +}; + +HD_DECLARE_DATASOURCE_HANDLES(MayaHydraDisplayStyleDataSource); + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // MAYAHYDRADISPLAYSTYLEDATASOURCE_H diff --git a/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraLightDataSource.cpp b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraLightDataSource.cpp new file mode 100644 index 0000000000..f2d1f812d7 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraLightDataSource.cpp @@ -0,0 +1,89 @@ +// +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +// 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 "mayaHydraLightDataSource.h" + +#include + +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +MayaHydraLightDataSource::MayaHydraLightDataSource( + const SdfPath& id, + TfToken type, + MayaHydraAdapter* adapter) + : _id(id) + , _type(type) + , _adapter(adapter) +{ +} + + +TfTokenVector +MayaHydraLightDataSource::GetNames() +{ + TfTokenVector result = { + HdTokens->filters, + HdTokens->lightLink, + HdTokens->shadowLink, + HdTokens->lightFilterLink, + HdTokens->isLight, + }; + return result; +} + +HdDataSourceBaseHandle +MayaHydraLightDataSource::Get(const TfToken& name) +{ + VtValue v; + if (_UseGet(name)) { + v = _adapter->Get(name); + } + else { + MayaHydraLightAdapter* lightAdapter = dynamic_cast(_adapter); + if (!lightAdapter) { + return nullptr; + } + v = lightAdapter->GetLightParamValue(name); + } + + if (name == HdLightTokens->params) { + return HdRetainedSampledDataSource::New(v); + } + else if (name == HdLightTokens->shadowParams) { + return HdRetainedSampledDataSource::New(v); + } + else if (name == HdLightTokens->shadowCollection) { + return HdRetainedSampledDataSource::New(v); + } + else { + return HdCreateTypedRetainedDataSource(v); + } +} + +bool MayaHydraLightDataSource::_UseGet(const TfToken& name) const { + if (name == HdLightTokens->params || + name == HdLightTokens->shadowParams || + name == HdLightTokens->shadowCollection) { + return true; + } + + return false; +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraLightDataSource.h b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraLightDataSource.h new file mode 100644 index 0000000000..30052db8b0 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraLightDataSource.h @@ -0,0 +1,58 @@ +// +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +// 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 MAYAHYDRALIGHTDATASOURCE_H +#define MAYAHYDRALIGHTDATASOURCE_H + +#include +#include +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +class MayaHydraAdapter; +/** + * \brief A container data source representing data unique to light + */ + class MayaHydraLightDataSource : public HdContainerDataSource +{ +public: + HD_DECLARE_DATASOURCE(MayaHydraLightDataSource); + + // ------------------------------------------------------------------------ + // HdContainerDataSource implementations + TfTokenVector GetNames() override; + HdDataSourceBaseHandle Get(const TfToken& name) override; + +private: + MayaHydraLightDataSource( + const SdfPath& id, + TfToken type, + MayaHydraAdapter* adapter); + + bool _UseGet(const TfToken& name) const; + + SdfPath _id; + TfToken _type; + MayaHydraAdapter* _adapter = nullptr; +}; + +HD_DECLARE_DATASOURCE_HANDLES(MayaHydraLightDataSource); + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // MAYAHYDRALIGHTDATASOURCE_H diff --git a/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraPrimvarDataSource.cpp b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraPrimvarDataSource.cpp new file mode 100644 index 0000000000..d35e5fb64f --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraPrimvarDataSource.cpp @@ -0,0 +1,90 @@ +// +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +// 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 "mayaHydraPrimvarDataSource.h" + +#include + +#include +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +MayaHydraPrimvarsDataSource::MayaHydraPrimvarsDataSource( + MayaHydraAdapter* adapter) + : _adapter(adapter) +{ +} + +void MayaHydraPrimvarsDataSource::AddDesc( + const TfToken& name, + const TfToken& interpolation, + const TfToken& role, bool indexed) +{ + _entries[name] = { interpolation, role, indexed }; +} + +TfTokenVector MayaHydraPrimvarsDataSource::GetNames() +{ + TfTokenVector result; + result.reserve(_entries.size()); + for (const auto& pair : _entries) { + result.push_back(pair.first); + } + return result; +} + +HdDataSourceBaseHandle MayaHydraPrimvarsDataSource::Get(const TfToken& name) +{ + _EntryMap::const_iterator it = _entries.find(name); + if (it == _entries.end()) { + return nullptr; + } + + // Need to handle indexed case? + assert(!(*it).second.indexed); + return HdPrimvarSchema::Builder() + .SetPrimvarValue(MayaHydraPrimvarValueDataSource::New( + name, _adapter)) + .SetInterpolation(HdPrimvarSchema::BuildInterpolationDataSource( + (*it).second.interpolation)) + .SetRole(HdPrimvarSchema::BuildRoleDataSource( + (*it).second.role)) + .Build(); +} + +MayaHydraPrimvarValueDataSource::MayaHydraPrimvarValueDataSource( + const TfToken& primvarName, + MayaHydraAdapter* adapter) + : _primvarName(primvarName) + , _adapter(adapter) +{ +} + +VtValue MayaHydraPrimvarValueDataSource::GetValue(Time shutterOffset) +{ + return _adapter->Get(_primvarName); +} + +bool MayaHydraPrimvarValueDataSource::GetContributingSampleTimesForInterval( + Time startTime, Time endTime, + std::vector